Compare commits

...

75 Commits

Author SHA1 Message Date
Dennis Sheirer
925353d515 Bump Version 0.5.0 Beta 3, Gradle 7.4.2, Embedded JDK 18.0.1 2022-05-04 14:51:27 -04:00
Denny Sheirer
7c32897321 #1229 Resolves issue with correctly identifying the port number for USB tuners that is causing some tuners not to be recognized and also generating udev errors when sdrtrunk attempts to start an already started device because it was incorrectly identified. (#1230)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-05-03 04:03:18 -04:00
Doug Hoffman
cbeda19d27 Generate audio metadata with correct to/from (#1215)
#1215 Generate audio metadata with correct to/from, preferring patch group, then talkgroup then other, for TO talkgroups.
2022-05-01 06:06:31 -04:00
Doug Hoffman
8148ca48af Normalize audio before recording (#1213)
#1213 Normalize audio before recording
2022-05-01 05:57:32 -04:00
VolFFProCoder
adcb55ffbc Add filter button to messages (#1098)
Adds filtering to channel decoded events panel.
2022-04-30 05:44:57 -04:00
Andrzej Pańkowski
1152b13220 Merge pull request #1177
* Fixes message filtering that was not being initialized.  Fixes minor spotted code issues
2022-04-30 04:25:33 -04:00
Doug Hoffman
166383e1ed Make Broadcastify Calls scheduled test configurable (#1218)
* Broadcastify Calls periodic keep-alive to avoid showing offline status
2022-04-30 04:04:10 -04:00
Denny Sheirer
0d5d1b20e3 #1223 Resolves NPE when Airspy and HackRF tuners are re-enabled after startup. Slight tweaks to TunerManager to change when discovered USB device startup takes place to potentially address intermittent fail on start for some tuners that users are seeing. Resolves issue where a started device was not unreferenced in LibUsb on shutdown. (#1224)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-04-29 08:00:18 -04:00
Denny Sheirer
77ea43f61a #1221 resolves null pointer exception in tuner editor when attempting to restart an error tuner. (#1222)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-04-28 04:40:52 -04:00
Denny Sheirer
f988344222 #1219 resolves issues with NPE and restarting an error tuner. (#1220)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-04-26 04:02:36 -04:00
Doug Hoffman
60489e1fef (icecast) Add support for inline metadata (#1212) 2022-04-25 04:38:54 -04:00
Doug Hoffman
ac8aa17a80 Check in with Broadcastify Calls to stay online while otherwise idle (#1214) 2022-04-24 05:15:34 -04:00
Denny Sheirer
25a42126df #1210 Resolves issue of tuner instances can't be garbage collected due to retained references. Updates several of the tuner controller and tuner editor classes to add/invoke dispose() methods to clear retained instance references. (#1216)
Co-authored-by: Denny Sheirer <sheirerd@ainfosec.com>
2022-04-24 04:56:10 -04:00
Denny Sheirer
1b90e0b50c #1209 updates tuner manager to properly deref usb devices on discovery. Updates tuner editors to separate enable/disable from tuner error state. Updates settings manager to disable auto-fail when loading xml file when unknown tags are encountered so that users' existing configs don't fail on startup and erase their existing settings. (#1211)
Co-authored-by: Denny Sheirer <sheirerd@ainfosec.com>
2022-04-23 06:05:12 -04:00
Denny Sheirer
7f5ad7eb20 Update README.md 2022-04-19 07:03:19 -04:00
Denny Sheirer
0e6713b6e2 Update README.md 2022-04-19 07:02:32 -04:00
Denny Sheirer
96d3996bec #1190 Tuner management redesign. Updates all tuners with direct threaded support. Incorporates USB Hotplug support. Adds black/white listing of tuners. Updates all tuner editors to have a common look and feel. Changes location of tuner configuration file and changes format to JSON. (#1208)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-04-18 07:30:58 -04:00
Denny Sheirer
0fb148d31e #1194 Resolves issue with audio stream bit rate scheduling that was causing stream disconnects for data rate under flows. (#1195)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-26 05:48:08 -04:00
Denny Sheirer
cebca28b19 Patch from @ImagoTrigger to implement headless operation: 9e4d2adfa9..c597d1967b (#1191)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-25 04:19:38 -04:00
Denny Sheirer
af8cc0255e #1186 removes error logging that indicates that a channel cannot be stopped because it doesn't exist. This happens when a second successive attempt to stop a channel fails to find the channel because the first attempt has already removed the channel from the list. (#1187)
Co-authored-by: Denny Sheirer <sheirerd@ainfosec.com>
2022-03-21 19:09:49 -04:00
Dennis Sheirer
775ae00861 Update version to 0.5.0 beta 2 2022-03-21 05:48:16 -04:00
Denny Sheirer
1b80f5f1b3 #1178 removes synchronization on channel processing manager's source event handler to enable more fine-grained channel start/stop event processing. This was causing the channel start and stop events to occur sequentially. Adds stream processing with coordinated heartbeat. (#1185)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-21 05:47:20 -04:00
Dennis Sheirer
f9a7bbeddf Update version to 0.5.0 beta 1 2022-03-18 11:53:03 -04:00
Denny Sheirer
58a0264565 #1175 increases scheduled thread pool size from 2 to 4 threads. (#1176)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-18 11:45:24 -04:00
Denny Sheirer
6d2af95e9a #1173 updates DMR decoder to support DCDM (#1174)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-18 07:00:19 -04:00
Denny Sheirer
bf46b19dc9 #1169 Resolves issue where P25 C4FM decoder can request an interpolated sample value that is less than 0. Not sure why or what is causing this but this change guards against an ArrayIndexOutOfBounds exception when it does happen. (#1172)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-16 05:08:31 -04:00
Denny Sheirer
7749fd10f1 #1170 resolves issue with MP3 encoded audio raspy. Adds auto-clipping of PCM audio prior to encoding and adds 4x new 32-bit input sample rate selections to choose from, in addition to original 4x 16-bit input formats. (#1171)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-16 05:00:07 -04:00
Denny Sheirer
90bc321aca #1165 reverts changes to gradle runtimeZip task behavior. Enables 3 options for building target OS and CPU images: 1)build using local JDK, 2)build all targets using downloaded JDKs, or 3)download all JDKs and build all targets. (#1166)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-13 07:47:32 -04:00
Dennis Sheirer
bad4fae1b8 Updates README 2022-03-12 09:09:09 -05:00
Dennis Sheirer
c3fc435fdc Updates github action to use liberica jdk 17 plus fx libraries 2022-03-12 08:55:46 -05:00
JaredLThompson
2ab0c78728 Add error message for R828D Tuner (#1140) 2022-03-12 08:49:40 -05:00
hans109h
187989f6b8 Update README.md (#1158)
Corrects link to Bellsoft JDK download rather than duplicating the Azul link.
2022-03-12 08:48:09 -05:00
Steve Myers
3b83412bcb Update gradle.yml (#1143) 2022-03-12 08:46:25 -05:00
Denny Sheirer
968ea458da #1163 resolves issue with airspy unpacked calibrations hanging. Updates each test to check elapsed timer on a more granular scale on the theory that the SIMD vector implementation is horribly slow on some systems. (#1164)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-12 08:33:55 -05:00
Denny Sheirer
a9f4fbbdd4 #1160 OSX aarch64 (M1) libusb support and JDK auto-download for target OS images. (#1161)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-12 07:43:58 -05:00
Dennis Sheirer
6b3b45e9af Cleanup. 2022-03-12 03:39:16 -05:00
Denny Sheirer
2355dfce07 #939 Adds MP3 encoder preferences to the User Preferences dialog and provides user control over sample rate and MP3 encoder settings. MP3 preferences are global and affect both recording and streaming. (#1159)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-10 04:17:35 -05:00
Dennis Sheirer
0e8c9591e4 #1154 updates JMBE creator to work correctly with arm32 and aarch64 architectures. 2022-03-06 07:30:48 -05:00
Denny Sheirer
54e4771f36 #1154 updates JMBE creator to work correctly with arm32 and aarch64 architectures. (#1155)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-06 07:00:49 -05:00
Denny Sheirer
b2eb245feb #1152 Resolves issue where patch groups were not being properly formatted when uploading to broadcastify calls api. (#1153)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-05 05:56:16 -05:00
Denny Sheirer
e826314d06 #1150 resolves issue with inability to create HTTPS SSL connections. (#1151)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-05 03:32:07 -05:00
Denny Sheirer
0eec2e7ab8 Updates complex decimation filters to use either scalar or vector optimal implementation. (#1148)
Updates HackRF native byte buffers with automatic DC removal.
Creates new half-band channel source to replace the cic-based channel source in the heterodyne channelizer.  Resolves issue in RTL native buffer processing.  Creates a CACHED thread pool and reduces the number of threads for the SCHEDULED thread pool to 2.  Changes each of the channel source implementations to use a dedicated thread (Dispatcher class) instead of the scheduled thread pool, to reduce the amount of buffered/retained samples objects in memory. Lots of other small changes and tweaks.
Removes last of Reusable buffer code.  Updates DFTProcessor to use a single thread scheduled executor.  Updates Polyphase channelizer to use threads instead of an executor.  Removes a number of unused classes.
Commenting out window calibration.  Scalar is faster in both implementations.
Adds vector and scalar implementations for windows.  Repackages window and window type classes.
WIP adds vector implementations for Airspy non-interleaved buffer iterator and calibration plugins for both interleaved and non-interleaved buffer iterators.
WIP adds vector implementations for Airspy interleaved buffer iterator.
Work on developing scalar and vector implementations of airspy sample conversion and airspy native buffer processing.
Introduces new INativeBuffer framework where tuner raw samples are retained in their native (compressed) format until when they're needed and they are converted on-demand into smaller complex sample buffers to aid garbage collection.
Removes all usage of ReusableComplexBuffers and ReusableChannelREsultsBuffers.  Updates polyphase channelizer to be more efficient in output channel processing.
Updates ViterbiDecoder to change Map usage to array for memory efficiency.
Updates channel processors to used scalar implementation for gain and mixer, until the polyphase channel results can be fixed to be a multiple of the SIMD lane width.
Resolves final changes for the conversion from interleaved sample arrays to complexsamples buffers for distributing baseband samples to channels and processing chains.
Updates tuner channel sources and all decoders to receive new complex samples buffers, removing the previous reusable complex buffers as the default baseband samples buffer object.
Still to resolve is the ComplexMixer implementations.  Needs validation and a calibration implementation with an update to the complex mixer factory.
Updates the DMR decoder to use optimal (scalar vs vector) implementations of filter and AGC controls.
Updates the AM demodulator for separated I&Q float sample processing.
Code review and testing changes.
updates build script for runtime configuration.
Integrates calibration manager with main sdrtrunk application.  Updates NBFMDecoder to use new optimized FIR and Decimation filter options.
Updates real half-band decimation filters to select the optimal (scalar vs vector) implementation.
Adds calibration manager plugins.
Adds calibration manager and plugins to determine the optimal (scalar vs vector) implementation type to use for various classes of dsp components.
Adds vector squelching FM demodulator and cleans up the IIR filter and power squelch to use all floats.
Adds vector implementation of FM demodulator.  This introduces a new interface that accepts i/q samples as two separate float arrays, versus an interleaved array, which enables more efficient processing.  This new complex sample interface will be used across the codebase eventually.
Adds ComplexOscillator and a SIMD version VectorComplexOscillator.  Deprecates the LowPhaseNoiseOscillator because the ComplexOscillator is about 35% faster.  The vectorized version is about 43% faster when SIMD lane width is 8x.
Adds new vector complex filters and new real oscillators.  Note: the RealOscillator using straight Java math sin() function is 3x slower than the existing legacy implementation.  Will replace this with a complex oscillator that only uses the real part.
Updates ComplexFIRFilter to be 50% more efficient.
Adds 23 tap filter implementations.  Will not add 63-tap implementations because they are slower than the default vector implementation.
Adds 15-tap customized vector half band filters.
Adds initial custom complex vector half-band decimation filters for 11-tap and 15-tap decimation filters..
Adds vector complex half band filter implementations.  Note: performance of the generic 64-bit 2-lane filter is horrible and is not included.
Optimizes complex half-band filter to be 27% more efficient.
Implements vectorized real half-band filter implementations.
Implements a new FIR filter class that processes sample buffers instead of single sample at a time.
Refactored all of the calibratons to be time-duration based.  Testing on windows i7 gen 3 CPU showed that it took 1000x longer for one of the calibrations to run, versus my AMD 5800x.
Changed gradle jvm args so that the windows plaf export is always applied.
Updates gradle build script to ensure incubator module is included and to cleanup javafx library management for building runtime images.
Testing tweaks and changes.
Adds logging limiter to dispatcher for buffer overflows
Adds User Preferences editor for performing Vector/SIMD calibrations and new startup dialog prompting the user to perform calibrations.
Updates calibration manager and all calibration implementations to add warm-up and test phases and to reduce the overall time it takes to calibrate a system.

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-04 08:45:05 -05:00
Denny Sheirer
5e9c239ea3 #1133 Resolves decimation rate too small issue for NBFM decoder. (#1134)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-01-10 04:26:57 -05:00
Denny Sheirer
1fd9b0fbcb Resolves #1130 - restores real time PPM measured frequency error broadcasts from DMR, P25P1 and P2 decoders. (#1131)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-01-02 09:01:32 -05:00
Denny Sheirer
8413343e76 Resolves 1122. Adds decimation to NBFMDecoder so that channel bandwidth is reduced to min sample rate needed. This reduces the impact on channel power measurement from adjacent channels. (#1129)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-01-02 08:35:19 -05:00
Denny Sheirer
2a0427c559 Adds support for power-of-2 decimation for both real and complex sample buffers. Creates new class to analyze the filter attenuation rates using a variety of filter lengths and window options to enable selecting the optimal combination of cascaded power-of-2 half band decimation filters. (#1127)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2021-12-31 10:20:15 -05:00
Denny Sheirer
5d9d232585 #1110 Updates DMR MBE frame recorder to correctly handle calls on separate timeslots and to fully capture identifiers associated with calls. (#1120)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2021-11-28 07:12:40 -05:00
Denny Sheirer
f0f0329f9b #1118 LRRP Latitude Hemisphere parsing. Updates parser to treat latitude value as a 1-bit hemisphere flag followed by a 31-bit integer. Previously, this field was parsed as a 32-bit two's complement field. (#1119)
There may be two separate formats that are used for LRRP position fields where one treats the value as a two's complement field and the other as a 1-bit hemisphere indicator followed by a 31-bit positive integer.  If so, we'll need to expose a user-selectable flag to toggle one format or the other.

For now, this change won't impact any Northern hemisphere users.

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2021-11-28 06:14:54 -05:00
Denny Sheirer
2a45849e6d #1115 Adds more parsing updates for LRRP packets. (#1116)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2021-11-27 13:42:04 -05:00
Denny Sheirer
aa89079e28 #1113 Updates LRRP protocol to correctly identify geo-fence positions and to add support for packet type identification. Corrects issue with N/S and E/W hemisphere calculations. (#1114)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2021-11-25 05:58:42 -05:00
Dennis Sheirer
31b9ee5910 #1014 Adds AMBE voice frame recording support. 2021-11-21 04:57:39 -05:00
Denny Sheirer
6a54fe101c #1014 Adds AMBE voice frame recording support. (#1108)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2021-11-21 04:56:58 -05:00
Denny Sheirer
87b3d34024 #1106 Adds half-band filter create to filter factory (#1107)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2021-11-21 04:17:54 -05:00
Denny Sheirer
b8bbe9264a #1104 Resolves issue where all assembled DMR packet sequences are occurring only on timeslot 2 (#1105)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2021-11-20 06:04:49 -05:00
Dennis Sheirer
0c7dd192b0 Disabled failing tests until test can be updated to work across all platforms. 2021-11-18 04:30:43 -05:00
Denny Sheirer
8b487d79e4 Adds Comparable to identifiers that are used in TreeSets. (#1103)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2021-11-15 04:20:08 -05:00
Justin Bull
cb83a8e870 Properly format and safely escape to CSV in DecodeEventLogger (#1092)
* Add failing test for toCSV()

* Add missing " to timeslot value

* Add failing test for quotes and commas in details

* Separate comma and quote tests

* Escape toCSV cells using utility

* Use CSVFormatter instead of escape utils

Allows us to control exactly how we want to format a CSV-valid row.

* Never have a null cell

Instead of using a stream map, opting for an explicit handling so the logger can explode when an unexpected null shows up. Prevents the situation where things are mysteriously blank, forcing developers to handle known null scenarios.
2021-11-15 03:53:52 -05:00
Denny Sheirer
0bc11a07cf Update README.md 2021-11-14 07:59:26 -05:00
Dennis Sheirer
560cbad5d7 Code cleanup. 2021-11-14 07:57:14 -05:00
Noah Lynch
87e9bf8749 Don't sort Alias TableView while bulk changes are applied (#1039)
* Don't sort backing SortedList while AliasBulkEditor is applying changes.

SortedList takes too long to sort and can cause large slowdowns and UI
freezes while many changes to Aliases are applied at once via the
AliasBulkEditor.
2021-11-14 07:47:27 -05:00
Wojciech
770f58c668 Show geo locations from DMR on map (#1001)
Thank you!
2021-11-14 07:36:34 -05:00
Denny Sheirer
4b806ecfde #36 NBFM squelch control. Updates NBFM decoder to incorporate squelch control. Squelch is a configuration item and also available to adjust at runtime with new 'Channel' tab in Now Playing window. All decoders were updated to produce continuous channel power (dB) measurements that are displayed in the Channel tab's new Power Meter. (#1100)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2021-11-14 06:59:58 -05:00
Denny Sheirer
7303e06943 Resolves #1094. (#1097)
Updates gradle build system to address classes that are now hidden in Java 17 and no longer exported from the base modules.

Updated build system to specifically call out JavaFX OS-specific libraries and target these to each of the OS-specific build products.

Removes jcenter as a maven repo since it's now deprecated.

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2021-11-06 09:47:27 -04:00
Denny Sheirer
846b249fb3 Update README.md 2021-11-06 09:05:23 -04:00
Denny Sheirer
67f23494e2 Update README.md 2021-11-02 04:39:31 -04:00
Denny Sheirer
d7efb627d7 Update README.md
Updated JMBE audio library instructions.
2021-11-02 04:38:42 -04:00
Denny Sheirer
08bc0b9963 Update README.md
Updated readme with Java 17 requirement
2021-11-02 04:30:49 -04:00
Dennis Sheirer
d1ed46860c Updates gradle wrapper to use version 7.2 and updates build script for 7.2 compatibility. 2021-10-30 10:00:19 -04:00
Dennis Sheirer
38f694d262 Updates gradle wrapper to use version 7.2 and updates build script for 7.2 compatibility. 2021-10-30 07:52:27 -04:00
Denny Sheirer
75ef3217ac #1021 Corrects previous implementation of dot-product filtering using Math.fma() (#1025)
Co-authored-by: Denny <denny@denny-desktop>
2020-12-29 03:50:24 -05:00
Denny Sheirer
2847dff64a #1021 Updates FIR filter to use the java Math.fma floating point multiply and accumulate instruction for more efficient processing. (#1022)
Co-authored-by: Denny <denny@denny-desktop>
2020-12-27 06:48:37 -05:00
Denny Sheirer
f0d9ef99ea Resolves multi-threading issue in buffer assembler where sample listener is nullified while buffer processing is still in process. (#1005)
Co-authored-by: Denny <denny@denny-desktop>
2020-12-01 04:06:03 -05:00
Robert Scullin
67702096d7 RR User Interface Improvements, including Premium notices (#992)
* RR UX Improvements, including Premium notices

* Fixing Error handling bug in LoginDialog
2020-11-02 03:40:30 -05:00
Denny
88f124af9d Updates build to 0.5.0 Alpha 6. 2020-10-26 04:27:20 -04:00
Denny Sheirer
d34460c4e3 #986 Resolves issue with HackRF tuner not loading correctly. (#987)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-26 04:26:29 -04:00
745 changed files with 44234 additions and 28181 deletions

34
.github/workflows/gradle.yml vendored Normal file
View 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

View File

@@ -2,6 +2,7 @@
<dictionary name="denny">
<words>
<w>bahroo</w>
<w>basebanded</w>
<w>broadcastify</w>
<w>correlator</w>
<w>deinterleave</w>

2
.idea/misc.xml generated
View File

@@ -1,7 +1,7 @@
<?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="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="liberica-17.0.2-fx" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

View File

@@ -1,3 +1,5 @@
![Gradle Build](https://github.com/dsheirer/sdrtrunk/actions/workflows/gradle.yml/badge.svg)
# 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:

View File

@@ -1,59 +1,64 @@
/*
* *****************************************************************************
* 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-alpha5'
sourceCompatibility = '13'
version = '0.5.0-beta3'
sourceCompatibility = '17'
sourceSets {
main.java.srcDirs 'src/main'
test.java.srcDirs 'src/test'
}
test {
useJUnitPlatform()
}
dependencies {
// JUnit Tests
testImplementation 'junit:junit:4.12'
testImplementation 'org.assertj:assertj-core:3.8.0'
testImplementation("org.junit.jupiter:junit-jupiter-api:5.7.0")
testImplementation 'org.mockito:mockito-core:3.+'
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 'ch.qos.logback:logback-core:1.2.3'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.13.2'
implementation 'com.fazecast:jSerialComm:2.5.0'
implementation 'com.github.jiconfont:jiconfont-font_awesome:4.7.0.1'
implementation 'com.github.jiconfont:jiconfont-javafx:1.0.0'
@@ -64,54 +69,40 @@ dependencies {
implementation 'com.jidesoft:jide-oss:3.6.18'
implementation 'com.miglayout:miglayout-swing:5.2'
implementation 'com.mpatric:mp3agic:0.9.1'
implementation 'commons-io:commons-io:2.7'
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 'org.apache.commons:commons-math3:3.6.1'
implementation 'org.apache.commons:commons-csv:1.9.0'
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.controlsfx:controlsfx:11.1.0'
implementation 'org.rauschig:jarchivelib:1.0.0'
implementation 'org.slf4j:slf4j-api:1.7.25'
implementation 'org.usb4java:libusb4java:1.3.0'
implementation 'org.usb4java:usb4java:1.3.0'
implementation 'org.usb4java:usb4java-javax:1.3.0'
implementation 'io.github.dsheirer:libusb4java-darwin-aarch64:1.3.0' //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-18.0.1-full'
String jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-18.0.1-full'
String jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-18.0.1-full.jdk'
String jdk_osx_aarch64 = jdk_base + 'osx-arm64/jdk-18.0.1-full.jdk'
String jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-18.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/18.0.1+12/bellsoft-jdk18.0.1+12-linux-aarch64-full.tar.gz")
}
targetPlatform(targetLinuxX86_64) {
jdkHome = jdkDownload("https://download.bell-sw.com/java/18.0.1+12/bellsoft-jdk18.0.1+12-linux-amd64-full.tar.gz")
}
targetPlatform(targetOsxAarch64) {
jdkHome = jdkDownload("https://download.bell-sw.com/java/18.0.1+12/bellsoft-jdk18.0.1+12-macos-aarch64-full.tar.gz")
}
targetPlatform(targetOsxX86_64) {
jdkHome = jdkDownload("https://download.bell-sw.com/java/18.0.1+12/bellsoft-jdk18.0.1+12-macos-amd64-full.tar.gz")
}
targetPlatform(targetWindowsX86_64) {
jdkHome = jdkDownload("https://download.bell-sw.com/java/18.0.1+12/bellsoft-jdk18.0.1+12-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")
}

View File

@@ -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.4.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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)

View File

@@ -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:

View File

@@ -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());
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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());

View File

@@ -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;

View File

@@ -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;
@@ -35,8 +32,11 @@ import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2AudioStreaming
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.InputAudioFormat;
import io.github.dsheirer.audio.convert.MP3AudioConverter;
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,42 +44,44 @@ 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;
}
}
@@ -92,12 +94,13 @@ public class BroadcastFactory
* @param configuration containing the requested output audio format
* @return audio convert or null
*/
public static IAudioConverter getAudioConverter(BroadcastConfiguration configuration)
public static IAudioConverter getAudioConverter(BroadcastConfiguration configuration, InputAudioFormat inputAudioFormat,
MP3Setting mp3Setting)
{
switch(configuration.getBroadcastFormat())
{
case MP3:
return new MP3AudioConverter(MP3_MONO_16_KHZ_BITRATE, MP3_CONSTANT_BITRATE);
return new MP3AudioConverter(inputAudioFormat, mp3Setting);
default:
mLog.info("Unrecognized broadcastAudio format: " + configuration.getBroadcastFormat().name());
}
@@ -137,12 +140,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");

View File

@@ -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)
{

View File

@@ -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;

View File

@@ -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
*/
@@ -417,38 +449,48 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
{
List<Identifier> toIdentifiers = audioRecording.getIdentifierCollection().getIdentifiers(Role.TO);
if(toIdentifiers.size() >= 1)
//Format a patch group first, if there are multiples
for(Identifier identifier: toIdentifiers)
{
Identifier to = toIdentifiers.get(0);
if(to instanceof TalkgroupIdentifier)
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
{
return format(patchGroupIdentifier);
}
}
for(Identifier identifier: toIdentifiers)
{
if(identifier instanceof TalkgroupIdentifier talkgroupIdentifier)
{
TalkgroupIdentifier talkgroupIdentifier = (TalkgroupIdentifier)to;
return String.valueOf(RadioReferenceDecoder.convertToRadioReferenceTalkgroup(talkgroupIdentifier.getValue(),
talkgroupIdentifier.getProtocol()));
talkgroupIdentifier.getProtocol()));
}
else if(to instanceof PatchGroupIdentifier)
else if(identifier instanceof RadioIdentifier radioIdentifier)
{
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();
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

View File

@@ -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()
{

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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();
}
}

View File

@@ -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()
{

View File

@@ -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;

View File

@@ -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);

View File

@@ -33,6 +33,7 @@ public enum IcecastHeader
PUBLIC("ice-public"),
URL("ice-url"),
USER_AGENT("User-Agent"),
METAINT("icy-metaint"),
UNKNOWN("unknown");

View File

@@ -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();
}
}

View File

@@ -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());
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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();

View File

@@ -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

View File

@@ -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();

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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
*

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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);
};
}
}

View File

@@ -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,135 @@
*
* 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 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.ArrayList;
import java.util.Arrays;
import java.util.Collections;
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;
/**
* 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
*/
public MP3AudioConverter(int bitRate, boolean variableBitRate)
public MP3AudioConverter(InputAudioFormat inputAudioFormat, MP3Setting setting)
{
mEncoder = new LameEncoder(AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO,
bitRate, MPEGMode.MONO, AUDIO_QUALITY, variableBitRate);
mInputAudioFormat = inputAudioFormat;
mEncoder = LameFactory.getLameEncoder(inputAudioFormat, setting);
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<>();
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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);

View 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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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,15 +14,14 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
* ****************************************************************************
*/
package io.github.dsheirer.audio.convert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class MP3SilenceGenerator implements ISilenceGenerator
@@ -31,47 +30,42 @@ public class MP3SilenceGenerator implements ISilenceGenerator
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);
}
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 +94,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");
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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))

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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] );
}
}

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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()
{

View File

@@ -0,0 +1,154 @@
/*
* *****************************************************************************
* 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) / 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);
}
}
}

View File

@@ -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.buffer;
import java.nio.ByteBuffer;
/**
* Implements a factory for creating ByteNativeBuffer instances
*/
public class ByteNativeBufferFactory implements INativeBufferFactory
{
private static final float DC_FILTER_GAIN = 0.007f; //Normalizes DC over period of ~142 (1 / .007) buffers
private float mAverageDc = 0.0f;
@Override
public INativeBuffer getBuffer(ByteBuffer samples, long timestamp)
{
byte[] copy = new byte[samples.capacity()];
samples.get(copy);
calculateDc(copy);
return new ByteNativeBuffer(copy, timestamp, mAverageDc);
}
/**
* 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)
{
double dcAccumulator = 0;
for(byte sample: samples)
{
dcAccumulator += (sample & 0xFF);
}
dcAccumulator /= samples.length;
dcAccumulator -= 127.0f;
dcAccumulator /= 128.0f;
dcAccumulator -= mAverageDc;
mAverageDc += (dcAccumulator * DC_FILTER_GAIN);
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View 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);
}
}
}

View 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();
}

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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;
/**

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,68 @@
/*
* *****************************************************************************
* 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 SignedByteNativeBuffer instances
*/
public class SignedByteNativeBufferFactory implements INativeBufferFactory
{
private static final float DC_FILTER_GAIN = 0.02f; //Normalizes DC over period of ~50 (1 / .02) buffers
private float mIAverageDc = 0.0f;
private float mQAverageDc = 0.0f;
@Override
public INativeBuffer getBuffer(ByteBuffer samples, long timestamp)
{
byte[] copy = new byte[samples.capacity()];
samples.get(copy);
calculateDc(copy);
return new SignedByteNativeBuffer(copy, timestamp, mIAverageDc, mQAverageDc);
}
/**
* 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)
{
double iDcAccumulator = 0;
double 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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
};
}
}

View File

@@ -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)];
}
}
}

View File

@@ -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();
}

View File

@@ -0,0 +1,98 @@
/*
* *****************************************************************************
* 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;
/**
* Scalar implementation of airspy sample converter for un-packed samples.
*/
public class ScalarPackedSampleConverter implements IAirspySampleConverter
{
private static final float DC_FILTER_GAIN = 0.007f; //Normalizes DC over period of ~142 (1 / .007) buffers
private float mAverageDc = 0.0f;
@Override
public short[] convert(ByteBuffer buffer)
{
int offset = 0;
long dcAccumulator = 0;
short sample;
short[] samples;
byte b1, b2, b3;
samples = new short[buffer.capacity() / 3 * 2];
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;
}
//Calculate the average scaled DC offset so that it can be applied in the native buffer's converted samples
float averageDcNow = ((float)dcAccumulator / (float)samples.length) - 2048.0f;
averageDcNow *= AirspyBufferIterator.SCALE_SIGNED_12_BIT_TO_FLOAT;
averageDcNow -= mAverageDc;
mAverageDc += (averageDcNow * DC_FILTER_GAIN);
return samples;
}
@Override
public float getAverageDc()
{
return mAverageDc;
}
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");
}
}

View File

@@ -0,0 +1,66 @@
/*
* *****************************************************************************
* 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;
/**
* Scalar implementation of airspy sample converter for un-packed samples.
*/
public class ScalarUnpackedSampleConverter implements IAirspySampleConverter
{
private static final float DC_FILTER_GAIN = 0.007f; //Normalizes DC over period of ~142 (1 / .007) buffers
private float mAverageDc = 0.0f;
@Override
public short[] convert(ByteBuffer buffer)
{
int offset = 0;
long dcAccumulator = 0;
short sample;
short[] samples;
byte b1, b2;
samples = new short[buffer.capacity() / 2];
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;
}
//Calculate the average scaled DC offset so that it can be applied in the native buffer's converted samples
float averageDcNow = ((float)dcAccumulator / (float)samples.length) - 2048.0f;
averageDcNow *= AirspyBufferIterator.SCALE_SIGNED_12_BIT_TO_FLOAT;
averageDcNow -= mAverageDc;
mAverageDc += (averageDcNow * DC_FILTER_GAIN);
return samples;
}
@Override
public float getAverageDc()
{
return mAverageDc;
}
}

View File

@@ -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 jdk.incubator.vector.ShortVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
import java.nio.ByteBuffer;
/**
* 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;
private static final float DC_FILTER_GAIN = 0.007f; //Normalizes DC over period of ~142 (1 / .007) buffers
private float mAverageDc = 0.0f;
@Override
public short[] convert(ByteBuffer buffer)
{
long dcAccumulator = 0;
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()];
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;
}
//Calculate the average scaled DC offset so that it can be applied in the native buffer's converted samples
float averageDcNow = ((float)dcAccumulator / (float)samples.length) - 2048.0f;
averageDcNow *= AirspyBufferIterator.SCALE_SIGNED_12_BIT_TO_FLOAT;
averageDcNow -= mAverageDc;
mAverageDc += (averageDcNow * DC_FILTER_GAIN);
return samples;
}
@Override
public float getAverageDc()
{
return mAverageDc;
}
}

View File

@@ -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.channel.metadata;

View File

@@ -22,6 +22,7 @@ package io.github.dsheirer.channel.metadata;
import com.jidesoft.swing.JideSplitPane;
import com.jidesoft.swing.JideTabbedPane;
import io.github.dsheirer.channel.details.ChannelDetailPanel;
import io.github.dsheirer.gui.power.ChannelPowerPanel;
import io.github.dsheirer.icon.IconModel;
import io.github.dsheirer.module.decode.event.DecodeEventPanel;
import io.github.dsheirer.module.decode.event.MessageActivityPanel;
@@ -38,6 +39,7 @@ public class NowPlayingPanel extends JPanel
private ChannelDetailPanel mChannelDetailPanel;
private DecodeEventPanel mDecodeEventPanel;
private MessageActivityPanel mMessageActivityPanel;
private ChannelPowerPanel mChannelPowerPanel;
/**
* GUI panel that combines the currently decoding channels metadata table and viewers for channel details,
@@ -49,6 +51,7 @@ public class NowPlayingPanel extends JPanel
mDecodeEventPanel = new DecodeEventPanel(iconModel, userPreferences, playlistManager.getAliasModel());
mMessageActivityPanel = new MessageActivityPanel(userPreferences);
mChannelMetadataPanel = new ChannelMetadataPanel(playlistManager, iconModel, userPreferences);
mChannelPowerPanel = new ChannelPowerPanel(playlistManager);
init();
}
@@ -61,6 +64,7 @@ public class NowPlayingPanel extends JPanel
tabbedPane.addTab("Details", mChannelDetailPanel);
tabbedPane.addTab("Events", mDecodeEventPanel);
tabbedPane.addTab("Messages", mMessageActivityPanel);
tabbedPane.addTab("Channel", mChannelPowerPanel);
tabbedPane.setFont(this.getFont());
tabbedPane.setForeground(Color.BLACK);
@@ -73,5 +77,6 @@ public class NowPlayingPanel extends JPanel
mChannelMetadataPanel.addProcessingChainSelectionListener(mChannelDetailPanel);
mChannelMetadataPanel.addProcessingChainSelectionListener(mDecodeEventPanel);
mChannelMetadataPanel.addProcessingChainSelectionListener(mMessageActivityPanel);
mChannelMetadataPanel.addProcessingChainSelectionListener(mChannelPowerPanel);
}
}

View File

@@ -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.channel.state;
@@ -57,6 +54,9 @@ public abstract class AbstractChannelState extends Module implements IChannelEve
protected boolean mSourceOverflow = false;
private HeartbeatReceiver mHeartbeatReceiver = new HeartbeatReceiver();
//TODO: remove the IOverflowListener code from this class
/**
* Constructs an instance
* @param channel configuration

View File

@@ -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.channel.state;

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