Compare commits

...

148 Commits

Author SHA1 Message Date
Denny Sheirer
421da1c1ce Cherry Pick: #1405 polyphase channelizer thread leak. 2023-03-05 06:34:29 -05:00
Denny Sheirer
2412833899 Updates release to 0.5.2 2023-03-05 06:32:56 -05:00
Denny Sheirer
9abf64c6ab #1449 Updates version to 0.5.1 final (#1451)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-02-19 07:17:10 -05:00
Denny Sheirer
1702103f5d #1449 Updates version to 0.5.1 beta-2 (#1450)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-02-19 07:10:44 -05:00
Denny Sheirer
8f68a989b9 #1444 Adds: (#1447)
* DMR Recording viewer utility
* DMR talker aliases
* DMR SMS messages
* DMR Hytera Tier-III compressed talkgroups support
* DMR UDT packet sequences
* DMR Hytera Aloha and other unknown CSBK and Link Control messages

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-02-18 08:36:55 -05:00
Denny Sheirer
b51a0102ef #1422 Resolves issue where MotoTRBO IP Site Connect and Capacity Plus voice group user message not decoding Radio & Talkgroup IDs correctly. Creates standalone application for viewing DMR messages from recordings. (#1442)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-02-11 15:32:41 -05:00
Denny Sheirer
e177c2c6b0 #1426 Adds JRE accessibility module to support assistive technologies. (#1427)
Co-authored-by: Denny Sheirer <sheirerd@ainfosec.com>
2023-01-21 07:21:00 -05:00
Denny Sheirer
3567fdf51d #1393 Updates DMR Talkgroup and Radio ID editors to use correct formatting. Patch submitted by @hockeyref2 (#1425)
Co-authored-by: Denny Sheirer <sheirerd@ainfosec.com>
2023-01-20 06:50:18 -05:00
Denny Sheirer
323097ba65 #1423 Updates Icecast/Broadcastify feed to attempt reconnect following a mountpoint in use error response. (#1424)
Co-authored-by: Denny Sheirer <sheirerd@ainfosec.com>
2023-01-20 05:39:38 -05:00
Denny Sheirer
33f2bab7ac #1419 Reduce logging from USBTunerController for (temporary) failed transfer buffer resubmits. (#1420)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-01-18 04:38:30 -05:00
Denny Sheirer
c063cfb201 #1416 Updates P25-P1 To Expose ICMP & Unknown Packet Events in Event History View (#1417)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-01-16 05:31:27 -05:00
Denny Sheirer
e99aa3d3b4 #1411 Updates version to 0.5.0 beta1 (#1412)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-01-15 06:36:22 -05:00
Denny Sheirer
ec162ff84c #1408 Updates USB tuners to enhance transfer buffer error handling and logging. When an error occurs while (re)submitting a transfer buffer to LibUsb to fill with sample data, the buffer is temporarily placed in an error queue. Upon successfully submitting the next transfer buffer, we attempt to resubmit a buffer from the error queue, so that we wait until the device can accept buffers before trying to resubmit. If all transfer buffers are exhausted because they are in the error queue, then the tuner gets shutdown with an error state ... there's really no way to recover at this point. (#1409)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-01-15 06:16:37 -05:00
Denny Sheirer
f0db2865f2 #1397 Resolves issue where decoding channels go IDLE after period of time. Isolated the cause to a multi-threading issue where the polyphase channel manager receives notification that the tuner center frequency has changed and sets a flag to update each of the channels. This happens concurrent with a previous tuner center frequency change where the flag gets set and the second thread is updating the output processors with the previous frequency change and then unsets the frequency change flag, completely missing the second/latest frequency change update and causing the channels to all tune-out their desired signals as the tuner center frequency changes and the output channels continue mixing to the previous frequency. (#1407)
Identified and resolved issue where a 2-stream polyphase synthesizer was being updated with an incorrectly designed synthesis filter which was causing massive droop on the second half of the reconstructed channel.  Oddly enough, this didn't seem to impact the P25 decoders, but it may have impacted other decoding.

