Compare commits

..

152 Commits

Author SHA1 Message Date
Denny Sheirer
b0a0ad4b43 Update README.md 2025-12-05 16:24:36 -05:00
Denny Sheirer
17cdd0df27 Update README.md 2025-12-05 16:07:38 -05:00
Denny Sheirer
6d23aaa06f Update README.md (#2330) 2025-12-05 15:55:46 -05:00
Denny Sheirer
933629e30d #2324 JMBE creator support for MacOS x86-64 (#2325)
Co-authored-by: denny <dennis.sheirer@gmail.com>
2025-11-29 05:56:38 -05:00
Denny Sheirer
8a6bc03ffc 2322 Tuner Manager logs MacOS Tahoe how-to fix when LibUSB doesn't get loaded. (#2323)
Co-authored-by: denny <dennis.sheirer@gmail.com>
2025-11-29 05:37:43 -05:00
Denny Sheirer
0c616d4531 2320 Mac OS Tahoe usb4java library update for legacy aarch64 support. (#2321)
Co-authored-by: denny <dennis.sheirer@gmail.com>
2025-11-29 04:19:29 -05:00
AJ Collins
b472c68b80 #1892 Icecast 2.4+: Fix retries after network disruption (#2279)
* #1892 Icecast 2.4+: Fix retries after network disruption

* #1892 Icecast 2.4+: Log the connection attempt/failure once and suppress subsequent logging until reconnected
2025-11-24 03:28:33 -05:00
Denny Sheirer
f6f4b07623 #2281 Updates P25 Adjacent Site Status logging format between TSBK and AMBTC formats (#2310)
Co-authored-by: denny <dennis.sheirer@gmail.com>
2025-11-22 06:00:00 -05:00
Denny Sheirer
de100b4662 #2304 sdrplay api now handles device failure without crash (#2309)
Co-authored-by: denny <dennis.sheirer@gmail.com>
2025-11-22 05:44:19 -05:00
Alan Crosswell
bbde3033b7 try to get resizing to be remembered and restored. (#2269)
Per tsinmw: Simplify to use mTable.setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS)
2025-11-22 05:24:30 -05:00
crsawyer
f6866f5408 Fixed mbe recording for P25-P2-TS2 (#2272) 2025-11-22 05:20:03 -05:00
crsawyer
d09fad56db P25 P1 MBE ESS order (#2273)
* Modified P25P1 MBE ESS order. ESS will now be populated after LDU2, not at the beginning of LDU2

* Added a version field and incremented it to '2' to signify a breaking change in the order of P25P1 ESS placement.
2025-11-22 05:18:41 -05:00
Denny Sheirer
7c06075880 #2283 Updates usb4java native libraries to 1.3.3 for MacOS Tahoe (26) fixes. (#2308)
Co-authored-by: denny <dennis.sheirer@gmail.com>
2025-11-22 04:23:02 -05:00
Denny Sheirer
5b597e29eb #2300 Fixes Motorola unit GPS parsing, fixes P25P1 message viewer issue, and restores map center on selected coordinate in track history. (#2307)
Co-authored-by: denny <dennis.sheirer@gmail.com>
2025-11-21 10:22:35 -05:00
Denny Sheirer
feb66831d4 #2283 Mac-OS Tahoe version 26. Updates usb4java with forked library to support multi-versioned native libraries on aarch-64 (#2306)
Co-authored-by: denny <dennis.sheirer@gmail.com>
2025-11-21 08:41:28 -05:00
Denny Sheirer
37d47f4219 #2295 JDK25 and Gradle 9.2 and updated 3rd party libraries. (#2296)
Co-authored-by: denny <dennis.sheirer@gmail.com>
2025-11-16 08:45:26 -05:00
Denny Sheirer
0d886c272e #1067 P25 Phase 1 C4FM/LSM decoder redesign. (#2294)
C4FM now uses SIMD differential decoding and soft sync detection.
LSM updated with soft sync detector and retains original Gardner error detector.
Adds new QPSK symbol view in the now playing channel tab.
Restores PLL error driven tuner Auto-PPM feature.
Deletes the demod viewer GUI application and all instrumented decoders
Updates DMR to send soft symbols to new symbol view in Now Playing.

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2025-11-16 06:52:17 -05:00
Denny Sheirer
48202da9e4 #2287 Resolves Motorola P25P1 LC GPS parsing issue. (#2293)
Co-authored-by: denny <dennis.sheirer@gmail.com>
2025-11-15 07:35:18 -05:00
Alan Crosswell
c4d9d58a5d Merge pull request #2263 from n2ygk/preserve_geometry
persist preferred spectral and control panel sizes
2025-08-04 03:17:33 -04:00
Denny Sheirer
fe5679fed7 #2265 NBFM noise squelch view saves user applied squelch changes and settings. (#2266)
Co-authored-by: denny <dennis.sheirer@gmail.com>
2025-08-03 04:17:29 -04:00
Denny Sheirer
b2be38b239 #1310 NBFM decoder uses new Noise Squelch and includes new noise squelch view and control. (#2264)
Co-authored-by: denny <dennis.sheirer@gmail.com>
2025-08-02 08:34:32 -04:00
Denny Sheirer
912925ee7d 2252 DMR Decoder error where LinearyInterpolator gets mu value outside 0 to 1.0 range. (#2253)
Co-authored-by: denny <dennis.sheirer@gmail.com>
2025-06-30 03:21:08 -04:00
Denny Sheirer
d86f8bf3d9 #2250 Logback configuration issue and suppress JDK24 warnings. (#2251)
Co-authored-by: denny <dennis.sheirer@gmail.com>
2025-06-28 07:53:27 -04:00
Denny Sheirer
88d8633958 #2234 Streaming Talkgroup Aliasing Feature (#2249)
Co-authored-by: denny <dennis.sheirer@gmail.com>
2025-06-28 06:53:55 -04:00
Denny Sheirer
3bf3de82fc #2247 JDK 24 and Gradle 8.14.2. Also updates several 3rd party java libraries. (#2248)
Co-authored-by: denny <dennis.sheirer@gmail.com>
2025-06-27 06:19:46 -04:00
Herb Jellinek
03a015f504 #2239 Gradle Javadoc task works correctly (#2240)
Fixes #2239 by adding the jdk.incubator.vector module, ignoring missing tags and malformed HTML.
2025-06-24 04:49:31 -04:00
Denny Sheirer
15b7923a4f #2226 DMR Decoder adds an equalizer to adjust symbol constellation balance/skew and gain to optimize symbol quadrant alignment/mapping. Reduces sync detectors from 3 to 2 and sets minimum objective channel sample rate to 19.2 kHz. (#2245)
Co-authored-by: denny <dennis.sheirer@gmail.com>
2025-06-24 04:38:10 -04:00
Denny Sheirer
189173e289 #2219 Byte native buffers now use 8192 (vs 2096) for complex buffer sample size so that the heterodyne channel buffer size is large enough to use with the carrier offset processor that reequires 128 sample buffers. (#2220)
Co-authored-by: denny <dennis.sheirer@gmail.com>
2025-04-20 08:25:24 -04:00
Denny Sheirer
2dcd994eb2 #2212 Introduce carrier offset measurement processor to monitor a 12.5 kHz channel and provide estimated carrier offset measurements that allow the tuner's frequency error monitor to correct for the average error reported from each channel. Uses a 15 dB threshold where the signal must be at least 15 dB above the noise floor before it will calculate and broadcast the estimate. Stores the updated PPM in the tuner configuration after each update to ensure the value persists across sessions. When selected in the tuner panel, updates the displayed PPM value after each PPM autocorrection. (#2218)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2025-04-20 06:55:43 -04:00
Denny Sheirer
afadb227db #2209 Playlist Manager double click selects the playlist (#2210)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2025-03-22 07:16:12 -04:00
tadscottsmith
5dbcd5a8ad OpenMHz Broadcast Enhancements (#2207) 2025-03-22 06:26:15 -04:00
Denny Sheirer
728a85c180 #2149 P25P1 adds L3Harris Talker GPS support (#2208)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2025-03-22 06:22:40 -04:00
Denny Sheirer
dd6ddd59e9 #2193 Removes "Log Settings" button from the channel view. This is a debug button. (#2205)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2025-03-21 08:02:47 -04:00
Denny Sheirer
758794fdb4 #2195 Fix P25P1 NPE when accessing AMBTC network status message data blocks. (#2204)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2025-03-21 07:54:47 -04:00
Denny Sheirer
49b95a9445 #2202 DMR talker aliases now show in channel activity summary. Added 'TA-' prefix to talker aliases (DMR & P25) to visually highlight what the value represents. (#2203)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2025-03-21 07:27:33 -04:00
Denny Sheirer
a4bcdfa769 #2127 DMR capacity plus wide area voice channel user link control messages now create a call event (#2201)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2025-03-21 06:46:28 -04:00
Denny Sheirer
872498badc #2166 DMR adds RS(12,9) error detection and correction on link control. Enhances DMR CRC mask manager to process CSBK and link control messages to improve handling of RAS enabled systems when the user has not opted to ignore CRC checksums. Fixes issue with CapMax voice channels not resolving to frequency. Fixes issue in playlist editor where DMR LCN description values were not updating correctly. (#2200)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2025-03-21 06:23:58 -04:00
Sir Christoff Von-Dryden Of The Bailey LLC
4afb74a847 Check the I2CRepeater state before changing it. (#2197)
Resolves #1923 - 'USB Error 9' preventing configuration of rtl-sdr devices
2025-03-20 02:17:31 -04:00
Denny Sheirer
96f0b7dd55 #2174 Playlist editor channels tab, frequency column sort ordering. (#2192)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2025-03-14 04:04:59 -04:00
Randy
b360950b80 Update IntegerFormatter.java for negative squelch value (#2187)
Two issues:
(1)documentation for change.getText() says it can be an empty string but never null. 
(2)Need to allow for typing a minus sign before validating min/max.
2025-03-14 03:52:49 -04:00
Denny Sheirer
5b0a1401a1 #2157 Resolves audio output getting 'stuck' when the thread is blocking on an attempt to write audio to the data line and that blocking call is not executing. Also updates previous attempt at fixing a stalled data line by closing and reopening the line, without having to recreate the data line, which was causing related issues with the gain and muting behavior. (#2191)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2025-03-13 04:09:26 -04:00
Daniel Ponte
3fa114501b Add talker alias field to rdio-scanner upload (#2186) 2025-03-11 02:53:51 -04:00
Denny Sheirer
8935835921 #2177 DMR TMS packet parsing issue (#2178)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2025-03-10 03:54:02 -04:00
Denny Sheirer
dec255d444 #2175 DMR Text Message Service (TMS) support. (#2176)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2025-03-10 03:45:16 -04:00
tadscottsmith
1cb0490985 Add Motorola Talker Alias Identifier (#2154)
* Add MotorolaTalkerAlias identifier.

* Remove duplicate call to talkerAliasManager.

* Use TalkerAlias RID and remove additional cosmetic updates.
2025-03-10 02:27:39 -04:00
Denny Sheirer
c8235ea4fc #2171 Updates OpenJDK to version 23.0.2+9 release for the build system. (#2172)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2025-03-09 09:00:29 -04:00
Randy
7d69d829b5 Fix adding available channel to wrong queue (#2169)
Available channel was added to P1 queue instead of P2 queue.
2025-03-09 07:40:38 -04:00
Randy
a46abf7da8 Cleanup of recording paths #2128 (#2168)
* Update replaceIllegalCharacters

* Add underscore after timestamp
2025-03-09 07:34:52 -04:00
modificaz
437264e272 Fix incorrect array assignment (frame_3 → frame_2) (#2159) 2025-03-09 07:29:23 -04:00
Denny Sheirer
8718d04ef5 #2162 APCO25 NID BCH decoder implementation. (#2163)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2025-02-27 09:09:46 -05:00
Denny Sheirer
4e367bb5fe W#2150 Adds AMBE audio tone aliasing support to DMR. (#2151)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2025-02-15 07:20:46 -05:00
Dennis Sheirer
71d2cb6fca sdrtrunk GitHub issues templates update. 2025-01-20 06:50:59 -05:00
Dennis Sheirer
e856de273d sdrtrunk GitHub issues templates update. 2025-01-20 06:48:04 -05:00
Denny Sheirer
ea0a9da24c #2138 DMR audio segments get chopped with every invalid link control message and remove the insert of Talkgroup 1 as a default value for conventional and direct mode channels. (#2139)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2025-01-20 06:40:27 -05:00
Denny Sheirer
b3cd7d787a #2125 Motorola Talker Alias CRC index out of bounds error. (#2126)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2025-01-07 03:44:58 -05:00
Denny Sheirer
9a13a82ed1 Update README.md (#2124)
Updates min OSX supported version.
2025-01-04 07:07:43 -05:00
Denny Sheirer
9b71508d49 #2122 Removes extraneous logging from DMR processing. (#2123)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2025-01-01 10:05:20 -05:00
Denny Sheirer
694ea83a80 #2120 DMR Decoder resolves index out of bounds error and also updates Hamming 16/17 decoders to correctly identify multi-bit errors when the hamming parity bit is the syndrome. (#2121)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2025-01-01 09:51:41 -05:00
Denny Sheirer
70fb74b787 #2117 DMR simplex transmissions now frame correctly and added additional case for FLC BPTC error correction. (#2118)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-12-25 08:14:48 -05:00
Denny Sheirer
9ffa02e2e5 #1633 DMR Decoder redesign. Uses new DQPSK vector/scalar demodulator implementations and soft sync detectors for more accurate detections and symbol timing and symbol period calculations. Includes two new calibrations with scalar and vector implementations for DQPSK demodulator and Interpolator. Enhances DMR error detection and correction algorithms, specifically the block product turbo code algorithms. (#2115)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-12-24 06:53:58 -05:00
ilyacodes
11abb87177 Add Motorola Failsoft LC opcode $02 (#2104)
This is broadcast on channels when a site is in failsoft operation (site controller has failed or too few channels are functional to operate in trunking mode).
2024-12-06 12:29:05 -05:00
Denny Sheirer
49cf92c7ab #2105 P25 P1/2 Motorola Talker Alias CRC Check (#2106)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-12-06 04:33:14 -05:00
Denny Sheirer
4cf51405ea #2102 Log Unknown Tuners From SDRPlay API (#2102)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-12-03 17:16:37 -05:00
Dennis Sheirer
db83d5121c Version 0.6.1 Final 2024-12-03 16:29:13 -05:00
ilyacodes
d61d9c07b0 Add Motorola OTA alias decoding (#2098) 2024-12-02 18:31:37 -05:00
Denny Sheirer
5c9c3fa503 #2089 Motorola P25P2 TDMA data channel support (#2090)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-11-12 02:22:22 -05:00
Denny Sheirer
552d3a6ce9 #2086 Audio Playback Manager can leak memory if audio queue processing thread is never started. (#2087)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-11-08 03:53:21 -05:00
Denny Sheirer
3bf425f94a #2084 Updates usb4java native library for windows aarch64 (#2085)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-11-06 03:59:14 -05:00
Denny Sheirer
660a5acd2f #2081 Windows aarch64 libusb support. (#2083)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-11-05 03:56:19 -05:00
Denny Sheirer
551277f399 #2078 Broadcastify feed verbose logging now includes logging of audio and metadata (#2080)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-11-03 03:20:17 -05:00
Dennis Sheirer
a12040be5c Revert "#2076 DMR CapMax Network and Site vales off by one. (#2077)"
This reverts commit e4be7dc5f4.
2024-11-02 06:58:23 -04:00
Denny Sheirer
e4be7dc5f4 #2076 DMR CapMax Network and Site vales off by one. (#2077)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-11-02 06:44:20 -04:00
Denny Sheirer
b615c1e16a #2074 DMR CapMax LRRP GPS positions sent to map now contain channel identifiers (alias list and system name). (#2075)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-11-02 06:12:38 -04:00
Denny Sheirer
912af4e3e6 #2071 Version 0.6.1 Beta 3 (#2072)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-11-01 05:46:42 -04:00
Denny Sheirer
df0d0468c8 #2055 MultiFrequencyTunerChannelSource Null Pointer Error On Channel Source Shutdown - different solution to resolve possible memory leak with previous solution. Also fixes P25 issue where a traffic channel that doesn't get started is returned to the traffic channel pool. (#2070)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-11-01 05:32:10 -04:00
Denny Sheirer
7509245751 #2068 P25 traffic channel manager ignores traffic channel requests if the frequency value is zero (#2069)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-31 05:53:42 -04:00
Denny Sheirer
02e236202c #2066 P25 Phase 1 frequency band link control messages are now checked for valid CRC before being used. (#2067)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-31 05:29:51 -04:00
Denny Sheirer
4b1d294211 #2064 Moto talker GPS event TO/FROM identifiers. (#2065)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-31 05:07:42 -04:00
Denny Sheirer
40840dbd5c #2062 Verbose logging option for Broadcastify Feed to troubleshoot streaming connection issues. (#2063)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-31 04:11:45 -04:00
Denny Sheirer
786ae9369b #1992 Traffic channels stuck in teardown. (#2061)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-30 03:57:42 -04:00
Denny Sheirer
b7bbf10415 #2055 Multi-Frequency channel source with channel rotation monitor startup can get ahead of the tuner channel source and cause an NPE. (#2056)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-28 03:30:37 -04:00
Denny Sheirer
486ac01416 #1867 Event logger ensures from identifier is the radio ID and not the system transmitted radio alias. (#2050)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-27 09:31:51 -04:00
Denny Sheirer
9e01cc48a5 #2034 Map plotted elements now include date in the location timestamp. (#2049)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-27 09:08:41 -04:00
Denny Sheirer
37996d0477 #2040 Reset AMBE/IMBE audio codec after each call to prevent carryover of last audio frame from previous call. (#2048)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-27 08:34:27 -04:00
Denny Sheirer
a15ff9040b #2037 JavaFX Windows Can't Be Shrunk. Reverts changes made under #1619. (#2047)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-27 08:22:45 -04:00
Denny Sheirer
b3cd3f431f #2045 P25 Phase 1 Data channels are now ignored if the channel config calls for it. (#2046)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-27 05:29:25 -04:00
Denny Sheirer
649d1f650a #2043 DMR Tier 3 Capacity Max talkgroups were parsing incorrectly. (#2044)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-26 16:35:59 -04:00
Denny Sheirer
215e4064fa #2039 P25P2 bogus talkgroup and radio values showing in calls. Corrects message length error that was causing mis-aligned mac message parsing that contained bad/errant talkgroup and radio values to be included in the call metadata. Adds support for a new Moto feature discovery that appears to be announcing talkgroup member radios that are active on/tuned to the traffic channel. (#2042)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-26 09:18:35 -04:00
Denny Sheirer
66a0b2543b #1060 Adds Alias List delete and rename operations to playlist editor (#2038)
Original code changes implemented by @whelgeson

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-21 05:34:06 -04:00
Denny Sheirer
378841b408 #2028 Adds support for Windows ARM/aarch64 Operating System (#2029)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-19 06:11:54 -04:00
Denny Sheirer
e28d726182 #2035 P25P1 Harris Patched Talkgroup Values Incorrect (#2036)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-19 04:43:15 -04:00
Denny Sheirer
71bc27e3d9 #2032 Audio output suppresses logging after 5 times when app can't get gain or mute controls for current audio output. (#2033)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-18 03:05:21 -04:00
Denny Sheirer
bcfb2700cb #2030 Playlist Editor double-click channel row to auto-start channel. (#2031)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-18 02:43:55 -04:00
Denny Sheirer
2d9491ac93 #2025 P25P1 audio module null codec error. (#2027)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-18 01:33:23 -04:00
Denny Sheirer
4977c3e4fa #2022 Audio output reset now reacquires gain and mute controls. (#2023)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-16 04:09:37 -04:00
Denny Sheirer
54a0ca5247 #2020 P25 channel null frequency band error. (#2021)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-16 02:59:55 -04:00
Denny Sheirer
024a624083 #2018 P25 Phase 1 resolves issue where NowPlaying identifiers were flickering as they are removed and readded with each TDULC terminator message. (#2019)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-15 09:53:52 -04:00
Denny Sheirer
9a02416403 #2016 Fixes code regression introduced in #1973 with null playback preferences. (#2017)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-14 17:29:58 -04:00
Denny Sheirer
91e5bc8ba1 #1973 JUnit test for patched talkgroup audio call streaming that tests streaming the audio as the patched group or as the individual patched talkgroups according to the user preferences setting in call management. (#2015)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-14 15:20:53 -04:00
Denny Sheirer
97afdfa8a9 #2013 Enhance SUID extension message handling and correct encryption sync when the algorithm is detected as ACCORDION and the key ID and MI are both zero, indicating a bad algorithm ID value. (#2014)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-14 13:32:40 -04:00
Denny Sheirer
67f91b80f2 #2006 AudioOutput now requests a new source data line from the mixer when it detects that the line no longer accepts audio byte data. (#2011)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-13 11:58:03 -04:00
Denny Sheirer
1b3ce431e6 #2007 P25 Call events tracking. P25 Traffic channel manager now uses event trackers to deconflict between control channel and traffic channel event updates. Protects against Motorola ISSI FQSUID that's all zeros. Resolves issue with running out of traffic channels after losing the control channel and going into CC hunt mode a few times. (#2010)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-13 11:53:12 -04:00
Denny Sheirer
d24895575c #2004 P25P1 Link Control Extended Source messages now process through decoder state correctly and display in Now Playing. (#2005)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-05 08:53:27 -04:00
Denny Sheirer
c97e355349 #2002 P25 fully qualified identifiers now formatted correctly in now playing window. (#2003)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-05 05:06:51 -04:00
Denny Sheirer
3c8cd0386d #1914 P25 P1 & P2 Talker Alias Support for Motorola and L3Harris. Talker alias manager caches aliases and applies to future events. Active (cached) talker aliases now listed in the channel Details tab. Enables multi-select in message viewer for all supported protocols. Note: decoding of Motorola's talker alias format not yet implemented - only recognition and reassembly of the encoded sequence is currently supported. (#2001)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-05 03:47:18 -04:00
Dennis Sheirer
600db3fb8a #1998 P25 Traffic Channel Manager error ConcurrentModification on Allocated Channels Map, introduced in #1996 2024-10-02 03:23:20 -04:00
Denny Sheirer
161595e7c8 #1996 P25P1 traffic channel monitor now clears allocated traffic channels each time the control frequency changes, which should be after a lose-sync event. This ensures that none of the traffic channel frequencies get blocked when the monitor thinks that a channel is already allocated. (#1997)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-10-01 04:03:25 -04:00
Denny Sheirer
0ecdf4dd2b #1994 P25 Phase1 missing subsequent calls audio when multiple conversations on the same traffic channel. (#1995)
Fixed issue where sdrtrunk was not checking HDU and LDU2 encryption sequences as valid before assessing the encryption state of a call.  Updated P25P1 audio module to check the valid flag for both the HDU and the LDU2 encryption segment before assigning encryption state.  Now caches all LDU messages until encryption state is determined.

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-09-29 07:53:13 -04:00
Denny Sheirer
32ce79b8af #1990 Ignores GPS positions with 0/0 latitude and longitude values. (#1991)
(cherry picked from commit 55b20c75c0)

Co-authored-by: sheirerd <sheirerd@ainfosec.com>
2024-09-22 09:56:00 -04:00
sheirerd
8070ae3c4b Revert "#1990 Ignores GPS positions with 0/0 latitude and longitude values."
This reverts commit 55b20c75c0.
2024-09-22 09:50:53 -04:00
sheirerd
55b20c75c0 #1990 Ignores GPS positions with 0/0 latitude and longitude values. 2024-09-22 09:46:11 -04:00
Denny Sheirer
5d4dff7948 #1985 Update to JDK 23 (#1987)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-09-20 09:33:36 -04:00
Denny Sheirer
b1229b6971 #1915 P25 audio playback can stop playing audio despite everything else appears to be normal. (#1986)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-09-19 15:23:31 -04:00
Denny Sheirer
9474d53a93 #1983 Reverts aliasing of fully qualified identifiers against locally assign ID or address values - must be an exact match to the fully qualified identifier. (#1984)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-09-18 03:51:47 -04:00
Denny Sheirer
6f05db723f #1981 Adds P25 Phase1 support for foreign system IDEN_UPD_TDMA messages on control channel. (#1982)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-09-17 03:54:16 -04:00
Doug Hoffman
056bccb89a Improvements to icecast failure handling (#1401)
- Add mechanism to persist failure reason while continuing to reconnect
- Add 'warning' attribute for failure conditions that may be temporary,
  but still need to be reported
- Add logging for failures
- Prevent possibly temporary server-side conditions from blocking
  reconnect attempts
- Detect icecast 2.4 auth failures
- Prevent generic errors from overriding existing error state
2024-09-15 08:48:00 -04:00
Denny Sheirer
e00cd90aef #1939 Duplicate call detector enhancements and unit testing. (#1980)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-09-15 08:39:53 -04:00
Denny Sheirer
8bb59d581f #1977 P25 FQSUID handling improvements. P25 fully qualified radio/talkgroup identifiers we're being flagged as invalid when ISSI patch assigned a local address/ID of zero. Changes format of FQSUID and FQTGID to use a value of zero for the local alias when the call type is an ISSI patch and suppress display of the 0 alias value. If the identifier represents a roaming user, then the local address value is displayed with the FQSUID appended in parenthesis. (#1978)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-09-15 05:37:01 -04:00
Denny Sheirer
b9068da99c #1971 P25 AMBTC Status Query - Error Parsing Source Address (#1976)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-09-09 09:37:09 -04:00
Denny Sheirer
9f3f7e65a4 #1974 P25 Fully Qualified Talkgroup/Radio values shouldn't replace local ID/address, when the value is zero, with the fully qualified ID, which causes incorrect aliasing. (#1975)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-09-09 09:24:05 -04:00
Denny Sheirer
e6a804836b #1948 Audio Stream starts are now spun off to a cached thread pool to prevent any initial stream connections from delaying the application startup. (#1968)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-09-03 04:13:25 -04:00
Denny Sheirer
5d8d3e4a41 #1966 P25 aliases not correctly matching when radio system is using fully qualified talkgroup/radio values. (#1967)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-09-02 08:05:17 -04:00
Denny Sheirer
298d9d7016 #1964 Mac OS issue when loading SDRPlay API native library from fully qualified path. (#1965)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-09-02 05:11:03 -04:00
Denny Sheirer
d7fbbcefc5 #1960 application root directory not found on fresh install (#1961)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-08-31 08:33:27 -04:00
Denny Sheirer
36e198eb1b #1955 Java JDK 22 and Gradle 8.10. Updates SDRPlay API to latest Project Panama FFM API changes. Increment app version to 0.6.1-beta2 (#1958)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-08-31 08:03:34 -04:00
Denny Sheirer
130e59f4c1 #1912 P25 Patch Group info no longer missing from Now Playing and recording and streaming metadata. (#1954)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-08-23 08:27:38 -04:00
Denny Sheirer
9223e7c056 #1952 P25 Traffic Channel Manager now clears stale call events from the traffic channel event map so that multiple calls don't get aggregated. (#1953)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-08-23 07:49:07 -04:00
Denny Sheirer
c3487411fb #1860 Resolves USB transfer buffer submit errors and resubmit error tracking. Synchronizes the submit transfers method so that multi-threaded access doesn't cause accounting errors. Ignores LIBUSB_ERROR_BUSY when (re)submitting due to accounting errors. Comments out several P25 unrecognized messaging loggers. (#1950)
Co-authored-by: sheirerd <sheirerd@ainfosec.com>
2024-08-21 03:27:44 -04:00
Denny Sheirer
142dd9d579 1944 SDRPlay API version 3.16 (#1945)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-07-29 04:20:03 -04:00
Denny Sheirer
53d537bda8 #1781 Radio Reference API - adds logging to log results of test connection or user login attempt for troubleshooting RR enterprise user login issues. (#1943)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-07-29 04:14:33 -04:00
Denny Sheirer
24e11b8ed6 #1934 P25P1 conventional fallback message toString() output fixed. (#1935)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-06-02 07:15:20 -04:00
Denny Sheirer
14a7b040f9 #1931 Resolves issue where P25 Phase 2 L3Harris Patch Group message incorrectly includes Talkgroup/Radio identifiers incorrectly parsed from the subsequent message. Updated code to respect the self-reported message octet length and also to test for correct length before adding patch group member talkgroup and radio identifiers. (#1932)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-06-01 06:38:38 -04:00
Denny Sheirer
2af3e992a1 #1926 Toolkit not initialized error when running headless during APCO25 channel start processing request. (#1928)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-05-30 03:48:04 -04:00
Denny Sheirer
737c3faacc #1916 SDRPlay API 3.15 and RSPdxR2 support. (#1921)
Co-authored-by: sheirerd <sheirerd@ainfosec.com>
2024-05-26 06:41:56 -04:00
Denny Sheirer
03792b0245 #1924 Resolves issue with talker GPS positions showing up in map panel as user identifiers. (#1925)
Co-authored-by: sheirerd <sheirerd@ainfosec.com>
2024-05-26 06:29:07 -04:00
Denny Sheirer
dddda052fa #1849 Map enhancements. Adds track list, zoom to selection, history trail size and other features. (#1920)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-05-19 07:06:44 -04:00
Denny Sheirer
c50a914632 #1907 Hytera GPS positions, XPT Adjacent site, and Hytera Talker Aliases. (#1908)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-05-04 18:27:54 -04:00
Denny Sheirer
5f082973eb #190t P25 Phase 1 encrypted call status (#1906)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-05-04 13:14:07 -04:00
Denny Sheirer
2f0419f796 #1903 P25 Phase 2 audio - resolves 2-second chopped audio segments (#1904)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-05-04 06:58:45 -04:00
Denny Sheirer
cbe451a793 #1876 Unifies reentrant lock between tuners and frequency controller. (#1901)
Co-authored-by: sheirerd <sheirerd@ainfosec.com>
2024-05-04 05:29:54 -04:00
Denny Sheirer
aa8d449140 #1899 radio reference API version 18 and update site editor to use new 'tdma_cc' flag to distinguish P25 Phase 2 FDMA vs TDMA control channel sites. (#1900)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-04-27 10:32:46 -04:00
Denny Sheirer
7e709d9d6f #1304 P25 Phase 1/2 decoder enhancements. (#1898)
* P25 Phase 2 now supports control channel decoding and decoder was enhanced to improve decodes.
* Radio reference site editor updated for phase 2 sites to allow user to select FDMA or TDMA control.
* Decode events are now minimized and de-duplicated.
* Fully parsing all phase 1/2 messages from the latest ICD.
* Vendor specific messaging recoveries (Moto & Harris).
* Patch group management enhancements with stale patch detection.
* Tuner editor now fully resets the min/max frequency values
* Default traffic channel count increased to 20 (from 3).
* ISSI roaming radio and talkgroup values now displayed and aliasable as fully qualified values (e.g. 1234(123.456.7890)
* Moto emergency alarm activation messaging support
* Expanded list of P25 encryption algorithms
* Message (bits) viewer enhanced for P25 phase 1 and phase 2
* IPV4 addresses and UDP ports no longer classified as user values - won't display in event views.
* New framework for describing message binary fields that should reduce overall memory footprint and enhance code readability

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-04-26 13:55:17 -04:00
Denny Sheirer
f5938fa8e2 #1856 DMR traffic channel thread dead-lock. Removing synchronized method access since content access is already protected by the reentrant lock. The synchronized method is redundant and also causing the dead-lock. (#1857)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-02-13 04:05:35 -05:00
Denny Sheirer
ca2509e9f9 #1854 Tuner minimum frequency of 1 Hertz now supported. Resolves issue where the FrequencyTextField wouldn't accept minimum frequency values smaller than 1 MHz on initial setup. (#1855)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-02-12 04:21:56 -05:00
Denny Sheirer
1d2ba8c06c #1852 Spectral channel banners incorrect color for multi-frequency channels. (#1853)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-02-12 03:29:13 -05:00
Denny Sheirer
73275aa8ae #172 Allow users to adjust tuner min/max frequency ranges so that they can ensure tuners stay put and support channels in a predicatable manner. (#1850)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-02-11 17:08:43 -05:00
Denny Sheirer
2c32096ec9 #1847 Updates spectral display to show channel overlay for all frequencies in a channel configuration that contains multiple frequencies. (#1848)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-02-11 06:19:39 -05:00
Denny Sheirer
6532e33296 #1845 Updates polyphase tuner channel manager to avoid retuning for a new channel if all channels will continue to fit using the current center frequency (#1846)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-02-11 05:22:18 -05:00
Denny Sheirer
7ea8cb63d2 #1843 Map entity plotter concurrent modification exception (#1844)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-02-11 04:26:54 -05:00
Denny Sheirer
1df29b62eb #1841 DMR call events include channel and frequency (#1842)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-02-10 08:04:25 -05:00
Denny Sheirer
8a5b31f143 #1839 DMR tone identifiers don't clear at the end of a call. (#1840)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-02-10 07:39:37 -05:00
Denny Sheirer
4d7a8e5980 #1837 Channel Stream Thread Naming With Channel Config Number, Protocol and Frequency (#1838)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2024-02-10 07:30:33 -05:00
1304 changed files with 93365 additions and 46964 deletions

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Support and Questions - How do I XXX? Does it support XXX?
url: https://github.com/DSheirer/sdrtrunk/wiki/Support
about: Please ask your user questions here -- DO NOT create an issue if you have a user question please!!

View File

@@ -1,16 +0,0 @@
---
name: How-To Question or Does The Software Support xxx
about: End user question or support discussion
title: STOP - Don't Use Issue Reporting to Post End-User Questions!!!
labels: ''
assignees: ''
---
STOP !!!!
GitHub issues are for reporting software bugs or requesting enhancements.
If you have a question about how to use the software, or if you want to know if sdrtrunk supports a protocol, tuner, or other aspect, please post your question in one of the Support forums.
Visit: https://github.com/DSheirer/sdrtrunk/wiki

View File

@@ -22,10 +22,10 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up JDK 20
- name: Set up JDK 25
uses: actions/setup-java@v3
with:
java-version: '20'
java-version: '25'
distribution: 'liberica'
java-package: 'jdk+fx'
- name: Build with Gradle

View File

@@ -15,10 +15,10 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up JDK 20
- name: Set up JDK 25
uses: actions/setup-java@v3
with:
java-version: '20'
java-version: '25'
distribution: 'liberica'
java-package: 'jdk+fx'

View File

@@ -2,18 +2,24 @@
<dictionary name="denny">
<words>
<w>bahroo</w>
<w>baseband</w>
<w>basebanded</w>
<w>broadcastify</w>
<w>cach</w>
<w>correlator</w>
<w>deinterleave</w>
<w>descrambled</w>
<w>dibit</w>
<w>dinf</w>
<w>dqpsk</w>
<w>esnid</w>
<w>fleetsync</w>
<w>heterdyned</w>
<w>heterodynes</w>
<w>hytera</w>
<w>icecast</w>
<w>ident</w>
<w>lcns</w>
<w>nutall</w>
<w>overflowable</w>
<w>polyphase</w>
@@ -29,6 +35,7 @@
<w>talkgroups</w>
<w>tgid</w>
<w>ultravox</w>
<w>unsynchronized</w>
<w>wacn</w>
<w>xtea</w>
</words>

2
.idea/misc.xml generated
View File

@@ -4,7 +4,7 @@
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_20_PREVIEW" project-jdk-name="liberica-20" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_25_PREVIEW" project-jdk-name="liberica-25" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

View File

@@ -1,6 +1,24 @@
![Gradle Build](https://github.com/dsheirer/sdrtrunk/actions/workflows/gradle.yml/badge.svg)
![Nightly Release](https://github.com/dsheirer/sdrtrunk/actions/workflows/nightly.yml/badge.svg)
# MacOS Tahoe 26.1 Users - Attention:
Changes to USB support in Tahoe version 26.x cause sdrtrunk to fail to launch. Do the following to install the latest libusb and create a symbolic link and then use the nightly build which includes an updated usb4java native library for Tahoe with ARM processor. There may still be issue(s) with MacOS accessing your USB SDR tuners.
```
brew install libusb --HEAD
cd /opt
sudo mkdir local
cd local
sudo mkdir lib
```
Next, find where brew installed the libusb library, for example: ```/opt/homebrew/Cellar/libusb/HEAD-9ceaa52/lib/libusb-1.0.0.dylib``` Note: the folder "HEAD-9ceaa52" is the version stamp for HEAD when you installed from it.
Finally, create a symbolic link from the installed library to the place where usb4java is expecting to find libusb (/opt/local/lib/libusb-1.0.0.dylib)
```
sudo ln -s /opt/homebrew/Cellar/libusb/HEAD-9ceaa52/lib/libusb-1.0.0.dylib /opt/local/lib/libusb-1.0.0.dylib
```
# sdrtrunk
A cross-platform java application for decoding, monitoring, recording and streaming trunked mobile and related radio protocols using Software Defined Radios (SDR).
@@ -28,6 +46,6 @@ playlist(s) before you use the nightly builds.** Note: the nightly release is u
committed to the code base, so it's not really 'nightly' as much as it is 'current'.
## Minimum System Requirements
* **Operating System:** Windows (~~32 or~~ 64-bit), Linux (~~32 or~~ 64-bit) or Mac/Linux (64-bit, 10.14 or higher)
* **Operating System:** Windows (~~32 or~~ 64-bit), Linux (~~32 or~~ 64-bit) or Mac (64-bit, 12.x or higher)
* **CPU:** 4-core
* **RAM:** 8GB or more (preferred). Depending on usage, 4GB may be sufficient.

View File

@@ -1,40 +0,0 @@
Auto-generating Java code from SDRPlay API using jextract.
Note: the api headers in the 3.11 folder are equivalent to the 3.08 headers. The only
difference from the 3.07 headers is the addition of the boolean 'valid' flag to DeviceT structure.
1. Download JDK19 compatible build of jextract JDK: https://jdk.java.net/jextract/
2. If generating on windows platform, modify the sdrplay_api.h to remove the windows
pieces of the file. Specifically:
2a. Comment out these lines but leave the *HANDLE typedef:
//#if defined(_M_X64) || defined(_M_IX86) || defined(_M_ARM64)
//#include "windows.h"
//#elif defined (__GNUC__)
typedef void *HANDLE;
//#endif
//#ifndef _SDRPLAY_DLL_QUALIFIER
//#if !defined(STATIC_LIB) && (defined(_M_X64) || defined(_M_IX86) || defined(_M_ARM64))
//#define _SDRPLAY_DLL_QUALIFIER __declspec(dllimport)
//#elif defined(STATIC_LIB) || defined(__GNUC__)
//#define _SDRPLAY_DLL_QUALIFIER
//#endif
//#endif // _SDRPLAY_DLL_QUALIFIER
2b. Remove the '_SDRPLAY_DLL_QUALIFIER' from in front of the method declarations
2c. Change the 3.11 version string to 3.08 either in the header file, or in the generated java class
3. Open command prompt to the root source code where the generated files should be placed:
..\SDRtrunk\sdrplay-api\src\main\java
4. Generate the code:
Windows:
c:\Users\Denny\Downloads\jdk-19-ea2-panama\bin\jextract "C:\Users\Denny\git\sdrtrunk\sdrplay-api\api\v3_11\sdrplay_api.h" -l libsdrplay_api --source -t io.github.dsheirer.source.tuner.sdrplay.api.v3_14
Linux:
~/Downloads/jextract-20/bin/jextract ~/IdeaProjects/sdrtrunk/artifacts/sdrplay-api-headers/v3_14/sdrplay_api.h -l libsdrplay_api --source -t io.github.dsheirer.source.tuner.sdrplay.api.v3_14
5. Compare the 3.07 header files to the newest version header files. Delete any auto-generated files that have not
changed and then update the factory class to construct variation classes where there are differences.

View File

@@ -1,230 +0,0 @@
#ifndef SDRPLAY_API_H
#define SDRPLAY_API_H
#include "sdrplay_api_dev.h"
#include "sdrplay_api_rx_channel.h"
#include "sdrplay_api_callback.h"
//#if defined(_M_X64) || defined(_M_IX86) || defined(_M_ARM64)
//#include "windows.h"
//#elif defined (__GNUC__)
typedef void *HANDLE;
//#endif
//#ifndef _SDRPLAY_DLL_QUALIFIER
//#if !defined(STATIC_LIB) && (defined(_M_X64) || defined(_M_IX86) || defined(_M_ARM64))
//#define _SDRPLAY_DLL_QUALIFIER __declspec(dllimport)
//#elif defined(STATIC_LIB) || defined(__GNUC__)
//#define _SDRPLAY_DLL_QUALIFIER
//#endif
//#endif // _SDRPLAY_DLL_QUALIFIER
// Application code should check that it is compiled against the same API version
// sdrplay_api_ApiVersion() returns the API version
#define SDRPLAY_API_VERSION (float)(3.11)
// API Constants
#define SDRPLAY_MAX_DEVICES (16)
#define SDRPLAY_MAX_TUNERS_PER_DEVICE (2)
#define SDRPLAY_MAX_SER_NO_LEN (64)
#define SDRPLAY_MAX_ROOT_NM_LEN (32)
#define SDRPLAY_RSP1_ID (1)
#define SDRPLAY_RSP1A_ID (255)
#define SDRPLAY_RSP2_ID (2)
#define SDRPLAY_RSPduo_ID (3)
#define SDRPLAY_RSPdx_ID (4)
// Enum types
typedef enum
{
sdrplay_api_Success = 0,
sdrplay_api_Fail = 1,
sdrplay_api_InvalidParam = 2,
sdrplay_api_OutOfRange = 3,
sdrplay_api_GainUpdateError = 4,
sdrplay_api_RfUpdateError = 5,
sdrplay_api_FsUpdateError = 6,
sdrplay_api_HwError = 7,
sdrplay_api_AliasingError = 8,
sdrplay_api_AlreadyInitialised = 9,
sdrplay_api_NotInitialised = 10,
sdrplay_api_NotEnabled = 11,
sdrplay_api_HwVerError = 12,
sdrplay_api_OutOfMemError = 13,
sdrplay_api_ServiceNotResponding = 14,
sdrplay_api_StartPending = 15,
sdrplay_api_StopPending = 16,
sdrplay_api_InvalidMode = 17,
sdrplay_api_FailedVerification1 = 18,
sdrplay_api_FailedVerification2 = 19,
sdrplay_api_FailedVerification3 = 20,
sdrplay_api_FailedVerification4 = 21,
sdrplay_api_FailedVerification5 = 22,
sdrplay_api_FailedVerification6 = 23,
sdrplay_api_InvalidServiceVersion = 24
} sdrplay_api_ErrT;
typedef enum
{
sdrplay_api_Update_None = 0x00000000,
// Reasons for master only mode
sdrplay_api_Update_Dev_Fs = 0x00000001,
sdrplay_api_Update_Dev_Ppm = 0x00000002,
sdrplay_api_Update_Dev_SyncUpdate = 0x00000004,
sdrplay_api_Update_Dev_ResetFlags = 0x00000008,
sdrplay_api_Update_Rsp1a_BiasTControl = 0x00000010,
sdrplay_api_Update_Rsp1a_RfNotchControl = 0x00000020,
sdrplay_api_Update_Rsp1a_RfDabNotchControl = 0x00000040,
sdrplay_api_Update_Rsp2_BiasTControl = 0x00000080,
sdrplay_api_Update_Rsp2_AmPortSelect = 0x00000100,
sdrplay_api_Update_Rsp2_AntennaControl = 0x00000200,
sdrplay_api_Update_Rsp2_RfNotchControl = 0x00000400,
sdrplay_api_Update_Rsp2_ExtRefControl = 0x00000800,
sdrplay_api_Update_RspDuo_ExtRefControl = 0x00001000,
sdrplay_api_Update_Master_Spare_1 = 0x00002000,
sdrplay_api_Update_Master_Spare_2 = 0x00004000,
// Reasons for master and slave mode
// Note: sdrplay_api_Update_Tuner_Gr MUST be the first value defined in this section!
sdrplay_api_Update_Tuner_Gr = 0x00008000,
sdrplay_api_Update_Tuner_GrLimits = 0x00010000,
sdrplay_api_Update_Tuner_Frf = 0x00020000,
sdrplay_api_Update_Tuner_BwType = 0x00040000,
sdrplay_api_Update_Tuner_IfType = 0x00080000,
sdrplay_api_Update_Tuner_DcOffset = 0x00100000,
sdrplay_api_Update_Tuner_LoMode = 0x00200000,
sdrplay_api_Update_Ctrl_DCoffsetIQimbalance = 0x00400000,
sdrplay_api_Update_Ctrl_Decimation = 0x00800000,
sdrplay_api_Update_Ctrl_Agc = 0x01000000,
sdrplay_api_Update_Ctrl_AdsbMode = 0x02000000,
sdrplay_api_Update_Ctrl_OverloadMsgAck = 0x04000000,
sdrplay_api_Update_RspDuo_BiasTControl = 0x08000000,
sdrplay_api_Update_RspDuo_AmPortSelect = 0x10000000,
sdrplay_api_Update_RspDuo_Tuner1AmNotchControl = 0x20000000,
sdrplay_api_Update_RspDuo_RfNotchControl = 0x40000000,
sdrplay_api_Update_RspDuo_RfDabNotchControl = 0x80000000,
} sdrplay_api_ReasonForUpdateT;
typedef enum
{
sdrplay_api_Update_Ext1_None = 0x00000000,
// Reasons for master only mode
sdrplay_api_Update_RspDx_HdrEnable = 0x00000001,
sdrplay_api_Update_RspDx_BiasTControl = 0x00000002,
sdrplay_api_Update_RspDx_AntennaControl = 0x00000004,
sdrplay_api_Update_RspDx_RfNotchControl = 0x00000008,
sdrplay_api_Update_RspDx_RfDabNotchControl = 0x00000010,
sdrplay_api_Update_RspDx_HdrBw = 0x00000020,
// Reasons for master and slave mode
} sdrplay_api_ReasonForUpdateExtension1T;
typedef enum
{
sdrplay_api_DbgLvl_Disable = 0,
sdrplay_api_DbgLvl_Verbose = 1,
sdrplay_api_DbgLvl_Warning = 2,
sdrplay_api_DbgLvl_Error = 3,
sdrplay_api_DbgLvl_Message = 4,
} sdrplay_api_DbgLvl_t;
// Device structure
typedef struct
{
char SerNo[SDRPLAY_MAX_SER_NO_LEN];
unsigned char hwVer;
sdrplay_api_TunerSelectT tuner;
sdrplay_api_RspDuoModeT rspDuoMode;
unsigned char valid;
double rspDuoSampleFreq;
HANDLE dev;
} sdrplay_api_DeviceT;
// Device parameter structure
typedef struct
{
sdrplay_api_DevParamsT *devParams;
sdrplay_api_RxChannelParamsT *rxChannelA;
sdrplay_api_RxChannelParamsT *rxChannelB;
} sdrplay_api_DeviceParamsT;
// Extended error message structure
typedef struct
{
char file[256];
char function[256];
int line;
char message[1024];
} sdrplay_api_ErrorInfoT;
// Comman API function types
typedef sdrplay_api_ErrT (*sdrplay_api_Open_t)(void);
typedef sdrplay_api_ErrT (*sdrplay_api_Close_t)(void);
typedef sdrplay_api_ErrT (*sdrplay_api_ApiVersion_t)(float *apiVer);
typedef sdrplay_api_ErrT (*sdrplay_api_LockDeviceApi_t)(void);
typedef sdrplay_api_ErrT (*sdrplay_api_UnlockDeviceApi_t)(void);
typedef sdrplay_api_ErrT (*sdrplay_api_GetDevices_t)(sdrplay_api_DeviceT *devices, unsigned int *numDevs, unsigned int maxDevs);
typedef sdrplay_api_ErrT (*sdrplay_api_SelectDevice_t)(sdrplay_api_DeviceT *device);
typedef sdrplay_api_ErrT (*sdrplay_api_ReleaseDevice_t)(sdrplay_api_DeviceT *device);
typedef const char* (*sdrplay_api_GetErrorString_t)(sdrplay_api_ErrT err);
typedef sdrplay_api_ErrorInfoT* (*sdrplay_api_GetLastError_t)(sdrplay_api_DeviceT *device);
typedef sdrplay_api_ErrT (*sdrplay_api_DisableHeartbeat_t)(void);
// Device API function types
typedef sdrplay_api_ErrT (*sdrplay_api_DebugEnable_t)(HANDLE dev, sdrplay_api_DbgLvl_t dbgLvl);
typedef sdrplay_api_ErrT (*sdrplay_api_GetDeviceParams_t)(HANDLE dev, sdrplay_api_DeviceParamsT **deviceParams);
typedef sdrplay_api_ErrT (*sdrplay_api_Init_t)(HANDLE dev, sdrplay_api_CallbackFnsT *callbackFns, void *cbContext);
typedef sdrplay_api_ErrT (*sdrplay_api_Uninit_t)(HANDLE dev);
typedef sdrplay_api_ErrT (*sdrplay_api_Update_t)(HANDLE dev, sdrplay_api_TunerSelectT tuner, sdrplay_api_ReasonForUpdateT reasonForUpdate, sdrplay_api_ReasonForUpdateExtension1T reasonForUpdateExt1);
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoActiveTuner_t)(HANDLE dev, sdrplay_api_TunerSelectT *tuner, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoDualTunerModeSampleRate_t)(double *currentSampleRate, double newSampleRate);
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoMode_t)(sdrplay_api_DeviceT *currDevice, sdrplay_api_DeviceParamsT **deviceParams,
sdrplay_api_RspDuoModeT rspDuoMode, double sampleRate, sdrplay_api_TunerSelectT tuner,
sdrplay_api_Bw_MHzT bwType, sdrplay_api_If_kHzT ifType, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
// API function definitions
#ifdef __cplusplus
extern "C"
{
#endif
// Comman API function definitions
sdrplay_api_ErrT sdrplay_api_Open(void);
sdrplay_api_ErrT sdrplay_api_Close(void);
sdrplay_api_ErrT sdrplay_api_ApiVersion(float *apiVer);
sdrplay_api_ErrT sdrplay_api_LockDeviceApi(void);
sdrplay_api_ErrT sdrplay_api_UnlockDeviceApi(void);
sdrplay_api_ErrT sdrplay_api_GetDevices(sdrplay_api_DeviceT *devices, unsigned int *numDevs, unsigned int maxDevs);
sdrplay_api_ErrT sdrplay_api_SelectDevice(sdrplay_api_DeviceT *device);
sdrplay_api_ErrT sdrplay_api_ReleaseDevice(sdrplay_api_DeviceT *device);
const char* sdrplay_api_GetErrorString(sdrplay_api_ErrT err);
sdrplay_api_ErrorInfoT* sdrplay_api_GetLastError(sdrplay_api_DeviceT *device);
sdrplay_api_ErrT sdrplay_api_DisableHeartbeat(void); // Must be called before sdrplay_api_SelectDevice()
// Device API function definitions
sdrplay_api_ErrT sdrplay_api_DebugEnable(HANDLE dev, sdrplay_api_DbgLvl_t enable);
sdrplay_api_ErrT sdrplay_api_GetDeviceParams(HANDLE dev, sdrplay_api_DeviceParamsT **deviceParams);
sdrplay_api_ErrT sdrplay_api_Init(HANDLE dev, sdrplay_api_CallbackFnsT *callbackFns, void *cbContext);
sdrplay_api_ErrT sdrplay_api_Uninit(HANDLE dev);
sdrplay_api_ErrT sdrplay_api_Update(HANDLE dev, sdrplay_api_TunerSelectT tuner, sdrplay_api_ReasonForUpdateT reasonForUpdate, sdrplay_api_ReasonForUpdateExtension1T reasonForUpdateExt1);
sdrplay_api_ErrT sdrplay_api_SwapRspDuoActiveTuner(HANDLE dev, sdrplay_api_TunerSelectT *currentTuner, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
sdrplay_api_ErrT sdrplay_api_SwapRspDuoDualTunerModeSampleRate(HANDLE dev, double *currentSampleRate, double newSampleRate);
sdrplay_api_ErrT sdrplay_api_SwapRspDuoMode(sdrplay_api_DeviceT *currDevice, sdrplay_api_DeviceParamsT **deviceParams,
sdrplay_api_RspDuoModeT rspDuoMode, double sampleRate, sdrplay_api_TunerSelectT tuner,
sdrplay_api_Bw_MHzT bwType, sdrplay_api_If_kHzT ifType, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
#ifdef __cplusplus
}
#endif
#endif //SDRPLAY_API_H

View File

@@ -1,40 +0,0 @@
#ifndef SDRPLAY_API_RSPduo_H
#define SDRPLAY_API_RSPduo_H
#define RSPDUO_NUM_LNA_STATES 10
#define RSPDUO_NUM_LNA_STATES_AMPORT 5
#define RSPDUO_NUM_LNA_STATES_AM 7
#define RSPDUO_NUM_LNA_STATES_LBAND 9
// RSPduo parameter enums
typedef enum
{
sdrplay_api_RspDuoMode_Unknown = 0,
sdrplay_api_RspDuoMode_Single_Tuner = 1,
sdrplay_api_RspDuoMode_Dual_Tuner = 2,
sdrplay_api_RspDuoMode_Master = 4,
sdrplay_api_RspDuoMode_Slave = 8,
} sdrplay_api_RspDuoModeT;
typedef enum
{
sdrplay_api_RspDuo_AMPORT_1 = 1,
sdrplay_api_RspDuo_AMPORT_2 = 0,
} sdrplay_api_RspDuo_AmPortSelectT;
// RSPduo parameter structs
typedef struct
{
int extRefOutputEn; // default: 0
} sdrplay_api_RspDuoParamsT;
typedef struct
{
unsigned char biasTEnable; // default: 0
sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel; // default: sdrplay_api_RspDuo_AMPORT_2
unsigned char tuner1AmNotchEnable; // default: 0
unsigned char rfNotchEnable; // default: 0
unsigned char rfDabNotchEnable; // default: 0
} sdrplay_api_RspDuoTunerParamsT;
#endif //SDRPLAY_API_RSPduo_H

View File

@@ -1,79 +0,0 @@
#ifndef SDRPLAY_API_CALLBACK_H
#define SDRPLAY_API_CALLBACK_H
// Event callback enums
typedef enum
{
sdrplay_api_Overload_Detected = 0,
sdrplay_api_Overload_Corrected = 1,
} sdrplay_api_PowerOverloadCbEventIdT;
typedef enum
{
sdrplay_api_MasterInitialised = 0,
sdrplay_api_SlaveAttached = 1,
sdrplay_api_SlaveDetached = 2,
sdrplay_api_SlaveInitialised = 3,
sdrplay_api_SlaveUninitialised = 4,
sdrplay_api_MasterDllDisappeared = 5,
sdrplay_api_SlaveDllDisappeared = 6,
} sdrplay_api_RspDuoModeCbEventIdT;
typedef enum
{
sdrplay_api_GainChange = 0,
sdrplay_api_PowerOverloadChange = 1,
sdrplay_api_DeviceRemoved = 2,
sdrplay_api_RspDuoModeChange = 3,
sdrplay_api_DeviceFailure = 4,
} sdrplay_api_EventT;
// Event callback parameter structs
typedef struct
{
unsigned int gRdB;
unsigned int lnaGRdB;
double currGain;
} sdrplay_api_GainCbParamT;
typedef struct
{
sdrplay_api_PowerOverloadCbEventIdT powerOverloadChangeType;
} sdrplay_api_PowerOverloadCbParamT;
typedef struct
{
sdrplay_api_RspDuoModeCbEventIdT modeChangeType;
} sdrplay_api_RspDuoModeCbParamT;
// Event parameters overlay
typedef union
{
sdrplay_api_GainCbParamT gainParams;
sdrplay_api_PowerOverloadCbParamT powerOverloadParams;
sdrplay_api_RspDuoModeCbParamT rspDuoModeParams;
} sdrplay_api_EventParamsT;
// Stream callback parameter structs
typedef struct
{
unsigned int firstSampleNum;
int grChanged;
int rfChanged;
int fsChanged;
unsigned int numSamples;
} sdrplay_api_StreamCbParamsT;
// Callback function prototypes
typedef void (*sdrplay_api_StreamCallback_t)(short *xi, short *xq, sdrplay_api_StreamCbParamsT *params, unsigned int numSamples, unsigned int reset, void *cbContext);
typedef void (*sdrplay_api_EventCallback_t)(sdrplay_api_EventT eventId, sdrplay_api_TunerSelectT tuner, sdrplay_api_EventParamsT *params, void *cbContext);
// Callback function struct
typedef struct
{
sdrplay_api_StreamCallback_t StreamACbFn;
sdrplay_api_StreamCallback_t StreamBCbFn;
sdrplay_api_EventCallback_t EventCbFn;
} sdrplay_api_CallbackFnsT;
#endif //SDRPLAY_API_CALLBACK_H

View File

@@ -1,55 +0,0 @@
#ifndef SDRPLAY_API_CONTROL_H
#define SDRPLAY_API_CONTROL_H
// Control parameter enums
typedef enum
{
sdrplay_api_AGC_DISABLE = 0,
sdrplay_api_AGC_100HZ = 1,
sdrplay_api_AGC_50HZ = 2,
sdrplay_api_AGC_5HZ = 3,
sdrplay_api_AGC_CTRL_EN = 4
} sdrplay_api_AgcControlT;
typedef enum
{
sdrplay_api_ADSB_DECIMATION = 0,
sdrplay_api_ADSB_NO_DECIMATION_LOWPASS = 1,
sdrplay_api_ADSB_NO_DECIMATION_BANDPASS_2MHZ = 2,
sdrplay_api_ADSB_NO_DECIMATION_BANDPASS_3MHZ = 3
} sdrplay_api_AdsbModeT;
// Control parameter structs
typedef struct
{
unsigned char DCenable; // default: 1
unsigned char IQenable; // default: 1
} sdrplay_api_DcOffsetT;
typedef struct
{
unsigned char enable; // default: 0
unsigned char decimationFactor; // default: 1
unsigned char wideBandSignal; // default: 0
} sdrplay_api_DecimationT;
typedef struct
{
sdrplay_api_AgcControlT enable; // default: sdrplay_api_AGC_50HZ
int setPoint_dBfs; // default: -60
unsigned short attack_ms; // default: 0
unsigned short decay_ms; // default: 0
unsigned short decay_delay_ms; // default: 0
unsigned short decay_threshold_dB; // default: 0
int syncUpdate; // default: 0
} sdrplay_api_AgcT;
typedef struct
{
sdrplay_api_DcOffsetT dcOffset;
sdrplay_api_DecimationT decimation;
sdrplay_api_AgcT agc;
sdrplay_api_AdsbModeT adsbMode; //default: sdrplay_api_ADSB_DECIMATION
} sdrplay_api_ControlParamsT;
#endif //SDRPLAY_API_CONTROL_H

View File

@@ -1,51 +0,0 @@
#ifndef SDRPLAY_API_DEV_H
#define SDRPLAY_API_DEV_H
#include "sdrplay_api_rsp1a.h"
#include "sdrplay_api_rsp2.h"
#include "sdrplay_api_rspDuo.h"
#include "sdrplay_api_rspDx.h"
// Dev parameter enums
typedef enum
{
sdrplay_api_ISOCH = 0,
sdrplay_api_BULK = 1
} sdrplay_api_TransferModeT;
// Dev parameter structs
typedef struct
{
double fsHz; // default: 2000000.0
unsigned char syncUpdate; // default: 0
unsigned char reCal; // default: 0
} sdrplay_api_FsFreqT;
typedef struct
{
unsigned int sampleNum; // default: 0
unsigned int period; // default: 0
} sdrplay_api_SyncUpdateT;
typedef struct
{
unsigned char resetGainUpdate; // default: 0
unsigned char resetRfUpdate; // default: 0
unsigned char resetFsUpdate; // default: 0
} sdrplay_api_ResetFlagsT;
typedef struct
{
double ppm; // default: 0.0
sdrplay_api_FsFreqT fsFreq;
sdrplay_api_SyncUpdateT syncUpdate;
sdrplay_api_ResetFlagsT resetFlags;
sdrplay_api_TransferModeT mode; // default: sdrplay_api_ISOCH
unsigned int samplesPerPkt; // default: 0 (output param)
sdrplay_api_Rsp1aParamsT rsp1aParams;
sdrplay_api_Rsp2ParamsT rsp2Params;
sdrplay_api_RspDuoParamsT rspDuoParams;
sdrplay_api_RspDxParamsT rspDxParams;
} sdrplay_api_DevParamsT;
#endif //SDRPLAY_API_DEV_H

View File

@@ -1,22 +0,0 @@
#ifndef SDRPLAY_API_RSP1A_H
#define SDRPLAY_API_RSP1A_H
#define RSPIA_NUM_LNA_STATES 10
#define RSPIA_NUM_LNA_STATES_AM 7
#define RSPIA_NUM_LNA_STATES_LBAND 9
// RSP1A parameter enums
// RSP1A parameter structs
typedef struct
{
unsigned char rfNotchEnable; // default: 0
unsigned char rfDabNotchEnable; // default: 0
} sdrplay_api_Rsp1aParamsT;
typedef struct
{
unsigned char biasTEnable; // default: 0
} sdrplay_api_Rsp1aTunerParamsT;
#endif //SDRPLAY_API_RSP1A_H

View File

@@ -1,35 +0,0 @@
#ifndef SDRPLAY_API_RSP2_H
#define SDRPLAY_API_RSP2_H
#define RSPII_NUM_LNA_STATES 9
#define RSPII_NUM_LNA_STATES_AMPORT 5
#define RSPII_NUM_LNA_STATES_420MHZ 6
// RSP2 parameter enums
typedef enum
{
sdrplay_api_Rsp2_ANTENNA_A = 5,
sdrplay_api_Rsp2_ANTENNA_B = 6,
} sdrplay_api_Rsp2_AntennaSelectT;
typedef enum
{
sdrplay_api_Rsp2_AMPORT_1 = 1,
sdrplay_api_Rsp2_AMPORT_2 = 0,
} sdrplay_api_Rsp2_AmPortSelectT;
// RSP2 parameter structs
typedef struct
{
unsigned char extRefOutputEn; // default: 0
} sdrplay_api_Rsp2ParamsT;
typedef struct
{
unsigned char biasTEnable; // default: 0
sdrplay_api_Rsp2_AmPortSelectT amPortSel; // default: sdrplay_api_Rsp2_AMPORT_2
sdrplay_api_Rsp2_AntennaSelectT antennaSel; // default: sdrplay_api_Rsp2_ANTENNA_A
unsigned char rfNotchEnable; // default: 0
} sdrplay_api_Rsp2TunerParamsT;
#endif //SDRPLAY_API_RSP2_H

View File

@@ -1,46 +0,0 @@
#ifndef SDRPLAY_API_RSPDX_H
#define SDRPLAY_API_RSPDX_H
#include "sdrplay_api_tuner.h"
#define RSPDX_NUM_LNA_STATES 28 // Number of LNA states in all bands (except where defined differently below)
#define RSPDX_NUM_LNA_STATES_AMPORT2_0_12 19 // Number of LNA states when using AM Port 2 between 0 and 12MHz
#define RSPDX_NUM_LNA_STATES_AMPORT2_12_50 20 // Number of LNA states when using AM Port 2 between 12 and 50MHz
#define RSPDX_NUM_LNA_STATES_AMPORT2_50_60 25 // Number of LNA states when using AM Port 2 between 50 and 60MHz
#define RSPDX_NUM_LNA_STATES_VHF_BAND3 27 // Number of LNA states in VHF and Band3
#define RSPDX_NUM_LNA_STATES_420MHZ 21 // Number of LNA states in 420MHz band
#define RSPDX_NUM_LNA_STATES_LBAND 19 // Number of LNA states in L-band
#define RSPDX_NUM_LNA_STATES_DX 22 // Number of LNA states in DX path
// RSPdx parameter enums
typedef enum
{
sdrplay_api_RspDx_ANTENNA_A = 0,
sdrplay_api_RspDx_ANTENNA_B = 1,
sdrplay_api_RspDx_ANTENNA_C = 2,
} sdrplay_api_RspDx_AntennaSelectT;
typedef enum
{
sdrplay_api_RspDx_HDRMODE_BW_0_200 = 0,
sdrplay_api_RspDx_HDRMODE_BW_0_500 = 1,
sdrplay_api_RspDx_HDRMODE_BW_1_200 = 2,
sdrplay_api_RspDx_HDRMODE_BW_1_700 = 3,
} sdrplay_api_RspDx_HdrModeBwT;
// RSPdx parameter structs
typedef struct
{
unsigned char hdrEnable; // default: 0
unsigned char biasTEnable; // default: 0
sdrplay_api_RspDx_AntennaSelectT antennaSel; // default: sdrplay_api_RspDx_ANTENNA_A
unsigned char rfNotchEnable; // default: 0
unsigned char rfDabNotchEnable; // default: 0
} sdrplay_api_RspDxParamsT;
typedef struct
{
sdrplay_api_RspDx_HdrModeBwT hdrBw; // default: sdrplay_api_RspDx_HDRMODE_BW_1_700
} sdrplay_api_RspDxTunerParamsT;
#endif //SDRPLAY_API_RSPDX_H

View File

@@ -1,21 +0,0 @@
#ifndef SDRPLAY_API_RX_CHANNEL_H
#define SDRPLAY_API_RX_CHANNEL_H
#include "sdrplay_api_tuner.h"
#include "sdrplay_api_control.h"
#include "sdrplay_api_rsp1a.h"
#include "sdrplay_api_rsp2.h"
#include "sdrplay_api_rspDuo.h"
#include "sdrplay_api_rspDx.h"
typedef struct
{
sdrplay_api_TunerParamsT tunerParams;
sdrplay_api_ControlParamsT ctrlParams;
sdrplay_api_Rsp1aTunerParamsT rsp1aTunerParams;
sdrplay_api_Rsp2TunerParamsT rsp2TunerParams;
sdrplay_api_RspDuoTunerParamsT rspDuoTunerParams;
sdrplay_api_RspDxTunerParamsT rspDxTunerParams;
} sdrplay_api_RxChannelParamsT;
#endif //SDRPLAY_API_RX_CHANNEL_H

View File

@@ -1,93 +0,0 @@
#ifndef SDRPLAY_API_TUNER_H
#define SDRPLAY_API_TUNER_H
#define MAX_BB_GR (59)
// Tuner parameter enums
typedef enum
{
sdrplay_api_BW_Undefined = 0,
sdrplay_api_BW_0_200 = 200,
sdrplay_api_BW_0_300 = 300,
sdrplay_api_BW_0_600 = 600,
sdrplay_api_BW_1_536 = 1536,
sdrplay_api_BW_5_000 = 5000,
sdrplay_api_BW_6_000 = 6000,
sdrplay_api_BW_7_000 = 7000,
sdrplay_api_BW_8_000 = 8000
} sdrplay_api_Bw_MHzT;
typedef enum
{
sdrplay_api_IF_Undefined = -1,
sdrplay_api_IF_Zero = 0,
sdrplay_api_IF_0_450 = 450,
sdrplay_api_IF_1_620 = 1620,
sdrplay_api_IF_2_048 = 2048
} sdrplay_api_If_kHzT;
typedef enum
{
sdrplay_api_LO_Undefined = 0,
sdrplay_api_LO_Auto = 1,
sdrplay_api_LO_120MHz = 2,
sdrplay_api_LO_144MHz = 3,
sdrplay_api_LO_168MHz = 4
} sdrplay_api_LoModeT;
typedef enum
{
sdrplay_api_EXTENDED_MIN_GR = 0,
sdrplay_api_NORMAL_MIN_GR = 20
} sdrplay_api_MinGainReductionT;
typedef enum
{
sdrplay_api_Tuner_Neither = 0,
sdrplay_api_Tuner_A = 1,
sdrplay_api_Tuner_B = 2,
sdrplay_api_Tuner_Both = 3,
} sdrplay_api_TunerSelectT;
// Tuner parameter structs
typedef struct
{
float curr;
float max;
float min;
} sdrplay_api_GainValuesT;
typedef struct
{
int gRdB; // default: 50
unsigned char LNAstate; // default: 0
unsigned char syncUpdate; // default: 0
sdrplay_api_MinGainReductionT minGr; // default: sdrplay_api_NORMAL_MIN_GR
sdrplay_api_GainValuesT gainVals; // output parameter
} sdrplay_api_GainT;
typedef struct
{
double rfHz; // default: 200000000.0
unsigned char syncUpdate; // default: 0
} sdrplay_api_RfFreqT;
typedef struct
{
unsigned char dcCal; // default: 3 (Periodic mode)
unsigned char speedUp; // default: 0 (No speedup)
int trackTime; // default: 1 (=> time in uSec = (72 * 3 * trackTime) / 24e6 = 9uSec)
int refreshRateTime; // default: 2048 (=> time in uSec = (72 * 3 * refreshRateTime) / 24e6 = 18432uSec)
} sdrplay_api_DcOffsetTunerT;
typedef struct
{
sdrplay_api_Bw_MHzT bwType; // default: sdrplay_api_BW_0_200
sdrplay_api_If_kHzT ifType; // default: sdrplay_api_IF_Zero
sdrplay_api_LoModeT loMode; // default: sdrplay_api_LO_Auto
sdrplay_api_GainT gain;
sdrplay_api_RfFreqT rfFreq;
sdrplay_api_DcOffsetTunerT dcOffsetTuner;
} sdrplay_api_TunerParamsT;
#endif //SDRPLAY_API_TUNER_H

View File

@@ -21,7 +21,7 @@ typedef void *HANDLE;
// Application code should check that it is compiled against the same API version
// sdrplay_api_ApiVersion() returns the API version
#define SDRPLAY_API_VERSION (float)(3.14)
#define SDRPLAY_API_VERSION (float)(3.15)
// API Constants
#define SDRPLAY_MAX_DEVICES (16)
@@ -36,6 +36,7 @@ typedef void *HANDLE;
#define SDRPLAY_RSPduo_ID (3)
#define SDRPLAY_RSPdx_ID (4)
#define SDRPLAY_RSP1B_ID (6)
#define SDRPLAY_RSPdxR2_ID (7)
// Enum types
typedef enum

View File

@@ -1,8 +1,6 @@
import java.text.SimpleDateFormat
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,12 +17,16 @@ import java.text.SimpleDateFormat
* ****************************************************************************
*/
import java.text.SimpleDateFormat
/**
* Instructions for building/compiling the sdrtrunk application.
*
* Prerequisites:
* Install and configure an OpenJDK version 19+ that includes the JavaFX modules (e.g. Bellsoft Liberica JDK)
* - Optional: install and configure Gradle 7.6+
* Install and configure an OpenJDK version 22+ that includes the JavaFX modules (e.g. Bellsoft Liberica JDK)
* - Optional: install and configure Gradle 8.10+
*
* Scenario 1: run the application via gradle command line from the source code root directory:
* command: ./gradlew run
@@ -45,21 +47,21 @@ plugins {
id 'application'
id 'java'
id 'idea'
id 'org.beryx.runtime' version '1.12.7'
id 'org.beryx.runtime' version '2.0.1'
}
repositories {
mavenCentral()
maven { url "https://jitpack.io" }
maven { url = "https://jitpack.io" }
}
//Version is defined in gradle.properties so that we can CLI override it for nightly builds.
version = project.findProperty('projectVersion')
//Java 20 is required for this version of the Project Panama preview/incubator feature
//Java 24 is required for this version of the Project Panama preview/incubator feature
java {
toolchain {
languageVersion = JavaLanguageVersion.of(20)
languageVersion = JavaLanguageVersion.of(25)
}
}
@@ -75,12 +77,11 @@ test {
dependencies {
// 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'
testImplementation(platform("org.junit:junit-bom:6.0.1"))
testImplementation 'org.junit.jupiter:junit-jupiter-api:6.0.1'
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:6.0.1")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:6.0.1")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
//Jitpack imports
implementation 'com.github.dnault:libresample4j:master'
@@ -88,48 +89,48 @@ dependencies {
implementation 'com.github.nwaldispuehl:java-lame:v3.98.4'
//mavenCentral/jcenter imports
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 'ch.qos.logback:logback-classic:1.5.21'
implementation 'ch.qos.logback:logback-core:1.5.21'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.20.1'
implementation 'com.fazecast:jSerialComm:2.11.4'
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.10'
implementation 'com.google.guava:guava:31.1-jre'
implementation 'com.google.code.gson:gson:2.13.2'
implementation 'com.google.guava:guava:33.5.0-jre'
implementation 'com.jidesoft:jide-oss:3.6.18'
implementation 'com.miglayout:miglayout-swing:11.0'
implementation 'com.miglayout:miglayout-swing:11.4.2'
implementation 'com.mpatric:mp3agic:0.9.1'
implementation 'commons-io:commons-io:2.11.0'
implementation 'commons-io:commons-io:2.21.0'
implementation 'eu.hansolo:charts:1.0.5'
implementation 'io.github.dsheirer:radio-reference-api:15.1.9'
implementation 'io.github.dsheirer:radio-reference-api:18.0.0'
implementation 'io.github.dsheirer:usb4java-native-libraries:1.3.3' //OSX & Windows aarch64 native libs
implementation 'io.github.dsheirer:usb4java:1.3.5' //Fork with Loader support for OSX Multi-Version
implementation 'javax.usb:usb-api:1.0.2'
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-compress:1.28.0'
implementation 'org.apache.commons:commons-lang3:3.19.0'
implementation 'org.apache.commons:commons-math3:3.6.1'
implementation 'org.apache.commons:commons-csv:1.9.0'
implementation 'org.apache.mina:mina-core:2.2.1'
implementation 'org.apache.mina:mina-http:2.2.1'
implementation 'org.controlsfx:controlsfx:11.1.2'
implementation 'org.apache.commons:commons-csv:1.14.1'
implementation 'org.apache.mina:mina-core:2.2.4'
implementation 'org.apache.mina:mina-http:2.2.4'
implementation 'org.controlsfx:controlsfx:11.2.2'
implementation 'org.rauschig:jarchivelib:1.2.0'
implementation 'org.slf4j:slf4j-api:2.0.5'
implementation 'org.slf4j:slf4j-api:2.0.17'
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'
}
def os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem
/**
* This is needed for the JDK19 panama and vector API ... until it moves out of incubation or preview
* This is needed for the JDK23 vector API ... until it moves out of incubation or preview
*/
tasks.withType(JavaCompile) {
options.compilerArgs.add("--enable-preview") //Panama Foreign Function/Memory is preview in JDK 19
options.compilerArgs.add("--add-modules=jdk.incubator.vector") //Needed while Panama Vector API remains in incubator
options.compilerArgs.add("--enable-preview") //Project Panama Vector API is still in incubator
options.compilerArgs.add("--add-modules=jdk.incubator.vector") //Project Panama Vector API is still in incubator
}
/**
@@ -138,10 +139,13 @@ tasks.withType(JavaCompile) {
def jvmArgsWindows =
['--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED', //Needed for controls-fx.jar
'--add-exports=java.desktop/com.sun.java.swing.plaf.windows=ALL-UNNAMED', //windows users for JIDE
'--add-modules=jdk.incubator.vector', //Needed while Project Panama API is still in incubator
'--enable-preview', //Project Panama Vector & Foreign Function APIs remain in incubator
'--enable-native-access=ALL-UNNAMED',
'-Djava.library.path=c:\\Program Files\\SDRplay\\API\\x64'] //Allows Project Panama Foreign Function access to system library
'--add-modules=jdk.incubator.vector', //Project Panama Vector API is still in incubator
'--enable-preview', //Project Panama Vector API is still in incubator
'--enable-native-access=ALL-UNNAMED', //Suppress unsafe warnings for FFM API Foreign Memory calls
'--enable-native-access=javafx.graphics', //JDK24 - suppress restricted java.lang.System calls
'--sun-misc-unsafe-memory-access=allow', //JDK24 - suppress sun.misc.Unsafe::allocateMemory warning
'-XX:+UseCompactObjectHeaders', //JDK25 - use compact object headers
'-Djava.library.path=c:\\Program Files\\SDRplay\\API\\x64'] //SDRPlay API library path
/**
* JVM Arguments for running the application on Linux.
@@ -150,11 +154,13 @@ def jvmArgsLinux =
['--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED', //Needed for controls-fx.jar
'--add-modules=jdk.incubator.vector', //Needed while Project Panama API is still in incubator
'--enable-preview', //Project Panama Vector & Foreign Function APIs remain in incubator
'--enable-native-access=ALL-UNNAMED',
'-Djava.library.path=/usr/local/lib'] //Allows Project Panama Foreign Function access to system library
'--sun-misc-unsafe-memory-access=allow', //JDK24 - suppress sun.misc.Unsafe::allocateMemory warning
'-XX:+UseCompactObjectHeaders', //JDK25 - use compact object headers
'--enable-native-access=javafx.graphics', //JDK24 - suppress restricted java.lang.System calls
'--enable-native-access=ALL-UNNAMED'] //SDRPlay API library path
application {
mainClassName = "io.github.dsheirer.gui.SDRTrunk"
mainClass = "io.github.dsheirer.gui.SDRTrunk"
if(os.isWindows()) {
applicationDefaultJvmArgs = jvmArgsWindows
@@ -164,6 +170,15 @@ application {
}
}
test {
if(os.isWindows()) {
jvmArgs = jvmArgsWindows
}
else {
jvmArgs = jvmArgsLinux
}
}
jar {
manifest {
attributes (
@@ -184,6 +199,11 @@ idea {
}
}
javadoc {
options.addStringOption('-add-modules', 'jdk.incubator.vector')
options.addStringOption('Xdoclint:none', '-quiet')
}
/**
* Runtime target OS and CPU image names. We append version here so that version details are at end of zip filename.
*/
@@ -192,28 +212,31 @@ 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
String targetWindowsAarch64 = 'windows-aarch64-v' + version
/**
* Optional pre-downloaded Java Development Kit (JDK) for each target OS and CPU. These locations are hard-coded to the
* author's development environment, but can be changed to your environment if you want to use this build pattern.
*/
String jdk_base = '/home/denny/java_jdks/'
String jdk_linux_aarch64 = jdk_base + 'linux-arm64/jdk-20.0.1-full'
String jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-20.0.1-full'
String jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-20.0.1-full.jdk'
String jdk_osx_aarch64 = jdk_base + 'osx-arm64/jdk-20.0.1-full.jdk'
String jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-20.0.1-full'
String jdk_linux_aarch64 = jdk_base + 'linux-arm64/jdk-25.0.1-full'
String jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-25.0.1-full'
String jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-25.0.1-full.jdk'
String jdk_osx_aarch64 = jdk_base + 'osx-arm64/jdk-25.0.1-full.jdk'
String jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-25.0.1-full'
String jdk_windows_aarch64 = jdk_base + 'windows-arm64/jdk-25.0.1-full'
/**
* Download URLs to download the JDK as part of the gradle build packaging process
*/
def jdk_download_base = "https://download.bell-sw.com/java/20.0.1+10/bellsoft-jdk20.0.1+10-"
def jdk_download_base = "https://download.bell-sw.com/java/25.0.1+11/bellsoft-jdk25.0.1+11-"
def jdk_download_suffix = "-full.tar.gz"
def jdk_download_linux_aarch64 = jdk_download_base + "linux-aarch64" + jdk_download_suffix
def jdk_download_linux_x86_64 = jdk_download_base + "linux-amd64" + jdk_download_suffix
def jdk_download_osx_x86_64 = jdk_download_base + "macos-amd64" + jdk_download_suffix
def jdk_download_osx_aarch64 = jdk_download_base + "macos-aarch64" + jdk_download_suffix
def jdk_download_windows_x86_64 = jdk_download_base + "windows-amd64-full.zip"
def jdk_download_windows_aarch64 = jdk_download_base + "windows-aarch64-full.zip"
/**
* Configures the runtime zip task with additional options/settings.
@@ -256,7 +279,12 @@ tasks.register('runtimeZipCurrent', org.beryx.runtime.RuntimeZipTask) {rt ->
def javaHome = org.gradle.internal.jvm.Jvm.current().getJavaHome().toString()
if(os.isWindows()) {
rt.extension.targetPlatform(targetWindowsX86_64, javaHome)
if(arch.equals("amd64")) {
rt.extension.targetPlatform(targetWindowsX86_64, javaHome)
}
else if(arch.equals("aarch64")) {
rt.extension.targetPlatform(targetWindowsAarch64, javaHome)
}
configure(rt, jvmArgsWindows)
}
else if(os.isLinux())
@@ -292,6 +320,16 @@ tasks.register('runtimeZipCurrent', org.beryx.runtime.RuntimeZipTask) {rt ->
* Usage: ./gradlew runtimeZipWindows (linux - using gradlew)
*/
tasks.register('runtimeZipWindows', org.beryx.runtime.RuntimeZipTask) {rt ->
//Windows aarch64
if(file(jdk_windows_aarch64).exists()) {
rt.extension.targetPlatform(targetWindowsAarch64, jdk_windows_aarch64, [])
}
else {
rt.extension.targetPlatform(targetWindowsAarch64) {
jdkHome = jdkDownload(jdk_download_windows_aarch64)
}
}
//Windows x86-64
if(file(jdk_windows_x86_64).exists()) {
rt.extension.targetPlatform(targetWindowsX86_64, jdk_windows_x86_64, [])
}
@@ -351,5 +389,5 @@ tasks.register('runtimeZipOthers', org.beryx.runtime.RuntimeZipTask) {rt ->
}
}
configure(rt, jvmArgsLinux);
configure(rt, jvmArgsLinux)
}

View File

@@ -28,4 +28,4 @@
# tag name, specifically the dashes inserted before and after the alpha/beta labels.
# See: https://github.com/DSheirer/sdrtrunk/issues/1651
projectVersion=0.6.1-beta-1
projectVersion=0.6.2-beta-1

Binary file not shown.

View File

@@ -1,6 +1,6 @@
#
# ******************************************************************************
# Copyright (C) 2014-2023 Dennis Sheirer
# Copyright (C) 2014-2025 Dennis Sheirer
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -19,7 +19,8 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

37
gradlew vendored
View File

@@ -2,7 +2,7 @@
#
# ******************************************************************************
# Copyright (C) 2014-2022 Dennis Sheirer
# Copyright (C) 2014-2025 Dennis Sheirer
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -58,7 +58,7 @@
# 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
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -86,10 +86,8 @@ 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"'
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -117,7 +115,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@@ -136,10 +134,13 @@ location of your Java installation."
fi
else
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.
if ! command -v java >/dev/null 2>&1
then
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
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
@@ -147,7 +148,7 @@ 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
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
@@ -155,7 +156,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -200,16 +201,20 @@ if "$cygwin" || "$msys" ; then
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.
# 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"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.

26
gradlew.bat vendored
View File

@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 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;
@@ -30,13 +27,22 @@ import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.alias.id.priority.Priority;
import io.github.dsheirer.alias.id.record.Record;
import io.github.dsheirer.alias.id.talkgroup.StreamAsTalkgroup;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
@@ -46,13 +52,6 @@ import javafx.util.Callback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
/**
* Alias provides an aliasing (e.g. name, color, etc) container that is linked to multiple alias identifiers and
* provides a corresponding set of actions related to the alias.
@@ -74,6 +73,7 @@ public class Alias
private StringProperty mName = new SimpleStringProperty();
private ObservableList<AliasID> mAliasIDs = FXCollections.observableArrayList();
private ObservableList<AliasAction> mAliasActions = FXCollections.observableArrayList();
private ObjectProperty<StreamAsTalkgroup> mStreamTalkgroupAlias = new SimpleObjectProperty<>();
/**
* Constructs an instance and sets the specified name.
@@ -180,6 +180,12 @@ public class Alias
return mColor;
}
@JsonIgnore
public ObjectProperty streamTalkgroupAliasProperty()
{
return mStreamTalkgroupAlias;
}
/**
* Icon name property
*/
@@ -229,6 +235,24 @@ public class Alias
mName.set(name);
}
/**
* Alias name
*/
@JacksonXmlProperty(isAttribute = true, localName = "stream_talkgroup_alias")
public StreamAsTalkgroup getStreamTalkgroupAlias()
{
return mStreamTalkgroupAlias.get();
}
/**
* Sets the stream as talkgroup value that will be used in the TO field for streamed audio calls.
* @param streamTalkgroupAlias with a talkgroup value.
*/
public void setStreamTalkgroupAlias(StreamAsTalkgroup streamTalkgroupAlias)
{
mStreamTalkgroupAlias.set(streamTalkgroupAlias);
}
/**
* Alias list name for the alias list that this alias belongs to
*/
@@ -710,6 +734,7 @@ public class Alias
{
return (Alias a) -> new Observable[] {a.recordableProperty(), a.streamableProperty(), a.colorProperty(),
a.aliasListNameProperty(), a.groupProperty(), a.iconNameProperty(), a.nameProperty(), a.aliasIds(),
a.aliasActions(), a.nonAudioIdentifierCountProperty(), a.overlapProperty()};
a.aliasActions(), a.nonAudioIdentifierCountProperty(), a.overlapProperty(), a.priorityProperty(),
a.streamTalkgroupAliasProperty()};
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -663,11 +663,13 @@ public class AliasList
public Alias getAlias(TalkgroupIdentifier identifier)
{
//Attempt to do a fully qualified identifier match only
if(identifier instanceof FullyQualifiedTalkgroupIdentifier fqti)
{
return mFullyQualifiedTalkgroupAliasMap.get(fqti.toString());
return mFullyQualifiedTalkgroupAliasMap.get(fqti.getFullyQualifiedTalkgroupAddress());
}
//Attempt to match the talkgroup value
int value = identifier.getValue();
Alias mapValue = mTalkgroupAliasMap.get(value);
@@ -676,6 +678,7 @@ public class AliasList
return mapValue;
}
//Alternatively, match the talkgroup to any talkgroup ranges
for(Map.Entry<TalkgroupRange, Alias> entry : mTalkgroupRangeAliasMap.entrySet())
{
if(entry.getKey().contains(value))
@@ -780,11 +783,13 @@ public class AliasList
public Alias getAlias(RadioIdentifier identifier)
{
//Match fully qualified identifier only.
if(identifier instanceof FullyQualifiedRadioIdentifier fqri)
{
return mFullyQualifiedRadioAliasMap.get(fqri.toString());
return mFullyQualifiedRadioAliasMap.get(fqri.getFullyQualifiedRadioAddress());
}
//Attempt to match against the radio identifier
int value = identifier.getValue();
Alias mapValue = mRadioAliasMap.get(value);
@@ -793,6 +798,7 @@ public class AliasList
return mapValue;
}
//Alternatively, attempt to match the radio address against any radio ranges.
for(Map.Entry<RadioRange, Alias> entry : mRadioRangeAliasMap.entrySet())
{
if(entry.getKey().contains(value))

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -63,6 +63,69 @@ public class AliasModel
return mAliasListNames;
}
/**
* Clears and reloads the list of alias list names from the current set of aliases.
*/
public void refreshAliasListNames()
{
mAliasListNames.clear();
for(Alias alias : mAliases)
{
String aliasListName = alias.getAliasListName();
if(aliasListName != null && !aliasListName.isEmpty() && !mAliasListNames.contains(aliasListName))
{
mAliasListNames.add(aliasListName);
}
}
}
/**
* Renames the alias list across the set of aliases.
* @param oldName currently used by the alias
* @param newName to apply to the alias
*/
public void renameAliasList(String oldName, String newName)
{
if(oldName == null || oldName.isEmpty() || newName == null || newName.isEmpty())
{
return;
}
mAliases.stream().filter(alias -> alias.getAliasListName().equals(oldName)).forEach(alias -> alias.setAliasListName(newName));
}
/**
* Deletes any aliases that have the alias list name
* @param aliasListName to delete
*/
public void deleteAliasList(String aliasListName)
{
if(aliasListName == null || aliasListName.isEmpty())
{
return;
}
mAliases.removeIf(alias -> alias.getAliasListName().equals(aliasListName));
mAliasListMap.remove(aliasListName);
}
/**
* Adds the list of alias list names to the currently managed set of alias list names.
* @param aliasListNames to add
*/
public void addAliasListNames(List<String> aliasListNames)
{
for(String aliasListName : aliasListNames)
{
if(!mAliasListNames.contains(aliasListName))
{
mAliasListNames.add(aliasListName);
}
}
}
/**
* Unmodifiable list of all aliases currently in the model
*/

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -42,6 +42,7 @@ import io.github.dsheirer.alias.id.record.Record;
import io.github.dsheirer.alias.id.status.UnitStatusID;
import io.github.dsheirer.alias.id.status.UserStatusID;
import io.github.dsheirer.alias.id.talkgroup.P25FullyQualifiedTalkgroup;
import io.github.dsheirer.alias.id.talkgroup.StreamAsTalkgroup;
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
import io.github.dsheirer.alias.id.tone.TonesID;
@@ -70,6 +71,7 @@ import javafx.util.Callback;
@JsonSubTypes.Type(value = RadioRange.class, name = "radioRange"),
@JsonSubTypes.Type(value = Record.class, name = "record"),
@JsonSubTypes.Type(value = SiteID.class, name = "siteID"),
@JsonSubTypes.Type(value = StreamAsTalkgroup.class, name = "streamAsTalkgroup"),
@JsonSubTypes.Type(value = Talkgroup.class, name = "talkgroup"),
@JsonSubTypes.Type(value = TalkgroupRange.class, name = "talkgroupRange"),
@JsonSubTypes.Type(value = TonesID.class, name = "tones"),

View File

@@ -0,0 +1,40 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.alias.id.talkgroup;
import io.github.dsheirer.protocol.Protocol;
/**
* Streaming talkgroup alias. For streamed audio metadata, this value is used to replace the decoded talkgroup or TO
* value with an alias value when there are talkgroup collisions resulting from streaming multiple channels to the
* same stream. This is common in P25 conventional repeater setups where each radio channel is using talkgroup 1.
*/
public class StreamAsTalkgroup extends Talkgroup
{
public StreamAsTalkgroup(int talkgroup)
{
super(Protocol.UNKNOWN, talkgroup);
}
public StreamAsTalkgroup()
{
//No arg JAXB constructor
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -27,6 +27,7 @@ import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
import io.github.dsheirer.identifier.MutableIdentifierCollection;
import io.github.dsheirer.identifier.encryption.EncryptionKeyIdentifier;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import java.util.Collection;
@@ -68,6 +69,7 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
private final static Logger mLog = LoggerFactory.getLogger(AudioSegment.class);
private BooleanProperty mComplete = new SimpleBooleanProperty(false);
private BooleanProperty mDuplicate = new SimpleBooleanProperty(false);
private BooleanProperty mEncrypted = new SimpleBooleanProperty(false);
private BooleanProperty mRecordAudio = new SimpleBooleanProperty(false);
private IntegerProperty mMonitorPriority = new SimpleIntegerProperty(Priority.DEFAULT_PRIORITY);
private ObservableSet<BroadcastChannel> mBroadcastChannels = FXCollections.observableSet(new HashSet<>());
@@ -129,6 +131,25 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
return (mSampleCount / 8); //8 kHz audio generates 8 samples per millisecond
}
/**
* Indicates if the audio segment contains encrypted audio.
*
* @return encrypted property
*/
public BooleanProperty encryptedProperty()
{
return mEncrypted;
}
/**
* Indicates if this audio segment is encrypted
* @return true if encrypted.
*/
public boolean isEncrypted()
{
return mEncrypted.get();
}
/**
* The complete property is used by the audio segment producer to signal that the segment is complete and no
* additional audio or identifiers will be added to the segment.
@@ -386,6 +407,11 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
throw new IllegalStateException("Can't add audio to an audio segment that is being disposed");
}
if(mAudioBuffers.isEmpty())
{
mStartTimestamp = System.currentTimeMillis() - 20;
}
mAudioBuffers.add(audioBuffer);
mSampleCount += audioBuffer.length;
}
@@ -415,6 +441,14 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
{
mIdentifierCollection.update(identifier);
/**
* If we have a late-add encryption key, set the encrypted flag to true.
*/
if(identifier instanceof EncryptionKeyIdentifier eki)
{
mEncrypted.set(eki.isEncrypted());
}
List<Alias> aliases = mAliasList.getAliases(identifier);
for(Alias alias: aliases)

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -28,7 +28,7 @@ import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.duplicate.CallManagementPreference;
import io.github.dsheirer.preference.duplicate.ICallManagementProvider;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.util.ThreadPool;
import java.util.ArrayList;
@@ -38,7 +38,8 @@ import java.util.Map;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -51,18 +52,41 @@ import org.slf4j.LoggerFactory;
public class DuplicateCallDetector implements Listener<AudioSegment>
{
private final static Logger mLog = LoggerFactory.getLogger(DuplicateCallDetector.class);
private CallManagementPreference mCallManagementPreference;
private ICallManagementProvider mCallManagementProvider;
private Map<String,SystemDuplicateCallDetector> mDetectorMap = new HashMap();
protected Listener<AudioSegment> mDuplicateCallDetectionListener;
/**
* Constructs an instance.
* @param userPreferences to access the duplicate call detection preferences.
*/
public DuplicateCallDetector(UserPreferences userPreferences)
{
mCallManagementPreference = userPreferences.getDuplicateCallDetectionPreference();
this(userPreferences.getCallManagementPreference());
}
/**
* Constructs an instance.
* @param callManagementProvider to provide call management preferences.
*/
public DuplicateCallDetector(ICallManagementProvider callManagementProvider)
{
mCallManagementProvider = callManagementProvider;
}
/**
* Optional listener to be notified each time an audio segment is flagged as duplicate.
* @param listener to register
*/
public void setDuplicateCallDetectionListener(Listener<AudioSegment> listener)
{
mDuplicateCallDetectionListener = listener;
}
@Override
public void receive(AudioSegment audioSegment)
{
if(mCallManagementPreference.isDuplicateCallDetectionEnabled())
if(mCallManagementProvider.isDuplicateCallDetectionEnabled())
{
Identifier identifier = audioSegment.getIdentifierCollection()
.getIdentifier(IdentifierClass.CONFIGURATION, Form.SYSTEM, Role.ANY);
@@ -77,7 +101,7 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
if(detector == null)
{
detector = new SystemDuplicateCallDetector();
detector = new SystemDuplicateCallDetector(mCallManagementProvider, system);
mDetectorMap.put(system, detector);
}
@@ -87,45 +111,99 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
}
}
/**
* System level duplicate call detector. Uses a scheduled executor to run every 25 ms to compare all ongoing call
* audio segments to detect duplicates.
*
* All audio segments remain in the queue until they are flagged as complete. While in the queue, each call is
* compared against the others to detect duplicates. Once all calls are either flagged as complete or flagged as
* duplicate and removed, the queue is empty and the monitoring is shutdown until a new audio segment arrives and
* then the monitoring starts again.
*/
public class SystemDuplicateCallDetector
{
private LinkedTransferQueue<AudioSegment> mAudioSegmentQueue = new LinkedTransferQueue<>();
private List<AudioSegment> mAudioSegments = new ArrayList<>();
private AtomicBoolean mMonitoring = new AtomicBoolean();
private final LinkedTransferQueue<AudioSegment> mAudioSegmentQueue = new LinkedTransferQueue<>();
private final List<AudioSegment> mAudioSegments = new ArrayList<>();
private ScheduledFuture<?> mProcessorFuture;
private Lock mLock = new ReentrantLock();
private boolean mMonitoring = false;
private final ICallManagementProvider mCallManagementProvider;
private String mSystem;
public SystemDuplicateCallDetector()
/**
* Constructs an instance
* @param callManagementProvider to check for duplicate monitoring preferences
*/
public SystemDuplicateCallDetector(ICallManagementProvider callManagementProvider, String system)
{
mCallManagementProvider = callManagementProvider;
mSystem = system;
}
/**
* Adds the audio segment to the monitoring queue.
* @param audioSegment to add
*/
public void add(AudioSegment audioSegment)
{
//Block on audio segment queue so that we don't interfere with monitoring shutdown
synchronized(mAudioSegmentQueue)
mLock.lock();
try
{
mAudioSegmentQueue.add(audioSegment);
startMonitoring();
}
}
private void startMonitoring()
{
if(mMonitoring.compareAndSet(false, true))
finally
{
mProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(() -> process(),
0, 25, TimeUnit.MILLISECONDS);
mLock.unlock();
}
}
/**
* Starts the call monitoring thread if it's not already running.
*
* Note: this method should only be called from a thread with the lock acquired.
*/
private void startMonitoring()
{
if(!mMonitoring)
{
mProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(this::process,
0, 25, TimeUnit.MILLISECONDS);
mMonitoring = true;
}
}
/**
* Stops the call monitoring thread if the audio segements queue is empty.
*
* Note: this should only be called from a separate thread and not from within the scheduled monitoring thread
* because cancelling the scheduled timer from within the process() method would kill the thread without
* releasing the lock, causing a deadlock.
*/
private void stopMonitoring()
{
if(mMonitoring.compareAndSet(true, false))
mLock.lock();
try
{
if(mProcessorFuture != null)
//Recheck the audio segments queue to make sure we didn't slip in another audio segment before we can
//shut down the scheduled monitoring thread.
if(mMonitoring && mAudioSegments.isEmpty())
{
mProcessorFuture.cancel(true);
if(mProcessorFuture != null)
{
mProcessorFuture.cancel(true);
mProcessorFuture = null;
}
mMonitoring = false;
}
}
finally
{
mLock.unlock();
}
}
/**
@@ -136,7 +214,7 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
*/
private boolean isDuplicate(AudioSegment segment1, AudioSegment segment2)
{
if(mCallManagementPreference.isDuplicateCallDetectionByTalkgroupEnabled())
if(mCallManagementProvider.isDuplicateCallDetectionByTalkgroupEnabled())
{
//Step 1 check for duplicate TO values
List<Identifier> to1 = segment1.getIdentifierCollection().getIdentifiers(Role.TO);
@@ -148,7 +226,7 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
}
}
if(mCallManagementPreference.isDuplicateCallDetectionByRadioEnabled())
if(mCallManagementProvider.isDuplicateCallDetectionByRadioEnabled())
{
//Step 2 check for duplicate FROM values
List<Identifier> from1 = segment1.getIdentifierCollection().getIdentifiers(Role.FROM);
@@ -168,53 +246,51 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
* @param identifiers2
* @return
*/
private boolean isDuplicate(List<Identifier> identifiers1, List<Identifier> identifiers2)
public static boolean isDuplicate(List<Identifier> identifiers1, List<Identifier> identifiers2)
{
for(Identifier identifier1: identifiers1)
{
if(identifier1 instanceof TalkgroupIdentifier)
if(identifier1 instanceof TalkgroupIdentifier tgId1)
{
int talkgroup1 = ((TalkgroupIdentifier)identifier1).getValue();
int tg1 = tgId1.getValue();
for(Identifier identifier2: identifiers2)
{
if(identifier2 instanceof TalkgroupIdentifier &&
((TalkgroupIdentifier)identifier2).getValue() == talkgroup1)
if(identifier2 instanceof TalkgroupIdentifier tgId2 && tgId2.getValue() == tg1)
{
return true;
}
else if(identifier2 instanceof PatchGroupIdentifier &&
((PatchGroupIdentifier)identifier2).getValue().getPatchGroup().getValue() == talkgroup1)
else if(identifier2 instanceof PatchGroupIdentifier pgId2 &&
pgId2.getValue().getPatchGroup().getValue() == tg1)
{
return true;
}
}
}
else if(identifier1 instanceof PatchGroupIdentifier)
else if(identifier1 instanceof PatchGroupIdentifier pgId1)
{
int talkgroup1 = ((PatchGroupIdentifier)identifier1).getValue().getPatchGroup().getValue();
int talkgroup1 = pgId1.getValue().getPatchGroup().getValue();
for(Identifier identifier2: identifiers2)
{
if(identifier2 instanceof TalkgroupIdentifier &&
((TalkgroupIdentifier)identifier2).getValue() == talkgroup1)
if(identifier2 instanceof TalkgroupIdentifier tgId2 && tgId2.getValue() == talkgroup1)
{
return true;
}
else if(identifier2 instanceof PatchGroupIdentifier &&
((PatchGroupIdentifier)identifier2).getValue().getPatchGroup().getValue() == talkgroup1)
else if(identifier2 instanceof PatchGroupIdentifier pgId2 &&
pgId2.getValue().getPatchGroup().getValue() == talkgroup1)
{
return true;
}
}
}
else if(identifier1 instanceof RadioIdentifier)
else if(identifier1 instanceof RadioIdentifier raId1)
{
int radio1 = ((RadioIdentifier)identifier1).getValue();
int radio1 = raId1.getValue();
for(Identifier identifier2: identifiers2)
{
if(identifier2 instanceof RadioIdentifier && ((RadioIdentifier)identifier2).getValue() == radio1)
if(identifier2 instanceof RadioIdentifier raId2 && raId2.getValue() == radio1)
{
return true;
}
@@ -230,6 +306,8 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
*/
private void process()
{
mLock.lock();
try
{
//Transfer in newly arrived audio segments
@@ -247,6 +325,19 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
return complete;
});
//Remove any encrypted audio segments.
mAudioSegments.removeIf(audioSegment -> {
boolean encrypted = audioSegment.isEncrypted();
if(encrypted)
{
audioSegment.decrementConsumerCount();
}
return encrypted;
});
//Only check for duplicates if there is more than one call
if(mAudioSegments.size() > 1)
{
@@ -257,7 +348,7 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
{
AudioSegment current = mAudioSegments.get(currentIndex);
if(!current.isDuplicate())
if(current.hasAudio() && !current.isDuplicate())
{
int checkIndex = currentIndex + 1;
@@ -272,6 +363,12 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
toCheck.setDuplicate(true);
toCheck.decrementConsumerCount();
duplicates.add(toCheck);
//Notify optional listener that we flagged the call as duplicate.
if(mDuplicateCallDetectionListener != null)
{
mDuplicateCallDetectionListener.receive(toCheck);
}
}
}
@@ -285,26 +382,12 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
mAudioSegments.removeAll(duplicates);
}
//Finally, if the audio segment queue is empty, shutdown montitoring until a new segment arrives
//Finally, if the audio segment queue is now empty, shutdown monitoring until a new segment arrives.
//The monitor shutdown method has to be called on a separate thread so that we don't kill our current
// thread and fail to release the lock.
if(mAudioSegments.isEmpty())
{
//Block on the audio segment queue so that we can shutdown before any new segments are added, and
//allow the add(segment) to restart monitoring as soon as needed.
synchronized(mAudioSegmentQueue)
{
if(mAudioSegmentQueue.isEmpty())
{
try
{
stopMonitoring();
}
catch(Exception e)
{
mLog.error("Unexpected error during duplicate audio segment monitoring shutdown", e);
//Do nothing, we got interrupted
}
}
}
ThreadPool.CACHED.submit(this::stopMonitoring);
}
}
catch(Throwable t)
@@ -312,6 +395,10 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
mLog.error("Unknown error while processing audio segments for duplicate call detection. Please report " +
"this to the developer.", t);
}
finally
{
mLock.unlock();
}
}
}
}

View File

@@ -34,6 +34,7 @@ public abstract class AbstractAudioBroadcaster<T extends BroadcastConfiguration>
private Listener<BroadcastEvent> mBroadcastEventListener;
private T mBroadcastConfiguration;
protected ObjectProperty<BroadcastState> mBroadcastState = new SimpleObjectProperty<>(BroadcastState.READY);
protected ObjectProperty<BroadcastState> mLastBadBroadcastState = new SimpleObjectProperty<>();
protected int mStreamedAudioCount = 0;
protected int mErrorAudioCount = 0;
protected int mAgedOffAudioCount = 0;
@@ -55,6 +56,14 @@ public abstract class AbstractAudioBroadcaster<T extends BroadcastConfiguration>
return mBroadcastState;
}
/**
* Observable last bad broadcast state property
*/
public ObjectProperty<BroadcastState> lastBadBroadcastStateProperty()
{
return mLastBadBroadcastState;
}
/**
* Current state of the broadcastAudio connection
*/
@@ -68,10 +77,26 @@ public abstract class AbstractAudioBroadcaster<T extends BroadcastConfiguration>
*/
public void setBroadcastState(BroadcastState broadcastState)
{
if(broadcastState == BroadcastState.CONNECTED)
{
mLastBadBroadcastState.setValue(null);
}
else if(broadcastState.isErrorState() || broadcastState.isWarningState())
{
mLastBadBroadcastState.setValue(broadcastState);
}
mBroadcastState.setValue(broadcastState);
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_STATE_CHANGE));
}
/**
* Last bad state of the broadcastAudio connection
*/
public BroadcastState getLastBadBroadcastState()
{
return mLastBadBroadcastState.get();
}
/**
* Starts the broadcaster
*/

View File

@@ -211,9 +211,17 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
super.setBroadcastState(state);
if(mBroadcastState.get() != null && mBroadcastState.get().isErrorState())
if(mBroadcastState.get() != null)
{
stop();
if(mBroadcastState.get().isErrorState())
{
mLog.error("[" + getStreamName() + "] status: " + mBroadcastState.get().toString());
stop();
}
else if(mBroadcastState.get().isWarningState())
{
mLog.warn("[" + getStreamName() + "] status: " + mBroadcastState.get().toString());
}
}
if(!connected())

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -136,7 +136,7 @@ public class AudioStreamingManager implements Listener<AudioSegment>
{
audioSegment = it.next();
if(audioSegment.isDuplicate() && mUserPreferences.getDuplicateCallDetectionPreference().isDuplicateStreamingSuppressionEnabled())
if(audioSegment.isDuplicate() && mUserPreferences.getCallManagementPreference().isDuplicateStreamingSuppressionEnabled())
{
it.remove();
audioSegment.decrementConsumerCount();
@@ -152,7 +152,7 @@ public class AudioStreamingManager implements Listener<AudioSegment>
if(identifiers.getToIdentifier() instanceof PatchGroupIdentifier patchGroupIdentifier)
{
if(mUserPreferences.getDuplicateCallDetectionPreference()
if(mUserPreferences.getCallManagementPreference()
.getPatchGroupStreamingOption() == PatchGroupStreamingOption.TALKGROUPS)
{
//Decompose the patch group into the individual (patched) talkgroups and process the audio

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -344,14 +344,13 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
deleteBroadcaster(configuredBroadcast);
}
AbstractAudioBroadcaster audioBroadcaster = BroadcastFactory.getBroadcaster(broadcastConfiguration,
final AbstractAudioBroadcaster audioBroadcaster = BroadcastFactory.getBroadcaster(broadcastConfiguration,
mAliasModel, mUserPreferences);
if(audioBroadcaster != null)
{
configuredBroadcast.setAudioBroadcaster(audioBroadcaster);
audioBroadcaster.setListener(mBroadcastEventListener);
audioBroadcaster.start();
mBroadcasterMap.put(audioBroadcaster.getBroadcastConfiguration().getId(), audioBroadcaster);
int index = mConfiguredBroadcasts.indexOf(configuredBroadcast);
@@ -362,6 +361,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
}
broadcast(new BroadcastEvent(audioBroadcaster, BroadcastEvent.Event.BROADCASTER_ADD));
ThreadPool.CACHED.submit(audioBroadcaster::start);
}
}
}

View File

@@ -23,90 +23,92 @@ public enum BroadcastState
/**
* General error in configuration that causes the remote server to reject the connection
*/
CONFIGURATION_ERROR("Configuration Error", true),
CONFIGURATION_ERROR("Configuration Error", true, false),
/**
* Connected to the broadcastAudio server and capable of streaming
*/
CONNECTED("Connected", false),
CONNECTED("Connected", false, false),
/**
* Connection interrupted, attempting to reconnect.
*/
CONNECTING("Connecting", false),
CONNECTING("Connecting", false, false),
/**
* Indicates the configuration is disabled
*/
DISABLED("Disabled", true),
DISABLED("Disabled", true, false),
/**
* Disconnected from the broadcastAudio server
*/
DISCONNECTED("Disconnected", false),
DISCONNECTED("Disconnected", false, false),
/**
* Invalid credentials - user name or password
*/
INVALID_CREDENTIALS("Invalid User Name/Password", true),
INVALID_CREDENTIALS("Invalid User Name/Password", true, false),
/**
* Invalid mount point
*/
INVALID_MOUNT_POINT("Invalid Mount/Stream ID", true),
INVALID_MOUNT_POINT("Invalid Mount/Stream ID", true, false),
/**
* Invalid configuration settings
*/
INVALID_SETTINGS("Invalid Settings", true),
INVALID_SETTINGS("Invalid Settings", true, false),
/**
* Remote server max sources has been exceeded
*/
MAX_SOURCES_EXCEEDED("Max Sources Exceeded", true),
MAX_SOURCES_EXCEEDED("Max Sources Exceeded", false, true),
/**
* Specified mount point is already in use
*/
MOUNT_POINT_IN_USE("Mount Point In Use", false),
MOUNT_POINT_IN_USE("Mount Point In Use", false, true),
/**
* Network is unavailable or the server address cannot be resolved
*/
NETWORK_UNAVAILABLE("Network Unavailable", false),
NETWORK_UNAVAILABLE("Network Unavailable", false, false),
/**
* Server is not known or reachable
*/
NO_SERVER("No Server", false),
NO_SERVER("No Server", false, false),
/**
* Initial state with no connection attempted.
*/
READY("Ready", false),
READY("Ready", false, false),
/**
* Error while broadcasting stream data. Temporary error state to allow connection to be reset.
*/
TEMPORARY_BROADCAST_ERROR("Temporary Broadcast Error", false),
TEMPORARY_BROADCAST_ERROR("Temporary Broadcast Error", false, false),
/**
* Unsupported audio format
*/
UNSUPPORTED_AUDIO_FORMAT("Unsupported Audio Type", true),
UNSUPPORTED_AUDIO_FORMAT("Unsupported Audio Type", true, false),
/**
* Unspecified error
*/
ERROR("Error", true);
ERROR("Error", true, false);
private String mLabel;
private boolean mErrorState;
private boolean mWarningState;
BroadcastState(String label, boolean errorState)
BroadcastState(String label, boolean errorState, boolean warningState)
{
mLabel = label;
mErrorState = errorState;
mWarningState = warningState;
}
public String toString()
@@ -118,4 +120,9 @@ public enum BroadcastState
{
return mErrorState;
}
public boolean isWarningState()
{
return mWarningState;
}
}

View File

@@ -37,6 +37,7 @@ public class ConfiguredBroadcast
private BroadcastConfiguration mBroadcastConfiguration;
private AbstractAudioBroadcaster mAudioBroadcaster;
private ObjectProperty<BroadcastState> mBroadcastState = new SimpleObjectProperty<>();
private ObjectProperty<BroadcastState> mLastBadBroadcastState = new SimpleObjectProperty<>();
/**
* Constructs an instance
@@ -89,6 +90,14 @@ public class ConfiguredBroadcast
return mBroadcastState;
}
/**
* Last bad broadcast state of the configured audio broadcaster (optional)
*/
public ObjectProperty<BroadcastState> lastBadBroadcastStateProperty()
{
return mLastBadBroadcastState;
}
/**
* Sets the audio broadcaster
* @param audioBroadcaster to use for this configuration
@@ -96,11 +105,13 @@ public class ConfiguredBroadcast
public void setAudioBroadcaster(AbstractAudioBroadcaster audioBroadcaster)
{
mBroadcastState.unbind();
mLastBadBroadcastState.unbind();
mAudioBroadcaster = audioBroadcaster;
if(audioBroadcaster != null)
{
mBroadcastState.bind(mAudioBroadcaster.broadcastStateProperty());
mLastBadBroadcastState.bind(mAudioBroadcaster.lastBadBroadcastStateProperty());
}
else
{
@@ -120,6 +131,7 @@ public class ConfiguredBroadcast
{
mBroadcastState.setValue(BroadcastState.CONFIGURATION_ERROR);
}
mLastBadBroadcastState.setValue(null);
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This 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,6 +20,8 @@
package io.github.dsheirer.audio.broadcast.broadcastify;
import com.google.common.net.HttpHeaders;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.AbstractAudioBroadcaster;
import io.github.dsheirer.audio.broadcast.AudioRecording;
@@ -32,6 +34,7 @@ import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier;
import io.github.dsheirer.identifier.configuration.ConfigurationLongIdentifier;
import io.github.dsheirer.identifier.patch.PatchGroup;
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
@@ -45,6 +48,7 @@ import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.CompletionException;
import java.util.concurrent.LinkedTransferQueue;
@@ -76,6 +80,7 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
.build();
private long mLastConnectionAttempt;
private long mConnectionAttemptInterval = 5000; //Every 5 seconds
final private AliasModel mAliasModel;
/**
* Constructs an instance of the broadcaster
@@ -86,6 +91,7 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
MP3Setting mp3Setting, AliasModel aliasModel)
{
super(config);
mAliasModel = aliasModel;
}
/**
@@ -443,10 +449,34 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
/**
* Creates a formatted string with the TO identifiers or uses a default of zero (0)
*/
private static String getTo(AudioRecording audioRecording)
private String getTo(AudioRecording audioRecording)
{
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
//Alias the TO value when the user specifies a 'Stream As Talkgroup'
if(identifier != null)
{
AliasListConfigurationIdentifier config = audioRecording.getIdentifierCollection().getAliasListConfiguration();
if(config != null)
{
AliasList aliasList = mAliasModel.getAliasList(config.getValue());
if(aliasList != null)
{
List<Alias> aliases = aliasList.getAliases(identifier);
for(Alias a: aliases)
{
if(a.getStreamTalkgroupAlias() != null)
{
return String.valueOf(a.getStreamTalkgroupAlias().getValue());
}
}
}
}
}
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
{
return format(patchGroupIdentifier);

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 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.broadcastify;
@@ -35,6 +32,7 @@ public class BroadcastifyFeedConfiguration extends IcecastTCPConfiguration
private final static Logger mLog = LoggerFactory.getLogger(BroadcastifyFeedConfiguration.class);
private int mFeedID;
private boolean mVerboseLogging = false;
public BroadcastifyFeedConfiguration()
{
@@ -123,4 +121,15 @@ public class BroadcastifyFeedConfiguration extends IcecastTCPConfiguration
{
mFeedID = feedID;
}
@JacksonXmlProperty(isAttribute = true, localName = "verbose_logging")
public boolean isVerboseLogging()
{
return mVerboseLogging;
}
public void setVerboseLogging(boolean verboseLogging)
{
mVerboseLogging = verboseLogging;
}
}

View File

@@ -52,6 +52,7 @@ import org.slf4j.LoggerFactory;
public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
{
private static final Logger mLog = LoggerFactory.getLogger(IcecastHTTPAudioBroadcaster.class);
private static final long CONNECTION_ATTEMPT_TIMEOUT_MILLISECONDS = 5000; // 5 seconds
private static final long RECONNECT_INTERVAL_MILLISECONDS = 30000; //30 seconds
private static final String HTTP_1_0_OK_HEX_DUMP = "48 54 54 50 2F 31 2E 30 20 32 30 30 20 4F 4B";
@@ -61,6 +62,7 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
private long mLastConnectionAttempt = 0;
private AtomicBoolean mConnecting = new AtomicBoolean();
private AtomicBoolean mConnectionFailureLogged = new AtomicBoolean(false);
/**
* Creates an Icecast 2.4.x compatible broadcaster using HTTP 1.1 protocol. This broadcaster is
@@ -141,7 +143,8 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
if(mSocketConnector == null)
{
mSocketConnector = new NioSocketConnector();
mSocketConnector.setConnectTimeoutCheckInterval(10000);
mSocketConnector.setConnectTimeoutMillis(CONNECTION_ATTEMPT_TIMEOUT_MILLISECONDS);
mSocketConnector.setConnectTimeoutCheckInterval(1000);
// LoggingFilter loggingFilter = new LoggingFilter(IcecastHTTPAudioBroadcaster.class);
// loggingFilter.setMessageReceivedLogLevel(LogLevel.DEBUG);
@@ -160,37 +163,58 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
{
setBroadcastState(BroadcastState.CONNECTING);
try
{
try {
ConnectFuture future = mSocketConnector
.connect(new InetSocketAddress(getBroadcastConfiguration().getHost(),
getBroadcastConfiguration().getPort()));
future.awaitUninterruptibly();
mStreamingSession = future.getSession();
}
catch(RuntimeIoException rie)
{
boolean connected;
try {
connected = future.await(CONNECTION_ATTEMPT_TIMEOUT_MILLISECONDS);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
if (mConnectionFailureLogged.compareAndSet(false, true)) {
mLog.debug("HTTP connect attempt interrupted");
}
setBroadcastState(BroadcastState.DISCONNECTED);
mLastConnectionAttempt = System.currentTimeMillis();
future.cancel();
disconnect();
return;
}
if (connected && future.isConnected()) {
mStreamingSession = future.getSession();
mConnecting.set(false);
mConnectionFailureLogged.set(false);
return;
} else {
if (mConnectionFailureLogged.compareAndSet(false, true)) {
mLog.debug("HTTP connect attempt timed out ({} ms) or not connected",
CONNECTION_ATTEMPT_TIMEOUT_MILLISECONDS);
}
setBroadcastState(BroadcastState.DISCONNECTED);
mLastConnectionAttempt = System.currentTimeMillis();
future.cancel();
disconnect();
}
} catch (RuntimeIoException rie) {
Throwable throwableCause = rie.getCause();
if(throwableCause instanceof ConnectException)
{
setBroadcastState(BroadcastState.NO_SERVER);
}
else if(throwableCause != null)
{
setBroadcastState(BroadcastState.DISCONNECTED);
mLog.debug("Failed to connect", rie);
}
else
{
setBroadcastState(BroadcastState.DISCONNECTED);
mLog.debug("Failed to connect - no exception is available");
}
setBroadcastState(BroadcastState.DISCONNECTED);
if (mConnectionFailureLogged.compareAndSet(false, true)) {
if (throwableCause != null) {
mLog.debug("Failed to connect", rie);
} else {
mLog.debug("Failed to connect - no exception is available");
}
}
mLastConnectionAttempt = System.currentTimeMillis();
disconnect();
} finally {
if (!connected()) {
mConnecting.set(false);
}
}
mConnecting.set(false);
}
};
@@ -210,6 +234,11 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
{
mStreamingSession.closeNow();
}
mLastConnectionAttempt = System.currentTimeMillis();
if(!getBroadcastState().isErrorState()) {
setBroadcastState(BroadcastState.DISCONNECTED);
}
}
/**
@@ -288,6 +317,7 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
@Override
public void sessionClosed(IoSession session) throws Exception
{
mLastConnectionAttempt = System.currentTimeMillis();
//If there is already an error state, don't override it. Otherwise, set state to disconnected
if(!getBroadcastState().isErrorState())
{
@@ -323,6 +353,7 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
if(hexDump.startsWith(HTTP_1_0_OK_HEX_DUMP))
{
setBroadcastState(BroadcastState.CONNECTED);
mConnectionFailureLogged.set(false);
}
else
{
@@ -341,15 +372,29 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
}
else
{
mLog.error("String [" + getStreamName() + "] - HTTP protocol decoder error - message:\n\n" + message);
mLog.error("String [" + getStreamName() + "] - HTTP 403 protocol decoder error - message:\n\n" + message);
setBroadcastState(BroadcastState.DISCONNECTED);
disconnect();
}
break;
case 401: //Unauthorized
if(message.toString().contains("Authentication Required"))
{
mLog.error("Stream [" + getStreamName() + "] - unable to connect - invalid credentials");
setBroadcastState(BroadcastState.INVALID_CREDENTIALS);
disconnect();
}
else
{
mLog.error("String [" + getStreamName() + "] - HTTP 401 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();
mLog.error("String [" + getStreamName() + "] - HTTP protocol decoder error - message:\n\n" + message);
setBroadcastState(BroadcastState.DISCONNECTED);
disconnect();
}
}
else
@@ -391,6 +436,7 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
case SUCCESS_OK:
setBroadcastState(BroadcastState.CONNECTED);
mConnecting.set(false);
mConnectionFailureLogged.set(false);
break;
case CLIENT_ERROR_UNAUTHORIZED:
setBroadcastState(BroadcastState.INVALID_CREDENTIALS);
@@ -401,9 +447,16 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
disconnect();
break;
default:
setBroadcastState(BroadcastState.ERROR);
disconnect();
/**
* Only allow a generic error to update state if we've not already experienced a more
* specific error. Otherwise, trailing messages will clear the more meaningful error state.
*/
if(!getBroadcastState().isErrorState())
{
setBroadcastState(BroadcastState.ERROR);
}
mLog.debug("Unspecified error: " + response.toString() + " Class:" + object.getClass());
disconnect();
break;
}
}

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 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;
@@ -25,21 +22,16 @@ 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 java.util.List;
import java.util.Optional;
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);
@@ -73,13 +65,23 @@ public class IcecastMetadata
if(to != null)
{
sb.append("TO:").append(to);
List<Alias> aliases = aliasList.getAliases(to);
if(aliases != null && !aliases.isEmpty())
//Check for 'Stream As Talkgroup' alias and use this instead of the decoded TO value.
Optional<Alias> streamAs = aliases.stream().filter(alias -> alias.getStreamTalkgroupAlias() != null).findFirst();
if(streamAs.isPresent())
{
sb.append(" ").append(Joiner.on(", ").skipNulls().join(aliases));
sb.append("TO:").append(streamAs.get().getStreamTalkgroupAlias().getValue());
}
else
{
sb.append("TO:").append(to);
if(!aliases.isEmpty())
{
sb.append(" ").append(Joiner.on(", ").skipNulls().join(aliases));
}
}
}
else

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This 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,6 +20,7 @@ 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.broadcastify.BroadcastifyFeedConfiguration;
import io.github.dsheirer.audio.broadcast.icecast.codec.IcecastCodecFactory;
import io.github.dsheirer.audio.convert.InputAudioFormat;
import io.github.dsheirer.audio.convert.MP3AudioConverter;
@@ -39,6 +40,8 @@ import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.logging.LogLevel;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -56,6 +59,7 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
private IoSession mStreamingSession = null;
private long mLastConnectionAttempt = 0;
private boolean mVerboseLogging = false;
private AtomicBoolean mConnecting = new AtomicBoolean();
/**
@@ -73,6 +77,11 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
MP3Setting mp3Setting, AliasModel aliasModel)
{
super(configuration, inputAudioFormat, mp3Setting, aliasModel);
if(configuration instanceof BroadcastifyFeedConfiguration broadcastify)
{
mVerboseLogging = broadcastify.isVerboseLogging();
}
}
/**
@@ -85,7 +94,6 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
{
if(mInlineActive)
{
byte[] metadata = IcecastMetadata.formatInline(IcecastMetadata.getTitle(identifierCollection, mAliasModel)).getBytes();
if (mInlineRemaining == -1)
{
mInlineRemaining = mInlineInterval;
@@ -96,16 +104,34 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
byte[] chunk = Arrays.copyOfRange(audio, audioOffset, Math.min(audioOffset + mInlineRemaining, audio.length));
mInlineRemaining -= chunk.length;
audioOffset += chunk.length;
if(mVerboseLogging)
{
mLog.info("Broadcasting audio data - sending [" + chunk.length + "/" + audio.length + "] bytes");
}
mStreamingSession.write(chunk);
if(mInlineRemaining == 0)
{
mInlineRemaining = mInlineInterval;
byte[] metadata = IcecastMetadata.formatInline(IcecastMetadata.getTitle(identifierCollection, mAliasModel)).getBytes();
if(mVerboseLogging)
{
mLog.info("Sending inline metadata: " + new String(metadata));
}
mStreamingSession.write(metadata);
}
}
}
else
{
if(mVerboseLogging)
{
mLog.info("Broadcasting audio data [" + audio.length + "] bytes");
}
mStreamingSession.write(audio);
}
}
@@ -132,9 +158,12 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
mSocketConnector = new NioSocketConnector();
mSocketConnector.getSessionConfig().setWriteTimeout(WRITE_TIMEOUT_SECONDS);
// LoggingFilter loggingFilter = new LoggingFilter(IcecastTCPAudioBroadcaster.class);
// loggingFilter.setMessageSentLogLevel(LogLevel.NONE);
// mSocketConnector.getFilterChain().addLast("logger", loggingFilter);
if(mVerboseLogging)
{
LoggingFilter loggingFilter = new LoggingFilter(IcecastTCPAudioBroadcaster.class);
loggingFilter.setMessageSentLogLevel(LogLevel.NONE);
mSocketConnector.getFilterChain().addLast("logger", loggingFilter);
}
mSocketConnector.getFilterChain().addLast("codec",
new ProtocolCodecFilter(new IcecastCodecFactory()));
@@ -148,6 +177,10 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
@Override
public void run()
{
if(mVerboseLogging)
{
mLog.info("Attempting connection ...");
}
setBroadcastState(BroadcastState.CONNECTING);
try
@@ -156,19 +189,42 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
.connect(new InetSocketAddress(getBroadcastConfiguration().getHost(),
getBroadcastConfiguration().getPort()));
if(mVerboseLogging)
{
mLog.info("Socket created - asynchronous connect requested - entering wait period");
}
boolean connected = future.await(CONNECTION_ATTEMPT_TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS);
if(connected)
{
if(mVerboseLogging)
{
mLog.info("Connected.");
}
mStreamingSession = future.getSession();
mConnecting.set(false);
return;
}
else
{
if(mVerboseLogging)
{
mLog.info("Not Connected. Connection attempt timeout [" + CONNECTION_ATTEMPT_TIMEOUT_MILLISECONDS + "ms] exceeded");
}
}
}
catch(RuntimeIoException rioe)
{
if(rioe.getCause() instanceof SocketException)
{
if(mVerboseLogging)
{
mLog.info("Socket error. This usually indicates sdrtrunk can't reach the server " +
"address over the current network connection. Setting state to " +
"NETWORK UNAVAILABLE", rioe);
}
setBroadcastState(BroadcastState.NETWORK_UNAVAILABLE);
mConnecting.set(false);
return;
@@ -176,12 +232,22 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
}
catch(UnresolvedAddressException uae)
{
if(mVerboseLogging)
{
mLog.info("Unresolved Address error. This means the domain name services can't resolve " +
"the server URL to an IP address. Setting state to NETWORK UNAVAILABLE", uae);
}
setBroadcastState(BroadcastState.NETWORK_UNAVAILABLE);
mConnecting.set(false);
return;
}
catch(Exception e)
{
if(mVerboseLogging)
{
mLog.info("Unknown error. An error occurred while attempting to connect to the server.", e);
}
mLog.error("Error", e);
//Disregard ... we'll disconnect and try again
}
@@ -190,6 +256,11 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
mLog.error("Throwable error caught", t);
}
if(mVerboseLogging)
{
mLog.info("Starting disconnect sequence since an error occurred while trying to connect.");
}
disconnect();
mConnecting.set(false);
}
@@ -209,6 +280,11 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
{
if(connected() && mStreamingSession != null)
{
if(mVerboseLogging)
{
mLog.info("Routine disconnect requested from a connected state with a non-null streaming session");
}
mStreamingSession.closeNow();
}
else
@@ -217,6 +293,12 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
//want to preserve the error state that got us here, so the user can see it.
if(!getBroadcastState().isErrorState())
{
if(mVerboseLogging)
{
mLog.info("Disconnect requested - previous non-error state was [" + getBroadcastState() +
"] - changing state to DISCONNECTED");
}
setBroadcastState(BroadcastState.DISCONNECTED);
}
@@ -282,12 +364,23 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
mInlineActive = false;
}
if(mVerboseLogging)
{
mLog.info("Session opened. Sending: " + sb);
}
session.write(sb.toString());
}
@Override
public void sessionClosed(IoSession session) throws Exception
{
if(mVerboseLogging)
{
mLog.info("Session closed. Setting connecting flag to false.");
}
mLastConnectionAttempt = System.currentTimeMillis();
//If there is already an error state, don't override it. Otherwise, set state to disconnected
@@ -311,15 +404,23 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
mLog.error("[" + getStreamName() + "] Broadcast error", cause);
}
if(mVerboseLogging)
{
mLog.info("Session error caught.", cause);
}
disconnect();
}
@Override
public void messageReceived(IoSession session, Object object) throws Exception
{
if(object instanceof String)
if(object instanceof String message)
{
String message = (String) object;
if(mVerboseLogging)
{
mLog.info("Message Received [" + message + "]");
}
if(message != null && !message.trim().isEmpty())
{
@@ -347,7 +448,15 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
else
{
mLog.error("Unrecognized server response:" + message);
setBroadcastState(BroadcastState.ERROR);
/**
* Only allow a generic error to update state if we've not already experienced a more
* specific error. Otherwise, trailing messages will clear the more meaningful error state.
*/
if(!getBroadcastState().isErrorState())
{
setBroadcastState(BroadcastState.ERROR);
}
}
}
}

View File

@@ -34,7 +34,8 @@
API_KEY("api_key"),
SOURCE_LIST("source_list"),
FREQ_LIST("freq_list"),
PATCHES("patches");
PATCH_LIST("patch_list"),
TALKER_ALIAS("talker_alias");
private String mHeader;

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,55 +19,45 @@
package io.github.dsheirer.audio.broadcast.openmhz;
import com.google.common.net.HttpHeaders;
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.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;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.MutableIdentifierCollection;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.configuration.ConfigurationLongIdentifier;
import io.github.dsheirer.identifier.patch.PatchGroup;
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.util.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.nio.file.Files;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ConnectException;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.CompletionException;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import com.google.common.net.HttpHeaders;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.alias.AliasModel;
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;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.alias.TalkerAliasIdentifier;
import io.github.dsheirer.identifier.configuration.ConfigurationLongIdentifier;
import io.github.dsheirer.identifier.patch.PatchGroup;
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.util.ThreadPool;
import java.io.IOException;
import java.net.ConnectException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.CompletionException;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -249,6 +239,7 @@
long timestampSeconds = (int)(audioRecording.getStartTime() / 1E3);
String talkgroup = getTo(audioRecording);
String radioId = getFrom(audioRecording);
String talkerAlias = getTalkerAlias(audioRecording);
Long frequency = getFrequency(audioRecording);
String patches = getPatches(audioRecording);
String talkgroupLabel = getTalkgroupLabel(audioRecording);
@@ -282,8 +273,8 @@
.addPart(FormField.TALKGROUP_NUM, talkgroup)
.addPart(FormField.EMERGENCY, 0)
.addPart(FormField.API_KEY, getBroadcastConfiguration().getApiKey())
// For future use if OpenMHz supports patch information
//.addPart(FormField.PATCHES, patches)
.addPart(FormField.PATCH_LIST, patches)
.addPart(FormField.TALKER_ALIAS, talkerAlias)
.addPart(FormField.SOURCE_LIST, "[{ \"pos\": 0.00, \"src\": " + radioId + "}]");
HttpRequest fileRequest = HttpRequest.newBuilder()
@@ -426,22 +417,40 @@
* Creates a formatted string with the TO identifiers or uses a default of zero (0)
*
*/
private static String getTo(AudioRecording audioRecording)
private String getTo(AudioRecording audioRecording)
{
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
if(identifier != null)
{
return patchGroupIdentifier.getValue().getPatchGroup().getValue().toString();
}
else if(identifier instanceof TalkgroupIdentifier talkgroupIdentifier)
{
return String.valueOf(RadioReferenceDecoder.convertToRadioReferenceTalkgroup(talkgroupIdentifier.getValue(),
talkgroupIdentifier.getProtocol()));
}
else if(identifier instanceof RadioIdentifier radioIdentifier)
{
return radioIdentifier.getValue().toString();
AliasList aliasList = mAliasModel.getAliasList(audioRecording.getIdentifierCollection());
if(aliasList != null)
{
List<Alias> aliases = aliasList.getAliases(identifier);
//Check for 'Stream As Talkgroup' alias and use this instead of the decoded TO value.
Optional<Alias> streamAs = aliases.stream().filter(alias -> alias.getStreamTalkgroupAlias() != null).findFirst();
if(streamAs.isPresent())
{
return String.valueOf(streamAs.get().getStreamTalkgroupAlias().getValue());
}
}
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
{
return patchGroupIdentifier.getValue().getPatchGroup().getValue().toString();
}
else if(identifier instanceof TalkgroupIdentifier talkgroupIdentifier)
{
return String.valueOf(RadioReferenceDecoder.convertToRadioReferenceTalkgroup(talkgroupIdentifier.getValue(),
talkgroupIdentifier.getProtocol()));
}
else if(identifier instanceof RadioIdentifier radioIdentifier)
{
return radioIdentifier.getValue().toString();
}
}
return "0";
@@ -547,6 +556,28 @@
return "[]";
}
/**
* Creates a formatted string with talkerAlias identifier
*/
private static String getTalkerAlias(AudioRecording audioRecording)
{
for(Identifier identifier: audioRecording.getIdentifierCollection().getIdentifiers(Role.FROM))
{
if(identifier instanceof TalkerAliasIdentifier)
{
TalkerAliasIdentifier talkerID = (TalkerAliasIdentifier)identifier;
if(talkerID.isValid())
{
return talkerID.getValue();
}
}
}
return "";
}
/**
* Tests both the connection and configuration against the OpenMHz Call API service
* @param configuration containing API key and system id

View File

@@ -36,6 +36,7 @@ public enum FormField
SOURCES("sources"),
SYSTEM("system"),
SYSTEM_LABEL("systemLabel"),
TALKER_ALIAS("talkerAlias"),
TALKGROUP_ID("talkgroup"),
TALKGROUP_GROUP("talkgroupGroup"),
TALKGROUP_LABEL("talkgroupLabel"),

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -34,6 +34,7 @@ import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.alias.TalkerAliasIdentifier;
import io.github.dsheirer.identifier.configuration.ConfigurationLongIdentifier;
import io.github.dsheirer.identifier.patch.PatchGroup;
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
@@ -48,6 +49,7 @@ import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.CompletionException;
import java.util.concurrent.LinkedTransferQueue;
@@ -227,6 +229,7 @@ public class RdioScannerBroadcaster extends AbstractAudioBroadcaster<RdioScanner
long timestampSeconds = (int)(audioRecording.getStartTime() / 1E3);
String talkgroup = getTo(audioRecording);
String radioId = getFrom(audioRecording);
String talkerAlias = getTalkerAlias(audioRecording);
Long frequency = getFrequency(audioRecording);
String patches = getPatches(audioRecording);
String talkgroupLabel = getTalkgroupLabel(audioRecording);
@@ -261,6 +264,7 @@ public class RdioScannerBroadcaster extends AbstractAudioBroadcaster<RdioScanner
.addPart(FormField.TALKGROUP_ID, talkgroup)
.addPart(FormField.SOURCE, radioId)
.addPart(FormField.FREQUENCY, frequency)
.addPart(FormField.TALKER_ALIAS, talkerAlias)
.addPart(FormField.TALKGROUP_LABEL, talkgroupLabel)
.addPart(FormField.TALKGROUP_GROUP, talkgroupGroup)
.addPart(FormField.SYSTEM_LABEL, systemLabel)
@@ -408,26 +412,62 @@ public class RdioScannerBroadcaster extends AbstractAudioBroadcaster<RdioScanner
return "0";
}
private static String getTalkerAlias(AudioRecording audioRecording)
{
for(Identifier identifier: audioRecording.getIdentifierCollection().getIdentifiers(Role.FROM))
{
if(identifier instanceof TalkerAliasIdentifier)
{
TalkerAliasIdentifier talkerID = (TalkerAliasIdentifier)identifier;
if(talkerID.isValid())
{
return talkerID.getValue();
}
}
}
return "";
}
/**
* Creates a formatted string with the TO identifiers or uses a default of zero (0)
*
*/
private static String getTo(AudioRecording audioRecording)
private String getTo(AudioRecording audioRecording)
{
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
if(identifier != null)
{
return patchGroupIdentifier.getValue().getPatchGroup().getValue().toString();
}
else if(identifier instanceof TalkgroupIdentifier talkgroupIdentifier)
{
return String.valueOf(RadioReferenceDecoder.convertToRadioReferenceTalkgroup(talkgroupIdentifier.getValue(),
talkgroupIdentifier.getProtocol()));
}
else if(identifier instanceof RadioIdentifier radioIdentifier)
{
return radioIdentifier.getValue().toString();
AliasList aliasList = mAliasModel.getAliasList(audioRecording.getIdentifierCollection());
if(aliasList != null)
{
List<Alias> aliases = aliasList.getAliases(identifier);
//Check for 'Stream As Talkgroup' alias and use this instead of the decoded TO value.
Optional<Alias> streamAs = aliases.stream().filter(alias -> alias.getStreamTalkgroupAlias() != null).findFirst();
if(streamAs.isPresent())
{
return String.valueOf(streamAs.get().getStreamTalkgroupAlias().getValue());
}
}
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
{
return patchGroupIdentifier.getValue().getPatchGroup().getValue().toString();
}
else if(identifier instanceof TalkgroupIdentifier talkgroupIdentifier)
{
return String.valueOf(RadioReferenceDecoder.convertToRadioReferenceTalkgroup(talkgroupIdentifier.getValue(),
talkgroupIdentifier.getProtocol()));
}
else if(identifier instanceof RadioIdentifier radioIdentifier)
{
return radioIdentifier.getValue().toString();
}
}
return "0";

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -29,6 +29,17 @@ import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.util.ThreadPool;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
@@ -41,17 +52,6 @@ import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.atomic.AtomicBoolean;
public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUpdater
{
private final static Logger mLog = LoggerFactory.getLogger(ShoutcastV1BroadcastMetadataUpdater.class);
@@ -212,10 +212,20 @@ public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUp
if(to != null)
{
sb.append("TO:").append(to);
List<Alias> aliases = aliasList.getAliases(to);
//Check for 'Stream As Talkgroup' alias and use this instead of the decoded TO value.
Optional<Alias> streamAs = aliases.stream().filter(alias -> alias.getStreamTalkgroupAlias() != null).findFirst();
if(streamAs.isPresent())
{
sb.append("TO:").append(streamAs.get().getStreamTalkgroupAlias().getValue());
}
else
{
sb.append("TO:").append(to);
}
if(!aliases.isEmpty())
{
sb.append(" ").append(Joiner.on(", ").skipNulls().join(aliases));

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -49,6 +49,15 @@ 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 java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.math3.util.FastMath;
import org.apache.mina.core.RuntimeIoException;
import org.apache.mina.core.future.ConnectFuture;
@@ -59,15 +68,6 @@ import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.atomic.AtomicBoolean;
public class ShoutcastV2AudioStreamingBroadcaster extends AudioStreamingBroadcaster implements IBroadcastMetadataUpdater
{
private final static Logger mLog = LoggerFactory.getLogger(ShoutcastV2AudioStreamingBroadcaster.class);
@@ -303,13 +303,23 @@ public class ShoutcastV2AudioStreamingBroadcaster extends AudioStreamingBroadcas
if(to != null)
{
sbTitle2.append("TO:").append(to);
List<Alias> aliases = aliasList.getAliases(to);
if(!aliases.isEmpty())
//Check for 'Stream As Talkgroup' alias and use this instead of the decoded TO value.
Optional<Alias> streamAs = aliases.stream().filter(alias -> alias.getStreamTalkgroupAlias() != null).findFirst();
if(streamAs.isPresent())
{
sbTitle2.append(" ").append(Joiner.on(", ").skipNulls().join(aliases));
sbTitle2.append("TO:").append(streamAs.get().getStreamTalkgroupAlias().getValue());
}
else
{
sbTitle2.append("TO:").append(to);
if(!aliases.isEmpty())
{
sbTitle2.append(" ").append(Joiner.on(", ").skipNulls().join(aliases));
}
}
}
else

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -41,6 +41,9 @@ import jmbe.iface.IAudioCodecLibrary;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base audio module for protocols that use the JMBE audio codec.
*/
public abstract class JmbeAudioModule extends AbstractAudioModule implements Listener<IMessage>, IMessageListener,
ISquelchStateListener
{
@@ -59,12 +62,23 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
loadConverter();
}
@Override
protected void closeAudioSegment()
{
super.closeAudioSegment();
//Reset the audio codec to clear any leftover frame data from the previous call.
if(mAudioCodec != null)
{
mAudioCodec.reset();
}
}
@Override
public void dispose()
{
super.dispose();
MyEventBus.getGlobalEventBus().unregister(this);
mAudioCodec = null;
}
public IAudioCodec getAudioCodec()

View File

@@ -36,7 +36,7 @@ import java.util.List;
* radio identifiers.
*/
@JsonRootName("mbe_call")
@JsonPropertyOrder({"protocol", "call_type", "from", "to", "encrypted", "frames"})
@JsonPropertyOrder({"protocol", "version", "call_type", "from", "to", "encrypted", "system", "site", "frames"})
@JsonInclude(JsonInclude.Include.NON_NULL)
public class MBECallSequence
{
@@ -77,6 +77,15 @@ public class MBECallSequence
mProtocol = protocol;
}
/**
* Gets the version number for the mbe file structure.
* Should be incremented only on breaking structural changes, not minor additions.
*/
@JsonProperty("version")
public Integer getVersion()
{
return 2;
}
/**
* Indicates if this sequences contains any audio frames

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This 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,7 @@ import io.github.dsheirer.controller.NamingThreadFactory;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
import io.github.dsheirer.log.LoggingSuppressor;
import io.github.dsheirer.preference.PreferenceType;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.sample.Broadcaster;
@@ -58,6 +59,7 @@ import javax.sound.sampled.SourceDataLine;
public abstract class AudioOutput implements LineListener, Listener<IdentifierUpdateNotification>
{
private final static Logger mLog = LoggerFactory.getLogger(AudioOutput.class);
private static final LoggingSuppressor LOGGING_SUPPRESSOR = new LoggingSuppressor(mLog);
private int mBufferStartThreshold;
private int mBufferStopThreshold;
private Listener<IdentifierCollection> mIdentifierCollectionListener;
@@ -82,6 +84,9 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
private boolean mDropDuplicates;
private long mOutputLastTimestamp = 0;
private static final long STALE_PLAYBACK_THRESHOLD_MS = 500;
private AudioFormat mAudioFormat;
private Line.Info mLineInfo;
private int mRequestedBufferSize;
/**
* Single audio channel playback with automatic starting and stopping of the
@@ -105,11 +110,14 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
mScheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new NamingThreadFactory(
"sdrtrunk audio output " + mixerChannel.name()));
mUserPreferences = userPreferences;
mDropDuplicates = mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled();
mDropDuplicates = mUserPreferences.getCallManagementPreference().isDuplicatePlaybackSuppressionEnabled();
mAudioFormat = audioFormat;
mLineInfo = lineInfo;
mRequestedBufferSize = requestedBufferSize;
try
{
mOutput = (SourceDataLine) mMixer.getLine(lineInfo);
mOutput = (SourceDataLine) mMixer.getLine(mLineInfo);
if(mOutput != null)
{
@@ -132,8 +140,9 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
}
catch(IllegalArgumentException iae)
{
mLog.warn("Couldn't obtain MASTER GAIN control for stereo line [" +
mixer.getMixerInfo().getName() + " | " + getChannelName() + "]");
LOGGING_SUPPRESSOR.error("no gain control", 2, "Couldn't obtain " +
"MASTER GAIN control for stereo line [" + mixer.getMixerInfo().getName() + " | " +
getChannelName() + "]");
}
try
@@ -143,8 +152,9 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
}
catch(IllegalArgumentException iae)
{
mLog.warn("Couldn't obtain MUTE control for stereo line [" +
mixer.getMixerInfo().getName() + " | " + getChannelName() + "]");
LOGGING_SUPPRESSOR.error("no mute control", 2, "Couldn't obtain " +
"MUTE control for stereo line [" + mixer.getMixerInfo().getName() + " | " +
getChannelName() + "]");
}
//Run the queue processor task every 100 milliseconds or 10 times a second
@@ -230,7 +240,7 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
}
else if(preferenceType == PreferenceType.DUPLICATE_CALL_DETECTION)
{
mDropDuplicates = mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled();
mDropDuplicates = mUserPreferences.getCallManagementPreference().isDuplicatePlaybackSuppressionEnabled();
}
}
@@ -266,7 +276,13 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
*/
private void playAudio(ByteBuffer buffer)
{
if(buffer != null)
if(mOutput == null)
{
LOGGING_SUPPRESSOR.error("null output", 2, "Audio Output is null - ignoring audio playback request");
return;
}
if(buffer != null && buffer.array().length > 0)
{
int wrote = 0;
@@ -286,6 +302,25 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
mOutputLastTimestamp = System.currentTimeMillis();
checkStart();
//Something is causing the source data line to fail to accept audio byte data via the write() method
//and this seems to have started around JDK22, maybe. When this happens, close and then re-open the
//data line to clear the error state. Note: in testing this error condition, the line is showing
//the buffer is empty and the capacity is fully available, so it should have accepted attempts to write
//data, but it failed, as indicated by wrote=0.
if(!mOutput.isRunning() && wrote <= 0)
{
mOutput.close();
try
{
mOutput.open();
}
catch(Exception e)
{
mLog.error("Error after closing and attempting to reopen audio output", e);
}
}
}
if(mOutput.isRunning() && wrote < buffer.array().length)

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -41,6 +41,7 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -65,6 +66,8 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
private LinkedTransferQueue<AudioSegment> mNewAudioSegmentQueue = new LinkedTransferQueue<>();
private ScheduledExecutorService mScheduledExecutorService =
Executors.newSingleThreadScheduledExecutor(new NamingThreadFactory("sdrtrunk audio manager"));
private AudioSegmentPrioritySorter mAudioSegmentPrioritySorter = new AudioSegmentPrioritySorter();
private ReentrantLock mAudioOutputLock = new ReentrantLock();
/**
* Constructs an instance.
@@ -95,6 +98,9 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
{
mLog.warn("No audio output devices available");
}
mProcessingTask = mScheduledExecutorService.scheduleAtFixedRate(new AudioSegmentProcessor(),
0, 100, TimeUnit.MILLISECONDS);
}
/**
@@ -120,7 +126,7 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
while(newSegment != null)
{
if(newSegment.isDuplicate() &&
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled())
mUserPreferences.getCallManagementPreference().isDuplicatePlaybackSuppressionEnabled())
{
newSegment.decrementConsumerCount();
}
@@ -148,7 +154,7 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
audioSegment = it.next();
if(audioSegment.isDuplicate() &&
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled())
mUserPreferences.getCallManagementPreference().isDuplicatePlaybackSuppressionEnabled())
{
it.remove();
audioSegment.decrementConsumerCount();
@@ -182,43 +188,60 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
audioSegment = it.next();
if(audioSegment.isDoNotMonitor() || (audioSegment.isDuplicate() &&
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled()))
mUserPreferences.getCallManagementPreference().isDuplicatePlaybackSuppressionEnabled()))
{
it.remove();
audioSegment.decrementConsumerCount();
}
else if(audioSegment.isLinked())
{
for(AudioOutput audioOutput: mAudioOutputs)
mAudioOutputLock.lock();
try
{
if(audioOutput.isLinkedTo(audioSegment))
for(AudioOutput audioOutput: mAudioOutputs)
{
it.remove();
audioOutput.play(audioSegment);
if(audioOutput.isLinkedTo(audioSegment))
{
it.remove();
audioOutput.play(audioSegment);
}
}
}
finally
{
mAudioOutputLock.unlock();
}
}
}
//Sort audio segments by playback priority and assign to empty audio outputs
if(!mAudioSegments.isEmpty())
{
//TODO: change this to take audio segment start time into account also
mAudioSegments.sort(Comparator.comparingInt(o -> o.monitorPriorityProperty().get()));
mAudioSegments.sort(mAudioSegmentPrioritySorter);
//Assign empty audio outputs first
for(AudioOutput audioOutput: mAudioOutputs)
mAudioOutputLock.lock();
try
{
if(audioOutput.isEmpty())
//Assign empty audio outputs first
for(AudioOutput audioOutput: mAudioOutputs)
{
audioOutput.play(mAudioSegments.remove(0));
if(mAudioSegments.isEmpty())
if(audioOutput.isEmpty())
{
return;
audioOutput.play(mAudioSegments.remove(0));
if(mAudioSegments.isEmpty())
{
return;
}
}
}
}
finally
{
mAudioOutputLock.unlock();
}
}
//Remove any audio segments marked as complete that didn't get assigned to an output
@@ -228,7 +251,7 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
audioSegment = it.next();
if(audioSegment.completeProperty().get() || (audioSegment.isDuplicate() &&
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled()))
mUserPreferences.getCallManagementPreference().isDuplicatePlaybackSuppressionEnabled()))
{
it.remove();
audioSegment.decrementConsumerCount();
@@ -288,37 +311,39 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
{
mControllerBroadcaster.broadcast(CONFIGURATION_CHANGE_STARTED);
if(mProcessingTask != null)
mAudioOutputLock.lock();
try
{
mProcessingTask.cancel(true);
for(AudioOutput audioOutput: mAudioOutputs)
{
audioOutput.dispose();
}
mAudioOutputs.clear();
switch(entry.getMixerChannel())
{
case MONO:
AudioOutput mono = new MonoAudioOutput(entry.getMixer(), mUserPreferences);
mAudioOutputs.add(mono);
break;
case STEREO:
AudioOutput left = new StereoAudioOutput(entry.getMixer(), MixerChannel.LEFT, mUserPreferences);
mAudioOutputs.add(left);
AudioOutput right = new StereoAudioOutput(entry.getMixer(), MixerChannel.RIGHT, mUserPreferences);
mAudioOutputs.add(right);
break;
default:
throw new AudioException("Unsupported mixer channel configuration: " + entry.getMixerChannel());
}
}
finally
{
mAudioOutputLock.unlock();
}
for(AudioOutput audioOutput: mAudioOutputs)
{
audioOutput.dispose();
}
mAudioOutputs.clear();
switch(entry.getMixerChannel())
{
case MONO:
AudioOutput mono = new MonoAudioOutput(entry.getMixer(), mUserPreferences);
mAudioOutputs.add(mono);
break;
case STEREO:
AudioOutput left = new StereoAudioOutput(entry.getMixer(), MixerChannel.LEFT, mUserPreferences);
mAudioOutputs.add(left);
AudioOutput right = new StereoAudioOutput(entry.getMixer(), MixerChannel.RIGHT, mUserPreferences);
mAudioOutputs.add(right);
break;
default:
throw new AudioException("Unsupported mixer channel configuration: " + entry.getMixerChannel());
}
mProcessingTask = mScheduledExecutorService.scheduleAtFixedRate(new AudioSegmentProcessor(),
0, 100, TimeUnit.MILLISECONDS);
mControllerBroadcaster.broadcast(CONFIGURATION_CHANGE_COMPLETE);
mMixerChannelConfiguration = entry;
}
@@ -389,4 +414,30 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
}
}
}
/**
* Audio segment comparator for sorting audio segments by: 1)Playback priority and 2)Segment start time
*/
public class AudioSegmentPrioritySorter implements Comparator<AudioSegment>
{
@Override
public int compare(AudioSegment segment1, AudioSegment segment2)
{
if(segment1 == null || segment2 == null)
{
return -1;
}
//If priority is the same, sort by start time
if(segment1.monitorPriorityProperty().get() == segment2.monitorPriorityProperty().get())
{
return Long.compare(segment1.getStartTimestamp(), segment2.getStartTimestamp());
}
//Otherwise, sort by priority
else
{
return Integer.compare(segment1.monitorPriorityProperty().get(), segment2.monitorPriorityProperty().get());
}
}
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -243,6 +243,16 @@ public class BinaryMessage extends BitSet
return mSize;
}
/**
* Current size of the message indicating the quantity of bits that have been added to this message. This value
* is less than or equal to the size() value.
* @return current size in bits.
*/
public int currentSize()
{
return mPointer;
}
public void setSize(int size)
{
mSize = size;
@@ -281,6 +291,20 @@ public class BinaryMessage extends BitSet
}
}
/**
* Adds bit1 and bit2 to this bitset.
*
* Note: this method does not provide overfill protection.
*
* @param bit1 to add.
* @param bit2 to add.
*/
public void add(boolean bit1, boolean bit2)
{
set(mPointer++, bit1);
set(mPointer++, bit2);
}
public String toString()
{
StringBuilder sb = new StringBuilder();
@@ -401,8 +425,8 @@ public class BinaryMessage extends BitSet
boolean[] returnValue = null;
if(startIndex >= 0 &&
startIndex < endIndex &&
endIndex < mSize)
startIndex < endIndex &&
endIndex < mSize)
{
returnValue = new boolean[endIndex - startIndex + 1];
@@ -433,7 +457,7 @@ public class BinaryMessage extends BitSet
if(bits.length > 32)
{
throw new IllegalArgumentException("Overflow - must be 32 bits "
+ "or less to fit into a primitive integer value");
+ "or less to fit into a primitive integer value");
}
int value = 0;
@@ -451,6 +475,203 @@ public class BinaryMessage extends BitSet
return value;
}
/**
* Returns the integer value of the message field described by the field argument.
* @param intField with start and end indices (inclusive).
* @return integer field value.
*/
public int getInt(IntField intField)
{
int value = 0;
for(int index = intField.start(); index <= intField.end(); index++)
{
value = Integer.rotateLeft(value, 1);
if(get(index))
{
value++;
}
}
return value;
}
/**
* Returns the integer value of the message field described by the field argument.
* @param fragmentedField with an array of message indices
* @return integer field value.
*/
public int getInt(FragmentedIntField fragmentedField)
{
int value = 0;
for(int index: fragmentedField.indices())
{
value = Integer.rotateLeft(value, 1);
if(get(index))
{
value++;
}
}
return value;
}
/**
* Indicates if the message field contains a non-zero value indicated by any bits set in the int field indices.
* @param intField to inspect
* @return true if any of the bits are set, indicating a non-zero value.
*/
public boolean hasInt(IntField intField)
{
return nextSetBit(intField.start()) <= intField.end();
}
/**
* Indicates if the message field contains a non-zero value indicated by any bits set in the int field indices.
* @param fragmentedField to inspect
* @return true if any of the bits are set, indicating a non-zero value.
*/
public boolean hasInt(FragmentedIntField fragmentedField)
{
for(int index: fragmentedField.indices())
{
if(get(index))
{
return true;
}
}
return false;
}
/**
* Returns the integer value of the message field described by the field argument where the message start index is
* offset within this binary message.
* @param intField with start and end indices (inclusive).
* @param offset to the start of the first message index.
* @return integer field value.
*/
public int getInt(IntField intField, int offset)
{
int value = 0;
for(int index = intField.start() + offset; index <= intField.end() + offset; index++)
{
value = Integer.rotateLeft(value, 1);
if(get(index))
{
value++;
}
}
return value;
}
/**
* Returns the integer value of the message field described by the field argument where the message start index is
* offset within this binary message.
* @param fragmentedField with an array of field indices
* @param offset to the start of the first message index.
* @return integer field value.
*/
public int getInt(FragmentedIntField fragmentedField, int offset)
{
int value = 0;
for(int index: fragmentedField.indices())
{
value = Integer.rotateLeft(value, 1);
if(get(index + offset))
{
value++;
}
}
return value;
}
/**
* Indicates if the message field contains a non-zero value indicated by any bits set in the int field indices.
* @param intField to inspect
* @param offset to the first bit of the message.
* @return true if any of the bits are set, indicating a non-zero value.
*/
public boolean hasInt(IntField intField, int offset)
{
return nextSetBit(intField.start() + offset) <= (intField.end() + offset);
}
/**
* Indicates if the message field contains a non-zero value indicated by any bits set in the int field indices.
* @param intField to inspect
* @param offset to the first bit of the message.
* @return true if any of the bits are set, indicating a non-zero value.
*/
public boolean hasInt(FragmentedIntField fragmentedField, int offset)
{
for(int index: fragmentedField.indices())
{
if(get(index + offset))
{
return true;
}
}
return false;
}
/**
* Returns the long value of the message field described by the field argument.
* @param intField with start and end indices (inclusive).
* @return integer field value.
*/
public long getLong(LongField intField)
{
long value = 0;
for(int index = intField.start(); index <= intField.end(); index++)
{
value = Long.rotateLeft(value, 1);
if(get(index))
{
value++;
}
}
return value;
}
/**
* Returns the long value of the message field described by the field argument where the message start index is
* offset within this binary message.
* @param intField with start and end indices (inclusive).
* @param offset to the start of the first message index.
* @return field value.
*/
public long getLong(LongField intField, int offset)
{
long value = 0;
for(int index = intField.start() + offset; index <= intField.end() + offset; index++)
{
value = Long.rotateLeft(value, 1);
if(get(index))
{
value++;
}
}
return value;
}
/**
* Returns the integer value represented by the bit array
*
@@ -465,7 +686,7 @@ public class BinaryMessage extends BitSet
if(bits.length > 32)
{
throw new IllegalArgumentException("Overflow - must be 32 bits "
+ "or less to fit into a primitive integer value");
+ "or less to fit into a primitive integer value");
}
int value = 0;
@@ -500,6 +721,28 @@ public class BinaryMessage extends BitSet
}
}
/**
* Sets the integer value to the field described by the argument.
* @param value to set
* @param intField describing the indices of the field.
*/
public void setInt(int value, IntField intField)
{
for(int x = 0; x < intField.width(); x++)
{
int mask = 1 << (intField.width() - x - 1);
if((value & mask) == mask)
{
set(intField.start() + x);
}
else
{
clear(intField.start() + x);
}
}
}
/**
* Returns the byte value represented by the bit array
*
@@ -513,7 +756,7 @@ public class BinaryMessage extends BitSet
if(bits.length != 8)
{
throw new IllegalArgumentException("Invalid - there must be 8"
+ "indexes to form a proper byte");
+ "indexes to form a proper byte");
}
int value = 0;
@@ -545,7 +788,7 @@ public class BinaryMessage extends BitSet
if(bits.length != 8)
{
throw new IllegalArgumentException("Invalid - there must be 8"
+ "indexes to form a proper byte");
+ "indexes to form a proper byte");
}
int value = 0;
@@ -673,7 +916,7 @@ public class BinaryMessage extends BitSet
if(bits.length > 64)
{
throw new IllegalArgumentException("Overflow - must be 64 bits "
+ "or less to fit into a primitive long value");
+ "or less to fit into a primitive long value");
}
long value = 0;
@@ -705,7 +948,7 @@ public class BinaryMessage extends BitSet
if(bits.length > 64)
{
throw new IllegalArgumentException("Overflow - must be 64 bits "
+ "or less to fit into a primitive long value");
+ "or less to fit into a primitive long value");
}
long value = 0;
@@ -742,6 +985,18 @@ public class BinaryMessage extends BitSet
return sb.toString();
}
/**
* Returns the integer field formatted as a hex value using zero prefixes to pad the hex character count to fully
* represent the size (width) of the field.
* @param field to parse as hex
* @return hex value.
*/
public String getHex(IntField field)
{
int width = Math.ceilDiv(field.width(), 4);
return String.format("%0" + width + "X", getInt(field));
}
/**
* Format the byte value that starts at the specified index as hexadecimal. If the length of the message is less
* than the start index plus 7 bits, then the value represents those bits as high-order bits with zero padding in
@@ -781,7 +1036,7 @@ public class BinaryMessage extends BitSet
else
{
throw new IllegalArgumentException("BitSetBuffer.getHex() "
+ "maximum array length is 63 bits");
+ "maximum array length is 63 bits");
}
}
@@ -802,7 +1057,7 @@ public class BinaryMessage extends BitSet
if(FastMath.abs(end - start) > 32)
{
throw new IllegalArgumentException("Overflow - must be 32 bits "
+ "or less to fit into a primitive integer value");
+ "or less to fit into a primitive integer value");
}
int value = 0;
@@ -848,14 +1103,14 @@ public class BinaryMessage extends BitSet
if(get(start))
{
//Negative value - flip and add one
BinaryMessage fragment = getSubMessage(start, end);
BinaryMessage fragment = getSubMessage(start, end + 1);
fragment.flip(0, fragment.size());
return fragment.getInt(1, fragment.size()) + 1;
return -(fragment.getInt(0, fragment.size() - 1) + 1);
}
else
{
//Positive value - return the contents.
return getInt(start + 1, end);
return getInt(start, end);
}
}
@@ -877,7 +1132,7 @@ public class BinaryMessage extends BitSet
if(FastMath.abs(end - start) > 64)
{
throw new IllegalArgumentException("Overflow - must be 64 bits "
+ "or less to fit into a primitive long value");
+ "or less to fit into a primitive long value");
}
long value = 0;
@@ -1032,7 +1287,7 @@ public class BinaryMessage extends BitSet
if(!message.matches("[01]*"))
{
throw new IllegalArgumentException(
"Message must contain only zeros and ones");
"Message must contain only zeros and ones");
}
BinaryMessage buffer = new BinaryMessage(message.length());

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 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;
@@ -84,9 +81,9 @@ public class CorrectedBinaryMessage extends BinaryMessage
/**
* Returns a new binary message containing the bits from (inclusive) to end (exclusive).
*
* @param start bit
* @param end bit
* @return message
* @param start bit inclusive
* @param end bit exclusive
* @return message with sub message bits
*/
public CorrectedBinaryMessage getSubMessage(int start, int end)
{

View File

@@ -0,0 +1,45 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received 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;
/**
* Defines a fragmented or non-contiguous bit field within a binary message.
* @param indices for the bits in the field.
*/
public record FragmentedIntField(int... indices)
{
public FragmentedIntField
{
if(indices.length > 32)
{
throw new IllegalArgumentException("Integer field indices size [" + indices.length + "] cannot exceed 32-bits for an integer");
}
}
/**
* Utility constructor method.
* @param indices (inclusive)
* @return constructed fragmented integer field.
*/
public static FragmentedIntField of(int... indices)
{
return new FragmentedIntField(indices);
}
}

View File

@@ -0,0 +1,141 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received 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;
/**
* Defines a contiguous bit field within a binary message.
* @param start bit index, inclusive.
* @param end bit index, inclusive.
*/
public record IntField(int start, int end)
{
public IntField
{
if(end - start > 32)
{
throw new IllegalArgumentException("Integer field length [" + (end - start) + "] cannot exceed 32-bits for an integer");
}
if(end < start)
{
throw new IllegalArgumentException("Integer field start index must be less than end index");
}
}
/**
* Width of the field in bit positions
* @return width
*/
public int width()
{
return end() - start() + 1;
}
/**
* Utility constructor method.
* @param start index (inclusive)
* @param end index (inclusive)
* @return constructed bit field.
*/
public static IntField range(int start, int end)
{
return new IntField(start, end);
}
/**
* Utility constructor method for a field with one four bits of length.
* @param start index (inclusive)
* @return constructed bit field.
*/
public static IntField length4(int start)
{
return new IntField(start, (start + 3));
}
/**
* Utility constructor method for a field with one four bits of length.
* @param start index (inclusive)
* @return constructed bit field.
*/
public static IntField length6(int start)
{
return new IntField(start, (start + 5));
}
/**
* Utility constructor method for a field with one octet of length.
* @param start index (inclusive)
* @return constructed bit field.
*/
public static IntField length8(int start)
{
return new IntField(start, (start + 7));
}
/**
* Utility constructor method for a field with 12 bits of length.
* @param start index (inclusive)
* @return constructed bit field.
*/
public static IntField length12(int start)
{
return new IntField(start, (start + 11));
}
/**
* Utility constructor method for a field with two octets of length.
* @param start index (inclusive)
* @return constructed bit field.
*/
public static IntField length16(int start)
{
return new IntField(start, (start + 15));
}
/**
* Utility constructor method for a field with 20 bits of length.
* @param start index (inclusive)
* @return constructed bit field.
*/
public static IntField length20(int start)
{
return new IntField(start, (start + 19));
}
/**
* Utility constructor method for a field with two octets of length.
* @param start index (inclusive)
* @return constructed bit field.
*/
public static IntField length24(int start)
{
return new IntField(start, (start + 23));
}
/**
* Utility constructor method for a field with three octets of length.
* @param start index (inclusive)
* @return constructed bit field.
*/
public static IntField length32(int start)
{
return new IntField(start, (start + 31));
}
}

View File

@@ -0,0 +1,122 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received 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;
/**
* Defines a contiguous bit field within a binary message.
* @param start bit index, inclusive.
* @param end bit index, inclusive.
*/
public record LongField(int start, int end)
{
public LongField
{
if(end - start > 64)
{
throw new IllegalArgumentException("Long field length [" + (end - start) + "] cannot exceed 64-bits for a long");
}
if(end < start)
{
throw new IllegalArgumentException("Long field start index must be less than end index");
}
}
/**
* Utility constructor method.
* @param start index (inclusive)
* @param end index (inclusive)
* @return constructed bit field.
*/
public static LongField range(int start, int end)
{
return new LongField(start, end);
}
/**
* Utility constructor method for a field with one four bits of length.
* @param start index (inclusive)
* @return constructed bit field.
*/
public static LongField length4(int start)
{
return new LongField(start, (start + 3));
}
/**
* Utility constructor method for a field with one octet of length.
* @param start index (inclusive)
* @return constructed bit field.
*/
public static LongField length8(int start)
{
return new LongField(start, (start + 7));
}
/**
* Utility constructor method for a field with 12 bits of length.
* @param start index (inclusive)
* @return constructed bit field.
*/
public static LongField length12(int start)
{
return new LongField(start, (start + 11));
}
/**
* Utility constructor method for a field with two octets of length.
* @param start index (inclusive)
* @return constructed bit field.
*/
public static LongField length16(int start)
{
return new LongField(start, (start + 15));
}
/**
* Utility constructor method for a field with 20 bits of length.
* @param start index (inclusive)
* @return constructed bit field.
*/
public static LongField length20(int start)
{
return new LongField(start, (start + 19));
}
/**
* Utility constructor method for a field with two octets of length.
* @param start index (inclusive)
* @return constructed bit field.
*/
public static LongField length24(int start)
{
return new LongField(start, (start + 23));
}
/**
* Utility constructor method for a field with three octets of length.
* @param start index (inclusive)
* @return constructed bit field.
*/
public static LongField length32(int start)
{
return new LongField(start, (start + 31));
}
}

View File

@@ -1,35 +1,31 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 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.bits;
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.Validate;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* Multi-Sync pattern matcher. Accepts multiple sync detector objects for
* comparision against the incoming bit stream.
@@ -39,6 +35,7 @@ import java.util.List;
* of the Long.rotate methods will wrap the MSB around to the LSB and
* corrupt the matching value.
*/
@Deprecated //remove after P25P2 decoder is updated
public class MultiSyncPatternMatcher
{
private final static Logger mLog = LoggerFactory.getLogger(MultiSyncPatternMatcher.class);

View File

@@ -1,23 +1,27 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
/*
* *****************************************************************************
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* 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.bits;
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
import org.apache.commons.lang3.Validate;
@Deprecated //Remove after P25P2 decoder is updated.
public class SoftSyncDetector implements ISyncProcessor
{
private ISyncDetectListener mListener;

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -29,7 +29,7 @@ import java.util.Iterator;
*/
public class ByteNativeBuffer extends AbstractNativeBuffer
{
private static final int FRAGMENT_SIZE = 2048;
private static final int FRAGMENT_SIZE = 8192;
private final static float[] LOOKUP_VALUES;
private float mAverageDc;

View File

@@ -1,57 +1,153 @@
/*******************************************************************************
* 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-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received 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;
/**
* Averaging buffer backed by a ring buffer implemented with a float array.
*/
public class FloatAveragingBuffer
{
private float[] mBuffer;
private float mAverage = 0.0f;
private int mBufferSize;
private int mBufferPointer;
public FloatAveragingBuffer( int size )
{
mBufferSize = size;
mBuffer = new float[ size ];
}
public float get( float newValue )
{
float oldValue = mBuffer[ mBufferPointer ];
private final float[] mBuffer;
private final int mBufferSize;
private float mAverage = 0.0f;
private int mBufferPointer;
private boolean mRapidFill = false;
private int mRapidFillCounter = 0;
private int mRapidFillIncrement;
if( Float.isInfinite( newValue ) || Float.isNaN( newValue ) )
{
mAverage = mAverage - ( oldValue / mBufferSize );
/**
* Constructs an instance that uses a rapid fill increment value where the initial values get counted multiple times
* to more quickly get to the average value instead of walking up to the average from the default average value of 0.
* @param size of buffer
* @param rapidFillIncrement indicating how many value additions per individual add to execute until the averaging
* buffer is filled. After the initial fill state is achieved, reverts to using a single value addition.
*/
public FloatAveragingBuffer(int size, int rapidFillIncrement)
{
this(size);
mRapidFillIncrement = rapidFillIncrement;
mRapidFill = true;
}
mBuffer[ mBufferPointer++ ] = 0.0f;
}
else
{
mAverage = mAverage - ( oldValue / mBufferSize ) + ( newValue / mBufferSize );
/**
* Constructs an instance that uses a ring buffer of the specified size.
* @param size of the averaging buffer.
*/
public FloatAveragingBuffer(int size)
{
mBufferSize = size;
mBuffer = new float[size];
}
mBuffer[ mBufferPointer++ ] = newValue;
}
/**
* Resets this buffer to an empty state
*/
public void reset()
{
Arrays.fill(mBuffer, 0);
mBufferPointer = 0;
mAverage = 0.0f;
if( mBufferPointer >= mBufferSize )
{
mBufferPointer = 0;
}
if(mRapidFillIncrement > 0)
{
mRapidFill = true;
mRapidFillCounter = 0;
}
}
return mAverage;
}
/**
* Current average value.
* @return average
*/
public float getAverage()
{
return mAverage;
}
/**
* Adds the value to the buffer and updates the average.
* @param value to add
*/
public void add(float value)
{
if(mRapidFill)
{
for(int x = 0; x < mRapidFillIncrement; x++)
{
float oldValue = mBuffer[mBufferPointer];
if(Float.isInfinite(value) || Float.isNaN(value))
{
mAverage = mAverage - (oldValue / mBufferSize);
mBuffer[mBufferPointer++] = 0.0f;
}
else
{
mAverage = mAverage - (oldValue / mBufferSize) + (value / mBufferSize);
mBuffer[mBufferPointer++] = value;
}
if(mBufferPointer >= mBufferSize)
{
mBufferPointer = 0;
}
}
mRapidFillCounter += mRapidFillIncrement;
if(mRapidFillCounter > mBufferSize)
{
mRapidFill = false;
}
}
else
{
float oldValue = mBuffer[mBufferPointer];
if(Float.isInfinite(value) || Float.isNaN(value))
{
mAverage = mAverage - (oldValue / mBufferSize);
mBuffer[mBufferPointer++] = 0.0f;
}
else
{
mAverage = mAverage - (oldValue / mBufferSize) + (value / mBufferSize);
mBuffer[mBufferPointer++] = value;
}
if(mBufferPointer >= mBufferSize)
{
mBufferPointer = 0;
}
}
}
/**
* Adds the value to the buffer and calculates a new average.
* @param value to add
* @return updated average
*/
public float get(float value)
{
add(value);
return mAverage;
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -30,7 +30,7 @@ import java.util.Iterator;
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 int FRAGMENT_SIZE = 4096;
protected static final float[] COEFFICIENTS =
HilbertTransform.convertHalfBandToHilbert(HilbertTransform.HALF_BAND_FILTER_47_TAP);

View File

@@ -22,6 +22,7 @@ package io.github.dsheirer.channel.metadata;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierUpdateListener;
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
@@ -57,6 +58,7 @@ public class ChannelMetadata implements Listener<IdentifierUpdateNotification>,
private DecoderTypeConfigurationIdentifier mDecoderTypeConfigurationIdentifier;
private Identifier mFromIdentifier;
private List<Alias> mFromIdentifierAliases;
private Identifier mTalkerAliasIdentifier;
private Identifier mToIdentifier;
private List<Alias> mToIdentifierAliases;
private Integer mTimeslot;
@@ -238,6 +240,22 @@ public class ChannelMetadata implements Listener<IdentifierUpdateNotification>,
return mFromIdentifierAliases;
}
/**
* Optional talker alias identifier
*/
public Identifier getTalkerAliasIdentifier()
{
return mTalkerAliasIdentifier;
}
/**
* Indicates if we have a non-null talker alias identifier
*/
public boolean hasTalkerAliasIdentifier()
{
return mTalkerAliasIdentifier != null;
}
/**
* Current call event TO identifier
*/
@@ -409,15 +427,22 @@ public class ChannelMetadata implements Listener<IdentifierUpdateNotification>,
case USER:
if(identifier.getRole() == Role.FROM)
{
mFromIdentifier = update.isAdd() ? identifier : null;
if(mAliasList != null && mFromIdentifier != null)
if(identifier.getForm() == Form.TALKER_ALIAS)
{
mFromIdentifierAliases = mAliasList.getAliases(mFromIdentifier);
mTalkerAliasIdentifier = identifier;
}
else
{
mFromIdentifierAliases = Collections.EMPTY_LIST;
mFromIdentifier = update.isAdd() ? identifier : null;
if(mAliasList != null && mFromIdentifier != null)
{
mFromIdentifierAliases = mAliasList.getAliases(mFromIdentifier);
}
else
{
mFromIdentifierAliases = Collections.EMPTY_LIST;
}
}
broadcastUpdate(ChannelMetadataField.USER_FROM);

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -38,6 +38,14 @@ import io.github.dsheirer.preference.identifier.TalkgroupFormatPreference;
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import java.awt.Color;
import java.awt.Component;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.text.DecimalFormat;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -52,14 +60,6 @@ import javax.swing.SwingConstants;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableCellRenderer;
import java.awt.Color;
import java.awt.Component;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.text.DecimalFormat;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
public class ChannelMetadataPanel extends JPanel implements ListSelectionListener
{
@@ -294,6 +294,10 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
{
text = EMPTY_VALUE;
}
else if(hasAdditionalIdentifier(channelMetadata))
{
text = text + " " + getAdditionalIdentifier(channelMetadata);
}
label.setText(text);
}
@@ -306,6 +310,8 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
}
public abstract Identifier getIdentifier(ChannelMetadata channelMetadata);
public abstract boolean hasAdditionalIdentifier(ChannelMetadata channelMetadata);
public abstract Identifier getAdditionalIdentifier(ChannelMetadata channelMetadata);
}
/**
@@ -323,6 +329,18 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
{
return channelMetadata.getFromIdentifier();
}
@Override
public Identifier getAdditionalIdentifier(ChannelMetadata channelMetadata)
{
return channelMetadata.getTalkerAliasIdentifier();
}
@Override
public boolean hasAdditionalIdentifier(ChannelMetadata channelMetadata)
{
return channelMetadata.hasTalkerAliasIdentifier();
}
}
/**
@@ -340,6 +358,11 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
{
return channelMetadata.getToIdentifier();
}
@Override
public Identifier getAdditionalIdentifier(ChannelMetadata channelMetadata) {return null;}
@Override
public boolean hasAdditionalIdentifier(ChannelMetadata channelMetadata) {return false;}
}
public class ColoredStateCellRenderer extends DefaultTableCellRenderer

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,7 +21,7 @@ package io.github.dsheirer.channel.metadata;
import com.jidesoft.swing.JideSplitPane;
import com.jidesoft.swing.JideTabbedPane;
import io.github.dsheirer.channel.details.ChannelDetailPanel;
import io.github.dsheirer.gui.power.ChannelPowerPanel;
import io.github.dsheirer.gui.channel.ChannelSpectrumPanel;
import io.github.dsheirer.icon.IconModel;
import io.github.dsheirer.module.decode.event.DecodeEventPanel;
import io.github.dsheirer.module.decode.event.MessageActivityPanel;
@@ -42,7 +42,7 @@ public class NowPlayingPanel extends JPanel
private final ChannelDetailPanel mChannelDetailPanel;
private final DecodeEventPanel mDecodeEventPanel;
private final MessageActivityPanel mMessageActivityPanel;
private final ChannelPowerPanel mChannelPowerPanel;
private final ChannelSpectrumPanel mChannelSpectrumSquelchPanel;
private JideTabbedPane mTabbedPane;
private JideSplitPane mSplitPane;
private boolean mDetailTabsVisible;
@@ -58,7 +58,7 @@ public class NowPlayingPanel extends JPanel
mDecodeEventPanel = new DecodeEventPanel(iconModel, userPreferences, playlistManager.getAliasModel());
mMessageActivityPanel = new MessageActivityPanel(userPreferences);
mChannelMetadataPanel = new ChannelMetadataPanel(playlistManager, iconModel, userPreferences);
mChannelPowerPanel = new ChannelPowerPanel(playlistManager, settingsManager);
mChannelSpectrumSquelchPanel = new ChannelSpectrumPanel(playlistManager, settingsManager);
mDetailTabsVisible = detailTabsVisible;
init();
@@ -96,12 +96,12 @@ public class NowPlayingPanel extends JPanel
mTabbedPane.addTab("Details", mChannelDetailPanel);
mTabbedPane.addTab("Events", mDecodeEventPanel);
mTabbedPane.addTab("Messages", mMessageActivityPanel);
mTabbedPane.addTab("Channel", mChannelPowerPanel);
mTabbedPane.addTab("Channel", mChannelSpectrumSquelchPanel);
mTabbedPane.setFont(this.getFont());
mTabbedPane.setForeground(Color.BLACK);
//Register state change listener to toggle visibility state for channel tab to turn-on/off FFT processing
mTabbedPane.addChangeListener(e -> mChannelPowerPanel.setPanelVisible(getTabbedPane().getSelectedIndex() == getTabbedPane()
.indexOfComponent(mChannelPowerPanel)));
mTabbedPane.addChangeListener(e -> mChannelSpectrumSquelchPanel.setPanelVisible(getTabbedPane().getSelectedIndex() == getTabbedPane()
.indexOfComponent(mChannelSpectrumSquelchPanel)));
}
return mTabbedPane;
@@ -135,6 +135,6 @@ public class NowPlayingPanel extends JPanel
mChannelMetadataPanel.addProcessingChainSelectionListener(mChannelDetailPanel);
mChannelMetadataPanel.addProcessingChainSelectionListener(mDecodeEventPanel);
mChannelMetadataPanel.addProcessingChainSelectionListener(mMessageActivityPanel);
mChannelMetadataPanel.addProcessingChainSelectionListener(mChannelPowerPanel);
mChannelMetadataPanel.addProcessingChainSelectionListener(mChannelSpectrumSquelchPanel);
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -35,11 +35,10 @@ import io.github.dsheirer.source.ISourceEventProvider;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.heartbeat.Heartbeat;
import io.github.dsheirer.source.heartbeat.IHeartbeatListener;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
public abstract class AbstractChannelState extends Module implements IChannelEventProvider, IDecodeEventProvider,
IDecoderStateEventProvider, ISourceEventProvider, IHeartbeatListener, ISquelchStateProvider,
IdentifierUpdateProvider, IOverflowListener
@@ -53,7 +52,8 @@ public abstract class AbstractChannelState extends Module implements IChannelEve
private Channel mChannel;
protected boolean mSourceOverflow = false;
private HeartbeatReceiver mHeartbeatReceiver = new HeartbeatReceiver();
protected boolean mTeardownSequenceStarted = false;
protected boolean mTeardownSequenceCompleted = false;
//TODO: remove the IOverflowListener code from this class
@@ -66,6 +66,22 @@ public abstract class AbstractChannelState extends Module implements IChannelEve
mChannel = channel;
}
/**
* Indicates if the teardown sequence was started.
*/
public boolean isTeardownSequenceCompleted()
{
return mTeardownSequenceCompleted;
}
/**
* Indicates if the teardown sequence was completed, meaning that the request disable channel event was dispatched.
*/
public boolean isTeardownSequenceStarted()
{
return mTeardownSequenceStarted;
}
/**
* Updates/replaces the current channel configuration with the argument.
*/
@@ -89,6 +105,11 @@ public abstract class AbstractChannelState extends Module implements IChannelEve
*/
protected abstract void checkState();
/**
* Indicates if any timeslot is currently in a TEARDOWN state.
*/
public abstract boolean isTeardownState();
public abstract List<ChannelMetadata> getChannelMetadata();
public abstract void updateChannelStateIdentifiers(IdentifierUpdateNotification notification);

View File

@@ -26,6 +26,7 @@ import io.github.dsheirer.message.IMessageListener;
import io.github.dsheirer.module.Module;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.event.ActivitySummaryProvider;
import io.github.dsheirer.module.decode.event.DecodeEventDuplicateDetector;
import io.github.dsheirer.module.decode.event.IDecodeEvent;
import io.github.dsheirer.module.decode.event.IDecodeEventProvider;
import io.github.dsheirer.sample.Broadcaster;
@@ -44,6 +45,7 @@ public abstract class AbstractDecoderState extends Module implements ActivitySum
protected Broadcaster<IDecodeEvent> mDecodeEventBroadcaster = new Broadcaster<>();
protected Listener<DecoderStateEvent> mDecoderStateListener;
private DecoderStateEventListener mDecoderStateEventListener = new DecoderStateEventListener();
private static final DecodeEventDuplicateDetector mDuplicateEventDetector = new DecodeEventDuplicateDetector();
private boolean mRunning;
public abstract DecoderType getDecoderType();
@@ -105,7 +107,10 @@ public abstract class AbstractDecoderState extends Module implements ActivitySum
*/
protected void broadcast(IDecodeEvent event)
{
mDecodeEventBroadcaster.broadcast(event);
if(!mDuplicateEventDetector.isDuplicate(event, System.currentTimeMillis()))
{
mDecodeEventBroadcaster.broadcast(event);
}
}
/**
@@ -161,6 +166,9 @@ public abstract class AbstractDecoderState extends Module implements ActivitySum
mDecoderStateListener = null;
}
/**
* Wrapper that implements the IDecoderStateEventListener interface
*/
private class DecoderStateEventListener implements Listener<DecoderStateEvent>
{
@Override
@@ -169,23 +177,4 @@ public abstract class AbstractDecoderState extends Module implements ActivitySum
receiveDecoderStateEvent(event);
}
}
/**
* Message listener that only passes messages while we're running. This is important because each of the decoders
* can process blocks of samples and that can result in additional messages being generated even after shutdown
* and so we shut off the processing of decoded messages when we're commanded to stop. The sample processing thread
* cannot be shutdown or forcefully interrupted because downstream decoder and channel states may have acquired
* locks that have to be properly released.
*/
private class MessageListener implements Listener<IMessage>
{
@Override
public void receive(IMessage message)
{
if(isRunning())
{
receive(message);
}
}
}
}

View File

@@ -25,6 +25,7 @@ import io.github.dsheirer.identifier.IdentifierUpdateNotification;
import io.github.dsheirer.identifier.MutableIdentifierCollection;
import io.github.dsheirer.identifier.configuration.ChannelDescriptorConfigurationIdentifier;
import io.github.dsheirer.identifier.configuration.DecoderTypeConfigurationIdentifier;
import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier;
import io.github.dsheirer.sample.Listener;
/**
@@ -38,13 +39,21 @@ public abstract class DecoderState extends AbstractDecoderState
private MutableIdentifierCollection mIdentifierCollection;
protected Listener<IdentifierUpdateNotification> mConfigurationIdentifierListener;
protected IChannelDescriptor mCurrentChannel;
private long mCurrentFrequency;
/**
* Constructs an instance
* @param mutableIdentifierCollection to preload into this decoder state
*/
public DecoderState(MutableIdentifierCollection mutableIdentifierCollection)
{
mIdentifierCollection = mutableIdentifierCollection;
mIdentifierCollection.update(new DecoderTypeConfigurationIdentifier(getDecoderType()));
}
/**
* Constructs an instance using an empty identifier collection.
*/
public DecoderState()
{
this(new MutableIdentifierCollection());
@@ -59,6 +68,23 @@ public abstract class DecoderState extends AbstractDecoderState
mIdentifierCollection.broadcastIdentifiers();
}
/**
* Current frequency for this channel.
*/
public long getCurrentFrequency()
{
return mCurrentFrequency;
}
/**
* Sets the current frequency for this channel.
* @param frequency to set.
*/
public void setCurrentFrequency(long frequency)
{
mCurrentFrequency = frequency;
}
/**
* Registers the listener to receive identifier update notifications
*/
@@ -153,9 +179,13 @@ public abstract class DecoderState extends AbstractDecoderState
{
getIdentifierCollection().receive(identifierUpdateNotification);
if(identifierUpdateNotification.getIdentifier() instanceof ChannelDescriptorConfigurationIdentifier)
if(identifierUpdateNotification.getIdentifier() instanceof ChannelDescriptorConfigurationIdentifier cdci)
{
setCurrentChannel(((ChannelDescriptorConfigurationIdentifier)identifierUpdateNotification.getIdentifier()).getValue());
setCurrentChannel(cdci.getValue());
}
else if(identifierUpdateNotification.getIdentifier() instanceof FrequencyConfigurationIdentifier fci)
{
setCurrentFrequency(fci.getValue());
}
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -44,13 +44,12 @@ import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceType;
import io.github.dsheirer.source.config.SourceConfigTuner;
import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Multi-Channel state tracks the overall state of all processing modules and decoders configured for the channel
@@ -166,6 +165,7 @@ public class MultiChannelState extends AbstractChannelState implements IDecoderS
mStateMachineMap.get(timeslot).setState(State.IDLE);
break;
case TEARDOWN:
mTeardownSequenceStarted = true;
if(getChannel().isTrafficChannel())
{
checkTeardown();
@@ -206,7 +206,16 @@ public class MultiChannelState extends AbstractChannelState implements IDecoderS
{
if(getChannel().isTrafficChannel())
{
broadcast(new ChannelEvent(getChannel(), ChannelEvent.Event.REQUEST_DISABLE));
try
{
broadcast(new ChannelEvent(getChannel(), ChannelEvent.Event.REQUEST_DISABLE));
}
catch(Throwable t)
{
mLog.error("Error broadcasting shutdown channel event", t);
}
mTeardownSequenceStarted = true;
}
else
{
@@ -238,6 +247,20 @@ public class MultiChannelState extends AbstractChannelState implements IDecoderS
}
}
@Override
public boolean isTeardownState()
{
for(StateMachine stateMachine: mStateMachineMap.values())
{
if(stateMachine.getState() == State.TEARDOWN)
{
return true;
}
}
return false;
}
/**
* Creates configuration identifiers for the channel name, system, site and alias list name.
*/

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -46,11 +46,10 @@ import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceType;
import io.github.dsheirer.source.config.SourceConfigTuner;
import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Channel state tracks the overall state of all processing modules and decoders configured for the channel and
@@ -163,9 +162,11 @@ public class SingleChannelState extends AbstractChannelState implements IDecoder
mStateMachine.setState(State.IDLE);
break;
case TEARDOWN:
mTeardownSequenceStarted = true;
if(getChannel().isTrafficChannel())
{
broadcast(new ChannelEvent(getChannel(), ChannelEvent.Event.REQUEST_DISABLE));
mTeardownSequenceCompleted = true;
}
else
{
@@ -175,6 +176,12 @@ public class SingleChannelState extends AbstractChannelState implements IDecoder
}
}
@Override
public boolean isTeardownState()
{
return mStateMachine.getState() == State.TEARDOWN;
}
@Override
protected void checkState()
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -26,12 +26,20 @@ import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
import io.github.dsheirer.identifier.MutableIdentifierCollection;
import io.github.dsheirer.identifier.configuration.ChannelDescriptorConfigurationIdentifier;
import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier;
import io.github.dsheirer.sample.Listener;
/**
* Base decoder state implementation for a multi-timeslot protocol that tracks the state for a single timeslot.
*/
public abstract class TimeslotDecoderState extends DecoderState
{
private int mTimeslot;
/**
* Constructs an instance
* @param timeslot to monitor/maintain state.
*/
public TimeslotDecoderState(int timeslot)
{
super(new MutableIdentifierCollection(timeslot));
@@ -39,6 +47,9 @@ public abstract class TimeslotDecoderState extends DecoderState
mConfigurationIdentifierListener = new TimeslotConfigurationIdentifierListener();
}
/**
* Monitored timeslot for this decoder state instance.
*/
protected int getTimeslot()
{
return mTimeslot;
@@ -82,6 +93,10 @@ public abstract class TimeslotDecoderState extends DecoderState
{
setCurrentChannel((IChannelDescriptor)identifier);
}
else if(identifier instanceof FrequencyConfigurationIdentifier fci)
{
setCurrentFrequency(fci.getValue());
}
}
}
}

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 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.controller.channel;
@@ -35,13 +32,16 @@ import io.github.dsheirer.record.RecorderType;
import io.github.dsheirer.record.config.RecordConfiguration;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceType;
import io.github.dsheirer.source.config.SourceConfigFactory;
import io.github.dsheirer.source.config.SourceConfigRecording;
import io.github.dsheirer.source.config.SourceConfigTuner;
import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
import io.github.dsheirer.source.config.SourceConfiguration;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import java.beans.Transient;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
@@ -55,9 +55,6 @@ import javafx.util.Callback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.beans.Transient;
import java.util.Objects;
@JacksonXmlRootElement(localName = "channel")
public class Channel extends Configuration implements Listener<SourceEvent>
{
@@ -88,7 +85,7 @@ public class Channel extends Configuration implements Listener<SourceEvent>
private BooleanProperty mAutoStart = new SimpleBooleanProperty();
private IntegerProperty mAutoStartOrder = new SimpleIntegerProperty();
private boolean mSelected;
private TunerChannel mTunerChannel = null;
private List<TunerChannel> mTunerChannels = null;
private ChannelType mChannelType = ChannelType.STANDARD;
@@ -634,9 +631,8 @@ public class Channel extends Configuration implements Listener<SourceEvent>
updateFrequencies();
//Clear the tune channel object so that it can be recreated if the
//source configuration changes
mTunerChannel = null;
//Clear the tune channels object so that it can be recreated if the source configuration changes
mTunerChannels = null;
}
/**
@@ -680,35 +676,50 @@ public class Channel extends Configuration implements Listener<SourceEvent>
}
/**
* Convenience method to construct a tuner channel object representing a
* tuner or recording source frequency and bandwidth that can be used by
* application components for graphically representing this channel.
* Convenience method to construct a tuner channel object representing a tuner or recording source frequency and
* bandwidth that can be used by application components for graphically representing this channel.
*
* If the source configuration is not a tuner or recording, this method
* returns null.
* If the source configuration is not a tuner or recording, this method returns an empty list.
*/
@JsonIgnore
public TunerChannel getTunerChannel()
public List<TunerChannel> getTunerChannels()
{
if(mTunerChannel == null)
if(mTunerChannels == null && mSourceConfiguration != null)
{
if(mSourceConfiguration.getSourceType() == SourceType.TUNER)
{
SourceConfigTuner config = (SourceConfigTuner)mSourceConfiguration;
mTunerChannels = new ArrayList<>();
mTunerChannel = new TunerChannel(config.getFrequency(),
mDecodeConfiguration.getChannelSpecification().getBandwidth());
}
else if(mSourceConfiguration.getSourceType() == SourceType.RECORDING)
switch(mSourceConfiguration.getSourceType())
{
SourceConfigRecording config = (SourceConfigRecording)mSourceConfiguration;
mTunerChannel = new TunerChannel(config.getFrequency(),
mDecodeConfiguration.getChannelSpecification().getBandwidth());
case TUNER:
if(mSourceConfiguration instanceof SourceConfigTuner tunerConfig)
{
mTunerChannels.add(new TunerChannel(tunerConfig.getFrequency(),
mDecodeConfiguration.getChannelSpecification().getBandwidth()));
}
break;
case TUNER_MULTIPLE_FREQUENCIES:
if(mSourceConfiguration instanceof SourceConfigTunerMultipleFrequency multiConfig)
{
for(long frequency: multiConfig.getFrequencies())
{
mTunerChannels.add(new TunerChannel(frequency,
mDecodeConfiguration.getChannelSpecification().getBandwidth()));
}
}
break;
case RECORDING:
if(mSourceConfiguration instanceof SourceConfigRecording recordingConfig)
{
mTunerChannels.add(new TunerChannel(recordingConfig.getFrequency(),
mDecodeConfiguration.getChannelSpecification().getBandwidth()));
}
break;
default:
mLog.warn("Unrecognized channel source type: " + mSourceConfiguration.getSourceType());
}
}
return mTunerChannel;
return mTunerChannels;
}
/**
@@ -717,9 +728,15 @@ public class Channel extends Configuration implements Listener<SourceEvent>
*/
public boolean isWithin(long minimum, long maximum)
{
TunerChannel tunerChannel = getTunerChannel();
for(TunerChannel tunerChannel: getTunerChannels())
{
if(tunerChannel.overlaps(minimum, maximum))
{
return true;
}
}
return tunerChannel != null && tunerChannel.overlaps(minimum, maximum);
return false;
}
/**

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 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.controller.channel;
@@ -26,15 +23,13 @@ import io.github.dsheirer.controller.channel.Channel.ChannelType;
import io.github.dsheirer.controller.channel.ChannelEvent.Event;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* Channel Model
*/
@@ -55,6 +50,58 @@ public class ChannelModel implements Listener<ChannelEvent>
mTrafficChannels.addListener(changeListener);
}
/**
* Get a deduplicated list of alias list names from across the channel configurations.
* @return list of alias list names.
*/
public List<String> getAliasListNames()
{
List<String> aliasListNames = new ArrayList<>();
for(Channel channel : mChannels)
{
String aliasListName = channel.getAliasListName();
if(aliasListName != null && !aliasListName.isEmpty() && !aliasListNames.contains(aliasListName))
{
aliasListNames.add(aliasListName);
}
}
return aliasListNames;
}
/**
* Renames the alias list across the set of aliases.
* @param oldName currently used by the alias
* @param newName to apply to the alias
*/
public void renameAliasList(String oldName, String newName)
{
if(oldName == null || oldName.isEmpty() || newName == null || newName.isEmpty())
{
return;
}
mChannels.stream().filter(channel -> channel.getAliasListName().equals(oldName))
.forEach(channel -> channel.setAliasListName(newName));
}
/**
* Deletes any aliases that have the alias list name
* @param aliasListName to delete
*/
public void deleteAliasList(String aliasListName)
{
if(aliasListName == null || aliasListName.isEmpty())
{
return;
}
mChannels.stream().filter(channel -> channel.getAliasListName().equals(aliasListName))
.forEach(channel -> channel.setAliasListName(null));
}
/**
* Observable list of channel configurations managed by this model
*/
@@ -109,6 +156,45 @@ public class ChannelModel implements Listener<ChannelEvent>
return null;
}
/**
* Renames the alias list across all channels.
* @param existing alias list name
* @param renamed alias list name.
*/
public void renameAliaslistForChannels(String existing, String renamed)
{
if(existing == null || existing.isEmpty() || renamed == null || renamed.isEmpty())
{
return;
}
mChannels.forEach(channel -> {
if(channel.getAliasListName() != null && channel.getAliasListName().equals(existing))
{
channel.setAliasListName(renamed);
}
});
}
/**
* Removes the alias list name from any channels.
* @param name to delete
*/
public void deleteAliasListName(String name)
{
if(name == null || name.isEmpty())
{
return;
}
mChannels.forEach(channel -> {
if(channel.getAliasListName() != null && channel.getAliasListName().equals(name))
{
channel.setAliasListName(null);
}
});
}
/**
* Returns a list of unique system values from across the channel set
*/

View File

@@ -279,15 +279,27 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
break;
case REQUEST_DISABLE:
case NOTIFICATION_DELETE:
if(channel.isProcessing())
if(channel != null)
{
try
{
stopProcessing(channel);
}
catch(ChannelException ce)
catch(Throwable t)
{
mLog.error("Error stopping channel [" + channel.getName() + "] - " + ce.getMessage());
mLog.error("Error stopping channel [" + channel + "]", t);
}
}
else
{
try
{
throw new IllegalArgumentException("Request to disable channel must have non-null channel");
}
catch(IllegalArgumentException iae)
{
mLog.error("Caught a [" + event.getEvent() + "] non-standard channel event - logging stack trace. " +
"This should not happen, please send this error to the developer.", iae);
}
}
break;
@@ -382,8 +394,10 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
try
{
String threadName = "sdrtrunk channel [" + channel.getChannelID() + "/" +
channel.getDecodeConfiguration().getDecoderType().getShortDisplayString() + "]";
source = mTunerManager.getSource(channel.getSourceConfiguration(),
channel.getDecodeConfiguration().getChannelSpecification());
channel.getDecodeConfiguration().getChannelSpecification(), threadName);
}
catch(SourceException se)
{
@@ -393,7 +407,10 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
if(source == null)
{
//This has to be done on the FX event thread when the playlist editor is constructed
Platform.runLater(() -> channel.setProcessing(false));
if(!GraphicsEnvironment.isHeadless())
{
Platform.runLater(() -> channel.setProcessing(false));
}
mChannelEventBroadcaster.broadcast(new ChannelEvent(channel,
ChannelEvent.Event.NOTIFICATION_PROCESSING_START_REJECTED, TUNER_UNAVAILABLE_DESCRIPTION));
@@ -516,7 +533,14 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
if(addProcessingChain(channel, processingChain))
{
processingChain.start();
try
{
processingChain.start();
}
catch(Throwable t)
{
mLog.error("Error caught during processing chain startup - continuing", t);
}
if(GraphicsEnvironment.isHeadless())
{
@@ -622,8 +646,28 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
}
else
{
//This has to be done on the FX event thread when the playlist editor is constructed
Platform.runLater(() -> channel.setProcessing(false));
//When we're in non-headless mode we have to change the processing property on the JavaFX
//event thread. However, if it hasn't yet been initialized (ie an FX window opened), we'll
//get an ISE. In that case, just set the property to false because there won't be any
//property listeners being triggered.
try
{
Platform.runLater(() -> {
try
{
channel.setProcessing(false);
}
catch(Exception e)
{
mLog.error("Error during channel stop while setting processing to false [" + channel +
"] - continuing channel stop process", e);
}
});
}
catch(IllegalStateException e)
{
channel.setProcessing(false);
}
}
try
@@ -842,6 +886,10 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
mLog.error("Error stopping channel [" + (toShutdown != null ? toShutdown.getName() : "unknown") +
"] with source error - " + ce.getMessage());
}
catch(Throwable t)
{
mLog.error("Error stopping channel [" + toShutdown + "]", t);
}
}
}
}
@@ -870,6 +918,8 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
sb.append("\tProcessing Chain - Processing: ").append(chain.isProcessing()).append("\n");
AbstractChannelState state = chain.getChannelState();
sb.append("Channel State: ").append(state.getClass()).append("\n");
sb.append(" Teardown Started:").append(state.isTeardownSequenceStarted()).append("\n");
sb.append(" Teardown Completed:").append(state.isTeardownSequenceCompleted()).append("\n");
for(ChannelMetadata metadata: state.getChannelMetadata())
{
sb.append(metadata.getDescription()).append("\n");

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -56,15 +56,14 @@ import io.github.dsheirer.dsp.window.WindowType;
import io.github.dsheirer.vector.calibrate.CalibrationManager;
import io.github.dsheirer.vector.calibrate.CalibrationType;
import io.github.dsheirer.vector.calibrate.Implementation;
import java.text.DecimalFormat;
import java.util.Set;
import java.util.TreeSet;
import org.apache.commons.math3.util.FastMath;
import org.jtransforms.fft.FloatFFT_1D;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DecimalFormat;
import java.util.Set;
import java.util.TreeSet;
/**
* Utility methods for designing various types of filters.
*/
@@ -627,11 +626,13 @@ public class FilterFactory
* @param samplesPerSymbol - number of samples per symbol
* @param symbolCount - number of symbol intervals for the filter. This directly impacts the filter size
* @param alpha - roll-off factor.
* @param gain of the filter
* @return - filter coefficients
*/
public static float[] getRootRaisedCosine(double samplesPerSymbol, int symbolCount, float alpha)
{
int taps = (int)(samplesPerSymbol * symbolCount);
samplesPerSymbol /= 2;
int taps = (int)Math.round(samplesPerSymbol * symbolCount);
float scale = 0;
@@ -639,47 +640,47 @@ public class FilterFactory
for(int x = 0; x < taps; x++)
{
float index = (float)x - ((float)taps / 2.0f);
double index = x - (taps / 2.0f);
float x1 = (float)FastMath.PI * index / (float)samplesPerSymbol;
float x2 = 4.0f * alpha * index / (float)samplesPerSymbol;
float x3 = x2 * x2 - 1.0f;
double x1 = FastMath.PI * index / samplesPerSymbol;
double x2 = 4.0 * alpha * index / samplesPerSymbol;
double x3 = x2 * x2 - 1.0;
float numerator, denominator;
double numerator, denominator;
if(FastMath.abs(x3) >= 0.000001)
{
if(x != taps / 2)
{
numerator = (float)FastMath.cos((1.0 + alpha) * x1) +
(float)FastMath.sin((1.0f - alpha) * x1) / (4.0f * alpha * index / (float)samplesPerSymbol);
numerator = FastMath.cos((1.0 + alpha) * x1) +
FastMath.sin((1.0 - alpha) * x1) / (4.0 * alpha * index / samplesPerSymbol);
}
else
{
numerator = (float)FastMath.cos((1.0f + alpha) * x1) + (1.0f - alpha) * (float)FastMath.PI / (4.0f * alpha);
numerator = FastMath.cos((1.0 + alpha) * x1) + (1.0 - alpha) * FastMath.PI / (4.0 * alpha);
}
denominator = x3 * (float)FastMath.PI;
denominator = x3 * FastMath.PI;
}
else
{
if(alpha == 1.0f)
if(alpha == 1.0)
{
coefficients[x] = -1.0f;
continue;
}
x3 = (1.0f - alpha) * x1;
x2 = (1.0f + alpha) * x1;
x3 = (1.0 - alpha) * x1;
x2 = (1.0 + alpha) * x1;
numerator = (float)(FastMath.sin(x2) * (1.0f + alpha) * FastMath.PI - FastMath.cos(x3) * ((1.0 - alpha) * FastMath.PI *
(double)samplesPerSymbol) / (4.0 * alpha * index) + FastMath.sin(x3) * (double)samplesPerSymbol *
(double)samplesPerSymbol / (4.0 * alpha * index * index));
numerator = (FastMath.sin(x2) * (1.0 + alpha) * FastMath.PI - FastMath.cos(x3) * ((1.0 - alpha) * FastMath.PI *
samplesPerSymbol) / (4.0 * alpha * index) + FastMath.sin(x3) * samplesPerSymbol *
samplesPerSymbol / (4.0 * alpha * index * index));
denominator = (float)(-32.0 * FastMath.PI * alpha * alpha * index / (double)samplesPerSymbol);
denominator = -32.0 * FastMath.PI * alpha * alpha * index / samplesPerSymbol;
}
coefficients[x] = 4.0f * alpha * numerator / denominator;
coefficients[x] = (float)(4.0 * alpha * numerator / denominator);
scale += coefficients[x];
}
@@ -1210,13 +1211,20 @@ public class FilterFactory
{
DecimalFormat df = new DecimalFormat("0.000000");
int length = 15;
int length = 49;
float [] taps = FilterFactory.getHalfBand(length, WindowType.HAMMING);
for(int x = 0; x < length; x++)
try
{
mLog.debug("Tap: " + x + " Value: " + df.format(taps[x]));
float [] taps = FilterFactory.getSinc(0.5, length, WindowType.NONE);
for(int x = 0; x < length; x++)
{
mLog.debug("Tap: " + x + " Value: " + df.format(taps[x]));
}
}
catch(Exception e)
{
e.printStackTrace();
}
mLog.debug("Done!");

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -189,9 +189,10 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
* Provides a Digital Drop Channel (DDC) for the specified tuner channel or returns null if the channel can't be
* sourced due to the current center frequency and/or sample rate.
* @param tunerChannel specifying center frequency and bandwidth.
* @param threadName for the channel's dispatcher
* @return source or null.
*/
public TunerChannelSource getChannel(TunerChannel tunerChannel)
public TunerChannelSource getChannel(TunerChannel tunerChannel, String threadName)
{
PolyphaseChannelSource channelSource = null;
@@ -200,7 +201,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
try
{
channelSource = new PolyphaseChannelSource(tunerChannel, mChannelCalculator, mFilterManager,
mChannelSourceEventListener);
mChannelSourceEventListener, threadName);
mChannelSources.add(channelSource);
}
@@ -251,11 +252,16 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
synchronized(mBufferDispatcher)
{
mChannelSources.remove(channelSource);
mPolyphaseChannelizer.removeChannel(channelSource);
if(mPolyphaseChannelizer != null)
{
mPolyphaseChannelizer.removeChannel(channelSource);
}
mSourceEventBroadcaster.broadcast(SourceEvent.channelCountChange(getTunerChannelCount()));
//If this is the last/only channel, deregister to stop the sample buffers
if(mPolyphaseChannelizer.getRegisteredChannelCount() == 0)
if(mPolyphaseChannelizer != null && mPolyphaseChannelizer.getRegisteredChannelCount() == 0)
{
mNativeBufferProvider.removeBufferListener(mBufferDispatcher);
mBufferDispatcher.stop();
@@ -296,10 +302,17 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
break;
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_LOCKED:
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_UNLOCKED:
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
case NOTIFICATION_RECORDING_FILE_LOADED:
//no-op
break;
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
//Re-broadcast this event to each channel so that the decoders can reset error tracking function(s).
List<TunerChannelSource> channels = new ArrayList<>(mChannelSources);
for(TunerChannelSource channel: channels)
{
channel.broadcastConsumerSourceEvent(sourceEvent);
}
break;
default:
mLog.info("Unrecognized source event: " + sourceEvent);
break;

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -56,12 +56,14 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
* @param channelCalculator for current channel center frequency and sample rate and index calculations
* @param filterManager for access to new or cached synthesis filters
* @param producerSourceEventListener to receive source event requests (e.g. start/stop sample stream)
* @param threadName for the channel's dispatcher
* @throws IllegalArgumentException if a channel low pass filter can't be designed to the channel specification
*/
public PolyphaseChannelSource(TunerChannel tunerChannel, ChannelCalculator channelCalculator, SynthesisFilterManager filterManager,
Listener<SourceEvent> producerSourceEventListener) throws IllegalArgumentException
Listener<SourceEvent> producerSourceEventListener, String threadName)
throws IllegalArgumentException
{
super(producerSourceEventListener, tunerChannel);
super(producerSourceEventListener, tunerChannel, threadName);
mChannelSampleRate = channelCalculator.getChannelSampleRate();
doUpdateOutputProcessor(channelCalculator, filterManager);
}
@@ -222,7 +224,7 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
{
case 1:
mPolyphaseChannelOutputProcessor = new OneChannelOutputProcessor(channelCalculator.getChannelSampleRate(),
indexes, channelCalculator.getChannelCount(), getHeartbeatManager());
indexes, channelCalculator.getChannelCount(), getHeartbeatManager(), mThreadName);
mPolyphaseChannelOutputProcessor.setListener(this);
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
mPolyphaseChannelOutputProcessor.start();
@@ -233,7 +235,7 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
float[] filter = filterManager.getFilter(channelCalculator.getChannelSampleRate(),
channelCalculator.getChannelBandwidth(), 2);
mPolyphaseChannelOutputProcessor = new TwoChannelOutputProcessor(channelCalculator.getChannelSampleRate(),
indexes, filter, channelCalculator.getChannelCount(), getHeartbeatManager());
indexes, filter, channelCalculator.getChannelCount(), getHeartbeatManager(), mThreadName);
mPolyphaseChannelOutputProcessor.setListener(this);
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
mPolyphaseChannelOutputProcessor.start();

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -41,16 +41,15 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
* oscillator support to apply frequency correction to the channel sample stream as requested by sample consumer.
*
* @param inputChannelCount is the number of input channels for this output processor
* @param sampleRate of the output channel. This is used to match the oscillator's sample rate to the output
* channel sample rate for frequency translation/correction.
* @param heartbeatManager to receive pings on the dispatcher thread
* @param threadName for the dispatcher
*/
public ChannelOutputProcessor(int inputChannelCount, double sampleRate, HeartbeatManager heartbeatManager)
public ChannelOutputProcessor(int inputChannelCount, HeartbeatManager heartbeatManager, String threadName)
{
mInputChannelCount = inputChannelCount;
//Process 1/10th of the sample rate per second at a rate of 20 times a second (200% of anticipated rate)
mHeartbeatManager = heartbeatManager;
mChannelResultsDispatcher = new Dispatcher("sdrtrunk polyphase channel",50, mHeartbeatManager);
mChannelResultsDispatcher = new Dispatcher(threadName,50, mHeartbeatManager);
mChannelResultsDispatcher.setListener(floats -> {
try
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -38,11 +38,12 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
* @param channelIndexes containing a single channel index.
* @param gain value to apply. This is typically the same as the channelizer's channel count.
* @param heartbeatManager to receive heartbeats on the dispatch thread
* @param threadName to use for this processor
*/
public OneChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float gain,
HeartbeatManager heartbeatManager)
HeartbeatManager heartbeatManager, String threadName)
{
super(1, sampleRate, heartbeatManager);
super(1, heartbeatManager, threadName);
setPolyphaseChannelIndices(channelIndexes);
mMixerAssembler = new OneChannelMixerAssembler(gain);
mMixerAssembler.getMixer().setSampleRate(sampleRate);

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -37,15 +37,17 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
*
* @param sampleRate of the output sample stream.
* @param channelIndexes containing two channel indices.
* @param filter to use
* @param gain to apply to output. Typically this is equal to the channelizer's channel count.
* @param heartbeatManager to be pinged on the dispatcher thread
* @param threadName for the dispatcher
*/
public TwoChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float[] filter, float gain,
HeartbeatManager heartbeatManager)
HeartbeatManager heartbeatManager, String threadName)
{
//Set the frequency correction oscillator to 2 x output sample rate since we'll be correcting the frequency
//after synthesizing both input channels
super(2, sampleRate, heartbeatManager);
super(2, heartbeatManager, threadName);
setPolyphaseChannelIndices(channelIndexes);
mMixerAssembler = new TwoChannelMixerAssembler(gain);
mMixerAssembler.getMixer().setSampleRate(sampleRate);

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -42,6 +42,7 @@ public class DecimationFilterFactory
switch(decimationRate)
{
case 0:
case 1:
return new RealDecimateX0Filter();
case 2:
return new RealDecimateX2Filter();

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This 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,7 @@
package io.github.dsheirer.dsp.filter.design;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.hilbert.HilbertTransform;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.window.WindowType;
import javafx.application.Application;
import javafx.scene.Scene;
@@ -59,12 +59,32 @@ public class FilterViewer extends Application
*/
private float[] getFilter()
{
int filterLength = 47;
WindowType windowType = WindowType.HAMMING;
int filterLength = 31;
WindowType windowType = WindowType.BLACKMAN_HARRIS_7;
float[] taps = FilterFactory.getHalfBand(filterLength, windowType);
FIRFilterSpecification specification = FIRFilterSpecification
.lowPassBuilder()
.sampleRate(12500)
.passBandCutoff(5200)
.passBandAmplitude(1.0).passBandRipple(0.01) //.01
.stopBandAmplitude(0.0).stopBandStart(6500) //6500
.stopBandRipple(0.01).build();
taps = HilbertTransform.HALF_BAND_FILTER_47_TAP;
float[] taps = null;
try
{
taps = FilterFactory.getTaps(specification);
}
catch(Exception fde) //FilterDesignException
{
System.out.println("Error");
}
// float[] taps = FilterFactory.getHighPass(8000, 3000, filterLength, windowType);
// float[] taps = FilterFactory.getRootRaisedCosine(50000.0 / 4800.0, 20, 0.2f);
// taps = HilbertTransform.HALF_BAND_FILTER_47_TAP;
if(taps == null)
{

View File

@@ -1,25 +1,24 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.dsp.filter.interpolator;
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2015 Dennis Sheirer
*
* Copyright (C) 2002,2012 Free Software Foundation, Inc.
* Ported to java from gnuradio - interpolator_taps.h
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
public abstract class Interpolator
{
public static final int NTAPS = 8;
@@ -158,4 +157,19 @@ public abstract class Interpolator
{ -1.98993e-04f, 1.24642e-03f, -5.41054e-03f, 9.98534e-01f, 7.89295e-03f, -2.76968e-03f, 8.53777e-04f, -1.54700e-04f }, // 127/128
{ 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 1.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f }, // 128/128
};
/**
* Calculates an interpolated value from eight samples that start at the offset into the sample array. The
* interpolated sample will fall within the middle of the eight sample array, between indexes offset+3 and
* offset+4. The mu argument is translated into an index position between 0 and 128, where a mu of 0.0 will be
* converted to index zero and will be equal to the sample at index offset+3 and a mu of 1.0 will be equal to
* the sample at offset+4. All mu values between 0.0 and 1.0 will be converted to a 1 - 127 index and will
* produce an approximated value from among 127 interpolated sample points between indexes offset+3 and offset+4.
*
* @param samples - sample array of length at least offset + 7
* @param offset into the sample array for the start of the 8-value sequence
* @param mu - interpolated sample position between 0.0 and 1.0
* @return - interpolated sample value
*/
public abstract float filter(float[] samples, int offset, float mu);
}

View File

@@ -0,0 +1,57 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.dsp.filter.interpolator;
import io.github.dsheirer.vector.calibrate.CalibrationManager;
import io.github.dsheirer.vector.calibrate.CalibrationType;
import io.github.dsheirer.vector.calibrate.Implementation;
/**
* Factory for creating an optimal scalar or vector (SIMD) interpolator implementation
*/
public class InterpolatorFactory
{
/**
* Selects and instantiates the best interpolator version, scalar or vector, based on previous calibration.
* @return interpolator
*/
public static Interpolator getInterpolator()
{
Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.INTERPOLATOR);
return getInterpolator(implementation);
}
/**
* Instantiates the specified interpolator implementation.
* @param implementation to construct.
* @return interpolator.
*/
public static Interpolator getInterpolator(Implementation implementation)
{
return switch(implementation)
{
case VECTOR_SIMD_64 -> new InterpolatorVector64();
case VECTOR_SIMD_128 -> new InterpolatorVector128();
case VECTOR_SIMD_256, VECTOR_SIMD_512 -> new InterpolatorVector256(); //Vector 512 not supported
case SCALAR, UNCALIBRATED -> new InterpolatorScalar();
default -> throw new IllegalArgumentException("Unknown implementation type: " + implementation);
};
}
}

View File

@@ -1,17 +1,35 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.dsp.filter.interpolator;
import io.github.dsheirer.sample.complex.Complex;
import java.text.DecimalFormat;
import java.util.Arrays;
import org.apache.commons.lang3.Validate;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DecimalFormat;
import java.util.Arrays;
public class RealInterpolator extends Interpolator
public class InterpolatorScalar extends Interpolator
{
private final static Logger mLog = LoggerFactory.getLogger(RealInterpolator.class);
private final static Logger mLog = LoggerFactory.getLogger(InterpolatorScalar.class);
private float mGain;
@@ -21,11 +39,16 @@ public class RealInterpolator extends Interpolator
*
* @param gain to apply to the interpolated sample
*/
public RealInterpolator(float gain)
public InterpolatorScalar(float gain)
{
mGain = gain;
}
public InterpolatorScalar()
{
this(1.0f);
}
/**
* Calculates an interpolated value from eight samples that start at the offset into the sample array. The
* interpolated sample will fall within the middle of the eight sample array, between indexes offset+3 and
@@ -68,7 +91,7 @@ public class RealInterpolator extends Interpolator
public static void main(String[] args)
{
RealInterpolator interpolator = new RealInterpolator(1.0f);
InterpolatorScalar interpolator = new InterpolatorScalar(1.0f);
DecimalFormat decimalFormat = new DecimalFormat("0.0000");
double TWO_PI = FastMath.PI * 2.0;

View File

@@ -0,0 +1,49 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.dsp.filter.interpolator;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Vector interpolator for 128 bit SIMD instructions.
*/
public class InterpolatorVector128 extends VectorInterpolator
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_128;
@Override
protected float vectorFilter(float[] samples, int offset, int index)
{
FloatVector accumulator = FloatVector.zero(VECTOR_SPECIES);
FloatVector tapsVector, sampleVector;
tapsVector = FloatVector.fromArray(VECTOR_SPECIES, TAPS[index], 0);
sampleVector = FloatVector.fromArray(VECTOR_SPECIES, samples, offset);
accumulator = tapsVector.fma(sampleVector, accumulator);
tapsVector = FloatVector.fromArray(VECTOR_SPECIES, TAPS[index], 4);
sampleVector = FloatVector.fromArray(VECTOR_SPECIES, samples, offset + 4);
accumulator = tapsVector.fma(sampleVector, accumulator);
return accumulator.reduceLanes(VectorOperators.ADD);
}
}

View File

@@ -0,0 +1,40 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.dsp.filter.interpolator;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Vector interpolator for 256 bit SIMD instructions.
*/
public class InterpolatorVector256 extends VectorInterpolator
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_256;
@Override
protected float vectorFilter(float[] samples, int offset, int index)
{
FloatVector tapsVector = FloatVector.fromArray(VECTOR_SPECIES, TAPS[index], 0);
FloatVector sampleVector = FloatVector.fromArray(VECTOR_SPECIES, samples, offset);
return sampleVector.mul(tapsVector).reduceLanes(VectorOperators.ADD);
}
}

View File

@@ -0,0 +1,57 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.dsp.filter.interpolator;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
/**
* Vector interpolator for 64 bit SIMD instructions.
*/
public class InterpolatorVector64 extends VectorInterpolator
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_64;
@Override
protected float vectorFilter(float[] samples, int offset, int index)
{
FloatVector accumulator = FloatVector.zero(VECTOR_SPECIES);
FloatVector tapsVector, sampleVector;
tapsVector = FloatVector.fromArray(VECTOR_SPECIES, TAPS[index], 0);
sampleVector = FloatVector.fromArray(VECTOR_SPECIES, samples, offset);
accumulator = tapsVector.fma(sampleVector, accumulator);
tapsVector = FloatVector.fromArray(VECTOR_SPECIES, TAPS[index], 2);
sampleVector = FloatVector.fromArray(VECTOR_SPECIES, samples, offset + 2);
accumulator = tapsVector.fma(sampleVector, accumulator);
tapsVector = FloatVector.fromArray(VECTOR_SPECIES, TAPS[index], 4);
sampleVector = FloatVector.fromArray(VECTOR_SPECIES, samples, offset + 4);
accumulator = tapsVector.fma(sampleVector, accumulator);
tapsVector = FloatVector.fromArray(VECTOR_SPECIES, TAPS[index], 6);
sampleVector = FloatVector.fromArray(VECTOR_SPECIES, samples, offset + 6);
accumulator = tapsVector.fma(sampleVector, accumulator);
return accumulator.reduceLanes(VectorOperators.ADD);
}
}

View File

@@ -0,0 +1,52 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.dsp.filter.interpolator;
/**
* Linear interpolator.
*/
public class LinearInterpolator
{
/**
* Calculates an interpolated value between x1 and x2 at a linear position mu between 0.0 and 1.0
* @param x1 first value
* @param x2 second value
* @param mu offset between first and second values in range 0.0 to 1.0
* @return interpolated value.
*/
public static float calculate(float x1, float x2, float mu)
{
if(mu < 0)
{
return x1;
}
else if(mu > 1)
{
return x2;
}
return x1 + ((x2 - x1) * mu);
}
public static float calculate(float x1, float x2, double mu)
{
return calculate(x1, x2, (float)mu);
}
}

View File

@@ -0,0 +1,50 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.dsp.filter.interpolator;
import org.apache.commons.lang3.Validate;
/**
* Base vector interpolator implementation.
*/
public abstract class VectorInterpolator extends Interpolator
{
@Override
public float filter(float[] samples, int offset, float mu)
{
/* Ensure we have enough samples in the array */
Validate.isTrue(samples.length >= offset + 7, "Offset [" + offset + "] must be 7 less than length[" + samples.length + "]");
//Identify the filter bank that corresponds to mu. Note: since we're not loading the TAPS in reverse order,
//we select the inverse tap index that has the mirrored set of taps, by subtracting from 127.
int index = 127 - (int)(NSTEPS * mu);
return vectorFilter(samples, offset, index);
}
/**
* Perform vectorized interpolation to find the interpolated value from the samples where the 8-value sequence
* starts at the offset into the array, using the filter coefficients specified by the index value.
* @param samples to interpolate
* @param offset to the start of the 8-value sequence
* @param index into the TAPS coefficients array.
* @return interpolated value.
*/
protected abstract float vectorFilter(float[] samples, int offset, int index);
}

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