mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-10 07:37:25 -05:00
Compare commits
92 Commits
v0.4.0-alp
...
v0.4.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e37f1e687 | ||
|
|
5e8f2d0523 | ||
|
|
9fb6c33319 | ||
|
|
fdecac4230 | ||
|
|
85c27a6cca | ||
|
|
57f56d31a2 | ||
|
|
df25675d3f | ||
|
|
277a84702a | ||
|
|
c651296378 | ||
|
|
866d25e86a | ||
|
|
5e62f1b889 | ||
|
|
e9d524ad14 | ||
|
|
158c8b4b8a | ||
|
|
41df207bb9 | ||
|
|
692ffabe82 | ||
|
|
944ad5a571 | ||
|
|
5ca70d3940 | ||
|
|
904752e3dd | ||
|
|
cf61798466 | ||
|
|
fd72f459f9 | ||
|
|
1bc462b300 | ||
|
|
1c6f91f101 | ||
|
|
a7c28f066a | ||
|
|
604c8ab6e5 | ||
|
|
23a3ee39cc | ||
|
|
fa16db129b | ||
|
|
de249c448b | ||
|
|
e1bc56c500 | ||
|
|
d1d115d11f | ||
|
|
aca41b4f6e | ||
|
|
1e3ae996ac | ||
|
|
b3b2e74688 | ||
|
|
44237bec5c | ||
|
|
53b44f44e3 | ||
|
|
9af7a184ad | ||
|
|
9c8d87ec64 | ||
|
|
4c34968d28 | ||
|
|
2d71f5173a | ||
|
|
2e942113c6 | ||
|
|
e20540ccc6 | ||
|
|
8d7e0e9f46 | ||
|
|
cc5d94f58d | ||
|
|
26197e433c | ||
|
|
f54b4dfd0e | ||
|
|
085ba52294 | ||
|
|
b8c24680a5 | ||
|
|
c658582157 | ||
|
|
83e518d503 | ||
|
|
2163d0f822 | ||
|
|
34c920eede | ||
|
|
35b0b5ef9b | ||
|
|
08672c33bd | ||
|
|
e2651d6c7c | ||
|
|
851a4e12d2 | ||
|
|
b7c66ec844 | ||
|
|
7522a3a820 | ||
|
|
6a0402d1cf | ||
|
|
5309af84f7 | ||
|
|
8d8d77fa11 | ||
|
|
284cf07e97 | ||
|
|
16414c7585 | ||
|
|
70c9eed0bf | ||
|
|
f49b1fef17 | ||
|
|
3fe070987a | ||
|
|
00d5610213 | ||
|
|
d3c1a605df | ||
|
|
85df151a96 | ||
|
|
8915797aac | ||
|
|
03a5d71a95 | ||
|
|
4158d4ac10 | ||
|
|
f934676bd0 | ||
|
|
d5b802e794 | ||
|
|
139227e46d | ||
|
|
2394d51a51 | ||
|
|
d9fa2bea5b | ||
|
|
0cb360eeed | ||
|
|
ea472f873f | ||
|
|
8cbdbd527e | ||
|
|
ff0d893bd9 | ||
|
|
7b924d0486 | ||
|
|
712eb07f4b | ||
|
|
d5c3ecc7df | ||
|
|
c4bf089667 | ||
|
|
378b03e06e | ||
|
|
34235382fb | ||
|
|
c05aff0768 | ||
|
|
f0c3e0fd1a | ||
|
|
d601cbda0e | ||
|
|
2653dc6b38 | ||
|
|
48cbbfc656 | ||
|
|
d6892b9203 | ||
|
|
59f0844fcc |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -17,7 +17,7 @@ out/
|
||||
*.class
|
||||
*.log
|
||||
# Package Files #
|
||||
*.jar
|
||||
#*.jar
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
|
||||
9
.idea/compiler.xml
generated
9
.idea/compiler.xml
generated
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel>
|
||||
<module name="sdr-trunk_main" target="11" />
|
||||
<module name="sdr-trunk_test" target="11" />
|
||||
</bytecodeTargetLevel>
|
||||
</component>
|
||||
</project>
|
||||
14
.idea/libraries/imports.xml
generated
14
.idea/libraries/imports.xml
generated
@@ -1,14 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="imports">
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/imports/jmbe-interface-0.3.3.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/imports/java-lame-3.98.4.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/imports/radioreference-14.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/imports/wsdl4j-1.5.1.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/imports/commons-math3-3.6.1.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/imports/libresample4j-0.1.3.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
3
.idea/misc.xml
generated
3
.idea/misc.xml
generated
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="false" project-jdk-name="11" project-jdk-type="JavaSDK">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_X" default="true" project-jdk-name="14" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
10
.idea/modules.xml
generated
10
.idea/modules.xml
generated
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/sdr-trunk.iml" filepath="$PROJECT_DIR$/.idea/modules/sdr-trunk.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/sdr-trunk_main.iml" filepath="$PROJECT_DIR$/.idea/modules/sdr-trunk_main.iml" group="sdr-trunk" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/sdr-trunk_test.iml" filepath="$PROJECT_DIR$/.idea/modules/sdr-trunk_test.iml" group="sdr-trunk" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
12
.idea/vcs.xml
generated
12
.idea/vcs.xml
generated
@@ -1,16 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="IssueNavigationConfiguration">
|
||||
<option name="links">
|
||||
<list>
|
||||
<IssueNavigationLink>
|
||||
<option name="issueRegexp" value="#(\d+)" />
|
||||
<option name="linkRegexp" value="https://github.com/DSheirer/sdrtrunk/issues/$1" />
|
||||
</IssueNavigationLink>
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
15
CHANGELOG
15
CHANGELOG
@@ -1,3 +1,18 @@
|
||||
0.4.0 20200404
|
||||
- Release includes embedded Java 14 & JavaFX 13 runtime
|
||||
- Adds Polyphase Channelizer with 2x channel oversampling and redesigned source management
|
||||
- Adds P25 Phase 2 decoder
|
||||
- Adds P25 radio identifier aliasing
|
||||
- JMBE library 1.0.4 with support for AMBE tone metadata
|
||||
- New JavaFX User Preferences dialog
|
||||
- HackRF tuner enhanced (sample rates and saturation issues)
|
||||
- Adds support for Airspy mini tuner
|
||||
- Redesigned audio sub-system that uses AudioSegments
|
||||
- USB tuner bus saturation management/monitoring
|
||||
- Bitstream (.bits) recording
|
||||
- MP3 Audio Recording
|
||||
- Numerous bug fixes
|
||||
|
||||
0.3.4 20180418
|
||||
- New P25 C4FM decoder that uses Differential QPSK decoder instead of FM demodulation with 4FSK.
|
||||
- Adds channel auto-start and ordering capability with startup dialog allowing user to invoke or bypass channel
|
||||
|
||||
68
README.md
68
README.md
@@ -2,9 +2,10 @@
|
||||
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](https://github.com/DSheirer/sdrtrunk/wiki/UserManual_V0.3.0)
|
||||
* [User's Manual Version 0.3.0 and 0.4.0](https://github.com/DSheirer/sdrtrunk/wiki/UserManual_V0.3.0)
|
||||
* [Download](https://github.com/DSheirer/sdrtrunk/releases)
|
||||
* [Support](https://groups.google.com/forum/#!forum/sdrtrunk)
|
||||
* [Support Group](https://groups.google.com/forum/#!forum/sdrtrunk)
|
||||
* [Discord Support Channel](https://discord.gg/HJQaKYE)
|
||||
|
||||

|
||||
**Figure 1:** sdrtrunk **Version 0.3.0** Application Screenshot
|
||||
@@ -13,68 +14,53 @@ A cross-platform java application for decoding, monitoring, recording and stream
|
||||
|
||||
If you simply want to download and run the program, please follow these instructions.
|
||||
|
||||
## Install Java 8 (or newer)
|
||||
|
||||
## Download the latest sdrtrunk release
|
||||
## Download the latest sdrtrunk release for your operating system
|
||||
|
||||
All release versions of sdrtrunk are available from the [releases](https://github.com/DSheirer/sdrtrunk/releases) tab.
|
||||
|
||||
**(final)** These versions have been tested and are the current release version.
|
||||
* **(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.
|
||||
|
||||
**(alpha)** These versions are under development feature previews and likely to contain bugs and unexpected behavior.
|
||||
## Unzip the release
|
||||
|
||||
**(beta)** These versions are currently being tested for bugs and functionality.
|
||||
Use 7-zip or any zip utility to unzip the release file
|
||||
|
||||
## Run the application
|
||||
## Start the application
|
||||
|
||||
Either double-click on the downloaded file (if supported on your operating system) or open a terminal/command window
|
||||
and change to the directory where you downloaded the release file and type:
|
||||
|
||||
```
|
||||
java -jar downloaded-jar-filename
|
||||
```
|
||||
|
||||
Note: replace _downloaded-jar-filename_ with the actual name of the sdrtrunk release version that you downloaded
|
||||
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. Copy the resulting JMBE audio library
|
||||
jar file to the same folder containing the sdrtrunk application to use the library with sdrtrunk.
|
||||
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 gradle as the build system. You can build it with locally installed gradle or if you do not have/want
|
||||
to install gradle you can use preconfigured linux/windows wrapper.
|
||||
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:
|
||||
|
||||
### Build with locally installed gradle 4.3.1
|
||||
### Linux
|
||||
```
|
||||
gradle clean buildSdr
|
||||
./gradlew clean build
|
||||
```
|
||||
### Build with preconfigured wrapper for linux/windows
|
||||
Linux
|
||||
### Windows
|
||||
```
|
||||
./gradlew clean buildSdr
|
||||
gradlew.bat clean build
|
||||
```
|
||||
Windows
|
||||
```
|
||||
gradlew.bat clean buildSdr
|
||||
```
|
||||
This would add gradle, download all project dependencies and build JAR
|
||||
|
||||
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.3.2'
|
||||
```
|
||||
|
||||
## Run SDRTrunk
|
||||
SDRTrunk is packed into single uber-jar file. There is need to have folders with dependencies and
|
||||
adding them with classpath. Just run with java.
|
||||
### Use Java8
|
||||
```
|
||||
java -jar build/libs/sdr-trunk-all-0.3.2.jar
|
||||
version = '0.4.0'
|
||||
```
|
||||
|
||||
259
build.gradle
259
build.gradle
@@ -1,46 +1,40 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'application'
|
||||
id 'java'
|
||||
id 'idea'
|
||||
id 'org.openjfx.javafxplugin' version '0.0.6'
|
||||
id 'org.beryx.runtime' version '1.1.1'
|
||||
id 'org.openjfx.javafxplugin' version '0.0.8'
|
||||
id 'org.beryx.runtime' version '1.8.0'
|
||||
}
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
|
||||
version = '0.4.0-alpha.6'
|
||||
sourceCompatibility = '11'
|
||||
version = '0.4.0'
|
||||
sourceCompatibility = '13'
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs 'src/main'
|
||||
@@ -48,44 +42,56 @@ sourceSets {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile 'org.assertj:assertj-core:3.8.0'
|
||||
compile fileTree(dir: 'imports', include: '*.jar')
|
||||
compile 'ch.qos.logback:logback-core:1.2.3'
|
||||
compile 'ch.qos.logback:logback-classic:1.2.3'
|
||||
compile 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.8'
|
||||
compile 'com.github.jiconfont:jiconfont-font_awesome:4.7.0.1'
|
||||
compile 'com.github.jiconfont:jiconfont-javafx:1.0.0'
|
||||
compile 'com.github.jiconfont:jiconfont-swing:1.0.1'
|
||||
compile 'com.github.wendykierp:JTransforms:3.1'
|
||||
compile 'com.google.guava:guava:27.0.1-jre'
|
||||
compile 'com.jidesoft:jide-oss:3.6.18'
|
||||
compile 'com.miglayout:miglayout-swing:5.2'
|
||||
compile 'javax.usb:usb-api:1.0.2'
|
||||
compile 'net.coderazzi:tablefilter-swing:5.4.0'
|
||||
compile 'org.apache.axis:axis:1.4'
|
||||
compile 'org.apache.axis:axis-jaxrpc:1.4'
|
||||
compile 'org.apache.commons:commons-lang3:3.8.1'
|
||||
compile 'org.apache.commons:commons-io:1.3.2'
|
||||
compile 'org.apache.mina:mina-core:2.0.19'
|
||||
compile 'org.apache.mina:mina-http:2.0.19'
|
||||
compile "org.openjfx:javafx-base:11:win"
|
||||
compile "org.openjfx:javafx-base:11:linux"
|
||||
compile "org.openjfx:javafx-base:11:mac"
|
||||
compile "org.openjfx:javafx-controls:11:win"
|
||||
compile "org.openjfx:javafx-controls:11:linux"
|
||||
compile "org.openjfx:javafx-controls:11:mac"
|
||||
compile "org.openjfx:javafx-graphics:11:win"
|
||||
compile "org.openjfx:javafx-graphics:11:linux"
|
||||
compile "org.openjfx:javafx-graphics:11:mac"
|
||||
compile "org.openjfx:javafx-swing:11:win"
|
||||
compile "org.openjfx:javafx-swing:11:linux"
|
||||
compile "org.openjfx:javafx-swing:11:mac"
|
||||
compile 'org.slf4j:slf4j-api:1.7.25'
|
||||
compile 'org.usb4java:libusb4java:1.3.0'
|
||||
compile 'org.usb4java:usb4java:1.3.0'
|
||||
compile 'org.usb4java:usb4java-javax:1.3.0'
|
||||
compile 'pl.edu.icm:JLargeArrays:1.6'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation 'org.assertj:assertj-core:3.8.0'
|
||||
|
||||
//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'
|
||||
|
||||
//mavenCentral/jcenter imports
|
||||
implementation 'org.apache.commons:commons-math3:3.6.1'
|
||||
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 '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.guava:guava:27.0.1-jre'
|
||||
implementation 'com.jidesoft:jide-oss:3.6.18'
|
||||
implementation 'com.miglayout:miglayout-swing:5.2'
|
||||
implementation 'com.mpatric:mp3agic:0.9.1'
|
||||
implementation 'eu.hansolo:charts:1.0.5'
|
||||
implementation 'io.github.dsheirer:radio-reference-api:15.1.2'
|
||||
implementation 'javax.usb:usb-api:1.0.2'
|
||||
implementation 'net.coderazzi:tablefilter-swing:5.4.0'
|
||||
implementation 'org.apache.commons:commons-lang3:3.8.1'
|
||||
implementation 'org.apache.commons:commons-io:1.3.2'
|
||||
implementation 'org.apache.mina:mina-core:2.0.19'
|
||||
implementation 'org.apache.mina:mina-http:2.0.19'
|
||||
implementation 'org.controlsfx:controlsfx:11.0.1'
|
||||
implementation 'org.slf4j:slf4j-api:1.7.25'
|
||||
implementation 'org.usb4java:libusb4java:1.3.0'
|
||||
implementation 'org.usb4java:usb4java:1.3.0'
|
||||
implementation 'org.usb4java:usb4java-javax:1.3.0'
|
||||
implementation 'pl.edu.icm:JLargeArrays:1.6'
|
||||
|
||||
//The must remain as compile options to ensure they are included in the build to produce multi-OS images
|
||||
compile "org.openjfx:javafx-base:13:win"
|
||||
compile "org.openjfx:javafx-base:13:linux"
|
||||
compile "org.openjfx:javafx-base:13:mac"
|
||||
compile "org.openjfx:javafx-controls:13.0.1:win"
|
||||
compile "org.openjfx:javafx-controls:13.0.1:linux"
|
||||
compile "org.openjfx:javafx-controls:13.0.1:mac"
|
||||
compile "org.openjfx:javafx-graphics:13.0.1:win"
|
||||
compile "org.openjfx:javafx-graphics:13.0.1:linux"
|
||||
compile "org.openjfx:javafx-graphics:13.0.1:mac"
|
||||
compile "org.openjfx:javafx-swing:13.0.1:win"
|
||||
compile "org.openjfx:javafx-swing:13.0.1:linux"
|
||||
compile "org.openjfx:javafx-swing:13.0.1:mac"
|
||||
}
|
||||
|
||||
application {
|
||||
@@ -103,11 +109,120 @@ javafx {
|
||||
modules = ['javafx.base', 'javafx.controls', 'javafx.graphics', 'javafx.swing']
|
||||
}
|
||||
|
||||
runtime {
|
||||
targetPlatform('linux-x64', '/media/denny/WD250GB/java/linux/jdk-11.0.1')
|
||||
targetPlatform('osx-x64', '/media/denny/WD250GB/java/osx/jdk-11.0.1.jdk/Contents/Home')
|
||||
targetPlatform('windows-x64', '/media/denny/WD250GB/java/windows/jdk-11.0.1')
|
||||
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
|
||||
modules = ['java.desktop', 'java.naming', 'jdk.unsupported', 'jdk.unsupported.desktop']
|
||||
imageZip = file("$buildDir/image/sdr-trunk-" + version + ".zip")
|
||||
jar {
|
||||
manifest {
|
||||
attributes (
|
||||
'Implementation-Title' : 'sdrtrunk project',
|
||||
'Implementation-Version' : archiveVersion,
|
||||
'Class-Path' : 'jmbe-1.0.4.jar',
|
||||
'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']}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
def jdk_linux_x86_64 = '/media/denny/WD250GB/java/bellsoft/linux-x64/jdk-14'
|
||||
def jdk_linux_x86_32 = '/media/denny/WD250GB/java/bellsoft/linux-x86/jdk-14'
|
||||
def jdk_osx_x86_64 = '/media/denny/WD250GB/java/bellsoft/osx-x64/jdk-14.jdk'
|
||||
def jdk_windows_x86_64 = '/media/denny/WD250GB/java/bellsoft/windows-x64/jdk-14'
|
||||
def jdk_windows_x86_32 = '/media/denny/WD250GB/java/bellsoft/windows-x86/jdk-14'
|
||||
|
||||
def file_jdk_linux_x86_64 = new File(jdk_linux_x86_64)
|
||||
def file_jdk_linux_x86_32 = new File(jdk_linux_x86_32)
|
||||
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 file_jdk_windows_x86_32 = new File(jdk_windows_x86_32)
|
||||
|
||||
def hasTargetJdk = file_jdk_linux_x86_32.exists() ||
|
||||
file_jdk_linux_x86_64.exists() ||
|
||||
file_jdk_osx_x86_64.exists() ||
|
||||
file_jdk_windows_x86_32.exists() ||
|
||||
file_jdk_windows_x86_64.exists()
|
||||
|
||||
runtime {
|
||||
if(file_jdk_linux_x86_64.exists())
|
||||
{
|
||||
targetPlatform('linux-x86_64-v' + version, jdk_linux_x86_64)
|
||||
}
|
||||
|
||||
if(file_jdk_linux_x86_32.exists())
|
||||
{
|
||||
targetPlatform('linux-x86_32-v' + version, jdk_linux_x86_32)
|
||||
}
|
||||
|
||||
if(file_jdk_osx_x86_64.exists())
|
||||
{
|
||||
targetPlatform('osx-x86_64-v' + version, jdk_osx_x86_64)
|
||||
}
|
||||
|
||||
if(file_jdk_windows_x86_64.exists())
|
||||
{
|
||||
targetPlatform('windows-x86_64-v' + version, jdk_windows_x86_64)
|
||||
}
|
||||
|
||||
if(file_jdk_windows_x86_32.exists())
|
||||
{
|
||||
targetPlatform('windows-x86_32-v' + version, jdk_windows_x86_32)
|
||||
}
|
||||
|
||||
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
|
||||
modules = ['java.desktop', 'java.naming', 'jdk.unsupported', 'jdk.unsupported.desktop', 'java.net.http']
|
||||
imageZip = hasTargetJdk ? file("$buildDir/image/sdr-trunk.zip") : file("$buildDir/image/sdr-trunk-" + version + ".zip")
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for existence of JDK folders for each supported platform before creating runtime images
|
||||
*/
|
||||
tasks.runtime.doFirst {
|
||||
|
||||
if(!file_jdk_linux_x86_64.exists())
|
||||
{
|
||||
println("Skipping OS Image - Linux x86 64-bit JDK was not found at " + jdk_linux_x86_64)
|
||||
}
|
||||
|
||||
if(!file_jdk_linux_x86_32.exists())
|
||||
{
|
||||
println("Skipping OS Image - Linux x86 32-bit JDK was not found at " + jdk_linux_x86_32)
|
||||
}
|
||||
|
||||
if(!file_jdk_osx_x86_64.exists())
|
||||
{
|
||||
println("Skipping OS Image - OSX x86 64-bit JDK was not found at " + jdk_osx_x86_64);
|
||||
}
|
||||
|
||||
if(!file_jdk_windows_x86_64.exists())
|
||||
{
|
||||
println("Skipping OS Image - Windows x86 64-bit JDK was not found at " + jdk_windows_x86_64)
|
||||
}
|
||||
|
||||
if(!file_jdk_windows_x86_32.exists())
|
||||
{
|
||||
println("Skipping OS Image - Windows x86 32-bit JDK was not found at " + jdk_windows_x86_32)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove unused JavaFX OS-specific libraries from each platform target directory
|
||||
*/
|
||||
tasks.runtime.doLast {
|
||||
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-linux-x86_32-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/ })
|
||||
delete(fileTree('build/image/sdr-trunk-windows-x86_32-v' + version + '/lib').include { it.name ==~ /javafx.*-(linux|mac)\.jar/ })
|
||||
|
||||
//JavaFX web and media modules are transitive dependencies of ControlsFX but are not required for this application
|
||||
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-linux-x86_32-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/ })
|
||||
delete(fileTree('build/image/sdr-trunk-windows-x86_32-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
|
||||
}
|
||||
|
||||
16
copyright.template
Normal file
16
copyright.template
Normal file
@@ -0,0 +1,16 @@
|
||||
******************************************************************************
|
||||
Copyright (C) 2014-${today.year} 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/>
|
||||
*****************************************************************************
|
||||
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,25 +1,5 @@
|
||||
#
|
||||
# *******************************************************************************
|
||||
# sdrtrunk
|
||||
# Copyright (C) 2014-2019 Dennis Sheirer
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# 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-5.1.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
51
gradlew
vendored
51
gradlew
vendored
@@ -1,5 +1,21 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
@@ -28,7 +44,7 @@ APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# 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"'
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
@@ -109,8 +125,8 @@ if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
# 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"`
|
||||
@@ -138,19 +154,19 @@ if $cygwin ; then
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
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" ;;
|
||||
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" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
@@ -159,14 +175,9 @@ save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# 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"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
18
gradlew.bat
vendored
18
gradlew.bat
vendored
@@ -1,3 +1,19 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@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"
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
352
sdr-trunk.ipr
352
sdr-trunk.ipr
@@ -11,9 +11,9 @@
|
||||
<entry name="!?*.java" />
|
||||
<entry name="!?*.groovy" />
|
||||
</wildcardResourcePatterns>
|
||||
<bytecodeTargetLevel target="1.8">
|
||||
<module name="sdr-trunk_main" target="1.8" />
|
||||
<module name="sdr-trunk_test" target="1.8" />
|
||||
<bytecodeTargetLevel target="11">
|
||||
<module name="sdr-trunk_main" target="11" />
|
||||
<module name="sdr-trunk_test" target="11" />
|
||||
</bytecodeTargetLevel>
|
||||
</component>
|
||||
<component name="CopyrightManager" default="sdrtrunk">
|
||||
@@ -30,12 +30,14 @@
|
||||
<component name="DependencyValidationManager">
|
||||
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
|
||||
</component>
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="distributionType" value="WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleHome" value="$USER_HOME$/gradle/gradle-2.12" />
|
||||
<option name="gradleJvm" value="11" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
@@ -59,21 +61,7 @@
|
||||
<version value="1.0" />
|
||||
</component>
|
||||
<component name="JavadocGenerationManager">
|
||||
<option name="OUTPUT_DIRECTORY" />
|
||||
<option name="OPTION_SCOPE" value="protected" />
|
||||
<option name="OPTION_HIERARCHY" value="true" />
|
||||
<option name="OPTION_NAVIGATOR" value="true" />
|
||||
<option name="OPTION_INDEX" value="true" />
|
||||
<option name="OPTION_SEPARATE_INDEX" value="true" />
|
||||
<option name="OPTION_DOCUMENT_TAG_USE" value="false" />
|
||||
<option name="OPTION_DOCUMENT_TAG_AUTHOR" value="false" />
|
||||
<option name="OPTION_DOCUMENT_TAG_VERSION" value="false" />
|
||||
<option name="OPTION_DOCUMENT_TAG_DEPRECATED" value="true" />
|
||||
<option name="OPTION_DEPRECATED_LIST" value="true" />
|
||||
<option name="OTHER_OPTIONS" value="" />
|
||||
<option name="HEAP_SIZE" />
|
||||
<option name="LOCALE" />
|
||||
<option name="OPEN_IN_BROWSER" value="true" />
|
||||
</component>
|
||||
<component name="Palette2">
|
||||
<group name="Swing">
|
||||
@@ -206,12 +194,12 @@
|
||||
</component>
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/sdr-trunk.iml" filepath="$PROJECT_DIR$/sdr-trunk.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/sdr-trunk.iml" filepath="$PROJECT_DIR$/sdr-trunk.iml" group="sdr-trunk" />
|
||||
<module fileurl="file://$PROJECT_DIR$/sdr-trunk_main.iml" filepath="$PROJECT_DIR$/sdr-trunk_main.iml" group="sdr-trunk" />
|
||||
<module fileurl="file://$PROJECT_DIR$/sdr-trunk_test.iml" filepath="$PROJECT_DIR$/sdr-trunk_test.iml" group="sdr-trunk" />
|
||||
</modules>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="11" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
<component name="SvnBranchConfigurationManager">
|
||||
@@ -255,48 +243,48 @@
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-annotations/2.9.0/a0ad4e203304ccab7e01266fa814115850edb8a9/jackson-annotations-2.9.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: com.fasterxml.jackson.core:jackson-core:2.9.3">
|
||||
<library name="Gradle: com.fasterxml.jackson.core:jackson-core:2.9.8">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-core/2.9.3/ea9b6fc7bc3ccba9777b0827091f9aa1f8580371/jackson-core-2.9.3.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-core/2.9.8/f5a654e4675769c716e5b387830d19b501ca191/jackson-core-2.9.8.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-core/2.9.3/a1c84964d4a1ef15eeb91d0c2b62460e7d7c4829/jackson-core-2.9.3-javadoc.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-core/2.9.8/acc65b3b6e07784dc1760fa4e460238eb2a9073a/jackson-core-2.9.8-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-core/2.9.3/99006a307afedb30000fc93b462e98f0d822fa11/jackson-core-2.9.3-sources.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-core/2.9.8/ecaea301e166a0b48f11615864246de739b6619b/jackson-core-2.9.8-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: com.fasterxml.jackson.core:jackson-databind:2.9.3">
|
||||
<library name="Gradle: com.fasterxml.jackson.core:jackson-databind:2.9.8">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-databind/2.9.3/193b96ef555b2f2573b576887ba9a93e4bf48e8c/jackson-databind-2.9.3.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-databind/2.9.8/11283f21cc480aa86c4df7a0a3243ec508372ed2/jackson-databind-2.9.8.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-databind/2.9.3/486b5cb643a0c98342b6f533d9fc5aa75ab736df/jackson-databind-2.9.3-javadoc.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-databind/2.9.8/a1dea243882227fc27c350a5b25ca76ef4ecb7d7/jackson-databind-2.9.8-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-databind/2.9.3/eab1ff15884e40c0adafe219a98c46513eb1d0e6/jackson-databind-2.9.3-sources.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-databind/2.9.8/f66792d499a6fea6c7a743558f940e0ebf775ce3/jackson-databind-2.9.8-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.3">
|
||||
<library name="Gradle: com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.8">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.9.3/c7d7d9f5316e0098e4d652f2d013aa9f9d69e3b1/jackson-dataformat-xml-2.9.3.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.9.8/6a068ddeeafe736da35fba3a7f64462081b562c9/jackson-dataformat-xml-2.9.8.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.9.3/9f09f5e7b1ff00b02953b0dc64e6edcc03d8dbc2/jackson-dataformat-xml-2.9.3-javadoc.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.9.8/ba3a439d041b333550de55e34c370d9221011911/jackson-dataformat-xml-2.9.8-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.9.3/585e357aeb2ce0428740e88efd1443889e5834f4/jackson-dataformat-xml-2.9.3-sources.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.9.8/2d48d64536472ff6138cab6ba731b29247fdb0fb/jackson-dataformat-xml-2.9.8-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: com.fasterxml.jackson.module:jackson-module-jaxb-annotations:2.9.3">
|
||||
<library name="Gradle: com.fasterxml.jackson.module:jackson-module-jaxb-annotations:2.9.8">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.module/jackson-module-jaxb-annotations/2.9.3/ffe9afb0a9ee84fc79a9fd451b00643da1fa2466/jackson-module-jaxb-annotations-2.9.3.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.module/jackson-module-jaxb-annotations/2.9.8/da08815ba1c7f7b435e7df02b7bf98327bad2fd4/jackson-module-jaxb-annotations-2.9.8.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.module/jackson-module-jaxb-annotations/2.9.3/21ec5b47ad8833bbe1ce1f99192bcb6a4d0d3846/jackson-module-jaxb-annotations-2.9.3-javadoc.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.module/jackson-module-jaxb-annotations/2.9.8/575e1a2a77b830cd3c369931b71b653114314a43/jackson-module-jaxb-annotations-2.9.8-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.module/jackson-module-jaxb-annotations/2.9.3/660f2e6f5bc3dcd507e21ec656218d92095e4e01/jackson-module-jaxb-annotations-2.9.3-sources.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.module/jackson-module-jaxb-annotations/2.9.8/b68061d95e61cc7c4a574f449cd9db7bad66d75b/jackson-module-jaxb-annotations-2.9.8-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: com.fasterxml.woodstox:woodstox-core:5.0.3">
|
||||
@@ -310,6 +298,61 @@
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fasterxml.woodstox/woodstox-core/5.0.3/f38482e8b060d9e5354c10103f4c4db53c58db7d/woodstox-core-5.0.3-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: com.fazecast:jSerialComm:2.5.0">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fazecast/jSerialComm/2.5.0/df146deefea578aa38de43a0658f06ac297acb6f/jSerialComm-2.5.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fazecast/jSerialComm/2.5.0/1aa3e4d710396f7bc56661b23a6d125bf5d3749/jSerialComm-2.5.0-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.fazecast/jSerialComm/2.5.0/b9e6222384a17cb10474b7f25768edf4b742ec16/jSerialComm-2.5.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: com.github.jiconfont:jiconfont-font_awesome:4.7.0.1">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont-font_awesome/4.7.0.1/121d019023f631af04a1357e637dd05a2cc91f7b/jiconfont-font_awesome-4.7.0.1.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont-font_awesome/4.7.0.1/278c25d163a2e2eec5b2e93669b73936365cea69/jiconfont-font_awesome-4.7.0.1-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont-font_awesome/4.7.0.1/8c79f5ccd872310c6dfd4f89f7fe3f748fba94a4/jiconfont-font_awesome-4.7.0.1-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: com.github.jiconfont:jiconfont-javafx:1.0.0">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont-javafx/1.0.0/233530f110ab0c3ff845aa52799c43645a11f1c8/jiconfont-javafx-1.0.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont-javafx/1.0.0/8cf9282bff0c0cf56cab9ba18f74943019f9a979/jiconfont-javafx-1.0.0-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont-javafx/1.0.0/687e090ac5e1b4d20a242995a80631043d4ee78d/jiconfont-javafx-1.0.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: com.github.jiconfont:jiconfont-swing:1.0.1">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont-swing/1.0.1/14621054d72340b4a664cce4710ba974a3882a19/jiconfont-swing-1.0.1.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont-swing/1.0.1/55df548424fa5159247b05f02eb712719ce75d5/jiconfont-swing-1.0.1-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont-swing/1.0.1/cae76b75041118062159ffa0a7e2109cc7f88fad/jiconfont-swing-1.0.1-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: com.github.jiconfont:jiconfont:1.0.0">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont/1.0.0/e17d93321975b6735ebc283f6832fa0350614d3a/jiconfont-1.0.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont/1.0.0/8dc32616a97fe4b54380821a6aab9d8930262262/jiconfont-1.0.0-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.jiconfont/jiconfont/1.0.0/e79316fba9458423ce944fba6da101262549464/jiconfont-1.0.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: com.github.wendykierp:JTransforms:3.1">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.github.wendykierp/JTransforms/3.1/f1f6d3c9c9bc04f848e38bdb77dea01e8764a01f/JTransforms-3.1.jar!/" />
|
||||
@@ -343,26 +386,26 @@
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.errorprone/error_prone_annotations/2.2.0/a8cd7823aa1dcd2fd6677c0c5988fdde9d1fb0a3/error_prone_annotations-2.2.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: com.google.guava:failureaccess:1.0">
|
||||
<library name="Gradle: com.google.guava:failureaccess:1.0.1">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/failureaccess/1.0/50bacb0bdbbeeda25142aff57e2214c402e1193c/failureaccess-1.0.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/failureaccess/1.0.1/1dcf1de382a0bf95a3d8b0849546c88bac1292c9/failureaccess-1.0.1.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/failureaccess/1.0/d4f5c095ed310f738d56b26bad666f342c1fd73d/failureaccess-1.0-javadoc.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/failureaccess/1.0.1/a9aadb2d577ff54ba3115ee7357cbe65bd2a470f/failureaccess-1.0.1-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/failureaccess/1.0/1e90402881010c96ea5e4b7e6f56acb283e78db3/failureaccess-1.0-sources.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/failureaccess/1.0.1/1d064e61aad6c51cc77f9b59dc2cccc78e792f5a/failureaccess-1.0.1-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: com.google.guava:guava:27.0-jre">
|
||||
<library name="Gradle: com.google.guava:guava:27.0.1-jre">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/guava/27.0-jre/c6ad87d2575af8ac8ec38e28e75aefa882cc3a1f/guava-27.0-jre.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/guava/27.0.1-jre/bd41a290787b5301e63929676d792c507bbc00ae/guava-27.0.1-jre.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/guava/27.0-jre/607bfb3210bdc9c60315b033710e35428bc400bb/guava-27.0-jre-javadoc.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/guava/27.0.1-jre/7515204e130bb73fea9f6df11e58559e6be2dcf0/guava-27.0.1-jre-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/guava/27.0-jre/d6484e2ee11ad928ccf61cf3e4ce9cedc2eead7e/guava-27.0-jre-sources.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.guava/guava/27.0.1-jre/cb5c1119df8d41a428013289b193eba3ccaf5f60/guava-27.0.1-jre-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava">
|
||||
@@ -394,26 +437,26 @@
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.jidesoft/jide-oss/3.6.18/b9cadcf869c0bdd032756228315252516856ad56/jide-oss-3.6.18-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: com.miglayout:miglayout-core:5.0">
|
||||
<library name="Gradle: com.miglayout:miglayout-core:5.2">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-core/5.0/956daa1576bccb24e727ab75540b078171c7c432/miglayout-core-5.0.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-core/5.2/c1234205bde8fa72a420ecc5f63b9f269edccbfb/miglayout-core-5.2.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-core/5.0/c346dd69f6de7ae6ab2e93d42fa5def41c99aa1c/miglayout-core-5.0-javadoc.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-core/5.2/43595a2a4d2da858dfe1163f0d0ce769b7fbb11/miglayout-core-5.2-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-core/5.0/c457b92ed51255fdb5dfab4e89c7aec06674cd7c/miglayout-core-5.0-sources.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-core/5.2/4b3f0bfa2312173be31f87caa3cc58b617eea1df/miglayout-core-5.2-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: com.miglayout:miglayout-swing:5.0">
|
||||
<library name="Gradle: com.miglayout:miglayout-swing:5.2">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-swing/5.0/7b4c4d92002459e68e8f115b9ae8a774b2660490/miglayout-swing-5.0.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-swing/5.2/53e2fc5586a120797e3af0ff4cf9adb3b403ab14/miglayout-swing-5.2.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-swing/5.0/7f87b7818189138a1e9691b861d06f20f121820d/miglayout-swing-5.0-javadoc.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-swing/5.2/9ca06755a629747815fa91b3cd480bfb40fbb0e1/miglayout-swing-5.2-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-swing/5.0/d3e06bcc8aabd8cdd1f40989e12ced6413fae68c/miglayout-swing-5.0-sources.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.miglayout/miglayout-swing/5.2/df798d1660b3106a0304efa9e04dfd1a1a2e6025/miglayout-swing-5.2-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: commons-io:commons-io:1.3.2">
|
||||
@@ -427,6 +470,28 @@
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/commons-io/commons-io/1.3.2/b41a5f39fa90007a6c247175933555ba5f166d70/commons-io-1.3.2-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: eu.hansolo:charts:1.0.5">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/eu.hansolo/charts/1.0.5/2bb9d233ac3e5f678920ff04effd57c0ba21d860/charts-1.0.5.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/eu.hansolo/charts/1.0.5/d65ed2f94c0d5579cf87eea01c5d14fa8196d235/charts-1.0.5-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/eu.hansolo/charts/1.0.5/aa26011a2c525ddf86cb92ed23558b18c0174fa4/charts-1.0.5-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: io.github.dsheirer:radio-reference-api:15.0.1">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/io.github.dsheirer/radio-reference-api/15.0.1/8af16506c26025517f0bc971ba35485e96130c9a/radio-reference-api-15.0.1.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/io.github.dsheirer/radio-reference-api/15.0.1/219528429b3ed73f285a8c2c4ded40d8eae61fc0/radio-reference-api-15.0.1-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/io.github.dsheirer/radio-reference-api/15.0.1/b699a9821030b54fbb7f472f97fcc634afb114fb/radio-reference-api-15.0.1-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: javax.usb:usb-api:1.0.2">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/javax.usb/usb-api/1.0.2/f0ae0d322a99811cf79d508d37f3dc2d82753070/usb-api-1.0.2.jar!/" />
|
||||
@@ -449,31 +514,15 @@
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/junit/junit/4.12/a6c32b40bf3d76eca54e3c601e5d1470c86fcdfa/junit-4.12-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: net.coderazzi:tablefilter-swing:5.3.1">
|
||||
<library name="Gradle: net.coderazzi:tablefilter-swing:5.4.0">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/net.coderazzi/tablefilter-swing/5.3.1/998bed231fba3ca6ab2f182aa576e5cb1df3339c/tablefilter-swing-5.3.1.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/net.coderazzi/tablefilter-swing/5.4.0/ba7415b409712bf1988b2f03eda7730deba26c10/tablefilter-swing-5.4.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/net.coderazzi/tablefilter-swing/5.3.1/90c361ce16cd898f1629906fa1df6d74a678704c/tablefilter-swing-5.3.1-javadoc.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/net.coderazzi/tablefilter-swing/5.4.0/3bc5c263fc43180f4137a41bd0848851830761ff/tablefilter-swing-5.4.0-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/net.coderazzi/tablefilter-swing/5.3.1/9e46adc554cfdf19029afa46ee81044ea5602b44/tablefilter-swing-5.3.1-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.apache.axis:axis-jaxrpc:1.4">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.axis/axis-jaxrpc/1.4/b393f1f0c0d95b68c86d0b1ab2e687bb71f3c075/axis-jaxrpc-1.4.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
<library name="Gradle: org.apache.axis:axis:1.4">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.axis/axis/1.4/94a9ce681a42d0352b3ad22659f67835e560d107/axis-1.4.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.axis/axis/1.4/442ab1fe3e66b6d5c916ae3f203fbfa618515f1c/axis-1.4-sources.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/net.coderazzi/tablefilter-swing/5.4.0/298121afccdf36281e234aaf478f97ddbe8aecf3/tablefilter-swing-5.4.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.apache.commons:commons-lang3:3.8.1">
|
||||
@@ -498,26 +547,26 @@
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-math3/3.5/1d3f412d28c8bff0e8f048240b08bcd21cfa432e/commons-math3-3.5-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.apache.mina:mina-core:2.0.16">
|
||||
<library name="Gradle: org.apache.mina:mina-core:2.0.19">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-core/2.0.16/f720f17643eaa7b0fec07c1d7f6272972c02bba4/mina-core-2.0.16.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-core/2.0.19/b6ba5d8c6d1dd38a1151c590018e4e5edd6c4cfa/mina-core-2.0.19.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-core/2.0.16/9b8b385cba38847719659ce6fde466828c4ab623/mina-core-2.0.16-javadoc.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-core/2.0.19/b50fe4a0802588c70fa9f23ae6dcefe54a688bca/mina-core-2.0.19-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-core/2.0.16/660fb813ca1c8d8a936f894324091400a5ac128a/mina-core-2.0.16-sources.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-core/2.0.19/a070a03134f7c4f5c9ad79b094423d48f3e9551b/mina-core-2.0.19-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.apache.mina:mina-http:2.0.16">
|
||||
<library name="Gradle: org.apache.mina:mina-http:2.0.19">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-http/2.0.16/b2081b5255a3998a044c34b12797b915e1640122/mina-http-2.0.16.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-http/2.0.19/b42328dbaeed685376821fe704f70ad36f4de24/mina-http-2.0.19.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-http/2.0.16/38119b808477795cc161a2aaf3e5bb16920a9aae/mina-http-2.0.16-javadoc.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-http/2.0.19/3d6ce6d25fc14d858486f50a96213872ca7ae467/mina-http-2.0.19-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-http/2.0.16/fadd15439d830c99ccd6e020a2e125f0a4224059/mina-http-2.0.16-sources.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.mina/mina-http/2.0.19/ce16dff76ec41be9f9c21df8bb8c271a3bb709cb/mina-http-2.0.19-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.assertj:assertj-core:3.8.0">
|
||||
@@ -575,37 +624,158 @@
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.hamcrest/hamcrest-core/1.3/1dc37250fbc78e23a65a67fbbaf71d2e9cbc3c0b/hamcrest-core-1.3-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.kordamp.ikonli:ikonli-core:2.1.1">
|
||||
<library name="Gradle: org.openjfx:javafx-base:11.0.2">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.kordamp.ikonli/ikonli-core/2.1.1/99b0142ea24785d14d51e329dd8050a9a55b8904/ikonli-core-2.1.1.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/7fb2e4a8528ec9e434a9ac9ee98b39af79e6dcb8/javafx-base-11.0.2.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.kordamp.ikonli/ikonli-core/2.1.1/2931b2864da41ce42f1a5973b4239d25e02d0f37/ikonli-core-2.1.1-javadoc.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/f0dfede8adbd1b28d1b5084bd2812ace69ebe673/javafx-base-11.0.2-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.kordamp.ikonli/ikonli-core/2.1.1/ce342290407ae3c6ce7b1249978c9ddf45b92974/ikonli-core-2.1.1-sources.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/c082cdf645f9833408dcd960bf68344c9384f569/javafx-base-11.0.2-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.kordamp.ikonli:ikonli-fontawesome-pack:2.1.1">
|
||||
<library name="Gradle: org.openjfx:javafx-base:linux:11.0.2">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.kordamp.ikonli/ikonli-fontawesome-pack/2.1.1/5c48f3fbb11a21b6fe460650569085aa0b7e4ae1/ikonli-fontawesome-pack-2.1.1.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/8db178adb1085d455e5ef643a48c34959c8771c1/javafx-base-11.0.2-linux.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.kordamp.ikonli/ikonli-fontawesome-pack/2.1.1/e8c0c8694953bdbcca312b8cc0e6dbc5c2e7ec8b/ikonli-fontawesome-pack-2.1.1-javadoc.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/f0dfede8adbd1b28d1b5084bd2812ace69ebe673/javafx-base-11.0.2-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.kordamp.ikonli/ikonli-fontawesome-pack/2.1.1/19de8abf192dcc83c0d0b7b507fa1fc72ce6bbb2/ikonli-fontawesome-pack-2.1.1-sources.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/c082cdf645f9833408dcd960bf68344c9384f569/javafx-base-11.0.2-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.kordamp.ikonli:ikonli-javafx:2.1.1">
|
||||
<library name="Gradle: org.openjfx:javafx-base:mac:11.0.2">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.kordamp.ikonli/ikonli-javafx/2.1.1/bf3e8f21146016f3fcee9ab48ab2228b7db4bc49/ikonli-javafx-2.1.1.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/b774922e5da6a56afee221d1a288d179a1342aa9/javafx-base-11.0.2-mac.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.kordamp.ikonli/ikonli-javafx/2.1.1/7c45822547d7a8a53d80ebb8e4a5602db10f9e45/ikonli-javafx-2.1.1-javadoc.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/f0dfede8adbd1b28d1b5084bd2812ace69ebe673/javafx-base-11.0.2-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.kordamp.ikonli/ikonli-javafx/2.1.1/a9c7c4aaad34e274df03701b84e064d9169ddd1d/ikonli-javafx-2.1.1-sources.jar!/" />
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/c082cdf645f9833408dcd960bf68344c9384f569/javafx-base-11.0.2-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.openjfx:javafx-base:win:11.0.2">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/1852e57b8cf9a9b6488c33605bccd5d06ff210e1/javafx-base-11.0.2-win.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/f0dfede8adbd1b28d1b5084bd2812ace69ebe673/javafx-base-11.0.2-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-base/11.0.2/c082cdf645f9833408dcd960bf68344c9384f569/javafx-base-11.0.2-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.openjfx:javafx-controls:linux:11.0.2">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/5f6929050a744aab39ebfc1e8e5dd03bcd2ad47b/javafx-controls-11.0.2-linux.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/baa088249ee757119688e78d9c3a256928189902/javafx-controls-11.0.2-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/1535a8b57f01a712405a739a0d69e09ab99dbb6b/javafx-controls-11.0.2-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.openjfx:javafx-controls:mac:11.0.2">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/996371d2868035845572cdb19179dca2bfd1139f/javafx-controls-11.0.2-mac.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/baa088249ee757119688e78d9c3a256928189902/javafx-controls-11.0.2-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/1535a8b57f01a712405a739a0d69e09ab99dbb6b/javafx-controls-11.0.2-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.openjfx:javafx-controls:win:11.0.2">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/6c763707769c18adce406904c771c2ad1fcc370b/javafx-controls-11.0.2-win.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/baa088249ee757119688e78d9c3a256928189902/javafx-controls-11.0.2-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-controls/11.0.2/1535a8b57f01a712405a739a0d69e09ab99dbb6b/javafx-controls-11.0.2-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.openjfx:javafx-graphics:11.0.2">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/e522eb4ea422eceeee207b1c266ba3db19b2343a/javafx-graphics-11.0.2.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/b7ae1857e54ed7d8ca0e30e01d12fae2b75ba400/javafx-graphics-11.0.2-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/15eadef01f327a851a6d3af0ac89d56292076b13/javafx-graphics-11.0.2-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.openjfx:javafx-graphics:linux:11.0.2">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/ef4c34f0ca77cd99100b76c2ccf1dce383572bb1/javafx-graphics-11.0.2-linux.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/b7ae1857e54ed7d8ca0e30e01d12fae2b75ba400/javafx-graphics-11.0.2-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/15eadef01f327a851a6d3af0ac89d56292076b13/javafx-graphics-11.0.2-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.openjfx:javafx-graphics:mac:11.0.2">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/c1d99ae5bc48e7173f4514a941d27d8f8c972c71/javafx-graphics-11.0.2-mac.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/b7ae1857e54ed7d8ca0e30e01d12fae2b75ba400/javafx-graphics-11.0.2-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/15eadef01f327a851a6d3af0ac89d56292076b13/javafx-graphics-11.0.2-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.openjfx:javafx-graphics:win:11.0.2">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/20459ea2cf714942bcbeb78a7f70ba3531dc1a44/javafx-graphics-11.0.2-win.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/b7ae1857e54ed7d8ca0e30e01d12fae2b75ba400/javafx-graphics-11.0.2-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-graphics/11.0.2/15eadef01f327a851a6d3af0ac89d56292076b13/javafx-graphics-11.0.2-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.openjfx:javafx-swing:linux:11.0.2">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-swing/11.0.2/cb57db3e5bd1e2b62b17c8a504858bfc26fa54b1/javafx-swing-11.0.2-linux.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-swing/11.0.2/336999871b9a7e88179a76e2251c73f47e3d5cb6/javafx-swing-11.0.2-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-swing/11.0.2/bcc8c0a402ff01a89a50888c9dc26824a9f3f2cc/javafx-swing-11.0.2-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.openjfx:javafx-swing:mac:11.0.2">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-swing/11.0.2/47efd914c1ebdeda8789a779c8341266340c743/javafx-swing-11.0.2-mac.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-swing/11.0.2/336999871b9a7e88179a76e2251c73f47e3d5cb6/javafx-swing-11.0.2-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-swing/11.0.2/bcc8c0a402ff01a89a50888c9dc26824a9f3f2cc/javafx-swing-11.0.2-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.openjfx:javafx-swing:win:11.0.2">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-swing/11.0.2/9b5728e61148142fae8c47f6295c84d7a8d6c89/javafx-swing-11.0.2-win.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-swing/11.0.2/336999871b9a7e88179a76e2251c73f47e3d5cb6/javafx-swing-11.0.2-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.openjfx/javafx-swing/11.0.2/bcc8c0a402ff01a89a50888c9dc26824a9f3f2cc/javafx-swing-11.0.2-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
<library name="Gradle: org.slf4j:slf4j-api:1.7.25">
|
||||
|
||||
@@ -1,34 +1,67 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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;
|
||||
|
||||
import com.jidesoft.swing.JideSplitPane;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastModel;
|
||||
import io.github.dsheirer.gui.editor.Editor;
|
||||
import io.github.dsheirer.icon.IconManager;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
|
||||
import net.coderazzi.filters.gui.AutoChoices;
|
||||
import net.coderazzi.filters.gui.TableFilterHeader;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.ListSelectionModel;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import java.awt.*;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AliasController extends JPanel
|
||||
implements ActionListener, ListSelectionListener
|
||||
public class AliasController extends JPanel implements ActionListener, ListSelectionListener
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String TABLE_PREFERENCE_KEY = "alias.controller";
|
||||
private AliasModel mAliasModel;
|
||||
private JTable mAliasTable;
|
||||
private TableFilterHeader mTableFilterHeader;
|
||||
private AliasEditor mAliasEditor;
|
||||
private MultipleAliasEditor mMultipleAliasEditor;
|
||||
private JideSplitPane mSplitPane;
|
||||
private UserPreferences mUserPreferences;
|
||||
|
||||
private static final String NEW_ALIAS = "New";
|
||||
private static final String COPY_ALIAS = "Copy";
|
||||
@@ -39,15 +72,16 @@ public class AliasController extends JPanel
|
||||
private JButton mDeleteButton = new JButton(DELETE_ALIAS);
|
||||
|
||||
private IconCellRenderer mIconCellRenderer;
|
||||
private JTableColumnWidthMonitor mColumnWidthMonitor;
|
||||
|
||||
public AliasController(AliasModel aliasModel, BroadcastModel broadcastModel, IconManager iconManager)
|
||||
public AliasController(AliasModel aliasModel, BroadcastModel broadcastModel, IconManager iconManager,
|
||||
UserPreferences userPreferences)
|
||||
{
|
||||
mAliasModel = aliasModel;
|
||||
|
||||
mAliasEditor = new AliasEditor(mAliasModel, broadcastModel, iconManager);
|
||||
mMultipleAliasEditor = new MultipleAliasEditor(mAliasModel, broadcastModel, iconManager);
|
||||
|
||||
mIconCellRenderer = new IconCellRenderer(iconManager);
|
||||
mUserPreferences = userPreferences;
|
||||
|
||||
init();
|
||||
|
||||
@@ -56,9 +90,7 @@ public class AliasController extends JPanel
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout(new MigLayout("insets 0 0 0 0",
|
||||
"[grow,fill]",
|
||||
"[grow,fill]"));
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
||||
|
||||
//System Configuration View and Editor
|
||||
mAliasTable = new JTable(mAliasModel);
|
||||
@@ -66,11 +98,11 @@ public class AliasController extends JPanel
|
||||
mAliasTable.getSelectionModel().addListSelectionListener(this);
|
||||
mAliasTable.setAutoCreateRowSorter(true);
|
||||
|
||||
mAliasTable.getColumnModel().getColumn(AliasModel.COLUMN_COLOR)
|
||||
.setCellRenderer(new ColorCellRenderer());
|
||||
mAliasTable.getColumnModel().getColumn(AliasModel.COLUMN_COLOR).setCellRenderer(new ColorCellRenderer());
|
||||
|
||||
mAliasTable.getColumnModel().getColumn(AliasModel.COLUMN_ICON)
|
||||
.setCellRenderer(mIconCellRenderer);
|
||||
mAliasTable.getColumnModel().getColumn(AliasModel.COLUMN_ICON).setCellRenderer(mIconCellRenderer);
|
||||
|
||||
mColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mAliasTable, TABLE_PREFERENCE_KEY);
|
||||
|
||||
mTableFilterHeader = new TableFilterHeader(mAliasTable, AutoChoices.ENABLED);
|
||||
mTableFilterHeader.setFilterOnUpdates(true);
|
||||
@@ -79,8 +111,8 @@ public class AliasController extends JPanel
|
||||
|
||||
JPanel buttonsPanel = new JPanel();
|
||||
|
||||
buttonsPanel.setLayout(
|
||||
new MigLayout("insets 0 0 0 0", "[grow,fill][grow,fill][grow,fill]", "[]"));
|
||||
buttonsPanel.setLayout(new MigLayout("insets 0 0 0 0",
|
||||
"[grow,fill][grow,fill][grow,fill]", "[]"));
|
||||
|
||||
mNewButton.addActionListener(this);
|
||||
mNewButton.setToolTipText("Adds a new alias");
|
||||
@@ -98,8 +130,8 @@ public class AliasController extends JPanel
|
||||
|
||||
JPanel listAndButtonsPanel = new JPanel();
|
||||
|
||||
listAndButtonsPanel.setLayout(
|
||||
new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill][]"));
|
||||
listAndButtonsPanel.setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]",
|
||||
"[grow,fill][]"));
|
||||
|
||||
listAndButtonsPanel.add(tableScroller, "wrap");
|
||||
listAndButtonsPanel.add(buttonsPanel);
|
||||
@@ -212,18 +244,11 @@ public class AliasController extends JPanel
|
||||
*/
|
||||
private void addAlias(Alias alias)
|
||||
{
|
||||
//HACK: when inserting a row to the model, the JTable gets
|
||||
//notified and attempts to tell the coderazzi table filter
|
||||
//adaptive choices filter to refresh before the table filter is
|
||||
//notified of the row additions, causing an index out of bounds
|
||||
//exception. We turn off adaptive choices temporarily, add the
|
||||
//channel, and turn on adaptive choices again.
|
||||
mTableFilterHeader.setAdaptiveChoices(false);
|
||||
//Remove any column headers so that the newly added alias shows correctly.
|
||||
mTableFilterHeader.resetFilter();
|
||||
|
||||
int index = mAliasModel.addAlias(alias);
|
||||
|
||||
mTableFilterHeader.setAdaptiveChoices(true);
|
||||
|
||||
if(index >= 0)
|
||||
{
|
||||
int translatedIndex = mAliasTable.convertRowIndexToView(index);
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.alias;
|
||||
|
||||
@@ -37,30 +39,34 @@ import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID;
|
||||
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncIDEditor;
|
||||
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID;
|
||||
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200IDEditor;
|
||||
import io.github.dsheirer.alias.id.legacy.mobileID.MINEditor;
|
||||
import io.github.dsheirer.alias.id.legacy.mobileID.Min;
|
||||
import io.github.dsheirer.alias.id.legacy.mpt1327.MPT1327ID;
|
||||
import io.github.dsheirer.alias.id.legacy.mpt1327.MPT1327IDEditor;
|
||||
import io.github.dsheirer.alias.id.legacy.nonrecordable.NonRecordable;
|
||||
import io.github.dsheirer.alias.id.legacy.nonrecordable.NonRecordableEditor;
|
||||
import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupID;
|
||||
import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupIDEditor;
|
||||
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
|
||||
import io.github.dsheirer.alias.id.lojack.LoJackIDEditor;
|
||||
import io.github.dsheirer.alias.id.legacy.mobileID.MINEditor;
|
||||
import io.github.dsheirer.alias.id.legacy.mobileID.Min;
|
||||
import io.github.dsheirer.alias.id.priority.Priority;
|
||||
import io.github.dsheirer.alias.id.priority.PriorityEditor;
|
||||
import io.github.dsheirer.alias.id.record.Record;
|
||||
import io.github.dsheirer.alias.id.record.RecordEditor;
|
||||
import io.github.dsheirer.alias.id.legacy.siteID.SiteID;
|
||||
import io.github.dsheirer.alias.id.legacy.siteID.SiteIDEditor;
|
||||
import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupID;
|
||||
import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupIDEditor;
|
||||
import io.github.dsheirer.alias.id.legacy.uniqueID.UniqueID;
|
||||
import io.github.dsheirer.alias.id.legacy.uniqueID.UniqueIDEditor;
|
||||
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
|
||||
import io.github.dsheirer.alias.id.lojack.LoJackIDEditor;
|
||||
import io.github.dsheirer.alias.id.priority.Priority;
|
||||
import io.github.dsheirer.alias.id.priority.PriorityEditor;
|
||||
import io.github.dsheirer.alias.id.radio.Radio;
|
||||
import io.github.dsheirer.alias.id.radio.RadioEditor;
|
||||
import io.github.dsheirer.alias.id.radio.RadioRange;
|
||||
import io.github.dsheirer.alias.id.radio.RadioRangeEditor;
|
||||
import io.github.dsheirer.alias.id.record.Record;
|
||||
import io.github.dsheirer.alias.id.record.RecordEditor;
|
||||
import io.github.dsheirer.alias.id.status.StatusID;
|
||||
import io.github.dsheirer.alias.id.status.StatusIDEditor;
|
||||
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
|
||||
import io.github.dsheirer.alias.id.talkgroup.TalkgroupEditor;
|
||||
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
||||
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRangeEditor;
|
||||
import io.github.dsheirer.alias.id.legacy.uniqueID.UniqueID;
|
||||
import io.github.dsheirer.alias.id.legacy.uniqueID.UniqueIDEditor;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastModel;
|
||||
import io.github.dsheirer.gui.editor.Editor;
|
||||
import io.github.dsheirer.gui.editor.EmptyEditor;
|
||||
@@ -102,6 +108,15 @@ public class AliasFactory
|
||||
Priority copyPriority = new Priority();
|
||||
copyPriority.setPriority(originalPriority.getPriority());
|
||||
return copyPriority;
|
||||
case RADIO_ID:
|
||||
Radio originalRadio = (Radio)id;
|
||||
Radio copyRadio = new Radio(originalRadio.getProtocol(), originalRadio.getValue());
|
||||
return copyRadio;
|
||||
case RADIO_ID_RANGE:
|
||||
RadioRange originalRadioRange = (RadioRange)id;
|
||||
RadioRange copyRadioRange = new RadioRange(originalRadioRange.getProtocol(), originalRadioRange.getMinRadio(),
|
||||
originalRadioRange.getMaxRadio());
|
||||
return copyRadioRange;
|
||||
case RECORD:
|
||||
return new Record();
|
||||
case SITE:
|
||||
@@ -213,6 +228,10 @@ public class AliasFactory
|
||||
return new MINEditor(aliasID);
|
||||
case PRIORITY:
|
||||
return new PriorityEditor(aliasID);
|
||||
case RADIO_ID:
|
||||
return new RadioEditor(aliasID);
|
||||
case RADIO_ID_RANGE:
|
||||
return new RadioRangeEditor(aliasID);
|
||||
case RECORD:
|
||||
return new RecordEditor(aliasID);
|
||||
case SITE:
|
||||
@@ -286,6 +305,10 @@ public class AliasFactory
|
||||
return new NonRecordable();
|
||||
case PRIORITY:
|
||||
return new Priority();
|
||||
case RADIO_ID:
|
||||
return new Radio();
|
||||
case RADIO_ID_RANGE:
|
||||
return new RadioRange();
|
||||
case RECORD:
|
||||
return new Record();
|
||||
case TALKGROUP:
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.alias;
|
||||
|
||||
@@ -24,6 +26,8 @@ import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
||||
import io.github.dsheirer.alias.id.esn.Esn;
|
||||
import io.github.dsheirer.alias.id.priority.Priority;
|
||||
import io.github.dsheirer.alias.id.radio.Radio;
|
||||
import io.github.dsheirer.alias.id.radio.RadioRange;
|
||||
import io.github.dsheirer.alias.id.status.StatusID;
|
||||
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
|
||||
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
||||
@@ -32,6 +36,7 @@ import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.identifier.esn.ESNIdentifier;
|
||||
import io.github.dsheirer.identifier.patch.PatchGroup;
|
||||
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.identifier.status.UnitStatusIdentifier;
|
||||
import io.github.dsheirer.identifier.status.UserStatusIdentifier;
|
||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||
@@ -48,10 +53,15 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* List of aliases that share the same alias list name and provides convenient methods for looking up alias
|
||||
* objects that match an identifier.
|
||||
*/
|
||||
public class AliasList implements Listener<AliasEvent>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AliasList.class);
|
||||
private Map<Protocol,TalkgroupAliasList> mTalkgroupProtocolMap = new HashMap<>();
|
||||
private Map<Protocol,RadioAliasList> mRadioProtocolMap = new HashMap<>();
|
||||
private Map<String,Alias> mESNMap = new HashMap<>();
|
||||
private Map<Integer,Alias> mStatusMap = new HashMap<>();
|
||||
private boolean mHasAliasActions = false;
|
||||
@@ -124,6 +134,32 @@ public class AliasList implements Listener<AliasEvent>
|
||||
|
||||
talkgroupRangeAliasList.add(talkgroupRange, alias);
|
||||
break;
|
||||
case RADIO_ID:
|
||||
Radio radio = (Radio)id;
|
||||
|
||||
RadioAliasList radioAliasList = mRadioProtocolMap.get(radio.getProtocol());
|
||||
|
||||
if(radioAliasList == null)
|
||||
{
|
||||
radioAliasList = new RadioAliasList();
|
||||
mRadioProtocolMap.put(radio.getProtocol(), radioAliasList);
|
||||
}
|
||||
|
||||
radioAliasList.add(radio, alias);
|
||||
break;
|
||||
case RADIO_ID_RANGE:
|
||||
RadioRange radioRange = (RadioRange)id;
|
||||
|
||||
RadioAliasList radioRangeAliasList = mRadioProtocolMap.get(radioRange.getProtocol());
|
||||
|
||||
if(radioRangeAliasList == null)
|
||||
{
|
||||
radioRangeAliasList = new RadioAliasList();
|
||||
mRadioProtocolMap.put(radioRange.getProtocol(), radioRangeAliasList);
|
||||
}
|
||||
|
||||
radioRangeAliasList.add(radioRange, alias);
|
||||
break;
|
||||
case ESN:
|
||||
String esn = ((Esn) id).getEsn();
|
||||
|
||||
@@ -253,14 +289,7 @@ public class AliasList implements Listener<AliasEvent>
|
||||
|
||||
if(talkgroupAliasList != null)
|
||||
{
|
||||
Alias alias = talkgroupAliasList.getAlias(talkgroup);
|
||||
|
||||
if(alias != null)
|
||||
{
|
||||
List<Alias> aliases = new ArrayList<>();
|
||||
aliases.add(alias);
|
||||
return aliases;
|
||||
}
|
||||
return toList(talkgroupAliasList.getAlias(talkgroup));
|
||||
}
|
||||
break;
|
||||
case PATCH_GROUP:
|
||||
@@ -293,53 +322,52 @@ public class AliasList implements Listener<AliasEvent>
|
||||
return aliases;
|
||||
}
|
||||
break;
|
||||
case RADIO:
|
||||
RadioIdentifier radio = (RadioIdentifier)identifier;
|
||||
|
||||
RadioAliasList radioAliasList = mRadioProtocolMap.get(identifier.getProtocol());
|
||||
|
||||
if(radioAliasList != null)
|
||||
{
|
||||
return toList(radioAliasList.getAlias(radio));
|
||||
}
|
||||
break;
|
||||
case ESN:
|
||||
if(identifier instanceof ESNIdentifier)
|
||||
{
|
||||
Alias alias = getESNAlias(((ESNIdentifier)identifier).getValue());
|
||||
|
||||
if(alias != null)
|
||||
{
|
||||
List<Alias> aliases = new ArrayList<>();
|
||||
aliases.add(alias);
|
||||
return aliases;
|
||||
}
|
||||
return toList(getESNAlias(((ESNIdentifier)identifier).getValue()));
|
||||
}
|
||||
break;
|
||||
case UNIT_STATUS:
|
||||
if(identifier instanceof UnitStatusIdentifier)
|
||||
{
|
||||
int status = ((UnitStatusIdentifier)identifier).getValue();
|
||||
|
||||
Alias alias = mStatusMap.get(status);
|
||||
|
||||
if(alias != null)
|
||||
{
|
||||
List<Alias> aliases = new ArrayList<>();
|
||||
aliases.add(alias);
|
||||
return aliases;
|
||||
}
|
||||
return toList(mStatusMap.get(status));
|
||||
}
|
||||
break;
|
||||
case USER_STATUS:
|
||||
if(identifier instanceof UserStatusIdentifier)
|
||||
{
|
||||
int status = ((UserStatusIdentifier)identifier).getValue();
|
||||
|
||||
Alias alias = mStatusMap.get(status);
|
||||
|
||||
if(alias != null)
|
||||
{
|
||||
List<Alias> aliases = new ArrayList<>();
|
||||
aliases.add(alias);
|
||||
return aliases;
|
||||
}
|
||||
return toList(mStatusMap.get(status));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.EMPTY_LIST;
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private static List<Alias> toList(Alias alias)
|
||||
{
|
||||
if(alias != null)
|
||||
{
|
||||
List<Alias> aliases = new ArrayList<>();
|
||||
aliases.add(alias);
|
||||
return aliases;
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -505,11 +533,35 @@ public class AliasList implements Listener<AliasEvent>
|
||||
|
||||
public void add(Talkgroup talkgroup, Alias alias)
|
||||
{
|
||||
//Detect talkgroup collisions
|
||||
if(mTalkgroupAliasMap.containsKey(talkgroup.getValue()))
|
||||
{
|
||||
Alias existing = mTalkgroupAliasMap.get(talkgroup.getValue());
|
||||
|
||||
if(!existing.equals(alias))
|
||||
{
|
||||
mLog.warn("Alias [" + alias.getName() + "] talkgroup [" + talkgroup.getValue() +
|
||||
"] has the same talkgroup value as alias [" + existing.getName() +
|
||||
"] - alias [" + alias.getName() + "] will be used for alias list [" + getName() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
mTalkgroupAliasMap.put(talkgroup.getValue(), alias);
|
||||
}
|
||||
|
||||
public void add(TalkgroupRange talkgroupRange, Alias alias)
|
||||
{
|
||||
//Log warning if the new talkgroup range overlaps with any existing ranges
|
||||
for(Map.Entry<TalkgroupRange,Alias> entry: mTalkgroupRangeAliasMap.entrySet())
|
||||
{
|
||||
if(talkgroupRange.overlaps(entry.getKey()) && !entry.getValue().equals(alias))
|
||||
{
|
||||
mLog.warn("Alias [" + alias.getName() + "] with talkgroup range [" + talkgroupRange.toString() +
|
||||
"] overlaps with alias [" + entry.getValue().getName() +
|
||||
"] with talkgroup range [" + entry.getKey().toString() + "] for alias list [" + getName() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
mTalkgroupRangeAliasMap.put(talkgroupRange, alias);
|
||||
}
|
||||
|
||||
@@ -532,4 +584,90 @@ public class AliasList implements Listener<AliasEvent>
|
||||
AliasList.remove(alias, mTalkgroupRangeAliasMap);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listing of radio IDs and ranges for a specific protocol
|
||||
*/
|
||||
public class RadioAliasList
|
||||
{
|
||||
private Map<Integer,Alias> mRadioAliasMap = new TreeMap<>();
|
||||
private Map<RadioRange, Alias> mRadioRangeAliasMap = new HashMap<>();
|
||||
|
||||
public RadioAliasList()
|
||||
{
|
||||
}
|
||||
|
||||
public Alias getAlias(RadioIdentifier identifier)
|
||||
{
|
||||
int value = identifier.getValue();
|
||||
|
||||
if(mRadioAliasMap.containsKey(value))
|
||||
{
|
||||
return mRadioAliasMap.get(value);
|
||||
}
|
||||
|
||||
for(RadioRange radioRange: mRadioRangeAliasMap.keySet())
|
||||
{
|
||||
if(radioRange.contains(value))
|
||||
{
|
||||
return mRadioRangeAliasMap.get(radioRange);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void add(Radio radio, Alias alias)
|
||||
{
|
||||
//Detect collisions
|
||||
if(mRadioAliasMap.containsKey(radio.getValue()))
|
||||
{
|
||||
Alias existing = mRadioAliasMap.get(radio.getValue());
|
||||
|
||||
if(!existing.equals(alias))
|
||||
{
|
||||
mLog.warn("Alias [" + alias.getName() + "] radio ID [" + radio.getValue() +
|
||||
"] has the same value as alias [" + existing.getName() +
|
||||
"] - alias [" + alias.getName() + "] will be used for alias list [" + getName() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
mRadioAliasMap.put(radio.getValue(), alias);
|
||||
}
|
||||
|
||||
public void add(RadioRange radioRange, Alias alias)
|
||||
{
|
||||
//Log warning if the new range overlaps with any existing ranges
|
||||
for(Map.Entry<RadioRange,Alias> entry: mRadioRangeAliasMap.entrySet())
|
||||
{
|
||||
if(radioRange.overlaps(entry.getKey()) && !entry.getValue().equals(alias))
|
||||
{
|
||||
mLog.warn("Alias [" + alias.getName() + "] with radio ID range [" + radioRange.toString() +
|
||||
"] overlaps with alias [" + entry.getValue().getName() +
|
||||
"] with range [" + entry.getKey().toString() + "] for alias list [" + getName() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
mRadioRangeAliasMap.put(radioRange, alias);
|
||||
}
|
||||
|
||||
public void remove(Radio radio)
|
||||
{
|
||||
mRadioAliasMap.remove(radio.getValue());
|
||||
}
|
||||
|
||||
public void remove(RadioRange radioRange)
|
||||
{
|
||||
mRadioRangeAliasMap.remove(radioRange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the alias from all internal maps
|
||||
*/
|
||||
public void remove(Alias alias)
|
||||
{
|
||||
AliasList.remove(alias, mRadioAliasMap);
|
||||
AliasList.remove(alias, mRadioRangeAliasMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
*/
|
||||
package io.github.dsheirer.alias.id;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
@@ -25,18 +29,20 @@ import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
||||
import io.github.dsheirer.alias.id.esn.Esn;
|
||||
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID;
|
||||
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID;
|
||||
import io.github.dsheirer.alias.id.legacy.mobileID.Min;
|
||||
import io.github.dsheirer.alias.id.legacy.mpt1327.MPT1327ID;
|
||||
import io.github.dsheirer.alias.id.legacy.nonrecordable.NonRecordable;
|
||||
import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupID;
|
||||
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
|
||||
import io.github.dsheirer.alias.id.legacy.mobileID.Min;
|
||||
import io.github.dsheirer.alias.id.priority.Priority;
|
||||
import io.github.dsheirer.alias.id.record.Record;
|
||||
import io.github.dsheirer.alias.id.legacy.siteID.SiteID;
|
||||
import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupID;
|
||||
import io.github.dsheirer.alias.id.legacy.uniqueID.UniqueID;
|
||||
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
|
||||
import io.github.dsheirer.alias.id.priority.Priority;
|
||||
import io.github.dsheirer.alias.id.radio.Radio;
|
||||
import io.github.dsheirer.alias.id.radio.RadioRange;
|
||||
import io.github.dsheirer.alias.id.record.Record;
|
||||
import io.github.dsheirer.alias.id.status.StatusID;
|
||||
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
|
||||
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
||||
import io.github.dsheirer.alias.id.legacy.uniqueID.UniqueID;
|
||||
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
|
||||
@JsonSubTypes({
|
||||
@@ -48,6 +54,8 @@ import io.github.dsheirer.alias.id.legacy.uniqueID.UniqueID;
|
||||
@JsonSubTypes.Type(value = Min.class, name = "min"),
|
||||
@JsonSubTypes.Type(value = MPT1327ID.class, name = "mpt1327ID"),
|
||||
@JsonSubTypes.Type(value = NonRecordable.class, name = "nonRecordable"),
|
||||
@JsonSubTypes.Type(value = Radio.class, name = "radio"),
|
||||
@JsonSubTypes.Type(value = RadioRange.class, name = "radioRange"),
|
||||
@JsonSubTypes.Type(value = Talkgroup.class, name = "talkgroup"),
|
||||
@JsonSubTypes.Type(value = TalkgroupRange.class, name = "talkgroupRange"),
|
||||
@JsonSubTypes.Type(value = Priority.class, name = "priority"),
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.alias.id;
|
||||
|
||||
@@ -28,6 +30,8 @@ public enum AliasIDType
|
||||
LTR_NET_UID("LTR-Net UID"),
|
||||
MIN("Passport MIN"),
|
||||
PRIORITY("Audio Priority"),
|
||||
RADIO_ID("Radio ID"),
|
||||
RADIO_ID_RANGE("Radio ID Range"),
|
||||
RECORD("Record"),
|
||||
SITE("Site"),
|
||||
STATUS("Status"),
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.alias.id;
|
||||
|
||||
@@ -124,6 +126,8 @@ public class AliasIdentifierEditor extends Editor<Alias>
|
||||
|
||||
menu.add(new AddAliasIdentifierItem(AliasIDType.TALKGROUP));
|
||||
menu.add(new AddAliasIdentifierItem(AliasIDType.TALKGROUP_RANGE));
|
||||
menu.add(new AddAliasIdentifierItem(AliasIDType.RADIO_ID));
|
||||
menu.add(new AddAliasIdentifierItem(AliasIDType.RADIO_ID_RANGE));
|
||||
menu.add(new AddAliasIdentifierItem(AliasIDType.ESN));
|
||||
// menu.add(new AddAliasIdentifierItem(AliasIDType.LOJACK));
|
||||
menu.add(new AddAliasIdentifierItem(AliasIDType.STATUS));
|
||||
|
||||
118
src/main/java/io/github/dsheirer/alias/id/radio/Radio.java
Normal file
118
src/main/java/io/github/dsheirer/alias/id/radio/Radio.java
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
package io.github.dsheirer.alias.id.radio;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.alias.id.AliasIDType;
|
||||
import io.github.dsheirer.alias.id.talkgroup.TalkgroupFormat;
|
||||
import io.github.dsheirer.alias.id.talkgroup.TalkgroupFormatter;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
|
||||
/**
|
||||
* Integer radio identifier with protocol.
|
||||
*/
|
||||
public class Radio extends AliasID
|
||||
{
|
||||
private Protocol mProtocol = Protocol.UNKNOWN;
|
||||
private int mValue;
|
||||
|
||||
public Radio()
|
||||
{
|
||||
//No arg JAXB constructor
|
||||
}
|
||||
|
||||
public Radio(Protocol protocol, int value)
|
||||
{
|
||||
mProtocol = protocol;
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "value")
|
||||
public int getValue()
|
||||
{
|
||||
return mValue;
|
||||
}
|
||||
|
||||
public void setValue(int value)
|
||||
{
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "protocol")
|
||||
public Protocol getProtocol()
|
||||
{
|
||||
return mProtocol;
|
||||
}
|
||||
|
||||
public void setProtocol(Protocol protocol)
|
||||
{
|
||||
mProtocol = protocol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid()
|
||||
{
|
||||
if(mProtocol == null || mProtocol == Protocol.UNKNOWN)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
RadioFormat radioFormat = RadioFormat.get(mProtocol);
|
||||
return radioFormat.getMinimumValidValue() <= mValue && mValue <= radioFormat.getMaximumValidValue();
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("Radio ID:").append(TalkgroupFormatter.format(mProtocol, mValue));
|
||||
sb.append(" Protocol:").append((mProtocol));
|
||||
|
||||
if(!isValid())
|
||||
{
|
||||
sb.append(" **NOT VALID**");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(AliasID id)
|
||||
{
|
||||
if(id instanceof Radio)
|
||||
{
|
||||
Radio tgid = (Radio)id;
|
||||
return (getProtocol() == tgid.getProtocol()) && (getValue() == tgid.getValue());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
||||
@Override
|
||||
public AliasIDType getType()
|
||||
{
|
||||
return AliasIDType.RADIO_ID;
|
||||
}
|
||||
}
|
||||
235
src/main/java/io/github/dsheirer/alias/id/radio/RadioEditor.java
Normal file
235
src/main/java/io/github/dsheirer/alias/id/radio/RadioEditor.java
Normal file
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
package io.github.dsheirer.alias.id.radio;
|
||||
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JFormattedTextField;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.text.MaskFormatter;
|
||||
import java.awt.Color;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.text.ParseException;
|
||||
|
||||
public class RadioEditor extends DocumentListenerEditor<AliasID>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(RadioEditor.class);
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String VALID_CHARACTERS_FOR_ASTERISK_MASKS = "0123456789 ";
|
||||
private MaskFormatter mMaskFormatter = new MaskFormatter();
|
||||
private JComboBox<Protocol> mComboProtocol;
|
||||
private JFormattedTextField mRadioIdField;
|
||||
|
||||
|
||||
public RadioEditor(AliasID aliasID)
|
||||
{
|
||||
initGUI();
|
||||
setItem(aliasID);
|
||||
}
|
||||
|
||||
private void initGUI()
|
||||
{
|
||||
setLayout(new MigLayout("fill,wrap 2", "[right][left]", "[][]"));
|
||||
|
||||
add(new JLabel("Protocol:"));
|
||||
|
||||
mComboProtocol = new JComboBox<>();
|
||||
|
||||
DefaultComboBoxModel<Protocol> model = new DefaultComboBoxModel<>();
|
||||
|
||||
for(Protocol protocol : Protocol.RADIO_ID_PROTOCOLS)
|
||||
{
|
||||
model.addElement(protocol);
|
||||
}
|
||||
|
||||
mComboProtocol.setModel(model);
|
||||
mComboProtocol.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
Protocol protocol = (Protocol)mComboProtocol.getSelectedItem();
|
||||
updateEditor(protocol);
|
||||
setModified(true);
|
||||
}
|
||||
});
|
||||
|
||||
add(mComboProtocol);
|
||||
|
||||
add(new JLabel("Value:"));
|
||||
|
||||
mRadioIdField = new JFormattedTextField(mMaskFormatter);
|
||||
mRadioIdField.getDocument().addDocumentListener(this);
|
||||
add(mRadioIdField, "growx,push");
|
||||
|
||||
JLabel help = new JLabel("Help ...");
|
||||
help.setForeground(Color.BLUE.brighter());
|
||||
help.setCursor(new Cursor(Cursor.HAND_CURSOR));
|
||||
help.addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e)
|
||||
{
|
||||
RadioFormat radioFormat = RadioFormat.get(getCurrentProtocol());
|
||||
JOptionPane.showMessageDialog(RadioEditor.this,
|
||||
radioFormat.getValidRangeHelpText(), "Help", JOptionPane.INFORMATION_MESSAGE);
|
||||
}
|
||||
});
|
||||
add(help, "align left");
|
||||
}
|
||||
|
||||
private void updateEditor(Protocol protocol)
|
||||
{
|
||||
RadioFormat mask = RadioFormat.get(protocol);
|
||||
int currentValue = 0;
|
||||
Radio radio = getRadio();
|
||||
|
||||
if(radio != null)
|
||||
{
|
||||
currentValue = radio.getValue();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
mRadioIdField.setValue(null);
|
||||
mRadioIdField.setToolTipText(mask.getValidRangeHelpText());
|
||||
mMaskFormatter.setMask(mask.getMask());
|
||||
if(mask.getMask().contains("*"))
|
||||
{
|
||||
mMaskFormatter.setValidCharacters(VALID_CHARACTERS_FOR_ASTERISK_MASKS);
|
||||
}
|
||||
mRadioIdField.setValue(RadioFormatter.format(protocol, currentValue));
|
||||
}
|
||||
catch(ParseException pe)
|
||||
{
|
||||
mLog.error("Error applying talkgroup editor mask to mask formatter [" + mask.getMask() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
private Protocol getCurrentProtocol()
|
||||
{
|
||||
return mComboProtocol.getItemAt(mComboProtocol.getSelectedIndex());
|
||||
}
|
||||
|
||||
/**
|
||||
* Current talkgroup alias id
|
||||
*/
|
||||
public Radio getRadio()
|
||||
{
|
||||
if(getItem() instanceof Radio)
|
||||
{
|
||||
return (Radio)getItem();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(AliasID aliasID)
|
||||
{
|
||||
super.setItem(aliasID);
|
||||
|
||||
Radio radio = getRadio();
|
||||
|
||||
if(radio != null)
|
||||
{
|
||||
Protocol currentProtocol = getCurrentProtocol();
|
||||
if(currentProtocol == radio.getProtocol())
|
||||
{
|
||||
updateEditor(radio.getProtocol());
|
||||
}
|
||||
mComboProtocol.getModel().setSelectedItem(radio.getProtocol());
|
||||
|
||||
String formatted = RadioFormatter.format(radio.getProtocol(), radio.getValue());
|
||||
mRadioIdField.setValue(formatted);
|
||||
}
|
||||
else
|
||||
{
|
||||
mComboProtocol.getModel().setSelectedItem(Protocol.UNKNOWN);
|
||||
mRadioIdField.setValue(RadioFormatter.format(getCurrentProtocol(), 0));
|
||||
}
|
||||
|
||||
setModified(false);
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
Radio radio = getRadio();
|
||||
|
||||
if(radio != null)
|
||||
{
|
||||
Protocol protocol = mComboProtocol.getItemAt(mComboProtocol.getSelectedIndex());
|
||||
|
||||
if(protocol == null)
|
||||
{
|
||||
protocol = Protocol.UNKNOWN;
|
||||
}
|
||||
|
||||
int value = -1;
|
||||
|
||||
try
|
||||
{
|
||||
value = RadioFormatter.parse(protocol, mRadioIdField.getText());
|
||||
}
|
||||
catch(ParseException pe)
|
||||
{
|
||||
//ignore ... value is still -1 and outside valid value range
|
||||
}
|
||||
|
||||
RadioFormat mask = RadioFormat.get(protocol);
|
||||
|
||||
//Check for valid value within range ... notify user but allow value to persist
|
||||
if(value < mask.getMinimumValidValue() || value > mask.getMaximumValidValue())
|
||||
{
|
||||
String message = "Invalid value [" + mRadioIdField.getText() + "]. " + protocol.name() +
|
||||
" valid range is [" + mask.getValidRangeDescription() + "]";
|
||||
|
||||
JOptionPane.showMessageDialog(RadioEditor.this, message, "Invalid Radio ID Value",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
else
|
||||
{
|
||||
radio.setValue(value);
|
||||
}
|
||||
|
||||
radio.setProtocol(protocol);
|
||||
}
|
||||
|
||||
setModified(false);
|
||||
}
|
||||
}
|
||||
117
src/main/java/io/github/dsheirer/alias/id/radio/RadioFormat.java
Normal file
117
src/main/java/io/github/dsheirer/alias/id/radio/RadioFormat.java
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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.alias.id.radio;
|
||||
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
|
||||
/**
|
||||
* Radio ID protocol identifier entry masks and min/max valid ranges and valid range descriptions
|
||||
*
|
||||
* Note: the getMask() value is intended for use with a MaskFormatter attached to a JFormattedTextField. The
|
||||
* protocol masks that have a dash (-) use the # (any digit) character and the protocol masks that do not have
|
||||
* a formatting dash use an asterisk (*). Within the editor(s) that use this enumeration, the editor must use
|
||||
* a setValidCharacters() on the mask formatter whenever it detects the mask containing one or more asterisks.
|
||||
*/
|
||||
public enum RadioFormat
|
||||
{
|
||||
APCO25("********", 0, 0xFFFFFF, "0 to 16,777,215",
|
||||
"<html>APCO25 valid range is 0 to 16,777,215"),
|
||||
UNKNOWN("********", 1, 0xFFFFFF, "1 to 16,777,215",
|
||||
"Unknown protocol valid value range is 1-16,777,215");
|
||||
|
||||
private String mMask;
|
||||
private int mMinimumValue;
|
||||
private int mMaximumValue;
|
||||
private String mValidRangeDescription;
|
||||
private String mValidRangeHelpText;
|
||||
|
||||
RadioFormat(String mask, int minimumValue, int maximumValue, String validRangeDescription, String validRangeHelpText)
|
||||
{
|
||||
mMask = mask;
|
||||
mMinimumValue = minimumValue;
|
||||
mMaximumValue = maximumValue;
|
||||
mValidRangeDescription = validRangeDescription;
|
||||
mValidRangeHelpText = validRangeHelpText;
|
||||
}
|
||||
|
||||
public String getMask()
|
||||
{
|
||||
return mMask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimum valid value for the protocol
|
||||
*/
|
||||
public int getMinimumValidValue()
|
||||
{
|
||||
return mMinimumValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum valid value for the protocol
|
||||
*/
|
||||
public int getMaximumValidValue()
|
||||
{
|
||||
return mMaximumValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the value is valid for the protocol
|
||||
*/
|
||||
public boolean isValid(int value)
|
||||
{
|
||||
return getMinimumValidValue() <= value && value <= getMaximumValidValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Short description of the valid value range
|
||||
*/
|
||||
public String getValidRangeDescription()
|
||||
{
|
||||
return mValidRangeDescription;
|
||||
}
|
||||
|
||||
/**
|
||||
* Help text for valid value ranges
|
||||
*/
|
||||
public String getValidRangeHelpText()
|
||||
{
|
||||
return mValidRangeHelpText;
|
||||
}
|
||||
|
||||
public static RadioFormat get(Protocol protocol)
|
||||
{
|
||||
if(protocol == null)
|
||||
{
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
switch(protocol)
|
||||
{
|
||||
case APCO25:
|
||||
return APCO25;
|
||||
default:
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.alias.id.radio;
|
||||
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.APCO25TalkgroupFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.IntegerFormatter;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Protocol-specific formatting of radio id values and parsing of formatted radio id values.
|
||||
*/
|
||||
public class RadioFormatter
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(RadioFormatter.class);
|
||||
private static Map<Protocol,IntegerFormatter> mFormatterMap = new HashMap<>();
|
||||
|
||||
static
|
||||
{
|
||||
mFormatterMap.put(Protocol.APCO25, new APCO25TalkgroupFormatter());
|
||||
mFormatterMap.put(Protocol.UNKNOWN, new IntegerFormatter());
|
||||
}
|
||||
|
||||
public RadioFormatter()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the formatted value into an integer using a protocol specific formatter
|
||||
*/
|
||||
public static int parse(Protocol protocol, String value) throws ParseException
|
||||
{
|
||||
IntegerFormatter formatter = mFormatterMap.get(protocol);
|
||||
|
||||
if(formatter == null)
|
||||
{
|
||||
formatter = mFormatterMap.get(Protocol.UNKNOWN);
|
||||
}
|
||||
|
||||
return formatter.parse(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the integer value using a protocol-specific formatter
|
||||
*/
|
||||
public static String format(Protocol protocol, int value)
|
||||
{
|
||||
IntegerFormatter formatter = mFormatterMap.get(protocol);
|
||||
|
||||
if(formatter == null)
|
||||
{
|
||||
formatter = mFormatterMap.get(Protocol.UNKNOWN);
|
||||
}
|
||||
|
||||
return formatter.format(value);
|
||||
}
|
||||
}
|
||||
166
src/main/java/io/github/dsheirer/alias/id/radio/RadioRange.java
Normal file
166
src/main/java/io/github/dsheirer/alias/id/radio/RadioRange.java
Normal file
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
package io.github.dsheirer.alias.id.radio;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.alias.id.AliasIDType;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
|
||||
/**
|
||||
* Integer radio identifier range of values with protocol.
|
||||
*/
|
||||
public class RadioRange extends AliasID
|
||||
{
|
||||
private Protocol mProtocol = Protocol.UNKNOWN;
|
||||
private int mMinRadio;
|
||||
private int mMaxRadio;
|
||||
|
||||
public RadioRange()
|
||||
{
|
||||
//No arg JAXB constructor
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a radio range of from - to radio values (inclusive) for the specified protocol
|
||||
* @param protocol for the radio range
|
||||
* @param minRadio starting or minimum radio value
|
||||
* @param maxRadio ending or maximum radio value
|
||||
*/
|
||||
public RadioRange(Protocol protocol, int minRadio, int maxRadio)
|
||||
{
|
||||
mProtocol = protocol;
|
||||
mMinRadio = minRadio;
|
||||
mMaxRadio = maxRadio;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "min")
|
||||
public int getMinRadio()
|
||||
{
|
||||
return mMinRadio;
|
||||
}
|
||||
|
||||
public void setMinRadio(int minRadio)
|
||||
{
|
||||
mMinRadio = minRadio;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "max")
|
||||
public int getMaxRadio()
|
||||
{
|
||||
return mMaxRadio;
|
||||
}
|
||||
|
||||
public void setMaxRadio(int maxRadio)
|
||||
{
|
||||
mMaxRadio = maxRadio;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "protocol")
|
||||
public Protocol getProtocol()
|
||||
{
|
||||
return mProtocol;
|
||||
}
|
||||
|
||||
public void setProtocol(Protocol protocol)
|
||||
{
|
||||
mProtocol = protocol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid()
|
||||
{
|
||||
if(mProtocol == null || mProtocol == Protocol.UNKNOWN)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
RadioFormat radioFormat = RadioFormat.get(mProtocol);
|
||||
return radioFormat.getMinimumValidValue() <= mMinRadio &&
|
||||
mMinRadio <= radioFormat.getMaximumValidValue() &&
|
||||
radioFormat.getMinimumValidValue() <= mMaxRadio &&
|
||||
mMaxRadio <= radioFormat.getMaximumValidValue();
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("Radio ID Range:").append(RadioFormatter.format(mProtocol, mMinRadio));
|
||||
sb.append(" to ").append(RadioFormatter.format(mProtocol, mMaxRadio));
|
||||
sb.append(" Protocol:").append(mProtocol != null ? mProtocol : "unspecified");
|
||||
|
||||
if(!isValid())
|
||||
{
|
||||
sb.append(" **NOT VALID**");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(AliasID id)
|
||||
{
|
||||
if(id instanceof RadioRange)
|
||||
{
|
||||
RadioRange tgid = (RadioRange)id;
|
||||
return (getProtocol() == tgid.getProtocol()) &&
|
||||
(getMinRadio() == tgid.getMinRadio()) &&
|
||||
(getMaxRadio() == tgid.getMaxRadio());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this radio range contains/includes the value
|
||||
* @param radioValue
|
||||
* @return
|
||||
*/
|
||||
public boolean contains(int radioValue)
|
||||
{
|
||||
return getMinRadio() <= radioValue && radioValue <= getMaxRadio();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this talkgroup range overlaps the talkgroup range argument.
|
||||
* @param radioRange to check for overlap
|
||||
* @return true if the ranges overlap
|
||||
*/
|
||||
public boolean overlaps(RadioRange radioRange)
|
||||
{
|
||||
return contains(radioRange.getMinRadio()) ||
|
||||
contains(radioRange.getMaxRadio()) ||
|
||||
(getMinRadio() < radioRange.getMinRadio() &&
|
||||
radioRange.getMaxRadio() < getMaxRadio()) ||
|
||||
(radioRange.getMinRadio() < getMinRadio() &&
|
||||
getMaxRadio() < radioRange.getMaxRadio());
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
||||
@Override
|
||||
public AliasIDType getType()
|
||||
{
|
||||
return AliasIDType.RADIO_ID_RANGE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
package io.github.dsheirer.alias.id.radio;
|
||||
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JFormattedTextField;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.text.MaskFormatter;
|
||||
import java.awt.Color;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.text.ParseException;
|
||||
|
||||
public class RadioRangeEditor extends DocumentListenerEditor<AliasID>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(RadioRangeEditor.class);
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String VALID_CHARACTERS_FOR_ASTERISK_MASKS = "0123456789 ";
|
||||
private MaskFormatter mMaskFormatter = new MaskFormatter();
|
||||
private JComboBox<Protocol> mComboProtocol;
|
||||
private JFormattedTextField mMinRadioField;
|
||||
private JFormattedTextField mMaxRadioField;
|
||||
|
||||
public RadioRangeEditor(AliasID aliasID)
|
||||
{
|
||||
initGUI();
|
||||
|
||||
setItem(aliasID);
|
||||
}
|
||||
|
||||
private void initGUI()
|
||||
{
|
||||
setLayout(new MigLayout("fill,wrap 2", "[right][left]", "[][]"));
|
||||
|
||||
add(new JLabel("Protocol:"));
|
||||
|
||||
mComboProtocol = new JComboBox<Protocol>();
|
||||
|
||||
DefaultComboBoxModel<Protocol> model = new DefaultComboBoxModel<>();
|
||||
|
||||
for(Protocol protocol : Protocol.RADIO_ID_PROTOCOLS)
|
||||
{
|
||||
model.addElement(protocol);
|
||||
}
|
||||
|
||||
mComboProtocol.setModel(model);
|
||||
mComboProtocol.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
Protocol protocol = (Protocol)mComboProtocol.getSelectedItem();
|
||||
updateEditor(protocol);
|
||||
setModified(true);
|
||||
}
|
||||
});
|
||||
|
||||
add(mComboProtocol);
|
||||
|
||||
add(new JLabel("Min:"));
|
||||
|
||||
mMinRadioField = new JFormattedTextField(mMaskFormatter);
|
||||
mMinRadioField.getDocument().addDocumentListener(this);
|
||||
add(mMinRadioField, "growx,push");
|
||||
|
||||
add(new JLabel("Max:"));
|
||||
|
||||
mMaxRadioField = new JFormattedTextField(mMaskFormatter);
|
||||
mMaxRadioField.getDocument().addDocumentListener(this);
|
||||
add(mMaxRadioField, "growx,push");
|
||||
|
||||
JLabel help = new JLabel("Help ...");
|
||||
help.setForeground(Color.BLUE.brighter());
|
||||
help.setCursor(new Cursor(Cursor.HAND_CURSOR));
|
||||
help.addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e)
|
||||
{
|
||||
RadioFormat radioFormat = RadioFormat.get(getCurrentProtocol());
|
||||
JOptionPane.showMessageDialog(RadioRangeEditor.this,
|
||||
radioFormat.getValidRangeHelpText(), "Help", JOptionPane.INFORMATION_MESSAGE);
|
||||
}
|
||||
});
|
||||
add(help, "align left");
|
||||
}
|
||||
|
||||
private void updateEditor(Protocol protocol)
|
||||
{
|
||||
RadioFormat mask = RadioFormat.get(protocol);
|
||||
int currentMinValue = 0;
|
||||
int currentMaxValue = 0;
|
||||
RadioRange radioRange = getRadioRange();
|
||||
|
||||
if(radioRange != null)
|
||||
{
|
||||
currentMinValue = radioRange.getMinRadio();
|
||||
currentMaxValue = radioRange.getMaxRadio();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
mMinRadioField.setValue(null);
|
||||
mMaxRadioField.setValue(null);
|
||||
|
||||
mMinRadioField.setToolTipText(mask.getValidRangeHelpText());
|
||||
mMaxRadioField.setToolTipText(mask.getValidRangeHelpText());
|
||||
mMaskFormatter.setMask(mask.getMask());
|
||||
if(mask.getMask().contains("*"))
|
||||
{
|
||||
mMaskFormatter.setValidCharacters(VALID_CHARACTERS_FOR_ASTERISK_MASKS);
|
||||
}
|
||||
|
||||
mMinRadioField.setValue(RadioFormatter.format(protocol, currentMinValue));
|
||||
mMaxRadioField.setValue(RadioFormatter.format(protocol, currentMaxValue));
|
||||
}
|
||||
catch(ParseException pe)
|
||||
{
|
||||
mLog.error("Error applying radio editor mask to mask formatter [" + mask.getMask() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
private Protocol getCurrentProtocol()
|
||||
{
|
||||
return mComboProtocol.getItemAt(mComboProtocol.getSelectedIndex());
|
||||
}
|
||||
|
||||
|
||||
|
||||
public RadioRange getRadioRange()
|
||||
{
|
||||
if(getItem() instanceof RadioRange)
|
||||
{
|
||||
return (RadioRange)getItem();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(AliasID aliasID)
|
||||
{
|
||||
super.setItem(aliasID);
|
||||
|
||||
RadioRange radioRange = getRadioRange();
|
||||
|
||||
updateEditor(radioRange.getProtocol());
|
||||
|
||||
if(radioRange != null)
|
||||
{
|
||||
mComboProtocol.getModel().setSelectedItem(radioRange.getProtocol());
|
||||
String minFormatted = RadioFormatter.format(radioRange.getProtocol(), radioRange.getMinRadio());
|
||||
mMinRadioField.setValue(minFormatted);
|
||||
String maxFormatted = RadioFormatter.format(radioRange.getProtocol(), radioRange.getMaxRadio());
|
||||
mMaxRadioField.setValue(maxFormatted);
|
||||
}
|
||||
else
|
||||
{
|
||||
mComboProtocol.getModel().setSelectedItem(Protocol.UNKNOWN);
|
||||
mMinRadioField.setValue(RadioFormatter.format(getCurrentProtocol(), 0));
|
||||
mMaxRadioField.setValue(RadioFormatter.format(getCurrentProtocol(), 0));
|
||||
}
|
||||
|
||||
setModified(false);
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
RadioRange radioRange = getRadioRange();
|
||||
|
||||
if(radioRange != null)
|
||||
{
|
||||
Protocol protocol = mComboProtocol.getItemAt(mComboProtocol.getSelectedIndex());
|
||||
|
||||
if(protocol == null)
|
||||
{
|
||||
protocol = Protocol.UNKNOWN;
|
||||
}
|
||||
|
||||
radioRange.setProtocol(protocol);
|
||||
|
||||
int minValue = -1;
|
||||
int maxValue = -1;
|
||||
|
||||
try
|
||||
{
|
||||
minValue = RadioFormatter.parse(protocol, mMinRadioField.getText());
|
||||
}
|
||||
catch(ParseException pe)
|
||||
{
|
||||
//ignore ... value is still -1 and outside valid value range
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
maxValue = RadioFormatter.parse(protocol, mMaxRadioField.getText());
|
||||
}
|
||||
catch(ParseException pe)
|
||||
{
|
||||
//ignore ... value is still -1 and outside valid value range
|
||||
}
|
||||
|
||||
RadioFormat mask = RadioFormat.get(protocol);
|
||||
|
||||
//Check for valid value within range ... notify user but allow value to persist
|
||||
if(minValue < mask.getMinimumValidValue() || minValue > mask.getMaximumValidValue())
|
||||
{
|
||||
String message = "Invalid minimum value [" + mMinRadioField.getText() + "]. " + protocol.name() +
|
||||
" valid range is [" + mask.getValidRangeDescription() + "]";
|
||||
|
||||
JOptionPane.showMessageDialog(RadioRangeEditor.this, message,
|
||||
"Invalid Radio Value", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
else
|
||||
{
|
||||
radioRange.setMinRadio(minValue);
|
||||
}
|
||||
|
||||
//Check for valid value within range ... notify user but allow value to persist
|
||||
if(maxValue < mask.getMinimumValidValue() || maxValue > mask.getMaximumValidValue())
|
||||
{
|
||||
String message = "Invalid maximum value [" + mMaxRadioField.getText() + "]. " + protocol.name() +
|
||||
" valid range is [" + mask.getValidRangeDescription() + "]";
|
||||
|
||||
JOptionPane.showMessageDialog(RadioRangeEditor.this, message,
|
||||
"Invalid Radio Value", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
else
|
||||
{
|
||||
radioRange.setMaxRadio(maxValue);
|
||||
}
|
||||
|
||||
radioRange.setProtocol(protocol);
|
||||
}
|
||||
|
||||
setModified(false);
|
||||
}
|
||||
}
|
||||
@@ -193,6 +193,11 @@ public class TalkgroupEditor extends DocumentListenerEditor<AliasID>
|
||||
{
|
||||
Protocol protocol = mComboProtocol.getItemAt(mComboProtocol.getSelectedIndex());
|
||||
|
||||
if(protocol == null)
|
||||
{
|
||||
protocol = Protocol.UNKNOWN;
|
||||
}
|
||||
|
||||
int value = -1;
|
||||
|
||||
try
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.alias.id.talkgroup;
|
||||
@@ -32,8 +34,8 @@ import io.github.dsheirer.protocol.Protocol;
|
||||
*/
|
||||
public enum TalkgroupFormat
|
||||
{
|
||||
APCO25("********", 1, 0xFFFFFF, "1 to 16,777,215",
|
||||
"<html>APCO25 valid range is 1 to 65,535(talkgroup)<br>or 1 to 16,777,215 (radio ID)"),
|
||||
APCO25("********", 0, 0xFFFFFF, "0 to 65,535",
|
||||
"<html>APCO25 talkgroup valid range is 0 to 65,535"),
|
||||
FLEETSYNC("###-####", 1, 0x7FFFFF, "001-0001 to 127-8192",
|
||||
"<html>Fleetsync valid ranges are 1-127(prefix)<br>and 1-8192(ident) (ie. 001-0001 to 127-8192)"),
|
||||
LTR("##-###", 257, 5375, "01-001 to 20-255",
|
||||
@@ -83,6 +85,14 @@ public enum TalkgroupFormat
|
||||
return mMaximumValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the value is valid for the protocol
|
||||
*/
|
||||
public boolean isValid(int value)
|
||||
{
|
||||
return getMinimumValidValue() <= value && value <= getMaximumValidValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Short description of the valid value range
|
||||
*/
|
||||
@@ -99,9 +109,14 @@ public enum TalkgroupFormat
|
||||
return mValidRangeHelpText;
|
||||
}
|
||||
|
||||
public static TalkgroupFormat get(Protocol prococol)
|
||||
public static TalkgroupFormat get(Protocol protocol)
|
||||
{
|
||||
switch(prococol)
|
||||
if(protocol == null)
|
||||
{
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
switch(protocol)
|
||||
{
|
||||
case APCO25:
|
||||
return APCO25;
|
||||
|
||||
@@ -140,6 +140,21 @@ public class TalkgroupRange extends AliasID
|
||||
return getMinTalkgroup() <= talkgroupValue && talkgroupValue <= getMaxTalkgroup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this talkgroup range overlaps the talkgroup range argument.
|
||||
* @param talkgroupRange to check for overlap
|
||||
* @return true if the ranges overlap
|
||||
*/
|
||||
public boolean overlaps(TalkgroupRange talkgroupRange)
|
||||
{
|
||||
return contains(talkgroupRange.getMinTalkgroup()) ||
|
||||
contains(talkgroupRange.getMaxTalkgroup()) ||
|
||||
(getMinTalkgroup() < talkgroupRange.getMinTalkgroup() &&
|
||||
talkgroupRange.getMaxTalkgroup() < getMaxTalkgroup()) ||
|
||||
(talkgroupRange.getMinTalkgroup() < getMinTalkgroup() &&
|
||||
getMaxTalkgroup() < talkgroupRange.getMaxTalkgroup());
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
||||
@Override
|
||||
public AliasIDType getType()
|
||||
|
||||
@@ -174,6 +174,8 @@ public class TalkgroupRangeEditor extends DocumentListenerEditor<AliasID>
|
||||
|
||||
TalkgroupRange talkgroupRange = getTalkgroupRange();
|
||||
|
||||
updateEditor(talkgroupRange.getProtocol());
|
||||
|
||||
if(talkgroupRange != null)
|
||||
{
|
||||
mComboProtocol.getModel().setSelectedItem(talkgroupRange.getProtocol());
|
||||
@@ -202,6 +204,12 @@ public class TalkgroupRangeEditor extends DocumentListenerEditor<AliasID>
|
||||
if(talkgroupRange != null)
|
||||
{
|
||||
Protocol protocol = mComboProtocol.getItemAt(mComboProtocol.getSelectedIndex());
|
||||
|
||||
if(protocol == null)
|
||||
{
|
||||
protocol = Protocol.UNKNOWN;
|
||||
}
|
||||
|
||||
talkgroupRange.setProtocol(protocol);
|
||||
|
||||
int minValue = -1;
|
||||
|
||||
@@ -20,41 +20,134 @@
|
||||
|
||||
package io.github.dsheirer.audio;
|
||||
|
||||
import io.github.dsheirer.audio.squelch.ISquelchStateListener;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.identifier.IdentifierUpdateListener;
|
||||
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
||||
import io.github.dsheirer.module.Module;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Base audio module implementation.
|
||||
*/
|
||||
public abstract class AbstractAudioModule extends Module implements IAudioPacketProvider, IdentifierUpdateListener,
|
||||
ISquelchStateListener
|
||||
public abstract class AbstractAudioModule extends Module implements IAudioSegmentProvider, IdentifierUpdateListener
|
||||
{
|
||||
//Static unique audio channel identifier.
|
||||
private static int AUDIO_CHANNEL_ID_GENERATOR = 1;
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AbstractAudioModule.class);
|
||||
private static final int MAX_SEGMENT_AUDIO_SAMPLE_LENGTH = 8000 * 60 * 1; //8 kHz - 1 minute
|
||||
|
||||
private int mAudioChannelId = AUDIO_CHANNEL_ID_GENERATOR++;
|
||||
private Listener<ReusableAudioPacket> mAudioPacketListener;
|
||||
private MutableIdentifierCollection mIdentifierCollection = new MutableIdentifierCollection();
|
||||
private Listener<AudioSegment> mAudioSegmentListener;
|
||||
protected MutableIdentifierCollection mIdentifierCollection = new MutableIdentifierCollection();
|
||||
private Broadcaster<IdentifierUpdateNotification> mIdentifierUpdateNotificationBroadcaster = new Broadcaster<>();
|
||||
private AliasList mAliasList;
|
||||
private AudioSegment mAudioSegment;
|
||||
private int mAudioSampleCount = 0;
|
||||
private boolean mRecordAudioOverride;
|
||||
|
||||
/**
|
||||
* Constructs an abstract audio module
|
||||
*/
|
||||
public AbstractAudioModule()
|
||||
public AbstractAudioModule(AliasList aliasList)
|
||||
{
|
||||
mAliasList = aliasList;
|
||||
mIdentifierUpdateNotificationBroadcaster.addListener(mIdentifierCollection);
|
||||
}
|
||||
|
||||
protected abstract int getTimeslot();
|
||||
|
||||
/**
|
||||
* Closes the current audio segment
|
||||
*/
|
||||
protected void closeAudioSegment()
|
||||
{
|
||||
synchronized(this)
|
||||
{
|
||||
if(mAudioSegment != null)
|
||||
{
|
||||
mAudioSegment.completeProperty().set(true);
|
||||
mIdentifierUpdateNotificationBroadcaster.removeListener(mAudioSegment);
|
||||
mAudioSegment = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
closeAudioSegment();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unique channel identifier for use in tagging audio packets with an audio channel ID so that they can
|
||||
* be identified within a combined audio packet stream
|
||||
* Gets the current audio segment, or creates a new audio segment as necessary and broadcasts it to any registered
|
||||
* listener(s).
|
||||
*/
|
||||
public int getAudioChannelId()
|
||||
protected AudioSegment getAudioSegment()
|
||||
{
|
||||
return mAudioChannelId;
|
||||
synchronized(this)
|
||||
{
|
||||
if(mAudioSegment == null)
|
||||
{
|
||||
mAudioSegment = new AudioSegment(mAliasList, getTimeslot());
|
||||
mAudioSegment.addIdentifiers(mIdentifierCollection.getIdentifiers());
|
||||
mIdentifierUpdateNotificationBroadcaster.addListener(mAudioSegment);
|
||||
|
||||
if(mRecordAudioOverride)
|
||||
{
|
||||
mAudioSegment.recordAudioProperty().set(true);
|
||||
}
|
||||
|
||||
if(mAudioSegmentListener != null)
|
||||
{
|
||||
mAudioSegment.incrementConsumerCount();
|
||||
mAudioSegmentListener.receive(mAudioSegment);
|
||||
}
|
||||
|
||||
mAudioSampleCount = 0;
|
||||
}
|
||||
|
||||
return mAudioSegment;
|
||||
}
|
||||
}
|
||||
|
||||
protected void addAudio(float[] audioBuffer)
|
||||
{
|
||||
AudioSegment audioSegment = getAudioSegment();
|
||||
|
||||
//If the current segment exceeds the max samples length, close it so that a new segment gets generated
|
||||
//and then link the segments together
|
||||
if(mAudioSampleCount >= MAX_SEGMENT_AUDIO_SAMPLE_LENGTH)
|
||||
{
|
||||
AudioSegment previous = getAudioSegment();
|
||||
closeAudioSegment();
|
||||
audioSegment = getAudioSegment();
|
||||
audioSegment.linkTo(previous);
|
||||
}
|
||||
|
||||
audioSegment.addAudio(audioBuffer);
|
||||
mAudioSampleCount += audioBuffer.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets all audio segments as recordable when the argument is true. Otherwise, defers to the aliased identifiers
|
||||
* from the identifier collection to determine whether to record the audio or not.
|
||||
* @param recordAudio set to true to mark all audio as recordable.
|
||||
*/
|
||||
public void setRecordAudio(boolean recordAudio)
|
||||
{
|
||||
mRecordAudioOverride = recordAudio;
|
||||
|
||||
if(mRecordAudioOverride)
|
||||
{
|
||||
synchronized(this)
|
||||
{
|
||||
if(mAudioSegment != null)
|
||||
{
|
||||
mAudioSegment.recordAudioProperty().set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,7 +156,7 @@ public abstract class AbstractAudioModule extends Module implements IAudioPacket
|
||||
@Override
|
||||
public Listener<IdentifierUpdateNotification> getIdentifierUpdateListener()
|
||||
{
|
||||
return mIdentifierCollection;
|
||||
return mIdentifierUpdateNotificationBroadcaster;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,37 +168,20 @@ public abstract class AbstractAudioModule extends Module implements IAudioPacket
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an audio packet listener to receive the output from this audio module.
|
||||
* Registers an audio segment listener to receive the output from this audio module.
|
||||
*/
|
||||
@Override
|
||||
public void setAudioPacketListener(Listener<ReusableAudioPacket> listener)
|
||||
public void setAudioSegmentListener(Listener<AudioSegment> listener)
|
||||
{
|
||||
mAudioPacketListener = listener;
|
||||
mAudioSegmentListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the audio packet listener from receiving audio packets from this module.
|
||||
* Unregisters the audio segment listener from receiving audio segments from this module.
|
||||
*/
|
||||
@Override
|
||||
public void removeAudioPacketListener()
|
||||
public void removeAudioSegmentListener()
|
||||
{
|
||||
mAudioPacketListener = null;
|
||||
mAudioSegmentListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registered listener for receiving audio packets produced by this module
|
||||
*/
|
||||
public Listener<ReusableAudioPacket> getAudioPacketListener()
|
||||
{
|
||||
return mAudioPacketListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if there is a listener registered to receive audio packets from this audio module.
|
||||
*/
|
||||
public boolean hasAudioPacketListener()
|
||||
{
|
||||
return mAudioPacketListener != null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* Processes audio packets to assign attributes to each packet based on alias list settings. Updates the
|
||||
* monitoring, recording and streaming attributes.
|
||||
*/
|
||||
public class AudioMetadataProcessor
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(AudioMetadataProcessor.class);
|
||||
private AliasModel mAliasModel;
|
||||
private Map<Integer, AliasList> mAliasListMap = new HashMap<>();
|
||||
private Set<Integer> mAliasListRetrievedSet = new TreeSet<>();
|
||||
|
||||
public AudioMetadataProcessor(AliasModel aliasModel)
|
||||
{
|
||||
mAliasModel = aliasModel;
|
||||
}
|
||||
|
||||
public void process(ReusableAudioPacket audioPacket)
|
||||
{
|
||||
if(audioPacket.hasIdentifierCollection() && audioPacket.getIdentifierCollection().hasAliasListConfiguration())
|
||||
{
|
||||
AliasList aliasList = null;
|
||||
|
||||
if(mAliasListRetrievedSet.contains(audioPacket.getAudioChannelId()))
|
||||
{
|
||||
aliasList = mAliasListMap.get(audioPacket.getAudioChannelId());
|
||||
}
|
||||
else
|
||||
{
|
||||
aliasList = mAliasModel.getAliasList(audioPacket.getIdentifierCollection().getAliasListConfiguration());
|
||||
|
||||
if(aliasList != null)
|
||||
{
|
||||
mAliasListMap.put(audioPacket.getAudioChannelId(), aliasList);
|
||||
mAliasListRetrievedSet.add(audioPacket.getAudioChannelId());
|
||||
}
|
||||
}
|
||||
|
||||
if(aliasList != null)
|
||||
{
|
||||
audioPacket.addBroadcastChannels(aliasList.getBroadcastChannels(audioPacket.getIdentifierCollection()));
|
||||
audioPacket.setMonitoringPriority(aliasList.getAudioPlaybackPriority(audioPacket.getIdentifierCollection()));
|
||||
|
||||
//If the audio packet is already marked recordable, leave it alone, otherwise attempt to determine
|
||||
//if we should record the audio packet from the aliased identifiers
|
||||
if(!audioPacket.isRecordable())
|
||||
{
|
||||
audioPacket.setRecordable(aliasList.isRecordable(audioPacket.getIdentifierCollection()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +1,49 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio;
|
||||
|
||||
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.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.remez.RemezFIRFilterDesigner;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
|
||||
import io.github.dsheirer.sample.buffer.ReusableAudioPacketQueue;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Provides packaging of demodulated audio sample buffers into audio packets for broadcast to registered audio packet
|
||||
* listeners. Includes audio packet metadata in constructed audio packets.
|
||||
* Provides packaging of demodulated audio sample buffers into audio segments for broadcast to registered listeners.
|
||||
* Includes audio packet metadata in constructed audio segments.
|
||||
*
|
||||
* Incorporates audio squelch state listener to control if audio packets are broadcast or ignored.
|
||||
*
|
||||
* This class is designed to support 8 kHz sample rate demodulated audio.
|
||||
*/
|
||||
public class AudioModule extends AbstractAudioModule implements IReusableBufferListener, Listener<ReusableFloatBuffer>
|
||||
public class AudioModule extends AbstractAudioModule implements ISquelchStateListener, IReusableBufferListener,
|
||||
Listener<ReusableFloatBuffer>
|
||||
{
|
||||
|
||||
private static final Logger mLog = LoggerFactory.getLogger(AudioModule.class);
|
||||
private static float[] sHighPassFilterCoefficients;
|
||||
|
||||
@@ -72,36 +73,31 @@ public class AudioModule extends AbstractAudioModule implements IReusableBufferL
|
||||
}
|
||||
}
|
||||
|
||||
private ReusableAudioPacketQueue mAudioPacketQueue = new ReusableAudioPacketQueue("AudioModule");
|
||||
private RealFIRFilter2 mHighPassFilter = new RealFIRFilter2(sHighPassFilterCoefficients);
|
||||
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
|
||||
private SquelchState mSquelchState = SquelchState.SQUELCH;
|
||||
private boolean mRecordAudioOverride;
|
||||
|
||||
/**
|
||||
* Creates an Audio Module.
|
||||
*/
|
||||
public AudioModule()
|
||||
public AudioModule(AliasList aliasList)
|
||||
{
|
||||
super(aliasList);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getTimeslot()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
removeAudioPacketListener();
|
||||
removeAudioSegmentListener();
|
||||
mSquelchStateListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets all audio packets as recordable when the argument is true. Otherwise, defers to the aliased identifiers
|
||||
* from the identifier collection to determine whether to record the audio or not.
|
||||
* @param recordAudio set to true to mark all audio as recordable.
|
||||
*/
|
||||
public void setRecordAudio(boolean recordAudio)
|
||||
{
|
||||
mRecordAudioOverride = recordAudio;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
@@ -111,32 +107,10 @@ public class AudioModule extends AbstractAudioModule implements IReusableBufferL
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
/* No start operations provided */
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
/* Issue an end-audio packet in case a recorder is still rolling */
|
||||
if(hasAudioPacketListener())
|
||||
{
|
||||
ReusableAudioPacket endAudioPacket = mAudioPacketQueue.getEndAudioBuffer();
|
||||
endAudioPacket.resetAttributes();
|
||||
endAudioPacket.setAudioChannelId(getAudioChannelId());
|
||||
endAudioPacket.setIdentifierCollection(getIdentifierCollection().copyOf());
|
||||
|
||||
if(mRecordAudioOverride)
|
||||
{
|
||||
endAudioPacket.setRecordable(true);
|
||||
}
|
||||
endAudioPacket.incrementUserCount();
|
||||
getAudioPacketListener().receive(endAudioPacket);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Listener<SquelchState> getSquelchStateListener()
|
||||
public Listener<SquelchStateEvent> getSquelchStateListener()
|
||||
{
|
||||
return mSquelchStateListener;
|
||||
}
|
||||
@@ -144,23 +118,11 @@ public class AudioModule extends AbstractAudioModule implements IReusableBufferL
|
||||
@Override
|
||||
public void receive(ReusableFloatBuffer reusableFloatBuffer)
|
||||
{
|
||||
if(hasAudioPacketListener() && mSquelchState == SquelchState.UNSQUELCH)
|
||||
if(mSquelchState == SquelchState.UNSQUELCH)
|
||||
{
|
||||
ReusableFloatBuffer highPassFiltered = mHighPassFilter.filter(reusableFloatBuffer);
|
||||
|
||||
ReusableAudioPacket audioPacket = mAudioPacketQueue.getBuffer(highPassFiltered.getSampleCount());
|
||||
audioPacket.resetAttributes();
|
||||
audioPacket.setAudioChannelId(getAudioChannelId());
|
||||
audioPacket.loadAudioFrom(highPassFiltered);
|
||||
if(mRecordAudioOverride)
|
||||
{
|
||||
audioPacket.setRecordable(true);
|
||||
}
|
||||
audioPacket.setIdentifierCollection(getIdentifierCollection().copyOf());
|
||||
|
||||
getAudioPacketListener().receive(audioPacket);
|
||||
|
||||
highPassFiltered.decrementUserCount();
|
||||
ReusableFloatBuffer buffer = mHighPassFilter.filter(reusableFloatBuffer);
|
||||
addAudio(buffer.getSamples());
|
||||
buffer.decrementUserCount();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -169,7 +131,7 @@ public class AudioModule extends AbstractAudioModule implements IReusableBufferL
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener getReusableBufferListener()
|
||||
public Listener<ReusableFloatBuffer> getReusableBufferListener()
|
||||
{
|
||||
//Redirect received reusable buffers to the receive(buffer) method
|
||||
return this;
|
||||
@@ -178,21 +140,17 @@ public class AudioModule extends AbstractAudioModule implements IReusableBufferL
|
||||
/**
|
||||
* Wrapper for squelch state listener
|
||||
*/
|
||||
public class SquelchStateListener implements Listener<SquelchState>
|
||||
public class SquelchStateListener implements Listener<SquelchStateEvent>
|
||||
{
|
||||
@Override
|
||||
public void receive(SquelchState state)
|
||||
public void receive(SquelchStateEvent event)
|
||||
{
|
||||
if(state == SquelchState.SQUELCH && hasAudioPacketListener())
|
||||
{
|
||||
ReusableAudioPacket endAudioPacket = mAudioPacketQueue.getEndAudioBuffer();
|
||||
endAudioPacket.resetAttributes();
|
||||
endAudioPacket.setAudioChannelId(getAudioChannelId());
|
||||
endAudioPacket.setIdentifierCollection(getIdentifierCollection().copyOf());
|
||||
getAudioPacketListener().receive(endAudioPacket);
|
||||
}
|
||||
mSquelchState = event.getSquelchState();
|
||||
|
||||
mSquelchState = state;
|
||||
if(mSquelchState == SquelchState.SQUELCH)
|
||||
{
|
||||
closeAudioSegment();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio;
|
||||
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.dsp.filter.channelizer.ContinuousReusableBufferProcessor;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Processes audio packets from all decoder channels, assigns alias attributes and distributes packets
|
||||
* for playback, streaming and recording.
|
||||
*/
|
||||
public class AudioPacketManager implements Listener<ReusableAudioPacket>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AudioPacketManager.class);
|
||||
private AudioMetadataProcessor mAudioMetadataProcessor;
|
||||
private ReusableBufferBroadcaster<ReusableAudioPacket> mBroadcaster = new ReusableBufferBroadcaster<>();
|
||||
private ContinuousReusableBufferProcessor<ReusableAudioPacket> mBufferProcessor;
|
||||
|
||||
public AudioPacketManager(AliasModel aliasModel)
|
||||
{
|
||||
mAudioMetadataProcessor = new AudioMetadataProcessor(aliasModel);
|
||||
mBufferProcessor = new ContinuousReusableBufferProcessor<>(10000, 500);
|
||||
mBufferProcessor.setListener(new Listener<List<ReusableAudioPacket>>()
|
||||
{
|
||||
@Override
|
||||
public void receive(List<ReusableAudioPacket> reusableAudioPackets)
|
||||
{
|
||||
for(ReusableAudioPacket audioPacket: reusableAudioPackets)
|
||||
{
|
||||
mAudioMetadataProcessor.process(audioPacket);
|
||||
mBroadcaster.broadcast(audioPacket);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void start()
|
||||
{
|
||||
mLog.info("Audio packet manager started");
|
||||
mBufferProcessor.start();
|
||||
}
|
||||
|
||||
public void stop()
|
||||
{
|
||||
mLog.info("Audio packet manager stopped");
|
||||
mBufferProcessor.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary processing method for receiving audio packets from all processing channels.
|
||||
*/
|
||||
@Override
|
||||
public void receive(ReusableAudioPacket audioPacket)
|
||||
{
|
||||
mBufferProcessor.receive(audioPacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the listener to receive enriched audio packets from this manager
|
||||
*/
|
||||
public void addListener(Listener<ReusableAudioPacket> listener)
|
||||
{
|
||||
mBroadcaster.addListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener from receiving enriched audio packets from this manager
|
||||
*/
|
||||
public void removeListener(Listener<ReusableAudioPacket> listener)
|
||||
{
|
||||
mBroadcaster.removeListener(listener);
|
||||
}
|
||||
}
|
||||
436
src/main/java/io/github/dsheirer/audio/AudioSegment.java
Normal file
436
src/main/java/io/github/dsheirer/audio/AudioSegment.java
Normal file
@@ -0,0 +1,436 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
package io.github.dsheirer.audio;
|
||||
|
||||
import io.github.dsheirer.alias.Alias;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
||||
import io.github.dsheirer.alias.id.priority.Priority;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
||||
import io.github.dsheirer.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;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Audio segment containing all related metadata and a dynamic collection of audio packets. An audio segment can be
|
||||
* a discrete (ie start/stop) audio event, or it may be a time-constrained portion of an ongoing continuous audio
|
||||
* broadcast. Since the audio segment is held in memory until all consumers have finished processing the segment,
|
||||
* producers should constrain the duration of each audio segment to a reasonable duration.
|
||||
*
|
||||
* Producers can link time-constrained audio segments from a continous broadcast (e.g. FM radio station) so that
|
||||
* consumers can identify audio segments that belong to a continous stream. This linkage presents a memory leak
|
||||
* potential. Therefore, the audio segment uses the consumer counter to trigger an unlinking via the dispose() mehtod.
|
||||
* Accurate accounting of consumer count via the increment/decrementConsumerCount() methods is essential for good
|
||||
* memory management.
|
||||
*
|
||||
* Producers will add audio buffers and update identifiers throughout the life-cycle of an audio segment. The producer
|
||||
* will signal the completion of an audio segment by setting the complete property to true. This allows consumers the
|
||||
* option to process the audio buffers throughout the life-cycle of the segment, or to process all of the buffers once
|
||||
* the segment is complete.
|
||||
*/
|
||||
public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AudioSegment.class);
|
||||
private BooleanProperty mComplete = new SimpleBooleanProperty(false);
|
||||
private BooleanProperty mRecordAudio = new SimpleBooleanProperty(false);
|
||||
private IntegerProperty mMonitorPriority = new SimpleIntegerProperty(Priority.DEFAULT_PRIORITY);
|
||||
private ObservableSet<BroadcastChannel> mBroadcastChannels = FXCollections.observableSet(new HashSet<>());
|
||||
private MutableIdentifierCollection mIdentifierCollection = new MutableIdentifierCollection();
|
||||
private Broadcaster<IdentifierUpdateNotification> mIdentifierUpdateNotificationBroadcaster = new Broadcaster<>();
|
||||
private List<float[]> mAudioBuffers = new CopyOnWriteArrayList();
|
||||
private AtomicInteger mConsumerCount = new AtomicInteger();
|
||||
private AliasList mAliasList;
|
||||
private long mStartTimestamp = System.currentTimeMillis();
|
||||
private boolean mDisposing = false;
|
||||
private AudioSegment mLinkedAudioSegment;
|
||||
private int mTimeslot;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param aliasList for accessing aliases associated with identifiers for this audio segment.
|
||||
*/
|
||||
public AudioSegment(AliasList aliasList, int timeslot)
|
||||
{
|
||||
mAliasList = aliasList;
|
||||
mTimeslot = timeslot;
|
||||
mIdentifierCollection.setTimeslot(timeslot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Timeslot for this audio segment
|
||||
*/
|
||||
public int getTimeslot()
|
||||
{
|
||||
return mTimeslot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creation timestamp for this audio segment
|
||||
* @return long milliseconds since epoch
|
||||
*/
|
||||
public long getStartTimestamp()
|
||||
{
|
||||
return mStartTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* The complete property is used by the audio segment producer to signal that the segment is complete and no
|
||||
* additional audio or identifiers will be added to the segment.
|
||||
*
|
||||
* @return complete property
|
||||
*/
|
||||
public BooleanProperty completeProperty()
|
||||
{
|
||||
return mComplete;
|
||||
}
|
||||
|
||||
/**
|
||||
* An observable list of broadcast channels for this audio segment. Broadcast channels are added to this segment
|
||||
* across the life-cycle of the segment. As each new alias identifier is added to the segment, any broadcast
|
||||
* channels assigned to the alias are added to this list. The audio segment producer can also add broadcast
|
||||
* channels to this list.
|
||||
*
|
||||
* @return observable set of broadcast channels
|
||||
*/
|
||||
public ObservableSet<BroadcastChannel> broadcastChannelsProperty()
|
||||
{
|
||||
return mBroadcastChannels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set of broadcast channels from identifier associated aliases for this segment.
|
||||
*/
|
||||
public Set<BroadcastChannel> getBroadcastChannels()
|
||||
{
|
||||
return Collections.unmodifiableSet(mBroadcastChannels);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this segment has audio streaming broadcast channels specified.
|
||||
*/
|
||||
public boolean hasBroadcastChannels()
|
||||
{
|
||||
return !mBroadcastChannels.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Property to signal that this audio segment should be recorded. This property can either be set by the producer
|
||||
* of the audio segment, or it can be flipped to true by any aliases that are added to this segment that require
|
||||
* audio associated with the identifier to be recorded.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public BooleanProperty recordAudioProperty()
|
||||
{
|
||||
return mRecordAudio;
|
||||
}
|
||||
|
||||
/**
|
||||
* Audio playback/monitor priority specified by identifier associated aliases for this segment.
|
||||
*/
|
||||
public IntegerProperty monitorPriorityProperty()
|
||||
{
|
||||
return mMonitorPriority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if at least one of the identifier associated aliases for this segments specifies Do Not Monitor.
|
||||
*/
|
||||
public boolean isDoNotMonitor()
|
||||
{
|
||||
return mMonitorPriority.get() <= Priority.DO_NOT_MONITOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias list for this audio segment
|
||||
*/
|
||||
public AliasList getAliasList()
|
||||
{
|
||||
return mAliasList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this audio segment is linked to a preceding audio segment.
|
||||
*/
|
||||
public boolean isLinked()
|
||||
{
|
||||
return mLinkedAudioSegment != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this audio segment is linked to the argument audio segment
|
||||
* @param audioSegment to check for linkage
|
||||
* @return true if this segment is linked to the argument
|
||||
*/
|
||||
public boolean isLinkedTo(AudioSegment audioSegment)
|
||||
{
|
||||
return isLinked() && audioSegment != null && mLinkedAudioSegment.equals(audioSegment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional linked audio segment is the audio segment that precedes this audio segment in a continuous audio
|
||||
* broadcast.
|
||||
*
|
||||
* @return linked audio segment or null.
|
||||
*/
|
||||
public AudioSegment getLinkedAudioSegment()
|
||||
{
|
||||
return mLinkedAudioSegment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Links this audio segment to a preceeding/previous audio segment
|
||||
* @param previousAudioSegment to set as the predecessor
|
||||
*/
|
||||
public void linkTo(AudioSegment previousAudioSegment)
|
||||
{
|
||||
mLinkedAudioSegment = previousAudioSegment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Immutable identifier collection for this audio segment
|
||||
*/
|
||||
public IdentifierCollection getIdentifierCollection()
|
||||
{
|
||||
return mIdentifierCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the collection of identifiers to this segment's identifier collection
|
||||
* @param identifiers to pre-load into this audio segment
|
||||
*/
|
||||
void addIdentifiers(Collection<Identifier> identifiers)
|
||||
{
|
||||
for(Identifier identifier: identifiers)
|
||||
{
|
||||
addIdentifier(identifier);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmodifiable copy of the list of audio buffers for this segment.
|
||||
*
|
||||
* @return list of audio buffers
|
||||
*/
|
||||
public List<float[]> getAudioBuffers()
|
||||
{
|
||||
return Collections.unmodifiableList(mAudioBuffers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count of audio buffers contained in this segment.
|
||||
*
|
||||
* Note: audio buffers can be added to an audio segment throughout the segment's life-cycle by the audio producer.
|
||||
*/
|
||||
public int getAudioBufferCount()
|
||||
{
|
||||
return mAudioBuffers.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the audio buffer at the specified index
|
||||
* @param index of the buffer to fetch
|
||||
* @return audio buffer
|
||||
* @throws IllegalArgumentException if requested index is not valid
|
||||
*/
|
||||
public float[] getAudioBuffer(int index)
|
||||
{
|
||||
if(0 <= index && index < getAudioBufferCount())
|
||||
{
|
||||
return mAudioBuffers.get(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalArgumentException("Requested audio buffer at index [" + index + "] does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this audio segment has one or more audio buffers
|
||||
*/
|
||||
public boolean hasAudio()
|
||||
{
|
||||
return !mAudioBuffers.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all audio buffers and decrements the user count on each so that the audio buffer can be reclaimed.
|
||||
*/
|
||||
private void dispose()
|
||||
{
|
||||
mDisposing = true;
|
||||
mAudioBuffers.clear();
|
||||
mIdentifierCollection.clear();
|
||||
mIdentifierUpdateNotificationBroadcaster.clear();
|
||||
mLinkedAudioSegment = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the consumer count to indicate that a consumer is currently processing this segment. When the
|
||||
* consumer count returns to zero, this indicates that all consumers are finished with the audio segment and the
|
||||
* resources can be reclaimed.
|
||||
*
|
||||
* Consumer count should only be increased by the producer of the audio segment, or if a consumer distributes the
|
||||
* segment to additional consumers.
|
||||
*/
|
||||
public void incrementConsumerCount()
|
||||
{
|
||||
mConsumerCount.incrementAndGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrements the consumer count. Consumers of this audio segment should invoke this method to signal that they
|
||||
* will no longer need this audio segment. When all consumers are finished with an audio segment, the audio
|
||||
* segment resources will be reclaimed.
|
||||
*/
|
||||
public void decrementConsumerCount()
|
||||
{
|
||||
int count = mConsumerCount.decrementAndGet();
|
||||
|
||||
if(count <= 0)
|
||||
{
|
||||
dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an audio buffer to this segment. Note the producer of the audio buffer should increment the user count
|
||||
* of the buffer prior to adding it to this segment. This segment will decrement the audio buffer user count once
|
||||
* all consumers of this audio segment have de-registered via the decrementConsumerCount() method.
|
||||
*
|
||||
* @param audioBuffer to add to this segment
|
||||
*/
|
||||
public void addAudio(float[] audioBuffer)
|
||||
{
|
||||
if(audioBuffer == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Can't add null audio buffer");
|
||||
}
|
||||
|
||||
if(mDisposing)
|
||||
{
|
||||
throw new IllegalStateException("Can't add audio to an audio segment that is being disposed");
|
||||
}
|
||||
|
||||
mAudioBuffers.add(audioBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to receive identifier update notifications
|
||||
*/
|
||||
public void addIdentifierUpdateNotificationListener(Listener<IdentifierUpdateNotification> listener)
|
||||
{
|
||||
mIdentifierUpdateNotificationBroadcaster.addListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the identifier update listener.
|
||||
*/
|
||||
public void removeIdentifierUpdateNotificationListener(Listener<IdentifierUpdateNotification> listener)
|
||||
{
|
||||
mIdentifierUpdateNotificationBroadcaster.removeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the identifier to this audio segment and updates record, priority and streaming properties.
|
||||
*
|
||||
* Note: identifiers pass to this method must be checked for timeslot match.
|
||||
*/
|
||||
public void addIdentifier(Identifier identifier)
|
||||
{
|
||||
mIdentifierCollection.update(identifier);
|
||||
|
||||
List<Alias> aliases = mAliasList.getAliases(identifier);
|
||||
|
||||
for(Alias alias: aliases)
|
||||
{
|
||||
if(alias.isRecordable())
|
||||
{
|
||||
mRecordAudio.set(true);
|
||||
}
|
||||
|
||||
//Add all broadcast channels for the alias ... let the set handle duplication.
|
||||
for(BroadcastChannel broadcastChannel: alias.getBroadcastChannels())
|
||||
{
|
||||
mBroadcastChannels.add(broadcastChannel);
|
||||
}
|
||||
|
||||
//Only assign a playback priority if it is lower priority than the current setting.
|
||||
int playbackPriority = alias.getPlaybackPriority();
|
||||
|
||||
if(playbackPriority < mMonitorPriority.get())
|
||||
{
|
||||
mMonitorPriority.set(playbackPriority);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes identifier update notifications and updates audio segment properties using the associated aliases
|
||||
* from the alias list.
|
||||
*
|
||||
* @param identifierUpdateNotification containing an identifier.
|
||||
*/
|
||||
@Override
|
||||
public void receive(IdentifierUpdateNotification identifierUpdateNotification)
|
||||
{
|
||||
//Only process add updates that match this timeslot
|
||||
if(identifierUpdateNotification.getTimeslot() == getTimeslot())
|
||||
{
|
||||
if(identifierUpdateNotification.isAdd() || identifierUpdateNotification.isSilentAdd())
|
||||
{
|
||||
addIdentifier(identifierUpdateNotification.getIdentifier());
|
||||
}
|
||||
|
||||
mIdentifierUpdateNotificationBroadcaster.broadcast(identifierUpdateNotification);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Audio Segment\n Timeslot:").append(getTimeslot()).append("\n");
|
||||
sb.append(" Start Time:").append(new Date(getStartTimestamp()));
|
||||
sb.append(" Linked:").append(isLinked()).append("\n");
|
||||
sb.append(" Recordable:").append(recordAudioProperty().get()).append("\n");
|
||||
sb.append("Do Not Monitor:").append(isDoNotMonitor()).append("\n");
|
||||
sb.append(" Stream To:").append(getBroadcastChannels()).append("\n");
|
||||
sb.append(" Identifiers:\n").append(getIdentifierCollection()).append("\n");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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;
|
||||
|
||||
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;
|
||||
|
||||
public class AudioSegmentBroadcaster<T extends AudioSegment> extends Broadcaster<T>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AudioSegmentBroadcaster.class);
|
||||
|
||||
/**
|
||||
* Increments the consumer count for the reusable complex buffer and then broadcasts the buffer to all registered
|
||||
* listeners.
|
||||
*
|
||||
* The total consumer count is established and applied to the buffer prior to dispatching. If we were to simply
|
||||
* increment the consumer count prior to sending to each consumer, there is a possibility that the consumer could
|
||||
* immediately decrement the consumer count and prematurely signal that the buffer is ready for disposal before we
|
||||
* send the buffer to all consumers.
|
||||
*/
|
||||
@Override
|
||||
public void broadcast(T audioSegment)
|
||||
{
|
||||
for(Listener<T> listener : mListeners)
|
||||
{
|
||||
audioSegment.incrementConsumerCount();
|
||||
listener.receive(audioSegment);
|
||||
}
|
||||
|
||||
//Decrement consumer counter for this broadcaster
|
||||
audioSegment.decrementConsumerCount();
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
package io.github.dsheirer.audio;
|
||||
|
||||
import io.github.dsheirer.sample.ConversionUtils;
|
||||
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -32,18 +31,17 @@ public class AudioUtils
|
||||
/**
|
||||
* Converts the audio packets into a byte array of 16-bit, little-endian audio samples
|
||||
*/
|
||||
public static byte[] convertTo16BitSamples(List<ReusableAudioPacket> audioPackets)
|
||||
public static byte[] convertTo16BitSamples(List<float[]> buffers)
|
||||
{
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
|
||||
try
|
||||
{
|
||||
for(ReusableAudioPacket audioPacket: audioPackets)
|
||||
for(float[] audioBuffer: buffers)
|
||||
{
|
||||
//Converting from 32-bit floats to signed 16-bit samples
|
||||
ByteBuffer buffer = ConversionUtils.convertToSigned16BitSamples(audioPacket.getAudioSamples());
|
||||
ByteBuffer buffer = ConversionUtils.convertToSigned16BitSamples(audioBuffer);
|
||||
stream.write(buffer.array());
|
||||
audioPacket.decrementUserCount();
|
||||
}
|
||||
}
|
||||
catch(IOException e)
|
||||
@@ -53,4 +51,29 @@ public class AudioUtils
|
||||
|
||||
return stream.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the audio packets into a byte array of 16-bit, little-endian audio samples
|
||||
*/
|
||||
public static byte[] convert(List<float[]> audioBuffers)
|
||||
{
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
|
||||
try
|
||||
{
|
||||
for(float[] audioBuffer: audioBuffers)
|
||||
{
|
||||
//Converting from 32-bit floats to signed 16-bit samples
|
||||
ByteBuffer buffer = ConversionUtils.convertToSigned16BitSamples(audioBuffer);
|
||||
stream.write(buffer.array());
|
||||
}
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
mLog.error("Error writing converted PCM bytes to output stream");
|
||||
}
|
||||
|
||||
return stream.toByteArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package io.github.dsheirer.audio;
|
||||
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
|
||||
|
||||
public interface IAudioPacketListener
|
||||
{
|
||||
public Listener<ReusableAudioPacket> getAudioPacketListener();
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package io.github.dsheirer.audio;
|
||||
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
|
||||
|
||||
public interface IAudioPacketProvider
|
||||
{
|
||||
public void setAudioPacketListener( Listener<ReusableAudioPacket> listener );
|
||||
public void removeAudioPacketListener();
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 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
|
||||
@@ -15,23 +14,20 @@
|
||||
*
|
||||
* 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.module.decode.p25;
|
||||
package io.github.dsheirer.audio;
|
||||
|
||||
import io.github.dsheirer.module.decode.p25.message.P25Message;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
|
||||
/**
|
||||
* Decoded metadata for an APCO25 channel
|
||||
* Interface for audio segment listener
|
||||
*/
|
||||
public class P25Metadata implements Listener<P25Message>
|
||||
public interface IAudioSegmentListener
|
||||
{
|
||||
|
||||
@Override
|
||||
public void receive(P25Message p25Message)
|
||||
{
|
||||
|
||||
}
|
||||
/**
|
||||
* Get the audio segment listener
|
||||
*/
|
||||
Listener<AudioSegment> getAudioSegmentListener();
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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;
|
||||
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
|
||||
/**
|
||||
* Interface for audio segment provider
|
||||
*/
|
||||
public interface IAudioSegmentProvider
|
||||
{
|
||||
/**
|
||||
* Registers the audio segment listener
|
||||
* @param listener of audio segments
|
||||
*/
|
||||
void setAudioSegmentListener(Listener<AudioSegment> listener);
|
||||
|
||||
/**
|
||||
* De-registers the audio segment listener
|
||||
*/
|
||||
void removeAudioSegmentListener();
|
||||
}
|
||||
@@ -1,21 +1,24 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
/*
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast;
|
||||
|
||||
import io.github.dsheirer.audio.convert.ISilenceGenerator;
|
||||
@@ -201,6 +204,10 @@ public abstract class AudioBroadcaster implements Listener<AudioRecording>
|
||||
mAudioRecordingQueue.offer(recording);
|
||||
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_QUEUE_CHANGE));
|
||||
}
|
||||
else
|
||||
{
|
||||
recording.removePendingReplay();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -373,9 +380,9 @@ public abstract class AudioBroadcaster implements Listener<AudioRecording>
|
||||
broadcastAudio(mSilenceGenerator.generate(PROCESSOR_RUN_INTERVAL_MS));
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
catch(Throwable t)
|
||||
{
|
||||
mLog.error("Error while processing audio streaming queue", e);
|
||||
mLog.error("Error while processing audio streaming queue", t);
|
||||
}
|
||||
|
||||
mProcessing.set(false);
|
||||
|
||||
@@ -18,22 +18,25 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.audio.broadcast;
|
||||
|
||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class AudioRecording implements Comparable<AudioRecording>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(StreamManager.class);
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AudioRecording.class);
|
||||
|
||||
private Path mPath;
|
||||
private long mStartTime;
|
||||
private long mRecordingLength;
|
||||
private AtomicInteger mPendingReplayCount = new AtomicInteger();
|
||||
private IdentifierCollection mIdentifierCollection;
|
||||
private Collection<BroadcastChannel> mBroadcastChannels;
|
||||
|
||||
/**
|
||||
* Audio recording that is ready to be streamed
|
||||
@@ -43,9 +46,11 @@ public class AudioRecording implements Comparable<AudioRecording>
|
||||
* @param start time of recording in milliseconds since epoch
|
||||
* @param recordingLength in milliseconds
|
||||
*/
|
||||
public AudioRecording(Path path, IdentifierCollection identifierCollection, long start, long recordingLength)
|
||||
public AudioRecording(Path path, Collection<BroadcastChannel> broadcastChannels,
|
||||
IdentifierCollection identifierCollection, long start, long recordingLength)
|
||||
{
|
||||
mPath = path;
|
||||
mBroadcastChannels = broadcastChannels;
|
||||
mIdentifierCollection = identifierCollection;
|
||||
mStartTime = start;
|
||||
mRecordingLength = recordingLength;
|
||||
@@ -59,6 +64,14 @@ public class AudioRecording implements Comparable<AudioRecording>
|
||||
return mPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collection of broadcast channels that this recording should be streamed to
|
||||
*/
|
||||
public Collection<BroadcastChannel> getBroadcastChannels()
|
||||
{
|
||||
return mBroadcastChannels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional audio metadata/identifiers for the recording.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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;
|
||||
|
||||
import io.github.dsheirer.audio.AudioSegment;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.record.AudioSegmentRecorder;
|
||||
import io.github.dsheirer.record.RecordFormat;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import io.github.dsheirer.util.TimeStamp;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Audio streaming manager monitors audio segments through completion and creates temporary streaming recordings on
|
||||
* disk and enqueues the temporary recording for streaming.
|
||||
*/
|
||||
public class AudioStreamingManager implements Listener<AudioSegment>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AudioStreamingManager.class);
|
||||
private LinkedTransferQueue<AudioSegment> mNewAudioSegments = new LinkedTransferQueue<>();
|
||||
private List<AudioSegment> mAudioSegments = new ArrayList<>();
|
||||
private Listener<AudioRecording> mAudioRecordingListener;
|
||||
private BroadcastFormat mBroadcastFormat;
|
||||
private UserPreferences mUserPreferences;
|
||||
private ScheduledFuture<?> mAudioSegmentProcessorFuture;
|
||||
private int mNextRecordingNumber = 1;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param listener to receive completed audio recordings
|
||||
* @param broadcastFormat for temporary recordings
|
||||
* @param userPreferences to manage recording directories
|
||||
*/
|
||||
public AudioStreamingManager(Listener<AudioRecording> listener, BroadcastFormat broadcastFormat, UserPreferences userPreferences)
|
||||
{
|
||||
mAudioRecordingListener = listener;
|
||||
mBroadcastFormat = broadcastFormat;
|
||||
mUserPreferences = userPreferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary receive method
|
||||
*/
|
||||
@Override
|
||||
public void receive(AudioSegment audioSegment)
|
||||
{
|
||||
mNewAudioSegments.add(audioSegment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the scheduled audio segment processor
|
||||
*/
|
||||
public void start()
|
||||
{
|
||||
if(mAudioSegmentProcessorFuture == null)
|
||||
{
|
||||
mAudioSegmentProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new AudioSegmentProcessor(),
|
||||
0, 250, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the scheduled audio segment processor
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
if(mAudioSegmentProcessorFuture != null)
|
||||
{
|
||||
mAudioSegmentProcessorFuture.cancel(true);
|
||||
mAudioSegmentProcessorFuture = null;
|
||||
}
|
||||
|
||||
for(AudioSegment audioSegment: mNewAudioSegments)
|
||||
{
|
||||
audioSegment.decrementConsumerCount();
|
||||
}
|
||||
|
||||
mNewAudioSegments.clear();
|
||||
|
||||
for(AudioSegment audioSegment: mAudioSegments)
|
||||
{
|
||||
audioSegment.decrementConsumerCount();
|
||||
}
|
||||
|
||||
mAudioSegments.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Main processing method to process audio segments
|
||||
*/
|
||||
private void processAudioSegments()
|
||||
{
|
||||
mNewAudioSegments.drainTo(mAudioSegments);
|
||||
|
||||
Iterator<AudioSegment> it = mAudioSegments.iterator();
|
||||
AudioSegment audioSegment;
|
||||
while(it.hasNext())
|
||||
{
|
||||
audioSegment = it.next();
|
||||
|
||||
if(audioSegment.completeProperty().get())
|
||||
{
|
||||
it.remove();
|
||||
|
||||
if(mAudioRecordingListener != null && audioSegment.hasBroadcastChannels())
|
||||
{
|
||||
Path path = getTemporaryRecordingPath();
|
||||
long length = 0;
|
||||
|
||||
for(float[] audioBuffer: audioSegment.getAudioBuffers())
|
||||
{
|
||||
length += audioBuffer.length;
|
||||
}
|
||||
|
||||
length /= 8; //Sample rate is 8000 samples per second, or 8 samples per millisecond.
|
||||
|
||||
try
|
||||
{
|
||||
AudioSegmentRecorder.record(audioSegment, path, RecordFormat.MP3);
|
||||
IdentifierCollection identifierCollectionCopy =
|
||||
new IdentifierCollection(audioSegment.getIdentifierCollection().getIdentifiers());
|
||||
|
||||
AudioRecording audioRecording = new AudioRecording(path, audioSegment.getBroadcastChannels(),
|
||||
identifierCollectionCopy, audioSegment.getStartTimestamp(), length);
|
||||
mAudioRecordingListener.receive(audioRecording);
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error recording temporary stream MP3");
|
||||
}
|
||||
}
|
||||
|
||||
audioSegment.decrementConsumerCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a temporary streaming recording file path
|
||||
*/
|
||||
private Path getTemporaryRecordingPath()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(BroadcastModel.TEMPORARY_STREAM_FILE_SUFFIX);
|
||||
|
||||
//Check for integer overflow and readjust negative value to 0
|
||||
if(mNextRecordingNumber < 0)
|
||||
{
|
||||
mNextRecordingNumber = 1;
|
||||
}
|
||||
|
||||
int recordingNumber = mNextRecordingNumber++;
|
||||
|
||||
sb.append(recordingNumber).append("_");
|
||||
sb.append(TimeStamp.getLongTimeStamp("_"));
|
||||
sb.append(mBroadcastFormat.getFileExtension());
|
||||
|
||||
Path temporaryRecordingPath = mUserPreferences.getDirectoryPreference().getDirectoryStreaming().resolve(sb.toString());
|
||||
|
||||
return temporaryRecordingPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scheduled runnable to process audio segments.
|
||||
*/
|
||||
public class AudioSegmentProcessor implements Runnable
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
processAudioSegments();
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
mLog.error("Error processing audio segments for streaming", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,24 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast;
|
||||
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
@@ -40,13 +43,10 @@ import io.github.dsheirer.audio.convert.MP3SilenceGenerator;
|
||||
import io.github.dsheirer.gui.editor.Editor;
|
||||
import io.github.dsheirer.gui.editor.EmptyEditor;
|
||||
import io.github.dsheirer.icon.IconManager;
|
||||
import io.github.dsheirer.record.AudioRecorder;
|
||||
import io.github.dsheirer.record.mp3.MP3Recorder;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class BroadcastFactory
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(BroadcastFactory.class);
|
||||
@@ -147,7 +147,8 @@ public class BroadcastFactory
|
||||
* @param broadcastModel model for broadcastAudio configurations
|
||||
* @return an editor for the specified broadcastAudio configuration
|
||||
*/
|
||||
public static Editor<BroadcastConfiguration> getEditor(BroadcastConfiguration configuration,
|
||||
public static Editor<BroadcastConfiguration> getEditor(UserPreferences userPreferences,
|
||||
BroadcastConfiguration configuration,
|
||||
BroadcastModel broadcastModel,
|
||||
AliasModel aliasModel,
|
||||
IconManager iconManager)
|
||||
@@ -157,7 +158,7 @@ public class BroadcastFactory
|
||||
switch(configuration.getBroadcastServerType())
|
||||
{
|
||||
case BROADCASTIFY:
|
||||
editor = new BroadcastifyConfigurationEditor(broadcastModel, aliasModel, iconManager);
|
||||
editor = new BroadcastifyConfigurationEditor(userPreferences, broadcastModel, aliasModel, iconManager);
|
||||
break;
|
||||
case ICECAST_TCP:
|
||||
editor = new IcecastTCPConfigurationEditor(broadcastModel, aliasModel, iconManager);
|
||||
@@ -181,21 +182,6 @@ public class BroadcastFactory
|
||||
return editor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an audio recorder for the specified broadcastAudio format using the specified path output file name
|
||||
*/
|
||||
public static AudioRecorder getAudioRecorder(Path path, BroadcastFormat broadcastFormat)
|
||||
{
|
||||
switch(broadcastFormat)
|
||||
{
|
||||
case MP3:
|
||||
return new MP3Recorder(path);
|
||||
default:
|
||||
mLog.debug("Unrecognized broadcastAudio format [" + broadcastFormat + "] cannot create audio recorder");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static ISilenceGenerator getSilenceGenerator(BroadcastFormat format)
|
||||
{
|
||||
switch(format)
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
||||
import io.github.dsheirer.icon.IconManager;
|
||||
@@ -27,7 +28,6 @@ import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.properties.SystemProperties;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -49,7 +49,7 @@ import java.util.function.Consumer;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class BroadcastModel extends AbstractTableModel implements Listener<ReusableAudioPacket>
|
||||
public class BroadcastModel extends AbstractTableModel implements Listener<AudioRecording>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(BroadcastModel.class);
|
||||
|
||||
@@ -74,7 +74,6 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Reusa
|
||||
private Map<String,BroadcastConfiguration> mBroadcastConfigurationMap = new HashMap<>();
|
||||
private Map<String,AudioBroadcaster> mBroadcasterMap = new HashMap<>();
|
||||
private IconManager mIconManager;
|
||||
private StreamManager mStreamManager;
|
||||
private AliasModel mAliasModel;
|
||||
private Broadcaster<BroadcastEvent> mBroadcastEventBroadcaster = new Broadcaster<>();
|
||||
|
||||
@@ -85,8 +84,6 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Reusa
|
||||
{
|
||||
mAliasModel = aliasModel;
|
||||
mIconManager = iconManager;
|
||||
mStreamManager = new StreamManager(new CompletedRecordingListener(), BroadcastFormat.MP3, userPreferences);
|
||||
mStreamManager.start();
|
||||
|
||||
//Monitor to remove temporary recording files that have been streamed by all audio broadcasters
|
||||
ThreadPool.SCHEDULED.scheduleAtFixedRate(new RecordingDeletionMonitor(), 15l, 15l, TimeUnit.SECONDS);
|
||||
@@ -279,22 +276,28 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Reusa
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ReusableAudioPacket audioPacket)
|
||||
public void receive(AudioRecording audioRecording)
|
||||
{
|
||||
if(audioPacket.isStreamable())
|
||||
if(audioRecording != null && !audioRecording.getBroadcastChannels().isEmpty())
|
||||
{
|
||||
for(BroadcastChannel channel: audioPacket.getBroadcastChannels())
|
||||
for(BroadcastChannel broadcastChannel : audioRecording.getBroadcastChannels())
|
||||
{
|
||||
if(mBroadcasterMap.containsKey(channel.getChannelName()))
|
||||
String channelName = broadcastChannel.getChannelName();
|
||||
|
||||
if(channelName != null)
|
||||
{
|
||||
audioPacket.incrementUserCount();
|
||||
mStreamManager.receive(audioPacket);
|
||||
return;
|
||||
AudioBroadcaster audioBroadcaster = getBroadcaster(channelName);
|
||||
|
||||
if(audioBroadcaster != null)
|
||||
{
|
||||
audioRecording.addPendingReplay();
|
||||
audioBroadcaster.receive(audioRecording);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
audioPacket.decrementUserCount();
|
||||
mRecordingQueue.add(audioRecording);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -309,19 +312,9 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Reusa
|
||||
|
||||
if(audioBroadcaster != null)
|
||||
{
|
||||
audioBroadcaster.setListener(new Listener<BroadcastEvent>()
|
||||
{
|
||||
@Override
|
||||
public void receive(BroadcastEvent broadcastEvent)
|
||||
{
|
||||
process(broadcastEvent);
|
||||
}
|
||||
});
|
||||
|
||||
audioBroadcaster.setListener(broadcastEvent -> process(broadcastEvent));
|
||||
audioBroadcaster.start();
|
||||
|
||||
mBroadcasterMap.put(audioBroadcaster.getBroadcastConfiguration().getName(), audioBroadcaster);
|
||||
|
||||
int index = mBroadcastConfigurations.indexOf(audioBroadcaster.getBroadcastConfiguration());
|
||||
|
||||
if(index >= 0)
|
||||
@@ -586,12 +579,20 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Reusa
|
||||
@Override
|
||||
public Class<?> getColumnClass(int columnIndex)
|
||||
{
|
||||
if(columnIndex == COLUMN_SERVER_ICON)
|
||||
switch(columnIndex)
|
||||
{
|
||||
return ImageIcon.class;
|
||||
case COLUMN_BROADCASTER_STATUS:
|
||||
return BroadcastState.class;
|
||||
case COLUMN_BROADCASTER_AGED_OFF_COUNT:
|
||||
case COLUMN_BROADCASTER_QUEUE_SIZE:
|
||||
case COLUMN_BROADCASTER_STREAMED_COUNT:
|
||||
return Integer.class;
|
||||
case COLUMN_SERVER_ICON:
|
||||
return ImageIcon.class;
|
||||
case COLUMN_STREAM_NAME:
|
||||
default:
|
||||
return String.class;
|
||||
}
|
||||
|
||||
return String.class;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -700,43 +701,6 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Reusa
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes completed audio recordings and distributes them to the audio broadcasters. Adds the recording to
|
||||
* the audio recording queue to be monitored for deletion.
|
||||
*/
|
||||
public class CompletedRecordingListener implements Listener<AudioRecording>
|
||||
{
|
||||
@Override
|
||||
public void receive(AudioRecording audioRecording)
|
||||
{
|
||||
if(audioRecording != null && audioRecording.hasIdentifierCollection())
|
||||
{
|
||||
AliasList aliasList = mAliasModel.getAliasList(audioRecording.getIdentifierCollection());
|
||||
|
||||
if(aliasList != null)
|
||||
{
|
||||
for(BroadcastChannel broadcastChannel : aliasList.getBroadcastChannels(audioRecording.getIdentifierCollection()))
|
||||
{
|
||||
String channelName = broadcastChannel.getChannelName();
|
||||
|
||||
if(channelName != null)
|
||||
{
|
||||
AudioBroadcaster audioBroadcaster = getBroadcaster(channelName);
|
||||
|
||||
if(audioBroadcaster != null)
|
||||
{
|
||||
audioRecording.addPendingReplay();
|
||||
audioBroadcaster.receive(audioRecording);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mRecordingQueue.add(audioRecording);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitors the recording queue and removes any recordings that have no pending replays by audio broadcasters
|
||||
*/
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast;
|
||||
|
||||
import com.jidesoft.swing.JideSplitPane;
|
||||
@@ -23,6 +26,7 @@ import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.gui.editor.Editor;
|
||||
import io.github.dsheirer.gui.editor.EmptyEditor;
|
||||
import io.github.dsheirer.icon.IconManager;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -51,6 +55,7 @@ public class BroadcastPanel extends JPanel implements ActionListener, ListSelect
|
||||
private BroadcastModel mBroadcastModel;
|
||||
private AliasModel mAliasModel;
|
||||
private IconManager mIconManager;
|
||||
private UserPreferences mUserPreferences;
|
||||
|
||||
private BroadcastStatusPanel mBroadcastStatusPanel;
|
||||
private JideSplitPane mSplitPane;
|
||||
@@ -60,12 +65,14 @@ public class BroadcastPanel extends JPanel implements ActionListener, ListSelect
|
||||
private JButton mCopyButton = new JButton(COPY_BROADCAST_CONFIGURATION);
|
||||
private JButton mDeleteButton = new JButton(DELETE_BROADCAST_CONFIGURATION);
|
||||
|
||||
public BroadcastPanel(BroadcastModel broadcastModel, AliasModel aliasModel, IconManager iconManager)
|
||||
public BroadcastPanel(BroadcastModel broadcastModel, AliasModel aliasModel, IconManager iconManager,
|
||||
UserPreferences userPreferences)
|
||||
{
|
||||
mBroadcastModel = broadcastModel;
|
||||
mAliasModel = aliasModel;
|
||||
mIconManager = iconManager;
|
||||
mEditor = mEmptyEditor;
|
||||
mUserPreferences = userPreferences;
|
||||
|
||||
init();
|
||||
}
|
||||
@@ -74,7 +81,8 @@ public class BroadcastPanel extends JPanel implements ActionListener, ListSelect
|
||||
{
|
||||
setLayout(new MigLayout("insets 0 0 0 0 ", "[grow,fill]", "[grow,fill]"));
|
||||
|
||||
mBroadcastStatusPanel = new BroadcastStatusPanel(mBroadcastModel);
|
||||
mBroadcastStatusPanel = new BroadcastStatusPanel(mBroadcastModel, mUserPreferences,
|
||||
"broadcast.panel.broadcast.status.panel");
|
||||
|
||||
mBroadcastStatusPanel.getTable().setAutoCreateRowSorter(true);
|
||||
mBroadcastStatusPanel.getTable().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
@@ -210,8 +218,8 @@ public class BroadcastPanel extends JPanel implements ActionListener, ListSelect
|
||||
|
||||
if (selectedConfiguration != null)
|
||||
{
|
||||
mEditor = BroadcastFactory.getEditor(selectedConfiguration, mBroadcastModel, mAliasModel,
|
||||
mIconManager);
|
||||
mEditor = BroadcastFactory.getEditor(mUserPreferences, selectedConfiguration, mBroadcastModel,
|
||||
mAliasModel, mIconManager);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*******************************************************************************
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 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
|
||||
@@ -14,10 +15,12 @@
|
||||
*
|
||||
* 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.preference.UserPreferences;
|
||||
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
@@ -32,12 +35,17 @@ import java.awt.Component;
|
||||
public class BroadcastStatusPanel extends JPanel
|
||||
{
|
||||
private JTable mTable;
|
||||
private JTableColumnWidthMonitor mColumnWidthMonitor;
|
||||
private JScrollPane mScrollPane;
|
||||
private BroadcastModel mBroadcastModel;
|
||||
private UserPreferences mUserPreferences;
|
||||
private String mPreferenceKey;
|
||||
|
||||
public BroadcastStatusPanel(BroadcastModel broadcastModel)
|
||||
public BroadcastStatusPanel(BroadcastModel broadcastModel, UserPreferences userPreferences, String preferenceKey)
|
||||
{
|
||||
mBroadcastModel = broadcastModel;
|
||||
mUserPreferences = userPreferences;
|
||||
mPreferenceKey = preferenceKey;
|
||||
|
||||
init();
|
||||
}
|
||||
@@ -57,6 +65,7 @@ public class BroadcastStatusPanel extends JPanel
|
||||
renderer.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
|
||||
mTable.getColumnModel().getColumn(BroadcastModel.COLUMN_BROADCASTER_STATUS).setCellRenderer(new StatusCellRenderer());
|
||||
mColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, mPreferenceKey);
|
||||
|
||||
mScrollPane = new JScrollPane(mTable);
|
||||
|
||||
|
||||
@@ -1,236 +0,0 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* 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.preference.UserPreferences;
|
||||
import io.github.dsheirer.record.AudioRecorder;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import io.github.dsheirer.util.TimeStamp;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class StreamManager implements Listener<ReusableAudioPacket>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(StreamManager.class);
|
||||
private static final long MAXIMUM_RECORDER_LIFESPAN_MILLIS = 30000; //30 seconds
|
||||
|
||||
private static AtomicInteger sNextRecordingNumber = new AtomicInteger();
|
||||
|
||||
private Listener<AudioRecording> mAudioRecordingListener;
|
||||
private BroadcastFormat mBroadcastFormat;
|
||||
private UserPreferences mUserPreferences;
|
||||
private Map<Integer,AudioRecorder> mStreamRecorders = new HashMap<>();
|
||||
private Runnable mRecorderMonitor;
|
||||
private ScheduledFuture<?> mRecorderMonitorFuture;
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
|
||||
/**
|
||||
* Stream manager processes all incoming audio packets and reassembles individual audio streams, converts audio
|
||||
* to desired output format and persists each stream to disc. Each recording is capped at a maximum length to
|
||||
* ensure that recordings don't run too long before they are streamed out and to ensure that inactive recordings
|
||||
* are closed in a timely fashion.
|
||||
*
|
||||
* Completed streamable audio recordings are nominated to the output listener (for broadcast) upon completion
|
||||
*
|
||||
* @param listener to receive completed audio recordings
|
||||
* @param broadcastFormat for the stream
|
||||
* @param userPreferences to obtain the temporary streaming recording directory
|
||||
*/
|
||||
public StreamManager(Listener<AudioRecording> listener, BroadcastFormat broadcastFormat, UserPreferences userPreferences)
|
||||
{
|
||||
mAudioRecordingListener = listener;
|
||||
mBroadcastFormat = broadcastFormat;
|
||||
mUserPreferences = userPreferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the stream manager. Schedules a processor to monitor for inactive temporary stream recorders
|
||||
* every 2 seconds.
|
||||
*/
|
||||
public void start()
|
||||
{
|
||||
if(mRunning.compareAndSet(false, true))
|
||||
{
|
||||
if(mRecorderMonitor == null)
|
||||
{
|
||||
mRecorderMonitor = new RecorderMonitor();
|
||||
}
|
||||
|
||||
mRecorderMonitorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(mRecorderMonitor,
|
||||
0,2, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the stream manager. Stops the inactive temporary stream recorder monitoring thread.
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
if(mRunning.compareAndSet(true, false))
|
||||
{
|
||||
if(mRecorderMonitorFuture != null)
|
||||
{
|
||||
mRecorderMonitorFuture.cancel(true);
|
||||
}
|
||||
|
||||
synchronized(mStreamRecorders)
|
||||
{
|
||||
List<Integer> streamKeys = new ArrayList<>(mStreamRecorders.keySet());
|
||||
|
||||
for(Integer streamKey : streamKeys)
|
||||
{
|
||||
removeRecorder(streamKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ReusableAudioPacket audioPacket)
|
||||
{
|
||||
if(mRunning.get() && audioPacket.hasIdentifierCollection())
|
||||
{
|
||||
synchronized(mStreamRecorders)
|
||||
{
|
||||
int channelMetadataID = audioPacket.getAudioChannelId();
|
||||
|
||||
ReusableAudioPacket.Type type = audioPacket.getType();
|
||||
|
||||
if(type == ReusableAudioPacket.Type.AUDIO)
|
||||
{
|
||||
if(mStreamRecorders.containsKey(channelMetadataID))
|
||||
{
|
||||
AudioRecorder recorder = mStreamRecorders.get(channelMetadataID);
|
||||
|
||||
if(recorder != null)
|
||||
{
|
||||
audioPacket.incrementUserCount();
|
||||
recorder.receive(audioPacket);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AudioRecorder recorder = BroadcastFactory.getAudioRecorder(getTemporaryRecordingPath(), mBroadcastFormat);
|
||||
recorder.start();
|
||||
audioPacket.incrementUserCount();
|
||||
recorder.receive(audioPacket);
|
||||
mStreamRecorders.put(channelMetadataID, recorder);
|
||||
}
|
||||
}
|
||||
else if(type == ReusableAudioPacket.Type.END)
|
||||
{
|
||||
removeRecorder(channelMetadataID);
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.info("Unrecognized Audio Packet Type: " + type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
audioPacket.decrementUserCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the recorder associated with the source channel ID.
|
||||
*
|
||||
* Note: this method invocation is not thread safe and must be invoked by a thread safe mechanism that protects the
|
||||
* mStreamRecorders map.
|
||||
*
|
||||
* @param sourceChannelID identifying the recorder
|
||||
*/
|
||||
private void removeRecorder(Integer sourceChannelID)
|
||||
{
|
||||
if(mStreamRecorders.containsKey(sourceChannelID))
|
||||
{
|
||||
AudioRecorder recorder = mStreamRecorders.remove(sourceChannelID);
|
||||
|
||||
recorder.close(new Listener<AudioRecorder>()
|
||||
{
|
||||
@Override
|
||||
public void receive(AudioRecorder audioRecorder)
|
||||
{
|
||||
AudioRecording audioRecording =
|
||||
new AudioRecording(audioRecorder.getPath(), audioRecorder.getIdentifierCollection(),
|
||||
audioRecorder.getTimeRecordingStart(), audioRecorder.getRecordingLength());
|
||||
|
||||
if(mAudioRecordingListener != null)
|
||||
{
|
||||
mAudioRecordingListener.receive(audioRecording);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a temporary streaming recording file path
|
||||
*/
|
||||
private Path getTemporaryRecordingPath()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(BroadcastModel.TEMPORARY_STREAM_FILE_SUFFIX);
|
||||
sb.append(sNextRecordingNumber.incrementAndGet()).append("_");
|
||||
sb.append(TimeStamp.getLongTimeStamp("_"));
|
||||
sb.append(mBroadcastFormat.getFileExtension());
|
||||
|
||||
Path temporaryRecordingPath = mUserPreferences.getDirectoryPreference().getDirectoryStreaming().resolve(sb.toString());
|
||||
|
||||
return temporaryRecordingPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitors recorders to ensure they don't exceed the maximum life-span allowed for a recording.
|
||||
*/
|
||||
public class RecorderMonitor implements Runnable
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
synchronized(mStreamRecorders)
|
||||
{
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
mStreamRecorders.entrySet().stream()
|
||||
.filter(entry -> entry.getValue().getTimeRecordingStart() + MAXIMUM_RECORDER_LIFESPAN_MILLIS < now)
|
||||
.forEach(entry ->
|
||||
{
|
||||
mLog.info("cycling recorder - max temporary streaming recording time limit reached [" +
|
||||
entry.getValue().getPath().toString() + "]");
|
||||
|
||||
removeRecorder(entry.getKey());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,26 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast.broadcastify;
|
||||
|
||||
import com.radioreference.api.soap2.UserFeedBroadcast;
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastConfigurationEditor;
|
||||
@@ -26,6 +28,8 @@ import io.github.dsheirer.audio.broadcast.BroadcastEvent;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastModel;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
|
||||
import io.github.dsheirer.icon.IconManager;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.rrapi.type.UserFeedBroadcast;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
@@ -64,10 +68,13 @@ public class BroadcastifyConfigurationEditor extends BroadcastConfigurationEdito
|
||||
private JCheckBox mEnabled;
|
||||
private JButton mSaveButton;
|
||||
private JButton mResetButton;
|
||||
private UserPreferences mUserPreferences;
|
||||
|
||||
public BroadcastifyConfigurationEditor(BroadcastModel broadcastModel, AliasModel aliasModel, IconManager iconManager)
|
||||
public BroadcastifyConfigurationEditor(UserPreferences userPreferences, BroadcastModel broadcastModel,
|
||||
AliasModel aliasModel, IconManager iconManager)
|
||||
{
|
||||
super(broadcastModel, aliasModel, iconManager);
|
||||
mUserPreferences = userPreferences;
|
||||
|
||||
init();
|
||||
}
|
||||
@@ -91,7 +98,7 @@ public class BroadcastifyConfigurationEditor extends BroadcastConfigurationEdito
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
final UserFeedSelectionDialog dialog = new UserFeedSelectionDialog(BroadcastifyConfigurationEditor.this);
|
||||
final UserFeedSelectionDialog dialog = new UserFeedSelectionDialog(mUserPreferences,BroadcastifyConfigurationEditor.this);
|
||||
|
||||
EventQueue.invokeLater(new Runnable()
|
||||
{
|
||||
@@ -391,7 +398,7 @@ public class BroadcastifyConfigurationEditor extends BroadcastConfigurationEdito
|
||||
@Override
|
||||
public void receive(UserFeedBroadcast userFeedBroadcast)
|
||||
{
|
||||
mName.setText(userFeedBroadcast.getDescr());
|
||||
mName.setText(userFeedBroadcast.getDescription());
|
||||
mServer.setText(userFeedBroadcast.getHostname());
|
||||
mPort.setText(userFeedBroadcast.getPort());
|
||||
mMountPoint.setText(userFeedBroadcast.getMount());
|
||||
|
||||
@@ -1,78 +1,155 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast.broadcastify;
|
||||
|
||||
import com.radioreference.api.soap2.UserFeedBroadcast;
|
||||
import io.github.dsheirer.radioreference.RadioReferenceService;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import io.github.dsheirer.eventbus.MyEventBus;
|
||||
import io.github.dsheirer.preference.PreferenceType;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.rrapi.RadioReferenceException;
|
||||
import io.github.dsheirer.rrapi.RadioReferenceService;
|
||||
import io.github.dsheirer.rrapi.type.AuthorizationInformation;
|
||||
import io.github.dsheirer.rrapi.type.UserFeedBroadcast;
|
||||
import io.github.dsheirer.rrapi.type.UserInfo;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JList;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.ListCellRenderer;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
import javax.xml.rpc.ServiceException;
|
||||
import java.awt.*;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Component;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.net.UnknownHostException;
|
||||
import java.rmi.RemoteException;
|
||||
import java.util.List;
|
||||
|
||||
public class UserFeedSelectionDialog extends JDialog implements DocumentListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( UserFeedSelectionDialog.class );
|
||||
|
||||
public static final String SDRTRUNK_APP_KEY = "88969092";
|
||||
|
||||
private Listener<UserFeedBroadcast> mListener;
|
||||
private UserPreferences mUserPreferences;
|
||||
private JTextField mUserNameText;
|
||||
private JTextField mPasswordText;
|
||||
private JCheckBox mSaveCredentialsCheckBox;
|
||||
private JButton mClearStoredCredentialsButton;
|
||||
private JButton mLookupButton;
|
||||
private JList mUserFeedBroadcastList;
|
||||
private JButton mOKButton;
|
||||
|
||||
public UserFeedSelectionDialog(Listener<UserFeedBroadcast> listener)
|
||||
public UserFeedSelectionDialog(UserPreferences userPreferences, Listener<UserFeedBroadcast> listener)
|
||||
{
|
||||
mUserPreferences = userPreferences;
|
||||
mListener = listener;
|
||||
|
||||
//Register to receive radio reference preferences updates
|
||||
MyEventBus.getEventBus().register(this);
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
JPanel panel = new JPanel();
|
||||
panel.setLayout(new MigLayout("","[align right,grow,fill][align left,grow,fill][]","[][][][][grow][]"));
|
||||
panel.setLayout(new MigLayout("","[align right,grow,fill][align left,grow,fill][]","[][][][][][grow][]"));
|
||||
|
||||
panel.add(new JLabel("Please enter your credentials"), "align left,span");
|
||||
|
||||
panel.add(new JLabel("User Name:"));
|
||||
mUserNameText = new JTextField();
|
||||
String storedUserName = mUserPreferences.getRadioReferencePreference().getUserName();
|
||||
if(storedUserName != null)
|
||||
{
|
||||
mUserNameText.setText(storedUserName);
|
||||
}
|
||||
mUserNameText.getDocument().addDocumentListener(this);
|
||||
panel.add(mUserNameText, "wrap");
|
||||
|
||||
panel.add(new JLabel("Password:"));
|
||||
mPasswordText = new JTextField();
|
||||
String storedPassword = mUserPreferences.getRadioReferencePreference().getPassword();
|
||||
if(storedPassword != null)
|
||||
{
|
||||
mPasswordText.setText(storedPassword);
|
||||
}
|
||||
mPasswordText.getDocument().addDocumentListener(this);
|
||||
panel.add(mPasswordText, "wrap");
|
||||
|
||||
mSaveCredentialsCheckBox = new JCheckBox("Store Credentials");
|
||||
mSaveCredentialsCheckBox.setSelected(mUserPreferences.getRadioReferencePreference().isStoreCredentials());
|
||||
mSaveCredentialsCheckBox.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
//If user unchecks box and there are stored credentials, prompt to clear them
|
||||
if(!mSaveCredentialsCheckBox.isSelected() && mUserPreferences.getRadioReferencePreference().hasStoredCredentials())
|
||||
{
|
||||
int choice = JOptionPane.showOptionDialog(UserFeedSelectionDialog.this,
|
||||
"Would you like to remove previously stored credentials from this computer?",
|
||||
"Clear Stored Credentials?", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE,
|
||||
null, null, 0);
|
||||
|
||||
if(choice == JOptionPane.YES_OPTION)
|
||||
{
|
||||
mUserPreferences.getRadioReferencePreference().removeStoreCredentials();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
panel.add(mSaveCredentialsCheckBox);
|
||||
|
||||
mClearStoredCredentialsButton = new JButton("Remove");
|
||||
mClearStoredCredentialsButton.setToolTipText("Clear stored credentials");
|
||||
mClearStoredCredentialsButton.setEnabled(mUserPreferences.getRadioReferencePreference().hasStoredCredentials());
|
||||
mClearStoredCredentialsButton.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
mUserPreferences.getRadioReferencePreference().removeStoreCredentials();
|
||||
}
|
||||
});
|
||||
panel.add(mClearStoredCredentialsButton, "wrap");
|
||||
|
||||
mLookupButton = new JButton("Get Feeds");
|
||||
mLookupButton.setEnabled(false);
|
||||
mLookupButton.setEnabled(storedPassword != null && storedPassword != null);
|
||||
mLookupButton.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
@@ -87,45 +164,25 @@ public class UserFeedSelectionDialog extends JDialog implements DocumentListener
|
||||
{
|
||||
try
|
||||
{
|
||||
RadioReferenceService service =
|
||||
new RadioReferenceService(mUserNameText.getText(), mPasswordText.getText());
|
||||
AuthorizationInformation authorizationInformation = new AuthorizationInformation(SDRTRUNK_APP_KEY,
|
||||
mUserNameText.getText(), mPasswordText.getText());
|
||||
|
||||
UserFeedBroadcast[] feeds = service.getUserFeedBroadcasts();
|
||||
mLog.debug("User:" + authorizationInformation.getUserName() + " Pass:" + authorizationInformation.getPassword() + " Key:" + authorizationInformation.getApplicationKey());
|
||||
|
||||
mUserFeedBroadcastList.setListData(feeds);
|
||||
RadioReferenceService service = new RadioReferenceService(authorizationInformation);
|
||||
UserInfo userInfo = service.getUserInfo();
|
||||
mLog.info("RR User:" + userInfo.getUserName() + " Expiration Date:" + userInfo.getExpirationDate());
|
||||
List<UserFeedBroadcast> feeds = service.getUserFeeds();
|
||||
|
||||
mUserFeedBroadcastList.setListData(feeds.toArray(new UserFeedBroadcast[feeds.size()]));
|
||||
}
|
||||
catch(ServiceException se)
|
||||
catch(RadioReferenceException rre)
|
||||
{
|
||||
JOptionPane.showMessageDialog(UserFeedSelectionDialog.this,
|
||||
"Unable to create radio reference web service client. See log file.",
|
||||
"Error", JOptionPane.ERROR_MESSAGE);
|
||||
|
||||
mLog.error("Error creating the radio reference web service client", se);
|
||||
}
|
||||
catch(RemoteException re)
|
||||
{
|
||||
Throwable cause = re.getCause();
|
||||
|
||||
if(cause == null)
|
||||
{
|
||||
JOptionPane.showMessageDialog(UserFeedSelectionDialog.this,
|
||||
re.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
else if(cause instanceof UnknownHostException)
|
||||
{
|
||||
JOptionPane.showMessageDialog(UserFeedSelectionDialog.this,
|
||||
"Broadcastify server is unavailable", "No Server",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
else
|
||||
{
|
||||
JOptionPane.showMessageDialog(UserFeedSelectionDialog.this,
|
||||
"An unknown error has occurred. Please see log file.", "" +
|
||||
"Unknown Error", JOptionPane.ERROR_MESSAGE);
|
||||
|
||||
mLog.error("Error retrieving user broadcast feed configurations from radio reference " +
|
||||
"service: ", re);
|
||||
}
|
||||
mLog.error("Error creating the radio reference web service client", rre);
|
||||
}
|
||||
|
||||
mLookupButton.setEnabled(true);
|
||||
@@ -160,6 +217,7 @@ public class UserFeedSelectionDialog extends JDialog implements DocumentListener
|
||||
{
|
||||
if(mListener != null)
|
||||
{
|
||||
storeCredentials();
|
||||
mListener.receive((UserFeedBroadcast)mUserFeedBroadcastList.getSelectedValue());
|
||||
}
|
||||
|
||||
@@ -182,11 +240,37 @@ public class UserFeedSelectionDialog extends JDialog implements DocumentListener
|
||||
setTitle("Broadcastify Feed Lookup");
|
||||
setLayout(new BorderLayout());
|
||||
add(panel, BorderLayout.CENTER);
|
||||
setSize(300,250);
|
||||
setSize(400,450);
|
||||
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
|
||||
setModalityType(ModalityType.APPLICATION_MODAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores login credentials in the user preferences
|
||||
*/
|
||||
private void storeCredentials()
|
||||
{
|
||||
boolean store = mSaveCredentialsCheckBox.isSelected();
|
||||
mUserPreferences.getRadioReferencePreference().setStoreCredentials(store);
|
||||
|
||||
if(store)
|
||||
{
|
||||
String userName = mUserNameText.getText();
|
||||
|
||||
if(userName != null)
|
||||
{
|
||||
mUserPreferences.getRadioReferencePreference().setUserName(userName);
|
||||
}
|
||||
|
||||
String password = mPasswordText.getText();
|
||||
|
||||
if(password != null)
|
||||
{
|
||||
mUserPreferences.getRadioReferencePreference().setPassword(password);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateButtons()
|
||||
{
|
||||
String username = mUserNameText.getText();
|
||||
@@ -240,8 +324,20 @@ public class UserFeedSelectionDialog extends JDialog implements DocumentListener
|
||||
setForeground(list.getForeground());
|
||||
}
|
||||
|
||||
setText(value.getDescr());
|
||||
setText(value.getDescription());
|
||||
|
||||
return this; }
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void preferenceUpdated(PreferenceType preferenceType)
|
||||
{
|
||||
if(preferenceType == PreferenceType.RADIO_REFERENCE)
|
||||
{
|
||||
//Update the store-credentials checkbox only, so we don't change the username or password currently displayed
|
||||
mSaveCredentialsCheckBox.setSelected(mUserPreferences.getRadioReferencePreference().isStoreCredentials());
|
||||
mClearStoredCredentialsButton.setEnabled(mUserPreferences.getRadioReferencePreference().hasStoredCredentials());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast.icecast;
|
||||
|
||||
@@ -31,43 +33,26 @@ 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.apache.mina.core.RuntimeIoException;
|
||||
import org.apache.mina.core.future.ConnectFuture;
|
||||
import org.apache.mina.core.service.IoHandlerAdapter;
|
||||
import org.apache.mina.core.session.IoSession;
|
||||
import org.apache.mina.http.HttpClientCodec;
|
||||
import org.apache.mina.http.HttpRequestImpl;
|
||||
import org.apache.mina.http.api.HttpMethod;
|
||||
import org.apache.mina.http.api.HttpRequest;
|
||||
import org.apache.mina.http.api.HttpVersion;
|
||||
import org.apache.mina.transport.socket.nio.NioSocketConnector;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.channels.UnresolvedAddressException;
|
||||
import java.util.HashMap;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
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 IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdater
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(IcecastBroadcastMetadataUpdater.class);
|
||||
private final static String UTF8 = "UTF-8";
|
||||
|
||||
private HttpClient mHttpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
|
||||
private IcecastConfiguration mIcecastConfiguration;
|
||||
private AliasModel mAliasModel;
|
||||
private NioSocketConnector mSocketConnector;
|
||||
private AtomicBoolean mUpdating = new AtomicBoolean();
|
||||
private Queue<String> mMetadataQueue = new LinkedTransferQueue<>();
|
||||
private Map<String,String> mHTTPHeaders;
|
||||
private boolean mStackTraceLoggingSuppressed;
|
||||
private boolean mConnectionLoggingSuppressed = false;
|
||||
|
||||
/**
|
||||
* Icecast song metadata updater. Each metadata update is processed in the order received and the dispatch of
|
||||
@@ -81,41 +66,6 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
|
||||
mAliasModel = aliasModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Socket connector - lazy constructor.
|
||||
*/
|
||||
private NioSocketConnector getSocketConnector()
|
||||
{
|
||||
if(mSocketConnector == null)
|
||||
{
|
||||
mSocketConnector = new NioSocketConnector();
|
||||
// mSocketConnector.getFilterChain().addLast("logger", new LoggingFilter(IcecastBroadcastMetadataUpdater.class));
|
||||
mSocketConnector.getFilterChain().addLast("http_client_codec", new HttpClientCodec());
|
||||
|
||||
//Each metadata update session is single-use, so we'll shut it down upon success or failure
|
||||
mSocketConnector.setHandler(new IoHandlerAdapter()
|
||||
{
|
||||
@Override
|
||||
public void exceptionCaught(IoSession session, Throwable cause) throws Exception
|
||||
{
|
||||
//Single-use session - close it after we receive an error
|
||||
session.closeNow();
|
||||
|
||||
mLog.error("Metadata update failed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageReceived(IoSession session, Object message) throws Exception
|
||||
{
|
||||
//Single-use session - close it after we receive a response
|
||||
session.closeNow();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mSocketConnector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a song metadata update to the remote server. Additional metadata updates received while the updater
|
||||
* is in the process of sending an existing metadata update will be queued and processed in received order.
|
||||
@@ -126,66 +76,87 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
|
||||
*/
|
||||
public void update(IdentifierCollection identifierCollection)
|
||||
{
|
||||
mMetadataQueue.offer(getSong(identifierCollection));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if(!mUpdating.get() && !mMetadataQueue.isEmpty())
|
||||
try
|
||||
{
|
||||
Runnable runnable = new Runnable()
|
||||
sb.append("http://");
|
||||
sb.append(mIcecastConfiguration.getHost());
|
||||
sb.append(":");
|
||||
sb.append(mIcecastConfiguration.getPort());
|
||||
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));
|
||||
}
|
||||
catch(UnsupportedEncodingException uee)
|
||||
{
|
||||
mLog.error("Error encoding metadata information to UTF-8", uee);
|
||||
sb = null;
|
||||
}
|
||||
|
||||
if(sb != null)
|
||||
{
|
||||
final String metadataUpdateURL = sb.toString();
|
||||
URI uri = URI.create(metadataUpdateURL);
|
||||
|
||||
ThreadPool.SCHEDULED.submit(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
if(!mMetadataQueue.isEmpty() && mUpdating.compareAndSet(false, true))
|
||||
try
|
||||
{
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(uri)
|
||||
.header(IcecastHeader.AUTHORIZATION.getValue(), mIcecastConfiguration.getBase64EncodedCredentials())
|
||||
.header(IcecastHeader.USER_AGENT.getValue(), SystemProperties.getInstance().getApplicationName())
|
||||
.GET()
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = null;
|
||||
|
||||
try
|
||||
{
|
||||
ConnectFuture connectFuture = getSocketConnector()
|
||||
.connect(new InetSocketAddress(mIcecastConfiguration.getHost(),
|
||||
mIcecastConfiguration.getPort()));
|
||||
connectFuture.awaitUninterruptibly();
|
||||
IoSession session = connectFuture.getSession();
|
||||
|
||||
if(session != null)
|
||||
response = mHttpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
if(!mConnectionLoggingSuppressed)
|
||||
{
|
||||
String song = mMetadataQueue.poll();
|
||||
mLog.error("IO Error submitting Icecast metadata update [" +
|
||||
(metadataUpdateURL != null ? metadataUpdateURL : "no url"), ioe);
|
||||
mConnectionLoggingSuppressed = true;
|
||||
}
|
||||
}
|
||||
catch(InterruptedException ie)
|
||||
{
|
||||
mLog.error("Interrupted Exception Error", ie);
|
||||
}
|
||||
|
||||
while(song != null)
|
||||
if(response != null)
|
||||
{
|
||||
if(response.statusCode() == 200)
|
||||
{
|
||||
mConnectionLoggingSuppressed = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!mConnectionLoggingSuppressed)
|
||||
{
|
||||
HttpRequest updateRequest = createUpdateRequest(song);
|
||||
|
||||
if(updateRequest != null)
|
||||
{
|
||||
session.write(updateRequest);
|
||||
}
|
||||
|
||||
//Fetch next metadata update to send
|
||||
song = mMetadataQueue.poll();
|
||||
mLog.info("Error submitting Icecast 2 Metadata update to URL [" + metadataUpdateURL +
|
||||
"] HTTP Response Code [" + response.statusCode() + "] Body [" + response.body() + "]");
|
||||
mConnectionLoggingSuppressed = true;
|
||||
}
|
||||
|
||||
session.closeNow();
|
||||
}
|
||||
}
|
||||
catch(UnresolvedAddressException | RuntimeIoException ure)
|
||||
{
|
||||
//Do nothing - the server is temporarily unavailable
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
Throwable throwableCause = e.getCause();
|
||||
|
||||
if(!mStackTraceLoggingSuppressed)
|
||||
{
|
||||
mLog.error("Error sending metadata update. Future errors will be suppressed", e);
|
||||
mStackTraceLoggingSuppressed = true;
|
||||
}
|
||||
}
|
||||
|
||||
mUpdating.set(false);
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
mLog.error("There was an error submitting an Icecast metadata update", t);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ThreadPool.SCHEDULED.schedule(runnable, 0l, TimeUnit.SECONDS);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,13 +178,22 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
|
||||
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.isEmpty())
|
||||
if(aliases != null && !aliases.isEmpty())
|
||||
{
|
||||
sb.append(" ").append(Joiner.on(", ").skipNulls().join(aliases));
|
||||
}
|
||||
@@ -223,7 +203,17 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
|
||||
sb.append("TO:UNKNOWN");
|
||||
}
|
||||
|
||||
Identifier from = identifierCollection.getIdentifier(IdentifierClass.USER, Form.TALKGROUP, Role.FROM);
|
||||
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)
|
||||
{
|
||||
@@ -231,7 +221,7 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
|
||||
|
||||
List<Alias> aliases = aliasList.getAliases(from);
|
||||
|
||||
if(!aliases.isEmpty())
|
||||
if(aliases != null && !aliases.isEmpty())
|
||||
{
|
||||
sb.append(" ").append(Joiner.on(", ").skipNulls().join(aliases));
|
||||
}
|
||||
@@ -248,46 +238,4 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an HTTP GET request to update the metadata on the remote server
|
||||
*/
|
||||
private HttpRequest createUpdateRequest(String song)
|
||||
{
|
||||
try
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("mode=updinfo");
|
||||
sb.append("&mount=").append(URLEncoder.encode(mIcecastConfiguration.getMountPoint(), UTF8));
|
||||
sb.append("&charset=UTF%2d8");
|
||||
sb.append("&song=").append(URLEncoder.encode(song, UTF8));
|
||||
|
||||
HttpRequestImpl request = new HttpRequestImpl(HttpVersion.HTTP_1_1, HttpMethod.GET, "/admin/metadata",
|
||||
sb.toString(), getHTTPHeaders());
|
||||
|
||||
return request;
|
||||
}
|
||||
catch(UnsupportedEncodingException e)
|
||||
{
|
||||
//This should never happen
|
||||
mLog.error("UTF-8 encoding is not supported - can't update song metadata");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP headers to use for a metadata update request.
|
||||
*/
|
||||
private Map<String,String> getHTTPHeaders()
|
||||
{
|
||||
if(mHTTPHeaders == null)
|
||||
{
|
||||
mHTTPHeaders = new HashMap<>();
|
||||
mHTTPHeaders.put(IcecastHeader.USER_AGENT.getValue(), SystemProperties.getInstance().getApplicationName());
|
||||
mHTTPHeaders.put(IcecastHeader.AUTHORIZATION.getValue(), mIcecastConfiguration.getBase64EncodedCredentials());
|
||||
}
|
||||
|
||||
return mHTTPHeaders;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast.icecast;
|
||||
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
@@ -30,11 +33,9 @@ import org.apache.mina.core.service.IoHandlerAdapter;
|
||||
import org.apache.mina.core.session.IoSession;
|
||||
import org.apache.mina.filter.codec.ProtocolDecoderException;
|
||||
import org.apache.mina.http.HttpClientCodec;
|
||||
import org.apache.mina.http.HttpException;
|
||||
import org.apache.mina.http.HttpRequestImpl;
|
||||
import org.apache.mina.http.api.DefaultHttpResponse;
|
||||
import org.apache.mina.http.api.HttpMethod;
|
||||
import org.apache.mina.http.api.HttpStatus;
|
||||
import org.apache.mina.http.api.HttpVersion;
|
||||
import org.apache.mina.transport.socket.nio.NioSocketConnector;
|
||||
import org.slf4j.Logger;
|
||||
@@ -51,6 +52,7 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(IcecastHTTPAudioBroadcaster.class);
|
||||
private static final long RECONNECT_INTERVAL_MILLISECONDS = 30000; //30 seconds
|
||||
private static final String HTTP_1_0_OK_HEX_DUMP = "48 54 54 50 2F 31 2E 30 20 32 30 30 20 4F 4B";
|
||||
|
||||
private NioSocketConnector mSocketConnector;
|
||||
private IoSession mStreamingSession = null;
|
||||
@@ -113,7 +115,7 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
mSocketConnector.setConnectTimeoutCheckInterval(10000);
|
||||
|
||||
// mSocketConnector.getFilterChain().addLast("logger",
|
||||
// new LoggingFilter(IcecastTCPAudioBroadcaster.class));
|
||||
// new LoggingFilter(IcecastHTTPAudioBroadcaster.class));
|
||||
|
||||
mSocketConnector.getFilterChain().addLast("codec", new HttpClientCodec());
|
||||
mSocketConnector.setHandler(new IcecastHTTPIOHandler());
|
||||
@@ -146,12 +148,12 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
}
|
||||
else if(throwableCause != null)
|
||||
{
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||
mLog.debug("Failed to connect", rie);
|
||||
}
|
||||
else
|
||||
{
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||
mLog.debug("Failed to connect - no exception is available");
|
||||
}
|
||||
|
||||
@@ -266,25 +268,30 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
{
|
||||
if(throwable instanceof ProtocolDecoderException)
|
||||
{
|
||||
Throwable cause = ((ProtocolDecoderException) throwable).getCause();
|
||||
Throwable cause = throwable.getCause();
|
||||
|
||||
if(cause instanceof HttpException &&
|
||||
((HttpException) cause).getStatusCode() == HttpStatus.CLIENT_ERROR_LENGTH_REQUIRED.code())
|
||||
/**
|
||||
* Warning: Hack. Icecast 2.4.2+ doesn't like to send HTTP headers along with their HTTP/1.0 OK
|
||||
* message which causes Apache Mina to think that it hasn't received the full HTTP response. So, we
|
||||
* 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))
|
||||
{
|
||||
//Ignore - Icecast 2.4 sometimes doesn't send any headers with their HTTP responses. Mina expects a
|
||||
//content-length for HTTP responses.
|
||||
setBroadcastState(BroadcastState.CONNECTED);
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("HTTP protocol decoder error", throwable);
|
||||
setBroadcastState(BroadcastState.TEMPORARY_BROADCAST_ERROR);
|
||||
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("Broadcast error", throwable);
|
||||
setBroadcastState(BroadcastState.TEMPORARY_BROADCAST_ERROR);
|
||||
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||
disconnect();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast.shoutcast.v1;
|
||||
|
||||
@@ -203,6 +205,15 @@ public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUp
|
||||
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);
|
||||
@@ -219,7 +230,17 @@ public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUp
|
||||
sb.append("TO:UNKNOWN");
|
||||
}
|
||||
|
||||
Identifier from = identifierCollection.getIdentifier(IdentifierClass.USER, Form.TALKGROUP, Role.FROM);
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast.shoutcast.v2;
|
||||
|
||||
@@ -278,6 +280,8 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr
|
||||
|
||||
if(identifierCollection != null)
|
||||
{
|
||||
StringBuilder sbTitle2 = new StringBuilder();
|
||||
|
||||
AliasList aliasList = mAliasModel.getAliasList(identifierCollection);
|
||||
|
||||
Identifier to = identifierCollection.getIdentifier(IdentifierClass.USER, Form.PATCH_GROUP, Role.TO);
|
||||
@@ -287,39 +291,61 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr
|
||||
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);
|
||||
sbTitle2.append("TO:").append(to);
|
||||
|
||||
List<Alias> aliases = aliasList.getAliases(to);
|
||||
|
||||
if(!aliases.isEmpty())
|
||||
{
|
||||
sb.append(" ").append(Joiner.on(", ").skipNulls().join(aliases));
|
||||
sbTitle2.append(" ").append(Joiner.on(", ").skipNulls().join(aliases));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("TO:UNKNOWN");
|
||||
sbTitle2.append("TO:UNKNOWN");
|
||||
}
|
||||
|
||||
Identifier from = identifierCollection.getIdentifier(IdentifierClass.USER, Form.TALKGROUP, Role.FROM);
|
||||
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);
|
||||
sbTitle2.append(" FROM:").append(from);
|
||||
|
||||
List<Alias> aliases = aliasList.getAliases(from);
|
||||
|
||||
if(!aliases.isEmpty())
|
||||
{
|
||||
sb.append(" ").append(Joiner.on(", ").skipNulls().join(aliases));
|
||||
sbTitle2.append(" ").append(Joiner.on(", ").skipNulls().join(aliases));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(" FROM:UNKNOWN");
|
||||
sbTitle2.append(" FROM:UNKNOWN");
|
||||
}
|
||||
|
||||
sb.append(UltravoxMetadata.TITLE_2.asXML(sbTitle2.toString()));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.codec.mbe;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public abstract class AmbeAudioModule extends JmbeAudioModule
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(AmbeAudioModule.class);
|
||||
private static final String AMBE_CODEC = "AMBE 3600 x 2450";
|
||||
private static boolean sLibraryStatusLogged = false;
|
||||
|
||||
public AmbeAudioModule(UserPreferences userPreferences, AliasList aliasList)
|
||||
{
|
||||
super(userPreferences, aliasList);
|
||||
|
||||
if(!sLibraryStatusLogged)
|
||||
{
|
||||
if(getAudioCodec() != null)
|
||||
{
|
||||
mLog.info("AMBE CODEC successfully loaded - P25-2/DMR/NXDN audio will be available");
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.warn("AMBE CODEC not loaded - P25-2/DMR/NXDN audio will NOT be available");
|
||||
}
|
||||
|
||||
sLibraryStatusLogged = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCodecName()
|
||||
{
|
||||
return AMBE_CODEC;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.codec.mbe;
|
||||
|
||||
import io.github.dsheirer.identifier.encryption.EncryptionKeyIdentifier;
|
||||
|
||||
/**
|
||||
* P25 Encryption Parameters
|
||||
*/
|
||||
public interface IEncryptionSyncParameters
|
||||
{
|
||||
/**
|
||||
* Encryption key identifier
|
||||
*/
|
||||
EncryptionKeyIdentifier getEncryptionKey();
|
||||
|
||||
/**
|
||||
* Message Indicator
|
||||
* @return key generator fill values
|
||||
*/
|
||||
String getMessageIndicator();
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.codec.mbe;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public abstract class ImbeAudioModule extends JmbeAudioModule
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(ImbeAudioModule.class);
|
||||
private static final String IMBE_CODEC = "IMBE";
|
||||
private static boolean sLibraryStatusLogged = false;
|
||||
|
||||
public ImbeAudioModule(UserPreferences userPreferences, AliasList aliasList)
|
||||
{
|
||||
super(userPreferences, aliasList);
|
||||
|
||||
if(!sLibraryStatusLogged)
|
||||
{
|
||||
if(getAudioCodec() != null)
|
||||
{
|
||||
mLog.info("JMBE audio conversion library IMBE CODEC successfully loaded - P25-1 audio will be available");
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.warn("JMBE audio conversion library, IMBE CODEC not loaded - P25-1 audio will NOT be available");
|
||||
}
|
||||
|
||||
sLibraryStatusLogged = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCodecName()
|
||||
{
|
||||
return IMBE_CODEC;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.codec.mbe;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.audio.AbstractAudioModule;
|
||||
import io.github.dsheirer.audio.squelch.ISquelchStateListener;
|
||||
import io.github.dsheirer.eventbus.MyEventBus;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
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;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
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 IAudioCodec mAudioCodec;
|
||||
private UserPreferences mUserPreferences;
|
||||
|
||||
public JmbeAudioModule(UserPreferences userPreferences, AliasList aliasList)
|
||||
{
|
||||
super(aliasList);
|
||||
mUserPreferences = userPreferences;
|
||||
MyEventBus.getEventBus().register(this);
|
||||
loadConverter();
|
||||
}
|
||||
|
||||
protected IAudioCodec getAudioCodec()
|
||||
{
|
||||
return mAudioCodec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that the JMBE audio library has been loaded and a suitable audio codec is usable (ie non-null)
|
||||
*/
|
||||
protected boolean hasAudioCodec()
|
||||
{
|
||||
return getAudioCodec() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<IMessage> getMessageListener()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives notifications that the JMBE library preference has been updated via the Guava event bus
|
||||
*
|
||||
* @param preferenceType that was updated
|
||||
*/
|
||||
@Subscribe
|
||||
public void preferenceUpdated(PreferenceType preferenceType)
|
||||
{
|
||||
if(preferenceType == PreferenceType.JMBE_LIBRARY)
|
||||
{
|
||||
mLibraryLoadStatusLogged.clear();
|
||||
loadConverter();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of the CODEC to use from the JMBE library
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
protected void loadConverter()
|
||||
{
|
||||
IAudioCodec audioConverter = null;
|
||||
|
||||
Path path = mUserPreferences.getJmbeLibraryPreference().getPathJmbeLibrary();
|
||||
|
||||
if(path != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||
{
|
||||
mLog.info("Loading JMBE library from [" + path.toString() + "]");
|
||||
}
|
||||
|
||||
URLClassLoader childClassLoader = new URLClassLoader(new URL[]{path.toUri().toURL()},
|
||||
this.getClass().getClassLoader());
|
||||
|
||||
Class classToLoad = Class.forName("jmbe.JMBEAudioLibrary", true, childClassLoader);
|
||||
|
||||
Object instance = classToLoad.getDeclaredConstructor().newInstance();
|
||||
|
||||
if(instance instanceof IAudioCodecLibrary)
|
||||
{
|
||||
IAudioCodecLibrary library = (IAudioCodecLibrary)instance;
|
||||
|
||||
if((library.getMajorVersion() == 1 && library.getMinorVersion() >= 0 &&
|
||||
library.getBuildVersion() >= 0) || library.getMajorVersion() >= 1)
|
||||
{
|
||||
audioConverter = library.getAudioConverter(getCodecName());
|
||||
|
||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||
{
|
||||
mLog.info("JMBE audio conversion library loaded: " + library.getVersion());
|
||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||
{
|
||||
mLog.warn("JMBE library version 1.0.0 or higher is required - found: " + library.getVersion());
|
||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||
{
|
||||
mLog.info("JMBE audio conversion library NOT FOUND");
|
||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
||||
}
|
||||
}
|
||||
}
|
||||
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))
|
||||
{
|
||||
mLog.error("Couldn't load JMBE audio conversion library - invocation target exception", ite);
|
||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
||||
}
|
||||
}
|
||||
catch(InstantiationException e1)
|
||||
{
|
||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||
{
|
||||
mLog.error("Couldn't load JMBE audio conversion library - instantiation exception", e1);
|
||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
||||
}
|
||||
}
|
||||
catch(IllegalAccessException e1)
|
||||
{
|
||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||
{
|
||||
mLog.error("Couldn't load JMBE audio conversion library - security restrictions");
|
||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||
{
|
||||
mLog.warn("JMBE audio library path is NOT SET in your User Preferences.");
|
||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
||||
}
|
||||
}
|
||||
|
||||
if(audioConverter != null)
|
||||
{
|
||||
mAudioCodec = audioConverter;
|
||||
}
|
||||
else
|
||||
{
|
||||
mAudioCodec = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
mAudioCodec = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.codec.mbe;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
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.encryption.EncryptionKey;
|
||||
import io.github.dsheirer.module.decode.p25.audio.VoiceFrame;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* MBE Call Sequence containing one or more voice frames with optional encryption parameters and optional from and to
|
||||
* radio identifiers.
|
||||
*/
|
||||
@JsonRootName("mbe_call")
|
||||
@JsonPropertyOrder({"protocol", "call_type", "from", "to", "encrypted", "frames"})
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class MBECallSequence
|
||||
{
|
||||
private String mProtocol;
|
||||
private String mFromIdentifier;
|
||||
private String mToIdentifier;
|
||||
private String mCallType;
|
||||
private String mSystem;
|
||||
private String mSite;
|
||||
private boolean mEncrypted;
|
||||
private List<VoiceFrame> mVoiceFrames = new ArrayList<>();
|
||||
private IEncryptionSyncParameters mTemporaryEncryptionSyncParameters;
|
||||
|
||||
/**
|
||||
* Constructs a call sequence
|
||||
*/
|
||||
public MBECallSequence(String protocol)
|
||||
{
|
||||
mProtocol = protocol;
|
||||
}
|
||||
|
||||
public MBECallSequence()
|
||||
{
|
||||
//no-arg constructor for faster jackson deserialization
|
||||
}
|
||||
|
||||
/**
|
||||
* Protocol/format for the voice frames
|
||||
*/
|
||||
@JsonProperty("protocol")
|
||||
public String getProtocol()
|
||||
{
|
||||
return mProtocol;
|
||||
}
|
||||
|
||||
public void setProtocol(String protocol)
|
||||
{
|
||||
mProtocol = protocol;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Indicates if this sequences contains any audio frames
|
||||
*/
|
||||
@JsonIgnore
|
||||
public boolean hasAudio()
|
||||
{
|
||||
return mVoiceFrames.size() > 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this call sequence is encrypted
|
||||
*/
|
||||
@JsonProperty("encrypted")
|
||||
public boolean isEncrypted()
|
||||
{
|
||||
return mEncrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the encrypted status of this call sequence
|
||||
*
|
||||
* @param encrypted status
|
||||
*/
|
||||
public void setEncrypted(boolean encrypted)
|
||||
{
|
||||
mEncrypted = encrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets encryption sync parameters to use with the next sequence of voice frames. This is usually obtained from the
|
||||
* HDU (phase1) or the PTT (phase2)
|
||||
*
|
||||
* @param encryptionSyncParameters to apply to the next set of voice frames
|
||||
*/
|
||||
public void setEncryptionSyncParameters(IEncryptionSyncParameters encryptionSyncParameters)
|
||||
{
|
||||
mTemporaryEncryptionSyncParameters = encryptionSyncParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the from radio identifier
|
||||
*
|
||||
* @param from id
|
||||
*/
|
||||
public void setFromIdentifier(String from)
|
||||
{
|
||||
if(from != null && !from.isEmpty() && !from.contentEquals("0"))
|
||||
{
|
||||
mFromIdentifier = from;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Radio identifier that originated the call
|
||||
*
|
||||
* @return from id
|
||||
*/
|
||||
@JsonProperty("from")
|
||||
public String getFromIdentifier()
|
||||
{
|
||||
return mFromIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the to radio identifier
|
||||
*
|
||||
* @param to id
|
||||
*/
|
||||
public void setToIdentifier(String to)
|
||||
{
|
||||
if(to != null && !to.isEmpty() && !to.contentEquals("0"))
|
||||
{
|
||||
mToIdentifier = to;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Radio identifier that received the call
|
||||
*
|
||||
* @return to id
|
||||
*/
|
||||
@JsonProperty("to")
|
||||
public String getToIdentifier()
|
||||
{
|
||||
return mToIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call type
|
||||
*
|
||||
* @param type from GROUP, INDIVIDUAL, or TELEPHONE INTERCONNECT
|
||||
*/
|
||||
public void setCallType(String type)
|
||||
{
|
||||
mCallType = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the call type
|
||||
*
|
||||
* @return call type
|
||||
*/
|
||||
@JsonProperty("call_type")
|
||||
public String getCallType()
|
||||
{
|
||||
return mCallType;
|
||||
}
|
||||
|
||||
/**
|
||||
* System name defined by the user
|
||||
* @return
|
||||
*/
|
||||
@JsonProperty("system")
|
||||
public String getSystem()
|
||||
{
|
||||
return mSystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the system name
|
||||
* @param system
|
||||
*/
|
||||
public void setSystem(String system)
|
||||
{
|
||||
mSystem = system;
|
||||
}
|
||||
|
||||
/**
|
||||
* Site name defined by the user
|
||||
* @return
|
||||
*/
|
||||
@JsonProperty("site")
|
||||
public String getSite()
|
||||
{
|
||||
return mSite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the site name
|
||||
* @param site
|
||||
*/
|
||||
public void setSite(String site)
|
||||
{
|
||||
mSite = site;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ordered list of voice frames for the call sequence
|
||||
*
|
||||
* @return list of unencrypted or encrypted audio frames
|
||||
*/
|
||||
@JsonProperty("frames")
|
||||
public List<VoiceFrame> getVoiceFrames()
|
||||
{
|
||||
return mVoiceFrames;
|
||||
}
|
||||
|
||||
public void setVoiceFrames(List<VoiceFrame> voiceFrames)
|
||||
{
|
||||
mVoiceFrames = voiceFrames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an unencrypted audio frame to this call sequence
|
||||
*
|
||||
* @param timestamp of the audio frame
|
||||
* @param frame of hexadecimal values representing the transmitted audio frame and ecc bits
|
||||
*/
|
||||
public void addVoiceFrame(long timestamp, String frame)
|
||||
{
|
||||
if(mTemporaryEncryptionSyncParameters != null)
|
||||
{
|
||||
addEncryptedVoiceFrame(timestamp, frame, mTemporaryEncryptionSyncParameters);
|
||||
mTemporaryEncryptionSyncParameters = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
mVoiceFrames.add(new VoiceFrame(timestamp, frame));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an encrypted audio frame to this call sequence
|
||||
*
|
||||
* @param timestamp of the audio frame
|
||||
* @param frame of hexadecimal values representing the transmitted audio frame and ecc bits
|
||||
* @param parameters identifying the encryption used for the frame
|
||||
*/
|
||||
public void addEncryptedVoiceFrame(long timestamp, String frame, IEncryptionSyncParameters parameters)
|
||||
{
|
||||
EncryptionKey encryption = parameters.getEncryptionKey().getValue();
|
||||
addEncryptedVoiceFrame(timestamp, frame, encryption.getAlgorithm(), encryption.getKey(), parameters.getMessageIndicator());
|
||||
setEncrypted(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an encrypted audio frame to this call sequence
|
||||
*
|
||||
* @param timestamp of the audio frame
|
||||
* @param frame of hexadecimal values representing the transmitted audio frame and ecc bits
|
||||
* @param algorithm identifier for encryption
|
||||
* @param keyid identifying which encryption key was used by the radio which may have multiple keys
|
||||
* @param messageIndicator to seed the key generator
|
||||
*/
|
||||
public void addEncryptedVoiceFrame(long timestamp, String frame, int algorithm, int keyid, String messageIndicator)
|
||||
{
|
||||
mVoiceFrames.add(new VoiceFrame(timestamp, frame, algorithm, keyid, messageIndicator));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.codec.mbe;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Utility for converting MBE call sequences (*.mbe) to PCM wave audio format
|
||||
*/
|
||||
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);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.codec.mbe;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.common.base.Joiner;
|
||||
import io.github.dsheirer.module.decode.p25.audio.VoiceFrame;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Reader for MBE call sequence recordings
|
||||
*/
|
||||
public class MBECallSequenceReader
|
||||
{
|
||||
public static List<String> getAudioFrames(Path path) throws IOException
|
||||
{
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
Object obj = mapper.readValue(path.toFile(), MBECallSequence.class);
|
||||
|
||||
if(obj instanceof MBECallSequence)
|
||||
{
|
||||
MBECallSequence sequence = (MBECallSequence)obj;
|
||||
|
||||
List<String> audioFrames = new ArrayList<>();
|
||||
|
||||
for(VoiceFrame voiceFrame: sequence.getVoiceFrames())
|
||||
{
|
||||
audioFrames.add(voiceFrame.getFrame());
|
||||
}
|
||||
|
||||
return audioFrames;
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
Path path = Path.of("/home/denny/SDRTrunk/recordings/20190706063149_154250000_7_TS1_65084_6591001.mbe");
|
||||
|
||||
try
|
||||
{
|
||||
List<String> frames = MBECallSequenceReader.getAudioFrames(path);
|
||||
|
||||
Files.writeString(Path.of("/home/denny/SDRTrunk/recordings/mbe_frames.txt"), Joiner.on("\",\n\"").join(frames));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.codec.mbe;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.message.IMessageListener;
|
||||
import io.github.dsheirer.module.Module;
|
||||
import io.github.dsheirer.preference.TimestampFormat;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Records MBE audio frame call sequences and metadata to a JSON format recording file
|
||||
*/
|
||||
public abstract class MBECallSequenceRecorder extends Module implements IMessageListener, Listener<IMessage>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(MBECallSequenceRecorder.class);
|
||||
|
||||
protected static final String CALL_TYPE_GROUP = "GROUP";
|
||||
protected static final String CALL_TYPE_INDIVIDUAL = "INDIVIDUAL";
|
||||
protected static final String CALL_TYPE_TELEPHONE_INTERCONNECT = "TELEPHONE INTERCONNECT";
|
||||
protected UserPreferences mUserPreferences;
|
||||
protected long mChannelFrequency;
|
||||
protected String mSystem;
|
||||
protected String mSite;
|
||||
private int mCallNumber = 1;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param userPreferences to obtain recording directory
|
||||
* @param channelFrequency for the channel to record
|
||||
*/
|
||||
public MBECallSequenceRecorder(UserPreferences userPreferences, long channelFrequency, String system, String site)
|
||||
{
|
||||
mUserPreferences = userPreferences;
|
||||
mChannelFrequency = channelFrequency;
|
||||
mSystem = system;
|
||||
mSite = site;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an MBE call sequence recording to the recording directory
|
||||
* @param sequence to write
|
||||
*/
|
||||
protected void writeCallSequence(MBECallSequence sequence)
|
||||
{
|
||||
writeCallSequence(sequence, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an MBE call sequence recording to the recording directory.
|
||||
*
|
||||
* @param optionalChannelTag to include in the filename
|
||||
* @param sequence containing voice frames
|
||||
*/
|
||||
protected void writeCallSequence(MBECallSequence sequence, String optionalChannelTag)
|
||||
{
|
||||
if(sequence != null && sequence.hasAudio())
|
||||
{
|
||||
sequence.setSystem(mSystem);
|
||||
sequence.setSite(mSite);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(TimestampFormat.TIMESTAMP_COMPACT.getFormatter().format(new Date(System.currentTimeMillis())));
|
||||
sb.append("_").append(mChannelFrequency);
|
||||
sb.append("_").append(mCallNumber++);
|
||||
|
||||
if(mCallNumber < 1)
|
||||
{
|
||||
mCallNumber = 1;
|
||||
}
|
||||
|
||||
if(optionalChannelTag != null)
|
||||
{
|
||||
sb.append("_").append(optionalChannelTag);
|
||||
}
|
||||
|
||||
if(sequence.getToIdentifier() != null)
|
||||
{
|
||||
sb.append("_").append(sequence.getToIdentifier().replace(":", ""));
|
||||
}
|
||||
if(sequence.getFromIdentifier() != null)
|
||||
{
|
||||
sb.append("_").append(sequence.getFromIdentifier().replace(":", ""));
|
||||
}
|
||||
|
||||
if(sequence.isEncrypted())
|
||||
{
|
||||
sb.append("_encrypted");
|
||||
}
|
||||
|
||||
sb.append(".mbe");
|
||||
|
||||
Path recordingDirectory = mUserPreferences.getDirectoryPreference().getDirectoryRecording();
|
||||
Path filePath = recordingDirectory.resolve(sb.toString());
|
||||
|
||||
try
|
||||
{
|
||||
OutputStream outputStream = Files.newOutputStream(filePath);
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.writerWithDefaultPrettyPrinter().writeValue(outputStream, sequence);
|
||||
outputStream.close();
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Couldn't write MBE call sequence to path [" + filePath.toString() + "]", ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of IMessageListener interface
|
||||
*/
|
||||
@Override
|
||||
public Listener<IMessage> getMessageListener()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,6 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface IAudioConverter
|
||||
@@ -27,7 +25,7 @@ public interface IAudioConverter
|
||||
/**
|
||||
* Converts the PCM audio packets to converted audio format. May produce partial audio frame data.
|
||||
*/
|
||||
public byte[] convert(List<ReusableAudioPacket> audioPackets);
|
||||
public byte[] convert(List<float[]> audioBuffers);
|
||||
|
||||
/**
|
||||
* Finalizes audio conversion by fully converting any partial frames left in the buffer and returning the
|
||||
|
||||
@@ -20,7 +20,6 @@ package io.github.dsheirer.audio.convert;
|
||||
|
||||
import io.github.dsheirer.audio.AudioFormats;
|
||||
import io.github.dsheirer.audio.AudioUtils;
|
||||
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
|
||||
import net.sourceforge.lame.lowlevel.LameEncoder;
|
||||
import net.sourceforge.lame.mp3.Lame;
|
||||
import net.sourceforge.lame.mp3.MPEGMode;
|
||||
@@ -54,7 +53,7 @@ public class MP3AudioConverter implements IAudioConverter
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] convert(List<ReusableAudioPacket> audioPackets)
|
||||
public byte[] convert(List<float[]> audioPackets)
|
||||
{
|
||||
mMP3Stream.reset();
|
||||
|
||||
@@ -84,6 +83,36 @@ public class MP3AudioConverter implements IAudioConverter
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] convertAudio(List<float[]> audioBuffers)
|
||||
{
|
||||
mMP3Stream.reset();
|
||||
|
||||
byte[] pcmBytes = AudioUtils.convert(audioBuffers);
|
||||
|
||||
int pcmBufferSize = Math.min(mMP3Buffer.length, pcmBytes.length);
|
||||
|
||||
int mp3BufferSize = 0;
|
||||
|
||||
int pcmBytesPosition = 0;
|
||||
|
||||
try
|
||||
{
|
||||
while (0 < (mp3BufferSize = mEncoder.encodeBuffer(pcmBytes, pcmBytesPosition, pcmBufferSize, mMP3Buffer)))
|
||||
{
|
||||
pcmBytesPosition += pcmBufferSize;
|
||||
pcmBufferSize = Math.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()
|
||||
{
|
||||
|
||||
@@ -18,9 +18,6 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
import io.github.dsheirer.record.mp3.MP3Recorder;
|
||||
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
|
||||
import io.github.dsheirer.sample.buffer.ReusableAudioPacketQueue;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -31,10 +28,11 @@ import java.util.List;
|
||||
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(MP3Recorder.MP3_BIT_RATE, MP3Recorder.CONSTANT_BIT_RATE);
|
||||
private MP3AudioConverter mGenerator = new MP3AudioConverter(MP3_BIT_RATE, CONSTANT_BIT_RATE);
|
||||
private byte[] mPreviousPartialFrameData;
|
||||
private ReusableAudioPacketQueue mAudioPacketQueue = new ReusableAudioPacketQueue("MP3 Silence Generator");
|
||||
|
||||
/**
|
||||
* Generates MP3 audio silence frames
|
||||
@@ -46,13 +44,10 @@ public class MP3SilenceGenerator implements ISilenceGenerator
|
||||
public byte[] generate(long duration)
|
||||
{
|
||||
int length = (int)(duration * 8); //8000 Hz sample rate
|
||||
ReusableAudioPacket silencePacket = mAudioPacketQueue.getBuffer(length);
|
||||
Arrays.fill(silencePacket.getAudioSamples(), 0.0f);
|
||||
|
||||
List<ReusableAudioPacket> silencePackets = new ArrayList<>();
|
||||
silencePackets.add(silencePacket);
|
||||
|
||||
byte[] frameData = mGenerator.convert(silencePackets);
|
||||
List<float[]> silenceBuffers = new ArrayList<>();
|
||||
silenceBuffers.add(new float[length]);
|
||||
byte[] frameData = mGenerator.convert(silenceBuffers);
|
||||
|
||||
frameData = merge(mPreviousPartialFrameData, frameData);
|
||||
|
||||
|
||||
@@ -0,0 +1,491 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv;
|
||||
|
||||
import com.fazecast.jSerialComm.SerialPort;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.AmbeMessage;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.AmbeMessageFactory;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.VocoderRate;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.request.AmbeRequest;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.request.DecodeSpeechRequest;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.request.EncodeSpeechRequest;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.request.ResetRequest;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.request.SetVocoderParametersRequest;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.request.SetVocoderRequest;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.DecodeSpeechResponse;
|
||||
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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Northwest Digital Radio (NWDR) ThumbDv dongle.
|
||||
*
|
||||
* Note: linux users must allow access to the serial port:
|
||||
*
|
||||
* sudo usermod -a -G uucp username
|
||||
* sudo usermod -a -G dialout username
|
||||
* sudo usermod -a -G lock username
|
||||
* sudo usermod -a -G tty username
|
||||
*/
|
||||
public class ThumbDv implements AutoCloseable
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ThumbDv.class);
|
||||
private static final String PORT_DESCRIPTION = "USB-to-Serial Port (ftdi_sio)";
|
||||
private static final String PORT_DESCRIPTION_FRAGMENT = "USB Serial Port";
|
||||
private static final int BAUD_RATE = 460800;
|
||||
private static final byte PACKET_START = (byte) 0x61;
|
||||
|
||||
public enum AudioProtocol
|
||||
{
|
||||
DMR,
|
||||
DSTAR,
|
||||
NXDN,
|
||||
P25_PHASE2,
|
||||
}
|
||||
|
||||
private SerialPort mSerialPort;
|
||||
private ScheduledFuture mSerialPortReaderHandle;
|
||||
private ScheduledFuture mAudioDecodeProcessorHandle;
|
||||
private LinkedTransferQueue<DecodeSpeechRequest> mDecodeSpeechRequests = new LinkedTransferQueue<>();
|
||||
private AudioProtocol mAudioProtocol;
|
||||
private Listener<ReusableFloatBuffer> mAudioBufferListener;
|
||||
private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("ThumbDv");
|
||||
private boolean mStarted;
|
||||
|
||||
public ThumbDv(AudioProtocol audioProtocol, Listener<ReusableFloatBuffer> listener)
|
||||
{
|
||||
mAudioProtocol = audioProtocol;
|
||||
mAudioBufferListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the audio code frame for decoding. Decoded PCM speech packet will be sent to the registered
|
||||
* audio packet listener.
|
||||
*
|
||||
* @param codecFrame
|
||||
*/
|
||||
public void decode(byte[] codecFrame)
|
||||
{
|
||||
if(!mStarted || mSerialPort == null)
|
||||
{
|
||||
throw new IllegalStateException("Must invoke start() method and thumbdv serial device must be available");
|
||||
}
|
||||
|
||||
mDecodeSpeechRequests.offer(new DecodeSpeechRequest(codecFrame));
|
||||
}
|
||||
|
||||
public void close() throws IOException
|
||||
{
|
||||
if(mSerialPortReaderHandle != null)
|
||||
{
|
||||
mSerialPortReaderHandle.cancel(true);
|
||||
mSerialPortReaderHandle = null;
|
||||
}
|
||||
|
||||
if(mSerialPort != null)
|
||||
{
|
||||
mSerialPort.closePort();
|
||||
}
|
||||
}
|
||||
|
||||
public void start() throws IOException
|
||||
{
|
||||
if(mSerialPort == null)
|
||||
{
|
||||
SerialPort[] ports = SerialPort.getCommPorts();
|
||||
|
||||
for(SerialPort port : ports)
|
||||
{
|
||||
mLog.debug("Serial Port Name:" + port.getDescriptivePortName());
|
||||
if(port.getDescriptivePortName().contentEquals(PORT_DESCRIPTION) ||
|
||||
port.getDescriptivePortName().contains(PORT_DESCRIPTION_FRAGMENT))
|
||||
{
|
||||
mSerialPort = port;
|
||||
mSerialPort.setBaudRate(BAUD_RATE);
|
||||
mSerialPort.openPort(0, 5000, 10000);
|
||||
|
||||
if(mSerialPort.isOpen())
|
||||
{
|
||||
mLog.info("Resetting ThumbDv Device");
|
||||
send(new ResetRequest());
|
||||
|
||||
mLog.info("Creating Serial Port Reader");
|
||||
final Runnable r = new SerialPortReader(mSerialPort.getInputStream());
|
||||
mLog.info("Starting Serial Port Reader");
|
||||
mSerialPortReaderHandle = ThreadPool.SCHEDULED.scheduleAtFixedRate(r, 0,
|
||||
5, TimeUnit.MILLISECONDS);
|
||||
|
||||
mStarted = true;
|
||||
mLog.info("Startup complete - awaiting reset response");
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.warn("Could not open serial port: " + mSerialPort.getSystemPortName());
|
||||
throw new IOException("Could not open serial port:" + mSerialPort.getSystemPortName());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(mSerialPort == null)
|
||||
{
|
||||
throw new IOException("ThumbDV serial port not found");
|
||||
}
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
public void send(byte[] message) throws IOException
|
||||
{
|
||||
if(mSerialPort == null)
|
||||
{
|
||||
throw new IOException("ThumbDv must be started before use");
|
||||
}
|
||||
|
||||
int bytesWritten = mSerialPort.writeBytes(message, message.length);
|
||||
|
||||
if(bytesWritten < 0)
|
||||
{
|
||||
throw new IOException("Unable to write message:" + Arrays.toString(message));
|
||||
}
|
||||
else if(bytesWritten != message.length)
|
||||
{
|
||||
throw new IOException("Unable to write message:" + Arrays.toString(message) + " Bytes Written:" + bytesWritten);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the AMBE request message
|
||||
*
|
||||
* @param request message
|
||||
* @throws IOException if there is an error
|
||||
*/
|
||||
public void send(AmbeRequest request) throws IOException
|
||||
{
|
||||
send(request.getData());
|
||||
}
|
||||
|
||||
private void receive(byte[] bytes)
|
||||
{
|
||||
AmbeMessage message = AmbeMessageFactory.getMessage(bytes);
|
||||
|
||||
if(message instanceof DecodeSpeechResponse && mAudioBufferListener != null)
|
||||
{
|
||||
float[] samples = ((DecodeSpeechResponse)message).getSamples();
|
||||
ReusableFloatBuffer audioBuffer = mReusableBufferQueue.getBuffer(samples, System.currentTimeMillis());
|
||||
mAudioBufferListener.receive(audioBuffer);
|
||||
}
|
||||
else if(message instanceof ReadyResponse && mAudioDecodeProcessorHandle == null)
|
||||
{
|
||||
if(mAudioDecodeProcessorHandle == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch(mAudioProtocol)
|
||||
{
|
||||
case DSTAR:
|
||||
send(new SetVocoderRequest(VocoderRate.RATE_33));
|
||||
send(new SetVocoderParametersRequest(0x0130, 0x0763, 0x4000, 0x0000, 0x0000, 0x0048));
|
||||
break;
|
||||
case DMR:
|
||||
case NXDN:
|
||||
case P25_PHASE2:
|
||||
send(new SetVocoderRequest(VocoderRate.RATE_33));
|
||||
send(new SetVocoderParametersRequest(0x0431, 0x0754, 0x2400, 0x0000, 0x0000, 0x6F48));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unrecognized audio protocol:" + mAudioProtocol);
|
||||
}
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error setting audio protocol vocoder parameters");
|
||||
}
|
||||
|
||||
mLog.info("ThumbDv Reset Complete");
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.info("ThumbDv Reset Detected");
|
||||
}
|
||||
}
|
||||
else if(message instanceof SetVocoderParameterResponse)
|
||||
{
|
||||
if(((SetVocoderParameterResponse)message).isSuccessful())
|
||||
{
|
||||
mLog.info("Audio vocoder parameters configured for " + mAudioProtocol);
|
||||
//Start the audio frame decode processor
|
||||
mAudioDecodeProcessorHandle = ThreadPool.SCHEDULED.scheduleAtFixedRate(new AudioDecodeProcessor(), 0,
|
||||
10, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
else if(message != null)
|
||||
{
|
||||
mLog.debug("RECEIVED:" + message.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the current settings of the serial port. Note: you must invoke start() before this method so that
|
||||
* the serial port can be discovered and opened.
|
||||
*/
|
||||
public void logSerialPort()
|
||||
{
|
||||
if(mSerialPort != null)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("\nPort:\t\t\t").append(mSerialPort.getSystemPortName()).append("\n");
|
||||
sb.append("Name:\t\t\t").append(mSerialPort.getDescriptivePortName()).append("\n");
|
||||
sb.append("Baud Rate:\t\t").append(mSerialPort.getBaudRate()).append("\n");
|
||||
sb.append("Data Bits:\t\t").append(mSerialPort.getNumDataBits()).append("\n");
|
||||
sb.append("Parity Bits:\t").append(mSerialPort.getParity()).append("\n");
|
||||
sb.append("Stop Bits:\t\t").append(mSerialPort.getNumStopBits()).append("\n");
|
||||
sb.append("Flow Control:\t").append(mSerialPort.getFlowControlSettings()).append("\n");
|
||||
sb.append("Is Open:\t\t").append(mSerialPort.isOpen()).append("\n");
|
||||
|
||||
mLog.info(sb.toString());
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.info("No serial port found");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the audio frame decode queue
|
||||
*/
|
||||
public class AudioDecodeProcessor implements Runnable
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
DecodeSpeechRequest request = mDecodeSpeechRequests.poll();
|
||||
|
||||
if(request != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
send(request);
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error decoding audio frame", ioe);
|
||||
}
|
||||
|
||||
// try
|
||||
// {
|
||||
// //Force a 20ms sleep after submitting an audio frame to avoid overflowing the thumbdv
|
||||
// Thread.sleep(20);
|
||||
// }
|
||||
// catch(InterruptedException ie)
|
||||
// {
|
||||
// //Do nothing
|
||||
// }
|
||||
}
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
mLog.error("Error", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SerialPortReader implements Runnable
|
||||
{
|
||||
private InputStream mInputStream;
|
||||
|
||||
public SerialPortReader(InputStream inputStream)
|
||||
{
|
||||
mInputStream = inputStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
if(mInputStream != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
while(mInputStream.available() > 0)
|
||||
{
|
||||
byte[] buffer = new byte[400];
|
||||
|
||||
int bytesRead = mInputStream.readNBytes(buffer, 0, 1);
|
||||
|
||||
if(bytesRead == 1 && buffer[0] == PACKET_START)
|
||||
{
|
||||
while(mInputStream.available() < 2)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch(InterruptedException ie)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
bytesRead = mInputStream.readNBytes(buffer, 1, 2);
|
||||
|
||||
if(bytesRead == 2)
|
||||
{
|
||||
int length = (0xFF & buffer[1]) << 8;
|
||||
length += (0xFF & buffer[2]);
|
||||
|
||||
if(0 < length && length < 400)
|
||||
{
|
||||
length++; //Add a byte for the type byte
|
||||
|
||||
while(mInputStream.available() < length)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch(InterruptedException ie)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
bytesRead = mInputStream.readNBytes(buffer, 3, length);
|
||||
|
||||
if(bytesRead == length)
|
||||
{
|
||||
receive(Arrays.copyOf(buffer, length + 3));
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.debug("Expected [" + length + "] but received [" + bytesRead + "] bytes - " + Arrays.toString(buffer));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("Received packet with unexpected length: " + length);
|
||||
//Don't process the buffer ... let the reader read and inspect 1 byte at a time
|
||||
//to regain sync on the packet start byte
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.debug("Expected [2] but received [" + bytesRead + "] -- this shouldn't happen");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.debug("Unrecognized byte: " + Arrays.toString(buffer));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error while reading ThumbDv serial port", ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
mLog.error("Error", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
mLog.debug("Starting");
|
||||
|
||||
String silence = "BEDDEA821EFD660C08";
|
||||
|
||||
String[] frames = {"0E46122323067C60F8", "0E469433C1067CF1BC", "0E46122B23067C60F8", "0E67162BE08874E2B4",
|
||||
"0E46163BE1067CF1BC", "0E46122B23067C60F8", "0A06163BE00A5C303E", "0E46122B23067C60F8", "0E46163BE1847CE1FC",
|
||||
"0E46122B23067C60F8"};
|
||||
|
||||
List<byte[]> frameData = new ArrayList<>();
|
||||
|
||||
for(String frame : frames)
|
||||
{
|
||||
byte[] bytes = new byte[frame.length() / 2];
|
||||
for(int x = 0; x < frame.length(); x += 2)
|
||||
{
|
||||
String hex = frame.substring(x, x + 2);
|
||||
bytes[x / 2] = (byte) (0xFF & Integer.parseInt(hex, 16));
|
||||
}
|
||||
|
||||
frameData.add(bytes);
|
||||
}
|
||||
|
||||
mLog.debug("Starting thumb dv thread(s)");
|
||||
|
||||
final Listener<ReusableFloatBuffer> listener = reusableAudioPacket -> {
|
||||
mLog.info("Got an audio packet!");
|
||||
reusableAudioPacket.decrementUserCount();
|
||||
};
|
||||
|
||||
try(ThumbDv thumbDv = new ThumbDv(AudioProtocol.P25_PHASE2, listener))
|
||||
{
|
||||
thumbDv.start();
|
||||
|
||||
Thread.sleep(6000);
|
||||
|
||||
for(int x = 0; x < 20; x++)
|
||||
{
|
||||
thumbDv.send(new EncodeSpeechRequest(new short[160]));
|
||||
Thread.sleep(20);
|
||||
}
|
||||
// for(byte[] frame : frameData)
|
||||
// {
|
||||
// thumbDv.decode(frame);
|
||||
// }
|
||||
|
||||
while(true);
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error", ioe);
|
||||
}
|
||||
catch(InterruptedException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv.message;
|
||||
|
||||
/**
|
||||
* AMBE-3000R Message
|
||||
*/
|
||||
public abstract class AmbeMessage
|
||||
{
|
||||
/**
|
||||
* Converts the byte array to a hexadecimal string array
|
||||
*/
|
||||
public static String toHex(byte[] data)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for(byte b: data)
|
||||
{
|
||||
sb.append(String.format("%02X", b));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv.message;
|
||||
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.DecodeSpeechResponse;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.EncodeSpeechResponse;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.GetConfigResponse;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.InitializeCodecResponse;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.ProductIdResponse;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.ReadyResponse;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetChannelFormatResponse;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetChannelResponse;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetPacketModeResponse;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetSpeechFormatResponse;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetVocoderParameterResponse;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetVocoderResponse;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.UnknownResponse;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.VersionResponse;
|
||||
|
||||
public class AmbeMessageFactory
|
||||
{
|
||||
private static final byte CONTROL_PACKET = (byte)0x00;
|
||||
private static final byte CHANNEL_PACKET = (byte)0x01;
|
||||
private static final byte SPEECH_PACKET = (byte)0x02;
|
||||
private static final int INDEX_PACKET_TYPE = 3;
|
||||
private static final int INDEX_CONTROL_PACKET_TYPE = 4;
|
||||
|
||||
|
||||
public static AmbeMessage getMessage(byte[] data)
|
||||
{
|
||||
if(data != null && data.length >= 5)
|
||||
{
|
||||
if(data[INDEX_PACKET_TYPE] == CONTROL_PACKET)
|
||||
{
|
||||
PacketField packetField = PacketField.fromValue(data[INDEX_CONTROL_PACKET_TYPE]);
|
||||
|
||||
switch(packetField)
|
||||
{
|
||||
case PKT_CHANNEL_0:
|
||||
return new SetChannelResponse(data);
|
||||
case PKT_CHANNEL_FORMAT:
|
||||
return new SetChannelFormatResponse(data);
|
||||
case PKT_CODEC_STOP:
|
||||
return new SetPacketModeResponse(data);
|
||||
case PKT_GET_CONFIG:
|
||||
return new GetConfigResponse(data);
|
||||
case PKT_INIT:
|
||||
return new InitializeCodecResponse(data);
|
||||
case PKT_PRODUCT_ID:
|
||||
return new ProductIdResponse(data);
|
||||
case PKT_RATE_PARAMETER:
|
||||
return new SetVocoderParameterResponse(data);
|
||||
case PKT_RATE_TABLE:
|
||||
return new SetVocoderResponse(data);
|
||||
case PKT_READY:
|
||||
return new ReadyResponse(data);
|
||||
case PKT_SPEECH_FORMAT:
|
||||
return new SetSpeechFormatResponse(data);
|
||||
case PKT_VERSION_STRING:
|
||||
return new VersionResponse(data);
|
||||
}
|
||||
}
|
||||
else if(data[INDEX_PACKET_TYPE] == CHANNEL_PACKET)
|
||||
{
|
||||
return new EncodeSpeechResponse(data);
|
||||
}
|
||||
else if(data[INDEX_PACKET_TYPE] == SPEECH_PACKET)
|
||||
{
|
||||
return new DecodeSpeechResponse(data);
|
||||
}
|
||||
}
|
||||
|
||||
return new UnknownResponse(data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv.message;
|
||||
|
||||
/**
|
||||
* AMBE-3000 Initialization Options
|
||||
*/
|
||||
public enum InitializeOption
|
||||
{
|
||||
ENCODER((byte)0x01),
|
||||
DECODER((byte)0x02),
|
||||
ENCODER_AND_DECODER((byte)0x03),
|
||||
ECHO_CANCELLER((byte)0x04),
|
||||
ENCODER_DECODER_ECHO_CANCELLER((byte)0x07);
|
||||
|
||||
private byte mCode;
|
||||
|
||||
InitializeOption(byte code)
|
||||
{
|
||||
mCode = code;
|
||||
}
|
||||
|
||||
public byte getCode()
|
||||
{
|
||||
return mCode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv.message;
|
||||
|
||||
/**
|
||||
* AMBE-3000R Control Packet Field enumeration
|
||||
*/
|
||||
public enum PacketField
|
||||
{
|
||||
PKT_ENCODER_CMODE((byte)0x05),
|
||||
PKT_DECODER_CMODE((byte)0x06),
|
||||
PKT_RATE_TABLE((byte)0x09),
|
||||
PKT_RATE_PARAMETER((byte)0x0A),
|
||||
PKT_INIT((byte)0x0B),
|
||||
PKT_LOW_POWER((byte)0x10),
|
||||
PKT_CHANNEL_FORMAT((byte)0x15),
|
||||
PKT_SPEECH_FORMAT((byte)0x16),
|
||||
PKT_CODEC_START((byte)0x2A),
|
||||
PKT_CODEC_STOP((byte)0x2B),
|
||||
PKT_PRODUCT_ID((byte)0x30),
|
||||
PKT_VERSION_STRING((byte)0x31),
|
||||
PKT_COMPAND((byte)0x32),
|
||||
PKT_RESET((byte)0x33),
|
||||
PKT_RESET_TO_SOFTWARE_CONFIG((byte)0x34),
|
||||
PKT_HALT((byte)0x35),
|
||||
PKT_GET_CONFIG((byte)0x36),
|
||||
PKT_READ_CONFIG((byte)0x37),
|
||||
PKT_CODEC_CFG((byte)0x38),
|
||||
PKT_READY((byte)0x39),
|
||||
PKT_PARITY_MODE((byte)0x3F),
|
||||
PKT_CHANNEL_0((byte)0x40),
|
||||
PKT_WRITE_I2C((byte)0x44),
|
||||
PKT_CLEAR_CODEC_RESET((byte)0x46),
|
||||
PKT_SET_CODEC_RESET((byte)0x47),
|
||||
PKT_DISCARD_CODEC_SAMPLES((byte)0x48),
|
||||
PKT_DELAY_NUMBER_MICRO_SECONDS((byte)0x49),
|
||||
PKT_DELAY_NUMBER_NANO_SECONDS((byte)0x4A),
|
||||
PKT_GAIN((byte)0x4B),
|
||||
PKT_RTS_THRESHOLD((byte)0x4E),
|
||||
|
||||
//Speech packet values
|
||||
SPEECH_DATA((byte)0x00),
|
||||
CHANNEL_DATA_HARD_SYMBOL((byte)0x01),
|
||||
CMODE((byte)0x02),
|
||||
SAMPLE_COUNT((byte)0x03),
|
||||
TONE((byte)0x08),
|
||||
CHANNEL_DATA_SOFT_SYMBOL((byte)0x17),
|
||||
VOCODER((byte)0x40),
|
||||
|
||||
//Packet Types
|
||||
PACKET_TYPE_CONTROL((byte)0x00),
|
||||
PACKET_TYPE_ENCODE_SPEECH((byte)0x01),
|
||||
PACKET_TYPE_DECODE_SPEECH((byte)0x02),
|
||||
|
||||
UNKNOWN((byte)0x00);
|
||||
|
||||
private byte mCode;
|
||||
|
||||
PacketField(byte code)
|
||||
{
|
||||
mCode = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Packet type byte code value
|
||||
*/
|
||||
public byte getCode()
|
||||
{
|
||||
return mCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup a packet type from the code byte value
|
||||
*/
|
||||
public static PacketField fromValue(byte value)
|
||||
{
|
||||
switch(value)
|
||||
{
|
||||
case (byte)0x05:
|
||||
return PKT_ENCODER_CMODE;
|
||||
case (byte)0x06:
|
||||
return PKT_DECODER_CMODE;
|
||||
case (byte)0x09:
|
||||
return PKT_RATE_TABLE;
|
||||
case (byte)0x0A:
|
||||
return PKT_RATE_PARAMETER;
|
||||
case (byte)0x0B:
|
||||
return PKT_INIT;
|
||||
case (byte)0x10:
|
||||
return PKT_LOW_POWER;
|
||||
case (byte)0x15:
|
||||
return PKT_CHANNEL_FORMAT;
|
||||
case (byte)0x16:
|
||||
return PKT_SPEECH_FORMAT;
|
||||
case (byte)0x2A:
|
||||
return PKT_CODEC_START;
|
||||
case (byte)0x2B:
|
||||
return PKT_CODEC_STOP;
|
||||
case (byte)0x30:
|
||||
return PKT_PRODUCT_ID;
|
||||
case (byte)0x31:
|
||||
return PKT_VERSION_STRING;
|
||||
case (byte)0x32:
|
||||
return PKT_COMPAND;
|
||||
case (byte)0x33:
|
||||
return PKT_RESET;
|
||||
case (byte)0x34:
|
||||
return PKT_RESET_TO_SOFTWARE_CONFIG;
|
||||
case (byte)0x35:
|
||||
return PKT_HALT;
|
||||
case (byte)0x36:
|
||||
return PKT_GET_CONFIG;
|
||||
case (byte)0x37:
|
||||
return PKT_READ_CONFIG;
|
||||
case (byte)0x38:
|
||||
return PKT_CODEC_CFG;
|
||||
case (byte)0x39:
|
||||
return PKT_READY;
|
||||
case (byte)0x3F:
|
||||
return PKT_PARITY_MODE;
|
||||
case (byte)0x40:
|
||||
return PKT_CHANNEL_0;
|
||||
case (byte)0x44:
|
||||
return PKT_WRITE_I2C;
|
||||
case (byte)0x46:
|
||||
return PKT_CLEAR_CODEC_RESET;
|
||||
case (byte)0x47:
|
||||
return PKT_SET_CODEC_RESET;
|
||||
case (byte)0x48:
|
||||
return PKT_DISCARD_CODEC_SAMPLES;
|
||||
case (byte)0x49:
|
||||
return PKT_DELAY_NUMBER_MICRO_SECONDS;
|
||||
case (byte)0x4A:
|
||||
return PKT_DELAY_NUMBER_NANO_SECONDS;
|
||||
case (byte)0x4B:
|
||||
return PKT_GAIN;
|
||||
case (byte)0x4E:
|
||||
return PKT_RTS_THRESHOLD;
|
||||
default:
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv.message;
|
||||
|
||||
public enum VocoderRate
|
||||
{
|
||||
//AMBE-1000 Standard Rates
|
||||
RATE_0((byte)0x00, 2400,0),
|
||||
RATE_1((byte)0x01, 3600,0),
|
||||
RATE_2((byte)0x02, 3600,1200),
|
||||
RATE_3((byte)0x03, 4800,0),
|
||||
RATE_4((byte)0x04, 9600,0),
|
||||
RATE_5((byte)0x05, 2350,50),
|
||||
RATE_6((byte)0x06, 4850,4750),
|
||||
RATE_7((byte)0x07, 4550,250),
|
||||
RATE_8((byte)0x08, 3100,1700),
|
||||
RATE_9((byte)0x09, 4400,2800),
|
||||
RATE_10((byte)0x0A, 4150,2250),
|
||||
RATE_11((byte)0x0B, 3350,250),
|
||||
RATE_12((byte)0x0C, 7750,250),
|
||||
RATE_13((byte)0x0D, 4650,3350),
|
||||
RATE_14((byte)0x0E, 3750,250),
|
||||
RATE_15((byte)0x0F, 4000,0),
|
||||
|
||||
//AMBE-2000 Standard Rates
|
||||
RATE_16((byte)0x10, 3600,0),
|
||||
RATE_17((byte)0x11, 4000,0),
|
||||
RATE_18((byte)0x12, 4800,0),
|
||||
RATE_19((byte)0x13, 6400,0),
|
||||
RATE_20((byte)0x14, 8000,0),
|
||||
RATE_21((byte)0x15, 9600,0),
|
||||
RATE_22((byte)0x16, 2400,1600),
|
||||
RATE_23((byte)0x17, 3600,1200),
|
||||
RATE_24((byte)0x18, 4000,800),
|
||||
RATE_25((byte)0x19, 2400,2400),
|
||||
RATE_26((byte)0x1A, 4000,2400),
|
||||
RATE_27((byte)0x1B, 4400,2800), //Is this compatible with APCO25 Phase 1?
|
||||
RATE_28((byte)0x1C, 4000,4000),
|
||||
RATE_29((byte)0x1D, 2400,7200),
|
||||
RATE_30((byte)0x1E, 3600,6000),
|
||||
RATE_31((byte)0x1F, 2000,0),
|
||||
RATE_32((byte)0x20, 3600,2800),
|
||||
|
||||
//AMBE-3000 Standard Rates (AMBE+2)
|
||||
RATE_33((byte)0x21, 2450,1150), //APCO25 HR (Phase 2), DMR, NXDN, DSTAR
|
||||
RATE_34((byte)0x22, 2450,0),
|
||||
RATE_35((byte)0x23, 2250,1150),
|
||||
RATE_36((byte)0x24, 2250,0),
|
||||
RATE_37((byte)0x25, 2400,0),
|
||||
RATE_38((byte)0x26, 3000,0),
|
||||
RATE_39((byte)0x27, 3600,0),
|
||||
RATE_40((byte)0x28, 4000,0),
|
||||
RATE_41((byte)0x29, 4400,0),
|
||||
RATE_42((byte)0x2A, 4800,0),
|
||||
RATE_43((byte)0x2B, 6400,0),
|
||||
RATE_44((byte)0x2C, 7200,0),
|
||||
RATE_45((byte)0x2D, 8000,0),
|
||||
RATE_46((byte)0x2E, 9600,0),
|
||||
RATE_47((byte)0x2F, 2450,250),
|
||||
RATE_48((byte)0x30, 3350,250),
|
||||
RATE_49((byte)0x31, 3750,250),
|
||||
RATE_50((byte)0x32, 4550,250),
|
||||
RATE_51((byte)0x33, 2450,1950),
|
||||
RATE_52((byte)0x34, 2450,2350),
|
||||
RATE_53((byte)0x35, 2450,3550),
|
||||
RATE_54((byte)0x36, 2450,4750),
|
||||
RATE_55((byte)0x37, 2600,1400),
|
||||
RATE_56((byte)0x38, 3600,1200),
|
||||
RATE_57((byte)0x39, 4000,800),
|
||||
RATE_58((byte)0x3A, 4000,2400),
|
||||
RATE_59((byte)0x3B, 4400,2800), //Is this compatible with APCO25 Phase 1?
|
||||
RATE_60((byte)0x3C, 4000,4000),
|
||||
RATE_61((byte)0x3D, 3600,6000);
|
||||
|
||||
private byte mCode;
|
||||
private int mSpeechRate;
|
||||
private int mFecRate;
|
||||
|
||||
VocoderRate(byte code, int speechRate, int fecRate)
|
||||
{
|
||||
mCode = code;
|
||||
mSpeechRate = speechRate;
|
||||
mFecRate = fecRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Byte code value for the vocoder rate
|
||||
*/
|
||||
public byte getCode()
|
||||
{
|
||||
return mCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Speech rate in bits per second (bps)
|
||||
*/
|
||||
public int getSpeechRate()
|
||||
{
|
||||
return mSpeechRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward Error Correction rate in bits per second (bps)
|
||||
*/
|
||||
public int getFecRate()
|
||||
{
|
||||
return mFecRate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
||||
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.AmbeMessage;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||
|
||||
/**
|
||||
* AMBE-3000R Request Packet
|
||||
*/
|
||||
public abstract class AmbeRequest extends AmbeMessage
|
||||
{
|
||||
private static final byte PACKET_START_BYTE = (byte)0x61;
|
||||
protected static final int PACKET_TYPE_INDEX = 3;
|
||||
protected static final int PAYLOAD_START_INDEX = 4;
|
||||
|
||||
public abstract PacketField getType();
|
||||
public abstract byte[] getData();
|
||||
|
||||
/**
|
||||
* Creates a byte array of the specified length plus 4 packet header bytes with packet start, length and control
|
||||
* bytes filled in.
|
||||
*
|
||||
* @param length of the message (not including the 4 byte packet header)
|
||||
* @return byte array with packet header pre-filled.
|
||||
*/
|
||||
protected byte[] createMessage(int length, PacketField type)
|
||||
{
|
||||
byte[] data = new byte[length + 4];
|
||||
|
||||
data[0] = PACKET_START_BYTE;
|
||||
data[1] = (byte)((length >> 8 & 0xFF));
|
||||
data[2] = (byte)(length & 0xFF);
|
||||
|
||||
if(type == PacketField.PACKET_TYPE_ENCODE_SPEECH || type == PacketField.PACKET_TYPE_DECODE_SPEECH)
|
||||
{
|
||||
data[PACKET_TYPE_INDEX] = type.getCode();
|
||||
}
|
||||
else
|
||||
{
|
||||
data[PACKET_TYPE_INDEX] = PacketField.PACKET_TYPE_CONTROL.getCode();
|
||||
data[PAYLOAD_START_INDEX] = type.getCode();
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
||||
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.VocoderRate;
|
||||
|
||||
/**
|
||||
* Decode speech request, used to request decode of an encoded audio frame.
|
||||
*/
|
||||
public class DecodeSpeechRequest extends AmbeRequest
|
||||
{
|
||||
private static final int CHANNEL_DATA_IDENTIFIER_INDEX = 4;
|
||||
private static final byte SAMPLE_COUNT = (byte)(0xFF & 160); //8 kHz Audio sample count for 20ms frame
|
||||
|
||||
private byte[] mAudioFrame;
|
||||
private VocoderRate mVocoderRate;
|
||||
|
||||
/**
|
||||
* Constructs an audio frame decode request using the specified vocoder rate.
|
||||
* @param audioFrame of encoded audio samples
|
||||
* @param vocoderRate to use when decoding
|
||||
*/
|
||||
public DecodeSpeechRequest(byte[] audioFrame, VocoderRate vocoderRate)
|
||||
{
|
||||
mAudioFrame = audioFrame;
|
||||
mVocoderRate = vocoderRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an audio frame decode request using the current vocoder rate.
|
||||
* @param audioFrame of encoded audio samples
|
||||
*/
|
||||
public DecodeSpeechRequest(byte[] audioFrame)
|
||||
{
|
||||
this(audioFrame, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketField getType()
|
||||
{
|
||||
return PacketField.PACKET_TYPE_ENCODE_SPEECH;
|
||||
}
|
||||
|
||||
private boolean hasVocoderRate()
|
||||
{
|
||||
return mVocoderRate != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getData()
|
||||
{
|
||||
if(hasVocoderRate())
|
||||
{
|
||||
int length = mAudioFrame.length + 9;
|
||||
|
||||
byte[] data = createMessage(length, getType());
|
||||
int offset = CHANNEL_DATA_IDENTIFIER_INDEX;
|
||||
|
||||
data[offset++] = PacketField.VOCODER.getCode();
|
||||
data[offset++] = mVocoderRate.getCode();
|
||||
|
||||
data[offset++] = PacketField.CHANNEL_DATA_HARD_SYMBOL.getCode();
|
||||
data[offset++] = (byte)(0xFF & (mAudioFrame.length * 8));
|
||||
System.arraycopy(mAudioFrame, 0, data, offset, mAudioFrame.length);
|
||||
offset += mAudioFrame.length;
|
||||
data[offset++] = PacketField.SAMPLE_COUNT.getCode();
|
||||
data[offset++] = (byte)0xA0;
|
||||
data[offset++] = (byte)0x02;
|
||||
data[offset++] = (byte)0x00;
|
||||
data[offset] = (byte)0x00;
|
||||
|
||||
return data;
|
||||
}
|
||||
else
|
||||
{
|
||||
int length = mAudioFrame.length + 2;
|
||||
byte[] data = createMessage(length, getType());
|
||||
|
||||
int offset = CHANNEL_DATA_IDENTIFIER_INDEX;
|
||||
data[offset++] = PacketField.CHANNEL_DATA_HARD_SYMBOL.getCode();
|
||||
data[offset++] = (byte)(0xFF & (mAudioFrame.length * 8));
|
||||
System.arraycopy(mAudioFrame, 0, data, offset, mAudioFrame.length);
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
||||
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||
|
||||
public class EncodeSpeechRequest extends AmbeRequest
|
||||
{
|
||||
private static final int CHANNEL_IDENTIFIER_INDEX = 4;
|
||||
private static final int SPEECH_DATA_IDENTIFIER_INDEX = 5;
|
||||
private static final int SAMPLE_COUNT_INDEX = 6;
|
||||
private static final int SPEECH_DATA_START_INDEX = 7;
|
||||
|
||||
private short[] mSamples;
|
||||
|
||||
public EncodeSpeechRequest(short[] samples)
|
||||
{
|
||||
mSamples = samples;
|
||||
}
|
||||
|
||||
public EncodeSpeechRequest(float[] samples)
|
||||
{
|
||||
mSamples = new short[samples.length];
|
||||
|
||||
for(int x = 0; x < samples.length; x++)
|
||||
{
|
||||
if(samples[x] > 1.0f)
|
||||
{
|
||||
mSamples[x] = Short.MAX_VALUE;
|
||||
}
|
||||
else if(samples[x] < -1.0f)
|
||||
{
|
||||
mSamples[x] = Short.MIN_VALUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
mSamples[x] = (short)(samples[x] * Short.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketField getType()
|
||||
{
|
||||
return PacketField.PACKET_TYPE_DECODE_SPEECH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getData()
|
||||
{
|
||||
int length = (mSamples.length * 2) + 3;
|
||||
byte[] data = createMessage(length, getType());
|
||||
|
||||
data[CHANNEL_IDENTIFIER_INDEX] = PacketField.PKT_CHANNEL_0.getCode();
|
||||
data[SPEECH_DATA_IDENTIFIER_INDEX] = (byte)0x00;
|
||||
data[SAMPLE_COUNT_INDEX] = (byte)(0xFF & mSamples.length);
|
||||
|
||||
int pointer = SPEECH_DATA_START_INDEX;
|
||||
|
||||
for(short sample: mSamples)
|
||||
{
|
||||
data[pointer++] = (byte)((sample >> 8 & 0xFF));
|
||||
data[pointer++] = (byte)(sample & 0xFF);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
||||
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||
|
||||
/**
|
||||
* Get configuration request packet
|
||||
*/
|
||||
public class GetConfigRequest extends AmbeRequest
|
||||
{
|
||||
@Override
|
||||
public PacketField getType()
|
||||
{
|
||||
return PacketField.PKT_GET_CONFIG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getData()
|
||||
{
|
||||
return createMessage(1, getType());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
||||
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.InitializeOption;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||
|
||||
/**
|
||||
* Initialize CODEC request packet
|
||||
*/
|
||||
public class InitializeCodecRequest extends AmbeRequest
|
||||
{
|
||||
private static final int INITIALIZE_OPTION_INDEX = 5;
|
||||
private InitializeOption mInitializeOption;
|
||||
|
||||
public InitializeCodecRequest(InitializeOption option)
|
||||
{
|
||||
mInitializeOption = option;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketField getType()
|
||||
{
|
||||
return PacketField.PKT_INIT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getData()
|
||||
{
|
||||
byte[] data = createMessage(2, getType());
|
||||
data[INITIALIZE_OPTION_INDEX] = mInitializeOption.getCode();
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
||||
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||
|
||||
/**
|
||||
* Product ID request packet
|
||||
*/
|
||||
public class ProductIdRequest extends AmbeRequest
|
||||
{
|
||||
@Override
|
||||
public PacketField getType()
|
||||
{
|
||||
return PacketField.PKT_PRODUCT_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getData()
|
||||
{
|
||||
return createMessage(1, getType());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
||||
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||
|
||||
/**
|
||||
* Reset request packet
|
||||
*/
|
||||
public class ResetRequest extends AmbeRequest
|
||||
{
|
||||
@Override
|
||||
public PacketField getType()
|
||||
{
|
||||
return PacketField.PKT_RESET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getData()
|
||||
{
|
||||
return createMessage(1, getType());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
||||
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||
|
||||
/**
|
||||
* Set channel format request packet
|
||||
*/
|
||||
public class SetChannelFormatRequest extends AmbeRequest
|
||||
{
|
||||
private static final int FORMAT_INDEX = 5;
|
||||
private static final byte FORMAT = (byte)0x00;
|
||||
|
||||
@Override
|
||||
public PacketField getType()
|
||||
{
|
||||
return PacketField.PKT_CHANNEL_FORMAT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getData()
|
||||
{
|
||||
byte[] data = createMessage(2, getType());
|
||||
data[FORMAT_INDEX] = FORMAT;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
||||
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||
|
||||
/**
|
||||
* Set channel request packet
|
||||
*/
|
||||
public class SetChannelRequest extends AmbeRequest
|
||||
{
|
||||
@Override
|
||||
public PacketField getType()
|
||||
{
|
||||
return PacketField.PKT_CHANNEL_0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getData()
|
||||
{
|
||||
return createMessage(1, getType());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
||||
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||
|
||||
/**
|
||||
* Packet mode (versus CODEC mode) request packet
|
||||
*/
|
||||
public class SetPacketModeRequest extends AmbeRequest
|
||||
{
|
||||
@Override
|
||||
public PacketField getType()
|
||||
{
|
||||
return PacketField.PKT_CODEC_STOP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getData()
|
||||
{
|
||||
return createMessage(1, getType());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
||||
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||
|
||||
/**
|
||||
* Set speech format request packet
|
||||
*/
|
||||
public class SetSpeechFormatRequest extends AmbeRequest
|
||||
{
|
||||
private static final int FORMAT_INDEX = 5;
|
||||
private static final byte FORMAT = (byte)0x00;
|
||||
|
||||
@Override
|
||||
public PacketField getType()
|
||||
{
|
||||
return PacketField.PKT_SPEECH_FORMAT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getData()
|
||||
{
|
||||
byte[] data = createMessage(2, getType());
|
||||
data[FORMAT_INDEX] = FORMAT;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
||||
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||
|
||||
/**
|
||||
* Set Vocoder parameters request packet
|
||||
*/
|
||||
public class SetVocoderParametersRequest extends AmbeRequest
|
||||
{
|
||||
private int mWord0;
|
||||
private int mWord1;
|
||||
private int mWord2;
|
||||
private int mWord3;
|
||||
private int mWord4;
|
||||
private int mWord5;
|
||||
|
||||
public SetVocoderParametersRequest(int word0, int word1, int word2, int word3, int word4, int word5)
|
||||
{
|
||||
mWord0 = word0;
|
||||
mWord1 = word1;
|
||||
mWord2 = word2;
|
||||
mWord3 = word3;
|
||||
mWord4 = word4;
|
||||
mWord5 = word5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketField getType()
|
||||
{
|
||||
return PacketField.PKT_RATE_PARAMETER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getData()
|
||||
{
|
||||
byte[] data = createMessage(14, getType());
|
||||
data[PAYLOAD_START_INDEX + 1] = (byte)(mWord0 >> 8 & 0xFF);
|
||||
data[PAYLOAD_START_INDEX + 2] = (byte)(mWord0 & 0xFF);
|
||||
data[PAYLOAD_START_INDEX + 3] = (byte)(mWord1 >> 8 & 0xFF);
|
||||
data[PAYLOAD_START_INDEX + 4] = (byte)(mWord1 & 0xFF);
|
||||
data[PAYLOAD_START_INDEX + 5] = (byte)(mWord2 >> 8 & 0xFF);
|
||||
data[PAYLOAD_START_INDEX + 6] = (byte)(mWord2 & 0xFF);
|
||||
data[PAYLOAD_START_INDEX + 7] = (byte)(mWord3 >> 8 & 0xFF);
|
||||
data[PAYLOAD_START_INDEX + 8] = (byte)(mWord3 & 0xFF);
|
||||
data[PAYLOAD_START_INDEX + 9] = (byte)(mWord4 >> 8 & 0xFF);
|
||||
data[PAYLOAD_START_INDEX + 10] = (byte)(mWord4 & 0xFF);
|
||||
data[PAYLOAD_START_INDEX + 11] = (byte)(mWord5 >> 8 & 0xFF);
|
||||
data[PAYLOAD_START_INDEX + 12] = (byte)(mWord5 & 0xFF);
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
||||
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.VocoderRate;
|
||||
|
||||
/**
|
||||
* Set Vocoder Rate request packet
|
||||
*/
|
||||
public class SetVocoderRequest extends AmbeRequest
|
||||
{
|
||||
private VocoderRate mVocoderRate;
|
||||
|
||||
public SetVocoderRequest(VocoderRate rate)
|
||||
{
|
||||
mVocoderRate = rate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketField getType()
|
||||
{
|
||||
return PacketField.PKT_RATE_TABLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getData()
|
||||
{
|
||||
byte[] data = createMessage(2, getType());
|
||||
data[PAYLOAD_START_INDEX + 1] = mVocoderRate.getCode();
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
||||
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||
|
||||
/**
|
||||
* Version string request packet
|
||||
*/
|
||||
public class VersionRequest extends AmbeRequest
|
||||
{
|
||||
@Override
|
||||
public PacketField getType()
|
||||
{
|
||||
return PacketField.PKT_VERSION_STRING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getData()
|
||||
{
|
||||
return createMessage(1, getType());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
||||
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.AmbeMessage;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* AMBE-3000R Response Packet
|
||||
*/
|
||||
public abstract class AmbeResponse extends AmbeMessage
|
||||
{
|
||||
protected static final int PAYLOAD_START_INDEX = 5;
|
||||
private byte[] mMessage;
|
||||
|
||||
protected AmbeResponse(byte[] message)
|
||||
{
|
||||
mMessage = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Control packet type
|
||||
*/
|
||||
public abstract PacketField getType();
|
||||
|
||||
/**
|
||||
* Received message bytes
|
||||
*/
|
||||
protected byte[] getMessage()
|
||||
{
|
||||
return mMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Payload of the packet (does not include the packet header)
|
||||
*/
|
||||
protected byte[] getPayload()
|
||||
{
|
||||
return Arrays.copyOfRange(getMessage(), PAYLOAD_START_INDEX, getMessage().length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* 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/>
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
||||
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||
import io.github.dsheirer.sample.ConversionUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Decode speech response
|
||||
*/
|
||||
public class DecodeSpeechResponse extends AmbeResponse
|
||||
{
|
||||
public DecodeSpeechResponse(byte[] message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketField getType()
|
||||
{
|
||||
return PacketField.PACKET_TYPE_DECODE_SPEECH;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Payload of the packet (does not include the packet header)
|
||||
*/
|
||||
protected byte[] getPayload()
|
||||
{
|
||||
return Arrays.copyOfRange(getMessage(), PAYLOAD_START_INDEX + 1, getMessage().length);
|
||||
}
|
||||
|
||||
public float[] getSamples()
|
||||
{
|
||||
return ConversionUtils.convertFromSigned16BitSamples(getPayload());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "DECODED SPEECH: SAMPLE COUNT:" + getSamples().length + " " + toHex(getPayload());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
||||
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Encode speech response
|
||||
*/
|
||||
public class EncodeSpeechResponse extends AmbeResponse
|
||||
{
|
||||
public EncodeSpeechResponse(byte[] message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketField getType()
|
||||
{
|
||||
return PacketField.PACKET_TYPE_ENCODE_SPEECH;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Payload of the packet (does not include the packet header)
|
||||
*/
|
||||
protected byte[] getPayload()
|
||||
{
|
||||
return Arrays.copyOfRange(getMessage(), PAYLOAD_START_INDEX + 1, getMessage().length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "ENCODED SPEECH: " + toHex(getPayload()) + " " + Arrays.toString(getMessage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
||||
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||
|
||||
/**
|
||||
* Get configuration response
|
||||
*/
|
||||
public class GetConfigResponse extends AmbeResponse
|
||||
{
|
||||
public GetConfigResponse(byte[] message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketField getType()
|
||||
{
|
||||
return PacketField.PKT_GET_CONFIG;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "CONFIGURATION: " + toHex(getPayload());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
||||
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||
|
||||
/**
|
||||
* Initialize CODEC response
|
||||
*/
|
||||
public class InitializeCodecResponse extends AmbeResponse
|
||||
{
|
||||
public InitializeCodecResponse(byte[] message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketField getType()
|
||||
{
|
||||
return PacketField.PKT_INIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Success / fail
|
||||
*/
|
||||
public boolean isSuccessful()
|
||||
{
|
||||
byte[] payload = getPayload();
|
||||
|
||||
return payload != null && payload.length ==1 && payload[0] == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "INITIALIZE CODEC " + (isSuccessful() ? "SUCCESSFUL" : "**FAILED**");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
||||
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||
|
||||
/**
|
||||
* Product ID Response
|
||||
*/
|
||||
public class ProductIdResponse extends AmbeResponse
|
||||
{
|
||||
public ProductIdResponse(byte[] message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketField getType()
|
||||
{
|
||||
return PacketField.PKT_PRODUCT_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Product ID string
|
||||
*/
|
||||
public String getProductId()
|
||||
{
|
||||
return new String(getPayload()).trim();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "PRODUCT ID:" + getProductId();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
||||
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Ready response
|
||||
*/
|
||||
public class ReadyResponse extends AmbeResponse
|
||||
{
|
||||
public ReadyResponse(byte[] message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketField getType()
|
||||
{
|
||||
return PacketField.PKT_READY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "READY" + Arrays.toString(getMessage());
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user