Identified and resolved issue with concurrent modification of the modules list in the processing chain associated with recent addition of the channel FFT view in the channel power tab.

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-01-15 04:12:42 -05:00
Denny Sheirer
f97b2150b9 #1397 Adds channel fft frequency overlay, cursor readout, pll tracking indicator, and channel bandwidth lines. (#1406)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-01-13 04:28:00 -05:00
Denny Sheirer
8ce8aaddf9 #1399 Resolves device ID parsing issue for Motorola ARS Device Registration. (#1400)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-01-06 04:17:57 -05:00
Denny Sheirer
e4a85a4ed7 #1386 Updates version to 0.5.0 (final) (#1387)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-25 07:11:25 -05:00
Denny Sheirer
f61a415b43 #1383 Updates tuner editor to resolve sequencing issue where a recently enabled tuner was having its enabled state set to true and then enabling the tuner, which caused the tuner editor to mis-configure the tuner editor panel. (#1385)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-25 07:03:28 -05:00
Denny Sheirer
29ed3b4553 #1287 Disables event panel filtering until the feature is fully implemented with #1368. The current partial implementation is preventing valid events from displaying for protocols like DMR and likely others. (#1384)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-25 06:45:50 -05:00
Denny Sheirer
a87309e792 #1378 Resolves potential error where tuner shutdown (because of USB I/O error) hangs and causes application to run out of memory. This patch doesn't resolve the original issue with the USB I/O error, but it does ensure that the tuner shutdown which is happening on the USB processing thread, can complete in a timely fashion so that the application doesn't (potentially) crash from out of memory. (#1382)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-24 09:10:17 -05:00
Denny Sheirer
ddb5b5ef87 #1380 Improves Icecast streaming error handling and preserves streaming error messages for broadcast state to display in streaming status panel. Resolves issue with streaming status panel not showing icons and stream type. (#1381)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-24 07:48:04 -05:00
Denny Sheirer
7a58bf85b6 #1312 Resolves issue with radio reference editor attempting to access county info for a site that has county ID set to 0 (ie no county specified). (#1379)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-24 04:45:21 -05:00
Doug Hoffman
8707eb2c88 Correctly identify padded MP3 frames (#1375) 2022-12-23 06:12:44 -05:00
Denny Sheirer
08499ac73f #1237 Ensure MP3 input sample rate is supported by LAME encoder settings. (#1376)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-23 06:06:10 -05:00
Dennis Sheirer
70170ff70f Revert "Disable resampling if requested sample rate not supported"
This reverts commit 7de28519a1.
2022-12-18 08:29:35 -05:00
Denny Sheirer
bad5589c0d #1369 Updates to Version 0.5.0 Beta 7 (#1370)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-18 07:17:23 -05:00
Doug Hoffman
7de28519a1 Disable resampling if requested sample rate not supported 2022-12-18 04:57:21 -05:00
Doug Hoffman
bbd907514e Handle streaming audio as frames (#1244) 2022-12-18 04:46:07 -05:00
Denny Sheirer
4a68cd9407 #1364 Enhancements to resolve issue with tuner shutdown hang on application shutdown. (#1365)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-18 04:29:55 -05:00
Denny Sheirer
dc327a2855 #1362 Updates heterodyne channelizer with alternate sorted set implementation to resolve issue when determining if the tuner can tune a requested channel. (#1363)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-17 09:28:29 -05:00
Denny Sheirer
9af593a785 #1360 Resolves issue with Polyphase channelizer and decoded message timestamps for P25 and other protocols. (#1361)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-17 08:50:29 -05:00
Denny Sheirer
24f4c22cdd #1358 Resolves issue where decoded AMBE (DMR & P25-2) tone identifiers are not sent to the alias action manager. (#1359)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-17 07:35:16 -05:00
Denny Sheirer
bc462b9652 #1351 Restores semi-accurate timestamps of sample buffers to ensure P25 & DMR decoded messages have accurate timestamps and MBE recording voice frames have accurate timestamps. (#1357)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-17 06:39:35 -05:00
Denny Sheirer
047dec6da1 #1355 Updates logging libraries to latest version and updates logging libraries to latest version. (#1356)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-17 04:06:44 -05:00
Denny Sheirer
bf935555bb #1353 Resolves issue where polyphase channelizer would mis-tune channels after a period of time resulting in idle decoding channels. (#1354)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-17 03:54:50 -05:00
Dennis Sheirer
efe36b0682 Updates README 2022-12-03 09:29:20 -05:00
Denny Sheirer
a18d4498aa #1346 Updates radio reference library to version 15.1.9 (#1347)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-11-25 09:11:10 -05:00
Denny Sheirer
803dc2531c #1318 Resolves issue where bitstream recorder no longer works. Solution submitted by @apankowski. (#1345)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-11-24 06:16:44 -05:00
Denny Sheirer
1b062bcd06 #1343 Resolves issue where user-initiated FFT size change causes waterfall panel pixel array to be updated/resized while a memory image source repaint has been requested and the parameters become incongruent, causing an array index out of bounds error. Pushes the pixel array update operation onto the swing thread to ensure only a single thread is modifying the pixel map for rendering. (#1344)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-11-24 04:23:48 -05:00
Denny Sheirer
a24c3736f4 #1340 Updates tuner and frequency controllers to apply locking to ensure multi-threaded access to freuqency control planes doesn't allow center tuned frequency to become indeterminant which can result in channels being mistuned and stop decoding. (#1341)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-11-20 07:55:30 -05:00
Denny Sheirer
ac61b681e6 #1338 Updates logger to include date in log entries. Removes info logger when creating channel log files. (#1339)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-11-20 05:54:05 -05:00
Denny Sheirer
0866bfd555 #1336 Resolves issue with color setting changes not inheriting the correct translucency which causes the channel overlays in the spectral display to be solid color instead of semi-transparent. (#1337)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-11-20 05:43:28 -05:00
Denny Sheirer
79fed701d9 #1337 Updates build to use JDK19 for distribution images and updates all 3rd-party libraries to latest. Note: build system continues to be compatible with JDK 17 and 18, but Gradle 7.6 to build distribution images. Gradle 7.6 is not released yet, but RC3 is available. (#1335)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-11-20 05:36:41 -05:00
Denny Sheirer
48a93990e8 #1291 Updates DC removal processing across tuner sample streams to run on a periodic timer to reduce overall processing requirements. Updates build version to 0.5.0 beta 5 (#1292)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-07-23 08:14:31 -04:00
Denny Sheirer
414ba25d61 #1284 Resolves issue with inherent DC bias (-0.5) in RTL2832 sample conversion code. (#1285)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-07-16 08:58:09 -04:00
Shahar
f67f90e8b0 moved RecordingTuner ChannelizerType set to after the recording tuner was started. This way the sample rate is not 0. (#1279) 2022-07-11 04:34:17 -04:00
Dennis Sheirer
5751c70212 Updates to release versio 0.5.0 2022-07-11 04:21:03 -04:00
Denny Sheirer
4e15c426a8 #1253 Updates USB tuner controller to handle LibUsb bug where on resubmitting a completed transfer LibUsb tells us that the transfer is already submitted. This is happening on some Window systems. (#1263)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-05-24 04:11:26 -04:00
Denny Sheirer
ceda189260 #1179 Resolves issue with JMBE Creator running on OSX. (#1262)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-05-24 03:57:33 -04:00
Denny Sheirer
fce0ae33e7 #1181 Updates libusb osx aarch64 driver library and gradle build. (#1261)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-05-23 04:06:42 -04:00
Denny Sheirer
fc5c6ef841 #1257 restores recording tuner feature. sdrtrunk now supports multiple recording tuners. Recordings are automatically loaded/restored on application start and each recording tuner has to be manually enabled for use. (#1258)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-05-22 08:24:42 -04:00
Denny Sheirer
d0033578d5 #1181 Adds logging for mac users to install libusb if sdrtrunk can't load library. (#1251)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-05-15 07:05:53 -04:00
Dennis Sheirer
670ec3a097 Adds debug logging to USB tuner controller. 2022-05-15 06:37:25 -04:00
Denny Sheirer
8bfb1d5470 #1243 Updates USB tuner error handling to prevent stack overflow when tuner error occurs while streaming. (#1247)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-05-09 07:16:35 -04:00
Denny Sheirer
46a916a945 #1245 Updates spectral display show tuner options to allow new display without changing disabled state of primary display. (#1246)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-05-08 05:01:52 -04:00
Denny Sheirer
459e77390a #1241 Adds user preference to enable/disable audio normalization prior to MP3 encoding/recording. (#1242)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-05-07 07:11:40 -04:00
Doug Hoffman
f4ab46b79d Prefer talkgroup ID over radio ID for Calls (#1227) 2022-05-07 06:51:27 -04:00
Denny Sheirer
3c58e98788 #1239 Resolves issue where a user can't view the spectrum of a tuner when the spectral display is disabled. Tuner editor button overrides the property setting so that the view completes correctly. (#1240)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-05-07 06:36:42 -04:00
Doug Hoffman
36de81248c Don't show tuner on spectral display while disabled (#1236) 2022-05-07 06:30:11 -04:00
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
Denny Sheirer
f4f3db7436 #973 Adds concurrent locking to primary tuner controller methods. (#983)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-25 08:59:06 -04:00
Denny
3ebfa90ca1 Updates build to 0.5.0 Alpha 5 2020-10-25 07:51:13 -04:00
Denny Sheirer
733fdd0d7a #981 Sets channel short title as ignore for serialization. (#982)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-25 07:49:12 -04:00
Noah Lynch
09893605eb Only update ViewByIdentifierEditor's list when the editor is selected (#944)
* Add type parameters to TableColumns to fix compiler warnings

* Only update AliasViewByIdentifierEditor list when the editor is selected

This prevents the editor's list from being rebuilt in the background as
aliases are updated.  This caused a massive slowdown when editing many
aliases with the AliasBulkEditor.

Bulk editing 1000 aliases apply time reduced from over a minute to under
5 seconds.
2020-10-25 06:54:04 -04:00
Robert Scullin
57468ef932 Fixes #740 - Adds Encrypted state to the Status column for P25 Phase 1 (#975) 2020-10-25 06:46:02 -04:00
Robert Scullin
c6afb1c133 Fixes #815 - Change usable bandwidth percentage based on Airspy samplerate (#976) 2020-10-25 06:33:25 -04:00
Denny Sheirer
05c0b51fde #979 Updates DMR Tier III channel number issues and provides user option to ignore CRC checksums for DMR decoder with RAS-enabled systems. Adds support for Tier III traffic channel system parameters SLCO message. (#980)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-25 06:09:57 -04:00
Denny Sheirer
de8cfa24f9 #959 Updates DMR decode event tracking and Con+ channel grant management. (#978)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-25 04:51:36 -04:00
Denny Sheirer
fe714c69a7 #956 Resolves issue with null pointer in identifier collection on channel start. (#977)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-24 04:43:15 -04:00
Denny Sheirer
ac219cf2ba #969 Resolves issue where P25P2 NAC value is saved incorrectly by configuration editor. (#970)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-17 04:47:48 -04:00
Denny Sheirer
77b1102100 #942 Adds channel number to neighbor report logging. (#965)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-16 07:02:29 -04:00
Denny Sheirer
5117e6cbe8 #961 Resolves issue where invoking channel actions from Swing thread causes error on JavaFX GUI thread. Removes the Start/Disable/Delete commands from the Swing menu options and replaces with a single View/Edit call to the JavaFX channel editor. Although this reduces ease/functionality, it sidesteps code complications until the main GUI can be recreated in JavaFX. (#964)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-16 06:38:05 -04:00
Denny Sheirer
61c4e13c3e #954 Channel rotation delay default value too small for MPT1327 and P25 channels. (#963)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-16 06:04:17 -04:00
Denny Sheirer
f1190b7be3 #960 Adds handling to broadcastify streaming to not log an error when broadcastify tells us to skip an audio upload. This is not an error because another broadcaster has already uploaded the audio segment. (#962)
Co-authored-by: Denny <denny@denny-desktop>
2020-10-16 05:19:50 -04:00
842 changed files with 53480 additions and 30758 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>

5
.idea/misc.xml generated
View File

@@ -1,7 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_13" default="false" project-jdk-name="15" project-jdk-type="JavaSDK">
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="liberica-19.0.1.fx" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

View File

@@ -1,67 +1,24 @@
![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).
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/GettingStarted_V0.3.0)
* [User's Manual Version 0.3.0 and 0.4.0](https://github.com/DSheirer/sdrtrunk/wiki/UserManual_V0.3.0)
* [Playlist Editor User Manual Version 0.5.0](https://github.com/DSheirer/sdrtrunk/wiki/Playlist-Editor)
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/Getting-Started)
* [User's Manual](https://github.com/DSheirer/sdrtrunk/wiki/User-Manual)
* [Download](https://github.com/DSheirer/sdrtrunk/releases)
* [Support Group](https://groups.google.com/forum/#!forum/sdrtrunk)
* [Discord Support Channel](https://discord.gg/HJQaKYE)
* [Support](https://github.com/DSheirer/sdrtrunk/wiki/Support)
![sdrtrunk Application Overview - Version 0.3.0](https://github.com/DSheirer/sdrtrunk/wiki/v0.3/images/ApplicationOverview_V0.3.0.png)
**Figure 1:** sdrtrunk **Version 0.3.0** Application Screenshot
![sdrtrunk Application](https://github.com/DSheirer/sdrtrunk/wiki/images/sdrtrunk.png)
**Figure 1:** sdrtrunk Application Screenshot
# End User Instructions:
If you simply want to download and run the program, please follow these instructions.
## Download the latest sdrtrunk release for your operating system
## Download the Latest Release
All release versions of sdrtrunk are available from the [releases](https://github.com/DSheirer/sdrtrunk/releases) tab.
* **(alpha)** These versions are under development feature previews and likely to contain bugs and unexpected behavior.
* **(beta)** These versions are currently being tested for bugs and functionality prior to final release.
* **(final)** These versions have been tested and are the current release version.
## Unzip the release
Use 7-zip or any zip utility to unzip the release file
## Start the application
Once unzipped, open a command prompt to where you unzipped the release. Change to the **/bin** directory and use the launch script to start the application:
* **Windows** sdr-trunk.bat
* **Linux/OSX** ./sdr-trunk
## Optional - P25 Audio
If you're using sdrtrunk with a P25 trunked radio system, the [JMBE](https://github.com/DSheirer/sdrtrunk/wiki/JMBE) wiki page contains instructions for downloading the JMBE audio library source code and compiling the JMBE library. Once you have compiled the library, launch the sdrtrunk application. From the menu bar, choose **View >> Preferences**. In the **JMBE Audio Codec** section, update the path to where your compiled JMBE library is located. Any channels that are started after you set the path will be able to produce P25 audio.
## Minimum System Requirements
* **Operating System:** Windows (~~32 or~~ 64-bit), Linux (~~32 or~~ 64-bit) or Mac/Linux (64-bit, 10.14 or higher)
* **CPU:** 4-core
* **RAM:** 8GB or more (preferred). Depending on usage, 4GB may be sufficient.
# Developer Instructions:
If you're interested in modifying and/or compiling the source code, please follow these instructions to use gradle to compile the code.
## Build the project
sdrtrunk uses the gradle build system. This requires OpenJDK 11 or higher installed on your local compuber. Use the gradle wrapper to build the source code:
### Linux
```
./gradlew clean build
```
### Windows
```
gradlew.bat clean build
```
The **/build/distributions** folder will contain the zip file of the compiled program. Unzip it and launch the program from the scripts in the **/bin** directory.
## Development
All dependencies/versions are controlled from build.gradle.
To change the new release version tag of artifact - change property:
```
version = '0.5.0'
```

View File

@@ -1,117 +1,108 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
plugins {
id 'application'
id 'java'
id 'idea'
id 'org.openjfx.javafxplugin' version '0.0.8'
id 'org.beryx.runtime' version '1.11.4'
id 'org.beryx.runtime' version '1.12.7'
}
repositories {
jcenter()
mavenCentral()
maven { url "https://jitpack.io" }
}
version = '0.5.0-alpha4'
sourceCompatibility = '13'
version = '0.5.2'
sourceCompatibility = '17'
sourceSets {
main.java.srcDirs 'src/main'
test.java.srcDirs 'src/test'
}
test {
useJUnitPlatform()
}
dependencies {
testImplementation 'junit:junit:4.12'
testImplementation 'org.assertj:assertj-core:3.8.0'
// JUnit Tests
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.assertj:assertj-core:3.23.1'
testImplementation('org.junit.jupiter:junit-jupiter-api:5.9.0')
testImplementation 'org.mockito:mockito-core:4.8.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
//Jitpack imports
implementation 'com.github.nwaldispuehl:java-lame:v3.98.4'
implementation 'com.github.dnault:libresample4j:master'
implementation 'com.github.DSheirer.jmbe:jmbe-api:1.0.0'
implementation 'com.github.nwaldispuehl:java-lame:v3.98.4'
//mavenCentral/jcenter imports
implementation 'ch.qos.logback:logback-core:1.2.3'
implementation 'ch.qos.logback:logback-classic:1.2.3'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.8'
implementation 'com.fazecast:jSerialComm:2.5.0'
implementation 'ch.qos.logback:logback-classic:1.4.5'
implementation 'ch.qos.logback:logback-core:1.4.5'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.14.0'
implementation 'com.fazecast:jSerialComm:2.9.3'
implementation 'com.github.jiconfont:jiconfont-font_awesome:4.7.0.1'
implementation 'com.github.jiconfont:jiconfont-javafx:1.0.0'
implementation 'com.github.jiconfont:jiconfont-swing:1.0.1'
implementation 'com.github.wendykierp:JTransforms:3.1'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.google.guava:guava:27.0.1-jre'
implementation 'com.google.code.gson:gson:2.10'
implementation 'com.google.guava:guava:31.1-jre'
implementation 'com.jidesoft:jide-oss:3.6.18'
implementation 'com.miglayout:miglayout-swing:5.2'
implementation 'com.miglayout:miglayout-swing:11.0'
implementation 'com.mpatric:mp3agic:0.9.1'
implementation 'commons-io:commons-io:2.11.0'
implementation 'eu.hansolo:charts:1.0.5'
implementation 'io.github.dsheirer:radio-reference-api:15.1.8'
implementation 'io.github.dsheirer:radio-reference-api:15.1.9'
implementation 'javax.usb:usb-api:1.0.2'
implementation 'net.coderazzi:tablefilter-swing:5.4.0'
implementation 'org.apache.commons:commons-compress:1.20'
implementation 'commons-io:commons-io:2.7'
implementation 'org.apache.commons:commons-lang3:3.8.1'
implementation 'net.coderazzi:tablefilter-swing:5.5.4'
implementation 'org.apache.commons:commons-compress:1.21'
implementation 'org.apache.commons:commons-lang3:3.12.0'
implementation 'org.apache.commons:commons-math3:3.6.1'
implementation 'org.apache.mina:mina-core:2.1.3'
implementation 'org.apache.mina:mina-http:2.1.3'
implementation 'org.controlsfx:controlsfx:11.0.2'
implementation 'org.rauschig:jarchivelib:1.0.0'
implementation 'org.slf4j:slf4j-api:1.7.25'
implementation 'org.apache.commons:commons-csv:1.9.0'
implementation 'org.apache.mina:mina-core:2.2.1'
implementation 'org.apache.mina:mina-http:2.2.1'
implementation 'org.controlsfx:controlsfx:11.1.2'
implementation 'org.rauschig:jarchivelib:1.2.0'
implementation 'org.slf4j:slf4j-api:2.0.5'
implementation 'org.usb4java:libusb4java:1.3.0'
implementation 'org.usb4java:usb4java:1.3.0'
implementation 'org.usb4java:usb4java-javax:1.3.0'
implementation 'io.github.dsheirer:libusb4java-darwin-aarch64:1.3.1' //usb4java native lib for OSX M1 cpu
implementation 'pl.edu.icm:JLargeArrays:1.6'
}
//These must remain as compile options to ensure they are included in the build to produce multi-OS images
compile "org.openjfx:javafx-base:15:win"
compile "org.openjfx:javafx-base:15:linux"
compile "org.openjfx:javafx-base:15:mac"
compile "org.openjfx:javafx-controls:15:win"
compile "org.openjfx:javafx-controls:15:linux"
compile "org.openjfx:javafx-controls:15:mac"
compile "org.openjfx:javafx-graphics:15:win"
compile "org.openjfx:javafx-graphics:15:linux"
compile "org.openjfx:javafx-graphics:15:mac"
compile "org.openjfx:javafx-swing:15:win"
compile "org.openjfx:javafx-swing:15:linux"
compile "org.openjfx:javafx-swing:15:mac"
/**
* 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,111 @@ jar {
}
}
idea {
module {
downloadJavadoc = true
downloadSources = true
}
}
/**
* Java Development Kit (JDK) locations. In order to build OS-specific images, these paths must point to the 'bin'
* directory within a JDK for each of the specified architectures. These JDKs are used by the runtime and runtimeZip
* tasks to produce platform-specific builds. If none of these paths exists, then an image will be created for the
* host OS using the installed JDK.
* Runtime target OS and CPU image names. We append version here so that version details are at end of zip filename.
*/
def jdk_base = '/media/denny/WD250GB/java/bellsoft/'
String targetLinuxAarch64 = 'linux-aarch64-v' + version
String targetLinuxX86_64 = 'linux-x86_64-v' + version
String targetOsxAarch64 = 'osx-aarch64-v' + version
String targetOsxX86_64 = 'osx-x86_64-v' + version
String targetWindowsX86_64 = 'windows-x86_64-v' + version
def jdk_linux_arm64 = jdk_base + 'linux-arm64/jdk-15'
def jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-15'
def jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-15.jdk'
def jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-15'
/**
* Optional pre-downloaded Java Development Kit (JDK) for each target OS and CPU. These locations are hard-coded to the
* author's development environment, but can be changed to your environment if you want to use this build pattern.
*/
String jdk_base = '/home/denny/java_jdks/'
String jdk_linux_aarch64 = jdk_base + 'linux-arm64/jdk-19.0.1-full'
String jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-19.0.1-full'
String jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-19.0.1-full.jdk'
String jdk_osx_aarch64 = jdk_base + 'osx-arm64/jdk-19.0.1-full.jdk'
String jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-19.0.1-full'
def file_jdk_linux_arm64 = new File(jdk_linux_arm64)
def file_jdk_linux_x86_64 = new File(jdk_linux_x86_64)
def file_jdk_osx_x86_64 = new File(jdk_osx_x86_64)
def file_jdk_windows_x86_64 = new File(jdk_windows_x86_64)
boolean hasTargetJdks = file(jdk_linux_x86_64).exists() || file(jdk_linux_aarch64).exists() ||
file(jdk_osx_x86_64).exists() || file(jdk_osx_aarch64).exists() || file(jdk_windows_x86_64).exists()
def hasTargetJdk = file_jdk_linux_x86_64.exists() ||
file_jdk_linux_arm64 ||
file_jdk_osx_x86_64.exists() ||
file_jdk_windows_x86_64.exists()
/**
* Auto-download target JDKs. Defaults to false, or the user can override via the gradle command line by
* including this option: '-PdownloadJdks=true'
*/
boolean downloadJdks = project.hasProperty('downloadJdks') ? project.getProperty('downloadJdks') : false
/**
* Beryx Runtime plugin for creating OS and CPU targeted build(s) of the software.
*
* Option 1: Create a single target build using the locally installed JDK, or optionally create target builds for
* all OS and CPU targets when pre-downloaded target JDKs are available (see 'jdk_*' properties above).
*
* >: gradle runtimeZip
*
* Option 2: Create all OS and CPU target builds by automatically downloading all JDKs needed to build the target images
*
* >: gradle runtimeZip -PdownloadJdks=true
*/
runtime {
if(file_jdk_linux_arm64.exists())
{
targetPlatform('linux-arm64-v' + version, jdk_linux_arm64)
//Build by automatically downloading JDKs for the OS and CPU targets
if(downloadJdks) {
println("Building all runtime images")
println("The [ > jre: ] build phase downloads several target JDKs and this may take a while ... please wait")
targetPlatform(targetLinuxAarch64) {
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-linux-aarch64-full.tar.gz")
}
targetPlatform(targetLinuxX86_64) {
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-linux-amd64-full.tar.gz")
}
targetPlatform(targetOsxAarch64) {
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-macos-aarch64-full.tar.gz")
}
targetPlatform(targetOsxX86_64) {
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-macos-amd64-full.tar.gz")
}
targetPlatform(targetWindowsX86_64) {
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-windows-amd64-full.zip")
}
}
else
{
println("Skipping OS Image - Linux ARM 64-bit JDK was not found at " + jdk_linux_arm64)
//Build using pre-downloaded JDKs
else if(hasTargetJdks) {
println("Building all runtime images using pre-downloaded JDKs")
if(file(jdk_linux_aarch64).exists()) {
targetPlatform(targetLinuxAarch64, jdk_linux_aarch64)
}
if(file(jdk_linux_x86_64).exists()) {
targetPlatform(targetLinuxX86_64, jdk_linux_x86_64)
}
if(file(jdk_osx_aarch64).exists()) {
targetPlatform(targetOsxAarch64, jdk_osx_aarch64)
}
if(file(jdk_osx_x86_64).exists()) {
targetPlatform(targetOsxX86_64, jdk_osx_x86_64)
}
if(file(jdk_windows_x86_64).exists()) {
targetPlatform(targetWindowsX86_64, jdk_windows_x86_64)
}
}
//Build using locally installed JDK only
else {
println("Building a single runtime image using locally installed JDK")
}
if(file_jdk_linux_x86_64.exists())
{
targetPlatform('linux-x86_64-v' + version, jdk_linux_x86_64)
}
else
{
println("Skipping OS Image - Linux x86 64-bit JDK was not found at " + jdk_linux_x86_64)
}
//Additional modules to include with auto-detected modules.
//jdk.crypto.ec needed for HTTPS connections (broadcastify calls & map tile server)
//jdk.accessibility is used with assistive technologies like screen readers
modules = ['jdk.crypto.ec','jdk.accessibility']
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")
}

Binary file not shown.

View File

@@ -1,5 +1,25 @@
#
# ******************************************************************************
# Copyright (C) 2014-2022 Dennis Sheirer
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
# *****************************************************************************
#
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

306
gradlew vendored
View File

@@ -1,94 +1,132 @@
#!/usr/bin/env sh
#!/bin/sh
#
# Copyright 2015 the original author or authors.
# ******************************************************************************
# Copyright (C) 2014-2022 Dennis Sheirer
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# https://www.apache.org/licenses/LICENSE-2.0
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
# *****************************************************************************
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -97,7 +135,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
@@ -105,79 +143,105 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

38
gradlew.bat vendored
View File

@@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,10 +25,14 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@@ -37,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -51,7 +55,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -61,38 +65,26 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal

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,34 +1,30 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.audio.broadcast;
import io.github.dsheirer.audio.convert.AudioFrames;
import io.github.dsheirer.audio.convert.ISilenceGenerator;
import io.github.dsheirer.audio.convert.InputAudioFormat;
import io.github.dsheirer.audio.convert.MP3FrameTools;
import io.github.dsheirer.audio.convert.MP3Setting;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.util.ThreadPool;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Queue;
@@ -36,22 +32,31 @@ import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration> extends AbstractAudioBroadcaster<T>
{
private final static Logger mLog = LoggerFactory.getLogger(AudioStreamingBroadcaster.class);
public static final int PROCESSOR_RUN_INTERVAL_MS = 1000;
private ScheduledFuture mRecordingQueueProcessorFuture;
private ScheduledFuture<?> mRecordingQueueProcessorFuture;
private RecordingQueueProcessor mRecordingQueueProcessor = new RecordingQueueProcessor();
private Queue<AudioRecording> mAudioRecordingQueue = new LinkedTransferQueue<>();
private ISilenceGenerator mSilenceGenerator;
private BroadcastFormat mBroadcastFormat;
private long mDelay;
private long mMaximumRecordingAge;
private AtomicBoolean mStreaming = new AtomicBoolean();
protected boolean mInlineActive = false;
protected int mInlineInterval;
protected int mInlineRemaining = -1;
private int mTimeOverrun = 0;
/**
* AudioBroadcaster for streaming audio recordings to a remote streaming audio server. Audio recordings are
* generated by an internal StreamManager that converts an inbound stream of AudioPackets into a recording of the
@@ -73,12 +78,14 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
* The last audio packet's metadata is automatically attached to the closed audio recording when it is enqueued for
* broadcast. That metadata will be updated on the remote server once the audio recording is opened for streaming.
*/
public AudioStreamingBroadcaster(T broadcastConfiguration)
public AudioStreamingBroadcaster(T broadcastConfiguration, InputAudioFormat inputAudioFormat, MP3Setting mp3Setting)
{
super(broadcastConfiguration);
mBroadcastFormat = broadcastConfiguration.getBroadcastFormat();
mDelay = getBroadcastConfiguration().getDelay();
mMaximumRecordingAge = getBroadcastConfiguration().getMaximumRecordingAge();
mSilenceGenerator = BroadcastFactory.getSilenceGenerator(broadcastConfiguration.getBroadcastFormat());
mSilenceGenerator = BroadcastFactory.getSilenceGenerator(broadcastConfiguration.getBroadcastFormat(),
inputAudioFormat, mp3Setting);
}
public void dispose()
@@ -88,7 +95,7 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
/**
* Broadcast binary audio data frames or sequences.
*/
protected abstract void broadcastAudio(byte[] audio);
protected abstract void broadcastAudio(byte[] audio, IdentifierCollection identifierCollection);
/**
* Protocol-specific metadata updater
@@ -204,13 +211,15 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
super.setBroadcastState(state);
if(mBroadcastState.get() instanceof BroadcastState && ((BroadcastState)mBroadcastState.get()).isErrorState())
if(mBroadcastState.get() != null && mBroadcastState.get().isErrorState())
{
stop();
}
if(!connected())
{
//Reset inline metadata
mInlineRemaining = -1;
//Remove all pending audio recordings
while(!mAudioRecordingQueue.isEmpty())
{
@@ -242,7 +251,6 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
public boolean canConnect()
{
BroadcastState state = getBroadcastState();
return state != BroadcastState.CONNECTED && !state.isErrorState();
}
@@ -263,10 +271,8 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
public class RecordingQueueProcessor implements Runnable
{
private AtomicBoolean mProcessing = new AtomicBoolean();
private ByteArrayInputStream mInputStream;
private long mFinalSilencePadding = 0;
private int mBytesStreamedActual = 0;
private int mBytesStreamedRequired = 0;
private AudioFrames mInputFrames;
private IdentifierCollection mInputIdentifierCollection;
@Override
public void run()
@@ -275,45 +281,35 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
{
try
{
if(mInputStream == null || mInputStream.available() <= 0)
{
if(mFinalSilencePadding > 0)
{
broadcastAudio(mSilenceGenerator.generate(mFinalSilencePadding));
mFinalSilencePadding = 0;
}
int timeSent = 0;
if(mInputFrames == null || !mInputFrames.hasNextFrame())
{
nextRecording();
}
if(mInputStream != null)
if(mInputFrames != null && mInputFrames.hasNextFrame())
{
//We need to stream at 13.888 fps (144 byte frame) to achieve 2000 Bps or 16 kbps
mBytesStreamedRequired += 2000; //2000 bytes per second for 16 kbps data rate
int bytesToStream = mBytesStreamedRequired - mBytesStreamedActual;
//Trim length to whole-frame intervals (144 byte frame)
bytesToStream -= (bytesToStream % 144);
int length = FastMath.min(bytesToStream, mInputStream.available());
byte[] audio = new byte[length];
try
while(mInputFrames.hasNextFrame() && timeSent < PROCESSOR_RUN_INTERVAL_MS)
{
mBytesStreamedActual += mInputStream.read(audio);
broadcastAudio(audio);
}
catch(IOException ioe)
{
mLog.error("Error reading from in-memory audio recording input stream", ioe);
mInputFrames.nextFrame();
broadcastAudio(mInputFrames.getCurrentFrame(), mInputIdentifierCollection);
timeSent += mInputFrames.getCurrentFrameDuration();
}
}
else
if((mInputFrames == null || !mInputFrames.hasNextFrame()) && timeSent < PROCESSOR_RUN_INTERVAL_MS)
{
broadcastAudio(mSilenceGenerator.generate(PROCESSOR_RUN_INTERVAL_MS));
AudioFrames silenceFrames = mSilenceGenerator.generate(PROCESSOR_RUN_INTERVAL_MS - mTimeOverrun - timeSent);
while(silenceFrames.hasNextFrame())
{
silenceFrames.nextFrame();
broadcastAudio(silenceFrames.getCurrentFrame(), null);
timeSent += silenceFrames.getCurrentFrameDuration();
}
}
mTimeOverrun += timeSent - PROCESSOR_RUN_INTERVAL_MS;
}
catch(Throwable t)
{
@@ -329,12 +325,9 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
*/
private void nextRecording()
{
mBytesStreamedActual = 0;
mBytesStreamedRequired = 0;
boolean metadataUpdateRequired = false;
if(mInputStream != null)
if(mInputFrames != null)
{
mStreamedAudioCount++;
broadcast(new BroadcastEvent(AudioStreamingBroadcaster.this,
@@ -342,7 +335,8 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
metadataUpdateRequired = true;
}
mInputStream = null;
mInputFrames = null;
mInputIdentifierCollection = null;
//Peek at the next recording but don't remove it from the queue yet, so we can inspect the start time for
//age limits and/or delay elapsed
@@ -370,17 +364,17 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
{
byte[] audio = Files.readAllBytes(nextRecording.getPath());
if(audio != null && audio.length > 0)
if(audio.length > 0)
{
mInputStream = new ByteArrayInputStream(audio);
mFinalSilencePadding = PROCESSOR_RUN_INTERVAL_MS -
(nextRecording.getRecordingLength() % PROCESSOR_RUN_INTERVAL_MS);
while(mFinalSilencePadding >= PROCESSOR_RUN_INTERVAL_MS)
switch(mBroadcastFormat)
{
mFinalSilencePadding -= PROCESSOR_RUN_INTERVAL_MS;
case MP3:
mInputFrames = MP3FrameTools.split(audio);
break;
default:
throw new IllegalArgumentException("Unsupported broadcast format [" + mBroadcastFormat + "]");
}
mInputIdentifierCollection = nextRecording.getIdentifierCollection();
if(connected())
{
@@ -396,7 +390,8 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
mLog.error("Stream [" + getBroadcastConfiguration().getName() + "] error reading temporary audio " +
"stream recording [" + nextRecording.getPath().toString() + "] - skipping recording - ", ioe);
mInputStream = null;
mInputFrames = null;
mInputIdentifierCollection = null;
metadataUpdateRequired = false;
}

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;
@@ -33,10 +30,11 @@ import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1AudioBroadcast
import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2AudioStreamingBroadcaster;
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2Configuration;
import io.github.dsheirer.audio.convert.IAudioConverter;
import io.github.dsheirer.audio.convert.ISilenceGenerator;
import io.github.dsheirer.audio.convert.MP3AudioConverter;
import io.github.dsheirer.audio.convert.InputAudioFormat;
import io.github.dsheirer.audio.convert.MP3Setting;
import io.github.dsheirer.audio.convert.MP3SilenceGenerator;
import io.github.dsheirer.preference.UserPreferences;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -44,67 +42,50 @@ public class BroadcastFactory
{
private final static Logger mLog = LoggerFactory.getLogger(BroadcastFactory.class);
public static final int MP3_MONO_16_KHZ_BITRATE = 16;
public static final boolean MP3_CONSTANT_BITRATE = false;
/**
* Creates an audio streaming broadcaster for the configuration
*
* @param configuration describing the server and audio types
* @return configured broadcaster or null
*/
public static AbstractAudioBroadcaster getBroadcaster(BroadcastConfiguration configuration, AliasModel aliasModel)
public static AbstractAudioBroadcaster getBroadcaster(BroadcastConfiguration configuration, AliasModel aliasModel,
UserPreferences userPreferences)
{
if(configuration != null)
{
IAudioConverter converter = getAudioConverter(configuration);
InputAudioFormat inputAudioFormat = userPreferences.getMP3Preference().getAudioSampleRate();
MP3Setting mp3Setting = userPreferences.getMP3Preference().getMP3Setting();
if(converter != null)
switch(configuration.getBroadcastServerType())
{
switch(configuration.getBroadcastServerType())
{
case BROADCASTIFY_CALL:
return new BroadcastifyCallBroadcaster((BroadcastifyCallConfiguration)configuration, aliasModel);
case BROADCASTIFY:
return new IcecastTCPAudioBroadcaster((BroadcastifyFeedConfiguration) configuration, aliasModel);
case ICECAST_TCP:
return new IcecastTCPAudioBroadcaster((IcecastTCPConfiguration) configuration, aliasModel);
case ICECAST_HTTP:
return new IcecastHTTPAudioBroadcaster((IcecastHTTPConfiguration) configuration, aliasModel);
case SHOUTCAST_V1:
return new ShoutcastV1AudioBroadcaster((ShoutcastV1Configuration) configuration, aliasModel);
case SHOUTCAST_V2:
return new ShoutcastV2AudioStreamingBroadcaster((ShoutcastV2Configuration) configuration, aliasModel);
case UNKNOWN:
default:
mLog.info("Unrecognized broadcastAudio configuration: " + configuration.getBroadcastFormat().name());
break;
}
case BROADCASTIFY_CALL:
return new BroadcastifyCallBroadcaster((BroadcastifyCallConfiguration)configuration,
inputAudioFormat, mp3Setting, aliasModel);
case BROADCASTIFY:
return new IcecastTCPAudioBroadcaster((BroadcastifyFeedConfiguration) configuration,
inputAudioFormat, mp3Setting, aliasModel);
case ICECAST_TCP:
return new IcecastTCPAudioBroadcaster((IcecastTCPConfiguration) configuration, inputAudioFormat,
mp3Setting, aliasModel);
case ICECAST_HTTP:
return new IcecastHTTPAudioBroadcaster((IcecastHTTPConfiguration) configuration, inputAudioFormat,
mp3Setting, aliasModel);
case SHOUTCAST_V1:
return new ShoutcastV1AudioBroadcaster((ShoutcastV1Configuration) configuration, inputAudioFormat,
mp3Setting, aliasModel);
case SHOUTCAST_V2:
return new ShoutcastV2AudioStreamingBroadcaster((ShoutcastV2Configuration) configuration,
inputAudioFormat, mp3Setting, aliasModel);
case UNKNOWN:
default:
mLog.info("Unrecognized broadcastAudio configuration: " + configuration.getBroadcastFormat().name());
break;
}
}
return null;
}
/**
* Creates an audio convert to convert from 8 kHz PCM audio to the specified format
*
* @param configuration containing the requested output audio format
* @return audio convert or null
*/
public static IAudioConverter getAudioConverter(BroadcastConfiguration configuration)
{
switch(configuration.getBroadcastFormat())
{
case MP3:
return new MP3AudioConverter(MP3_MONO_16_KHZ_BITRATE, MP3_CONSTANT_BITRATE);
default:
mLog.info("Unrecognized broadcastAudio format: " + configuration.getBroadcastFormat().name());
}
return null;
}
/**
* Creates a broadcastAudio configuration for the specified server type and format
*
@@ -137,12 +118,12 @@ public class BroadcastFactory
return null;
}
public static ISilenceGenerator getSilenceGenerator(BroadcastFormat format)
public static ISilenceGenerator getSilenceGenerator(BroadcastFormat format, InputAudioFormat inputAudioFormat, MP3Setting mp3Setting)
{
switch(format)
{
case MP3:
return new MP3SilenceGenerator();
return new MP3SilenceGenerator(inputAudioFormat, mp3Setting);
default:
throw new IllegalArgumentException("Unrecognized broadcast format [" + format +
"] can't create silence generator");

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;
@@ -29,13 +26,6 @@ import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.util.ThreadPool;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.ImageIcon;
import javax.swing.table.AbstractTableModel;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
@@ -51,6 +41,12 @@ import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.table.AbstractTableModel;
public class BroadcastModel extends AbstractTableModel implements Listener<AudioRecording>
{
@@ -61,7 +57,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
private static final String UNIQUE_NAME_REGEX = "(.*)\\((\\d*)\\)";
public static final int COLUMN_SERVER_ICON = 0;
public static final int COLUMN_BROADCAST_SERVER_TYPE = 0;
public static final int COLUMN_STREAM_NAME = 1;
public static final int COLUMN_BROADCASTER_STATUS = 2;
public static final int COLUMN_BROADCASTER_QUEUE_SIZE = 3;
@@ -70,7 +66,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
public static final int COLUMN_BROADCASTER_ERROR_COUNT = 6;
public static final String[] COLUMN_NAMES = new String[]
{"Streaming", "Name", "Status", "Queued", "Streamed/Uploaded", "Aged Off", "Upload Error"};
{"Stream Type", "Name", "Status", "Queued", "Streamed/Uploaded", "Aged Off", "Upload Error"};
private ObservableList<ConfiguredBroadcast> mConfiguredBroadcasts =
FXCollections.observableArrayList(ConfiguredBroadcast.extractor());
@@ -80,6 +76,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
private AliasModel mAliasModel;
private Broadcaster<BroadcastEvent> mBroadcastEventBroadcaster = new Broadcaster<>();
private BroadcastEventListener mBroadcastEventListener = new BroadcastEventListener();
private UserPreferences mUserPreferences;
/**
* Model for managing Broadcast configurations and any associated broadcaster instances.
@@ -88,6 +85,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
{
mAliasModel = aliasModel;
mIconModel = iconModel;
mUserPreferences = userPreferences;
//Monitor to remove temporary recording files that have been streamed by all audio broadcasters
ThreadPool.SCHEDULED.scheduleAtFixedRate(new RecordingDeletionMonitor(), 15l, 15l, TimeUnit.SECONDS);
@@ -346,7 +344,8 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
deleteBroadcaster(configuredBroadcast);
}
AbstractAudioBroadcaster audioBroadcaster = BroadcastFactory.getBroadcaster(broadcastConfiguration, mAliasModel);
AbstractAudioBroadcaster audioBroadcaster = BroadcastFactory.getBroadcaster(broadcastConfiguration,
mAliasModel, mUserPreferences);
if(audioBroadcaster != null)
{
@@ -575,15 +574,8 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
{
switch(columnIndex)
{
case COLUMN_SERVER_ICON:
String iconPath = configuredBroadcast.getBroadcastConfiguration()
.getBroadcastServerType().getIconPath();
if(iconPath != null && mIconModel != null)
{
return mIconModel.getScaledIcon(iconPath, 14);
}
break;
case COLUMN_BROADCAST_SERVER_TYPE:
return configuredBroadcast.getBroadcastServerType();
case COLUMN_STREAM_NAME:
return configuredBroadcast.getBroadcastConfiguration().getName();
case COLUMN_BROADCASTER_STATUS:
@@ -653,8 +645,8 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
case COLUMN_BROADCASTER_STREAMED_COUNT:
case COLUMN_BROADCASTER_ERROR_COUNT:
return Integer.class;
case COLUMN_SERVER_ICON:
return ImageIcon.class;
case COLUMN_BROADCAST_SERVER_TYPE:
return BroadcastServerType.class;
case COLUMN_STREAM_NAME:
default:
return String.class;

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;
public enum BroadcastServerType
@@ -23,17 +23,17 @@ public enum BroadcastServerType
/**
* Broadcastify feeds (ie streaming) service
*/
BROADCASTIFY("Broadcastify Feed", "/images/broadcastify.png"), //Icecast Server 2.3.2
BROADCASTIFY("Broadcastify Feed", "images/broadcastify.png"), //Icecast Server 2.3.2
/**
* Broadcastify calls - completed audio recording push service
*/
BROADCASTIFY_CALL("Broadcastify Call", "/images/broadcastify.png"),
BROADCASTIFY_CALL("Broadcastify Call", "images/broadcastify.png"),
ICECAST_HTTP("Icecast 2 (v2.4+)", "/images/icecast.png"),
ICECAST_TCP("Icecast (v2.3)", "/images/icecast.png"),
SHOUTCAST_V1("Shoutcast v1.x", "/images/shoutcast.png"),
SHOUTCAST_V2("Shoutcast v2.x", "/images/shoutcast.png"),
ICECAST_HTTP("Icecast 2 (v2.4+)", "images/icecast.png"),
ICECAST_TCP("Icecast (v2.3)", "images/icecast.png"),
SHOUTCAST_V1("Shoutcast v1.x", "images/shoutcast.png"),
SHOUTCAST_V2("Shoutcast v2.x", "images/shoutcast.png"),
UNKNOWN("Unknown", null);
private String mLabel;

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;
public enum BroadcastState
@@ -103,10 +103,10 @@ public enum BroadcastState
private String mLabel;
private boolean mErrorState;
private BroadcastState(String label, boolean connected)
BroadcastState(String label, boolean errorState)
{
mLabel = label;
mErrorState = connected;
mErrorState = errorState;
}
public String toString()

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,23 +14,29 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.audio.broadcast;
import io.github.dsheirer.icon.Icon;
import io.github.dsheirer.icon.IconModel;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
import java.awt.Color;
import java.awt.Component;
import net.miginfocom.swing.MigLayout;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingConstants;
import javax.swing.table.DefaultTableCellRenderer;
import java.awt.Color;
import java.awt.Component;
/**
* Table of broadcast streams and statuses.
*/
public class BroadcastStatusPanel extends JPanel
{
private JTable mTable;
@@ -41,6 +46,12 @@ public class BroadcastStatusPanel extends JPanel
private UserPreferences mUserPreferences;
private String mPreferenceKey;
/**
* Constructs an instance
* @param broadcastModel to access the streams
* @param userPreferences for configuring the panel
* @param preferenceKey to store column preferences for this panel.
*/
public BroadcastStatusPanel(BroadcastModel broadcastModel, UserPreferences userPreferences, String preferenceKey)
{
mBroadcastModel = broadcastModel;
@@ -65,6 +76,7 @@ public class BroadcastStatusPanel extends JPanel
renderer.setHorizontalAlignment(SwingConstants.CENTER);
mTable.getColumnModel().getColumn(BroadcastModel.COLUMN_BROADCASTER_STATUS).setCellRenderer(new StatusCellRenderer());
mTable.getColumnModel().getColumn(BroadcastModel.COLUMN_BROADCAST_SERVER_TYPE).setCellRenderer(new ServerTypeRenderer());
mColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, mPreferenceKey);
mScrollPane = new JScrollPane(mTable);
@@ -72,6 +84,37 @@ public class BroadcastStatusPanel extends JPanel
add(mScrollPane);
}
public class ServerTypeRenderer extends DefaultTableCellRenderer
{
public ServerTypeRenderer()
{
setOpaque(true);
setHorizontalAlignment(SwingConstants.CENTER);
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
{
JLabel component = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if(value instanceof BroadcastServerType broadcastServerType)
{
component.setText(broadcastServerType.toString());
Icon icon = new Icon("empty", broadcastServerType.getIconPath());
ImageIcon imageIcon = icon.getIcon();
ImageIcon scaledIcon = IconModel.getScaledIcon(imageIcon, 13);
component.setIcon(scaledIcon);
}
else
{
component.setText(null);
component.setIcon(null);
}
return component;
}
}
/**
* Custom cell renderer for the broadcast state column.
*/

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
*/
@@ -323,6 +355,11 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
audioRecording.removePendingReplay();
}
}
else if(urlResponse.startsWith("1 SKIPPED"))
{
//Broadcastify is telling us to skip audio upload - someone already uploaded it
audioRecording.removePendingReplay();
}
else
{
mLog.error("Broadcastify calls API upload URL request failed [" + urlResponse + "]");
@@ -410,40 +447,42 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
*/
private static String getTo(AudioRecording audioRecording)
{
List<Identifier> toIdentifiers = audioRecording.getIdentifierCollection().getIdentifiers(Role.TO);
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
if(toIdentifiers.size() >= 1)
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
{
Identifier to = toIdentifiers.get(0);
if(to instanceof TalkgroupIdentifier)
{
TalkgroupIdentifier talkgroupIdentifier = (TalkgroupIdentifier)to;
return String.valueOf(RadioReferenceDecoder.convertToRadioReferenceTalkgroup(talkgroupIdentifier.getValue(),
return format(patchGroupIdentifier);
}
else if(identifier instanceof TalkgroupIdentifier talkgroupIdentifier)
{
return String.valueOf(RadioReferenceDecoder.convertToRadioReferenceTalkgroup(talkgroupIdentifier.getValue(),
talkgroupIdentifier.getProtocol()));
}
else if(to instanceof PatchGroupIdentifier)
{
PatchGroup patchGroup = ((PatchGroupIdentifier)to).getValue();
StringBuilder sb = new StringBuilder();
sb.append(patchGroup.getPatchGroup().getValue().toString());
for(TalkgroupIdentifier patched: patchGroup.getPatchedGroupIdentifiers())
{
sb.append(",").append(patched.getValue());
}
return sb.toString();
}
else if(to instanceof RadioIdentifier)
{
return ((RadioIdentifier)to).getValue().toString();
}
}
else if(identifier instanceof RadioIdentifier radioIdentifier)
{
return radioIdentifier.getValue().toString();
}
return "0";
}
/**
* Formats a patch group
*/
public static String format(PatchGroupIdentifier patchGroupIdentifier)
{
PatchGroup patchGroup = patchGroupIdentifier.getValue();
StringBuilder sb = new StringBuilder();
sb.append(patchGroup.getPatchGroup().getValue().toString());
for(TalkgroupIdentifier patched: patchGroup.getPatchedGroupIdentifiers())
{
sb.append(",").append(patched.getValue());
}
return sb.toString();
}
/**
* Tests both the connection and configuration against the Broadcastify Call API service
* @param configuration containing API key and system id

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,31 +1,39 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.audio.broadcast.icecast;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.BroadcastState;
import io.github.dsheirer.audio.convert.InputAudioFormat;
import io.github.dsheirer.audio.convert.MP3AudioConverter;
import io.github.dsheirer.audio.convert.MP3Setting;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.util.ThreadPool;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.mina.core.RuntimeIoException;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.future.ConnectFuture;
@@ -41,13 +49,6 @@ import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
{
private static final Logger mLog = LoggerFactory.getLogger(IcecastHTTPAudioBroadcaster.class);
@@ -72,23 +73,51 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
*
* @param configuration for the Icecast stream
*/
public IcecastHTTPAudioBroadcaster(IcecastHTTPConfiguration configuration, AliasModel aliasModel)
public IcecastHTTPAudioBroadcaster(IcecastHTTPConfiguration configuration, InputAudioFormat inputAudioFormat,
MP3Setting mp3Setting, AliasModel aliasModel)
{
super(configuration, aliasModel);
super(configuration, inputAudioFormat, mp3Setting, aliasModel);
}
/**
* Broadcasts the audio frame or sequence
*/
@Override
protected void broadcastAudio(byte[] audio)
protected void broadcastAudio(byte[] audio, IdentifierCollection identifierCollection)
{
if(audio != null && audio.length > 0 && connect() && mStreamingSession != null && mStreamingSession.isConnected())
{
IoBuffer buffer = IoBuffer.allocate(audio.length);
buffer.put(audio);
buffer.flip();
if(mInlineActive)
{
byte[] metadata = IcecastMetadata.formatInline(IcecastMetadata.getTitle(identifierCollection, mAliasModel)).getBytes();
buffer.setAutoExpand(true);
if (mInlineRemaining == -1)
{
mInlineRemaining = mInlineInterval;
}
int audioOffset = 0;
while(audioOffset < audio.length)
{
byte[] chunk = Arrays.copyOfRange(audio, audioOffset, Math.min(audioOffset + mInlineRemaining, audio.length));
mInlineRemaining -= chunk.length;
audioOffset += chunk.length;
buffer.put(chunk);
if(mInlineRemaining == 0)
{
mInlineRemaining = mInlineInterval;
buffer.put(metadata);
}
}
buffer.shrink();
}
else
{
buffer.put(audio);
}
buffer.flip();
mStreamingSession.write(buffer);
}
}
@@ -114,8 +143,9 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
mSocketConnector = new NioSocketConnector();
mSocketConnector.setConnectTimeoutCheckInterval(10000);
// mSocketConnector.getFilterChain().addLast("logger",
// new LoggingFilter(IcecastHTTPAudioBroadcaster.class));
// LoggingFilter loggingFilter = new LoggingFilter(IcecastHTTPAudioBroadcaster.class);
// loggingFilter.setMessageReceivedLogLevel(LogLevel.DEBUG);
// mSocketConnector.getFilterChain().addLast("logger", loggingFilter);
mSocketConnector.getFilterChain().addLast("codec", new HttpClientCodec());
mSocketConnector.setHandler(new IcecastHTTPIOHandler());
@@ -164,8 +194,7 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
}
};
ThreadPool.SCHEDULED.schedule(runnable, 0l, TimeUnit.SECONDS);
ThreadPool.CACHED.submit(runnable);
}
return connected();
@@ -240,6 +269,17 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
{
mHTTPHeaders.put(IcecastHeader.BITRATE.getValue(), String.valueOf(getConfiguration().getBitRate()));
}
if(getConfiguration().hasInline())
{
mInlineActive = true;
mInlineInterval = getConfiguration().getInlineInterval();
mHTTPHeaders.put(IcecastHeader.METAINT.getValue(), String.valueOf(mInlineInterval));
}
else
{
mInlineActive = false;
}
}
return mHTTPHeaders;
@@ -276,10 +316,49 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
* intercept the out of bounds exception and then inspect the message hex dump to see if it's an
* OK response and then play like everything is well and good.
*/
if(cause instanceof ArrayIndexOutOfBoundsException &&
((ProtocolDecoderException)throwable).getHexdump().startsWith(HTTP_1_0_OK_HEX_DUMP))
if(cause instanceof ArrayIndexOutOfBoundsException)
{
setBroadcastState(BroadcastState.CONNECTED);
String hexDump = ((ProtocolDecoderException)throwable).getHexdump();
if(hexDump.startsWith(HTTP_1_0_OK_HEX_DUMP))
{
setBroadcastState(BroadcastState.CONNECTED);
}
else
{
HttpDumpMessage message = new HttpDumpMessage(hexDump);
if(message.hasHttpResponseCode())
{
switch(message.getHttpResponseCode())
{
case 403: //Forbidden
if(message.toString().contains("Mountpoint in use"))
{
mLog.error("Stream [" + getStreamName() + "] - unable to connect - mountpoint in use");
setBroadcastState(BroadcastState.MOUNT_POINT_IN_USE);
disconnect();
}
else
{
mLog.error("String [" + getStreamName() + "] - HTTP protocol decoder error - message:\n\n" + message);
setBroadcastState(BroadcastState.DISCONNECTED);
disconnect();
}
break;
default:
mLog.error("String [" + getStreamName() + "] - HTTP protocol decoder error - message:\n\n" + message);
setBroadcastState(BroadcastState.DISCONNECTED);
disconnect();
}
}
else
{
mLog.error("HTTP protocol decoder error - message:\n\n" + message);
setBroadcastState(BroadcastState.DISCONNECTED);
disconnect();
}
}
}
else
{
@@ -335,4 +414,97 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
}
}
}
/**
* Class for parsing HTTP response messages from Icecast 2.4.2+
*/
public class HttpDumpMessage
{
private String mHexDump;
private String mMessage;
private int mHttpResponseCode = -1;
public HttpDumpMessage(String hexDump)
{
mHexDump = hexDump;
String[] split = mHexDump.split(" ");
byte[] bytes = new byte[split.length];
int pointer = 0;
for(String a : split)
{
try
{
int value = Integer.parseInt(a, 16);
bytes[pointer++] = (byte) (value & 0xFF);
}
catch(Exception e)
{
pointer++;
}
}
mMessage = new String(bytes);
Pattern pattern = Pattern.compile("HTTP/1.0 (\\d{3})");
Matcher m = pattern.matcher(mMessage);
if(m.find())
{
try
{
mHttpResponseCode = Integer.parseInt(m.group(1));
}
catch(Exception e)
{
mLog.error("Unable to parse HTTP response code that was matched from message: " + m.group(1));
}
}
}
/**
* Indicates if an HTTP response code was parsed from the message
* @return true if it was parsed.
*/
public boolean hasHttpResponseCode()
{
return mHttpResponseCode != -1;
}
/**
* Response code parsed from the hexdump message
* @return value.
*/
public int getHttpResponseCode()
{
return mHttpResponseCode;
}
@Override
public String toString()
{
return mMessage;
}
}
public static void main(String[] args)
{
String text = "HTTP/1.0 403 Forbidden";
Pattern pattern = Pattern.compile("HTTP/1.0 (\\d{3})");
Matcher m = pattern.matcher(text);
if(m.find())
{
mLog.info("Matching Group Count: " + m.groupCount());
mLog.info("Match 0: " + m.group(0));
mLog.info("Match 1: " + m.group(1));
}
else
{
mLog.info("No Match");
}
}
}

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,16 +14,26 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
* ****************************************************************************
*/
package io.github.dsheirer.audio.broadcast.icecast;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.BroadcastState;
import io.github.dsheirer.audio.broadcast.icecast.codec.IcecastCodecFactory;
import io.github.dsheirer.audio.convert.InputAudioFormat;
import io.github.dsheirer.audio.convert.MP3AudioConverter;
import io.github.dsheirer.audio.convert.MP3Setting;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.util.ThreadPool;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.channels.UnresolvedAddressException;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.mina.core.RuntimeIoException;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoHandlerAdapter;
@@ -33,17 +43,10 @@ import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.channels.UnresolvedAddressException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
{
private final static Logger mLog = LoggerFactory.getLogger(IcecastTCPAudioBroadcaster.class);
private final static String TERMINATOR = "\r\n";
private final static String TERMINATOR = "\n";
private final static String SEPARATOR = ":";
private static final long RECONNECT_INTERVAL_MILLISECONDS = 3000; //3 seconds
private static final long CONNECTION_ATTEMPT_TIMEOUT_MILLISECONDS = 5000; //5 seconds
@@ -66,20 +69,45 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
*
* @param configuration for the Icecast stream
*/
public IcecastTCPAudioBroadcaster(IcecastTCPConfiguration configuration, AliasModel aliasModel)
public IcecastTCPAudioBroadcaster(IcecastTCPConfiguration configuration, InputAudioFormat inputAudioFormat,
MP3Setting mp3Setting, AliasModel aliasModel)
{
super(configuration, aliasModel);
super(configuration, inputAudioFormat, mp3Setting, aliasModel);
}
/**
* Broadcasts the audio frame or sequence
*/
@Override
protected void broadcastAudio(byte[] audio)
protected void broadcastAudio(byte[] audio, IdentifierCollection identifierCollection)
{
if(audio != null && audio.length > 0 && connect() && mStreamingSession != null && mStreamingSession.isConnected())
{
mStreamingSession.write(audio);
if(mInlineActive)
{
byte[] metadata = IcecastMetadata.formatInline(IcecastMetadata.getTitle(identifierCollection, mAliasModel)).getBytes();
if (mInlineRemaining == -1)
{
mInlineRemaining = mInlineInterval;
}
int audioOffset = 0;
while(audioOffset < audio.length)
{
byte[] chunk = Arrays.copyOfRange(audio, audioOffset, Math.min(audioOffset + mInlineRemaining, audio.length));
mInlineRemaining -= chunk.length;
audioOffset += chunk.length;
mStreamingSession.write(chunk);
if(mInlineRemaining == 0)
{
mInlineRemaining = mInlineInterval;
mStreamingSession.write(metadata);
}
}
}
else
{
mStreamingSession.write(audio);
}
}
}
@@ -167,7 +195,7 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
}
};
ThreadPool.SCHEDULED.schedule(runnable, 0l, TimeUnit.SECONDS);
ThreadPool.CACHED.submit(runnable);
}
return connected();
@@ -185,7 +213,13 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
}
else
{
setBroadcastState(BroadcastState.DISCONNECTED);
//Only set broadcast state to disconnected if it's not already in an error state, to prevent a restart. We
//want to preserve the error state that got us here, so the user can see it.
if(!getBroadcastState().isErrorState())
{
setBroadcastState(BroadcastState.DISCONNECTED);
}
mLastConnectionAttempt = System.currentTimeMillis();
}
}
@@ -235,7 +269,18 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
.append(getConfiguration().getDescription()).append(TERMINATOR);
}
sb.append(TERMINATOR).append(TERMINATOR);
if(getConfiguration().hasInline())
{
mInlineActive = true;
mInlineInterval = getConfiguration().getInlineInterval();
sb.append(IcecastHeader.METAINT.getValue()).append(SEPARATOR)
.append(String.valueOf(mInlineInterval))
.append(TERMINATOR);
}
else
{
mInlineActive = false;
}
session.write(sb.toString());
}

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,133 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.audio.convert;
import java.util.List;
import org.apache.commons.math3.util.FastMath;
public abstract class AudioFrames
{
protected final int audioDuration;
protected final List<byte[]> audioFrames;
protected int mCurrentFrame = -1;
/**
* Create new AudioFrame
* @param audioDuration total duration in milliseconds
* @param audioFrames series of audio frames, split on frame boundaries
*/
public AudioFrames(int audioDuration, List<byte[]> audioFrames)
{
this.audioDuration = audioDuration;
this.audioFrames = audioFrames;
}
/**
* Get total duration of frames
* @return total duration in milliseconds
*/
public int getDuration()
{
return audioDuration;
}
/**
* Get all frames
* @return list of byte arrays, with each element representing a single frame of audio
*/
public List<byte[]> getFrames()
{
return audioFrames;
}
/**
* Get current frame
* @return byte array with the current frame of audio
*/
public byte[] getCurrentFrame()
{
return audioFrames.get(mCurrentFrame);
}
/**
* Get current frame duration
* @return frame duration in milliseconds
*/
public abstract int getCurrentFrameDuration();
/**
* Indicates if there is a frame before the current one
* @return true if the frame exists, otherwise false
*/
public boolean hasPrevFrame()
{
int prevFrame = mCurrentFrame - 1;
return prevFrame >= 0 && prevFrame < audioFrames.size();
}
/**
* Indicates if there is a frame after the current one
* @return true if the frame exists, otherwise false
*/
public boolean hasNextFrame()
{
int nextFrame = mCurrentFrame + 1;
return nextFrame >= 0 && nextFrame < audioFrames.size();
}
/**
* Seek to the previous frame
*/
public void prevFrame()
{
mCurrentFrame -= 1;
}
/**
* Seek to the next frame
*/
public void nextFrame()
{
mCurrentFrame += 1;
}
/**
* Restart from beginning
* Must call nextFrame() to get first frame
*/
public void restart()
{
mCurrentFrame = -1;
}
/**
* Seek by the specified time
* @param duration_ms time in milliseconds, positive or negative
*/
public void seek(int duration_ms)
{
int step = duration_ms < 0 ? -1 : 1;
int actual_ms = 0;
while(FastMath.abs(actual_ms) < FastMath.abs(duration_ms))
{
mCurrentFrame += step;
actual_ms += getCurrentFrameDuration();
}
}
}

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);
AudioFrames generate(long duration);
}

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.audio.convert;
import io.github.dsheirer.audio.AudioFormats;
import java.util.EnumSet;
import javax.sound.sampled.AudioFormat;
/**
* Enumeration of MP3 input audio formats for mono audio.
*/
public enum InputAudioFormat
{
SR_8000(AudioFormats.PCM_SIGNED_8000_HZ_16_BIT_MONO, "16-Bit 8000 Hz (no resample)"),
SR_16000(AudioFormats.PCM_SIGNED_16000_HZ_16_BIT_MONO, "16-Bit 16000 Hz (default)"),
SR_22050(AudioFormats.PCM_SIGNED_22050_HZ_16_BIT_MONO, "16-Bit 22050 Hz"),
SR_44100(AudioFormats.PCM_SIGNED_44100_HZ_16_BIT_MONO, "16-Bit 44100 Hz"),
SR_32_8000(AudioFormats.PCM_SIGNED_8000_HZ_32_BIT_MONO, "32-Bit 8000 Hz (no resample)"),
SR_32_16000(AudioFormats.PCM_SIGNED_16000_HZ_32_BIT_MONO, "32-Bit 16000 Hz"),
SR_32_22050(AudioFormats.PCM_SIGNED_22050_HZ_32_BIT_MONO, "32-Bit 22050 Hz"),
SR_32_44100(AudioFormats.PCM_SIGNED_44100_HZ_32_BIT_MONO, "32-Bit 44100 Hz");
private AudioFormat mAudioFormat;
private String mLabel;
/**
* Set that includes sample rates of 8 or 16 kHz
*/
public static final EnumSet<InputAudioFormat> SAMPLE_RATES_8_16 = EnumSet.of(SR_8000, SR_32_8000, SR_16000, SR_32_16000);
/**
* Set that includes sample rates of 8, 16, or 22 kHz
*/
public static final EnumSet<InputAudioFormat> SAMPLE_RATES_8_16_22 = EnumSet.of(SR_8000, SR_32_8000, SR_16000,
SR_32_16000, SR_22050, SR_32_22050);
/**
* Constructs an instance
* @param audioFormat for the specified sample rate
*/
InputAudioFormat(AudioFormat audioFormat, String label)
{
mAudioFormat = audioFormat;
mLabel = label;
}
/**
* Default sample rate
*/
public static InputAudioFormat getDefault()
{
return SR_16000;
}
/**
* Audio format for the specified sample rate entry
*/
public AudioFormat getAudioFormat()
{
return mAudioFormat;
}
/**
* Sample rate for the format
*/
public double getSampleRate()
{
return getAudioFormat().getSampleRate();
}
@Override
public String toString()
{
return mLabel;
}
}

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,151 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
* ****************************************************************************
*/
package io.github.dsheirer.audio.convert;
import io.github.dsheirer.audio.AudioFormats;
import io.github.dsheirer.audio.AudioUtils;
import io.github.dsheirer.dsp.filter.resample.RealResampler;
import io.github.dsheirer.sample.ConversionUtils;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import net.sourceforge.lame.lowlevel.LameEncoder;
import net.sourceforge.lame.mp3.Lame;
import net.sourceforge.lame.mp3.MPEGMode;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.util.Arrays;
import java.util.List;
/**
* Converts PCM audio packets to Mono, MP3 encoded audio
*/
public class MP3AudioConverter implements IAudioConverter
{
private final static Logger mLog = LoggerFactory.getLogger( MP3AudioConverter.class );
public static final int AUDIO_QUALITY = Lame.QUALITY_LOW;
private LameEncoder mEncoder;
private ByteArrayOutputStream mMP3Stream = new ByteArrayOutputStream();
private byte[] mMP3Buffer;
private RealResampler mResampler;
private ByteArrayOutputStream mMP3EncodedFramesStream = new ByteArrayOutputStream();
private byte[] mOutputFramesBuffer;
private InputAudioFormat mInputAudioFormat;
private boolean mNormalizeAudio;
/**
* Converts PCM 8kHz 16-bit Little Endian audio packets to Mono, Low Quality MP3 compressed audio.
* Constructs an instance.
*
* @param bitRate for converted MP3 audio
* @param variableBitRate (VBR) true or false for constant bit rate (CBR)
* @param inputAudioFormat for the desired input sample rate and bit size (resampled from default 8 kHz as needed)
* @param setting to configure the LAME encoder
* @param normalizeAudio to normalize the audio gain prior to encoding
*/
public MP3AudioConverter(int bitRate, boolean variableBitRate)
public MP3AudioConverter(InputAudioFormat inputAudioFormat, MP3Setting setting, boolean normalizeAudio)
{
mEncoder = new LameEncoder(AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO,
bitRate, MPEGMode.MONO, AUDIO_QUALITY, variableBitRate);
mInputAudioFormat = inputAudioFormat;
mEncoder = LameFactory.getLameEncoder(inputAudioFormat, setting);
mNormalizeAudio = normalizeAudio;
mMP3Buffer = new byte[mEncoder.getPCMBufferSize()];
//Ensure input audio sample rate is supported for the MP3 setting and update as necessary - should never happen.
if((mInputAudioFormat.getSampleRate() - mEncoder.getEffectiveSampleRate()) > 1.0)
{
mInputAudioFormat = InputAudioFormat.getDefault();
mLog.warn("MP3 setting [" + setting + "] does not support requested input audio sample rate [" +
inputAudioFormat + "] - using default sample rate [" + mInputAudioFormat + "]");
mEncoder = LameFactory.getLameEncoder(inputAudioFormat, setting);
}
//Resampling is only required if desired input sample rate is not system default of 8kHz
if(mInputAudioFormat != InputAudioFormat.SR_8000 && mInputAudioFormat != InputAudioFormat.SR_32_8000)
{
mResampler = LameFactory.getResampler(inputAudioFormat);
}
mOutputFramesBuffer = new byte[mEncoder.getPCMBufferSize()];
}
/**
* Converts the list of PCM audio packets to MP3 encoded.
* @param audioPackets of PCM audio sampled at 8 kHz
* @return encoded MP3 audio
*/
public List<byte[]> convert(List<float[]> audioPackets)
{
List<byte[]> converted = new ArrayList<>();
if(mNormalizeAudio)
{
audioPackets = AudioUtils.normalize(audioPackets);
}
if(mResampler != null)
{
audioPackets = mResampler.resample(audioPackets);
}
for(int x = 0; x < audioPackets.size(); x++)
{
byte[] bytesToEncode = null;
if(mInputAudioFormat.getAudioFormat().getSampleSizeInBits() == 16)
{
bytesToEncode = ConversionUtils.convertToSigned16BitSamples(audioPackets.get(x)).array();
}
else
{
bytesToEncode = ConversionUtils.convertToSigned32BitSamples(audioPackets.get(x)).array();
}
int bytesToEncodePointer = 0;
int inputChunkSize = FastMath.min(mOutputFramesBuffer.length, bytesToEncode.length);
int outputChunkSize = 0;
try
{
while(bytesToEncodePointer < bytesToEncode.length)
{
outputChunkSize = mEncoder.encodeBuffer(bytesToEncode, bytesToEncodePointer, inputChunkSize, mOutputFramesBuffer);
bytesToEncodePointer += inputChunkSize;
inputChunkSize = FastMath.min(mOutputFramesBuffer.length, bytesToEncode.length - bytesToEncodePointer);
if(outputChunkSize > 0)
{
converted.add(Arrays.copyOf(mOutputFramesBuffer, outputChunkSize));
}
}
}
catch(Exception e)
{
mLog.error("There was an error converting audio to MP3: " + e.getMessage());
}
}
int finalChunkSize = mEncoder.encodeFinish(mOutputFramesBuffer);
if(finalChunkSize > 0)
{
converted.add(Arrays.copyOf(mOutputFramesBuffer, finalChunkSize));
}
return converted;
}
@Override
public byte[] convert(List<float[]> audioPackets)
{
mMP3Stream.reset();
byte[] pcmBytes = AudioUtils.convertTo16BitSamples(audioPackets);
int pcmBufferSize = FastMath.min(mMP3Buffer.length, pcmBytes.length);
int mp3BufferSize = 0;
int pcmBytesPosition = 0;
try
{
while (0 < (mp3BufferSize = mEncoder.encodeBuffer(pcmBytes, pcmBytesPosition, pcmBufferSize, mMP3Buffer)))
{
pcmBytesPosition += pcmBufferSize;
pcmBufferSize = FastMath.min(mMP3Buffer.length, pcmBytes.length - pcmBytesPosition);
mMP3Stream.write(mMP3Buffer, 0, mp3BufferSize);
}
return mMP3Stream.toByteArray();
}
catch(Exception e)
{
mLog.error("There was an error converting audio to MP3: " + e.getMessage());
return new byte[0];
}
}
public byte[] convertAudio(List<float[]> audioBuffers)
{
mMP3Stream.reset();
byte[] pcmBytes = AudioUtils.convert(audioBuffers);
int pcmBufferSize = FastMath.min(mMP3Buffer.length, pcmBytes.length);
int mp3BufferSize = 0;
int pcmBytesPosition = 0;
try
{
while (0 < (mp3BufferSize = mEncoder.encodeBuffer(pcmBytes, pcmBytesPosition, pcmBufferSize, mMP3Buffer)))
{
pcmBytesPosition += pcmBufferSize;
pcmBufferSize = FastMath.min(mMP3Buffer.length, pcmBytes.length - pcmBytesPosition);
mMP3Stream.write(mMP3Buffer, 0, mp3BufferSize);
}
return mMP3Stream.toByteArray();
}
catch(Exception e)
{
mLog.error("There was an error converting audio to MP3: " + e.getMessage());
return new byte[0];
}
}
@Override
public byte[] flush()
public List<byte[]> flush()
{
byte[] lastPartialFrame = new byte[mEncoder.getMP3BufferSize()];
int length = mEncoder.encodeFinish(lastPartialFrame);
return Arrays.copyOf(lastPartialFrame, length);
byte[] frame = Arrays.copyOf(lastPartialFrame, length);
if(frame.length == 0)
{
return Collections.emptyList();
}
return Collections.singletonList(frame);
}
}

View File

@@ -0,0 +1,46 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.audio.convert;
import java.util.List;
public class MP3AudioFrames extends AudioFrames
{
/**
* Create new MP3AudioFrame
* @param audioDuration total duration in milliseconds
* @param audioFrames series of audio frames, split on frame boundaries
*/
public MP3AudioFrames(int audioDuration, List<byte[]> audioFrames)
{
super(audioDuration, audioFrames);
}
/**
* Get current frame duration
* @return frame duration in milliseconds
*/
public int getCurrentFrameDuration()
{
return MP3Header.getFrameDuration(getCurrentFrame(), 0);
}
}

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,76 +14,58 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
* ****************************************************************************
*/
package io.github.dsheirer.audio.convert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
public class MP3FrameInspector
{
private final static Logger mLog = LoggerFactory.getLogger(MP3FrameInspector.class);
public static void inspect(byte[] frames)
public static void inspect(List<byte[]> frames)
{
if(frames == null || frames.length == 0)
if(frames.isEmpty())
{
mLog.info("No Frame Data!");
return;
}
if(frames.length % 144 != 0)
for(byte[] frame: frames)
{
log("Frame byte array not multiple of 144 byte frame size - length:" + frames.length, frames);
return;
}
StringBuilder sb = new StringBuilder();
int frameCounter = 0;
//Check for erroneous frame sync
for(int x = 0; x < frames.length - 1; x++)
{
if(frames[x] == (byte)0xFF && (frames[x + 1] & 0xE0) == 0xE0 && (x % 144) != 0)
for(int x = 0; x < frame.length; x++)
{
log("Bad frame sync detected at byte " + x + " in frame " + (x / 144), frames);
return;
}
}
if(MP3Header.isValid(frame, x))
{
sb.append("\n").append(++frameCounter).append(": ");
sb.append(MP3Header.inspect(frame, x)).append(" - ");
}
//Check MP3 headers
// log("Frames ...", frames);
}
private static void log(String message, byte[] frames)
{
StringBuilder sb = new StringBuilder();
sb.append(message).append("\n");
int frameCounter = 0;
sb.append(frameCounter++).append(" ");
for(int x = 0; x < frames.length; x++)
{
if(x > 0 && x % 144 == 0)
{
sb.append("\n");
sb.append(frameCounter++).append(" ");
sb.append(String.format("%02X ", frame[x]));
}
sb.append(String.format("%02X ", frames[x]));
mLog.info(sb.toString());
}
mLog.info(sb.toString());
}
/* TODO: Rework for MP3SilenceGenerator returning IAudioFrames
public static void main(String[] args)
{
mLog.info("Starting ...");
MP3SilenceGenerator gen = new MP3SilenceGenerator();
MP3SilenceGenerator gen = new MP3SilenceGenerator(InputAudioFormat.SR_8000, MP3Setting.getDefault());
byte[] audio = gen.generate(173);
List<byte[]> audio = gen.generate(173);
inspect(audio);
mLog.info("Finished");
}
*/
}

View File

@@ -0,0 +1,59 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.audio.convert;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.math3.util.FastMath;
public class MP3FrameTools {
private MP3FrameTools() {}
/**
* Split MP3 audio at frame boundaries
* @param input byte array of audio
* @return list of byte arrays containing audio frames
*/
public static MP3AudioFrames split(byte[] input)
{
List<byte[]> mp3Frames = new ArrayList<>();
int audioDuration = 0;
int offset = 0;
while(offset < input.length)
{
if(MP3Header.hasSync(input, offset))
{
int framelen = MP3Header.getFrameLength(input, offset);
mp3Frames.add(Arrays.copyOfRange(input, offset, offset + FastMath.min(framelen, input.length - offset)));
audioDuration += MP3Header.getFrameDuration(input, offset);
offset += framelen;
}
else
{
offset += 1;
}
}
return new MP3AudioFrames(audioDuration, mp3Frames);
}
}

View File

@@ -0,0 +1,184 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.audio.convert;
import io.github.dsheirer.audio.convert.MPEGLayer;
import io.github.dsheirer.audio.convert.MPEGVersion;
import org.apache.commons.math3.util.FastMath;
/**
* Utility class for inspecting/parsing MP3 frames
*/
public class MP3Header
{
private MP3Header() {}
/**
* Indicates if the byte array at the offset has a valid MP3 frame sync and is at least 4 bytes long
* @param frame array of bytes
* @param offset into the array
* @return true if the first two offset bytes contain a frame sync and length from offset is at least 4
*/
public static boolean isValid(byte[] frame, int offset)
{
return frame.length >= (offset + 4) && hasSync(frame, offset);
}
/**
* Indicates if the byte array at the offset has a valid MP3 frame sync
* @param frame array of bytes
* @param offset into the array
* @return true if the first two offset bytes contain a frame sync
*/
public static boolean hasSync(byte[] frame, int offset)
{
return (frame[offset] & 0xFF) == 0xFF && (frame[offset + 1] & 0xE0) == 0xE0;
}
/**
* MPEG version for the frame
*/
public static MPEGVersion getMPEGVersion(byte[] frame, int offset)
{
return MPEGVersion.fromValue((frame[offset + 1] & 0x18) >> 3);
}
/**
* MPEG layer for the frame. Normally layer 3 for audio.
*/
public static MPEGLayer getMPEGLayer(byte[] frame, int offset)
{
return MPEGLayer.fromValue((frame[offset + 1] & 0x6) >> 1);
}
/**
* MP3 bit rate
*/
public static int getBitRate(byte[] frame, int offset)
{
return MP3BitRate.getLayer3BitRate(getMPEGVersion(frame, offset), (frame[offset + 2] & 0xF0) >> 4);
}
/**
* Input audio sample rate.
*/
public static int getSampleRate(byte[] frame, int offset)
{
return MP3SampleRate.getSampleRate(getMPEGVersion(frame, offset), (frame[offset + 2] & 0x0C) >> 2);
}
/**
* Channel mode
*/
public static ChannelMode getChannelMode(byte[] frame, int offset)
{
return ChannelMode.fromValue((frame[offset + 3] & 0xC0) >> 6);
}
/**
* Indicates if the frame has padding
* @param frame
* @param offset
* @return boolean
*/
public static boolean isPadded(byte[] frame, int offset)
{
return (frame[offset + 2] & 0x02) == 0x02;
}
/**
* Get the number of bytes in the frame
* @param frame
* @param offset
* @return frame length in bytes
*/
public static int getFrameLength(byte[] frame, int offset)
{
return (getFrameSamples(frame, offset) / 8) * (getBitRate(frame, offset) * 1000) / getSampleRate(frame, offset) + (isPadded(frame, offset) ? 1 : 0);
}
/**
* Get the number of samples in the frame
* @param frame
* @param offset
* @return length of frame in samples
*/
public static int getFrameSamples(byte[] frame, int offset)
{
switch(getMPEGVersion(frame, offset))
{
case V_1:
switch(getMPEGLayer(frame, offset))
{
case LAYER1: return 384;
case LAYER2: return 1152;
case LAYER3: return 1152;
default:
}
case V_2:
case V_2_5:
switch(getMPEGLayer(frame, offset))
{
case LAYER1: return 384;
case LAYER2: return 1152;
case LAYER3: return 576;
default:
}
default:
}
return 0;
}
/**
* Get the frame duration in milliseconds
* @param frame
* @param offset
* @return duration in milliseconds
*/
public static int getFrameDuration(byte[] frame, int offset)
{
return (int)FastMath.round((double)getFrameSamples(frame, offset) / (double)getSampleRate(frame, offset) * 1000);
}
/**
* Inspects the frame bytes and provides details about the header.
*/
public static String inspect(byte[] frame, int offset)
{
StringBuilder sb = new StringBuilder();
sb.append("MP3 FRAME");
if(!isValid(frame, offset))
{
sb.append(" INVALID");
return sb.toString();
}
sb.append(" VER:").append(getMPEGVersion(frame, offset));
sb.append(" MODE:").append(getChannelMode(frame, offset));
sb.append(" ").append(getMPEGLayer(frame, offset));
sb.append(" BR:").append(getBitRate(frame, offset));
sb.append(" SR:").append(getSampleRate(frame, offset));
//TODO: add MONO/STEREO
return sb.toString();
}
}

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,113 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.audio.convert;
import java.util.Arrays;
import java.util.List;
import net.sourceforge.lame.mp3.Lame;
/**
* MP3 encoder settings.
*
* A curated list of LAME audio encoder settings that are optimized for mono-channel voice that is originally
* sampled at 8,000 Hz.
*
* See: https://svn.code.sf.net/p/lame/svn/trunk/lame/doc/html/usage.html
*/
public enum MP3Setting
{
CBR_16(false, 16, "CBR - Constant Bit Rate 16 kbps (default)"),
CBR_32(false, 32, "CBR - Constant Bit Rate 32 kbps"),
ABR_56(true, 56, "ABR - Average Bit Rate 56 kbps"),
VBR_5(true, Lame.QUALITY_MIDDLE, "VBR - Variable Bit Rate - Better Quality"),
VBR_7(true, Lame.QUALITY_LOW, "VBR - Variable Bit Rate - Good Quality");
private boolean mVariableBitRate;
private int mSetting;
private String mLabel;
/**
* Constructs an instance
* @param variableBitRate flag
* @param setting for the entry
* @param label to display
*/
MP3Setting(boolean variableBitRate, int setting, String label)
{
mVariableBitRate = variableBitRate;
mSetting = setting;
mLabel = label;
}
/**
* Default setting
*/
public static MP3Setting getDefault()
{
return MP3Setting.CBR_16;
}
/**
* Indicates if the entry is Constant Bit Rate (CBR), or Average/Variable Bit Rate (ABR or VBR)
* @return true if CBR
*/
public boolean isVariableBitRate()
{
return mVariableBitRate;
}
/**
* LAME encoder quality value
*/
public int getSetting()
{
return mSetting;
}
/**
* Input audio formats that are supported by the MP3Setting value.
* @return list of supported input audio sample rates.
*/
public List<InputAudioFormat> getSupportedSampleRates()
{
switch(this)
{
case CBR_16:
{
return InputAudioFormat.SAMPLE_RATES_8_16.stream().toList();
}
case CBR_32:
case VBR_7:
{
return InputAudioFormat.SAMPLE_RATES_8_16_22.stream().toList();
}
default:
{
return Arrays.stream(InputAudioFormat.values()).toList();
}
}
}
@Override
public String toString()
{
return mLabel;
}
}

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,64 +14,89 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
* ****************************************************************************
*/
package io.github.dsheirer.audio.convert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.commons.math3.util.FastMath;
public class MP3SilenceGenerator implements ISilenceGenerator
{
private final static Logger mLog = LoggerFactory.getLogger(MP3SilenceGenerator.class);
public static final int MP3_BIT_RATE = 16;
public static final boolean CONSTANT_BIT_RATE = false;
private MP3AudioConverter mGenerator = new MP3AudioConverter(MP3_BIT_RATE, CONSTANT_BIT_RATE);
private byte[] mPreviousPartialFrameData;
private MP3AudioConverter mMP3AudioConverter;
private InputAudioFormat mInputAudioFormat;
private byte[] mSilenceFrame;
private int mSilenceFrameDuration;
/**
* Generates MP3 audio silence frames
*/
public MP3SilenceGenerator()
public MP3SilenceGenerator(InputAudioFormat inputAudioFormat, MP3Setting setting)
{
mInputAudioFormat = inputAudioFormat;
mMP3AudioConverter = new MP3AudioConverter(inputAudioFormat, setting, false);
generate_frame();
}
public byte[] generate(long duration)
/**
* Generates a single silence frame
* @return
*/
private void generate_frame()
{
int length = (int)(duration * 8); //8000 Hz sample rate
//We generate silence at 8000 kHz, because it gets resampled by the silence generator to the target rate
int length = (int)(InputAudioFormat.SR_8000.getSampleRate());
List<float[]> silenceBuffers = new ArrayList<>();
silenceBuffers.add(new float[length]);
byte[] frameData = mGenerator.convert(silenceBuffers);
frameData = merge(mPreviousPartialFrameData, frameData);
if(frameData != null && frameData.length > 0)
int added = 0;
while(added < length)
{
if(frameData.length < 144)
{
mPreviousPartialFrameData = frameData;
return null;
}
else if((frameData.length % 144) == 0)
{
mPreviousPartialFrameData = null;
return frameData;
}
else
{
int integralFrameLength = (int)(frameData.length / 144) * 144;
mPreviousPartialFrameData = Arrays.copyOfRange(frameData, integralFrameLength, frameData.length);
return Arrays.copyOf(frameData, integralFrameLength);
}
int chunk = Math.min(length - added, 256);
silenceBuffers.add(new float[chunk]);
added += chunk;
}
return null;
List<byte[]> mp3Audio = mMP3AudioConverter.convert(silenceBuffers);
byte[] silence = new byte[0];
for(byte[] mp3Chunk: mp3Audio)
{
silence = merge(silence, mp3Chunk);
}
MP3AudioFrames mp3Frames = MP3FrameTools.split(silence);
if(mp3Frames.hasNextFrame())
{
mp3Frames.nextFrame();
mSilenceFrame = mp3Frames.getCurrentFrame();
mSilenceFrameDuration = mp3Frames.getCurrentFrameDuration();
}
}
/**
* Generates silence frames
* @param duration_ms in milliseconds
* @return
*/
public MP3AudioFrames generate(long duration_ms)
{
List<byte[]> silenceFrames = new ArrayList<>();
int silenceDuration = 0;
while(silenceDuration < duration_ms)
{
silenceFrames.add(mSilenceFrame);
silenceDuration += mSilenceFrameDuration;
}
return new MP3AudioFrames(silenceDuration, silenceFrames);
}
private static byte[] merge(byte[] a, byte[] b)
@@ -96,23 +121,30 @@ public class MP3SilenceGenerator implements ISilenceGenerator
return c;
}
/* TODO: Rework for MP3SilenceGenerator returning IAudioFrames
public static void main(String[] args)
{
mLog.debug("Starting ...");
for(long x = 243; x < 500; x ++)
MP3SilenceGenerator generator = new MP3SilenceGenerator(InputAudioFormat.SR_44100, MP3Setting.VBR_7);
int x = 100;
// for(; x < 1000; x += 10)
// {
List<byte[]> silenceFrames = generator.generate(x);
mLog.debug("Generated [" + silenceFrames.size() + "] silence frames");
for(byte[] silence: silenceFrames)
{
MP3SilenceGenerator generator = new MP3SilenceGenerator();
byte[] silence = generator.generate(x);
if(silence != null && silence.length > 0)
{
mLog.debug("Silence:" + x + " Length:" + silence.length);
MP3FrameInspector.inspect(silence);
}
mLog.debug("Silence:" + x + " Length:" + silence.length);
}
MP3FrameInspector.inspect(silenceFrames);
// }
mLog.debug("Finished");
}
*/
}

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

@@ -1,41 +1,44 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.bits;
import io.github.dsheirer.edac.CRC;
import java.io.UnsupportedEncodingException;
import java.util.BitSet;
import org.apache.commons.lang3.Validate;
import org.apache.commons.math3.util.FastMath;
import java.util.BitSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BinaryMessage extends BitSet
{
private static final long serialVersionUID = 1L;
private static final Logger LOGGER = LoggerFactory.getLogger(BinaryMessage.class);
private static final int[] CHARACTER_7_BIT = new int[]{0, 1, 2, 3, 4, 5, 6};
private static final int[] CHARACTER_8_BIT = new int[]{0, 1, 2, 3, 4, 5, 6, 7};
private static final String UTF_8 = "UTF-8";
/**
* 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 +46,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 +217,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()
{
@@ -1040,6 +1041,51 @@ public class BinaryMessage extends BitSet
return buffer;
}
/**
* Loads the hexadecimal text into a binary message
* @param hex text to load from
* @return loaded binary message
*/
public static BinaryMessage loadHex(String hex)
{
if(!hex.matches("[0-9A-F]*"))
{
throw new IllegalArgumentException("Message must contain only 0-9 and A-F hexadecimal characters");
}
int length = hex.length() / 2;
if(length * 2 < hex.length())
{
length++;
}
BinaryMessage buffer = new BinaryMessage(length * 8);
int bufferOffset = 0;
for(int x = 0; x < hex.length(); x += 2)
{
int endIndex = x + 2;
if(endIndex > hex.length())
{
endIndex--;
}
String rawHex = hex.substring(x, endIndex);
if(rawHex.length() == 1)
{
rawHex = rawHex + "0";
}
int value = Integer.parseInt(rawHex, 16);
buffer.setByte(bufferOffset, (byte)(value & 0xFF));
bufferOffset += 8;
}
return buffer;
}
/**
* Left rotates the bits between start and end indices, number of places.
*/
@@ -1135,19 +1181,101 @@ public class BinaryMessage extends BitSet
this.xor(mask);
}
public static void main(String[] args)
/**
* Parse ISO-7 bit characters from the message starting at the offset.
* @param offset where to start parsing.
* @param characterCount number of characters to parse
* @return parsed message.
*/
public String parseISO7(int offset, int characterCount)
{
BinaryMessage message = new BinaryMessage(10);
message.set(1);
message.set(4);
message.set(5);
message.set(8);
message.set(9);
byte[] bytes = new byte[characterCount];
System.out.println("Hex: " + message.toHexString() + " Size:" + message.size());
for(int x = 0; x < 10; x++)
for(int x = 0; x < characterCount; x++)
{
System.out.println(x + ": " + message.getHex(x, x + 10));
bytes[x] = getByte(CHARACTER_7_BIT, x * 7 + offset);
}
return new String(bytes);
}
/**
* Parse ISO-8 bit characters from the message starting at the offset.
* @param offset where to start parsing.
* @param characterCount number of characters to parse
* @return parsed message.
*/
public String parseISO8(int offset, int characterCount)
{
byte[] bytes = new byte[characterCount];
for(int x = 0; x < characterCount; x++)
{
bytes[x] = getByte(CHARACTER_8_BIT, x * 8 + offset);
}
return new String(bytes);
}
/**
* Parse UTF-8 characters from the message starting at the offset.
* @param offset where to start parsing.
* @param characterCount number of characters to parse
* @return parsed message.
* @throws UnsupportedEncodingException if UTF-8 encoding is not supported.
*/
public String parseUTF8(int offset, int characterCount) throws UnsupportedEncodingException
{
byte[] bytes = new byte[characterCount];
for(int x = 0; x < characterCount; x++)
{
bytes[x] = getByte(CHARACTER_8_BIT, x * 8 + offset);
}
return new String(bytes, UTF_8);
}
/**
* Parse 16-bit unicode characters from the message starting at the offset.
* @param offset where to start parsing.
* @param characterCount number of characters to parse
* @return parsed message.
*/
public String parseUnicode(int offset, int characterCount)
{
int length = characterCount * 2;
byte[] bytes = new byte[length];
for(int x = 0; x < length; x++)
{
bytes[x] = getByte(CHARACTER_8_BIT, x * 8 + offset);
}
return new String(bytes);
}
/**
* Parse 4-bit Binary Coded Decimal (BCD) characters from the message starting at the offset.
* @param offset where to start parsing.
* @param characterCount number of characters to parse
* @return parsed message.
*/
public String parseBCD4(int offset, int characterCount)
{
StringBuilder sb = new StringBuilder();
int count = 0;
while(count < characterCount)
{
int nibble = getNibble(offset);
//This will parse digits 0-9 and anything higher will parse as hexadecimal A-F
sb.append(Integer.toHexString(nibble).toUpperCase());
offset += 4;
count++;
}
return sb.toString();
}
}

View File

@@ -0,0 +1,70 @@
/*
* *****************************************************************************
* 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;
/**
* Base native buffer class.
*/
public abstract class AbstractNativeBuffer implements INativeBuffer
{
private long mTimestamp;
private float mSamplesPerMillisecond;
/**
* Constructs an instance
* @param timestamp for the start of this buffer
* @param samplesPerMillisecond to calculate the time offset of sub-buffers extracted from this native buffer.
*/
public AbstractNativeBuffer(long timestamp, float samplesPerMillisecond)
{
mTimestamp = timestamp;
mSamplesPerMillisecond = samplesPerMillisecond;
}
/**
* Timestamp for the start of this buffer
* @return timestamp in milliseconds
*/
@Override
public long getTimestamp()
{
return mTimestamp;
}
/**
* Quantity of samples representing one millisecond of sample data, used for calculating fragment timestamp offsets.
* @return samples per millisecond count.
*/
public float getSamplesPerMillisecond()
{
return mSamplesPerMillisecond;
}
/**
* Calculates the timestamp for a samples buffer fragment based on where it is extracted from relative to the
* native buffer starting timestamp.
* @param samplesPointer for the start of the fragment. Note: this value will be divided by 2 to account for I/Q sample pairs.
* @return timestamp adjusted to the fragment or sub-buffer start sample.
*/
protected long getFragmentTimestamp(int samplesPointer)
{
return getTimestamp() + (long)(samplesPointer / 2 / getSamplesPerMillisecond());
}
}

View File

@@ -0,0 +1,44 @@
/*
* *****************************************************************************
* 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;
/**
* Base class for native buffer factories.
*/
public abstract class AbstractNativeBufferFactory implements INativeBufferFactory
{
private float mSamplesPerMillisecond = 0.0f;
@Override
public void setSamplesPerMillisecond(float samplesPerMillisecond)
{
mSamplesPerMillisecond = samplesPerMillisecond;
}
/**
* Quantity of I/Q sample pairs per milli-second at the current sample rate to use in calculating an accurate
* timestamp for sub-buffer that are generated from the native buffer.
* @return samples per millisecond.
*/
public float getSamplesPerMillisecond()
{
return mSamplesPerMillisecond;
}
}

View File

@@ -0,0 +1,150 @@
/*
* *****************************************************************************
* 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 extends AbstractNativeBuffer
{
private static final int FRAGMENT_SIZE = 2048;
private final static float[] LOOKUP_VALUES;
private float mAverageDc;
//Creates a static lookup table that converts the 8-bit valued range from 0 - 255 into scaled float values
//of -1.0 to 0 to 1.0
static
{
LOOKUP_VALUES = new float[256];
for(int x = 0; x < 256; x++)
{
LOOKUP_VALUES[x] = ((float)x - 127.5f) / 128.0f;
}
}
private byte[] mSamples;
/**
* Constructs an instance
* @param samples to process
* @param timestamp of the samples
* @param averageDc measured from sample stream
* @param samplesPerMillisecond to calculate derivative timestamps for sub-buffers.
*/
public ByteNativeBuffer(byte[] samples, long timestamp, float averageDc, float samplesPerMillisecond)
{
super(timestamp, samplesPerMillisecond);
//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;
mAverageDc = averageDc;
}
@Override
public int sampleCount()
{
return mSamples.length / 2;
}
@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()
{
long timestamp = getFragmentTimestamp(mSamplesPointer);
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, timestamp);
}
}
/**
* 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()
{
long timestamp = getFragmentTimestamp(mSamplesPointer);
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, timestamp);
}
}
}

View File

@@ -0,0 +1,64 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.buffer;
import java.nio.ByteBuffer;
/**
* Implements a factory for creating ByteNativeBuffer instances
*/
public class ByteNativeBufferFactory extends AbstractNativeBufferFactory
{
private DcCorrectionManager mDcCorrectionManager = new DcCorrectionManager();
@Override
public INativeBuffer getBuffer(ByteBuffer samples, long timestamp)
{
byte[] copy = new byte[samples.capacity()];
samples.get(copy);
if(mDcCorrectionManager.shouldCalculateDc())
{
calculateDc(copy);
}
return new ByteNativeBuffer(copy, timestamp, mDcCorrectionManager.getAverageDc(), getSamplesPerMillisecond());
}
/**
* Calculates the average DC in the sample stream so that it can be subtracted from the samples when the
* native buffer is used.
* @param samples containing DC offset
*/
private void calculateDc(byte[] samples)
{
float dcAccumulator = 0;
for(byte sample: samples)
{
dcAccumulator += (sample & 0xFF);
}
dcAccumulator /= samples.length;
dcAccumulator -= 127.5f;
dcAccumulator /= 128.0f;
mDcCorrectionManager.adjust(dcAccumulator);
}
}

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

@@ -0,0 +1,114 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.buffer;
import java.util.concurrent.TimeUnit;
/**
* Provides DC offset correction value and manages calculation processing interval.
*/
public class DcCorrectionManager
{
// private static Logger mLog = LoggerFactory.getLogger(DcCorrectionManager.class);
// private DecimalFormat mDecimalFormat = new DecimalFormat("0.00000");
/**
* Time (delay) interval for running DC removal calculations
*/
private static final long DC_PROCESSING_INTERVAL = TimeUnit.SECONDS.toMillis(50);
/**
* Number of buffers to process per DC calculation processing interval.
*/
private static final int DC_CALCULATIONS_PER_INTERVAL = 5;
/**
* DC offset processing residual threshold for ending DC processing interval
*/
private static final float TARGET_DC_OFFSET_REMAINING = 0.0001f;
/**
* Number of DC calculations remaining in the current interval
*/
private int mDcCalculationsRemaining = DC_CALCULATIONS_PER_INTERVAL; //Initial value for coarse correction
/**
* Last time DC offset calculation interval ran
*/
private long mLastDcCalculationTimestamp = 0;
/**
* DC offset calculation gain.
*/
private static final float DC_FILTER_GAIN = 0.05f;
/**
* Current DC offset correction value
*/
private float mAverageDc = 0.0f;
public float getAverageDc()
{
return mAverageDc;
}
/**
* Adjusts the average DC based on the current calculated DC offset present in a sample buffer.
* @param calculatedDc for the current sample buffer
*/
public void adjust(float calculatedDc)
{
//Calculate the average scaled DC offset so that it can be applied in the native buffer's converted samples
float residualOffsetToRemove = calculatedDc - mAverageDc;
mAverageDc += (residualOffsetToRemove * DC_FILTER_GAIN);
if(Math.abs(residualOffsetToRemove) < TARGET_DC_OFFSET_REMAINING)
{
mDcCalculationsRemaining--;
}
//
// mLog.info("DC: " + mDecimalFormat.format(mAverageDc) +
// " RES:" + mDecimalFormat.format(residualOffsetToRemove) +
// " REM:" + mDcCalculationsRemaining);
}
/**
* Indicates if a DC offset calculation for a buffer should be performed.
*/
public boolean shouldCalculateDc()
{
if(System.currentTimeMillis() > (mLastDcCalculationTimestamp + DC_PROCESSING_INTERVAL))
{
if(mDcCalculationsRemaining > 0)
{
return true;
}
else
{
//Reset the number of buffers to calculate and the timestamp
mDcCalculationsRemaining = DC_CALCULATIONS_PER_INTERVAL;
mLastDcCalculationTimestamp = System.currentTimeMillis();
}
}
return false;
}
}

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,128 @@
/*
* *****************************************************************************
* 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 extends AbstractNativeBuffer
{
private float[] mInterleavedComplexSamples;
/**
* Constructs an instance
* @param complexSamples interleaved to wrap
* @param timestamp for the buffer
*/
public FloatNativeBuffer(float[] complexSamples, long timestamp, float samplesPerMillisecond)
{
super(timestamp, samplesPerMillisecond);
mInterleavedComplexSamples = complexSamples;
}
/**
* Constructs an instance
* @param samples to wrap
* @param timestamp for the buffer
*/
public FloatNativeBuffer(ComplexSamples samples)
{
this(SampleUtils.interleave(samples), samples.timestamp(), 0.0f);
}
/**
* Constructs an instance
* @param samples to wrap
*/
public FloatNativeBuffer(InterleavedComplexSamples samples)
{
this(samples.samples(), samples.timestamp(), 0.0f);
}
@Override
public Iterator<ComplexSamples> iterator()
{
return new ComplexSamplesIterator();
}
@Override
public Iterator<InterleavedComplexSamples> iteratorInterleaved()
{
return new InterleavedComplexSamplesIterator();
}
@Override
public int sampleCount()
{
return mInterleavedComplexSamples.length / 2;
}
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, getTimestamp());
}
}
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, getTimestamp());
}
}
}

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,47 @@
/*
* *****************************************************************************
* 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
* @param samplesPerMillisecond to calculate timestamp offset for child buffers.
* @return instance
*/
INativeBuffer getBuffer(ByteBuffer samples, long timestamp);
/**
* Sets the samples per millisecond rate based on the current sample rate.
*
* @param samplesPerMillisecond to calculate timestamp offset for child buffers.
*/
void setSamplesPerMillisecond(float samplesPerMillisecond);
}

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,159 @@
/*
* *****************************************************************************
* 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 extends AbstractNativeBuffer
{
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 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
* @param samplesPerMillisecond to calculate sub-buffer timestamps
*/
public SignedByteNativeBuffer(byte[] samples, long timestamp, float iAverageDc, float qAverageDc, float samplesPerMillisecond)
{
super(timestamp, samplesPerMillisecond);
//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;
mIAverageDc = iAverageDc;
mQAverageDc = qAverageDc;
}
@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()
{
long timestamp = getFragmentTimestamp(mSamplesPointer);
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, timestamp);
}
}
/**
* 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()
{
long timestamp = getFragmentTimestamp(mSamplesPointer);
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, timestamp);
}
}
}

View File

@@ -0,0 +1,137 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.buffer;
import java.nio.ByteBuffer;
import java.util.concurrent.TimeUnit;
/**
* Implements a factory for creating SignedByteNativeBuffer instances
*/
public class SignedByteNativeBufferFactory extends AbstractNativeBufferFactory
{
/**
* DC removal calculations will run once a minute
*/
private static final long DC_PROCESSING_INTERVAL = TimeUnit.SECONDS.toMillis(60);
/**
* Number of buffers to process per DC calculation processing interval.
*/
private static final int DC_CALCULATIONS_PER_INTERVAL = 5;
/**
* DC offset processing residual threshold for ending DC processing interval
*/
private static final float TARGET_DC_OFFSET_REMAINING = 0.0002f;
/**
* Number of DC calculations remaining in the current interval
*/
private int mDcCalculationsRemaining = DC_CALCULATIONS_PER_INTERVAL; //Initial value for coarse correction
/**
* Last time DC offset calculation interval ran
*/
private long mLastDcCalculationTimestamp = 0;
/**
* DC offset calculation gain.
*/
private static final float DC_FILTER_GAIN = 0.05f;
/**
* Current DC offset correction for Inphase samples
*/
private float mIAverageDc = 0.0f;
/**
* Current DC offset correction for Quadrature samples
*/
private float mQAverageDc = 0.0f;
@Override
public INativeBuffer getBuffer(ByteBuffer samples, long timestamp)
{
byte[] copy = new byte[samples.capacity()];
samples.get(copy);
if(shouldCalculateDc())
{
calculateDc(copy);
}
return new SignedByteNativeBuffer(copy, timestamp, mIAverageDc, mQAverageDc, getSamplesPerMillisecond());
}
/**
* Indicates if a DC offset calculation for a buffer should be performed.
*/
private boolean shouldCalculateDc()
{
if(System.currentTimeMillis() > (mLastDcCalculationTimestamp + DC_PROCESSING_INTERVAL))
{
if(mDcCalculationsRemaining > 0)
{
return true;
}
else
{
//Reset the number of buffers to calculate and the timestamp
mDcCalculationsRemaining = DC_CALCULATIONS_PER_INTERVAL;
mLastDcCalculationTimestamp = System.currentTimeMillis();
}
}
return false;
}
/**
* Calculates the average DC in the sample stream so that it can be subtracted from the samples when the
* native buffer is used.
* @param samples containing DC offset
*/
private void calculateDc(byte[] samples)
{
float iDcAccumulator = 0;
float qDcAccumulator = 0;
for(int x = 0; x < samples.length; x += 2)
{
iDcAccumulator += samples[x];
qDcAccumulator += samples[x + 1];
}
iDcAccumulator /= (samples.length / 2);
iDcAccumulator /= 128.0f;
iDcAccumulator -= mIAverageDc;
mIAverageDc += (iDcAccumulator * DC_FILTER_GAIN);
qDcAccumulator /= (samples.length / 2);
qDcAccumulator /= 128.0f;
qDcAccumulator -= mQAverageDc;
mQAverageDc += (qDcAccumulator * DC_FILTER_GAIN);
if(Math.abs(iDcAccumulator) < TARGET_DC_OFFSET_REMAINING && Math.abs(qDcAccumulator) < TARGET_DC_OFFSET_REMAINING)
{
mDcCalculationsRemaining--;
}
}
}

