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

|
||||
|
||||
# sdrtrunk
|
||||
A cross-platform java application for decoding, monitoring, recording and streaming trunked mobile and related radio protocols using Software Defined Radios (SDR).
|
||||
|
||||
@@ -33,8 +35,12 @@ Once unzipped, open a command prompt to where you unzipped the release. Change
|
||||
* **Windows** sdr-trunk.bat
|
||||
* **Linux/OSX** ./sdr-trunk
|
||||
|
||||
## Optional - P25 Audio
|
||||
If you're using sdrtrunk with a P25 trunked radio system, the [JMBE](https://github.com/DSheirer/sdrtrunk/wiki/JMBE) wiki page contains instructions for downloading the JMBE audio library source code and compiling the JMBE library. Once you have compiled the library, launch the sdrtrunk application. From the menu bar, choose **View >> Preferences**. In the **JMBE Audio Codec** section, update the path to where your compiled JMBE library is located. Any channels that are started after you set the path will be able to produce P25 audio.
|
||||
## Optional - P25/DMR Audio
|
||||
If you're using sdrtrunk with a P25 or DMR radio system, you'll need to compile and include the [JMBE](https://github.com/DSheirer/sdrtrunk/wiki/JMBE) digital audio library.
|
||||
|
||||
In Version 0.5.0 and higher, the SDRTrunk application has support for building the JMBE library. On the menu, choose **View >> User Preferences** and then click the **Decoder >> JMBE Audio Library** tab.
|
||||
|
||||
For versions prior to Version 0.5.0, 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)
|
||||
@@ -46,7 +52,15 @@ If you're using sdrtrunk with a P25 trunked radio system, the [JMBE](https://git
|
||||
If you're interested in modifying and/or compiling the source code, please follow these instructions to use gradle to compile the code.
|
||||
|
||||
## Build the project
|
||||
sdrtrunk uses the gradle build system. This requires OpenJDK 11 or higher installed on your local compuber. Use the gradle wrapper to build the source code:
|
||||
sdrtrunk uses the gradle build system. This requires **OpenJDK 17** or higher installed on your local computer.
|
||||
|
||||
### OpenJDK
|
||||
There are many vendors producing OpenJDK distributions. A few that I use are:
|
||||
* [Azul Zulu OpenJDK](https://www.azul.com/downloads/?package=jdk)
|
||||
* [Bellsoft Liberica OpenJDK](https://bell-sw.com/pages/downloads/#/java-17-lts%20/%20current)
|
||||
* [SDKMAN](https://sdkman.io/) - Linux Only
|
||||
|
||||
Use the gradle wrapper to build the source code:
|
||||
|
||||
### Linux
|
||||
```
|
||||
@@ -59,6 +73,18 @@ gradlew.bat clean build
|
||||
|
||||
The **/build/distributions** folder will contain the zip file of the compiled program. Unzip it and launch the program from the scripts in the **/bin** directory.
|
||||
|
||||
## Run the Application (from Gradle)
|
||||
|
||||
### Linux
|
||||
```
|
||||
./gradlew clean run
|
||||
```
|
||||
### Windows
|
||||
```
|
||||
gradlew.bat clean run
|
||||
```
|
||||
|
||||
|
||||
## Development
|
||||
All dependencies/versions are controlled from build.gradle.
|
||||
To change the new release version tag of artifact - change property:
|
||||
|
||||
281
build.gradle
281
build.gradle
@@ -1,117 +1,108 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
plugins {
|
||||
id 'application'
|
||||
id 'java'
|
||||
id 'idea'
|
||||
id 'org.openjfx.javafxplugin' version '0.0.8'
|
||||
id 'org.beryx.runtime' version '1.11.4'
|
||||
id 'org.beryx.runtime' version '1.12.7'
|
||||
}
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
|
||||
version = '0.5.0-alpha4'
|
||||
sourceCompatibility = '13'
|
||||
version = '0.5.0-beta6'
|
||||
sourceCompatibility = '17'
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs 'src/main'
|
||||
test.java.srcDirs 'src/test'
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation 'org.assertj:assertj-core:3.8.0'
|
||||
|
||||
// JUnit Tests
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'org.assertj:assertj-core:3.23.1'
|
||||
testImplementation('org.junit.jupiter:junit-jupiter-api:5.9.0')
|
||||
testImplementation 'org.mockito:mockito-core:4.8.0'
|
||||
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
|
||||
|
||||
//Jitpack imports
|
||||
implementation 'com.github.nwaldispuehl:java-lame:v3.98.4'
|
||||
implementation 'com.github.dnault:libresample4j:master'
|
||||
implementation 'com.github.DSheirer.jmbe:jmbe-api:1.0.0'
|
||||
implementation 'com.github.nwaldispuehl:java-lame:v3.98.4'
|
||||
|
||||
//mavenCentral/jcenter imports
|
||||
implementation 'ch.qos.logback:logback-core:1.2.3'
|
||||
implementation 'ch.qos.logback:logback-classic:1.2.3'
|
||||
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.8'
|
||||
implementation 'com.fazecast:jSerialComm:2.5.0'
|
||||
implementation 'ch.qos.logback:logback-classic:1.4.4'
|
||||
implementation 'ch.qos.logback:logback-core:1.4.4'
|
||||
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.14.0'
|
||||
implementation 'com.fazecast:jSerialComm:2.9.2'
|
||||
implementation 'com.github.jiconfont:jiconfont-font_awesome:4.7.0.1'
|
||||
implementation 'com.github.jiconfont:jiconfont-javafx:1.0.0'
|
||||
implementation 'com.github.jiconfont:jiconfont-swing:1.0.1'
|
||||
implementation 'com.github.wendykierp:JTransforms:3.1'
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
implementation 'com.google.guava:guava:27.0.1-jre'
|
||||
implementation 'com.google.code.gson:gson:2.10'
|
||||
implementation 'com.google.guava:guava:31.1-jre'
|
||||
implementation 'com.jidesoft:jide-oss:3.6.18'
|
||||
implementation 'com.miglayout:miglayout-swing:5.2'
|
||||
implementation 'com.miglayout:miglayout-swing:11.0'
|
||||
implementation 'com.mpatric:mp3agic:0.9.1'
|
||||
implementation 'commons-io:commons-io:2.11.0'
|
||||
implementation 'eu.hansolo:charts:1.0.5'
|
||||
implementation 'io.github.dsheirer:radio-reference-api:15.1.8'
|
||||
implementation 'javax.usb:usb-api:1.0.2'
|
||||
implementation 'net.coderazzi:tablefilter-swing:5.4.0'
|
||||
implementation 'org.apache.commons:commons-compress:1.20'
|
||||
implementation 'commons-io:commons-io:2.7'
|
||||
implementation 'org.apache.commons:commons-lang3:3.8.1'
|
||||
implementation 'net.coderazzi:tablefilter-swing:5.5.4'
|
||||
implementation 'org.apache.commons:commons-compress:1.21'
|
||||
implementation 'org.apache.commons:commons-lang3:3.12.0'
|
||||
implementation 'org.apache.commons:commons-math3:3.6.1'
|
||||
implementation 'org.apache.mina:mina-core:2.1.3'
|
||||
implementation 'org.apache.mina:mina-http:2.1.3'
|
||||
implementation 'org.controlsfx:controlsfx:11.0.2'
|
||||
implementation 'org.rauschig:jarchivelib:1.0.0'
|
||||
|
||||
implementation 'org.slf4j:slf4j-api:1.7.25'
|
||||
implementation 'org.apache.commons:commons-csv:1.9.0'
|
||||
implementation 'org.apache.mina:mina-core:2.2.1'
|
||||
implementation 'org.apache.mina:mina-http:2.2.1'
|
||||
implementation 'org.controlsfx:controlsfx:11.1.2'
|
||||
implementation 'org.rauschig:jarchivelib:1.2.0'
|
||||
implementation 'org.slf4j:slf4j-api:2.0.3'
|
||||
implementation 'org.usb4java:libusb4java:1.3.0'
|
||||
implementation 'org.usb4java:usb4java:1.3.0'
|
||||
implementation 'org.usb4java:usb4java-javax:1.3.0'
|
||||
implementation 'io.github.dsheirer:libusb4java-darwin-aarch64:1.3.1' //usb4java native lib for OSX M1 cpu
|
||||
implementation 'pl.edu.icm:JLargeArrays:1.6'
|
||||
}
|
||||
|
||||
//These must remain as compile options to ensure they are included in the build to produce multi-OS images
|
||||
compile "org.openjfx:javafx-base:15:win"
|
||||
compile "org.openjfx:javafx-base:15:linux"
|
||||
compile "org.openjfx:javafx-base:15:mac"
|
||||
compile "org.openjfx:javafx-controls:15:win"
|
||||
compile "org.openjfx:javafx-controls:15:linux"
|
||||
compile "org.openjfx:javafx-controls:15:mac"
|
||||
compile "org.openjfx:javafx-graphics:15:win"
|
||||
compile "org.openjfx:javafx-graphics:15:linux"
|
||||
compile "org.openjfx:javafx-graphics:15:mac"
|
||||
compile "org.openjfx:javafx-swing:15:win"
|
||||
compile "org.openjfx:javafx-swing:15:linux"
|
||||
compile "org.openjfx:javafx-swing:15:mac"
|
||||
/**
|
||||
* This is needed for the JDK17 vector API ... until it moves out of incubation
|
||||
*/
|
||||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs.add("--add-modules=jdk.incubator.vector") //Needed while Panama Vector API remains in incubator
|
||||
}
|
||||
|
||||
application {
|
||||
mainClassName = "io.github.dsheirer.gui.SDRTrunk"
|
||||
}
|
||||
|
||||
idea {
|
||||
module {
|
||||
downloadJavadoc = true
|
||||
downloadSources = true
|
||||
}
|
||||
}
|
||||
|
||||
javafx {
|
||||
version = "15"
|
||||
modules = ['javafx.base', 'javafx.controls', 'javafx.graphics', 'javafx.swing']
|
||||
applicationDefaultJvmArgs =
|
||||
['--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED', //Needed for controls-fx.jar
|
||||
'--add-modules=jdk.incubator.vector', //Needed while Panama Vector API remains in incubator
|
||||
'--add-exports=java.desktop/com.sun.java.swing.plaf.windows=ALL-UNNAMED'] //Windows Swing - jide-oss library
|
||||
}
|
||||
|
||||
jar {
|
||||
@@ -127,85 +118,109 @@ jar {
|
||||
}
|
||||
}
|
||||
|
||||
idea {
|
||||
module {
|
||||
downloadJavadoc = true
|
||||
downloadSources = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Runtime target OS and CPU image names. We append version here so that version details are at end of zip filename.
|
||||
*/
|
||||
def jdk_base = '/media/denny/WD250GB/java/bellsoft/'
|
||||
String targetLinuxAarch64 = 'linux-aarch64-v' + version
|
||||
String targetLinuxX86_64 = 'linux-x86_64-v' + version
|
||||
String targetOsxAarch64 = 'osx-aarch64-v' + version
|
||||
String targetOsxX86_64 = 'osx-x86_64-v' + version
|
||||
String targetWindowsX86_64 = 'windows-x86_64-v' + version
|
||||
|
||||
def jdk_linux_arm64 = jdk_base + 'linux-arm64/jdk-15'
|
||||
def jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-15'
|
||||
def jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-15.jdk'
|
||||
def jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-15'
|
||||
/**
|
||||
* Optional pre-downloaded Java Development Kit (JDK) for each target OS and CPU. These locations are hard-coded to the
|
||||
* author's development environment, but can be changed to your environment if you want to use this build pattern.
|
||||
*/
|
||||
String jdk_base = '/home/denny/java_jdks/'
|
||||
String jdk_linux_aarch64 = jdk_base + 'linux-arm64/jdk-19.0.1-full'
|
||||
String jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-19.0.1-full'
|
||||
String jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-19.0.1-full.jdk'
|
||||
String jdk_osx_aarch64 = jdk_base + 'osx-arm64/jdk-19.0.1-full.jdk'
|
||||
String jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-19.0.1-full'
|
||||
|
||||
def file_jdk_linux_arm64 = new File(jdk_linux_arm64)
|
||||
def file_jdk_linux_x86_64 = new File(jdk_linux_x86_64)
|
||||
def file_jdk_osx_x86_64 = new File(jdk_osx_x86_64)
|
||||
def file_jdk_windows_x86_64 = new File(jdk_windows_x86_64)
|
||||
boolean hasTargetJdks = file(jdk_linux_x86_64).exists() || file(jdk_linux_aarch64).exists() ||
|
||||
file(jdk_osx_x86_64).exists() || file(jdk_osx_aarch64).exists() || file(jdk_windows_x86_64).exists()
|
||||
|
||||
def hasTargetJdk = file_jdk_linux_x86_64.exists() ||
|
||||
file_jdk_linux_arm64 ||
|
||||
file_jdk_osx_x86_64.exists() ||
|
||||
file_jdk_windows_x86_64.exists()
|
||||
/**
|
||||
* Auto-download target JDKs. Defaults to false, or the user can override via the gradle command line by
|
||||
* including this option: '-PdownloadJdks=true'
|
||||
*/
|
||||
boolean downloadJdks = project.hasProperty('downloadJdks') ? project.getProperty('downloadJdks') : false
|
||||
|
||||
/**
|
||||
* Beryx Runtime plugin for creating OS and CPU targeted build(s) of the software.
|
||||
*
|
||||
* Option 1: Create a single target build using the locally installed JDK, or optionally create target builds for
|
||||
* all OS and CPU targets when pre-downloaded target JDKs are available (see 'jdk_*' properties above).
|
||||
*
|
||||
* >: gradle runtimeZip
|
||||
*
|
||||
* Option 2: Create all OS and CPU target builds by automatically downloading all JDKs needed to build the target images
|
||||
*
|
||||
* >: gradle runtimeZip -PdownloadJdks=true
|
||||
*/
|
||||
runtime {
|
||||
if(file_jdk_linux_arm64.exists())
|
||||
{
|
||||
targetPlatform('linux-arm64-v' + version, jdk_linux_arm64)
|
||||
//Build by automatically downloading JDKs for the OS and CPU targets
|
||||
if(downloadJdks) {
|
||||
println("Building all runtime images")
|
||||
println("The [ > jre: ] build phase downloads several target JDKs and this may take a while ... please wait")
|
||||
targetPlatform(targetLinuxAarch64) {
|
||||
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-linux-aarch64-full.tar.gz")
|
||||
}
|
||||
targetPlatform(targetLinuxX86_64) {
|
||||
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-linux-amd64-full.tar.gz")
|
||||
}
|
||||
targetPlatform(targetOsxAarch64) {
|
||||
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-macos-aarch64-full.tar.gz")
|
||||
}
|
||||
targetPlatform(targetOsxX86_64) {
|
||||
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-macos-amd64-full.tar.gz")
|
||||
}
|
||||
targetPlatform(targetWindowsX86_64) {
|
||||
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-windows-amd64-full.zip")
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
println("Skipping OS Image - Linux ARM 64-bit JDK was not found at " + jdk_linux_arm64)
|
||||
//Build using pre-downloaded JDKs
|
||||
else if(hasTargetJdks) {
|
||||
println("Building all runtime images using pre-downloaded JDKs")
|
||||
if(file(jdk_linux_aarch64).exists()) {
|
||||
targetPlatform(targetLinuxAarch64, jdk_linux_aarch64)
|
||||
}
|
||||
if(file(jdk_linux_x86_64).exists()) {
|
||||
targetPlatform(targetLinuxX86_64, jdk_linux_x86_64)
|
||||
}
|
||||
if(file(jdk_osx_aarch64).exists()) {
|
||||
targetPlatform(targetOsxAarch64, jdk_osx_aarch64)
|
||||
}
|
||||
if(file(jdk_osx_x86_64).exists()) {
|
||||
targetPlatform(targetOsxX86_64, jdk_osx_x86_64)
|
||||
}
|
||||
if(file(jdk_windows_x86_64).exists()) {
|
||||
targetPlatform(targetWindowsX86_64, jdk_windows_x86_64)
|
||||
}
|
||||
}
|
||||
//Build using locally installed JDK only
|
||||
else {
|
||||
println("Building a single runtime image using locally installed JDK")
|
||||
}
|
||||
|
||||
if(file_jdk_linux_x86_64.exists())
|
||||
{
|
||||
targetPlatform('linux-x86_64-v' + version, jdk_linux_x86_64)
|
||||
}
|
||||
else
|
||||
{
|
||||
println("Skipping OS Image - Linux x86 64-bit JDK was not found at " + jdk_linux_x86_64)
|
||||
}
|
||||
//Additional modules to include with auto-detected modules.
|
||||
modules = ['jdk.crypto.ec'] //jdk.crypto.ec needed for HTTPS connections (broadcastify calls & map tile server)
|
||||
|
||||
if(file_jdk_osx_x86_64.exists())
|
||||
{
|
||||
targetPlatform('osx-x86_64-v' + version, jdk_osx_x86_64)
|
||||
}
|
||||
else
|
||||
{
|
||||
println("Skipping OS Image - OSX x86 64-bit JDK was not found at " + jdk_osx_x86_64);
|
||||
}
|
||||
|
||||
if(file_jdk_windows_x86_64.exists())
|
||||
{
|
||||
targetPlatform('windows-x86_64-v' + version, jdk_windows_x86_64)
|
||||
}
|
||||
else
|
||||
{
|
||||
println("Skipping OS Image - Windows x86 64-bit JDK was not found at " + jdk_windows_x86_64)
|
||||
}
|
||||
//Use auto-detected modules and 'add' any specified modules.
|
||||
additive = true
|
||||
|
||||
//Recommended options for reducing/shrinking runtime target image size
|
||||
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
|
||||
modules = ['java.desktop', 'java.naming', 'jdk.unsupported', 'jdk.unsupported.desktop', 'java.net.http',
|
||||
'java.sql', 'jdk.crypto.ec']
|
||||
imageZip = hasTargetJdk ? file("$buildDir/image/sdr-trunk.zip") : file("$buildDir/image/sdr-trunk-" + version + ".zip")
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy JavaFX OS-specific libraries from each JDK to each platform target directory
|
||||
*/
|
||||
tasks.runtime.doLast
|
||||
{
|
||||
delete(fileTree('build/image/sdr-trunk-linux-arm64-v' + version + '/lib').include { it.name ==~ /javafx.*-(win|mac)\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-linux-x86_64-v' + version + '/lib').include { it.name ==~ /javafx.*-(win|mac)\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-osx-x86_64-v' + version + '/lib').include { it.name ==~ /javafx.*-(win|linux)\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-windows-x86_64-v' + version + '/lib').include { it.name ==~ /javafx.*-(linux|mac)\.jar/ })
|
||||
|
||||
//JavaFX web and media modules are transitive dependencies of ControlsFX but are not required for this application
|
||||
delete(fileTree('build/image/sdr-trunk-linux-arm64-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-linux-x86_64-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-osx-x86_64-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-windows-x86_64-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
|
||||
//Runtime images have auto appended version in image zip name. Override to include version in single target image.
|
||||
imageZip = (hasTargetJdks | downloadJdks) ? file("$buildDir/image/sdr-trunk.zip") :
|
||||
file("$buildDir/image/sdr-trunk-" + version + ".zip")
|
||||
}
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
21
gradle/wrapper/gradle-wrapper.properties
vendored
21
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,24 @@
|
||||
#
|
||||
# ******************************************************************************
|
||||
# Copyright (C) 2014-2022 Dennis Sheirer
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
# *****************************************************************************
|
||||
#
|
||||
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-rc-3-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
290
gradlew
vendored
290
gradlew
vendored
@@ -1,94 +1,132 @@
|
||||
#!/usr/bin/env sh
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
# ******************************************************************************
|
||||
# Copyright (C) 2014-2022 Dennis Sheirer
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
# *****************************************************************************
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
APP_BASE_NAME=${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" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
@@ -97,7 +135,7 @@ Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
@@ -105,79 +143,95 @@ location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
25
gradlew.bat
vendored
25
gradlew.bat
vendored
@@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@@ -37,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
@@ -51,7 +54,7 @@ goto fail
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
@@ -61,28 +64,14 @@ echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.alias;
|
||||
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.alias;
|
||||
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.alias;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,7 +14,7 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.alias.action;
|
||||
@@ -26,8 +25,8 @@ import io.github.dsheirer.alias.Alias;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
|
||||
import javax.swing.JOptionPane;
|
||||
import java.awt.EventQueue;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -92,7 +91,7 @@ public abstract class RecurringAction extends AliasAction
|
||||
*/
|
||||
private void performThreadedAction(final Alias alias, final IMessage message)
|
||||
{
|
||||
ThreadPool.SCHEDULED.schedule(() -> performAction(alias, message), 0, TimeUnit.SECONDS);
|
||||
ThreadPool.CACHED.execute(() -> performAction(alias, message));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,7 +14,7 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.alias.action.beep;
|
||||
@@ -72,7 +71,7 @@ public class BeepAction extends RecurringAction
|
||||
@Override
|
||||
public void performAction(Alias alias, IMessage message )
|
||||
{
|
||||
DataLine.Info info = new DataLine.Info(Clip.class, AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO);
|
||||
DataLine.Info info = new DataLine.Info(Clip.class, AudioFormats.PCM_SIGNED_8000_HZ_16_BIT_MONO);
|
||||
|
||||
if(!AudioSystem.isLineSupported(info))
|
||||
{
|
||||
@@ -83,7 +82,7 @@ public class BeepAction extends RecurringAction
|
||||
try
|
||||
{
|
||||
Clip clip = (Clip)AudioSystem.getLine(info);
|
||||
clip.open(AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO, mToneBytes, 0, mToneBytes.length);
|
||||
clip.open(AudioFormats.PCM_SIGNED_8000_HZ_16_BIT_MONO, mToneBytes, 0, mToneBytes.length);
|
||||
clip.start();
|
||||
}
|
||||
catch(Exception e)
|
||||
|
||||
@@ -46,6 +46,8 @@ public enum TalkgroupFormat
|
||||
"MDC-1200 valid value range is 1-65,535"),
|
||||
MPT1327("###-####", 1, 0x7FFFFF, "000-0001 to 127-8192",
|
||||
"<html>MPT-1327 valid ranges are 0-127(prefix)<br>and 1-8192(ident) (ie. 000-0001 to 127-8192)"),
|
||||
NBFM("*****", 1, 0xFFFF, "1 to 65,535",
|
||||
"NBFM valid value range is 1-65,535"),
|
||||
PASSPORT("*****", 1, 0xFFFF, "1 to 65,535",
|
||||
"Passport valid value range is 1-65,535"),
|
||||
UNKNOWN("********", 1, 0xFFFFFF, "1 to 16,777,215",
|
||||
@@ -131,6 +133,8 @@ public enum TalkgroupFormat
|
||||
return MDC1200;
|
||||
case MPT1327:
|
||||
return MPT1327;
|
||||
case NBFM:
|
||||
return NBFM;
|
||||
case PASSPORT:
|
||||
return PASSPORT;
|
||||
default:
|
||||
|
||||
@@ -28,6 +28,7 @@ import io.github.dsheirer.preference.identifier.talkgroup.FleetsyncTalkgroupForm
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.LTRTalkgroupFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.MDC1200TalkgroupFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.MPT1327TalkgroupFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.NBFMTalkgroupFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.PassportTalkgroupFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.UnknownTalkgroupFormatter;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
@@ -56,6 +57,7 @@ public class TalkgroupFormatter
|
||||
mFormatterMap.put(Protocol.LTR_NET, ltr);
|
||||
mFormatterMap.put(Protocol.MDC1200, new MDC1200TalkgroupFormatter());
|
||||
mFormatterMap.put(Protocol.MPT1327, new MPT1327TalkgroupFormatter());
|
||||
mFormatterMap.put(Protocol.NBFM, new NBFMTalkgroupFormatter());
|
||||
mFormatterMap.put(Protocol.PASSPORT, new PassportTalkgroupFormatter());
|
||||
mFormatterMap.put(Protocol.UNKNOWN, new UnknownTalkgroupFormatter());
|
||||
}
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
@@ -5,81 +24,107 @@ import javax.sound.sampled.DataLine;
|
||||
import javax.sound.sampled.Line;
|
||||
import javax.sound.sampled.SourceDataLine;
|
||||
|
||||
/**
|
||||
* Audio formats
|
||||
*/
|
||||
public class AudioFormats
|
||||
{
|
||||
public static final AudioFormat.Encoding IMBE_ENCODING =
|
||||
new AudioFormat.Encoding( "IMBE" );
|
||||
|
||||
public static final boolean LITTLE_ENDIAN = false;
|
||||
public static final boolean BIG_ENDIAN = true;
|
||||
|
||||
public static final float IMBE_FRAME_RATE = 50;
|
||||
public static final float IMBE_SAMPLE_RATE = 50;
|
||||
public static final float PCM_8KHZ_RATE = 8000;
|
||||
public static final float PCM_48KHZ_RATE = 48000;
|
||||
|
||||
public static final int IMBE_FRAME_SIZE_BYTES = 18;
|
||||
public static final int IMBE_SAMPLE_SIZE_BITS = 144;
|
||||
|
||||
public static final float PCM_8_KHZ_RATE = 8000;
|
||||
public static final float PCM_16_KHZ_RATE = 16000;
|
||||
public static final float PCM_22050_HZ_RATE = 22050;
|
||||
public static final float PCM_44100_HZ_RATE = 44100;
|
||||
|
||||
public static final int ONE_CHANNEL = 1;
|
||||
public static final int TWO_CHANNELS = 2;
|
||||
public static final int PCM_SAMPLE_SIZE_BITS = 16;
|
||||
public static final int PCM_FRAME_SIZE_BYTES_MONO = 2;
|
||||
public static final int PCM_FRAME_SIZE_BYTES_STEREO = 4;
|
||||
|
||||
public static AudioFormat IMBE_AUDIO_FORMAT =
|
||||
new AudioFormat( IMBE_ENCODING,
|
||||
IMBE_SAMPLE_RATE,
|
||||
IMBE_SAMPLE_SIZE_BITS,
|
||||
ONE_CHANNEL,
|
||||
IMBE_FRAME_SIZE_BYTES,
|
||||
IMBE_FRAME_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_8KHZ_16BITS_MONO =
|
||||
new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_8KHZ_RATE,
|
||||
PCM_SAMPLE_SIZE_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_MONO,
|
||||
PCM_8KHZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
public static final int PCM_SAMPLE_SIZE_16_BITS = 16;
|
||||
public static final int PCM_SAMPLE_SIZE_32_BITS = 32;
|
||||
public static final int PCM_FRAME_SIZE_BYTES_16_BIT_MONO = 2;
|
||||
public static final int PCM_FRAME_SIZE_BYTES_32_BIT_MONO = 4;
|
||||
public static final int PCM_FRAME_SIZE_BYTES_16_BIT_STEREO = 4;
|
||||
|
||||
public static AudioFormat PCM_SIGNED_8KHZ_16BITS_STEREO =
|
||||
new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_8KHZ_RATE,
|
||||
PCM_SAMPLE_SIZE_BITS,
|
||||
TWO_CHANNELS,
|
||||
PCM_FRAME_SIZE_BYTES_STEREO,
|
||||
PCM_8KHZ_RATE,
|
||||
public static AudioFormat PCM_SIGNED_8000_HZ_32_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_8_KHZ_RATE,
|
||||
PCM_SAMPLE_SIZE_32_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_32_BIT_MONO,
|
||||
PCM_8_KHZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_8000_HZ_16_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_8_KHZ_RATE,
|
||||
PCM_SAMPLE_SIZE_16_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_16_BIT_MONO,
|
||||
PCM_8_KHZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_16000_HZ_16_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_16_KHZ_RATE,
|
||||
PCM_SAMPLE_SIZE_16_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_16_BIT_MONO,
|
||||
PCM_16_KHZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_16000_HZ_32_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_16_KHZ_RATE,
|
||||
PCM_SAMPLE_SIZE_32_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_32_BIT_MONO,
|
||||
PCM_16_KHZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_22050_HZ_16_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_22050_HZ_RATE,
|
||||
PCM_SAMPLE_SIZE_16_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_16_BIT_MONO,
|
||||
PCM_22050_HZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_22050_HZ_32_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_22050_HZ_RATE,
|
||||
PCM_SAMPLE_SIZE_32_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_32_BIT_MONO,
|
||||
PCM_22050_HZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_44100_HZ_16_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_44100_HZ_RATE,
|
||||
PCM_SAMPLE_SIZE_16_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_16_BIT_MONO,
|
||||
PCM_44100_HZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_44100_HZ_32_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_44100_HZ_RATE,
|
||||
PCM_SAMPLE_SIZE_32_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_32_BIT_MONO,
|
||||
PCM_44100_HZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_8000_HZ_16BITS_STEREO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_8_KHZ_RATE,
|
||||
PCM_SAMPLE_SIZE_16_BITS,
|
||||
TWO_CHANNELS,
|
||||
PCM_FRAME_SIZE_BYTES_16_BIT_STEREO,
|
||||
PCM_8_KHZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_48KHZ_16BITS_MONO =
|
||||
new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_48KHZ_RATE,
|
||||
PCM_SAMPLE_SIZE_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_MONO,
|
||||
PCM_48KHZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_48KHZ_16BITS_STEREO =
|
||||
new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_48KHZ_RATE,
|
||||
PCM_SAMPLE_SIZE_BITS,
|
||||
TWO_CHANNELS,
|
||||
PCM_FRAME_SIZE_BYTES_STEREO,
|
||||
PCM_48KHZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
/**
|
||||
* Source Data Line Info for a 48 kHz, 16-bits signed PCM one channel
|
||||
*/
|
||||
public static final Line.Info MONO_SOURCE_DATALINE_INFO =
|
||||
new DataLine.Info( SourceDataLine.class, PCM_SIGNED_8KHZ_16BITS_MONO);
|
||||
new DataLine.Info( SourceDataLine.class, PCM_SIGNED_8000_HZ_16_BIT_MONO);
|
||||
|
||||
/**
|
||||
* Source Data Line Info for a 48 kHz, 16-bits signed PCM two channels
|
||||
*/
|
||||
public static final Line.Info STEREO_SOURCE_DATALINE_INFO =
|
||||
new DataLine.Info( SourceDataLine.class, PCM_SIGNED_8KHZ_16BITS_STEREO);
|
||||
new DataLine.Info( SourceDataLine.class, PCM_SIGNED_8000_HZ_16BITS_STEREO);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio;
|
||||
|
||||
@@ -25,13 +22,13 @@ import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.audio.squelch.ISquelchStateListener;
|
||||
import io.github.dsheirer.audio.squelch.SquelchState;
|
||||
import io.github.dsheirer.audio.squelch.SquelchStateEvent;
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
||||
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter2;
|
||||
import io.github.dsheirer.dsp.filter.fir.real.IRealFilter;
|
||||
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import io.github.dsheirer.sample.real.IRealBufferListener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -41,8 +38,8 @@ import org.slf4j.LoggerFactory;
|
||||
*
|
||||
* Incorporates audio squelch state listener to control if audio packets are broadcast or ignored.
|
||||
*/
|
||||
public class AudioModule extends AbstractAudioModule implements ISquelchStateListener, IReusableBufferListener,
|
||||
Listener<ReusableFloatBuffer>
|
||||
public class AudioModule extends AbstractAudioModule implements ISquelchStateListener, IRealBufferListener,
|
||||
Listener<float[]>
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(AudioModule.class);
|
||||
private static float[] sHighPassFilterCoefficients;
|
||||
@@ -73,7 +70,7 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
|
||||
}
|
||||
}
|
||||
|
||||
private RealFIRFilter2 mHighPassFilter = new RealFIRFilter2(sHighPassFilterCoefficients);
|
||||
private IRealFilter mHighPassFilter = FilterFactory.getRealFilter(sHighPassFilterCoefficients);
|
||||
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
|
||||
private SquelchState mSquelchState = SquelchState.SQUELCH;
|
||||
|
||||
@@ -120,22 +117,17 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ReusableFloatBuffer audioBuffer)
|
||||
public void receive(float[] audioBuffer)
|
||||
{
|
||||
if(mSquelchState == SquelchState.UNSQUELCH)
|
||||
{
|
||||
ReusableFloatBuffer highPassFilteredAudio = mHighPassFilter.filter(audioBuffer);
|
||||
addAudio(highPassFilteredAudio.getSamplesCopy());
|
||||
highPassFilteredAudio.decrementUserCount();
|
||||
}
|
||||
else
|
||||
{
|
||||
audioBuffer.decrementUserCount();
|
||||
audioBuffer = mHighPassFilter.filter(audioBuffer);
|
||||
addAudio(audioBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<ReusableFloatBuffer> getReusableBufferListener()
|
||||
public Listener<float[]> getBufferListener()
|
||||
{
|
||||
//Redirect received reusable buffers to the receive(buffer) method
|
||||
return this;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -20,7 +20,6 @@ package io.github.dsheirer.audio;
|
||||
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.AbstractReusableBuffer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -40,7 +39,7 @@ public class AudioSegmentBroadcaster<T extends AudioSegment> extends Broadcaster
|
||||
@Override
|
||||
public void broadcast(T audioSegment)
|
||||
{
|
||||
for(Listener<T> listener : mListeners)
|
||||
for(Listener<T> listener : getListeners())
|
||||
{
|
||||
audioSegment.incrementConsumerCount();
|
||||
listener.receive(audioSegment);
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio;
|
||||
|
||||
import io.github.dsheirer.sample.ConversionUtils;
|
||||
@@ -21,7 +24,10 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.Float;
|
||||
import java.lang.Math;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AudioUtils
|
||||
@@ -76,4 +82,42 @@ public class AudioUtils
|
||||
return stream.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize audio samples
|
||||
*/
|
||||
public static List<float[]> normalize(List<float[]> audioBuffers)
|
||||
{
|
||||
float targetMax = 0.95f; // -0.44dBFS
|
||||
float max = 0.1f; // Initialize at 0.1 (-20dBFS) to limit maximum gain
|
||||
|
||||
for(float[] audioBuffer: audioBuffers)
|
||||
{
|
||||
for(float value: audioBuffer)
|
||||
{
|
||||
float newMax = Math.max(max, Math.abs(value));
|
||||
if(!Float.isNaN(newMax))
|
||||
{
|
||||
max = newMax;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(targetMax == max)
|
||||
{
|
||||
return audioBuffers;
|
||||
}
|
||||
|
||||
float gain = targetMax / max;
|
||||
|
||||
for(float[] audioBuffer: audioBuffers)
|
||||
{
|
||||
for(int i=0;i<audioBuffer.length;i++)
|
||||
{
|
||||
audioBuffer[i] *= gain;
|
||||
}
|
||||
}
|
||||
|
||||
return audioBuffers;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast;
|
||||
|
||||
import io.github.dsheirer.audio.convert.ISilenceGenerator;
|
||||
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
@@ -31,6 +30,7 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
@@ -42,7 +42,7 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AudioStreamingBroadcaster.class);
|
||||
|
||||
public static final int PROCESSOR_RUN_INTERVAL_MS = 1000;
|
||||
private ScheduledFuture mRecordingQueueProcessorFuture;
|
||||
private ScheduledFuture<?> mRecordingQueueProcessorFuture;
|
||||
|
||||
private RecordingQueueProcessor mRecordingQueueProcessor = new RecordingQueueProcessor();
|
||||
private Queue<AudioRecording> mAudioRecordingQueue = new LinkedTransferQueue<>();
|
||||
@@ -52,6 +52,10 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
|
||||
private long mMaximumRecordingAge;
|
||||
private AtomicBoolean mStreaming = new AtomicBoolean();
|
||||
|
||||
protected boolean mInlineActive = false;
|
||||
protected int mInlineInterval;
|
||||
protected int mInlineRemaining = -1;
|
||||
|
||||
/**
|
||||
* AudioBroadcaster for streaming audio recordings to a remote streaming audio server. Audio recordings are
|
||||
* generated by an internal StreamManager that converts an inbound stream of AudioPackets into a recording of the
|
||||
@@ -73,12 +77,13 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
|
||||
* The last audio packet's metadata is automatically attached to the closed audio recording when it is enqueued for
|
||||
* broadcast. That metadata will be updated on the remote server once the audio recording is opened for streaming.
|
||||
*/
|
||||
public AudioStreamingBroadcaster(T broadcastConfiguration)
|
||||
public AudioStreamingBroadcaster(T broadcastConfiguration, InputAudioFormat inputAudioFormat, MP3Setting mp3Setting)
|
||||
{
|
||||
super(broadcastConfiguration);
|
||||
mDelay = getBroadcastConfiguration().getDelay();
|
||||
mMaximumRecordingAge = getBroadcastConfiguration().getMaximumRecordingAge();
|
||||
mSilenceGenerator = BroadcastFactory.getSilenceGenerator(broadcastConfiguration.getBroadcastFormat());
|
||||
mSilenceGenerator = BroadcastFactory.getSilenceGenerator(broadcastConfiguration.getBroadcastFormat(),
|
||||
inputAudioFormat, mp3Setting);
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
@@ -88,7 +93,7 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
|
||||
/**
|
||||
* Broadcast binary audio data frames or sequences.
|
||||
*/
|
||||
protected abstract void broadcastAudio(byte[] audio);
|
||||
protected abstract void broadcastAudio(byte[] audio, IdentifierCollection identifierCollection);
|
||||
|
||||
/**
|
||||
* Protocol-specific metadata updater
|
||||
@@ -204,13 +209,15 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
|
||||
|
||||
super.setBroadcastState(state);
|
||||
|
||||
if(mBroadcastState.get() instanceof BroadcastState && ((BroadcastState)mBroadcastState.get()).isErrorState())
|
||||
if(mBroadcastState.get() != null && mBroadcastState.get().isErrorState())
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
if(!connected())
|
||||
{
|
||||
//Reset inline metadata
|
||||
mInlineRemaining = -1;
|
||||
//Remove all pending audio recordings
|
||||
while(!mAudioRecordingQueue.isEmpty())
|
||||
{
|
||||
@@ -264,9 +271,9 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
|
||||
{
|
||||
private AtomicBoolean mProcessing = new AtomicBoolean();
|
||||
private ByteArrayInputStream mInputStream;
|
||||
private IdentifierCollection mInputIdentifierCollection;
|
||||
private long mFinalSilencePadding = 0;
|
||||
private int mBytesStreamedActual = 0;
|
||||
private int mBytesStreamedRequired = 0;
|
||||
private int mBytesToStreamPerInterval = 0;
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
@@ -277,42 +284,45 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
|
||||
{
|
||||
if(mInputStream == null || mInputStream.available() <= 0)
|
||||
{
|
||||
if(mFinalSilencePadding > 0)
|
||||
{
|
||||
broadcastAudio(mSilenceGenerator.generate(mFinalSilencePadding));
|
||||
mFinalSilencePadding = 0;
|
||||
}
|
||||
|
||||
nextRecording();
|
||||
}
|
||||
|
||||
if(mInputStream != null)
|
||||
{
|
||||
//We need to stream at 13.888 fps (144 byte frame) to achieve 2000 Bps or 16 kbps
|
||||
mBytesStreamedRequired += 2000; //2000 bytes per second for 16 kbps data rate
|
||||
int bytesToStream = mBytesStreamedRequired - mBytesStreamedActual;
|
||||
|
||||
//Trim length to whole-frame intervals (144 byte frame)
|
||||
bytesToStream -= (bytesToStream % 144);
|
||||
|
||||
int length = FastMath.min(bytesToStream, mInputStream.available());
|
||||
int length = FastMath.min(mBytesToStreamPerInterval, mInputStream.available());
|
||||
|
||||
byte[] audio = new byte[length];
|
||||
|
||||
try
|
||||
{
|
||||
mBytesStreamedActual += mInputStream.read(audio);
|
||||
|
||||
broadcastAudio(audio);
|
||||
int read = mInputStream.read(audio);
|
||||
broadcastAudio(audio, mInputIdentifierCollection);
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error reading from in-memory audio recording input stream", ioe);
|
||||
}
|
||||
|
||||
//If this is the final frame fragment then append silence padding to fill the final interval segment
|
||||
if(length < mBytesToStreamPerInterval && mFinalSilencePadding > 0)
|
||||
{
|
||||
List<byte[]> finalSilence = mSilenceGenerator.generate(mFinalSilencePadding);
|
||||
|
||||
for(byte[] silence: finalSilence)
|
||||
{
|
||||
broadcastAudio(silence, null);
|
||||
}
|
||||
|
||||
mFinalSilencePadding = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
broadcastAudio(mSilenceGenerator.generate(PROCESSOR_RUN_INTERVAL_MS));
|
||||
List<byte[]> silenceFrames = mSilenceGenerator.generate(PROCESSOR_RUN_INTERVAL_MS);
|
||||
for(byte[] silence: silenceFrames)
|
||||
{
|
||||
broadcastAudio(silence, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Throwable t)
|
||||
@@ -329,9 +339,6 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
|
||||
*/
|
||||
private void nextRecording()
|
||||
{
|
||||
mBytesStreamedActual = 0;
|
||||
mBytesStreamedRequired = 0;
|
||||
|
||||
boolean metadataUpdateRequired = false;
|
||||
|
||||
if(mInputStream != null)
|
||||
@@ -343,6 +350,7 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
|
||||
}
|
||||
|
||||
mInputStream = null;
|
||||
mInputIdentifierCollection = null;
|
||||
|
||||
//Peek at the next recording but don't remove it from the queue yet, so we can inspect the start time for
|
||||
//age limits and/or delay elapsed
|
||||
@@ -370,9 +378,14 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
|
||||
{
|
||||
byte[] audio = Files.readAllBytes(nextRecording.getPath());
|
||||
|
||||
if(audio != null && audio.length > 0)
|
||||
if(audio.length > 0)
|
||||
{
|
||||
//Calculate how many bytes we'll send with each processor run interval
|
||||
double intervals = (double)nextRecording.getRecordingLength() / (double)PROCESSOR_RUN_INTERVAL_MS;
|
||||
mBytesToStreamPerInterval = (int)((double)audio.length / intervals);
|
||||
|
||||
mInputStream = new ByteArrayInputStream(audio);
|
||||
mInputIdentifierCollection = nextRecording.getIdentifierCollection();
|
||||
|
||||
mFinalSilencePadding = PROCESSOR_RUN_INTERVAL_MS -
|
||||
(nextRecording.getRecordingLength() % PROCESSOR_RUN_INTERVAL_MS);
|
||||
@@ -397,6 +410,7 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
|
||||
"stream recording [" + nextRecording.getPath().toString() + "] - skipping recording - ", ioe);
|
||||
|
||||
mInputStream = null;
|
||||
mInputIdentifierCollection = null;
|
||||
metadataUpdateRequired = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -150,7 +150,7 @@ public class AudioStreamingManager implements Listener<AudioSegment>
|
||||
|
||||
try
|
||||
{
|
||||
AudioSegmentRecorder.record(audioSegment, path, RecordFormat.MP3);
|
||||
AudioSegmentRecorder.record(audioSegment, path, RecordFormat.MP3, mUserPreferences);
|
||||
IdentifierCollection identifierCollectionCopy =
|
||||
new IdentifierCollection(audioSegment.getIdentifierCollection().getIdentifiers());
|
||||
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast;
|
||||
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast;
|
||||
|
||||
@@ -33,10 +30,11 @@ import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1AudioBroadcast
|
||||
import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
|
||||
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2AudioStreamingBroadcaster;
|
||||
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2Configuration;
|
||||
import io.github.dsheirer.audio.convert.IAudioConverter;
|
||||
import io.github.dsheirer.audio.convert.ISilenceGenerator;
|
||||
import io.github.dsheirer.audio.convert.MP3AudioConverter;
|
||||
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||
import io.github.dsheirer.audio.convert.MP3SilenceGenerator;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -44,67 +42,50 @@ public class BroadcastFactory
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(BroadcastFactory.class);
|
||||
|
||||
public static final int MP3_MONO_16_KHZ_BITRATE = 16;
|
||||
public static final boolean MP3_CONSTANT_BITRATE = false;
|
||||
|
||||
/**
|
||||
* Creates an audio streaming broadcaster for the configuration
|
||||
*
|
||||
* @param configuration describing the server and audio types
|
||||
* @return configured broadcaster or null
|
||||
*/
|
||||
public static AbstractAudioBroadcaster getBroadcaster(BroadcastConfiguration configuration, AliasModel aliasModel)
|
||||
public static AbstractAudioBroadcaster getBroadcaster(BroadcastConfiguration configuration, AliasModel aliasModel,
|
||||
UserPreferences userPreferences)
|
||||
{
|
||||
if(configuration != null)
|
||||
{
|
||||
IAudioConverter converter = getAudioConverter(configuration);
|
||||
InputAudioFormat inputAudioFormat = userPreferences.getMP3Preference().getAudioSampleRate();
|
||||
MP3Setting mp3Setting = userPreferences.getMP3Preference().getMP3Setting();
|
||||
|
||||
if(converter != null)
|
||||
switch(configuration.getBroadcastServerType())
|
||||
{
|
||||
switch(configuration.getBroadcastServerType())
|
||||
{
|
||||
case BROADCASTIFY_CALL:
|
||||
return new BroadcastifyCallBroadcaster((BroadcastifyCallConfiguration)configuration, aliasModel);
|
||||
case BROADCASTIFY:
|
||||
return new IcecastTCPAudioBroadcaster((BroadcastifyFeedConfiguration) configuration, aliasModel);
|
||||
case ICECAST_TCP:
|
||||
return new IcecastTCPAudioBroadcaster((IcecastTCPConfiguration) configuration, aliasModel);
|
||||
case ICECAST_HTTP:
|
||||
return new IcecastHTTPAudioBroadcaster((IcecastHTTPConfiguration) configuration, aliasModel);
|
||||
case SHOUTCAST_V1:
|
||||
return new ShoutcastV1AudioBroadcaster((ShoutcastV1Configuration) configuration, aliasModel);
|
||||
case SHOUTCAST_V2:
|
||||
return new ShoutcastV2AudioStreamingBroadcaster((ShoutcastV2Configuration) configuration, aliasModel);
|
||||
case UNKNOWN:
|
||||
default:
|
||||
mLog.info("Unrecognized broadcastAudio configuration: " + configuration.getBroadcastFormat().name());
|
||||
break;
|
||||
}
|
||||
case BROADCASTIFY_CALL:
|
||||
return new BroadcastifyCallBroadcaster((BroadcastifyCallConfiguration)configuration,
|
||||
inputAudioFormat, mp3Setting, aliasModel);
|
||||
case BROADCASTIFY:
|
||||
return new IcecastTCPAudioBroadcaster((BroadcastifyFeedConfiguration) configuration,
|
||||
inputAudioFormat, mp3Setting, aliasModel);
|
||||
case ICECAST_TCP:
|
||||
return new IcecastTCPAudioBroadcaster((IcecastTCPConfiguration) configuration, inputAudioFormat,
|
||||
mp3Setting, aliasModel);
|
||||
case ICECAST_HTTP:
|
||||
return new IcecastHTTPAudioBroadcaster((IcecastHTTPConfiguration) configuration, inputAudioFormat,
|
||||
mp3Setting, aliasModel);
|
||||
case SHOUTCAST_V1:
|
||||
return new ShoutcastV1AudioBroadcaster((ShoutcastV1Configuration) configuration, inputAudioFormat,
|
||||
mp3Setting, aliasModel);
|
||||
case SHOUTCAST_V2:
|
||||
return new ShoutcastV2AudioStreamingBroadcaster((ShoutcastV2Configuration) configuration,
|
||||
inputAudioFormat, mp3Setting, aliasModel);
|
||||
case UNKNOWN:
|
||||
default:
|
||||
mLog.info("Unrecognized broadcastAudio configuration: " + configuration.getBroadcastFormat().name());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an audio convert to convert from 8 kHz PCM audio to the specified format
|
||||
*
|
||||
* @param configuration containing the requested output audio format
|
||||
* @return audio convert or null
|
||||
*/
|
||||
public static IAudioConverter getAudioConverter(BroadcastConfiguration configuration)
|
||||
{
|
||||
switch(configuration.getBroadcastFormat())
|
||||
{
|
||||
case MP3:
|
||||
return new MP3AudioConverter(MP3_MONO_16_KHZ_BITRATE, MP3_CONSTANT_BITRATE);
|
||||
default:
|
||||
mLog.info("Unrecognized broadcastAudio format: " + configuration.getBroadcastFormat().name());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a broadcastAudio configuration for the specified server type and format
|
||||
*
|
||||
@@ -137,12 +118,12 @@ public class BroadcastFactory
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ISilenceGenerator getSilenceGenerator(BroadcastFormat format)
|
||||
public static ISilenceGenerator getSilenceGenerator(BroadcastFormat format, InputAudioFormat inputAudioFormat, MP3Setting mp3Setting)
|
||||
{
|
||||
switch(format)
|
||||
{
|
||||
case MP3:
|
||||
return new MP3SilenceGenerator();
|
||||
return new MP3SilenceGenerator(inputAudioFormat, mp3Setting);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized broadcast format [" + format +
|
||||
"] can't create silence generator");
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast;
|
||||
|
||||
@@ -34,7 +31,7 @@ import javafx.collections.ObservableList;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.*;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.DirectoryStream;
|
||||
@@ -80,6 +77,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
private AliasModel mAliasModel;
|
||||
private Broadcaster<BroadcastEvent> mBroadcastEventBroadcaster = new Broadcaster<>();
|
||||
private BroadcastEventListener mBroadcastEventListener = new BroadcastEventListener();
|
||||
private UserPreferences mUserPreferences;
|
||||
|
||||
/**
|
||||
* Model for managing Broadcast configurations and any associated broadcaster instances.
|
||||
@@ -88,6 +86,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
{
|
||||
mAliasModel = aliasModel;
|
||||
mIconModel = iconModel;
|
||||
mUserPreferences = userPreferences;
|
||||
|
||||
//Monitor to remove temporary recording files that have been streamed by all audio broadcasters
|
||||
ThreadPool.SCHEDULED.scheduleAtFixedRate(new RecordingDeletionMonitor(), 15l, 15l, TimeUnit.SECONDS);
|
||||
@@ -346,7 +345,8 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
deleteBroadcaster(configuredBroadcast);
|
||||
}
|
||||
|
||||
AbstractAudioBroadcaster audioBroadcaster = BroadcastFactory.getBroadcaster(broadcastConfiguration, mAliasModel);
|
||||
AbstractAudioBroadcaster audioBroadcaster = BroadcastFactory.getBroadcaster(broadcastConfiguration,
|
||||
mAliasModel, mUserPreferences);
|
||||
|
||||
if(audioBroadcaster != null)
|
||||
{
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.broadcast;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -25,6 +25,8 @@ import io.github.dsheirer.audio.broadcast.AbstractAudioBroadcaster;
|
||||
import io.github.dsheirer.audio.broadcast.AudioRecording;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastEvent;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastState;
|
||||
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||
import io.github.dsheirer.gui.playlist.radioreference.RadioReferenceDecoder;
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
@@ -66,6 +68,7 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
|
||||
private static final String MULTIPART_TYPE = "multipart";
|
||||
private static final String DEFAULT_SUBTYPE = "form-data";
|
||||
private static final String MULTIPART_FORM_DATA = MULTIPART_TYPE + "/" + DEFAULT_SUBTYPE;
|
||||
private ScheduledFuture<?> mBroadcastifyTestFuture;
|
||||
private Queue<AudioRecording> mAudioRecordingQueue = new LinkedTransferQueue<>();
|
||||
private ScheduledFuture<?> mAudioRecordingProcessorFuture;
|
||||
private HttpClient mHttpClient = HttpClient.newBuilder()
|
||||
@@ -81,7 +84,8 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
|
||||
* @param config to use
|
||||
* @param aliasModel for access to aliases
|
||||
*/
|
||||
public BroadcastifyCallBroadcaster(BroadcastifyCallConfiguration config, AliasModel aliasModel)
|
||||
public BroadcastifyCallBroadcaster(BroadcastifyCallConfiguration config, InputAudioFormat inputAudioFormat,
|
||||
MP3Setting mp3Setting, AliasModel aliasModel)
|
||||
{
|
||||
super(config);
|
||||
}
|
||||
@@ -106,6 +110,12 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
}
|
||||
|
||||
if(mBroadcastifyTestFuture == null && getBroadcastConfiguration().isTestEnabled())
|
||||
{
|
||||
// Test periodically so we don't get marked offline due to radio inactivity
|
||||
mBroadcastifyTestFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new BroadcastifyCallTest(), getBroadcastConfiguration().getTestInterval(), getBroadcastConfiguration().getTestInterval(), TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
if(mAudioRecordingProcessorFuture == null)
|
||||
{
|
||||
mAudioRecordingProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new AudioRecordingProcessor(),
|
||||
@@ -119,6 +129,11 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
if(mBroadcastifyTestFuture != null)
|
||||
{
|
||||
mBroadcastifyTestFuture.cancel(true);
|
||||
mBroadcastifyTestFuture = null;
|
||||
}
|
||||
if(mAudioRecordingProcessorFuture != null)
|
||||
{
|
||||
mAudioRecordingProcessorFuture.cancel(true);
|
||||
@@ -128,6 +143,23 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
|
||||
}
|
||||
}
|
||||
|
||||
public class BroadcastifyCallTest implements Runnable
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
String response = testConnection(getBroadcastConfiguration());
|
||||
if(response != null && response.toLowerCase().startsWith("ok"))
|
||||
{
|
||||
mLog.info("Broadcastify Calls keep-alive success");
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.info("Broadcastify Calls keep-alive failure");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares for disposal
|
||||
*/
|
||||
@@ -323,6 +355,11 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
|
||||
audioRecording.removePendingReplay();
|
||||
}
|
||||
}
|
||||
else if(urlResponse.startsWith("1 SKIPPED"))
|
||||
{
|
||||
//Broadcastify is telling us to skip audio upload - someone already uploaded it
|
||||
audioRecording.removePendingReplay();
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("Broadcastify calls API upload URL request failed [" + urlResponse + "]");
|
||||
@@ -410,40 +447,42 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
|
||||
*/
|
||||
private static String getTo(AudioRecording audioRecording)
|
||||
{
|
||||
List<Identifier> toIdentifiers = audioRecording.getIdentifierCollection().getIdentifiers(Role.TO);
|
||||
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
|
||||
|
||||
if(toIdentifiers.size() >= 1)
|
||||
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
|
||||
{
|
||||
Identifier to = toIdentifiers.get(0);
|
||||
|
||||
if(to instanceof TalkgroupIdentifier)
|
||||
{
|
||||
TalkgroupIdentifier talkgroupIdentifier = (TalkgroupIdentifier)to;
|
||||
return String.valueOf(RadioReferenceDecoder.convertToRadioReferenceTalkgroup(talkgroupIdentifier.getValue(),
|
||||
return format(patchGroupIdentifier);
|
||||
}
|
||||
else if(identifier instanceof TalkgroupIdentifier talkgroupIdentifier)
|
||||
{
|
||||
return String.valueOf(RadioReferenceDecoder.convertToRadioReferenceTalkgroup(talkgroupIdentifier.getValue(),
|
||||
talkgroupIdentifier.getProtocol()));
|
||||
}
|
||||
else if(to instanceof PatchGroupIdentifier)
|
||||
{
|
||||
PatchGroup patchGroup = ((PatchGroupIdentifier)to).getValue();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(patchGroup.getPatchGroup().getValue().toString());
|
||||
for(TalkgroupIdentifier patched: patchGroup.getPatchedGroupIdentifiers())
|
||||
{
|
||||
sb.append(",").append(patched.getValue());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
else if(to instanceof RadioIdentifier)
|
||||
{
|
||||
return ((RadioIdentifier)to).getValue().toString();
|
||||
}
|
||||
}
|
||||
else if(identifier instanceof RadioIdentifier radioIdentifier)
|
||||
{
|
||||
return radioIdentifier.getValue().toString();
|
||||
}
|
||||
|
||||
return "0";
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a patch group
|
||||
*/
|
||||
public static String format(PatchGroupIdentifier patchGroupIdentifier)
|
||||
{
|
||||
PatchGroup patchGroup = patchGroupIdentifier.getValue();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(patchGroup.getPatchGroup().getValue().toString());
|
||||
for(TalkgroupIdentifier patched: patchGroup.getPatchedGroupIdentifiers())
|
||||
{
|
||||
sb.append(",").append(patched.getValue());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests both the connection and configuration against the Broadcastify Call API service
|
||||
* @param configuration containing API key and system id
|
||||
|
||||
@@ -24,7 +24,9 @@ import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastFormat;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
@@ -42,6 +44,8 @@ public class BroadcastifyCallConfiguration extends BroadcastConfiguration
|
||||
|
||||
private IntegerProperty mSystemID = new SimpleIntegerProperty();
|
||||
private StringProperty mApiKey = new SimpleStringProperty();
|
||||
private BooleanProperty mTestEnabled= new SimpleBooleanProperty(false);
|
||||
private IntegerProperty mTestInterval = new SimpleIntegerProperty(15);
|
||||
|
||||
/**
|
||||
* Constructor for faster jackson
|
||||
@@ -128,6 +132,34 @@ public class BroadcastifyCallConfiguration extends BroadcastConfiguration
|
||||
return BroadcastServerType.BROADCASTIFY_CALL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this instance should schedule tests after connecting
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "test_enabled")
|
||||
public boolean isTestEnabled()
|
||||
{
|
||||
return mTestEnabled.get();
|
||||
}
|
||||
|
||||
public void setTestEnabled(boolean enabled)
|
||||
{
|
||||
mTestEnabled.set(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates the interval at which tests should be performed while enabled
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "test_interval")
|
||||
public int getTestInterval()
|
||||
{
|
||||
return mTestInterval.get();
|
||||
}
|
||||
|
||||
public void setTestInterval(int interval)
|
||||
{
|
||||
mTestInterval.set(interval);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BroadcastConfiguration copyOf()
|
||||
{
|
||||
|
||||
@@ -54,6 +54,7 @@ public class BroadcastifyFeedConfiguration extends IcecastTCPConfiguration
|
||||
setBitRate(16);
|
||||
setChannels(1);
|
||||
setSampleRate(8000);
|
||||
setInline(true);
|
||||
}
|
||||
|
||||
public static BroadcastifyFeedConfiguration from(UserFeedBroadcast userFeedBroadcast)
|
||||
@@ -86,9 +87,9 @@ public class BroadcastifyFeedConfiguration extends IcecastTCPConfiguration
|
||||
copy.setName(getName());
|
||||
copy.setHost(getHost());
|
||||
copy.setPort(getPort());
|
||||
copy.setInline(getInline());
|
||||
copy.setPassword(getPassword());
|
||||
copy.setDelay(getDelay());
|
||||
|
||||
copy.setEnabled(false);
|
||||
|
||||
//Icecast Configuration Parameters
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,23 +14,26 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast.icecast;
|
||||
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.audio.broadcast.AudioStreamingBroadcaster;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
|
||||
import io.github.dsheirer.audio.broadcast.IBroadcastMetadataUpdater;
|
||||
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||
|
||||
public abstract class IcecastAudioBroadcaster extends AudioStreamingBroadcaster
|
||||
{
|
||||
private IBroadcastMetadataUpdater mMetadataUpdater;
|
||||
private AliasModel mAliasModel;
|
||||
protected AliasModel mAliasModel;
|
||||
|
||||
public IcecastAudioBroadcaster(BroadcastConfiguration broadcastConfiguration, AliasModel aliasModel)
|
||||
public IcecastAudioBroadcaster(BroadcastConfiguration broadcastConfiguration, InputAudioFormat inputAudioFormat,
|
||||
MP3Setting mp3Setting, AliasModel aliasModel)
|
||||
{
|
||||
super(broadcastConfiguration);
|
||||
super(broadcastConfiguration, inputAudioFormat, mp3Setting);
|
||||
mAliasModel = aliasModel;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast.icecast;
|
||||
|
||||
@@ -26,6 +23,8 @@ import io.github.dsheirer.alias.Alias;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.audio.broadcast.IBroadcastMetadataUpdater;
|
||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastConfiguration;
|
||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastMetadata;
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
@@ -76,6 +75,12 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
|
||||
*/
|
||||
public void update(IdentifierCollection identifierCollection)
|
||||
{
|
||||
if(mIcecastConfiguration.hasInline())
|
||||
{
|
||||
// Never send out-of-band metadata if inline metadata is enabled.
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
try
|
||||
@@ -87,7 +92,7 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
|
||||
sb.append("/admin/metadata?mode=updinfo&mount=");
|
||||
sb.append(URLEncoder.encode(mIcecastConfiguration.getMountPoint(), UTF8));
|
||||
sb.append("&charset=UTF%2d8");
|
||||
sb.append("&song=").append(URLEncoder.encode(getSong(identifierCollection), UTF8));
|
||||
sb.append("&song=").append(URLEncoder.encode(IcecastMetadata.getTitle(identifierCollection, mAliasModel), UTF8));
|
||||
}
|
||||
catch(UnsupportedEncodingException uee)
|
||||
{
|
||||
@@ -100,7 +105,7 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
|
||||
final String metadataUpdateURL = sb.toString();
|
||||
URI uri = URI.create(metadataUpdateURL);
|
||||
|
||||
ThreadPool.SCHEDULED.submit(new Runnable()
|
||||
ThreadPool.CACHED.submit(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
@@ -160,82 +165,4 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the song information for a metadata update
|
||||
*/
|
||||
private String getSong(IdentifierCollection identifierCollection)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if(identifierCollection != null)
|
||||
{
|
||||
AliasList aliasList = mAliasModel.getAliasList(identifierCollection);
|
||||
|
||||
Identifier to = identifierCollection.getIdentifier(IdentifierClass.USER, Form.PATCH_GROUP, Role.TO);
|
||||
|
||||
if(to == null)
|
||||
{
|
||||
to = identifierCollection.getIdentifier(IdentifierClass.USER, Form.TALKGROUP, Role.TO);
|
||||
}
|
||||
|
||||
if(to == null)
|
||||
{
|
||||
List<Identifier> toIdentifiers = identifierCollection.getIdentifiers(Role.TO);
|
||||
if(!toIdentifiers.isEmpty())
|
||||
{
|
||||
to = toIdentifiers.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
if(to != null)
|
||||
{
|
||||
sb.append("TO:").append(to);
|
||||
|
||||
List<Alias> aliases = aliasList.getAliases(to);
|
||||
|
||||
if(aliases != null && !aliases.isEmpty())
|
||||
{
|
||||
sb.append(" ").append(Joiner.on(", ").skipNulls().join(aliases));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("TO:UNKNOWN");
|
||||
}
|
||||
|
||||
Identifier from = identifierCollection.getIdentifier(IdentifierClass.USER, Form.RADIO, Role.FROM);
|
||||
|
||||
if(from == null)
|
||||
{
|
||||
List<Identifier> fromIdentifiers = identifierCollection.getIdentifiers(Role.FROM);
|
||||
|
||||
if(!fromIdentifiers.isEmpty())
|
||||
{
|
||||
from = fromIdentifiers.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
if(from != null)
|
||||
{
|
||||
sb.append(" FROM:").append(from);
|
||||
|
||||
List<Alias> aliases = aliasList.getAliases(from);
|
||||
|
||||
if(aliases != null && !aliases.isEmpty())
|
||||
{
|
||||
sb.append(" ").append(Joiner.on(", ").skipNulls().join(aliases));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(" FROM:UNKNOWN");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("Scanning ....");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ public abstract class IcecastConfiguration extends BroadcastConfiguration
|
||||
private int mChannels = 1;
|
||||
private int mSampleRate = 8000;
|
||||
private String mURL;
|
||||
private boolean mInline = true;
|
||||
|
||||
public IcecastConfiguration(BroadcastFormat format)
|
||||
{
|
||||
@@ -275,6 +276,37 @@ public abstract class IcecastConfiguration extends BroadcastConfiguration
|
||||
return mURL != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Control whether metadata is sent inline or out-of-band.
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "inline")
|
||||
public boolean getInline()
|
||||
{
|
||||
return mInline;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public int getInlineInterval()
|
||||
{
|
||||
if (hasBitRate())
|
||||
{
|
||||
// Interval = BitRate * 1000 / 8 = 1 second
|
||||
return getBitRate() * 125;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void setInline(boolean inline)
|
||||
{
|
||||
mInline = inline;
|
||||
}
|
||||
|
||||
public boolean hasInline()
|
||||
{
|
||||
// Bitrate must be known to calculate metadata interval
|
||||
return mInline != false && hasBitRate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid()
|
||||
{
|
||||
|
||||
@@ -1,29 +1,30 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast.icecast;
|
||||
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastState;
|
||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastMetadata;
|
||||
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
||||
import io.github.dsheirer.audio.convert.MP3AudioConverter;
|
||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.properties.SystemProperties;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.apache.mina.core.RuntimeIoException;
|
||||
@@ -43,9 +44,9 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
@@ -72,23 +73,51 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
*
|
||||
* @param configuration for the Icecast stream
|
||||
*/
|
||||
public IcecastHTTPAudioBroadcaster(IcecastHTTPConfiguration configuration, AliasModel aliasModel)
|
||||
public IcecastHTTPAudioBroadcaster(IcecastHTTPConfiguration configuration, InputAudioFormat inputAudioFormat,
|
||||
MP3Setting mp3Setting, AliasModel aliasModel)
|
||||
{
|
||||
super(configuration, aliasModel);
|
||||
super(configuration, inputAudioFormat, mp3Setting, aliasModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the audio frame or sequence
|
||||
*/
|
||||
@Override
|
||||
protected void broadcastAudio(byte[] audio)
|
||||
protected void broadcastAudio(byte[] audio, IdentifierCollection identifierCollection)
|
||||
{
|
||||
if(audio != null && audio.length > 0 && connect() && mStreamingSession != null && mStreamingSession.isConnected())
|
||||
{
|
||||
IoBuffer buffer = IoBuffer.allocate(audio.length);
|
||||
buffer.put(audio);
|
||||
buffer.flip();
|
||||
|
||||
if(mInlineActive)
|
||||
{
|
||||
byte[] metadata = IcecastMetadata.formatInline(IcecastMetadata.getTitle(identifierCollection, mAliasModel)).getBytes();
|
||||
buffer.setAutoExpand(true);
|
||||
if (mInlineRemaining == -1)
|
||||
{
|
||||
mInlineRemaining = mInlineInterval;
|
||||
}
|
||||
int audioOffset = 0;
|
||||
while(audioOffset < audio.length)
|
||||
{
|
||||
byte[] chunk = Arrays.copyOfRange(audio, audioOffset, Math.min(audioOffset + mInlineRemaining, audio.length));
|
||||
mInlineRemaining -= chunk.length;
|
||||
audioOffset += chunk.length;
|
||||
buffer.put(chunk);
|
||||
if(mInlineRemaining == 0)
|
||||
{
|
||||
mInlineRemaining = mInlineInterval;
|
||||
buffer.put(metadata);
|
||||
}
|
||||
}
|
||||
buffer.shrink();
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.put(audio);
|
||||
}
|
||||
|
||||
buffer.flip();
|
||||
mStreamingSession.write(buffer);
|
||||
}
|
||||
}
|
||||
@@ -164,8 +193,7 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
}
|
||||
};
|
||||
|
||||
ThreadPool.SCHEDULED.schedule(runnable, 0l, TimeUnit.SECONDS);
|
||||
|
||||
ThreadPool.CACHED.submit(runnable);
|
||||
}
|
||||
|
||||
return connected();
|
||||
@@ -240,6 +268,17 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
{
|
||||
mHTTPHeaders.put(IcecastHeader.BITRATE.getValue(), String.valueOf(getConfiguration().getBitRate()));
|
||||
}
|
||||
|
||||
if(getConfiguration().hasInline())
|
||||
{
|
||||
mInlineActive = true;
|
||||
mInlineInterval = getConfiguration().getInlineInterval();
|
||||
mHTTPHeaders.put(IcecastHeader.METAINT.getValue(), String.valueOf(mInlineInterval));
|
||||
}
|
||||
else
|
||||
{
|
||||
mInlineActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
return mHTTPHeaders;
|
||||
|
||||
@@ -55,6 +55,7 @@ public class IcecastHTTPConfiguration extends IcecastConfiguration
|
||||
copy.setName(getName());
|
||||
copy.setHost(getHost());
|
||||
copy.setPort(getPort());
|
||||
copy.setInline(getInline());
|
||||
copy.setPassword(getPassword());
|
||||
copy.setDelay(getDelay());
|
||||
copy.setEnabled(false);
|
||||
|
||||
@@ -33,6 +33,7 @@ public enum IcecastHeader
|
||||
PUBLIC("ice-public"),
|
||||
URL("ice-url"),
|
||||
USER_AGENT("User-Agent"),
|
||||
METAINT("icy-metaint"),
|
||||
|
||||
UNKNOWN("unknown");
|
||||
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast.icecast;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import io.github.dsheirer.alias.Alias;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastConfiguration;
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
import io.github.dsheirer.properties.SystemProperties;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.List;
|
||||
|
||||
public class IcecastMetadata
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(IcecastMetadata.class);
|
||||
|
||||
/**
|
||||
* Creates the title for a metadata update
|
||||
*/
|
||||
public static String getTitle(IdentifierCollection identifierCollection, AliasModel AliasModel)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if(identifierCollection != null)
|
||||
{
|
||||
AliasList aliasList = AliasModel.getAliasList(identifierCollection);
|
||||
|
||||
Identifier to = identifierCollection.getIdentifier(IdentifierClass.USER, Form.PATCH_GROUP, Role.TO);
|
||||
|
||||
if(to == null)
|
||||
{
|
||||
to = identifierCollection.getIdentifier(IdentifierClass.USER, Form.TALKGROUP, Role.TO);
|
||||
}
|
||||
|
||||
if(to == null)
|
||||
{
|
||||
List<Identifier> toIdentifiers = identifierCollection.getIdentifiers(Role.TO);
|
||||
if(!toIdentifiers.isEmpty())
|
||||
{
|
||||
to = toIdentifiers.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
if(to != null)
|
||||
{
|
||||
sb.append("TO:").append(to);
|
||||
|
||||
List<Alias> aliases = aliasList.getAliases(to);
|
||||
|
||||
if(aliases != null && !aliases.isEmpty())
|
||||
{
|
||||
sb.append(" ").append(Joiner.on(", ").skipNulls().join(aliases));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("TO:UNKNOWN");
|
||||
}
|
||||
|
||||
Identifier from = identifierCollection.getIdentifier(IdentifierClass.USER, Form.RADIO, Role.FROM);
|
||||
|
||||
if(from == null)
|
||||
{
|
||||
List<Identifier> fromIdentifiers = identifierCollection.getIdentifiers(Role.FROM);
|
||||
|
||||
if(!fromIdentifiers.isEmpty())
|
||||
{
|
||||
from = fromIdentifiers.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
if(from != null)
|
||||
{
|
||||
sb.append(" FROM:").append(from);
|
||||
|
||||
List<Alias> aliases = aliasList.getAliases(from);
|
||||
|
||||
if(aliases != null && !aliases.isEmpty())
|
||||
{
|
||||
sb.append(" ").append(Joiner.on(", ").skipNulls().join(aliases));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(" FROM:UNKNOWN");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("Scanning...");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String formatInline(String title)
|
||||
{
|
||||
title = "StreamTitle='" + title + "';";
|
||||
int chunks = (int)Math.ceil((title.length() + 1) / 16.0d);
|
||||
int nulls = (chunks * 16) - title.length();
|
||||
char[] padding = new char[nulls];
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append((char)chunks).append(title).append(padding);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,14 +14,18 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast.icecast;
|
||||
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastState;
|
||||
import io.github.dsheirer.audio.broadcast.icecast.codec.IcecastCodecFactory;
|
||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastMetadata;
|
||||
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
||||
import io.github.dsheirer.audio.convert.MP3AudioConverter;
|
||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.properties.SystemProperties;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.apache.mina.core.RuntimeIoException;
|
||||
@@ -37,13 +41,14 @@ import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.nio.channels.UnresolvedAddressException;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(IcecastTCPAudioBroadcaster.class);
|
||||
private final static String TERMINATOR = "\r\n";
|
||||
private final static String TERMINATOR = "\n";
|
||||
private final static String SEPARATOR = ":";
|
||||
private static final long RECONNECT_INTERVAL_MILLISECONDS = 3000; //3 seconds
|
||||
private static final long CONNECTION_ATTEMPT_TIMEOUT_MILLISECONDS = 5000; //5 seconds
|
||||
@@ -66,20 +71,45 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
*
|
||||
* @param configuration for the Icecast stream
|
||||
*/
|
||||
public IcecastTCPAudioBroadcaster(IcecastTCPConfiguration configuration, AliasModel aliasModel)
|
||||
public IcecastTCPAudioBroadcaster(IcecastTCPConfiguration configuration, InputAudioFormat inputAudioFormat,
|
||||
MP3Setting mp3Setting, AliasModel aliasModel)
|
||||
{
|
||||
super(configuration, aliasModel);
|
||||
super(configuration, inputAudioFormat, mp3Setting, aliasModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the audio frame or sequence
|
||||
*/
|
||||
@Override
|
||||
protected void broadcastAudio(byte[] audio)
|
||||
protected void broadcastAudio(byte[] audio, IdentifierCollection identifierCollection)
|
||||
{
|
||||
if(audio != null && audio.length > 0 && connect() && mStreamingSession != null && mStreamingSession.isConnected())
|
||||
{
|
||||
mStreamingSession.write(audio);
|
||||
if(mInlineActive)
|
||||
{
|
||||
byte[] metadata = IcecastMetadata.formatInline(IcecastMetadata.getTitle(identifierCollection, mAliasModel)).getBytes();
|
||||
if (mInlineRemaining == -1)
|
||||
{
|
||||
mInlineRemaining = mInlineInterval;
|
||||
}
|
||||
int audioOffset = 0;
|
||||
while(audioOffset < audio.length)
|
||||
{
|
||||
byte[] chunk = Arrays.copyOfRange(audio, audioOffset, Math.min(audioOffset + mInlineRemaining, audio.length));
|
||||
mInlineRemaining -= chunk.length;
|
||||
audioOffset += chunk.length;
|
||||
mStreamingSession.write(chunk);
|
||||
if(mInlineRemaining == 0)
|
||||
{
|
||||
mInlineRemaining = mInlineInterval;
|
||||
mStreamingSession.write(metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mStreamingSession.write(audio);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +197,7 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
}
|
||||
};
|
||||
|
||||
ThreadPool.SCHEDULED.schedule(runnable, 0l, TimeUnit.SECONDS);
|
||||
ThreadPool.CACHED.submit(runnable);
|
||||
}
|
||||
|
||||
return connected();
|
||||
@@ -235,7 +265,18 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
.append(getConfiguration().getDescription()).append(TERMINATOR);
|
||||
}
|
||||
|
||||
sb.append(TERMINATOR).append(TERMINATOR);
|
||||
if(getConfiguration().hasInline())
|
||||
{
|
||||
mInlineActive = true;
|
||||
mInlineInterval = getConfiguration().getInlineInterval();
|
||||
sb.append(IcecastHeader.METAINT.getValue()).append(SEPARATOR)
|
||||
.append(String.valueOf(mInlineInterval))
|
||||
.append(TERMINATOR);
|
||||
}
|
||||
else
|
||||
{
|
||||
mInlineActive = false;
|
||||
}
|
||||
|
||||
session.write(sb.toString());
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ public class IcecastTCPConfiguration extends IcecastConfiguration
|
||||
copy.setName(getName());
|
||||
copy.setHost(getHost());
|
||||
copy.setPort(getPort());
|
||||
copy.setInline(getInline());
|
||||
copy.setPassword(getPassword());
|
||||
copy.setDelay(getDelay());
|
||||
copy.setEnabled(false);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,8 +14,8 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast.icecast.codec;
|
||||
|
||||
import org.apache.mina.core.buffer.IoBuffer;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,14 +14,17 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast.shoutcast.v1;
|
||||
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.audio.broadcast.AudioStreamingBroadcaster;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastState;
|
||||
import io.github.dsheirer.audio.broadcast.IBroadcastMetadataUpdater;
|
||||
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.apache.mina.core.RuntimeIoException;
|
||||
import org.apache.mina.core.buffer.IoBuffer;
|
||||
@@ -37,7 +40,6 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.IOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class ShoutcastV1AudioBroadcaster extends AudioStreamingBroadcaster
|
||||
@@ -62,9 +64,10 @@ public class ShoutcastV1AudioBroadcaster extends AudioStreamingBroadcaster
|
||||
*
|
||||
* @param configuration for the Shoutcast stream
|
||||
*/
|
||||
public ShoutcastV1AudioBroadcaster(ShoutcastV1Configuration configuration, AliasModel aliasModel)
|
||||
public ShoutcastV1AudioBroadcaster(ShoutcastV1Configuration configuration, InputAudioFormat inputAudioFormat,
|
||||
MP3Setting mp3Setting, AliasModel aliasModel)
|
||||
{
|
||||
super(configuration);
|
||||
super(configuration, inputAudioFormat, mp3Setting);
|
||||
mAliasModel = aliasModel;
|
||||
}
|
||||
|
||||
@@ -91,7 +94,7 @@ public class ShoutcastV1AudioBroadcaster extends AudioStreamingBroadcaster
|
||||
* Broadcasts the audio frame or sequence
|
||||
*/
|
||||
@Override
|
||||
protected void broadcastAudio(byte[] audio)
|
||||
protected void broadcastAudio(byte[] audio, IdentifierCollection identifierCollection)
|
||||
{
|
||||
if(audio != null && audio.length > 0 && connect() && mStreamingSession != null && mStreamingSession.isConnected())
|
||||
{
|
||||
@@ -179,7 +182,7 @@ public class ShoutcastV1AudioBroadcaster extends AudioStreamingBroadcaster
|
||||
}
|
||||
};
|
||||
|
||||
ThreadPool.SCHEDULED.schedule(runnable, 0l, TimeUnit.SECONDS);
|
||||
ThreadPool.CACHED.submit(runnable);
|
||||
}
|
||||
|
||||
return connected();
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast.shoutcast.v1;
|
||||
|
||||
@@ -53,7 +50,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUpdater
|
||||
@@ -138,7 +134,7 @@ public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUp
|
||||
|
||||
if(updateRequest != null)
|
||||
{
|
||||
ThreadPool.SCHEDULED.schedule(new Runnable()
|
||||
ThreadPool.CACHED.submit(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
@@ -176,7 +172,7 @@ public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUp
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 0l, TimeUnit.SECONDS);
|
||||
});
|
||||
}
|
||||
|
||||
//Fetch next metadata update to send
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast.shoutcast.v2;
|
||||
|
||||
@@ -43,6 +40,8 @@ import io.github.dsheirer.audio.broadcast.shoutcast.v2.ultravox.UltravoxMessageF
|
||||
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ultravox.UltravoxMessageType;
|
||||
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ultravox.UltravoxMetadata;
|
||||
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ultravox.UltravoxProtocolFactory;
|
||||
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
@@ -67,7 +66,6 @@ import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class ShoutcastV2AudioStreamingBroadcaster extends AudioStreamingBroadcaster implements IBroadcastMetadataUpdater
|
||||
@@ -95,9 +93,10 @@ public class ShoutcastV2AudioStreamingBroadcaster extends AudioStreamingBroadcas
|
||||
*
|
||||
* @param configuration for the Shoutcast V2 stream
|
||||
*/
|
||||
public ShoutcastV2AudioStreamingBroadcaster(ShoutcastV2Configuration configuration, AliasModel aliasModel)
|
||||
public ShoutcastV2AudioStreamingBroadcaster(ShoutcastV2Configuration configuration, InputAudioFormat inputAudioFormat,
|
||||
MP3Setting mp3Setting, AliasModel aliasModel)
|
||||
{
|
||||
super(configuration);
|
||||
super(configuration, inputAudioFormat, mp3Setting);
|
||||
mAliasModel = aliasModel;
|
||||
}
|
||||
|
||||
@@ -110,7 +109,7 @@ public class ShoutcastV2AudioStreamingBroadcaster extends AudioStreamingBroadcas
|
||||
* Broadcasts the audio frame or sequence
|
||||
*/
|
||||
@Override
|
||||
protected void broadcastAudio(byte[] audio)
|
||||
protected void broadcastAudio(byte[] audio, IdentifierCollection identifierCollection)
|
||||
{
|
||||
//Dispatch any queued metadata messages
|
||||
UltravoxMessage metadataMessage = mMetadataMessageQueue.poll();
|
||||
@@ -225,7 +224,7 @@ public class ShoutcastV2AudioStreamingBroadcaster extends AudioStreamingBroadcas
|
||||
}
|
||||
};
|
||||
|
||||
ThreadPool.SCHEDULED.schedule(runnable, 0l, TimeUnit.SECONDS);
|
||||
ThreadPool.CACHED.submit(runnable);
|
||||
}
|
||||
|
||||
return connected();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,8 +14,8 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast.shoutcast.v2.ultravox;
|
||||
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastFormat;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,8 +14,8 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast.shoutcast.v2.ultravox;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,8 +14,8 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast.shoutcast.v2.ultravox;
|
||||
|
||||
public class UltravoxMessageFactory
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.codec.mbe;
|
||||
@@ -27,6 +24,7 @@ import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.encryption.EncryptionKey;
|
||||
import io.github.dsheirer.module.decode.p25.audio.VoiceFrame;
|
||||
|
||||
@@ -132,6 +130,18 @@ public class MBECallSequence
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the from radio identifier
|
||||
* @param identifier for from entity
|
||||
*/
|
||||
public void setFromIdentifier(Identifier identifier)
|
||||
{
|
||||
if(identifier != null)
|
||||
{
|
||||
setFromIdentifier(identifier.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Radio identifier that originated the call
|
||||
*
|
||||
@@ -156,6 +166,18 @@ public class MBECallSequence
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the TO identifier
|
||||
* @param identifier for the TO entity
|
||||
*/
|
||||
public void setToIdentifier(Identifier identifier)
|
||||
{
|
||||
if(identifier != null)
|
||||
{
|
||||
setToIdentifier(identifier.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Radio identifier that received the call
|
||||
*
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
/**
|
||||
* MP3 Channel Mode
|
||||
*
|
||||
* see: http://www.mp3-tech.org/programmer/frame_header.html
|
||||
*/
|
||||
public enum ChannelMode
|
||||
{
|
||||
STEREO("STEREO"),
|
||||
JOINT_STEREO("JOINT STEREO"),
|
||||
DUAL_CHANNEL("DUAL CHANNEL"),
|
||||
MONO("MONO");
|
||||
|
||||
private String mLabel;
|
||||
|
||||
ChannelMode(String label)
|
||||
{
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
public static ChannelMode fromValue(int value)
|
||||
{
|
||||
switch(value)
|
||||
{
|
||||
case 0:
|
||||
return STEREO;
|
||||
case 1:
|
||||
return JOINT_STEREO;
|
||||
case 2:
|
||||
return DUAL_CHANNEL;
|
||||
case 3:
|
||||
default:
|
||||
return MONO;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,8 +14,8 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
import java.util.List;
|
||||
@@ -25,11 +25,11 @@ public interface IAudioConverter
|
||||
/**
|
||||
* Converts the PCM audio packets to converted audio format. May produce partial audio frame data.
|
||||
*/
|
||||
public byte[] convert(List<float[]> audioBuffers);
|
||||
List<byte[]> convert(List<float[]> audioBuffers);
|
||||
|
||||
/**
|
||||
* Finalizes audio conversion by fully converting any partial frames left in the buffer and returning the
|
||||
* remaining bytes to produce a full frame.
|
||||
*/
|
||||
public byte[] flush();
|
||||
List<byte[]> flush();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,11 +14,13 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ISilenceGenerator
|
||||
{
|
||||
byte[] generate(long duration);
|
||||
List<byte[]> generate(long duration);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
import io.github.dsheirer.audio.AudioFormats;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
|
||||
/**
|
||||
* Enumeration of MP3 input audio formats for mono audio.
|
||||
*/
|
||||
public enum InputAudioFormat
|
||||
{
|
||||
SR_8000(AudioFormats.PCM_SIGNED_8000_HZ_16_BIT_MONO, "16-Bit 8000 Hz (no resample)"),
|
||||
SR_16000(AudioFormats.PCM_SIGNED_16000_HZ_16_BIT_MONO, "16-Bit 16000 Hz"),
|
||||
SR_22050(AudioFormats.PCM_SIGNED_22050_HZ_16_BIT_MONO, "16-Bit 22050 Hz (default)"),
|
||||
SR_44100(AudioFormats.PCM_SIGNED_44100_HZ_16_BIT_MONO, "16-Bit 44100 Hz"),
|
||||
|
||||
SR_32_8000(AudioFormats.PCM_SIGNED_8000_HZ_32_BIT_MONO, "32-Bit 8000 Hz (no resample)"),
|
||||
SR_32_16000(AudioFormats.PCM_SIGNED_16000_HZ_32_BIT_MONO, "32-Bit 16000 Hz"),
|
||||
SR_32_22050(AudioFormats.PCM_SIGNED_22050_HZ_32_BIT_MONO, "32-Bit 22050 Hz"),
|
||||
SR_32_44100(AudioFormats.PCM_SIGNED_44100_HZ_32_BIT_MONO, "32-Bit 44100 Hz");
|
||||
|
||||
private AudioFormat mAudioFormat;
|
||||
private String mLabel;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param audioFormat for the specified sample rate
|
||||
*/
|
||||
InputAudioFormat(AudioFormat audioFormat, String label)
|
||||
{
|
||||
mAudioFormat = audioFormat;
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default sample rate
|
||||
*/
|
||||
public static InputAudioFormat getDefault()
|
||||
{
|
||||
return SR_22050;
|
||||
}
|
||||
|
||||
/**
|
||||
* Audio format for the specified sample rate entry
|
||||
*/
|
||||
public AudioFormat getAudioFormat()
|
||||
{
|
||||
return mAudioFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample rate for the format
|
||||
*/
|
||||
public double getSampleRate()
|
||||
{
|
||||
return getAudioFormat().getSampleRate();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.resample.RealResampler;
|
||||
import net.sourceforge.lame.lowlevel.LameEncoder;
|
||||
import net.sourceforge.lame.mp3.MPEGMode;
|
||||
|
||||
/**
|
||||
* Factory class to create and configure LAME MP3 encoder
|
||||
*/
|
||||
public class LameFactory
|
||||
{
|
||||
/**
|
||||
* Creates a LAME MP3 encoder using the specified input sample rate and MP3 settings
|
||||
* @param input sample rate
|
||||
* @param setting to apply to the encoder
|
||||
* @return lame encoder
|
||||
*/
|
||||
public static LameEncoder getLameEncoder(InputAudioFormat input, MP3Setting setting)
|
||||
{
|
||||
return switch(setting)
|
||||
{
|
||||
case CBR_16 -> new LameEncoder(input.getAudioFormat(), 16, MPEGMode.MONO, LameEncoder.DEFAULT_QUALITY, false);
|
||||
case CBR_32 -> new LameEncoder(input.getAudioFormat(), 32, MPEGMode.MONO, LameEncoder.DEFAULT_QUALITY, false);
|
||||
case ABR_56 -> new LameEncoder(input.getAudioFormat(), 56, MPEGMode.MONO, LameEncoder.DEFAULT_QUALITY, true);
|
||||
case VBR_5 -> new LameEncoder(input.getAudioFormat(), LameEncoder.DEFAULT_BITRATE, MPEGMode.MONO, 5, true);
|
||||
case VBR_7 -> new LameEncoder(input.getAudioFormat(), LameEncoder.DEFAULT_BITRATE, MPEGMode.MONO, 7, true);
|
||||
default -> throw new IllegalArgumentException("Unrecognized MP3 setting: " + setting);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a resampler to resample from 8 kHz default audio sample rate to the specified sample rate.
|
||||
* @param sampleRate to resample to
|
||||
* @return resampler.
|
||||
*/
|
||||
public static RealResampler getResampler(InputAudioFormat sampleRate)
|
||||
{
|
||||
return switch(sampleRate)
|
||||
{
|
||||
case SR_16000, SR_32_16000 -> new RealResampler(8000, 16000, 4096, 512);
|
||||
case SR_22050, SR_32_22050 -> new RealResampler(8000, 22050, 4096, 512);
|
||||
case SR_44100, SR_32_44100 -> new RealResampler(8000, 44100, 4096, 512);
|
||||
default -> throw new IllegalArgumentException("Unrecognized sample rate for resampling: " + sampleRate);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,113 +14,142 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
import io.github.dsheirer.audio.AudioFormats;
|
||||
import io.github.dsheirer.audio.AudioUtils;
|
||||
import io.github.dsheirer.dsp.filter.resample.RealResampler;
|
||||
import io.github.dsheirer.sample.ConversionUtils;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import net.sourceforge.lame.lowlevel.LameEncoder;
|
||||
import net.sourceforge.lame.mp3.Lame;
|
||||
import net.sourceforge.lame.mp3.MPEGMode;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Converts PCM audio packets to Mono, MP3 encoded audio
|
||||
*/
|
||||
public class MP3AudioConverter implements IAudioConverter
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( MP3AudioConverter.class );
|
||||
public static final int AUDIO_QUALITY = Lame.QUALITY_LOW;
|
||||
private LameEncoder mEncoder;
|
||||
private ByteArrayOutputStream mMP3Stream = new ByteArrayOutputStream();
|
||||
private byte[] mMP3Buffer;
|
||||
private RealResampler mResampler;
|
||||
private ByteArrayOutputStream mMP3EncodedFramesStream = new ByteArrayOutputStream();
|
||||
private byte[] mOutputFramesBuffer;
|
||||
private InputAudioFormat mInputAudioFormat;
|
||||
|
||||
private boolean mNormalizeAudio;
|
||||
|
||||
/**
|
||||
* Converts PCM 8kHz 16-bit Little Endian audio packets to Mono, Low Quality MP3 compressed audio.
|
||||
* Constructs an instance.
|
||||
*
|
||||
* @param bitRate for converted MP3 audio
|
||||
* @param variableBitRate (VBR) true or false for constant bit rate (CBR)
|
||||
* @param inputAudioFormat for the desired input sample rate and bit size (resampled from default 8 kHz as needed)
|
||||
* @param setting to configure the LAME encoder
|
||||
* @param normalizeAudio to normalize the audio gain prior to encoding
|
||||
*/
|
||||
public MP3AudioConverter(int bitRate, boolean variableBitRate)
|
||||
public MP3AudioConverter(InputAudioFormat inputAudioFormat, MP3Setting setting, boolean normalizeAudio)
|
||||
{
|
||||
mEncoder = new LameEncoder(AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO,
|
||||
bitRate, MPEGMode.MONO, AUDIO_QUALITY, variableBitRate);
|
||||
mInputAudioFormat = inputAudioFormat;
|
||||
mEncoder = LameFactory.getLameEncoder(inputAudioFormat, setting);
|
||||
mNormalizeAudio = normalizeAudio;
|
||||
|
||||
mMP3Buffer = new byte[mEncoder.getPCMBufferSize()];
|
||||
//Resampling is only required if desired input sample rate is not system default of 8kHz
|
||||
if(inputAudioFormat != InputAudioFormat.SR_8000 && inputAudioFormat != InputAudioFormat.SR_32_8000)
|
||||
{
|
||||
mResampler = LameFactory.getResampler(inputAudioFormat);
|
||||
}
|
||||
|
||||
mOutputFramesBuffer = new byte[mEncoder.getPCMBufferSize()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the list of PCM audio packets to MP3 encoded.
|
||||
* @param audioPackets of PCM audio sampled at 8 kHz
|
||||
* @return encoded MP3 audio
|
||||
*/
|
||||
public List<byte[]> convert(List<float[]> audioPackets)
|
||||
{
|
||||
List<byte[]> converted = new ArrayList<>();
|
||||
|
||||
if(mNormalizeAudio)
|
||||
{
|
||||
audioPackets = AudioUtils.normalize(audioPackets);
|
||||
}
|
||||
|
||||
if(mResampler != null)
|
||||
{
|
||||
audioPackets = mResampler.resample(audioPackets);
|
||||
}
|
||||
|
||||
for(int x = 0; x < audioPackets.size(); x++)
|
||||
{
|
||||
byte[] bytesToEncode = null;
|
||||
|
||||
if(mInputAudioFormat.getAudioFormat().getSampleSizeInBits() == 16)
|
||||
{
|
||||
bytesToEncode = ConversionUtils.convertToSigned16BitSamples(audioPackets.get(x)).array();
|
||||
}
|
||||
else
|
||||
{
|
||||
bytesToEncode = ConversionUtils.convertToSigned32BitSamples(audioPackets.get(x)).array();
|
||||
}
|
||||
|
||||
int bytesToEncodePointer = 0;
|
||||
|
||||
int inputChunkSize = FastMath.min(mOutputFramesBuffer.length, bytesToEncode.length);
|
||||
int outputChunkSize = 0;
|
||||
|
||||
try
|
||||
{
|
||||
while(bytesToEncodePointer < bytesToEncode.length)
|
||||
{
|
||||
outputChunkSize = mEncoder.encodeBuffer(bytesToEncode, bytesToEncodePointer, inputChunkSize, mOutputFramesBuffer);
|
||||
bytesToEncodePointer += inputChunkSize;
|
||||
inputChunkSize = FastMath.min(mOutputFramesBuffer.length, bytesToEncode.length - bytesToEncodePointer);
|
||||
|
||||
if(outputChunkSize > 0)
|
||||
{
|
||||
converted.add(Arrays.copyOf(mOutputFramesBuffer, outputChunkSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("There was an error converting audio to MP3: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
int finalChunkSize = mEncoder.encodeFinish(mOutputFramesBuffer);
|
||||
|
||||
if(finalChunkSize > 0)
|
||||
{
|
||||
converted.add(Arrays.copyOf(mOutputFramesBuffer, finalChunkSize));
|
||||
}
|
||||
|
||||
return converted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] convert(List<float[]> audioPackets)
|
||||
{
|
||||
mMP3Stream.reset();
|
||||
|
||||
byte[] pcmBytes = AudioUtils.convertTo16BitSamples(audioPackets);
|
||||
|
||||
int pcmBufferSize = FastMath.min(mMP3Buffer.length, pcmBytes.length);
|
||||
|
||||
int mp3BufferSize = 0;
|
||||
|
||||
int pcmBytesPosition = 0;
|
||||
|
||||
try
|
||||
{
|
||||
while (0 < (mp3BufferSize = mEncoder.encodeBuffer(pcmBytes, pcmBytesPosition, pcmBufferSize, mMP3Buffer)))
|
||||
{
|
||||
pcmBytesPosition += pcmBufferSize;
|
||||
pcmBufferSize = FastMath.min(mMP3Buffer.length, pcmBytes.length - pcmBytesPosition);
|
||||
mMP3Stream.write(mMP3Buffer, 0, mp3BufferSize);
|
||||
}
|
||||
|
||||
return mMP3Stream.toByteArray();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("There was an error converting audio to MP3: " + e.getMessage());
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] convertAudio(List<float[]> audioBuffers)
|
||||
{
|
||||
mMP3Stream.reset();
|
||||
|
||||
byte[] pcmBytes = AudioUtils.convert(audioBuffers);
|
||||
|
||||
int pcmBufferSize = FastMath.min(mMP3Buffer.length, pcmBytes.length);
|
||||
|
||||
int mp3BufferSize = 0;
|
||||
|
||||
int pcmBytesPosition = 0;
|
||||
|
||||
try
|
||||
{
|
||||
while (0 < (mp3BufferSize = mEncoder.encodeBuffer(pcmBytes, pcmBytesPosition, pcmBufferSize, mMP3Buffer)))
|
||||
{
|
||||
pcmBytesPosition += pcmBufferSize;
|
||||
pcmBufferSize = FastMath.min(mMP3Buffer.length, pcmBytes.length - pcmBytesPosition);
|
||||
mMP3Stream.write(mMP3Buffer, 0, mp3BufferSize);
|
||||
}
|
||||
|
||||
return mMP3Stream.toByteArray();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("There was an error converting audio to MP3: " + e.getMessage());
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] flush()
|
||||
public List<byte[]> flush()
|
||||
{
|
||||
byte[] lastPartialFrame = new byte[mEncoder.getMP3BufferSize()];
|
||||
|
||||
int length = mEncoder.encodeFinish(lastPartialFrame);
|
||||
|
||||
return Arrays.copyOf(lastPartialFrame, length);
|
||||
byte[] frame = Arrays.copyOf(lastPartialFrame, length);
|
||||
|
||||
if(frame.length == 0)
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return Collections.singletonList(frame);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
public class MP3BitRate
|
||||
{
|
||||
private final static int[] MPEG_1_L3 = new int[]{0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320};
|
||||
private final static int[] MPEG_2_L3 = new int[]{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160};
|
||||
private final static int[] MPEG_2_5_L3 = new int[]{0, 8, 16, 24, 32, 40, 48, 56, 64};
|
||||
|
||||
public static int getLayer3BitRate(MPEGVersion version, int value)
|
||||
{
|
||||
switch(version)
|
||||
{
|
||||
case V_1:
|
||||
if(0 <= value && value < MPEG_1_L3.length)
|
||||
{
|
||||
return MPEG_1_L3[value];
|
||||
}
|
||||
break;
|
||||
case V_2:
|
||||
if(0 <= value && value < MPEG_2_L3.length)
|
||||
{
|
||||
return MPEG_2_L3[value];
|
||||
}
|
||||
break;
|
||||
case V_2_5:
|
||||
if(0 <= value && value < MPEG_2_5_L3.length)
|
||||
{
|
||||
return MPEG_2_5_L3[value];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,73 +14,53 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MP3FrameInspector
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(MP3FrameInspector.class);
|
||||
|
||||
public static void inspect(byte[] frames)
|
||||
public static void inspect(List<byte[]> frames)
|
||||
{
|
||||
if(frames == null || frames.length == 0)
|
||||
if(frames.isEmpty())
|
||||
{
|
||||
mLog.info("No Frame Data!");
|
||||
return;
|
||||
}
|
||||
|
||||
if(frames.length % 144 != 0)
|
||||
for(byte[] frame: frames)
|
||||
{
|
||||
log("Frame byte array not multiple of 144 byte frame size - length:" + frames.length, frames);
|
||||
return;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int frameCounter = 0;
|
||||
|
||||
//Check for erroneous frame sync
|
||||
for(int x = 0; x < frames.length - 1; x++)
|
||||
{
|
||||
if(frames[x] == (byte)0xFF && (frames[x + 1] & 0xE0) == 0xE0 && (x % 144) != 0)
|
||||
for(int x = 0; x < frame.length; x++)
|
||||
{
|
||||
log("Bad frame sync detected at byte " + x + " in frame " + (x / 144), frames);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(MP3Header.isValid(frame, x))
|
||||
{
|
||||
sb.append("\n").append(++frameCounter).append(": ");
|
||||
sb.append(MP3Header.inspect(frame, x)).append(" - ");
|
||||
}
|
||||
|
||||
//Check MP3 headers
|
||||
|
||||
// log("Frames ...", frames);
|
||||
}
|
||||
|
||||
private static void log(String message, byte[] frames)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(message).append("\n");
|
||||
|
||||
int frameCounter = 0;
|
||||
sb.append(frameCounter++).append(" ");
|
||||
|
||||
for(int x = 0; x < frames.length; x++)
|
||||
{
|
||||
if(x > 0 && x % 144 == 0)
|
||||
{
|
||||
sb.append("\n");
|
||||
sb.append(frameCounter++).append(" ");
|
||||
sb.append(String.format("%02X ", frame[x]));
|
||||
}
|
||||
|
||||
sb.append(String.format("%02X ", frames[x]));
|
||||
mLog.info(sb.toString());
|
||||
}
|
||||
|
||||
mLog.info(sb.toString());
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
mLog.info("Starting ...");
|
||||
MP3SilenceGenerator gen = new MP3SilenceGenerator();
|
||||
MP3SilenceGenerator gen = new MP3SilenceGenerator(InputAudioFormat.SR_8000, MP3Setting.getDefault());
|
||||
|
||||
byte[] audio = gen.generate(173);
|
||||
List<byte[]> audio = gen.generate(173);
|
||||
|
||||
inspect(audio);
|
||||
|
||||
|
||||
115
src/main/java/io/github/dsheirer/audio/convert/MP3Header.java
Normal file
115
src/main/java/io/github/dsheirer/audio/convert/MP3Header.java
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
/**
|
||||
* Utility class for inspecting/parsing MP3 frames
|
||||
*/
|
||||
public class MP3Header
|
||||
{
|
||||
private MP3Header() {}
|
||||
|
||||
/**
|
||||
* Indicates if the byte array at the offset has a valid MP3 frame sync and is at least 4 bytes long
|
||||
* @param frame array of bytes
|
||||
* @param offset into the array
|
||||
* @return true if the first two offset bytes contain a frame sync and length from offset is at least 4
|
||||
*/
|
||||
public static boolean isValid(byte[] frame, int offset)
|
||||
{
|
||||
return frame.length >= (offset + 4) && hasSync(frame, offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the byte array at the offset has a valid MP3 frame sync
|
||||
* @param frame array of bytes
|
||||
* @param offset into the array
|
||||
* @return true if the first two offset bytes contain a frame sync
|
||||
*/
|
||||
public static boolean hasSync(byte[] frame, int offset)
|
||||
{
|
||||
return (frame[offset] & 0xFF) == 0xFF && (frame[offset + 1] & 0xE0) == 0xE0;
|
||||
}
|
||||
|
||||
/**
|
||||
* MPEG version for the frame
|
||||
*/
|
||||
public static MPEGVersion getMPEGVersion(byte[] frame, int offset)
|
||||
{
|
||||
return MPEGVersion.fromValue((frame[offset + 1] & 0x18) >> 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* MPEG layer for the frame. Normally layer 3 for audio.
|
||||
*/
|
||||
public static MPEGLayer getMPEGLayer(byte[] frame, int offset)
|
||||
{
|
||||
return MPEGLayer.fromValue((frame[offset + 1] & 0x6) >> 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* MP3 bit rate
|
||||
*/
|
||||
public static int getBitRate(byte[] frame, int offset)
|
||||
{
|
||||
return MP3BitRate.getLayer3BitRate(getMPEGVersion(frame, offset), (frame[offset + 2] & 0xF0) >> 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Input audio sample rate.
|
||||
*/
|
||||
public static int getSampleRate(byte[] frame, int offset)
|
||||
{
|
||||
return MP3SampleRate.getSampleRate(getMPEGVersion(frame, offset), (frame[offset + 2] & 0x0C) >> 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel mode
|
||||
*/
|
||||
public static ChannelMode getChannelMode(byte[] frame, int offset)
|
||||
{
|
||||
return ChannelMode.fromValue((frame[offset + 3] & 0xC0) >> 6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspects the frame bytes and provides details about the header.
|
||||
*/
|
||||
public static String inspect(byte[] frame, int offset)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("MP3 FRAME");
|
||||
|
||||
if(!isValid(frame, offset))
|
||||
{
|
||||
sb.append(" INVALID");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
sb.append(" VER:").append(getMPEGVersion(frame, offset));
|
||||
sb.append(" MODE:").append(getChannelMode(frame, offset));
|
||||
sb.append(" ").append(getMPEGLayer(frame, offset));
|
||||
sb.append(" BR:").append(getBitRate(frame, offset));
|
||||
sb.append(" SR:").append(getSampleRate(frame, offset));
|
||||
|
||||
//TODO: add MONO/STEREO
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
public class MP3SampleRate
|
||||
{
|
||||
private final static int[] MPEG_1 = new int[]{44100, 48000, 32000};
|
||||
private final static int[] MPEG_2 = new int[]{22050, 24000, 16000};
|
||||
private final static int[] MPEG2_5 = new int[]{11025, 12000, 8000};
|
||||
|
||||
public static int getSampleRate(MPEGVersion version, int value)
|
||||
{
|
||||
switch(version)
|
||||
{
|
||||
case V_1:
|
||||
if(0 <= value && value < MPEG_1.length)
|
||||
{
|
||||
return MPEG_1[value];
|
||||
}
|
||||
break;
|
||||
case V_2:
|
||||
if(0 <= value && value < MPEG_2.length)
|
||||
{
|
||||
return MPEG_2[value];
|
||||
}
|
||||
break;
|
||||
case V_2_5:
|
||||
if(0 <= value && value < MPEG2_5.length)
|
||||
{
|
||||
return MPEG2_5[value];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
import net.sourceforge.lame.mp3.Lame;
|
||||
|
||||
/**
|
||||
* MP3 encoder settings.
|
||||
*
|
||||
* A curated list of LAME audio encoder settings that are optimized for mono-channel voice that is originally
|
||||
* sampled at 8,000 Hz.
|
||||
*
|
||||
* See: https://svn.code.sf.net/p/lame/svn/trunk/lame/doc/html/usage.html
|
||||
*/
|
||||
public enum MP3Setting
|
||||
{
|
||||
CBR_16(false, 16, "CBR - Constant Bit Rate 16 kbps (default)"),
|
||||
CBR_32(false, 32, "CBR - Constant Bit Rate 32 kbps"),
|
||||
ABR_56(true, 56, "ABR - Average Bit Rate 56 kbps"),
|
||||
VBR_5(true, Lame.QUALITY_MIDDLE, "VBR - Variable Bit Rate - Better Quality"),
|
||||
VBR_7(true, Lame.QUALITY_LOW, "VBR - Variable Bit Rate - Good Quality");
|
||||
|
||||
private boolean mVariableBitRate;
|
||||
private int mSetting;
|
||||
private String mLabel;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param variableBitRate flag
|
||||
* @param setting for the entry
|
||||
* @param label to display
|
||||
*/
|
||||
MP3Setting(boolean variableBitRate, int setting, String label)
|
||||
{
|
||||
mVariableBitRate = variableBitRate;
|
||||
mSetting = setting;
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default setting
|
||||
*/
|
||||
public static MP3Setting getDefault()
|
||||
{
|
||||
return MP3Setting.CBR_16;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the entry is Constant Bit Rate (CBR), or Average/Variable Bit Rate (ABR or VBR)
|
||||
* @return true if CBR
|
||||
*/
|
||||
public boolean isVariableBitRate()
|
||||
{
|
||||
return mVariableBitRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* LAME encoder quality value
|
||||
*/
|
||||
public int getSetting()
|
||||
{
|
||||
return mSetting;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,64 +14,57 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
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(MP3_BIT_RATE, CONSTANT_BIT_RATE);
|
||||
private MP3AudioConverter mMP3AudioConverter;
|
||||
private InputAudioFormat mInputAudioFormat;
|
||||
private byte[] mPreviousPartialFrameData;
|
||||
|
||||
/**
|
||||
* Generates MP3 audio silence frames
|
||||
*/
|
||||
public MP3SilenceGenerator()
|
||||
public MP3SilenceGenerator(InputAudioFormat inputAudioFormat, MP3Setting setting)
|
||||
{
|
||||
mInputAudioFormat = inputAudioFormat;
|
||||
mMP3AudioConverter = new MP3AudioConverter(inputAudioFormat, setting, false);
|
||||
}
|
||||
|
||||
public byte[] generate(long duration)
|
||||
/**
|
||||
* Generates silence frames
|
||||
* @param duration_ms in milliseconds
|
||||
* @return
|
||||
*/
|
||||
public List<byte[]> generate(long duration_ms)
|
||||
{
|
||||
int length = (int)(duration * 8); //8000 Hz sample rate
|
||||
double duration_secs = (double)duration_ms / 1000.0;
|
||||
|
||||
//We generate silence at 8000 kHz, because it gets resampled by the silence generator to the target rate
|
||||
int length = (int)(duration_secs * InputAudioFormat.SR_8000.getSampleRate());
|
||||
|
||||
List<float[]> silenceBuffers = new ArrayList<>();
|
||||
silenceBuffers.add(new float[length]);
|
||||
byte[] frameData = mGenerator.convert(silenceBuffers);
|
||||
|
||||
frameData = merge(mPreviousPartialFrameData, frameData);
|
||||
|
||||
if(frameData != null && frameData.length > 0)
|
||||
int added = 0;
|
||||
while(added < length)
|
||||
{
|
||||
if(frameData.length < 144)
|
||||
{
|
||||
mPreviousPartialFrameData = frameData;
|
||||
return null;
|
||||
}
|
||||
else if((frameData.length % 144) == 0)
|
||||
{
|
||||
mPreviousPartialFrameData = null;
|
||||
return frameData;
|
||||
}
|
||||
else
|
||||
{
|
||||
int integralFrameLength = (int)(frameData.length / 144) * 144;
|
||||
mPreviousPartialFrameData = Arrays.copyOfRange(frameData, integralFrameLength, frameData.length);
|
||||
return Arrays.copyOf(frameData, integralFrameLength);
|
||||
}
|
||||
int chunk = Math.min(length - added, 256);
|
||||
silenceBuffers.add(new float[chunk]);
|
||||
added += chunk;
|
||||
}
|
||||
|
||||
return null;
|
||||
return mMP3AudioConverter.convert(silenceBuffers);
|
||||
}
|
||||
|
||||
private static byte[] merge(byte[] a, byte[] b)
|
||||
@@ -100,19 +93,24 @@ public class MP3SilenceGenerator implements ISilenceGenerator
|
||||
{
|
||||
mLog.debug("Starting ...");
|
||||
|
||||
for(long x = 243; x < 500; x ++)
|
||||
MP3SilenceGenerator generator = new MP3SilenceGenerator(InputAudioFormat.SR_44100, MP3Setting.VBR_7);
|
||||
|
||||
int x = 100;
|
||||
|
||||
// for(; x < 1000; x += 10)
|
||||
// {
|
||||
List<byte[]> silenceFrames = generator.generate(x);
|
||||
|
||||
mLog.debug("Generated [" + silenceFrames.size() + "] silence frames");
|
||||
|
||||
for(byte[] silence: silenceFrames)
|
||||
{
|
||||
MP3SilenceGenerator generator = new MP3SilenceGenerator();
|
||||
|
||||
byte[] silence = generator.generate(x);
|
||||
|
||||
if(silence != null && silence.length > 0)
|
||||
{
|
||||
mLog.debug("Silence:" + x + " Length:" + silence.length);
|
||||
MP3FrameInspector.inspect(silence);
|
||||
}
|
||||
mLog.debug("Silence:" + x + " Length:" + silence.length);
|
||||
}
|
||||
|
||||
MP3FrameInspector.inspect(silenceFrames);
|
||||
// }
|
||||
|
||||
mLog.debug("Finished");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
/**
|
||||
* MPEG Layer
|
||||
*
|
||||
* see: http://www.mp3-tech.org/programmer/frame_header.html
|
||||
*/
|
||||
public enum MPEGLayer
|
||||
{
|
||||
RESERVED("LAYER-RESERVED"),
|
||||
LAYER3("LAYER-3"),
|
||||
LAYER2("LAYER-2"),
|
||||
LAYER1("LAYER-1");
|
||||
|
||||
private String mLabel;
|
||||
|
||||
MPEGLayer(String label)
|
||||
{
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
public static MPEGLayer fromValue(int value)
|
||||
{
|
||||
switch(value)
|
||||
{
|
||||
case 1:
|
||||
return LAYER3;
|
||||
case 2:
|
||||
return LAYER2;
|
||||
case 3:
|
||||
return LAYER1;
|
||||
default:
|
||||
case 0:
|
||||
return RESERVED;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
/**
|
||||
* MPEG Version
|
||||
*
|
||||
* see: http://www.mp3-tech.org/programmer/frame_header.html
|
||||
*/
|
||||
public enum MPEGVersion
|
||||
{
|
||||
V_2_5("2.5"),
|
||||
RESERVED("RESERVED"),
|
||||
V_2("2"),
|
||||
V_1("1");
|
||||
|
||||
private String mLabel;
|
||||
|
||||
MPEGVersion(String label)
|
||||
{
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
public static MPEGVersion fromValue(int value)
|
||||
{
|
||||
switch(value)
|
||||
{
|
||||
case 0:
|
||||
return V_2_5;
|
||||
case 2:
|
||||
return V_2;
|
||||
case 3:
|
||||
return V_1;
|
||||
default:
|
||||
return RESERVED;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv;
|
||||
@@ -36,8 +33,6 @@ import io.github.dsheirer.audio.convert.thumbdv.message.response.DecodeSpeechRes
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.ReadyResponse;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetVocoderParameterResponse;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -82,11 +77,10 @@ public class ThumbDv implements AutoCloseable
|
||||
private ScheduledFuture mAudioDecodeProcessorHandle;
|
||||
private LinkedTransferQueue<DecodeSpeechRequest> mDecodeSpeechRequests = new LinkedTransferQueue<>();
|
||||
private AudioProtocol mAudioProtocol;
|
||||
private Listener<ReusableFloatBuffer> mAudioBufferListener;
|
||||
private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("ThumbDv");
|
||||
private Listener<float[]> mAudioBufferListener;
|
||||
private boolean mStarted;
|
||||
|
||||
public ThumbDv(AudioProtocol audioProtocol, Listener<ReusableFloatBuffer> listener)
|
||||
public ThumbDv(AudioProtocol audioProtocol, Listener<float[]> listener)
|
||||
{
|
||||
mAudioProtocol = audioProtocol;
|
||||
mAudioBufferListener = listener;
|
||||
@@ -209,8 +203,7 @@ public class ThumbDv implements AutoCloseable
|
||||
if(message instanceof DecodeSpeechResponse && mAudioBufferListener != null)
|
||||
{
|
||||
float[] samples = ((DecodeSpeechResponse)message).getSamples();
|
||||
ReusableFloatBuffer audioBuffer = mReusableBufferQueue.getBuffer(samples, System.currentTimeMillis());
|
||||
mAudioBufferListener.receive(audioBuffer);
|
||||
mAudioBufferListener.receive(samples);
|
||||
}
|
||||
else if(message instanceof ReadyResponse && mAudioDecodeProcessorHandle == null)
|
||||
{
|
||||
@@ -456,9 +449,8 @@ public class ThumbDv implements AutoCloseable
|
||||
|
||||
mLog.debug("Starting thumb dv thread(s)");
|
||||
|
||||
final Listener<ReusableFloatBuffer> listener = reusableAudioPacket -> {
|
||||
final Listener<float[]> listener = packet -> {
|
||||
mLog.info("Got an audio packet!");
|
||||
reusableAudioPacket.decrementUserCount();
|
||||
};
|
||||
|
||||
try(ThumbDv thumbDv = new ThumbDv(AudioProtocol.P25_PHASE2, listener))
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv.message;
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.invert;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.FloatFIRFilter;
|
||||
import io.github.dsheirer.dsp.filter.Window.WindowType;
|
||||
import io.github.dsheirer.dsp.mixer.IOscillator;
|
||||
import io.github.dsheirer.dsp.mixer.Oscillator;
|
||||
import io.github.dsheirer.dsp.oscillator.IRealOscillator;
|
||||
import io.github.dsheirer.dsp.oscillator.OscillatorFactory;
|
||||
import io.github.dsheirer.dsp.window.WindowType;
|
||||
|
||||
/**
|
||||
* Based on the AudioType applied, produces normal, muted, or audio invert
|
||||
@@ -30,7 +31,7 @@ import io.github.dsheirer.dsp.mixer.Oscillator;
|
||||
public class AudioAdapter
|
||||
{
|
||||
private static int mGain = 5;
|
||||
private IOscillator mSineWaveGenerator;
|
||||
private IRealOscillator mSineWaveGenerator;
|
||||
private FloatFIRFilter mPostInversionFilter;
|
||||
private FloatFIRFilter mAudioHighPassFilter;
|
||||
private AudioType mAudioType = AudioType.NORMAL;
|
||||
@@ -39,7 +40,7 @@ public class AudioAdapter
|
||||
public AudioAdapter( int sampleRate )
|
||||
{
|
||||
mSampleRate = sampleRate;
|
||||
mSineWaveGenerator = new Oscillator( 3000, mSampleRate );
|
||||
mSineWaveGenerator = OscillatorFactory.getRealOscillator(3000, mSampleRate );
|
||||
mAudioHighPassFilter = new FloatFIRFilter(
|
||||
FilterFactory.getHighPass( mSampleRate, 200, 1600, 48,
|
||||
WindowType.HAMMING, true ), mGain );
|
||||
@@ -58,7 +59,7 @@ public class AudioAdapter
|
||||
|
||||
if( inversionFrequency != 0 )
|
||||
{
|
||||
mSineWaveGenerator.setFrequency( inversionFrequency );;
|
||||
mSineWaveGenerator.setFrequency( inversionFrequency );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,9 +90,7 @@ public class AudioAdapter
|
||||
default:
|
||||
retVal = (float)( mPostInversionFilter.get(
|
||||
mAudioHighPassFilter.get( sample ) *
|
||||
mSineWaveGenerator.inphase() ) );
|
||||
|
||||
mSineWaveGenerator.rotate();
|
||||
mSineWaveGenerator.generate(1)[0] ) );
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.invert;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.Filters;
|
||||
import io.github.dsheirer.dsp.filter.FloatFIRFilter;
|
||||
import io.github.dsheirer.dsp.mixer.IOscillator;
|
||||
import io.github.dsheirer.dsp.mixer.Oscillator;
|
||||
import io.github.dsheirer.dsp.oscillator.IRealOscillator;
|
||||
import io.github.dsheirer.dsp.oscillator.OscillatorFactory;
|
||||
import io.github.dsheirer.sample.real.RealSampleListener;
|
||||
|
||||
/**
|
||||
@@ -29,17 +29,33 @@ import io.github.dsheirer.sample.real.RealSampleListener;
|
||||
*/
|
||||
public class AudioInverter implements RealSampleListener
|
||||
{
|
||||
private IOscillator mSineWaveGenerator;
|
||||
private static final float[] LOW_PASS_FILTER = new float[] { -0.0052419787903715655f,
|
||||
-0.0003789909443307852f, 0.0012970137854134386f, 0.004210460544067679f,
|
||||
0.008221266221665309f, 0.012951576579356689f, 0.017794312853913318f,
|
||||
0.021959871931790928f, 0.024583800118549094f, 0.024878144443784132f,
|
||||
0.022296319647647538f, 0.016685495012933472f, 0.008390268267799136f,
|
||||
-0.001718264469387386f, -0.012288389660616603f, -0.02161633130497537f,
|
||||
-0.027882128567957774f, -0.02943377233328899f, -0.025006516193566884f,
|
||||
-0.014040074880106284f, 0.0032217508351258103f, 0.025660031586092195f,
|
||||
0.05136009984280478f, 0.0778336106888181f, 0.10230926486919244f,
|
||||
0.12209961841343948f, 0.13496813439824185f, 0.13943117253534673f,
|
||||
0.13496813439824185f, 0.12209961841343948f, 0.10230926486919244f,
|
||||
0.0778336106888181f, 0.05136009984280478f, 0.025660031586092195f,
|
||||
0.0032217508351258103f, -0.014040074880106284f, -0.025006516193566884f,
|
||||
-0.02943377233328899f, -0.027882128567957774f, -0.02161633130497537f,
|
||||
-0.012288389660616603f, -0.001718264469387386f, 0.008390268267799136f,
|
||||
0.016685495012933472f, 0.022296319647647538f, 0.024878144443784132f,
|
||||
0.024583800118549094f, 0.021959871931790928f, 0.017794312853913318f,
|
||||
0.012951576579356689f, 0.008221266221665309f, 0.004210460544067679f,
|
||||
0.0012970137854134386f, -0.0003789909443307852f, -0.0052419787903715655f };
|
||||
private IRealOscillator mSineWaveGenerator;
|
||||
private FloatFIRFilter mPostInversionLowPassFilter;
|
||||
private RealSampleListener mListener;
|
||||
|
||||
public AudioInverter( int inversionFrequency, int sampleRate )
|
||||
{
|
||||
mSineWaveGenerator = new Oscillator( inversionFrequency, sampleRate );
|
||||
|
||||
mPostInversionLowPassFilter = new FloatFIRFilter(
|
||||
Filters.FIRLP_55TAP_48000FS_3000FC.getCoefficients(), 1.04f );
|
||||
|
||||
mSineWaveGenerator = OscillatorFactory.getRealOscillator( inversionFrequency, sampleRate );
|
||||
mPostInversionLowPassFilter = new FloatFIRFilter(LOW_PASS_FILTER, 1.04f );
|
||||
mPostInversionLowPassFilter.setListener( new FilteredSampleProcessor() );
|
||||
}
|
||||
|
||||
@@ -60,10 +76,7 @@ public class AudioInverter implements RealSampleListener
|
||||
//the low pass filter
|
||||
if( mPostInversionLowPassFilter != null )
|
||||
{
|
||||
mPostInversionLowPassFilter.receive(
|
||||
sample * mSineWaveGenerator.inphase() );
|
||||
|
||||
mSineWaveGenerator.rotate();
|
||||
mPostInversionLowPassFilter.receive(sample * mSineWaveGenerator.generate(1)[0] );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,7 +14,7 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.playback;
|
||||
|
||||
@@ -30,7 +29,6 @@ import io.github.dsheirer.icon.IconModel;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.settings.SettingsManager;
|
||||
import io.github.dsheirer.source.SourceManager;
|
||||
import io.github.dsheirer.source.mixer.MixerChannelConfiguration;
|
||||
import jiconfont.icons.font_awesome.FontAwesome;
|
||||
import jiconfont.swing.IconFontSwing;
|
||||
@@ -69,7 +67,6 @@ public class AudioPanel extends JPanel implements Listener<AudioEvent>
|
||||
|
||||
private IconModel mIconModel;
|
||||
private SettingsManager mSettingsManager;
|
||||
private SourceManager mSourceManager;
|
||||
private IAudioController mController;
|
||||
private AliasModel mAliasModel;
|
||||
private UserPreferences mUserPreferences;
|
||||
@@ -78,11 +75,10 @@ public class AudioPanel extends JPanel implements Listener<AudioEvent>
|
||||
private AudioChannelsPanel mAudioChannelsPanel;
|
||||
|
||||
public AudioPanel(IconModel iconModel, UserPreferences userPreferences, SettingsManager settingsManager,
|
||||
SourceManager sourceManager, IAudioController controller, AliasModel aliasModel)
|
||||
IAudioController controller, AliasModel aliasModel)
|
||||
{
|
||||
mIconModel = iconModel;
|
||||
mSettingsManager = settingsManager;
|
||||
mSourceManager = sourceManager;
|
||||
mController = controller;
|
||||
mAliasModel = aliasModel;
|
||||
mUserPreferences = userPreferences;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,8 +14,8 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.playback;
|
||||
|
||||
import io.github.dsheirer.audio.AudioFormats;
|
||||
@@ -36,7 +36,7 @@ public class MonoAudioOutput extends AudioOutput
|
||||
|
||||
public MonoAudioOutput(Mixer mixer, UserPreferences userPreferences)
|
||||
{
|
||||
super(mixer, MixerChannel.MONO, AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO,
|
||||
super(mixer, MixerChannel.MONO, AudioFormats.PCM_SIGNED_8000_HZ_16_BIT_MONO,
|
||||
AudioFormats.MONO_SOURCE_DATALINE_INFO, BUFFER_SIZE, userPreferences);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,8 +14,8 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.playback;
|
||||
|
||||
import io.github.dsheirer.audio.AudioFormats;
|
||||
@@ -36,7 +36,7 @@ public class StereoAudioOutput extends AudioOutput
|
||||
|
||||
public StereoAudioOutput(Mixer mixer, MixerChannel channel, UserPreferences userPreferences)
|
||||
{
|
||||
super(mixer, channel, AudioFormats.PCM_SIGNED_8KHZ_16BITS_STEREO, AudioFormats.STEREO_SOURCE_DATALINE_INFO,
|
||||
super(mixer, channel, AudioFormats.PCM_SIGNED_8000_HZ_16BITS_STEREO, AudioFormats.STEREO_SOURCE_DATALINE_INFO,
|
||||
BUFFER_SIZE, userPreferences);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ public class BinaryMessage extends BitSet
|
||||
* Logical (ie constructed) size of this bitset, despite the actual size of
|
||||
* the super bitset that this class is based on
|
||||
*/
|
||||
private int mSize = 0;
|
||||
private int mSize;
|
||||
|
||||
/**
|
||||
* Pointer to the next fill index location, when adding bits to this bitset
|
||||
@@ -43,21 +43,21 @@ public class BinaryMessage extends BitSet
|
||||
*/
|
||||
private int mPointer = 0;
|
||||
|
||||
/**
|
||||
* Bitset that buffers bits added one at a time, up to the size of the this
|
||||
* bitset.
|
||||
*
|
||||
* Note: the super class bitset behind this class may have a size larger
|
||||
* that the size parameter specified.
|
||||
* @param size
|
||||
*/
|
||||
|
||||
/**
|
||||
* Used for temporary storage of CRC check results when we're passing this
|
||||
* message to an EDAC function.
|
||||
*/
|
||||
private CRC mCRC;
|
||||
|
||||
/**
|
||||
* Constructs a bitset that buffers bits added one at a time, up to the size
|
||||
* of the this bitset.
|
||||
* <p>
|
||||
* Note: the super class bitset behind this class may have a size larger
|
||||
* that the size parameter specified.
|
||||
*
|
||||
* @param size of constructed bitset
|
||||
*/
|
||||
public BinaryMessage(int size)
|
||||
{
|
||||
super(size);
|
||||
@@ -214,9 +214,7 @@ public class BinaryMessage extends BitSet
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a (new) copy of this bitsetbuffer
|
||||
*
|
||||
* @return
|
||||
* @return a (new) copy of this binary message
|
||||
*/
|
||||
public BinaryMessage copy()
|
||||
{
|
||||
|
||||
153
src/main/java/io/github/dsheirer/buffer/ByteNativeBuffer.java
Normal file
153
src/main/java/io/github/dsheirer/buffer/ByteNativeBuffer.java
Normal file
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.buffer;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Native buffer sample array wrapper class that provides access to a stream of either interleaved or
|
||||
* non-interleaved complex sample buffers converted from the raw byte sample array.
|
||||
*/
|
||||
public class ByteNativeBuffer implements INativeBuffer
|
||||
{
|
||||
private static final int FRAGMENT_SIZE = 2048;
|
||||
private final static float[] LOOKUP_VALUES;
|
||||
private float mAverageDc;
|
||||
|
||||
//Creates a static lookup table that converts the 8-bit valued range from 0 - 255 into scaled float values
|
||||
//of -1.0 to 0 to 1.0
|
||||
static
|
||||
{
|
||||
LOOKUP_VALUES = new float[256];
|
||||
|
||||
for(int x = 0; x < 256; x++)
|
||||
{
|
||||
LOOKUP_VALUES[x] = ((float)x - 127.5f) / 128.0f;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] mSamples;
|
||||
private long mTimestamp;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param samples to process
|
||||
* @param timestamp of the samples
|
||||
* @param averageDc measured from sample stream
|
||||
*/
|
||||
public ByteNativeBuffer(byte[] samples, long timestamp, float averageDc)
|
||||
{
|
||||
//Ensure we're an even multiple of the fragment size. Typically, this will be 64k or 128k
|
||||
if(samples.length % FRAGMENT_SIZE != 0)
|
||||
{
|
||||
throw new IllegalArgumentException("Samples byte[] length [" + samples.length + "] must be an even multiple of " + FRAGMENT_SIZE);
|
||||
}
|
||||
|
||||
mSamples = samples;
|
||||
mTimestamp = timestamp;
|
||||
mAverageDc = averageDc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sampleCount()
|
||||
{
|
||||
return mSamples.length / 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimestamp()
|
||||
{
|
||||
return mTimestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<ComplexSamples> iterator()
|
||||
{
|
||||
return new ComplexSamplesIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<InterleavedComplexSamples> iteratorInterleaved()
|
||||
{
|
||||
return new InterleavedComplexSamplesIterator();
|
||||
}
|
||||
|
||||
private class ComplexSamplesIterator implements Iterator<ComplexSamples>
|
||||
{
|
||||
private int mSamplesPointer = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
return mSamplesPointer < mSamples.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComplexSamples next()
|
||||
{
|
||||
float[] i = new float[FRAGMENT_SIZE];
|
||||
float[] q = new float[FRAGMENT_SIZE];
|
||||
int samplesOffset = mSamplesPointer;
|
||||
|
||||
for(int pointer = 0; pointer < i.length; pointer++)
|
||||
{
|
||||
i[pointer] = LOOKUP_VALUES[(0xFF & mSamples[samplesOffset++])] - mAverageDc;
|
||||
q[pointer] = LOOKUP_VALUES[(0xFF & mSamples[samplesOffset++])] - mAverageDc;
|
||||
}
|
||||
|
||||
mSamplesPointer = samplesOffset;
|
||||
|
||||
return new ComplexSamples(i, q);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterator of interleaved complex samples over the native byte buffer array.
|
||||
*/
|
||||
private class InterleavedComplexSamplesIterator implements Iterator<InterleavedComplexSamples>
|
||||
{
|
||||
private int mSamplesPointer = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
return mSamplesPointer < mSamples.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterleavedComplexSamples next()
|
||||
{
|
||||
float[] converted = new float[FRAGMENT_SIZE * 2];
|
||||
|
||||
int samplesPointer = mSamplesPointer;
|
||||
|
||||
for(int pointer = 0; pointer < converted.length; pointer++)
|
||||
{
|
||||
converted[pointer] = LOOKUP_VALUES[(0xFF & mSamples[samplesPointer++])] - mAverageDc;
|
||||
}
|
||||
|
||||
mSamplesPointer = samplesPointer;
|
||||
|
||||
return new InterleavedComplexSamples(converted, mTimestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.buffer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Implements a factory for creating ByteNativeBuffer instances
|
||||
*/
|
||||
public class ByteNativeBufferFactory implements INativeBufferFactory
|
||||
{
|
||||
private DcCorrectionManager mDcCorrectionManager = new DcCorrectionManager();
|
||||
|
||||
@Override
|
||||
public INativeBuffer getBuffer(ByteBuffer samples, long timestamp)
|
||||
{
|
||||
byte[] copy = new byte[samples.capacity()];
|
||||
samples.get(copy);
|
||||
|
||||
if(mDcCorrectionManager.shouldCalculateDc())
|
||||
{
|
||||
calculateDc(copy);
|
||||
}
|
||||
|
||||
return new ByteNativeBuffer(copy, timestamp, mDcCorrectionManager.getAverageDc());
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the average DC in the sample stream so that it can be subtracted from the samples when the
|
||||
* native buffer is used.
|
||||
* @param samples containing DC offset
|
||||
*/
|
||||
private void calculateDc(byte[] samples)
|
||||
{
|
||||
float dcAccumulator = 0;
|
||||
|
||||
for(byte sample: samples)
|
||||
{
|
||||
dcAccumulator += (sample & 0xFF);
|
||||
}
|
||||
|
||||
dcAccumulator /= samples.length;
|
||||
dcAccumulator -= 127.5f;
|
||||
dcAccumulator /= 128.0f;
|
||||
mDcCorrectionManager.adjust(dcAccumulator);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,21 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.buffer;
|
||||
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
|
||||
114
src/main/java/io/github/dsheirer/buffer/DcCorrectionManager.java
Normal file
114
src/main/java/io/github/dsheirer/buffer/DcCorrectionManager.java
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.buffer;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Provides DC offset correction value and manages calculation processing interval.
|
||||
*/
|
||||
public class DcCorrectionManager
|
||||
{
|
||||
// private static Logger mLog = LoggerFactory.getLogger(DcCorrectionManager.class);
|
||||
// private DecimalFormat mDecimalFormat = new DecimalFormat("0.00000");
|
||||
|
||||
/**
|
||||
* Time (delay) interval for running DC removal calculations
|
||||
*/
|
||||
private static final long DC_PROCESSING_INTERVAL = TimeUnit.SECONDS.toMillis(50);
|
||||
|
||||
/**
|
||||
* Number of buffers to process per DC calculation processing interval.
|
||||
*/
|
||||
private static final int DC_CALCULATIONS_PER_INTERVAL = 5;
|
||||
|
||||
/**
|
||||
* DC offset processing residual threshold for ending DC processing interval
|
||||
*/
|
||||
private static final float TARGET_DC_OFFSET_REMAINING = 0.0001f;
|
||||
|
||||
/**
|
||||
* Number of DC calculations remaining in the current interval
|
||||
*/
|
||||
private int mDcCalculationsRemaining = DC_CALCULATIONS_PER_INTERVAL; //Initial value for coarse correction
|
||||
|
||||
/**
|
||||
* Last time DC offset calculation interval ran
|
||||
*/
|
||||
private long mLastDcCalculationTimestamp = 0;
|
||||
|
||||
/**
|
||||
* DC offset calculation gain.
|
||||
*/
|
||||
private static final float DC_FILTER_GAIN = 0.05f;
|
||||
|
||||
/**
|
||||
* Current DC offset correction value
|
||||
*/
|
||||
private float mAverageDc = 0.0f;
|
||||
|
||||
public float getAverageDc()
|
||||
{
|
||||
return mAverageDc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the average DC based on the current calculated DC offset present in a sample buffer.
|
||||
* @param calculatedDc for the current sample buffer
|
||||
*/
|
||||
public void adjust(float calculatedDc)
|
||||
{
|
||||
//Calculate the average scaled DC offset so that it can be applied in the native buffer's converted samples
|
||||
float residualOffsetToRemove = calculatedDc - mAverageDc;
|
||||
|
||||
mAverageDc += (residualOffsetToRemove * DC_FILTER_GAIN);
|
||||
|
||||
if(Math.abs(residualOffsetToRemove) < TARGET_DC_OFFSET_REMAINING)
|
||||
{
|
||||
mDcCalculationsRemaining--;
|
||||
}
|
||||
//
|
||||
// mLog.info("DC: " + mDecimalFormat.format(mAverageDc) +
|
||||
// " RES:" + mDecimalFormat.format(residualOffsetToRemove) +
|
||||
// " REM:" + mDcCalculationsRemaining);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if a DC offset calculation for a buffer should be performed.
|
||||
*/
|
||||
public boolean shouldCalculateDc()
|
||||
{
|
||||
if(System.currentTimeMillis() > (mLastDcCalculationTimestamp + DC_PROCESSING_INTERVAL))
|
||||
{
|
||||
if(mDcCalculationsRemaining > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Reset the number of buffers to calculate and the timestamp
|
||||
mDcCalculationsRemaining = DC_CALCULATIONS_PER_INTERVAL;
|
||||
mLastDcCalculationTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,21 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.buffer;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -25,13 +26,12 @@ import java.util.Arrays;
|
||||
* fills buffer with 0-valued samples
|
||||
*
|
||||
* Can be used as a delay-type buffer, to delay samples by the 'size' amount
|
||||
*
|
||||
* @author denny
|
||||
*/
|
||||
public class FloatCircularBuffer
|
||||
{
|
||||
float[] mBuffer;
|
||||
int mBufferPointer = 0;
|
||||
private float[] mBuffer;
|
||||
private int mBufferPointer = 0;
|
||||
private float mOldestSample;
|
||||
|
||||
/**
|
||||
* Creates a circular double buffer of the specified size and all entries filled with the specified initial value.
|
||||
@@ -61,25 +61,27 @@ public class FloatCircularBuffer
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the new value into the buffer and returns the oldest buffer value that it replaced
|
||||
* Gets the current buffer value and overwrites that value position in the buffer with the new value.
|
||||
*
|
||||
* @param newValue to add to the buffer
|
||||
* @return oldest value that was overwritten by the new value
|
||||
*/
|
||||
public float get(float newValue)
|
||||
public float getAndPut(float newValue)
|
||||
{
|
||||
float oldestSample = mBuffer[mBufferPointer];
|
||||
mOldestSample = mBuffer[mBufferPointer];
|
||||
put(newValue);
|
||||
return mOldestSample;
|
||||
}
|
||||
|
||||
mBuffer[mBufferPointer] = newValue;
|
||||
|
||||
mBufferPointer++;
|
||||
|
||||
if(mBufferPointer >= mBuffer.length)
|
||||
{
|
||||
mBufferPointer = 0;
|
||||
}
|
||||
|
||||
return oldestSample;
|
||||
/**
|
||||
* Puts the value into the buffer, updates the pointer and returns the buffer value at the pointer position.
|
||||
* @param sample to add
|
||||
* @return next pointed sample.
|
||||
*/
|
||||
public float putAndGet(float sample)
|
||||
{
|
||||
put(sample);
|
||||
return mBuffer[mBufferPointer];
|
||||
}
|
||||
|
||||
public float[] getAll()
|
||||
@@ -109,12 +111,7 @@ public class FloatCircularBuffer
|
||||
public void put(float newValue)
|
||||
{
|
||||
mBuffer[mBufferPointer] = newValue;
|
||||
|
||||
mBufferPointer++;
|
||||
|
||||
if(mBufferPointer >= mBuffer.length)
|
||||
{
|
||||
mBufferPointer = 0;
|
||||
}
|
||||
mBufferPointer %= mBuffer.length;
|
||||
}
|
||||
}
|
||||
|
||||
145
src/main/java/io/github/dsheirer/buffer/FloatNativeBuffer.java
Normal file
145
src/main/java/io/github/dsheirer/buffer/FloatNativeBuffer.java
Normal file
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.buffer;
|
||||
|
||||
import io.github.dsheirer.sample.SampleUtils;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Simple native buffer implementation that simply wraps a single, existing, non-native sample buffer.
|
||||
*/
|
||||
public class FloatNativeBuffer implements INativeBuffer
|
||||
{
|
||||
private float[] mInterleavedComplexSamples;
|
||||
private long mTimestamp;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param complexSamples to wrap
|
||||
* @param timestamp for the buffer
|
||||
*/
|
||||
public FloatNativeBuffer(float[] complexSamples, long timestamp)
|
||||
{
|
||||
mInterleavedComplexSamples = complexSamples;
|
||||
mTimestamp = timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param complexSamples to wrap
|
||||
*/
|
||||
public FloatNativeBuffer(float[] complexSamples)
|
||||
{
|
||||
this(complexSamples, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param samples to wrap
|
||||
* @param timestamp for the buffer
|
||||
*/
|
||||
public FloatNativeBuffer(ComplexSamples samples, long timestamp)
|
||||
{
|
||||
this(SampleUtils.interleave(samples), timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param samples to wrap
|
||||
*/
|
||||
public FloatNativeBuffer(InterleavedComplexSamples samples)
|
||||
{
|
||||
this(samples.samples(), samples.timestamp());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<ComplexSamples> iterator()
|
||||
{
|
||||
return new ComplexSamplesIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<InterleavedComplexSamples> iteratorInterleaved()
|
||||
{
|
||||
return new InterleavedComplexSamplesIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sampleCount()
|
||||
{
|
||||
return mInterleavedComplexSamples.length / 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimestamp()
|
||||
{
|
||||
return mTimestamp;
|
||||
}
|
||||
|
||||
private class ComplexSamplesIterator implements Iterator<ComplexSamples>
|
||||
{
|
||||
private boolean mEmpty;
|
||||
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
return !mEmpty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComplexSamples next()
|
||||
{
|
||||
if(mEmpty)
|
||||
{
|
||||
throw new IllegalStateException("No more samples");
|
||||
}
|
||||
|
||||
mEmpty = true;
|
||||
return SampleUtils.deinterleave(mInterleavedComplexSamples);
|
||||
}
|
||||
}
|
||||
|
||||
private class InterleavedComplexSamplesIterator implements Iterator<InterleavedComplexSamples>
|
||||
{
|
||||
private boolean mEmpty;
|
||||
|
||||
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
return !mEmpty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterleavedComplexSamples next()
|
||||
{
|
||||
if(mEmpty)
|
||||
{
|
||||
throw new IllegalStateException("No more samples");
|
||||
}
|
||||
|
||||
mEmpty = true;
|
||||
return new InterleavedComplexSamples(mInterleavedComplexSamples, mTimestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
53
src/main/java/io/github/dsheirer/buffer/INativeBuffer.java
Normal file
53
src/main/java/io/github/dsheirer/buffer/INativeBuffer.java
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.buffer;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* A buffer of raw samples copied from native (ie outside of the JVM) memory into the JVM heap and
|
||||
* convertible to an iterable stream of interleaved or non-interleaved sample instances.
|
||||
*/
|
||||
public interface INativeBuffer
|
||||
{
|
||||
/**
|
||||
* Provides an iterator that converts the raw samples to complex samples format with separate (non-interleaved) I and Q sample arrays.
|
||||
*/
|
||||
Iterator<ComplexSamples> iterator();
|
||||
|
||||
/**
|
||||
* Provides an iterator that converts the raw samples to interleaved complex samples format.
|
||||
*/
|
||||
Iterator<InterleavedComplexSamples> iteratorInterleaved();
|
||||
|
||||
/**
|
||||
* Total number of complex samples for this buffer
|
||||
*/
|
||||
int sampleCount();
|
||||
|
||||
/**
|
||||
* Timestamp for this buffer
|
||||
* @return millis since epoch
|
||||
*/
|
||||
long getTimestamp();
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.buffer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Factory for creating native buffer wrapper instances
|
||||
*/
|
||||
public interface INativeBufferFactory
|
||||
{
|
||||
/**
|
||||
* Create a native buffer implementation that wraps the samples argument.
|
||||
*
|
||||
* Note: implementations of this factory should treat the samples argument as read-only and only make data copies.
|
||||
*
|
||||
* @param samples byte array copied from native memory
|
||||
* @param timestamp of the samples
|
||||
* @return instance
|
||||
*/
|
||||
INativeBuffer getBuffer(ByteBuffer samples, long timestamp);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.buffer;
|
||||
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
|
||||
/**
|
||||
* Interface for a provider of interleaved complex samples
|
||||
*/
|
||||
public interface INativeBufferProvider
|
||||
{
|
||||
/**
|
||||
* Adds the listener to receive buffers
|
||||
*/
|
||||
void addBufferListener(Listener<INativeBuffer> listener);
|
||||
|
||||
/**
|
||||
* Removes the listener from receiving buffers
|
||||
*/
|
||||
void removeBufferListener(Listener<INativeBuffer> listener);
|
||||
|
||||
/**
|
||||
* Indicates if there are any buffer listeners registered
|
||||
*/
|
||||
boolean hasBufferListeners();
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.buffer;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Poison pill implementation for use with Dispatcher.
|
||||
*/
|
||||
public class NativeBufferPoisonPill implements INativeBuffer
|
||||
{
|
||||
@Override
|
||||
public Iterator<ComplexSamples> iterator()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<InterleavedComplexSamples> iteratorInterleaved()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sampleCount()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimestamp()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,24 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.sample.buffer;
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.buffer;
|
||||
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -22,7 +26,7 @@ import org.slf4j.LoggerFactory;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
|
||||
/**
|
||||
* Delay buffer/queue for reusable complex buffers with support for pre-loading new buffer listeners (ie channels)
|
||||
* Delay buffer/queue with support for pre-loading new listeners (ie channels)
|
||||
* with delayed sample buffers and broadcasting of incoming sample buffers to a list of listeners.
|
||||
*
|
||||
* This class is designed to compensate/account for any delays in processing of a control channel and subsequent
|
||||
@@ -39,15 +43,13 @@ import java.util.concurrent.LinkedTransferQueue;
|
||||
* so as not to delay the stream of sample buffers. Channel listeners are expected to implement buffer queue processing
|
||||
* on another thread.
|
||||
*/
|
||||
public class ReusableComplexDelayBuffer implements Listener<ReusableComplexBuffer>
|
||||
public class NativeSampleDelayBuffer implements Listener<INativeBuffer>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ReusableComplexDelayBuffer.class);
|
||||
private final static Logger mLog = LoggerFactory.getLogger(NativeSampleDelayBuffer.class);
|
||||
|
||||
private ReusableBufferBroadcaster<ReusableComplexBuffer> mBroadcaster = new ReusableBufferBroadcaster<>();
|
||||
private LinkedTransferQueue<ActionRequest> mActionRequestQueue = new LinkedTransferQueue<>();
|
||||
private ActionRequest mActionRequest;
|
||||
|
||||
private ReusableComplexBuffer[] mDelayBuffer;
|
||||
private Broadcaster<INativeBuffer> mBroadcaster = new Broadcaster<>();
|
||||
private LinkedTransferQueue<ActionRequest> mActionQueue = new LinkedTransferQueue<>();
|
||||
private INativeBuffer[] mDelayBuffer;
|
||||
private int mDelayBufferPointer = 0;
|
||||
private long mBufferDuration;
|
||||
|
||||
@@ -57,9 +59,9 @@ public class ReusableComplexDelayBuffer implements Listener<ReusableComplexBuffe
|
||||
* @param size of the delay queue
|
||||
* @param bufferDuration in milliseconds for each complex buffer processed by this instance
|
||||
*/
|
||||
public ReusableComplexDelayBuffer(int size, long bufferDuration)
|
||||
public NativeSampleDelayBuffer(int size, long bufferDuration)
|
||||
{
|
||||
mDelayBuffer = new ReusableComplexBuffer[size];
|
||||
mDelayBuffer = new INativeBuffer[size];
|
||||
mBufferDuration = bufferDuration;
|
||||
}
|
||||
|
||||
@@ -69,12 +71,13 @@ public class ReusableComplexDelayBuffer implements Listener<ReusableComplexBuffe
|
||||
public void dispose()
|
||||
{
|
||||
clearBuffer();
|
||||
mBroadcaster.clear();
|
||||
}
|
||||
|
||||
public void clear()
|
||||
{
|
||||
//Submit a clear buffer request to be processed upon the next buffer that arrives
|
||||
mActionRequestQueue.offer(new ActionRequest());
|
||||
mActionQueue.offer(new ActionRequest());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,59 +87,42 @@ public class ReusableComplexDelayBuffer implements Listener<ReusableComplexBuffe
|
||||
{
|
||||
for(int x = 0; x < mDelayBuffer.length; x++)
|
||||
{
|
||||
if(mDelayBuffer[x] != null)
|
||||
{
|
||||
mDelayBuffer[x].decrementUserCount();
|
||||
mDelayBuffer[x] = null;
|
||||
}
|
||||
mDelayBuffer[x] = null;
|
||||
}
|
||||
|
||||
mDelayBufferPointer = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void receive(ReusableComplexBuffer reusableComplexBuffer)
|
||||
public synchronized void receive(INativeBuffer samples)
|
||||
{
|
||||
mActionRequest = mActionRequestQueue.poll();
|
||||
ActionRequest actionRequest = mActionQueue.poll();
|
||||
|
||||
while(mActionRequest != null)
|
||||
while(actionRequest != null)
|
||||
{
|
||||
switch(mActionRequest.getAction())
|
||||
switch(actionRequest.getAction())
|
||||
{
|
||||
case ADD_USER:
|
||||
processNewListener(mActionRequest);
|
||||
case ADD_LISTENER:
|
||||
processNewListener(actionRequest);
|
||||
break;
|
||||
case REMOVE_USER:
|
||||
mBroadcaster.removeListener(mActionRequest.getListener());
|
||||
case REMOVE_LISTENER:
|
||||
mBroadcaster.removeListener(actionRequest.getListener());
|
||||
break;
|
||||
case CLEAR_BUFFER:
|
||||
clearBuffer();
|
||||
break;
|
||||
}
|
||||
|
||||
mActionRequest = mActionRequestQueue.poll();
|
||||
actionRequest = mActionQueue.poll();
|
||||
}
|
||||
|
||||
//Increment user count before we hand it to the broadcaster so that we can keep it as a copy for the
|
||||
//delay buffer
|
||||
reusableComplexBuffer.incrementUserCount();
|
||||
|
||||
mBroadcaster.receive(reusableComplexBuffer);
|
||||
|
||||
//Decrement the user count on the oldest buffer before we replace it in the delay queue
|
||||
if(mDelayBuffer[mDelayBufferPointer] != null)
|
||||
{
|
||||
mDelayBuffer[mDelayBufferPointer].decrementUserCount();
|
||||
}
|
||||
mBroadcaster.receive(samples);
|
||||
|
||||
//Store the new buffer in the delay queue and increment the pointer
|
||||
mDelayBuffer[mDelayBufferPointer++] = reusableComplexBuffer;
|
||||
mDelayBuffer[mDelayBufferPointer++] = samples;
|
||||
|
||||
//Wrap the delay buffer pointer as needed
|
||||
if(mDelayBufferPointer >= mDelayBuffer.length)
|
||||
{
|
||||
mDelayBufferPointer = 0;
|
||||
}
|
||||
mDelayBufferPointer %= mDelayBuffer.length;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -153,23 +139,18 @@ public class ReusableComplexDelayBuffer implements Listener<ReusableComplexBuffe
|
||||
*/
|
||||
private void processNewListener(ActionRequest listenerToAdd)
|
||||
{
|
||||
ReusableComplexBuffer bufferToEvaluate;
|
||||
INativeBuffer toEvaluate;
|
||||
|
||||
int pointer = mDelayBufferPointer;
|
||||
|
||||
int preloadedBufferCount = 0;
|
||||
|
||||
for(int x = 0; x < mDelayBuffer.length; x++)
|
||||
{
|
||||
bufferToEvaluate = mDelayBuffer[pointer];
|
||||
toEvaluate = mDelayBuffer[pointer];
|
||||
|
||||
if(bufferToEvaluate != null &&
|
||||
(bufferToEvaluate.getTimestamp() >= listenerToAdd.getTimestamp() ||
|
||||
bufferToEvaluate.getTimestamp() + mBufferDuration >= listenerToAdd.getTimestamp()))
|
||||
if(toEvaluate != null &&
|
||||
((toEvaluate.getTimestamp() + mBufferDuration) >= listenerToAdd.getTimestamp()))
|
||||
{
|
||||
bufferToEvaluate.incrementUserCount();
|
||||
listenerToAdd.getListener().receive(bufferToEvaluate);
|
||||
preloadedBufferCount++;
|
||||
listenerToAdd.getListener().receive(toEvaluate);
|
||||
}
|
||||
|
||||
pointer++;
|
||||
@@ -190,28 +171,28 @@ public class ReusableComplexDelayBuffer implements Listener<ReusableComplexBuffe
|
||||
* @param listener to add
|
||||
* @param timestamp of the oldest sample buffers to preload to the listener
|
||||
*/
|
||||
public void addListener(Listener<ReusableComplexBuffer> listener, long timestamp)
|
||||
public void addListener(Listener<INativeBuffer> listener, long timestamp)
|
||||
{
|
||||
mActionRequestQueue.add(new ActionRequest(listener, timestamp));
|
||||
mActionQueue.add(new ActionRequest(listener, timestamp));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener from receiving sample buffers
|
||||
*/
|
||||
public void removeListener(Listener<ReusableComplexBuffer> listener)
|
||||
public void removeListener(Listener<INativeBuffer> listener)
|
||||
{
|
||||
mActionRequestQueue.add(new ActionRequest(listener));
|
||||
mActionQueue.add(new ActionRequest(listener));
|
||||
}
|
||||
|
||||
private enum Action{ADD_USER, REMOVE_USER, CLEAR_BUFFER};
|
||||
private enum Action{ADD_LISTENER, REMOVE_LISTENER, CLEAR_BUFFER};
|
||||
|
||||
/**
|
||||
* Actions that must be completed on the incoming sample stream thread.
|
||||
*/
|
||||
public class ActionRequest
|
||||
public class ActionRequest<T extends INativeBuffer>
|
||||
{
|
||||
private Action mAction;
|
||||
private Listener<ReusableComplexBuffer> mListener;
|
||||
private Listener<T> mListener;
|
||||
private long mTimestamp;
|
||||
|
||||
/**
|
||||
@@ -219,9 +200,9 @@ public class ReusableComplexDelayBuffer implements Listener<ReusableComplexBuffe
|
||||
* @param listener to add
|
||||
* @param timestamp for the preloading buffers to the listener from the delay queue
|
||||
*/
|
||||
public ActionRequest(Listener<ReusableComplexBuffer> listener, long timestamp)
|
||||
public ActionRequest(Listener<T> listener, long timestamp)
|
||||
{
|
||||
mAction = Action.ADD_USER;
|
||||
mAction = Action.ADD_LISTENER;
|
||||
mListener = listener;
|
||||
mTimestamp = timestamp;
|
||||
}
|
||||
@@ -230,9 +211,9 @@ public class ReusableComplexDelayBuffer implements Listener<ReusableComplexBuffe
|
||||
* Creates a remove listener request
|
||||
* @param listener
|
||||
*/
|
||||
public ActionRequest(Listener<ReusableComplexBuffer> listener)
|
||||
public ActionRequest(Listener<T> listener)
|
||||
{
|
||||
mAction = Action.REMOVE_USER;
|
||||
mAction = Action.REMOVE_LISTENER;
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@@ -252,7 +233,7 @@ public class ReusableComplexDelayBuffer implements Listener<ReusableComplexBuffe
|
||||
/**
|
||||
* Listener to be added/removed
|
||||
*/
|
||||
public Listener<ReusableComplexBuffer> getListener()
|
||||
public Listener<T> getListener()
|
||||
{
|
||||
return mListener;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,8 +14,8 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.buffer;
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.buffer;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Native buffer sample array wrapper class that provides access to a stream of either interleaved or
|
||||
* non-interleaved complex sample buffers converted from the raw byte sample array.
|
||||
*/
|
||||
public class SignedByteNativeBuffer implements INativeBuffer
|
||||
{
|
||||
private static final int FRAGMENT_SIZE = 2048;
|
||||
private final static float[] LOOKUP_VALUES;
|
||||
|
||||
//Creates a static lookup table that converts the signed byte values as:
|
||||
// Index 0 - 127: 0.0f to 1.0f
|
||||
// Index 128 - 255: -1.0f to -0.0f
|
||||
static
|
||||
{
|
||||
LOOKUP_VALUES = new float[256];
|
||||
|
||||
for(int x = 0; x < 256; x++)
|
||||
{
|
||||
LOOKUP_VALUES[x] = (float)((byte)x) / 128.0f;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] mSamples;
|
||||
private long mTimestamp;
|
||||
private float mIAverageDc;
|
||||
private float mQAverageDc;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param samples to process
|
||||
* @param timestamp of the samples
|
||||
* @param iAverageDc of the sample stream
|
||||
* @param qAverageDc of the sample stream
|
||||
*/
|
||||
public SignedByteNativeBuffer(byte[] samples, long timestamp, float iAverageDc, float qAverageDc)
|
||||
{
|
||||
//Ensure we're an even multiple of the fragment size. Typically, this will be 64k or 128k
|
||||
if(samples.length % FRAGMENT_SIZE != 0)
|
||||
{
|
||||
throw new IllegalArgumentException("Samples byte[] length [" + samples.length + "] must be an even multiple of " + FRAGMENT_SIZE);
|
||||
}
|
||||
|
||||
mSamples = samples;
|
||||
mTimestamp = timestamp;
|
||||
mIAverageDc = iAverageDc;
|
||||
mQAverageDc = qAverageDc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimestamp()
|
||||
{
|
||||
return mTimestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sampleCount()
|
||||
{
|
||||
return mSamples.length / 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<ComplexSamples> iterator()
|
||||
{
|
||||
return new ComplexSamplesIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<InterleavedComplexSamples> iteratorInterleaved()
|
||||
{
|
||||
return new InterleavedComplexSamplesIterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterator of complex samples over the native byte buffer array
|
||||
*/
|
||||
private class ComplexSamplesIterator implements Iterator<ComplexSamples>
|
||||
{
|
||||
private int mSamplesPointer = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
return mSamplesPointer < mSamples.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComplexSamples next()
|
||||
{
|
||||
float[] i = new float[FRAGMENT_SIZE];
|
||||
float[] q = new float[FRAGMENT_SIZE];
|
||||
int samplesOffset = mSamplesPointer;
|
||||
|
||||
for(int pointer = 0; pointer < i.length; pointer++)
|
||||
{
|
||||
i[pointer] = LOOKUP_VALUES[(0xFF & mSamples[samplesOffset++])] - mIAverageDc;
|
||||
q[pointer] = LOOKUP_VALUES[(0xFF & mSamples[samplesOffset++])] - mQAverageDc;
|
||||
}
|
||||
|
||||
mSamplesPointer = samplesOffset;
|
||||
return new ComplexSamples(i, q);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterator of interleaved complex samples over the native byte buffer array.
|
||||
*/
|
||||
private class InterleavedComplexSamplesIterator implements Iterator<InterleavedComplexSamples>
|
||||
{
|
||||
private int mSamplesPointer = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
return mSamplesPointer < mSamples.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterleavedComplexSamples next()
|
||||
{
|
||||
float[] converted = new float[FRAGMENT_SIZE * 2];
|
||||
|
||||
int samplesOffset = mSamplesPointer;
|
||||
|
||||
for(int pointer = 0; pointer < converted.length; pointer += 2)
|
||||
{
|
||||
converted[pointer] = LOOKUP_VALUES[(0xFF & mSamples[samplesOffset++])] - mIAverageDc;
|
||||
converted[pointer + 1] = LOOKUP_VALUES[(0xFF & mSamples[samplesOffset++])] - mQAverageDc;
|
||||
}
|
||||
|
||||
mSamplesPointer = samplesOffset;
|
||||
|
||||
return new InterleavedComplexSamples(converted, mTimestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.buffer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Implements a factory for creating SignedByteNativeBuffer instances
|
||||
*/
|
||||
public class SignedByteNativeBufferFactory implements INativeBufferFactory
|
||||
{
|
||||
/**
|
||||
* DC removal calculations will run once a minute
|
||||
*/
|
||||
private static final long DC_PROCESSING_INTERVAL = TimeUnit.SECONDS.toMillis(60);
|
||||
|
||||
/**
|
||||
* Number of buffers to process per DC calculation processing interval.
|
||||
*/
|
||||
private static final int DC_CALCULATIONS_PER_INTERVAL = 5;
|
||||
|
||||
/**
|
||||
* DC offset processing residual threshold for ending DC processing interval
|
||||
*/
|
||||
private static final float TARGET_DC_OFFSET_REMAINING = 0.0002f;
|
||||
|
||||
/**
|
||||
* Number of DC calculations remaining in the current interval
|
||||
*/
|
||||
private int mDcCalculationsRemaining = DC_CALCULATIONS_PER_INTERVAL; //Initial value for coarse correction
|
||||
|
||||
/**
|
||||
* Last time DC offset calculation interval ran
|
||||
*/
|
||||
private long mLastDcCalculationTimestamp = 0;
|
||||
|
||||
/**
|
||||
* DC offset calculation gain.
|
||||
*/
|
||||
private static final float DC_FILTER_GAIN = 0.05f;
|
||||
|
||||
/**
|
||||
* Current DC offset correction for Inphase samples
|
||||
*/
|
||||
private float mIAverageDc = 0.0f;
|
||||
|
||||
/**
|
||||
* Current DC offset correction for Quadrature samples
|
||||
*/
|
||||
private float mQAverageDc = 0.0f;
|
||||
|
||||
@Override
|
||||
public INativeBuffer getBuffer(ByteBuffer samples, long timestamp)
|
||||
{
|
||||
byte[] copy = new byte[samples.capacity()];
|
||||
samples.get(copy);
|
||||
|
||||
if(shouldCalculateDc())
|
||||
{
|
||||
calculateDc(copy);
|
||||
}
|
||||
|
||||
return new SignedByteNativeBuffer(copy, timestamp, mIAverageDc, mQAverageDc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if a DC offset calculation for a buffer should be performed.
|
||||
*/
|
||||
private boolean shouldCalculateDc()
|
||||
{
|
||||
if(System.currentTimeMillis() > (mLastDcCalculationTimestamp + DC_PROCESSING_INTERVAL))
|
||||
{
|
||||
if(mDcCalculationsRemaining > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Reset the number of buffers to calculate and the timestamp
|
||||
mDcCalculationsRemaining = DC_CALCULATIONS_PER_INTERVAL;
|
||||
mLastDcCalculationTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the average DC in the sample stream so that it can be subtracted from the samples when the
|
||||
* native buffer is used.
|
||||
* @param samples containing DC offset
|
||||
*/
|
||||
private void calculateDc(byte[] samples)
|
||||
{
|
||||
float iDcAccumulator = 0;
|
||||
float qDcAccumulator = 0;
|
||||
|
||||
for(int x = 0; x < samples.length; x += 2)
|
||||
{
|
||||
iDcAccumulator += samples[x];
|
||||
qDcAccumulator += samples[x + 1];
|
||||
}
|
||||
|
||||
iDcAccumulator /= (samples.length / 2);
|
||||
iDcAccumulator /= 128.0f;
|
||||
iDcAccumulator -= mIAverageDc;
|
||||
mIAverageDc += (iDcAccumulator * DC_FILTER_GAIN);
|
||||
|
||||
qDcAccumulator /= (samples.length / 2);
|
||||
qDcAccumulator /= 128.0f;
|
||||
qDcAccumulator -= mQAverageDc;
|
||||
mQAverageDc += (qDcAccumulator * DC_FILTER_GAIN);
|
||||
|
||||
if(Math.abs(iDcAccumulator) < TARGET_DC_OFFSET_REMAINING && Math.abs(qDcAccumulator) < TARGET_DC_OFFSET_REMAINING)
|
||||
{
|
||||
mDcCalculationsRemaining--;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.buffer.airspy;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.hilbert.HilbertTransform;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Base complex samples iterator for raw Airspy sample buffers. Incorporates a Hilbert transform filter
|
||||
* for converting the real sample array to complex samples.
|
||||
* @param <T> either ComplexSamples or InterleavedComplexSamples
|
||||
*/
|
||||
public abstract class AirspyBufferIterator<T> implements Iterator<T>
|
||||
{
|
||||
protected static final float SCALE_SIGNED_12_BIT_TO_FLOAT = 1.0f / 2048.0f;
|
||||
protected static final int FRAGMENT_SIZE = 2048;
|
||||
protected static final float[] COEFFICIENTS =
|
||||
HilbertTransform.convertHalfBandToHilbert(HilbertTransform.HALF_BAND_FILTER_47_TAP);
|
||||
|
||||
public static final int I_OVERLAP = 11;
|
||||
public static final int Q_OVERLAP = 23;
|
||||
|
||||
protected float[] mIBuffer = new float[FRAGMENT_SIZE + I_OVERLAP];
|
||||
protected float[] mQBuffer = new float[FRAGMENT_SIZE + Q_OVERLAP];
|
||||
protected short[] mSamples;
|
||||
protected int mSamplesPointer = 0;
|
||||
protected long mTimestamp;
|
||||
protected float mAverageDc;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param samples from the airspy.
|
||||
* @param residualI samples from previous buffer
|
||||
* @param residualQ samples from previous buffer
|
||||
* @param averageDc as measured
|
||||
* @param timestamp of the buffer
|
||||
*/
|
||||
public AirspyBufferIterator(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp)
|
||||
{
|
||||
if(residualI.length != I_OVERLAP || residualQ.length != Q_OVERLAP)
|
||||
{
|
||||
throw new IllegalArgumentException("Residual I length [" + residualI.length +
|
||||
"] must be " + I_OVERLAP + " and Residual Q length [" + residualQ.length +
|
||||
"] must be " + Q_OVERLAP);
|
||||
}
|
||||
|
||||
int requiredInterval = FRAGMENT_SIZE * 2; //requires 4 bytes (2 samples) per fragment
|
||||
|
||||
if(samples.length % requiredInterval != 0)
|
||||
{
|
||||
throw new IllegalArgumentException("Samples short array length [" + mSamples.length +
|
||||
"]must be an integer multiple of " + requiredInterval);
|
||||
}
|
||||
|
||||
mAverageDc = averageDc;
|
||||
mSamples = samples;
|
||||
mTimestamp = timestamp;
|
||||
|
||||
//Transfer and scale the residual I & Q samples from the previous buffer
|
||||
for(int i = 0; i < residualI.length; i++)
|
||||
{
|
||||
mIBuffer[i] = scale(residualI[i], averageDc);
|
||||
}
|
||||
|
||||
for(int q = 0; q < residualQ.length; q++)
|
||||
{
|
||||
mQBuffer[q] = scale(residualQ[q], averageDc);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
return mSamplesPointer < mSamples.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scales the short sample value to a 12-bit floating point and subtracts the average DC offset
|
||||
* @param value to scale
|
||||
* @param averageDc detected
|
||||
* @return scaled and normalized sample
|
||||
*/
|
||||
public static float scale(short value, float averageDc)
|
||||
{
|
||||
return ((float)value - 2048) * SCALE_SIGNED_12_BIT_TO_FLOAT - averageDc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.buffer.airspy;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Scalar implementation for non-packed Airspy native buffers
|
||||
*/
|
||||
public class AirspyBufferIteratorScalar extends AirspyBufferIterator<ComplexSamples>
|
||||
{
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the airspy, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
*/
|
||||
public AirspyBufferIteratorScalar(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
int offset = mSamplesPointer;
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
mIBuffer[x + I_OVERLAP] = scale(mSamples[offset++], mAverageDc);
|
||||
mQBuffer[x + Q_OVERLAP] = scale(mSamples[offset++], mAverageDc);
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
//The i line is a simple delay line, so we'll just copy those as a new array here.
|
||||
float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE);
|
||||
float[] q = new float[FRAGMENT_SIZE];
|
||||
|
||||
float accumulator;
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = 0;
|
||||
|
||||
for(int tap = 0; tap < COEFFICIENTS.length; tap++)
|
||||
{
|
||||
accumulator += COEFFICIENTS[tap] * mQBuffer[x + tap];
|
||||
}
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
q[x] = accumulator;
|
||||
}
|
||||
else
|
||||
{
|
||||
i[x] = -i[x];
|
||||
q[x] = -accumulator;
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new ComplexSamples(i, q);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.buffer.airspy;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Vector SIMD implementation for non-packed Airspy native buffers
|
||||
*/
|
||||
public class AirspyBufferIteratorVector128Bits extends AirspyBufferIterator<ComplexSamples>
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_128;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the airspy, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
*/
|
||||
public AirspyBufferIteratorVector128Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
int offset = mSamplesPointer;
|
||||
int fragmentPointer = 0;
|
||||
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
|
||||
|
||||
while(fragmentPointer < FRAGMENT_SIZE)
|
||||
{
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
|
||||
{
|
||||
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
|
||||
scaledSamples[y] = mSamples[offset++];
|
||||
}
|
||||
|
||||
//SIMD scaling operation
|
||||
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
|
||||
.sub(2048.0f)
|
||||
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
|
||||
.sub(mAverageDc)
|
||||
.intoArray(scaledSamples, 0);
|
||||
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
|
||||
{
|
||||
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
|
||||
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
|
||||
}
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
//The i line is a simple delay line, so we'll just copy those as a new array here.
|
||||
float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE);
|
||||
float[] q = new float[FRAGMENT_SIZE];
|
||||
|
||||
FloatVector accumulator;
|
||||
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
|
||||
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 4);
|
||||
FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8);
|
||||
FloatVector f4 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 12);
|
||||
FloatVector f5 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16);
|
||||
FloatVector f6 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 20);
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = FloatVector.zero(VECTOR_SPECIES);
|
||||
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
|
||||
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 4), accumulator);
|
||||
accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
|
||||
accumulator = f4.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 12), accumulator);
|
||||
accumulator = f5.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator);
|
||||
accumulator = f6.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 20), accumulator);
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
q[x] = accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
else
|
||||
{
|
||||
i[x] = -i[x];
|
||||
q[x] = -accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new ComplexSamples(i, q);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.buffer.airspy;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Vector SIMD implementation for non-packed Airspy native buffers
|
||||
*/
|
||||
public class AirspyBufferIteratorVector256Bits extends AirspyBufferIterator<ComplexSamples>
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_256;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the airspy, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
*/
|
||||
public AirspyBufferIteratorVector256Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
int offset = mSamplesPointer;
|
||||
int fragmentPointer = 0;
|
||||
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
|
||||
|
||||
while(fragmentPointer < FRAGMENT_SIZE)
|
||||
{
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
|
||||
{
|
||||
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
|
||||
scaledSamples[y] = mSamples[offset++];
|
||||
}
|
||||
|
||||
//SIMD scaling operation
|
||||
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
|
||||
.sub(2048.0f)
|
||||
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
|
||||
.sub(mAverageDc)
|
||||
.intoArray(scaledSamples, 0);
|
||||
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
|
||||
{
|
||||
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
|
||||
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
|
||||
}
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
//The i line is a simple delay line, so we'll just copy those as a new array here.
|
||||
float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE);
|
||||
float[] q = new float[FRAGMENT_SIZE];
|
||||
|
||||
FloatVector accumulator;
|
||||
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
|
||||
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8);
|
||||
FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16);
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = FloatVector.zero(VECTOR_SPECIES);
|
||||
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
|
||||
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
|
||||
accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator);
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
q[x] = accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
else
|
||||
{
|
||||
i[x] = -i[x];
|
||||
q[x] = -accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new ComplexSamples(i, q);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.buffer.airspy;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Vector SIMD implementation for non-packed Airspy native buffers
|
||||
*/
|
||||
public class AirspyBufferIteratorVector512Bits extends AirspyBufferIterator<ComplexSamples>
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_512;
|
||||
private float[] mFilterPart2;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the airspy, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
*/
|
||||
public AirspyBufferIteratorVector512Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp);
|
||||
//Arrange the last 8 taps of the filter into the higher indices of an array to align for SIMD
|
||||
mFilterPart2 = new float[16];
|
||||
System.arraycopy(COEFFICIENTS, 16, mFilterPart2, 8, 8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
int offset = mSamplesPointer;
|
||||
int fragmentPointer = 0;
|
||||
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
|
||||
|
||||
while(fragmentPointer < FRAGMENT_SIZE)
|
||||
{
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
|
||||
{
|
||||
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
|
||||
scaledSamples[y] = mSamples[offset++];
|
||||
}
|
||||
|
||||
//SIMD scaling operation
|
||||
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
|
||||
.sub(2048.0f)
|
||||
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
|
||||
.sub(mAverageDc)
|
||||
.intoArray(scaledSamples, 0);
|
||||
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
|
||||
{
|
||||
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
|
||||
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
|
||||
}
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
//The i line is a simple delay line, so we'll just copy those as a new array here.
|
||||
float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE);
|
||||
float[] q = new float[FRAGMENT_SIZE];
|
||||
|
||||
FloatVector accumulator;
|
||||
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
|
||||
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, mFilterPart2, 0);
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = FloatVector.zero(VECTOR_SPECIES);
|
||||
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
|
||||
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
q[x] = accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
else
|
||||
{
|
||||
i[x] = -i[x];
|
||||
q[x] = -accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new ComplexSamples(i, q);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.buffer.airspy;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Vector SIMD implementation for non-packed Airspy native buffers
|
||||
*/
|
||||
public class AirspyBufferIteratorVector64Bits extends AirspyBufferIterator<ComplexSamples>
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_64;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the airspy, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
*/
|
||||
public AirspyBufferIteratorVector64Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
int offset = mSamplesPointer;
|
||||
int fragmentPointer = 0;
|
||||
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
|
||||
|
||||
while(fragmentPointer < FRAGMENT_SIZE)
|
||||
{
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
|
||||
{
|
||||
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
|
||||
scaledSamples[y] = mSamples[offset++];
|
||||
}
|
||||
|
||||
//SIMD scaling operation
|
||||
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
|
||||
.sub(2048.0f)
|
||||
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
|
||||
.sub(mAverageDc)
|
||||
.intoArray(scaledSamples, 0);
|
||||
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
|
||||
{
|
||||
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
|
||||
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
|
||||
}
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
//The i line is a simple delay line, so we'll just copy those as a new array here.
|
||||
float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE);
|
||||
float[] q = new float[FRAGMENT_SIZE];
|
||||
|
||||
FloatVector accumulator;
|
||||
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
|
||||
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 2);
|
||||
FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 4);
|
||||
FloatVector f4 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 6);
|
||||
FloatVector f5 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8);
|
||||
FloatVector f6 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 10);
|
||||
FloatVector f7 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 12);
|
||||
FloatVector f8 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 14);
|
||||
FloatVector f9 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16);
|
||||
FloatVector f10 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 18);
|
||||
FloatVector f11 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 20);
|
||||
FloatVector f12 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 22);
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = FloatVector.zero(VECTOR_SPECIES);
|
||||
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
|
||||
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 2), accumulator);
|
||||
accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 4), accumulator);
|
||||
accumulator = f4.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 6), accumulator);
|
||||
accumulator = f5.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
|
||||
accumulator = f6.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 10), accumulator);
|
||||
accumulator = f7.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 12), accumulator);
|
||||
accumulator = f8.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 14), accumulator);
|
||||
accumulator = f9.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator);
|
||||
accumulator = f10.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 18), accumulator);
|
||||
accumulator = f11.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 20), accumulator);
|
||||
accumulator = f12.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 22), accumulator);
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
q[x] = accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
else
|
||||
{
|
||||
i[x] = -i[x];
|
||||
q[x] = -accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new ComplexSamples(i, q);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.buffer.airspy;
|
||||
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
|
||||
/**
|
||||
* Scalar implementation for non-packed Airspy native buffers
|
||||
*/
|
||||
public class AirspyInterleavedBufferIteratorScalar extends AirspyBufferIterator<InterleavedComplexSamples>
|
||||
{
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the airspy, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
*/
|
||||
public AirspyInterleavedBufferIteratorScalar(short[] samples, short[] residualI, short[] residualQ, float averageDc,
|
||||
long timestamp)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterleavedComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
int offset = mSamplesPointer;
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
mIBuffer[x + I_OVERLAP] = scale(mSamples[offset++], mAverageDc);
|
||||
mQBuffer[x + Q_OVERLAP] = scale(mSamples[offset++], mAverageDc);
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
float[] samples = new float[FRAGMENT_SIZE * 2];
|
||||
|
||||
float accumulator;
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = 0;
|
||||
|
||||
for(int tap = 0; tap < COEFFICIENTS.length; tap++)
|
||||
{
|
||||
accumulator += COEFFICIENTS[tap] * mQBuffer[x + tap];
|
||||
}
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
samples[2 * x] = mIBuffer[x];
|
||||
samples[2 * x + 1] = accumulator;
|
||||
}
|
||||
else
|
||||
{
|
||||
samples[2 * x] = -mIBuffer[x];
|
||||
samples[2 * x + 1] = -accumulator;
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new InterleavedComplexSamples(samples, mTimestamp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.buffer.airspy;
|
||||
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
/**
|
||||
* Vector SIMD implementation for non-packed Airspy native buffers
|
||||
*/
|
||||
public class AirspyInterleavedBufferIteratorVector128Bits extends AirspyBufferIterator<InterleavedComplexSamples>
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_128;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the airspy, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
*/
|
||||
public AirspyInterleavedBufferIteratorVector128Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
|
||||
long timestamp)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterleavedComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
int offset = mSamplesPointer;
|
||||
int fragmentPointer = 0;
|
||||
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
|
||||
|
||||
while(fragmentPointer < FRAGMENT_SIZE)
|
||||
{
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
|
||||
{
|
||||
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
|
||||
scaledSamples[y] = mSamples[offset++];
|
||||
}
|
||||
|
||||
//SIMD scaling operation
|
||||
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
|
||||
.sub(2048.0f)
|
||||
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
|
||||
.sub(mAverageDc)
|
||||
.intoArray(scaledSamples, 0);
|
||||
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
|
||||
{
|
||||
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
|
||||
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
|
||||
}
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
float[] samples = new float[FRAGMENT_SIZE * 2];
|
||||
FloatVector accumulator;
|
||||
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
|
||||
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 4);
|
||||
FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8);
|
||||
FloatVector f4 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 12);
|
||||
FloatVector f5 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16);
|
||||
FloatVector f6 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 20);
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = FloatVector.zero(VECTOR_SPECIES);
|
||||
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
|
||||
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 4), accumulator);
|
||||
accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
|
||||
accumulator = f4.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 12), accumulator);
|
||||
accumulator = f5.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator);
|
||||
accumulator = f6.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 20), accumulator);
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
samples[2 * x] = mIBuffer[x];
|
||||
samples[2 * x + 1] = accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
else
|
||||
{
|
||||
samples[2 * x] = -mIBuffer[x];
|
||||
samples[2 * x + 1] = -accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new InterleavedComplexSamples(samples, mTimestamp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.buffer.airspy;
|
||||
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
/**
|
||||
* Vector SIMD implementation for non-packed Airspy native buffers
|
||||
*/
|
||||
public class AirspyInterleavedBufferIteratorVector256Bits extends AirspyBufferIterator<InterleavedComplexSamples>
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_256;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the airspy, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
*/
|
||||
public AirspyInterleavedBufferIteratorVector256Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
|
||||
long timestamp)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterleavedComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
int offset = mSamplesPointer;
|
||||
int fragmentPointer = 0;
|
||||
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
|
||||
|
||||
while(fragmentPointer < FRAGMENT_SIZE)
|
||||
{
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
|
||||
{
|
||||
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
|
||||
scaledSamples[y] = mSamples[offset++];
|
||||
}
|
||||
|
||||
//SIMD scaling operation
|
||||
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
|
||||
.sub(2048.0f)
|
||||
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
|
||||
.sub(mAverageDc)
|
||||
.intoArray(scaledSamples, 0);
|
||||
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
|
||||
{
|
||||
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
|
||||
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
|
||||
}
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
float[] samples = new float[FRAGMENT_SIZE * 2];
|
||||
FloatVector accumulator;
|
||||
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
|
||||
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8);
|
||||
FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16);
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = FloatVector.zero(VECTOR_SPECIES);
|
||||
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
|
||||
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
|
||||
accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator);
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
samples[2 * x] = mIBuffer[x];
|
||||
samples[2 * x + 1] = accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
else
|
||||
{
|
||||
samples[2 * x] = -mIBuffer[x];
|
||||
samples[2 * x + 1] = -accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new InterleavedComplexSamples(samples, mTimestamp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.buffer.airspy;
|
||||
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
/**
|
||||
* Vector SIMD implementation for non-packed Airspy native buffers
|
||||
*/
|
||||
public class AirspyInterleavedBufferIteratorVector512Bits extends AirspyBufferIterator<InterleavedComplexSamples>
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_512;
|
||||
private float[] mFilterPart2;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the airspy, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
*/
|
||||
public AirspyInterleavedBufferIteratorVector512Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
|
||||
long timestamp)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp);
|
||||
|
||||
//Arrange the last 8 taps of the filter into the higher indices of an array to align for SIMD
|
||||
mFilterPart2 = new float[16];
|
||||
System.arraycopy(COEFFICIENTS, 16, mFilterPart2, 8, 8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterleavedComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
int offset = mSamplesPointer;
|
||||
int fragmentPointer = 0;
|
||||
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
|
||||
|
||||
while(fragmentPointer < FRAGMENT_SIZE)
|
||||
{
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
|
||||
{
|
||||
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
|
||||
scaledSamples[y] = mSamples[offset++];
|
||||
}
|
||||
|
||||
//SIMD scaling operation
|
||||
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
|
||||
.sub(2048.0f)
|
||||
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
|
||||
.sub(mAverageDc)
|
||||
.intoArray(scaledSamples, 0);
|
||||
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
|
||||
{
|
||||
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
|
||||
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
|
||||
}
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
float[] samples = new float[FRAGMENT_SIZE * 2];
|
||||
FloatVector accumulator;
|
||||
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
|
||||
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, mFilterPart2, 0);
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = FloatVector.zero(VECTOR_SPECIES);
|
||||
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
|
||||
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
samples[2 * x] = mIBuffer[x];
|
||||
samples[2 * x + 1] = accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
else
|
||||
{
|
||||
samples[2 * x] = -mIBuffer[x];
|
||||
samples[2 * x + 1] = -accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new InterleavedComplexSamples(samples, mTimestamp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.buffer.airspy;
|
||||
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
/**
|
||||
* Vector SIMD implementation for non-packed Airspy native buffers
|
||||
*/
|
||||
public class AirspyInterleavedBufferIteratorVector64Bits extends AirspyBufferIterator<InterleavedComplexSamples>
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_64;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the airspy, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
*/
|
||||
public AirspyInterleavedBufferIteratorVector64Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
|
||||
long timestamp)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterleavedComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
int offset = mSamplesPointer;
|
||||
int fragmentPointer = 0;
|
||||
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
|
||||
|
||||
while(fragmentPointer < FRAGMENT_SIZE)
|
||||
{
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
|
||||
{
|
||||
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
|
||||
scaledSamples[y] = mSamples[offset++];
|
||||
}
|
||||
|
||||
//SIMD scaling operation
|
||||
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
|
||||
.sub(2048.0f)
|
||||
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
|
||||
.sub(mAverageDc)
|
||||
.intoArray(scaledSamples, 0);
|
||||
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
|
||||
{
|
||||
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
|
||||
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
|
||||
}
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
float[] samples = new float[FRAGMENT_SIZE * 2];
|
||||
FloatVector accumulator;
|
||||
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
|
||||
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 2);
|
||||
FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 4);
|
||||
FloatVector f4 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 6);
|
||||
FloatVector f5 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8);
|
||||
FloatVector f6 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 10);
|
||||
FloatVector f7 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 12);
|
||||
FloatVector f8 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 14);
|
||||
FloatVector f9 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16);
|
||||
FloatVector f10 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 18);
|
||||
FloatVector f11 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 20);
|
||||
FloatVector f12 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 22);
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = FloatVector.zero(VECTOR_SPECIES);
|
||||
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
|
||||
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 2), accumulator);
|
||||
accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 4), accumulator);
|
||||
accumulator = f4.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 6), accumulator);
|
||||
accumulator = f5.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
|
||||
accumulator = f6.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 10), accumulator);
|
||||
accumulator = f7.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 12), accumulator);
|
||||
accumulator = f8.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 14), accumulator);
|
||||
accumulator = f9.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator);
|
||||
accumulator = f10.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 18), accumulator);
|
||||
accumulator = f11.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 20), accumulator);
|
||||
accumulator = f12.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 22), accumulator);
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
samples[2 * x] = mIBuffer[x];
|
||||
samples[2 * x + 1] = accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
else
|
||||
{
|
||||
samples[2 * x] = -mIBuffer[x];
|
||||
samples[2 * x + 1] = -accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new InterleavedComplexSamples(samples, mTimestamp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.buffer.airspy;
|
||||
|
||||
import io.github.dsheirer.buffer.INativeBuffer;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
import io.github.dsheirer.vector.calibrate.Implementation;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Native buffer scalar implementation for Airspy non-packed samples.
|
||||
*/
|
||||
public class AirspyNativeBuffer implements INativeBuffer
|
||||
{
|
||||
private short[] mSamples;
|
||||
private short[] mResidualI;
|
||||
private short[] mResidualQ;
|
||||
private float mAverageDc;
|
||||
private long mTimestamp;
|
||||
private Implementation mInterleavedImplementation;
|
||||
private Implementation mNonInterleavedImplementation;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param samples (non-packed) from the airspy device
|
||||
* @param residualI samples from previous buffer
|
||||
* @param residualQ samples from previous buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
* @param interleavedImplementation optimal, scalar vs vector SIMD
|
||||
* @param nonInterleavedImplementation optimal, scalar vs vector SIMD
|
||||
*/
|
||||
public AirspyNativeBuffer(short[] samples, short[] residualI, short[] residualQ, float averageDc,
|
||||
long timestamp, Implementation interleavedImplementation,
|
||||
Implementation nonInterleavedImplementation)
|
||||
{
|
||||
//Ensure we're an even multiple of the fragment size. Typically, this will be 64k or 128k
|
||||
if(samples.length % AirspyBufferIterator.FRAGMENT_SIZE != 0)
|
||||
{
|
||||
throw new IllegalArgumentException("Samples short[] length [" + samples.length +
|
||||
"] must be an even multiple of " + AirspyBufferIterator.FRAGMENT_SIZE);
|
||||
}
|
||||
|
||||
mSamples = samples;
|
||||
mResidualI = residualI;
|
||||
mResidualQ = residualQ;
|
||||
mAverageDc = averageDc;
|
||||
mTimestamp = timestamp;
|
||||
mInterleavedImplementation = interleavedImplementation;
|
||||
mNonInterleavedImplementation = nonInterleavedImplementation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sampleCount()
|
||||
{
|
||||
return mSamples.length / 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimestamp()
|
||||
{
|
||||
return mTimestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<ComplexSamples> iterator()
|
||||
{
|
||||
return switch(mInterleavedImplementation)
|
||||
{
|
||||
case VECTOR_SIMD_512 -> new AirspyBufferIteratorVector512Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
|
||||
case VECTOR_SIMD_256-> new AirspyBufferIteratorVector256Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
|
||||
case VECTOR_SIMD_128-> new AirspyBufferIteratorVector128Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
|
||||
case VECTOR_SIMD_64 ->new AirspyBufferIteratorVector64Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
|
||||
default -> new AirspyBufferIteratorScalar(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<InterleavedComplexSamples> iteratorInterleaved()
|
||||
{
|
||||
return switch(mInterleavedImplementation)
|
||||
{
|
||||
case VECTOR_SIMD_512 -> new AirspyInterleavedBufferIteratorVector512Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
|
||||
case VECTOR_SIMD_256-> new AirspyInterleavedBufferIteratorVector256Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
|
||||
case VECTOR_SIMD_128-> new AirspyInterleavedBufferIteratorVector128Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
|
||||
case VECTOR_SIMD_64 ->new AirspyInterleavedBufferIteratorVector64Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
|
||||
default -> new AirspyInterleavedBufferIteratorScalar(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.buffer.airspy;
|
||||
|
||||
import io.github.dsheirer.buffer.INativeBuffer;
|
||||
import io.github.dsheirer.buffer.INativeBufferFactory;
|
||||
import io.github.dsheirer.vector.calibrate.CalibrationManager;
|
||||
import io.github.dsheirer.vector.calibrate.CalibrationType;
|
||||
import io.github.dsheirer.vector.calibrate.Implementation;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Implements a factory for creating SignedByteNativeBuffer instances
|
||||
*/
|
||||
public class AirspyNativeBufferFactory implements INativeBufferFactory
|
||||
{
|
||||
private boolean mSamplePacking = false;
|
||||
private short[] mResidualI = new short[AirspyBufferIterator.I_OVERLAP];
|
||||
private short[] mResidualQ = new short[AirspyBufferIterator.Q_OVERLAP];
|
||||
private IAirspySampleConverter mConverter;
|
||||
private Implementation mInterleavedIteratorImplementation;
|
||||
private Implementation mNonInterleavedIteratorImplementation;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public AirspyNativeBufferFactory()
|
||||
{
|
||||
updateConverter();
|
||||
|
||||
mInterleavedIteratorImplementation = CalibrationManager.getInstance()
|
||||
.getImplementation(CalibrationType.AIRSPY_UNPACKED_INTERLEAVED_ITERATOR);
|
||||
mNonInterleavedIteratorImplementation = CalibrationManager.getInstance()
|
||||
.getImplementation(CalibrationType.AIRSPY_UNPACKED_ITERATOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample packing places two 12-bit samples into 3 bytes when enabled or
|
||||
* places two 12-bit samples into 4 bytes when disabled.
|
||||
*
|
||||
* @param enabled
|
||||
*/
|
||||
public void setSamplePacking(boolean enabled)
|
||||
{
|
||||
mSamplePacking = enabled;
|
||||
updateConverter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an optimal sample converter instance.
|
||||
*/
|
||||
private void updateConverter()
|
||||
{
|
||||
if(mSamplePacking)
|
||||
{
|
||||
if(!(mConverter instanceof ScalarPackedSampleConverter))
|
||||
{
|
||||
mConverter = new ScalarPackedSampleConverter();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.AIRSPY_SAMPLE_CONVERTER);
|
||||
|
||||
if(implementation == Implementation.VECTOR_SIMD_PREFERRED)
|
||||
{
|
||||
if(!(mConverter instanceof VectorUnpackedSampleConverter))
|
||||
{
|
||||
mConverter = new VectorUnpackedSampleConverter();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!(mConverter instanceof ScalarUnpackedSampleConverter))
|
||||
{
|
||||
mConverter = new ScalarUnpackedSampleConverter();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public INativeBuffer getBuffer(ByteBuffer buffer, long timestamp)
|
||||
{
|
||||
short[] samples = mConverter.convert(buffer);
|
||||
|
||||
INativeBuffer nativeBuffer = new AirspyNativeBuffer(samples,
|
||||
Arrays.copyOf(mResidualI, mResidualI.length),
|
||||
Arrays.copyOf(mResidualQ, mResidualQ.length), mConverter.getAverageDc(), timestamp,
|
||||
mInterleavedIteratorImplementation, mNonInterleavedIteratorImplementation);
|
||||
|
||||
extractResidual(samples);
|
||||
|
||||
return nativeBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the residual overlap samples needed for continuity in the Hilbert transform filter.
|
||||
* @param samples to extract residual from
|
||||
*/
|
||||
private void extractResidual(short[] samples)
|
||||
{
|
||||
int offset = samples.length - (AirspyBufferIterator.I_OVERLAP * 2);
|
||||
|
||||
for(int i = 0; i < AirspyBufferIterator.I_OVERLAP; i++)
|
||||
{
|
||||
mResidualI[i] = samples[offset + (2 * i)];
|
||||
}
|
||||
|
||||
offset = samples.length - (AirspyBufferIterator.Q_OVERLAP * 2) + 1;
|
||||
|
||||
for(int q = 0; q < AirspyBufferIterator.Q_OVERLAP; q++)
|
||||
{
|
||||
mResidualQ[q] = samples[offset + (2 * q)];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.buffer.airspy;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Interface to convert airspy samples from a byte buffer to an array of shorts.
|
||||
*/
|
||||
public interface IAirspySampleConverter
|
||||
{
|
||||
/**
|
||||
* Converts the airspy byte samples contained in the byte buffer to their short-valued representation
|
||||
* @param buffer of airspy samples
|
||||
* @return converted samples
|
||||
*/
|
||||
short[] convert(ByteBuffer buffer);
|
||||
|
||||
/**
|
||||
* Current DC average of the samples that have been processed thus far.
|
||||
*/
|
||||
float getAverageDc();
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.buffer.airspy;
|
||||
|
||||
import io.github.dsheirer.buffer.DcCorrectionManager;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Scalar implementation of airspy sample converter for un-packed samples.
|
||||
*/
|
||||
public class ScalarPackedSampleConverter implements IAirspySampleConverter
|
||||
{
|
||||
/**
|
||||
* Manages DC calculations and processing interval
|
||||
*/
|
||||
private DcCorrectionManager mDcCalculationManager = new DcCorrectionManager();
|
||||
|
||||
@Override
|
||||
public short[] convert(ByteBuffer buffer)
|
||||
{
|
||||
boolean shouldCalculateDc = mDcCalculationManager.shouldCalculateDc();
|
||||
|
||||
int offset = 0;
|
||||
short sample;
|
||||
short[] samples;
|
||||
byte b1, b2, b3;
|
||||
|
||||
samples = new short[buffer.capacity() / 3 * 2];
|
||||
|
||||
if(shouldCalculateDc)
|
||||
{
|
||||
long dcAccumulator = 0;
|
||||
|
||||
for(int x = 0; x < samples.length; x += 2)
|
||||
{
|
||||
b1 = buffer.get(offset++);
|
||||
b2 = buffer.get(offset++);
|
||||
b3 = buffer.get(offset++);
|
||||
|
||||
sample = (short)(((b1 & 0xFF) << 4) | ((b2 & 0xF0) >> 4));
|
||||
samples[x] = sample;
|
||||
dcAccumulator += sample;
|
||||
|
||||
sample = (short)(((b2 & 0xF) << 8) | (b3 & 0xFF));
|
||||
samples[x] = sample;
|
||||
dcAccumulator += sample;
|
||||
}
|
||||
|
||||
float averageDcNow = ((float)dcAccumulator / (float)samples.length) - 2048.0f;
|
||||
averageDcNow *= AirspyBufferIterator.SCALE_SIGNED_12_BIT_TO_FLOAT;
|
||||
mDcCalculationManager.adjust(averageDcNow);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int x = 0; x < samples.length; x += 2)
|
||||
{
|
||||
b1 = buffer.get(offset++);
|
||||
b2 = buffer.get(offset++);
|
||||
b3 = buffer.get(offset++);
|
||||
|
||||
sample = (short)(((b1 & 0xFF) << 4) | ((b2 & 0xF0) >> 4));
|
||||
samples[x] = sample;
|
||||
|
||||
sample = (short)(((b2 & 0xF) << 8) | (b3 & 0xFF));
|
||||
samples[x] = sample;
|
||||
}
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getAverageDc()
|
||||
{
|
||||
return mDcCalculationManager.getAverageDc();
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
byte[] raw = new byte[262144];
|
||||
ByteBuffer buffer = ByteBuffer.wrap(raw);
|
||||
|
||||
ScalarPackedSampleConverter scalar = new ScalarPackedSampleConverter();
|
||||
VectorUnpackedSampleConverter vector = new VectorUnpackedSampleConverter();
|
||||
|
||||
long accumulator = 0;
|
||||
int iterations = 1_000_000;
|
||||
|
||||
System.out.println("Starting ...");
|
||||
long start = System.currentTimeMillis();
|
||||
for(int count = 0; count < iterations; count++)
|
||||
{
|
||||
// short[] samples = scalar.convert(buffer);
|
||||
short[] samples = vector.convert(buffer);
|
||||
accumulator += samples[1];
|
||||
}
|
||||
long elapsed = System.currentTimeMillis() - start;
|
||||
|
||||
System.out.println("Accumulator: " + accumulator);
|
||||
System.out.println("Elapsed: " + (elapsed / 1000.0f));
|
||||
System.out.println("Finished");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.buffer.airspy;
|
||||
|
||||
import io.github.dsheirer.buffer.DcCorrectionManager;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Scalar implementation of airspy sample converter for un-packed samples.
|
||||
*/
|
||||
public class ScalarUnpackedSampleConverter implements IAirspySampleConverter
|
||||
{
|
||||
/**
|
||||
* Manages DC calculations and processing interval
|
||||
*/
|
||||
private DcCorrectionManager mDcCalculationManager = new DcCorrectionManager();
|
||||
|
||||
@Override
|
||||
public short[] convert(ByteBuffer buffer)
|
||||
{
|
||||
boolean shouldCalculateDc = mDcCalculationManager.shouldCalculateDc();
|
||||
|
||||
int offset = 0;
|
||||
short sample;
|
||||
short[] samples;
|
||||
byte b1, b2;
|
||||
|
||||
samples = new short[buffer.capacity() / 2];
|
||||
|
||||
if(shouldCalculateDc)
|
||||
{
|
||||
long dcAccumulator = 0;
|
||||
|
||||
for(int x = 0; x < samples.length; x++)
|
||||
{
|
||||
b1 = buffer.get(offset++);
|
||||
b2 = buffer.get(offset++);
|
||||
sample = (short)(((b2 & 0x0F) << 8) | (b1 & 0xFF));
|
||||
samples[x] = sample;
|
||||
dcAccumulator += sample;
|
||||
}
|
||||
|
||||
float averageDcNow = ((float)dcAccumulator / (float)samples.length) - 2048.0f;
|
||||
averageDcNow *= AirspyBufferIterator.SCALE_SIGNED_12_BIT_TO_FLOAT;
|
||||
mDcCalculationManager.adjust(averageDcNow);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int x = 0; x < samples.length; x++)
|
||||
{
|
||||
b1 = buffer.get(offset++);
|
||||
b2 = buffer.get(offset++);
|
||||
sample = (short)(((b2 & 0x0F) << 8) | (b1 & 0xFF));
|
||||
samples[x] = sample;
|
||||
}
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getAverageDc()
|
||||
{
|
||||
return mDcCalculationManager.getAverageDc();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.buffer.airspy;
|
||||
|
||||
import io.github.dsheirer.buffer.DcCorrectionManager;
|
||||
import java.nio.ByteBuffer;
|
||||
import jdk.incubator.vector.ShortVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
/**
|
||||
* Scalar implementation of airspy sample converter for un-packed samples.
|
||||
*/
|
||||
public class VectorUnpackedSampleConverter implements IAirspySampleConverter
|
||||
{
|
||||
private static final VectorSpecies<Short> VECTOR_SPECIES = ShortVector.SPECIES_PREFERRED;
|
||||
|
||||
/**
|
||||
* Manages DC calculations and processing interval
|
||||
*/
|
||||
private DcCorrectionManager mDcCalculationManager = new DcCorrectionManager();
|
||||
|
||||
@Override
|
||||
public short[] convert(ByteBuffer buffer)
|
||||
{
|
||||
boolean shouldCalculateDc = mDcCalculationManager.shouldCalculateDc();
|
||||
|
||||
short sample;
|
||||
short[] samples;
|
||||
byte b1, b2;
|
||||
|
||||
samples = new short[buffer.capacity() / 2];
|
||||
|
||||
int bytesOffset;
|
||||
int rawPointer = 0;
|
||||
ShortVector vector;
|
||||
|
||||
int samplesOffset = 0;
|
||||
|
||||
short[] bytes1 = new short[VECTOR_SPECIES.length()];
|
||||
short[] bytes2 = new short[VECTOR_SPECIES.length()];
|
||||
|
||||
if(shouldCalculateDc)
|
||||
{
|
||||
long dcAccumulator = 0;
|
||||
|
||||
for(; samplesOffset < VECTOR_SPECIES.loopBound(samples.length); samplesOffset += VECTOR_SPECIES.length())
|
||||
{
|
||||
bytesOffset = 0;
|
||||
|
||||
while(bytesOffset < VECTOR_SPECIES.length())
|
||||
{
|
||||
bytes1[bytesOffset] = (short)(buffer.get(rawPointer++) & 0xFF);
|
||||
bytes2[bytesOffset++] = (short)(buffer.get(rawPointer++) & 0x0F);
|
||||
}
|
||||
|
||||
vector = ShortVector.fromArray(VECTOR_SPECIES, bytes1, 0)
|
||||
.or(ShortVector.fromArray(VECTOR_SPECIES, bytes2, 0).lanewise(VectorOperators.LSHL, 8));
|
||||
dcAccumulator += vector.reduceLanes(VectorOperators.ADD);
|
||||
vector.intoArray(samples, samplesOffset);
|
||||
}
|
||||
|
||||
for(; samplesOffset < samples.length; samplesOffset++)
|
||||
{
|
||||
b1 = buffer.get(rawPointer++);
|
||||
b2 = buffer.get(rawPointer++);
|
||||
|
||||
sample = (short)(((b2 & 0x0F) << 8) | (b1 & 0xFF));
|
||||
samples[samplesOffset] = sample;
|
||||
dcAccumulator += sample;
|
||||
}
|
||||
|
||||
float averageDcNow = ((float)dcAccumulator / (float)samples.length) - 2048.0f;
|
||||
averageDcNow *= AirspyBufferIterator.SCALE_SIGNED_12_BIT_TO_FLOAT;
|
||||
mDcCalculationManager.adjust(averageDcNow);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(; samplesOffset < VECTOR_SPECIES.loopBound(samples.length); samplesOffset += VECTOR_SPECIES.length())
|
||||
{
|
||||
bytesOffset = 0;
|
||||
|
||||
while(bytesOffset < VECTOR_SPECIES.length())
|
||||
{
|
||||
bytes1[bytesOffset] = (short)(buffer.get(rawPointer++) & 0xFF);
|
||||
bytes2[bytesOffset++] = (short)(buffer.get(rawPointer++) & 0x0F);
|
||||
}
|
||||
|
||||
vector = ShortVector.fromArray(VECTOR_SPECIES, bytes1, 0)
|
||||
.or(ShortVector.fromArray(VECTOR_SPECIES, bytes2, 0).lanewise(VectorOperators.LSHL, 8));
|
||||
vector.intoArray(samples, samplesOffset);
|
||||
}
|
||||
|
||||
for(; samplesOffset < samples.length; samplesOffset++)
|
||||
{
|
||||
b1 = buffer.get(rawPointer++);
|
||||
b2 = buffer.get(rawPointer++);
|
||||
|
||||
sample = (short)(((b2 & 0x0F) << 8) | (b1 & 0xFF));
|
||||
samples[samplesOffset] = sample;
|
||||
}
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getAverageDc()
|
||||
{
|
||||
return mDcCalculationManager.getAverageDc();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user