View File

@@ -0,0 +1,119 @@
/*
* *****************************************************************************
* 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 float mAverageDc;
private long mTimestamp;
private float mSamplesPerMillisecond;
/**
* 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
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public AirspyBufferIterator(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp,
float samplesPerMillisecond)
{
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;
mSamplesPerMillisecond = samplesPerMillisecond;
//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);
}
}
/**
* Calculates the timestamp for a samples buffer fragment based on where it is extracted from relative to the
* native buffer starting timestamp.
* @param samplesPointer for the start of the fragment. Note: this value will be divided by 2 to account for I/Q sample pairs.
* @return timestamp adjusted to the fragment or sub-buffer start sample.
*/
protected long getFragmentTimestamp(int samplesPointer)
{
return mTimestamp + (long)(samplesPointer / 2 / mSamplesPerMillisecond);
}
@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,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 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
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public AirspyBufferIteratorScalar(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
}
@Override
public ComplexSamples next()
{
if(mSamplesPointer >= mSamples.length)
{
throw new IllegalStateException("End of buffer exceeded");
}
long timestamp = getFragmentTimestamp(mSamplesPointer);
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, timestamp);
}
}

View File

@@ -0,0 +1,128 @@
/*
* *****************************************************************************
* 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;
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 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
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public AirspyBufferIteratorVector128Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
}
@Override
public ComplexSamples next()
{
if(mSamplesPointer >= mSamples.length)
{
throw new IllegalStateException("End of buffer exceeded");
}
long timestamp = getFragmentTimestamp(mSamplesPointer);
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, timestamp);
}
}

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 java.util.Arrays;
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 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
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public AirspyBufferIteratorVector256Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
}
@Override
public ComplexSamples next()
{
if(mSamplesPointer >= mSamples.length)
{
throw new IllegalStateException("End of buffer exceeded");
}
long timestamp = getFragmentTimestamp(mSamplesPointer);
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, timestamp);
}
}

View File

@@ -0,0 +1,125 @@
/*
* *****************************************************************************
* 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;
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 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
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public AirspyBufferIteratorVector512Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
//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");
}
long timestamp = getFragmentTimestamp(mSamplesPointer);
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, timestamp);
}
}

View File

@@ -0,0 +1,141 @@
/*
* *****************************************************************************
* 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;
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 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
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public AirspyBufferIteratorVector64Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
}
@Override
public ComplexSamples next()
{
if(mSamplesPointer >= mSamples.length)
{
throw new IllegalStateException("End of buffer exceeded");
}
long timestamp = getFragmentTimestamp(mSamplesPointer);
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, timestamp);
}
}

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.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
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public AirspyInterleavedBufferIteratorScalar(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
}
@Override
public InterleavedComplexSamples next()
{
if(mSamplesPointer >= mSamples.length)
{
throw new IllegalStateException("End of buffer exceeded");
}
long timestamp = getFragmentTimestamp(mSamplesPointer);
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, timestamp);
}
}

View File

@@ -0,0 +1,125 @@
/*
* *****************************************************************************
* 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
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public AirspyInterleavedBufferIteratorVector128Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
}
@Override
public InterleavedComplexSamples next()
{
if(mSamplesPointer >= mSamples.length)
{
throw new IllegalStateException("End of buffer exceeded");
}
long timestamp = getFragmentTimestamp(mSamplesPointer);
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, timestamp);
}
}

View File

@@ -0,0 +1,119 @@
/*
* *****************************************************************************
* 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
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public AirspyInterleavedBufferIteratorVector256Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
}
@Override
public InterleavedComplexSamples next()
{
if(mSamplesPointer >= mSamples.length)
{
throw new IllegalStateException("End of buffer exceeded");
}
long timestamp = getFragmentTimestamp(mSamplesPointer);
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, timestamp);
}
}

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