Compare commits

...

97 Commits

Author SHA1 Message Date
Denny Sheirer
02363c3113 Merge pull request #1690 from DSheirer/1689-harris-patch-group-prefixes
#1689 Increment Build 0.6.0 Beta 3 and Remove TG/RA prefixes for Harris Patch Groups
2023-11-04 07:13:10 -04:00
Dennis Sheirer
cfaac0fcbc #1689 Increment Build 0.6.0 Beta 3 and Remove TG/RA prefixes for Harris patch groups. 2023-11-04 07:12:29 -04:00
Denny Sheirer
bee0405e00 Merge pull request #1688 from DSheirer/1687-rtl2832-fc0013
Support For RTL2832 With Embedded FC0013 Tuner
2023-11-04 06:54:55 -04:00
Dennis Sheirer
50bdf0ee81 #1687 Adds support for RTL2832 with embedded FC0013 tuner. 2023-11-04 06:52:37 -04:00
Denny Sheirer
a27ddbe0d0 Merge pull request #1679 from DSheirer/1643-r828d-v4-tuner
#1643 RTL2832/R828D aka RTL-SDR V4 Dongle & Bias-T Support
2023-10-22 08:40:33 -04:00
Dennis Sheirer
0d652b9153 #1643 Adds support for RTL2832 with embedded R828D tuner and RTL-SDR.com V4 dongles. Introduces Bias-T support for all RTL-2832 dongles. 2023-10-22 08:38:16 -04:00
Denny Sheirer
9468fd189b Merge pull request #1677 from DSheirer/1607-harris-p25-patch-groups
#1607 P25 P1/P2 L3Harris Patch Group Support
2023-10-21 07:00:41 -04:00
sheirerd
215737ad9b #1607 Add support for L3Harris dynamic regroup/patch messages and events. Includes parsing for L3Harris unknown opcodes 10, 42 and 43. 2023-10-21 06:58:11 -04:00
Denny Sheirer
123c25b142 Merge pull request #1672 from DSheirer/1650-fm-decoder-7_5-bandwidth
#1650 Adds 7.5 kHz channel bandwidth option for FM channels.
2023-10-15 09:22:48 -04:00
Dennis Sheirer
af56198f0e #1650 Adds 7.5 kHz channel bandwidth option for FM channels. 2023-10-15 09:21:38 -04:00
Denny Sheirer
3c73fdfd6e Merge pull request #1671 from DSheirer/1670-dmr-cap-plus-missing-call-audio
#1670 Missing Audio Segments - DMR Cap+
2023-10-15 08:56:48 -04:00
Dennis Sheirer
b391ebbb5f #1670 Resolves missing audio segments for some DMR Cap+ call events. 2023-10-15 08:55:35 -04:00
Denny Sheirer
bad5a1f9f0 Merge pull request #1669 from DSheirer/1668-message-viewer-p25-support
#1668 Message Recording Viewer - P25P1 Support
2023-10-15 07:17:06 -04:00
Dennis Sheirer
a7470b463f #1668 Updates message recording (.bits) viewer to support P25 Phase 1. 2023-10-15 07:14:53 -04:00
Denny Sheirer
d6c706cb35 Merge pull request #1667 from DSheirer/1666-p25-moto-unit-gps
#1666 P25 Motorola Unit GPS Parsing & Plot to Map
2023-10-14 08:16:48 -04:00
Dennis Sheirer
a0d6950c75 #1666 P25 Motorola unit self-reported GPS location parsing and plotting to map. 2023-10-14 08:15:35 -04:00
Denny Sheirer
2495200b36 Merge pull request #1663 from DSheirer/1648-dmr-lrrp-not-plotting-map
#1648 DMR Embedded GPS & LRRP Plotting
2023-10-05 03:32:08 -04:00
Dennis Sheirer
bfe7ae0b1b #1648 Resolves issue with plotting DMR embedded GPS and LRRP positions. 2023-10-05 03:31:17 -04:00
Denny Sheirer
4912abbf07 Merge pull request #1662 from DSheirer/1661-dmr-gps-location-parsing-error
#1661 DMR Talker GPS Lat/Long Values Incorrect
2023-10-05 03:21:24 -04:00
Dennis Sheirer
2f2998d02e #1661 Resolves issue with parsing of DMR embedded GPS latitude and longitude values. 2023-10-05 03:20:17 -04:00
Dennis Sheirer
c96349fa1c Updates version to 0.6.0 Beta 2 2023-08-31 07:14:58 -04:00
Denny Sheirer
f640576cc3 Merge pull request #1646 from DSheirer/1637-dmr-cap-plus-and-max-trunking
#1637 DMR Cap+/CapMax Channel Frequency Map / Trunking Issue
2023-08-31 07:10:21 -04:00
Dennis Sheirer
77d725a488 #1637 Resolves issue with DMR Cap+/CapMax trunking channel to frequency map. Updates the DMR channel map to use LCN instead of LSN and aligns standard DMR with Tier-III DMR for channel identification.
Warning: breaking change.  Users will need to either recreate their DMR channel to frequency maps or simply delete and reload the channel from Radio Reference to correct the channel to frequency map.

Updates the DMR Decoder State to better track and update DMR events versus creating a new event and causing lots of scrolling in the event window of the Now Playing tab.
2023-08-31 07:06:50 -04:00
Denny Sheirer
2c0305f928 Merge pull request #1642 from DSheirer/1641-hytera-rrs-packets
#1641 DMR Hytera RRS packet support.
2023-08-27 07:47:14 -04:00
Dennis Sheirer
21fd61d507 #1641 DMR Hytera RRS packet support. 2023-08-27 07:44:43 -04:00
Denny Sheirer
8fb56a5342 Merge pull request #1640 from DSheirer/1638-hytera-encryption-parameters
#1638 DMR Decoder - Encryption Parameters & Hytera RRS
2023-08-26 07:36:31 -04:00
Dennis Sheirer
5d7b73f876 #1638 Enhances DMR decoder to include additional encryption details from two major vendor implementations. Adds Hytera short data Radio Registration Service (RRS) processing. 2023-08-26 07:32:46 -04:00
Dennis Sheirer
85b2a38058 Version 0.6.0 Beta 1 2023-08-19 05:16:20 -04:00
Denny Sheirer
c78517ee75 Merge pull request #1631 from DSheirer/1630-flc-arc4-encryption-parameters
#1630 DMR FLC ARC4/EP Encryption Parameters
2023-08-18 07:08:30 -04:00
Dennis Sheirer
ae116d3dfe #1630 Updates ARC4 FLC encryption parameters message to detect and parse both Hytera & Motorola message formats. 2023-08-18 07:06:26 -04:00
Denny Sheirer
93252f9a40 Merge pull request #1629 from DSheirer/1628-libusb-device-list-error
#1628 LibUsb Error -99 During Device Discovery
2023-08-18 03:57:50 -04:00
Dennis Sheirer
3c03a4db2e #1628 Handle libusb get device list error condition and retry with increased logging during error code -99 to determine what causes this error condition, primarily on some Linux systems. 2023-08-18 03:55:42 -04:00
Denny Sheirer
d0fcd617aa Merge pull request #1627 from DSheirer/1626-dmr-call-events-missing-timeslot
#1626 Updates DMR call events to include timeslot and adds DMR encryption info when available.
2023-08-15 04:01:52 -04:00
Dennis Sheirer
6ca0decbd6 #1626 Updates DMR call events to include timeslot and adds DMR encryption information when available. 2023-08-15 04:01:27 -04:00
Denny Sheirer
ddd9ca933e Merge pull request #1624 from DSheirer/1623-unknown-opcode-message-filter
#1623 P25 Message Filter Set - Unknown Vendor Opcode
2023-08-14 04:04:30 -04:00
Dennis Sheirer
b2cc29569e #1623 resolves issue with handling of unknown P25 trunking opcode in message filter set. 2023-08-14 04:03:06 -04:00
Denny Sheirer
411735772b Merge pull request #1622 from DSheirer/dmr-cap-plus-identifier-updates
#1618 DMR Decoder Message Enhancements
2023-08-13 08:38:12 -04:00
sheirerd
2ffb2963af #1618 Update Cap+ radio & group parsing, process TXI & voice frame encryption parameters, and many other changes to the DMR message processing. 2023-08-13 08:30:23 -04:00
Denny Sheirer
227c88c8a2 Merge pull request #1614 from DSheirer/1612-dcs-alias-identifier
#1612 Enhance aliasing to support DCS tones
2023-07-24 04:34:46 -04:00
Dennis Sheirer
cbe9c79498 #1612 Enhance aliasing to support DCS tones 2023-07-24 04:30:16 -04:00
Denny Sheirer
1b8c100b43 Merge pull request #1611 from DSheirer/1605-tuner-editor-fails-to-display
#1605 Blank Tuner Editor Panel on Certain Linux Configurations
2023-07-22 06:00:38 -04:00
Dennis Sheirer
30c5fd64f2 #1605 Resolves issue where certain Linux users were unable to display the tuner editor panel due to an issue with the frequency control editor's usage of custom cursor. 2023-07-22 05:55:43 -04:00
Denny Sheirer
3a6b486340 Merge pull request #1610 from DSheirer/1604-event-panel-row-layout
#1604 Event Panel UI Layout Creates Excess Space On Resizing
2023-07-22 05:25:11 -04:00
Dennis Sheirer
ded7335dbf #1604 Resolves issue with event panel UI layout creating excess space between history management panel and events table. 2023-07-22 05:21:26 -04:00
Denny Sheirer
005a4f8119 Merge pull request #1608 from DSheirer/1603-duplicate-events
#1603 De-Duplicate Call Events
2023-07-22 05:06:11 -04:00
Dennis Sheirer
1ac49bfa7f #1603 Resolves issue with de-duplicating events to allow call duration values to display correctly and minimize the quantity of duplicate events. 2023-07-22 04:57:26 -04:00
Denny Sheirer
c302c4d121 Merge pull request #1601 from DSheirer/1368-decode-event-filtering-2
1368 Enhance Message & Event Filtering Options
2023-07-14 06:51:57 -04:00
Dennis Sheirer
b0a524cf62 #1368 Restructures Message and Event filter framework. 2023-07-14 06:50:07 -04:00
Christopher Varner
d246ffff30 Adds event type as a filtering option in the Events tab 2023-07-03 05:56:50 -04:00
Denny Sheirer
adfc8b2d6f #1586 Resolves issue in P25 message framer where after transmitting the current message the mDataUnitId is set to null and this causes an NPE when subsequent code is executed prior to shutdown of the channel. (#1593)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-07-03 05:11:20 -04:00
Denny Sheirer
ad2ab08466 #1588 Use JDK20 and Gradle 8.2. Updates SDRPlay API for compatibility with JDK20 Project Panama Foreign Memory/Function changes. (#1589)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-07-02 06:42:27 -04:00
Denny Sheirer
f3369410b2 #1590 Resolves issue with RSP tuners generating too many native buffers causing excessive memory consumption (#1591)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-07-02 06:23:00 -04:00
Denny Sheirer
bac38a5bd8 #1584 Updates DMR & P25 decoders to stop processing residual samples from the current buffer after a traffic channel shutdown has been signalled. (#1585)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-06-25 03:56:08 -04:00
Denny Sheirer
e5f5df74ad #1563 Updates RSP tuner to produce sample buffers with optimal power-of-2 length to enable downstream vectorized operations. (#1583)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-06-24 09:09:30 -04:00
Denny Sheirer
b3837f581a #1581 Updates Rdio Scanner configuration editor to automatically append the 'api/call-upload' path to the end of the protocol and host value provided by the user. Sets the host default value to 'http://localhost' but allows the user to overwrite. (#1582)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-06-24 07:05:44 -04:00
Denny Sheirer
b2ab6f58e9 #1579 Resolves issue with array index out of bounds error generated in P25 Phase 1 decoders where the Dibit circular buffer attempts to write a (#1580)
dibit past the length of the array.  Issue likely occurs when a partial/incomplete put operation adds a dibit and increments the buffer pointer but the thread is interrupted prior to wrapping the buffer pointer.  When the next runnable iteration executes, the pointer is pointing past the end of the buffer.  Updates the code to check the pointer both before and after the put operation.

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-06-24 06:09:57 -04:00
Denny Sheirer
e04b59ba15 #1575 Updates traffic channel names to be 'T-' concatenated with the parent control channel name from the channel configuration. (#1578)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-06-24 05:15:27 -04:00
Denny Sheirer
366eacd234 #1573 Adds Digital Coded Squelch (DCS) auxiliary decoder support. (#1574)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-06-19 05:13:58 -04:00
Denny Sheirer
f792876c0a #1568 Updates NBFM decoder configuration so user can enable/disable high-pass filter option in the audio module to effect audio playback and audio recording of the demodulated audio. Disabling the filter allows recording of DC and sub-audible signalling that may be present in the demodulated audio (e.g. LTR or squelching signalling). (#1569)
Co-authored-by: sheirerd <sheirerd@ainfosec.com>
2023-06-05 07:19:16 -04:00
Denny Sheirer
19f7a5171b #1565 updates LTR, LTRNet and Passport decoders with new slope decoder. Resolves issue with LTR Standard decoder state that was combining individual calls to the same talkgroup and causing excessively long calls. (#1567)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-06-05 06:13:12 -04:00
Denny Sheirer
5b6377cc11 #1558 Resolves issue where heterodyne channelizer allows tuning a set of channels that exceed the tuner's bandwidth. (#1561)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-05-21 06:12:11 -04:00
Denny Sheirer
9502ca3069 #1547 Updates playlist alias editor to make add/delete operations more efficient. (#1560)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-05-21 04:54:26 -04:00
Denny Sheirer
77629000b2 #1548 Reduces default decoder AF gain by 40% to resolve issue with distorted audio from local strong aircraft transmissions. (#1559)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-05-21 04:16:51 -04:00
Nokoa
ca7916f992 added Rsp1TunerConfiguration to JsonSubTypes of RspTunerConfiguration (#1555)
Following reports that RSP1 tuner configurations are reset to default on startup. Unable to test
2023-05-20 07:45:05 -04:00
Denny Sheirer
9de83843a3 #1556 DMR Capacity Plus parsing enhancements. (#1557)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-05-20 07:44:07 -04:00
tadscottsmith
cbab7bb0c9 Adds native Rdio Scanner streaming. (#1) (#1476)
#1476 Adds rdio-scanner call upload API streaming support
---------

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-05-15 05:28:07 -04:00
wedgef5
3df8d60eed #1507 Resolves issue with Cap+ LSN parsing. Updates Cap+ Site Status and adds RAS bit handling. (#1536)
Co-authored-by: Matthew Foster <matthew.foster@noaa.gov>
2023-05-14 08:03:49 -04:00
Denny Sheirer
b39dd1a19f #648 Resolves issue with audio panel sometimes displaying stale metadata that doesn't clear until the next audio segment is loaded. (#1545)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-05-10 04:29:46 -04:00
Denny Sheirer
0af5963d08 #1528 Restores consistent audio segment start tone insertion, broken in #1506 (#1543)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-05-08 03:59:42 -04:00
Denny Sheirer
4f8b8e978b #35 Updates AM & NBFM decoders to use Adaptive Squelch, a power squelch that includes an adaptive noise floor monitor. Implements vectorized magnitude operations for AM & FM decoders. Adds talkgroup assignment and squelch controls to AM channel configuration editor. Implements new AM gain control that attempts to normalize audio gain quickly at beginning of call/transmission and maintains that gain across lifecycle of the call and resets the gain for the next call. (#1540)
Notes on usage:
- When using either AM or FM squelch control use either the Heterodyne channelizer or if using the Polyphase channelizer, avoid using trunk tracking decoders like DMR or P25 or set the squelch threshold high enough to avoid changes in noise floor from polyphase channelizer where it can vary between using the one channel or the two-channel combined channel sources.

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-05-07 07:27:44 -04:00
Dennis Sheirer
5654254cbb Revert "#1531 Updates version to 0.6.0 alpha 7 (#1532)"
This reverts commit 11f37e896a.
2023-04-24 04:39:20 -04:00
Denny Sheirer
11f37e896a #1531 Updates version to 0.6.0 alpha 7 (#1532)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-04-24 04:38:27 -04:00
Denny Sheirer
e8f563deeb #1529 Adds logging suppression to Broadcaster to limit total logged error messages. (#1530)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-04-24 04:36:02 -04:00
Denny Sheirer
f877354a11 #1455 Add Airspy HF+/Discovery Tuner Support (#1524)
* #1455 Add support for airspy hf+/discovery tuners

* #1455 testing updates

---------

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-04-15 07:18:37 -04:00
Denny Sheirer
df3d3b5ddc #1519 Adds null checking to channel results array to detect null on tuner shutdown. (#1520)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-04-13 04:31:46 -04:00
Denny Sheirer
59e74d70b4 #1515 Updates version to 0.6.0 alpha 5 (#1516)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-04-12 03:14:54 -04:00
Denny Sheirer
8d6b7c3ac8 #1511 Resolves issue with SDRPlay library crashes application when linux service is in stopped state. (#1514)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-04-11 04:32:58 -04:00
Denny Sheirer
372d2ff870 #1506 Resolves multiple performance issues: tuner freezing after a period of run time, buffer overflows, degraded audio playback. (#1513)
-Reduces message and event histories per channel from 500 to 200.
-Resolves issue of tuner freezing due to cancelled USB transfer buffers not being resubmitted.
-Reworks the audio playback system to remove in-progress call preemption and remove thread locks which can cause glitchy behavior during audio playback.
-Updates audio outputs to also use single thread pool for processing audio segments.
-Updates MBECallSequenceConverter to batch convert MBE to WAV audio.
-Unifies recording filename prefixes across baseband, mbe and binary recordings.
-Resolves issue with registering heartbeat manager to dispatcher.
-Uses single-thread thread pools for Dispatcher to support batch & process to overcome context switching consistency issue in Windows.

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-04-11 04:07:41 -04:00
Denny Sheirer
32a8363b45 Update issue templates
Updated issue templates.
2023-03-25 06:49:58 -04:00
Denny Sheirer
87f1f8986f Update issue templates
Adds issue template to prevent end-users from posting support questions in the issues/bug tracker.
2023-03-25 06:48:08 -04:00
Denny Sheirer
b3c08053fb Update issue templates
Adds feature request template.
2023-03-25 06:42:27 -04:00
Denny Sheirer
a5208dcf1e Update issue templates
Initial bug report template.
2023-03-25 06:40:11 -04:00
Denny Sheirer
2782953518 #1500 Makes Channel Auto-Start timeout a user-preference item to select in range 0-30 seconds. (#1501)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-03-21 04:32:28 -04:00
Denny Sheirer
366055145e #1498 Updates JMBE audio module to retain class reference instead of reflectively loading the class each time for a new instance. Reduces pressure on both the class loader and the garbage collector. (#1499)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-03-20 19:56:57 -04:00
Denny Sheirer
39ebdb47b1 #1491 Adds diagnostic logging to dispatcher to support troubleshooting polyphase channel manager's dispatch thread being blocked. (#1492)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-03-12 09:21:37 -04:00
Denny Sheirer
c788292db8 #1489 SDRPlay RSP tuner editors now lock sample rate combo when sourcing channels. (#1490)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-03-12 07:21:23 -04:00
Denny Sheirer
f0dc80437c #1487 Updates hot code classes to remove usage of reentrant locks for thread synchronization. (#1488)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-03-11 16:04:55 -05:00
Denny Sheirer
2d6c5c2331 #1485 Sets Blackman-Harris 7 As Default FFT Window (#1486)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-03-11 15:05:25 -05:00
Denny Sheirer
bc4e6cb5b0 #1473 Adds IF AGC Control for RSP tuners and Power Overload indication. Reverts changes to default MacOS library location in API. (#1484)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-03-11 14:58:58 -05:00
Denny Sheirer
d69ad46c10 #1471 Handling of unknown Hytera packet messages. (#1483)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-03-11 06:04:06 -05:00
Denny Sheirer
ba7b616f88 #1405 resolves issue with polyphase channel thread not shutting down correctly. Resolves issue with building distro releases using non-local JDKs. (#1477)
Co-authored-by: Denny Sheirer <sheirerd@ainfosec.com>
2023-03-05 05:55:01 -05:00
Denny Sheirer
8355b7e3b9 #1471 Adds support for Hytera Proprietary Long Data Service SMS messages. Resolves issue parsing Multi-Block CSBKs. (#1472)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-02-26 10:23:00 -05:00
Denny Sheirer
22ffb656be #1464 Reduces logging when SDRplay API is not installed on local machine. (#1465)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-02-20 12:10:17 -05:00
Denny Sheirer
de80d2a6c1 #1459 Updates gradle build to fix bug related to building releases for each OS (#1463)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-02-20 12:04:48 -05:00
Denny Sheirer
a320b4a24f #1461 Updates expected sdrplay library path for OSX (#1462)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-02-20 04:55:50 -05:00
Denny Sheirer
fc13777211 #1456 Resolves issue with shutdown when no sdrplay api is available. Version update to 0.6.0 alpha-2 (#1457)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-02-19 10:18:19 -05:00
Denny Sheirer
75347f9d0a #52 SDRPlay RSP tuner support. (#1453)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-02-19 08:21:54 -05:00
906 changed files with 66235 additions and 10596 deletions

38
.github/ISSUE_TEMPLATE/bug-report.md vendored Normal file
View File

@@ -0,0 +1,38 @@
---
name: Bug Report
about: Create a report to help us improve sdrtrunk
title: ''
labels: bug
assignees: ''
---
**sdrtrunk Version**
Indicate which sdrtrunk version you are using. Use 'master-branch' if you are running the latest code.
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Application Log**
If applicable, include either the full application log,or a snippet of the error logging.
**Desktop (optional - complete the following information):**
- OS: [e.g. iOS]
- CPU Cores: [e.g. 4 cores]
- RAM:[e.g. 16GB]
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: feature
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -0,0 +1,16 @@
---
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,13 +22,13 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Set up JDK 17
uses: actions/setup-java@v2
- name: Set up JDK 20
uses: actions/setup-java@v3
with:
java-version: '17'
java-version: '20'
distribution: 'liberica'
java-package: 'jdk+fx'
- name: Build with Gradle
uses: gradle/gradle-build-action@937999e9cc2425eddc7fd62d1053baf041147db7
uses: gradle/gradle-build-action@v2
with:
arguments: clean build

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_17" project-jdk-name="liberica-19.0.1.fx" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_20" default="true" project-jdk-name="liberica-20" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

View File

@@ -0,0 +1,36 @@
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:
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
5. Do this for all versions of the API.

View File

@@ -0,0 +1,229 @@
#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)
//#include "windows.h"
//#elif defined (__GNUC__)
typedef void *HANDLE;
//#endif
//#ifndef _SDRPLAY_DLL_QUALIFIER
//#if !defined(STATIC_LIB) && (defined(_M_X64) || defined(_M_IX86))
//#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.07)
// 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;
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);
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);
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

@@ -0,0 +1,78 @@
#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_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

@@ -0,0 +1,55 @@
#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

@@ -0,0 +1,51 @@
#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

@@ -0,0 +1,22 @@
#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

@@ -0,0 +1,35 @@
#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

@@ -0,0 +1,40 @@
#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

@@ -0,0 +1,45 @@
#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_60 20 // Number of LNA states when using AM Port 2 between 12 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 26 // 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

@@ -0,0 +1,21 @@
#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

@@ -0,0 +1,93 @@
#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

@@ -0,0 +1,230 @@
#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)
//#include "windows.h"
//#elif defined (__GNUC__)
typedef void *HANDLE;
//#endif
//
//#ifndef _SDRPLAY_DLL_QUALIFIER
//#if !defined(STATIC_LIB) && (defined(_M_X64) || defined(_M_IX86))
//#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.08)
// 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_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Open(void);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Close(void);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_ApiVersion(float *apiVer);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_LockDeviceApi(void);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_UnlockDeviceApi(void);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_GetDevices(sdrplay_api_DeviceT *devices, unsigned int *numDevs, unsigned int maxDevs);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_SelectDevice(sdrplay_api_DeviceT *device);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_ReleaseDevice(sdrplay_api_DeviceT *device);
// _SDRPLAY_DLL_QUALIFIER const char* sdrplay_api_GetErrorString(sdrplay_api_ErrT err);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrorInfoT* sdrplay_api_GetLastError(sdrplay_api_DeviceT *device);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_DisableHeartbeat(void); // Must be called before sdrplay_api_SelectDevice()
//
// // Device API function definitions
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_DebugEnable(HANDLE dev, sdrplay_api_DbgLvl_t enable);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_GetDeviceParams(HANDLE dev, sdrplay_api_DeviceParamsT **deviceParams);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Init(HANDLE dev, sdrplay_api_CallbackFnsT *callbackFns, void *cbContext);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Uninit(HANDLE dev);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Update(HANDLE dev, sdrplay_api_TunerSelectT tuner, sdrplay_api_ReasonForUpdateT reasonForUpdate, sdrplay_api_ReasonForUpdateExtension1T reasonForUpdateExt1);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_SwapRspDuoActiveTuner(HANDLE dev, sdrplay_api_TunerSelectT *currentTuner, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_SwapRspDuoDualTunerModeSampleRate(HANDLE dev, double *currentSampleRate, double newSampleRate);
// _SDRPLAY_DLL_QUALIFIER 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

@@ -0,0 +1,78 @@
#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_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

@@ -0,0 +1,55 @@
#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

@@ -0,0 +1,51 @@
#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

@@ -0,0 +1,22 @@
#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

@@ -0,0 +1,35 @@
#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

@@ -0,0 +1,40 @@
#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

@@ -0,0 +1,45 @@
#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_60 20 // Number of LNA states when using AM Port 2 between 12 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 26 // 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

@@ -0,0 +1,21 @@
#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

@@ -0,0 +1,93 @@
#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

@@ -0,0 +1,230 @@
#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

@@ -0,0 +1,79 @@
#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

@@ -0,0 +1,55 @@
#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

@@ -0,0 +1,51 @@
#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

@@ -0,0 +1,22 @@
#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

@@ -0,0 +1,35 @@
#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

@@ -0,0 +1,40 @@
#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

@@ -0,0 +1,46 @@
#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

@@ -0,0 +1,21 @@
#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

@@ -0,0 +1,93 @@
#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

@@ -1,6 +1,8 @@
import java.text.SimpleDateFormat
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -16,6 +18,29 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
/**
* 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+
*
* Scenario 1: run the application via gradle command line from the source code root directory:
* command: ./gradlew run
*
* Scenario 2: create an application release for the current operating system
* command: ./gradlew runtimeZipCurrent
* Note: release image is located in the /build/image/ directory
*
* Scenario 3: create releases for Linux and OSX operating systems using downloaded JDKs
* command: ./gradlew runtimeZipOthers
* Note: release images are located in the /build/image/ directory
*
* Scenario 4: create release for Windows operating system using downloaded JDK
* command: ./gradlew runtimeZipWindows
* Note: release image is located in the /build/image/ directory
*/
plugins {
id 'application'
id 'java'
@@ -28,8 +53,15 @@ repositories {
maven { url "https://jitpack.io" }
}
version = '0.5.1'
sourceCompatibility = '17'
//Note: this should match the GitLab release tag.
version = '0.6.0-beta-3'
//Java 20 is required for this version of the Project Panama preview/incubator feature
java {
toolchain {
languageVersion = JavaLanguageVersion.of(20)
}
}
sourceSets {
main.java.srcDirs 'src/main'
@@ -90,19 +122,46 @@ dependencies {
implementation 'pl.edu.icm:JLargeArrays:1.6'
}
def os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem
/**
* This is needed for the JDK17 vector API ... until it moves out of incubation
* This is needed for the JDK19 panama and 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
}
/**
* JVM Arguments for running the application on Windows.
*/
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
/**
* JVM Arguments for running the application on Linux.
*/
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
application {
mainClassName = "io.github.dsheirer.gui.SDRTrunk"
applicationDefaultJvmArgs =
['--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED', //Needed for controls-fx.jar
'--add-modules=jdk.incubator.vector', //Needed while Panama Vector API remains in incubator
'--add-exports=java.desktop/com.sun.java.swing.plaf.windows=ALL-UNNAMED'] //Windows Swing - jide-oss library
if(os.isWindows()) {
applicationDefaultJvmArgs = jvmArgsWindows
}
else {
applicationDefaultJvmArgs = jvmArgsLinux
}
}
jar {
@@ -111,7 +170,7 @@ jar {
'Implementation-Title' : 'sdrtrunk project',
'Implementation-Version' : archiveVersion,
'Created-By' : "Gradle ${gradle.gradleVersion}",
'Build-Timestamp' : new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()),
'Build-Timestamp' : new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()),
'Build-JDK' : "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']}",
'Build-OS' : "${System.properties['os.name']} (${System.properties['os.arch']} ${System.properties['os.version']}"
)
@@ -139,90 +198,158 @@ String targetWindowsX86_64 = 'windows-x86_64-v' + version
* author's development environment, but can be changed to your environment if you want to use this build pattern.
*/
String jdk_base = '/home/denny/java_jdks/'
String jdk_linux_aarch64 = jdk_base + 'linux-arm64/jdk-19.0.1-full'
String jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-19.0.1-full'
String jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-19.0.1-full.jdk'
String jdk_osx_aarch64 = jdk_base + 'osx-arm64/jdk-19.0.1-full.jdk'
String jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-19.0.1-full'
boolean hasTargetJdks = file(jdk_linux_x86_64).exists() || file(jdk_linux_aarch64).exists() ||
file(jdk_osx_x86_64).exists() || file(jdk_osx_aarch64).exists() || file(jdk_windows_x86_64).exists()
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'
/**
* Auto-download target JDKs. Defaults to false, or the user can override via the gradle command line by
* including this option: '-PdownloadJdks=true'
* Download URLs to download the JDK as part of the gradle build packaging process
*/
boolean downloadJdks = project.hasProperty('downloadJdks') ? project.getProperty('downloadJdks') : false
def jdk_download_base = "https://download.bell-sw.com/java/20.0.1+10/bellsoft-jdk20.0.1+10-"
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-aarch64" + jdk_download_suffix
def jdk_download_osx_aarch64 = jdk_download_base + "macos-amd64" + jdk_download_suffix
def jdk_download_windows_x86_64 = jdk_download_base + "windows-amd64-full.zip"
/**
* Beryx Runtime plugin for creating OS and CPU targeted build(s) of the software.
*
* Option 1: Create a single target build using the locally installed JDK, or optionally create target builds for
* all OS and CPU targets when pre-downloaded target JDKs are available (see 'jdk_*' properties above).
*
* >: gradle runtimeZip
*
* Option 2: Create all OS and CPU target builds by automatically downloading all JDKs needed to build the target images
*
* >: gradle runtimeZip -PdownloadJdks=true
* Configures the runtime zip task with additional options/settings.
* @param rt task to configure
* @param jvmArgs to use (windows or linux)
* @return configured task.
*/
runtime {
//Build by automatically downloading JDKs for the OS and CPU targets
if(downloadJdks) {
println("Building all runtime images")
println("The [ > jre: ] build phase downloads several target JDKs and this may take a while ... please wait")
targetPlatform(targetLinuxAarch64) {
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-linux-aarch64-full.tar.gz")
}
targetPlatform(targetLinuxX86_64) {
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-linux-amd64-full.tar.gz")
}
targetPlatform(targetOsxAarch64) {
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-macos-aarch64-full.tar.gz")
}
targetPlatform(targetOsxX86_64) {
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-macos-amd64-full.tar.gz")
}
targetPlatform(targetWindowsX86_64) {
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-windows-amd64-full.zip")
}
}
//Build using pre-downloaded JDKs
else if(hasTargetJdks) {
println("Building all runtime images using pre-downloaded JDKs")
if(file(jdk_linux_aarch64).exists()) {
targetPlatform(targetLinuxAarch64, jdk_linux_aarch64)
}
if(file(jdk_linux_x86_64).exists()) {
targetPlatform(targetLinuxX86_64, jdk_linux_x86_64)
}
if(file(jdk_osx_aarch64).exists()) {
targetPlatform(targetOsxAarch64, jdk_osx_aarch64)
}
if(file(jdk_osx_x86_64).exists()) {
targetPlatform(targetOsxX86_64, jdk_osx_x86_64)
}
if(file(jdk_windows_x86_64).exists()) {
targetPlatform(targetWindowsX86_64, jdk_windows_x86_64)
}
}
//Build using locally installed JDK only
else {
println("Building a single runtime image using locally installed JDK")
}
def configure(org.beryx.runtime.RuntimeZipTask rt, List<String> jvmArgs) {
//Use the correct set of JVM arguments
rt.extension.launcherData.get().jvmArgs = jvmArgs
//Additional modules to include with auto-detected modules.
//jdk.crypto.ec needed for HTTPS connections (broadcastify calls & map tile server)
//jdk.crypto.ec - needed for HTTPS connections (broadcastify calls & map tile server)
//jdk.incubator.vector - needed for Project Panama foreign function and vector apis
//jdk.accessibility is used with assistive technologies like screen readers
modules = ['jdk.crypto.ec','jdk.accessibility']
rt.extension.addModules('jdk.crypto.ec', 'jdk.incubator.vector', 'jdk.accessibility')
//Use auto-detected modules and 'add' any specified modules.
additive = true
rt.extension.additive.set(true)
//Recommended options for reducing/shrinking runtime target image size
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
rt.extension.addOptions('--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages')
//Runtime images have auto appended version in image zip name. Override to include version in single target image.
imageZip = (hasTargetJdks | downloadJdks) ? file("$buildDir/image/sdr-trunk.zip") :
file("$buildDir/image/sdr-trunk-" + version + ".zip")
rt.extension.imageZip.set(file("$buildDir/image/sdr-trunk.zip"))
}
/**
* Creates a release package for the current operating system.
*
* Note: you cannot create both Windows and Linux releases at the same time due to limitations of the Runtime plugin
*
* Usage: gradle runtimeZipCurrent
* Usage: gradlew runtimeZipCurrent (windows - using gradlew)
* Usage: ./gradlew runtimeZipCurrent (linux - using gradlew)
*/
tasks.register('runtimeZipCurrent', org.beryx.runtime.RuntimeZipTask) {rt ->
def arch = System.properties.getProperty("os.arch")
def javaHome = org.gradle.internal.jvm.Jvm.current().getJavaHome().toString()
if(os.isWindows()) {
rt.extension.targetPlatform(targetWindowsX86_64, javaHome)
configure(rt, jvmArgsWindows)
}
else if(os.isLinux())
{
if(arch.equals("amd64")) {
rt.extension.targetPlatform(targetLinuxX86_64, javaHome)
}
else if(arch.equals("aarch64")) {
rt.extension.targetPlatform(targetLinuxAarch64, javaHome)
}
configure(rt, jvmArgsLinux)
}
else if(os.isMacOsX())
{
if(arch.equals("amd64")) {
rt.extension.targetPlatform(targetOsxX86_64, javaHome)
}
else if(arch.equals("aarch64")) {
rt.extension.targetPlatform(targetOsxAarch64, javaHome)
}
configure(rt, jvmArgsLinux)
}
}
/**
* Creates a Windows release package.
*
* Note: you cannot create both Windows and Linux releases at the same time due to limitations of the Runtime plugin
*
* Usage: gradle runtimeZipWindows
* Usage: gradlew runtimeZipWindows (windows - using gradlew)
* Usage: ./gradlew runtimeZipWindows (linux - using gradlew)
*/
tasks.register('runtimeZipWindows', org.beryx.runtime.RuntimeZipTask) {rt ->
if(file(jdk_windows_x86_64).exists()) {
rt.extension.targetPlatform(targetWindowsX86_64, jdk_windows_x86_64, [])
}
else {
rt.extension.targetPlatform(targetWindowsX86_64) {
jdkHome = jdkDownload(jdk_download_windows_x86_64)
}
}
configure(rt, jvmArgsWindows)
}
/**
* Creates Linux & OSX release packages.
*
* Note: you cannot create both Windows and Linux releases at the same time due to limitations of the Runtime plugin
*
* Usage: gradle runtimeZipOthers
* Usage: gradlew runtimeZipOthers (windows - using gradlew)
* Usage: ./gradlew runtimeZipOthers (linux - using gradlew)
*/
tasks.register('runtimeZipOthers', org.beryx.runtime.RuntimeZipTask) {rt ->
//Linux aarch64
if(file(jdk_linux_aarch64).exists()) {
rt.extension.targetPlatform(targetLinuxAarch64, jdk_linux_aarch64, [])
}
else {
rt.extension.targetPlatform(targetLinuxAarch64) {
jdkHome = jdkDownload(jdk_download_linux_aarch64)
}
}
//Linux x86-64
if(file(jdk_linux_x86_64).exists()) {
rt.extension.targetPlatform(targetLinuxX86_64, jdk_linux_x86_64, [])
}
else {
rt.extension.targetPlatform(targetLinuxX86_64) {
jdkHome = jdkDownload(jdk_download_linux_x86_64)
}
}
//OSX aarch64
if(file(jdk_osx_aarch64).exists()) {
rt.extension.targetPlatform(targetOsxAarch64, jdk_osx_aarch64, [])
}
else {
rt.extension.targetPlatform(targetOsxAarch64) {
jdkHome = jdkDownload(jdk_download_osx_aarch64)
}
}
//Linux x86-64
if(file(jdk_osx_x86_64).exists()) {
rt.extension.targetPlatform(targetOsxX86_64, jdk_osx_x86_64, [])
}
else {
rt.extension.targetPlatform(targetOsxX86_64) {
jdkHome = jdkDownload(jdk_download_osx_x86_64)
}
}
configure(rt, jvmArgsLinux);
}

View File

@@ -1,6 +1,6 @@
#
# ******************************************************************************
# Copyright (C) 2014-2022 Dennis Sheirer
# 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
@@ -19,7 +19,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -19,22 +19,15 @@
package io.github.dsheirer.alias;
import io.github.dsheirer.alias.action.AliasAction;
import io.github.dsheirer.alias.action.AliasActionType;
import io.github.dsheirer.alias.action.beep.BeepAction;
import io.github.dsheirer.alias.action.clip.ClipAction;
import io.github.dsheirer.alias.action.script.ScriptAction;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.alias.id.dcs.Dcs;
import io.github.dsheirer.alias.id.esn.Esn;
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID;
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID;
import io.github.dsheirer.alias.id.legacy.mobileID.Min;
import io.github.dsheirer.alias.id.legacy.mpt1327.MPT1327ID;
import io.github.dsheirer.alias.id.legacy.nonrecordable.NonRecordable;
import io.github.dsheirer.alias.id.legacy.siteID.SiteID;
import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupID;
import io.github.dsheirer.alias.id.legacy.uniqueID.UniqueID;
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
import io.github.dsheirer.alias.id.priority.Priority;
import io.github.dsheirer.alias.id.radio.Radio;
@@ -45,11 +38,10 @@ import io.github.dsheirer.alias.id.status.UserStatusID;
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
import io.github.dsheirer.alias.id.tone.TonesID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AliasFactory
{
@@ -64,6 +56,11 @@ public class AliasFactory
BroadcastChannel copyBroadcast = new BroadcastChannel();
copyBroadcast.setChannelName(originalBroadcast.getChannelName());
return copyBroadcast;
case DCS:
Dcs originalDcs = (Dcs)id;
Dcs copyDcs = new Dcs();
copyDcs.setDCSCode(originalDcs.getDCSCode());
return copyDcs;
case ESN:
Esn originalESN = (Esn)id;
Esn copyESN = new Esn();
@@ -234,65 +231,4 @@ public class AliasFactory
return copy;
}
public static AliasID getAliasID(AliasIDType type)
{
switch(type)
{
case BROADCAST_CHANNEL:
return new BroadcastChannel();
case ESN:
return new Esn();
case FLEETSYNC:
return new FleetsyncID();
case LTR_NET_UID:
return new UniqueID();
case LOJACK:
return new LoJackFunctionAndID();
case MDC1200:
return new MDC1200ID();
case MIN:
return new Min();
case MPT1327:
return new MPT1327ID();
case NON_RECORDABLE:
return new NonRecordable();
case PRIORITY:
return new Priority();
case RADIO_ID:
return new Radio();
case RADIO_ID_RANGE:
return new RadioRange();
case RECORD:
return new Record();
case TALKGROUP:
return new Talkgroup();
case TALKGROUP_RANGE:
return new TalkgroupRange();
case SITE:
return new SiteID();
case STATUS:
return new UserStatusID();
case LEGACY_TALKGROUP:
return new LegacyTalkgroupID();
default:
throw new IllegalArgumentException("Unrecognized Alias ID type: " + type);
}
}
public static AliasAction getAliasAction(AliasActionType type)
{
switch(type)
{
case BEEP:
return new BeepAction();
case CLIP:
return new ClipAction();
case SCRIPT:
return new ScriptAction();
default:
throw new IllegalArgumentException("Unrecognized Alias Action type: " + type);
}
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -21,6 +21,7 @@ package io.github.dsheirer.alias;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.alias.id.dcs.Dcs;
import io.github.dsheirer.alias.id.esn.Esn;
import io.github.dsheirer.alias.id.priority.Priority;
import io.github.dsheirer.alias.id.radio.Radio;
@@ -32,6 +33,7 @@ import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
import io.github.dsheirer.alias.id.tone.TonesID;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.dcs.DCSIdentifier;
import io.github.dsheirer.identifier.esn.ESNIdentifier;
import io.github.dsheirer.identifier.patch.PatchGroup;
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
@@ -41,12 +43,8 @@ import io.github.dsheirer.identifier.status.UserStatusIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.identifier.tone.ToneIdentifier;
import io.github.dsheirer.identifier.tone.ToneSequence;
import io.github.dsheirer.module.decode.dcs.DCSCode;
import io.github.dsheirer.protocol.Protocol;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -57,6 +55,10 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* List of aliases that share the same alias list name and provides convenient methods for looking up alias
@@ -67,6 +69,7 @@ public class AliasList
private final static Logger mLog = LoggerFactory.getLogger(AliasList.class);
private Map<Protocol,TalkgroupAliasList> mTalkgroupProtocolMap = new EnumMap<>(Protocol.class);
private Map<Protocol,RadioAliasList> mRadioProtocolMap = new EnumMap<>(Protocol.class);
private Map<DCSCode,Alias> mDCSCodeAliasMap = new EnumMap<>(DCSCode.class);
private Map<String,Alias> mESNMap = new HashMap<>();
private Map<Integer,Alias> mUnitStatusMap = new HashMap<>();
private Map<Integer,Alias> mUserStatusMap = new HashMap<>();
@@ -178,6 +181,12 @@ public class AliasList
radioRangeAliasList.add(radioRange, alias);
break;
case DCS:
if(id instanceof Dcs dcs)
{
mDCSCodeAliasMap.put(dcs.getDCSCode(), alias);
}
break;
case ESN:
String esn = ((Esn)id).getEsn();
@@ -379,6 +388,8 @@ public class AliasList
}
break;
case PATCH_GROUP:
List<Alias> aliases = new ArrayList<>();
PatchGroupIdentifier patchGroupIdentifier = (PatchGroupIdentifier)identifier;
PatchGroup patchGroup = patchGroupIdentifier.getValue();
@@ -386,8 +397,6 @@ public class AliasList
if(patchGroupAliasList != null)
{
List<Alias> aliases = new ArrayList<>();
Alias alias = patchGroupAliasList.getAlias(patchGroup.getPatchGroup());
if(alias != null)
@@ -395,19 +404,36 @@ public class AliasList
aliases.add(alias);
}
for(TalkgroupIdentifier patchedGroup: patchGroup.getPatchedGroupIdentifiers())
for(TalkgroupIdentifier patchedTalkgroup: patchGroup.getPatchedTalkgroupIdentifiers())
{
Alias patchedAlias = patchGroupAliasList.getAlias(patchedGroup);
Alias patchedTalkgroupAlias = patchGroupAliasList.getAlias(patchedTalkgroup);
if(patchedAlias != null && !aliases.contains(patchedAlias))
if(patchedTalkgroupAlias != null && !aliases.contains(patchedTalkgroupAlias))
{
aliases.add(patchedAlias);
aliases.add(patchedTalkgroupAlias);
}
}
return aliases;
}
break;
if(patchGroup.hasPatchedRadios())
{
RadioAliasList radioAliasList = mRadioProtocolMap.get(patchGroupIdentifier.getProtocol());
if(radioAliasList != null)
{
for(RadioIdentifier patchedRadio: patchGroup.getPatchedRadioIdentifiers())
{
Alias patchedRadioAlias = radioAliasList.getAlias(patchedRadio);
if(patchedRadioAlias != null && !aliases.contains(patchedRadioAlias))
{
aliases.add(patchedRadioAlias);
}
}
}
}
return aliases;
case RADIO:
RadioIdentifier radio = (RadioIdentifier)identifier;
@@ -418,7 +444,7 @@ public class AliasList
return toList(radioAliasList.getAlias(radio));
}
break;
case ESN:
case DCS:
if(identifier instanceof ESNIdentifier)
{
return toList(getESNAlias(((ESNIdentifier)identifier).getValue()));
@@ -439,9 +465,9 @@ public class AliasList
}
break;
case TONE:
if(identifier instanceof ToneIdentifier)
if(identifier instanceof ToneIdentifier toneIdentifier)
{
ToneSequence toneSequence = ((ToneIdentifier)identifier).getValue();
ToneSequence toneSequence = toneIdentifier.getValue();
if(toneSequence != null && toneSequence.hasTones())
{
@@ -454,6 +480,15 @@ public class AliasList
}
}
}
else if(identifier instanceof DCSIdentifier dcsIdentifier)
{
DCSCode dcsCode = dcsIdentifier.getValue();
if(dcsCode != null)
{
return toList(mDCSCodeAliasMap.get(dcsCode));
}
}
break;
}
}
@@ -465,9 +500,7 @@ public class AliasList
{
if(alias != null)
{
List<Alias> aliases = new ArrayList<>();
aliases.add(alias);
return aliases;
return Collections.singletonList(alias);
}
return Collections.emptyList();

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -23,17 +23,16 @@ import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Alias Model contains all aliases and is responsible for creation and management of alias lists. Alias lists are a
@@ -184,10 +183,8 @@ public class AliasModel
*/
public void addAliases(List<Alias> aliases)
{
for(Alias alias : aliases)
{
addAlias(alias);
}
removeAliases(aliases);
mAliases.addAll(aliases);
}
/**
@@ -241,6 +238,26 @@ public class AliasModel
}
}
/**
* Removes the list of aliases from this model and any alias lists that might contain each alias.
* @param aliases
*/
public void removeAliases(List<Alias> aliases)
{
if(aliases != null && !aliases.isEmpty())
{
mAliases.removeAll(aliases);
for(Alias alias: aliases)
{
if(hasAliasList(alias.getAliasListName()))
{
getAliasList(alias.getAliasListName()).removeAlias(alias);
}
}
}
}
/**
* Retrieves all aliases that match the alias list and have at least one alias ID of the specified type
* @param aliasListName to search

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.alias.id;
@@ -26,6 +23,7 @@ import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.alias.id.dcs.Dcs;
import io.github.dsheirer.alias.id.esn.Esn;
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID;
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID;
@@ -54,6 +52,7 @@ import javafx.util.Callback;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = BroadcastChannel.class, name = "broadcastChannel"),
@JsonSubTypes.Type(value = Dcs.class, name = "dcs"),
@JsonSubTypes.Type(value = Esn.class, name = "esn"),
@JsonSubTypes.Type(value = FleetsyncID.class, name = "fleetsyncID"),
@JsonSubTypes.Type(value = LegacyTalkgroupID.class, name = "talkgroupID"),

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.alias.id;
@@ -26,6 +23,7 @@ import java.util.EnumSet;
public enum AliasIDType
{
BROADCAST_CHANNEL("Audio Broadcast Channel"),
DCS("Digital Coded Squelch (DCS)"),
ESN("ESN"),
INVERT("Audio Inversion"),
LOJACK("LoJack"),

View File

@@ -0,0 +1,111 @@
/*
* *****************************************************************************
* 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.alias.id.dcs;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.module.decode.dcs.DCSCode;
/**
* Digital Coded Squelch (DCS) tone identifier
*/
public class Dcs extends AliasID implements Comparable<Dcs>
{
private DCSCode mDCSCode;
@Override
public AliasIDType getType()
{
return AliasIDType.DCS;
}
@Override
public boolean matches(AliasID id)
{
if(isValid() && id instanceof Dcs other)
{
return other.isValid() && getDCSCode().equals(other.getDCSCode());
}
return false;
}
/**
* DCS code
* @return DCS code or null.
*/
@JacksonXmlProperty(isAttribute = true, localName = "code")
public DCSCode getDCSCode()
{
return mDCSCode;
}
/**
* Sets the DCS code value.
* @param dcsCode to set
*/
public void setDCSCode(DCSCode dcsCode)
{
mDCSCode = dcsCode;
updateValueProperty();
}
@Override
public boolean isValid()
{
return mDCSCode != null;
}
@Override
public boolean isAudioIdentifier()
{
return false;
}
@Override
public String toString()
{
if(isValid())
{
return getDCSCode().toString();
}
return "DCS-Invalid - No Tone Selected";
}
@Override
public int compareTo(Dcs o)
{
if(isValid())
{
if(o.isValid())
{
return getDCSCode().compareTo(o.getDCSCode());
}
else
{
return 1;
}
}
return -1;
}
}

View File

@@ -34,6 +34,8 @@ import io.github.dsheirer.protocol.Protocol;
*/
public enum TalkgroupFormat
{
AM("*****", 1, 0xFFFF, "1 to 65,535",
"AM valid value range is 1-65,535"),
APCO25("********", 0, 0xFFFF, "0 to 65,535",
"<html>APCO25 talkgroup valid range is 0 to 65,535"),
DMR("********", 0, 0xFFFFFF, "0 to 16,777,215",
@@ -122,6 +124,8 @@ public enum TalkgroupFormat
switch(protocol)
{
case AM:
return AM;
case APCO25:
return APCO25;
case FLEETSYNC:

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
* *****************************************************************************
* 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
@@ -15,7 +14,7 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.alias.id.talkgroup;
@@ -23,21 +22,20 @@ package io.github.dsheirer.alias.id.talkgroup;
import io.github.dsheirer.preference.identifier.IntegerFormat;
import io.github.dsheirer.preference.identifier.talkgroup.APCO25TalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.AbstractIntegerFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.AnalogTalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.DMRTalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.FleetsyncTalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.LTRTalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.MDC1200TalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.MPT1327TalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.NBFMTalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.PassportTalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.UnknownTalkgroupFormatter;
import io.github.dsheirer.protocol.Protocol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.ParseException;
import java.util.EnumMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Protocol-specific formatting of talkgroup values and parsing of formatted talkgroup values.
@@ -49,6 +47,10 @@ public class TalkgroupFormatter
static
{
AnalogTalkgroupFormatter analogTalkgroupFormatter = new AnalogTalkgroupFormatter();
mFormatterMap.put(Protocol.AM, analogTalkgroupFormatter);
mFormatterMap.put(Protocol.NBFM, analogTalkgroupFormatter);
mFormatterMap.put(Protocol.APCO25, new APCO25TalkgroupFormatter());
mFormatterMap.put(Protocol.DMR, new DMRTalkgroupFormatter());
mFormatterMap.put(Protocol.FLEETSYNC, new FleetsyncTalkgroupFormatter());
@@ -57,7 +59,6 @@ public class TalkgroupFormatter
mFormatterMap.put(Protocol.LTR_NET, ltr);
mFormatterMap.put(Protocol.MDC1200, new MDC1200TalkgroupFormatter());
mFormatterMap.put(Protocol.MPT1327, new MPT1327TalkgroupFormatter());
mFormatterMap.put(Protocol.NBFM, new NBFMTalkgroupFormatter());
mFormatterMap.put(Protocol.PASSPORT, new PassportTalkgroupFormatter());
mFormatterMap.put(Protocol.UNKNOWN, new UnknownTalkgroupFormatter());
}

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* 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
@@ -15,7 +14,7 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.audio;
@@ -106,7 +105,7 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
* Gets the current audio segment, or creates a new audio segment as necessary and broadcasts it to any registered
* listener(s).
*/
protected AudioSegment getAudioSegment()
public AudioSegment getAudioSegment()
{
synchronized(this)
{
@@ -135,7 +134,7 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
}
}
protected void addAudio(float[] audioBuffer)
public void addAudio(float[] audioBuffer)
{
AudioSegment audioSegment = getAudioSegment();

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -43,6 +43,7 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
{
private static final Logger mLog = LoggerFactory.getLogger(AudioModule.class);
private static float[] sHighPassFilterCoefficients;
private final boolean mAudioFilterEnable;
static
{
@@ -70,27 +71,33 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
}
}
private IRealFilter mHighPassFilter = FilterFactory.getRealFilter(sHighPassFilterCoefficients);
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
private final IRealFilter mHighPassFilter = FilterFactory.getRealFilter(sHighPassFilterCoefficients);
private final SquelchStateListener mSquelchStateListener = new SquelchStateListener();
private SquelchState mSquelchState = SquelchState.SQUELCH;
/**
* Creates an Audio Module.
*
* @param aliasList for aliasing identifiers
* @param timeslot for this audio module
* @param maxAudioSegmentLength in milliseconds
* @param audioFilterEnable to enable or disable high-pass audio filter
*/
public AudioModule(AliasList aliasList, int timeslot, long maxAudioSegmentLength)
public AudioModule(AliasList aliasList, int timeslot, long maxAudioSegmentLength, boolean audioFilterEnable)
{
super(aliasList, timeslot, maxAudioSegmentLength);
mAudioFilterEnable = audioFilterEnable;
}
/**
* Creates an Audio Module.
* @param aliasList for aliasing identifiers
* @param audioFilterEnable to enable or disable high-pass audio filter
*/
public AudioModule(AliasList aliasList)
public AudioModule(AliasList aliasList, boolean audioFilterEnable)
{
super(aliasList);
mAudioFilterEnable = audioFilterEnable;
}
@Override
@@ -121,7 +128,11 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
{
if(mSquelchState == SquelchState.UNSQUELCH)
{
audioBuffer = mHighPassFilter.filter(audioBuffer);
if(mAudioFilterEnable)
{
audioBuffer = mHighPassFilter.filter(audioBuffer);
}
addAudio(audioBuffer);
}
}
@@ -141,11 +152,16 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
@Override
public void receive(SquelchStateEvent event)
{
mSquelchState = event.getSquelchState();
SquelchState squelchState = event.getSquelchState();
if(mSquelchState == SquelchState.SQUELCH)
if(mSquelchState != squelchState)
{
closeAudioSegment();
mSquelchState = squelchState;
if(mSquelchState == SquelchState.SQUELCH)
{
closeAudioSegment();
}
}
}
}

View File

@@ -1,18 +1,21 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2020 Dennis Sheirer
/*
* *****************************************************************************
* 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 free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.audio;
@@ -26,15 +29,6 @@ import io.github.dsheirer.identifier.IdentifierUpdateNotification;
import io.github.dsheirer.identifier.MutableIdentifierCollection;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
@@ -43,6 +37,14 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Audio segment containing all related metadata and a dynamic collection of audio packets. An audio segment can be
@@ -138,6 +140,15 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
return mComplete;
}
/**
* Indicates if this audio segment is completed.
* @return true if completed.
*/
public boolean isComplete()
{
return mComplete.get();
}
/**
* Duplicate call audio property. This flag is set to true whenever a duplicate call detection function detects
* an audio segment is a duplicate.

View File

@@ -24,6 +24,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallConfiguration;
import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerConfiguration;
import io.github.dsheirer.audio.broadcast.icecast.IcecastConfiguration;
import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2Configuration;
@@ -46,6 +47,7 @@ import java.net.SocketAddress;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = BroadcastifyCallConfiguration.class, name="broadcastifyCallConfiguration"),
@JsonSubTypes.Type(value = RdioScannerConfiguration.class, name="RdioScannerConfiguration"),
@JsonSubTypes.Type(value = IcecastConfiguration.class, name="icecastConfiguration"),
@JsonSubTypes.Type(value = ShoutcastV1Configuration.class, name="shoutcastV1Configuration"),
@JsonSubTypes.Type(value = ShoutcastV2Configuration.class, name="shoutcastV2Configuration"),

View File

@@ -22,6 +22,9 @@ import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallBroadcaster;
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallConfiguration;
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyFeedConfiguration;
import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerBroadcaster;
import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerConfiguration;
import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerFeedConfiguration;
import io.github.dsheirer.audio.broadcast.icecast.IcecastHTTPAudioBroadcaster;
import io.github.dsheirer.audio.broadcast.icecast.IcecastHTTPConfiguration;
import io.github.dsheirer.audio.broadcast.icecast.IcecastTCPAudioBroadcaster;
@@ -61,6 +64,9 @@ public class BroadcastFactory
case BROADCASTIFY_CALL:
return new BroadcastifyCallBroadcaster((BroadcastifyCallConfiguration)configuration,
inputAudioFormat, mp3Setting, aliasModel);
case RDIOSCANNER_CALL:
return new RdioScannerBroadcaster((RdioScannerConfiguration)configuration,
inputAudioFormat, mp3Setting, aliasModel);
case BROADCASTIFY:
return new IcecastTCPAudioBroadcaster((BroadcastifyFeedConfiguration) configuration,
inputAudioFormat, mp3Setting, aliasModel);
@@ -99,6 +105,8 @@ public class BroadcastFactory
{
case BROADCASTIFY_CALL:
return new BroadcastifyCallConfiguration(format);
case RDIOSCANNER_CALL:
return new RdioScannerConfiguration(format);
case BROADCASTIFY:
return new BroadcastifyFeedConfiguration(format);
case ICECAST_HTTP:

View File

@@ -31,6 +31,7 @@ public enum BroadcastServerType
BROADCASTIFY_CALL("Broadcastify Call", "images/broadcastify.png"),
ICECAST_HTTP("Icecast 2 (v2.4+)", "images/icecast.png"),
RDIOSCANNER_CALL("Rdio Scanner", "images/rdioscanner.png"),
ICECAST_TCP("Icecast (v2.3)", "images/icecast.png"),
SHOUTCAST_V1("Shoutcast v1.x", "images/shoutcast.png"),
SHOUTCAST_V2("Shoutcast v2.x", "images/shoutcast.png"),

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -38,9 +38,6 @@ 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.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
@@ -48,12 +45,13 @@ 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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Audio broadcaster to push completed audio recordings to the Broadcastify call push API.
@@ -475,7 +473,11 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
StringBuilder sb = new StringBuilder();
sb.append(patchGroup.getPatchGroup().getValue().toString());
for(TalkgroupIdentifier patched: patchGroup.getPatchedGroupIdentifiers())
for(TalkgroupIdentifier patched: patchGroup.getPatchedTalkgroupIdentifiers())
{
sb.append(",").append(patched.getValue());
}
for(RadioIdentifier patched: patchGroup.getPatchedRadioIdentifiers())
{
sb.append(",").append(patched.getValue());
}

View File

@@ -0,0 +1,56 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.audio.broadcast.rdioscanner;
/**
* HTTP headers used for posting to Rdio Scanner API
*/
public enum FormField
{
AUDIO("audio"),
AUDIO_NAME("audioName"),
AUDIO_TYPE("audioType"),
DATE_TIME("dateTime"),
FREQUENCIES("frequencies"),
FREQUENCY("frequency"),
KEY("key"),
PATCHES("patches"),
SOURCE("source"),
SOURCES("sources"),
SYSTEM("system"),
SYSTEM_LABEL("systemLabel"),
TALKGROUP_ID("talkgroup"),
TALKGROUP_GROUP("talkgroupGroup"),
TALKGROUP_LABEL("talkgroupLabel"),
TALKGROUP_TAG("talkgroupTag"),
TEST("test");
private String mHeader;
FormField(String header)
{
mHeader = header;
}
public String getHeader()
{
return mHeader;
}
}

View File

@@ -0,0 +1,615 @@
/*
* *****************************************************************************
* 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.audio.broadcast.rdioscanner;
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.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.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.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;
/**
* Audio broadcaster to push completed audio recordings to the Rdio Scanner call upload API.
*
*/
public class RdioScannerBroadcaster extends AbstractAudioBroadcaster<RdioScannerConfiguration>
{
private final static Logger mLog = LoggerFactory.getLogger(RdioScannerBroadcaster.class);
private static final String ENCODING_TYPE_MP3 = "mp3";
private static final String MULTIPART_TYPE = "multipart";
private static final String DEFAULT_SUBTYPE = "form-data";
private static final String MULTIPART_FORM_DATA = MULTIPART_TYPE + "/" + DEFAULT_SUBTYPE;
private Queue<AudioRecording> mAudioRecordingQueue = new LinkedTransferQueue<>();
private ScheduledFuture<?> mAudioRecordingProcessorFuture;
private HttpClient mHttpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.followRedirects(HttpClient.Redirect.NORMAL)
.connectTimeout(Duration.ofSeconds(20))
.build();
private long mLastConnectionAttempt;
private long mConnectionAttemptInterval = 5000; //Every 5 seconds
private AliasModel mAliasModel;
/**
* Constructs an instance of the broadcaster
* @param config to use
* @param aliasModel for access to aliases
*/
public RdioScannerBroadcaster(RdioScannerConfiguration config, InputAudioFormat inputAudioFormat,
MP3Setting mp3Setting, AliasModel aliasModel)
{
super(config);
mAliasModel = aliasModel;
}
/**
* Starts the audio recording processor thread
*/
@Override
public void start()
{
setBroadcastState(BroadcastState.CONNECTING);
String response = testConnection(getBroadcastConfiguration());
mLastConnectionAttempt = System.currentTimeMillis();
/**
* Rdio Scanner API does not currently expose a test method.
*/
if(response != null && response.toLowerCase().startsWith("incomplete call data: no talkgroup"))
{
setBroadcastState(BroadcastState.CONNECTED);
}
else
{
mLog.error("Error connecting to Rdio Scanner server on startup [" + response + "]");
setBroadcastState(BroadcastState.ERROR);
}
if(mAudioRecordingProcessorFuture == null)
{
mAudioRecordingProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new AudioRecordingProcessor(),
0, 500, TimeUnit.MILLISECONDS);
}
}
/**
* Stops the audio recording processor thread
*/
@Override
public void stop()
{
if(mAudioRecordingProcessorFuture != null)
{
mAudioRecordingProcessorFuture.cancel(true);
mAudioRecordingProcessorFuture = null;
dispose();
setBroadcastState(BroadcastState.DISCONNECTED);
}
}
/**
* Prepares for disposal
*/
@Override
public void dispose()
{
AudioRecording audioRecording = mAudioRecordingQueue.poll();
while(audioRecording != null)
{
audioRecording.removePendingReplay();
audioRecording = mAudioRecordingQueue.poll();
}
}
/**
* Indicates if this broadcaster continues to have successful connections to and transactions with the remote
* server. If there is a connectivity or other issue, the broadcast state is set to temporary error and
* the audio processor thread will persistently invoke this method to attempt a reconnect.
*
* Rdio Scanner does not have a test API endpoint, so we look for the incomplete call response.
*/
private boolean connected()
{
if(getBroadcastState() != BroadcastState.CONNECTED &&
(System.currentTimeMillis() - mLastConnectionAttempt > mConnectionAttemptInterval))
{
setBroadcastState(BroadcastState.CONNECTING);
String response = testConnection(getBroadcastConfiguration());
mLastConnectionAttempt = System.currentTimeMillis();
if(response != null && response.toLowerCase().startsWith("incomplete call data: no talkgroup"))
{
setBroadcastState(BroadcastState.CONNECTED);
}
else
{
setBroadcastState(BroadcastState.ERROR);
}
}
return getBroadcastState() == BroadcastState.CONNECTED;
}
@Override
public int getAudioQueueSize()
{
return mAudioRecordingQueue.size();
}
@Override
public void receive(AudioRecording audioRecording)
{
mAudioRecordingQueue.offer(audioRecording);
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_QUEUE_CHANGE));
}
/**
* Indicates if the audio recording is non-null and not too old, meaning that the age of the recording has not
* exceeded the max age value indicated in the broadcast configuration. Audio recordings that are too old will be
* deleted to ensure that the in-memory queue size doesn't blow up.
* @param audioRecording to test
* @return true if the recording is valid
*/
private boolean isValid(AudioRecording audioRecording)
{
return audioRecording != null && System.currentTimeMillis() - audioRecording.getStartTime() <=
getBroadcastConfiguration().getMaximumRecordingAge();
}
/**
* Processes any enqueued audio recordings. This method employs asynchronous
* interaction with the server, so multiple audio recording uploads can occur simultaneously.
*/
private void processRecordingQueue()
{
while(connected() && !mAudioRecordingQueue.isEmpty())
{
final AudioRecording audioRecording = mAudioRecordingQueue.poll();
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_QUEUE_CHANGE));
if(isValid(audioRecording) && audioRecording.getRecordingLength() > 0)
{
float durationSeconds = (float)(audioRecording.getRecordingLength() / 1E3f);
long timestampSeconds = (int)(audioRecording.getStartTime() / 1E3);
String talkgroup = getTo(audioRecording);
String radioId = getFrom(audioRecording);
Long frequency = getFrequency(audioRecording);
String patches = getPatches(audioRecording);
String talkgroupLabel = getTalkgroupLabel(audioRecording);
String talkgroupGroup = getTalkgroupGroup(audioRecording);
String systemLabel = getSystemLabel(audioRecording);
try
{
byte[] audioBytes = null;
try
{
audioBytes = Files.readAllBytes(audioRecording.getPath());
}
catch(IOException e)
{
mLog.error("Rdio Scanner API - audio recording file not found - ignoring upload");
}
if(audioBytes != null)
{
RdioScannerBuilder bodyBuilder = new RdioScannerBuilder();
bodyBuilder.addPart(FormField.KEY, getBroadcastConfiguration().getApiKey())
.addPart(FormField.SYSTEM, getBroadcastConfiguration().getSystemID())
.addFile(audioBytes)
.addPart(FormField.DATE_TIME, timestampSeconds)
.addPart(FormField.TALKGROUP_ID, talkgroup)
.addPart(FormField.SOURCE, radioId)
.addPart(FormField.FREQUENCY, frequency)
.addPart(FormField.TALKGROUP_LABEL, talkgroupLabel)
.addPart(FormField.TALKGROUP_GROUP, talkgroupGroup)
.addPart(FormField.SYSTEM_LABEL, systemLabel)
.addPart(FormField.PATCHES, patches);
HttpRequest fileRequest = HttpRequest.newBuilder()
.uri(URI.create(getBroadcastConfiguration().getHost()))
.header(HttpHeaders.CONTENT_TYPE, MULTIPART_FORM_DATA + "; boundary=" + bodyBuilder.getBoundary())
.header(HttpHeaders.USER_AGENT, "sdrtrunk")
.header(HttpHeaders.CONTENT_TYPE, "audio/mpeg")
.POST(bodyBuilder.build())
.build();
mHttpClient.sendAsync(fileRequest, HttpResponse.BodyHandlers.ofString())
.whenComplete((fileResponse, throwable1) -> {
if(throwable1 != null || fileResponse.statusCode() != 200)
{
if(throwable1 instanceof IOException || throwable1 instanceof CompletionException)
{
//We get socket reset exceptions occasionally when the remote server doesn't
//fully read our request and immediately responds.
setBroadcastState(BroadcastState.TEMPORARY_BROADCAST_ERROR);
mLog.error("Rdio Scanner API file upload fail [" +
fileResponse.statusCode() + "] response [" +
fileResponse.body() + "]");
}
else
{
setBroadcastState(BroadcastState.TEMPORARY_BROADCAST_ERROR);
mLog.error("Rdio Scanner API file upload fail [" +
fileResponse.statusCode() + "] response [" +
fileResponse.body() + "]");
}
incrementErrorAudioCount();
broadcast(new BroadcastEvent(RdioScannerBroadcaster.this,
BroadcastEvent.Event.BROADCASTER_ERROR_COUNT_CHANGE));
}
else
{
String fileResponseString = fileResponse.body();
if(fileResponseString.contains("Call imported successfully."))
{
incrementStreamedAudioCount();
broadcast(new BroadcastEvent(RdioScannerBroadcaster.this,
BroadcastEvent.Event.BROADCASTER_STREAMED_COUNT_CHANGE));
audioRecording.removePendingReplay();
}
else if(fileResponseString.contains("duplicate call rejected"))
{
//Rdio Scanner is telling us to skip audio upload - someone already uploaded it
audioRecording.removePendingReplay();
}
else
{
setBroadcastState(BroadcastState.TEMPORARY_BROADCAST_ERROR);
mLog.error("Rdio Scanner API file upload fail [" +
fileResponse.statusCode() + "] response [" +
fileResponse.body() + "]");
}
}
});
}
else
{
//Register an error for the file not found exception
mLog.error("Rdio Scanner API - upload file not found [" +
audioRecording.getPath().toString() + "]");
incrementErrorAudioCount();
broadcast(new BroadcastEvent(RdioScannerBroadcaster.this,
BroadcastEvent.Event.BROADCASTER_ERROR_COUNT_CHANGE));
audioRecording.removePendingReplay();
}
}
catch(Exception e)
{
mLog.error("Unknown Error", e);
setBroadcastState(BroadcastState.ERROR);
incrementErrorAudioCount();
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_ERROR_COUNT_CHANGE));
audioRecording.removePendingReplay();
}
}
}
//If we're not connected and there are recordings in the queue, check the recording at the head of the queue
// and start age-off once the recordings become too old. The recordings should be time ordered in the queue.
AudioRecording audioRecording = mAudioRecordingQueue.peek();
while(audioRecording != null)
{
if(isValid(audioRecording))
{
return;
}
else
{
//Remove the recording from the queue, remove a replay, and peek at the next recording in the queue
mAudioRecordingQueue.poll();
audioRecording.removePendingReplay();
incrementAgedOffAudioCount();
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_AGED_OFF_COUNT_CHANGE));
audioRecording = mAudioRecordingQueue.peek();
}
}
}
/**
* Creates a frequency value from the audio recording identifier collection.
*/
private static Long getFrequency(AudioRecording audioRecording)
{
Identifier identifier = audioRecording.getIdentifierCollection().getIdentifier(IdentifierClass.CONFIGURATION,
Form.CHANNEL_FREQUENCY, Role.ANY);
if(identifier instanceof ConfigurationLongIdentifier)
{
Long value = ((ConfigurationLongIdentifier)identifier).getValue();
if(value != null)
{
return value;
}
}
return Long.valueOf(0);
}
/**
* Creates a formatted string with the FROM identifier or uses a default of zero(0)
*/
private static String getFrom(AudioRecording audioRecording)
{
for(Identifier identifier: audioRecording.getIdentifierCollection().getIdentifiers(Role.FROM))
{
if(identifier instanceof RadioIdentifier)
{
return ((RadioIdentifier)identifier).getValue().toString();
}
}
return "0";
}
/**
* Creates a formatted string with the TO identifiers or uses a default of zero (0)
*
*/
private static String getTo(AudioRecording audioRecording)
{
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
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";
}
/**
* Creates a formatted string with the Talkgroup Label from the Audio Recording Alias
* If this is a PatchGroup we return only the first label as the primary talkgroup label.
*
*/
private String getTalkgroupLabel(AudioRecording audioRecording)
{
AliasList aliasList = mAliasModel.getAliasList(audioRecording.getIdentifierCollection());
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
StringBuilder sb = new StringBuilder();
if(identifier != null)
{
List<Alias> aliases = aliasList.getAliases(identifier);
if(!aliases.isEmpty())
{
sb.append(aliases.get(0));
}
}
return sb.toString();
}
/**
* Creates a formatted string with the Talkgroup Group from the Audio Recording Alias
* If this is a PatchGroup we return only the first group as the primary talkgroup group.
*
*/
private String getTalkgroupGroup(AudioRecording audioRecording)
{
AliasList aliasList = mAliasModel.getAliasList(audioRecording.getIdentifierCollection());
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
StringBuilder sb = new StringBuilder();
if(identifier != null)
{
List<Alias> aliases = aliasList.getAliases(identifier);
if(!aliases.isEmpty())
{
sb.append(aliases.get(0).getGroup());
}
}
return sb.toString();
}
/**
* Creates a formatted string with the System Label from the Audio Recording Alias
* If this is a PatchGroup we return only the first sytem as the primary talkgroup system.
*
*/
private String getSystemLabel(AudioRecording audioRecording)
{
List<Identifier> systems = audioRecording.getIdentifierCollection().getIdentifiers(Form.SYSTEM);
StringBuilder sb = new StringBuilder();
if(!systems.isEmpty())
{
sb.append(systems.get(0));
}
return sb.toString();
}
/**
* Creates a formatted string with the patched talkgroups
*/
public static String getPatches(AudioRecording audioRecording)
{
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
{
PatchGroup patchGroup = patchGroupIdentifier.getValue();
StringBuilder sb = new StringBuilder();
sb.append("[");
sb.append(patchGroup.getPatchGroup().getValue().toString());
for(TalkgroupIdentifier patched: patchGroup.getPatchedTalkgroupIdentifiers())
{
sb.append(",").append(patched.getValue());
}
for(RadioIdentifier patched: patchGroup.getPatchedRadioIdentifiers())
{
sb.append(",").append(patched.getValue());
}
sb.append("]");
return sb.toString();
}
return "[]";
}
/**
* Tests both the connection and configuration against the RdioScanner Call API service
* @param configuration containing API key and system id
* @return error string or null if test is successful
*/
public static String testConnection(RdioScannerConfiguration configuration)
{
HttpClient httpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_1_1)
.followRedirects(HttpClient.Redirect.NORMAL)
.connectTimeout(Duration.ofSeconds(20))
.build();
RdioScannerBuilder bodyBuilder = new RdioScannerBuilder();
bodyBuilder.addPart(FormField.KEY, configuration.getApiKey())
.addPart(FormField.SYSTEM, configuration.getSystemID())
.addPart(FormField.TEST, 1);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(configuration.getHost()))
.header(HttpHeaders.CONTENT_TYPE, MULTIPART_FORM_DATA + "; boundary=" + bodyBuilder.getBoundary())
.header(HttpHeaders.USER_AGENT, "sdrtrunk")
.header(HttpHeaders.ACCEPT, "*/*")
.POST(bodyBuilder.build())
.build();
HttpResponse.BodyHandler<String> responseHandler = HttpResponse.BodyHandlers.ofString();
try
{
HttpResponse<String> response = httpClient.send(request, responseHandler);
String responseBody = response.body();
return (responseBody != null ? responseBody : "(no response)") + " Status Code:" + response.statusCode();
}
catch(Exception e)
{
return e.getLocalizedMessage();
}
}
public class AudioRecordingProcessor implements Runnable
{
@Override
public void run()
{
processRecordingQueue();
}
}
public static void main(String[] args)
{
mLog.debug("Starting ...");
RdioScannerConfiguration config = new RdioScannerConfiguration();
config.setHost("https://api.RdioScanner.com/call-upload-dev");
config.setApiKey("c33aae37-8572-11ea-bd8b-0ecc8ab9ccec");
config.setSystemID(11);
String response = testConnection(config);
if(response == null)
{
mLog.debug("Test Successful!");
}
else
{
if(response.contains("1 Invalid-API-Key"))
{
mLog.error("Invalid API Key");
}
else if(response.contains("1 API-Key-Access-Denied"))
{
mLog.error("System ID not valid for API Key");
}
else
{
mLog.debug("Response: " + response);
}
}
mLog.debug("Finished!");
}
}

View File

@@ -0,0 +1,216 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.audio.broadcast.rdioscanner;
import java.net.http.HttpRequest;
import java.util.ArrayList;
import java.util.List;
import java.lang.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
* Builder for an HTTP body publisher to produce a RdioScanner call event
*/
public class RdioScannerBuilder
{
private static final String DASH_DASH = "--";
private static final String BOUNDARY = "--sdrtrunk-sdrtrunk-sdrtrunk";
private List<Part> mParts = new ArrayList<>();
private byte[] audioBytes = null;
/**
* Constructs an instance
*/
public RdioScannerBuilder()
{
}
/**
* Access the static multi-part boundary string
*/
public String getBoundary()
{
return BOUNDARY;
}
/**
* Adds a Audio bytes part to the call
*/
public RdioScannerBuilder addFile(byte[] value)
{
audioBytes = value;
return this;
}
/**
* Adds a string part to the call
*/
public RdioScannerBuilder addPart(FormField key, String value)
{
if(key != null && value != null)
{
mParts.add(new Part(key.getHeader(), value));
}
return this;
}
/**
* Adds a number part to the call
*/
public RdioScannerBuilder addPart(FormField key, Number value)
{
if(key != null && value != null)
{
mParts.add(new Part(key.getHeader(), value.toString()));
}
return this;
}
/**
* Creates a form-data item
*/
private static String formatPart(Part part, String boundary)
{
StringBuilder sb = new StringBuilder();
sb.append(DASH_DASH).append(boundary).append("\r\n");
sb.append("Content-Disposition: form-data; name=\"").append(part.mKey).append("\"\r\n\r\n");
sb.append(part.getValue()).append("\r\n");
return sb.toString();
}
/**
* Creates the audio file item
*/
private String formatFilePart(String boundary)
{
if(audioBytes == null)
{
return "";
}
StringBuilder sb= new StringBuilder();
sb.append(DASH_DASH).append(boundary).append("\r\n");
sb.append("Content-Disposition: form-data; name=\"").append("audio").append("\"\r\n\r\n");
return sb.toString();
}
/**
* Creates the boundary closing item
*/
private static String getClosingBoundary(String boundary)
{
StringBuilder sb = new StringBuilder();
sb.append(DASH_DASH).append(boundary).append(DASH_DASH).append("\r\n");
return sb.toString();
}
/**
* Creates a BodyPublisher for accessing the call form data
*/
public HttpRequest.BodyPublisher build()
{
StringBuilder sb = new StringBuilder();
for(Part part: mParts)
{
sb.append(formatPart(part, BOUNDARY));
}
sb.append(formatFilePart(BOUNDARY));
/**
* We need to create a ByteArray consisting of the Sting "parts" and the audio file bytes
*/
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try
{
outputStream.write(sb.toString().getBytes());
if(audioBytes != null)
{
outputStream.write(audioBytes);
}
sb = new StringBuilder();
sb.append("\r\n");
sb.append(getClosingBoundary(BOUNDARY));
outputStream.write(sb.toString().getBytes());
}
catch(IOException e)
{
//mLog.error("Rdio Scanner API - unable to create POST reqeust.");
}
return HttpRequest.BodyPublishers.ofByteArray(outputStream.toByteArray());
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
for(Part part: mParts)
{
sb.append(formatPart(part, BOUNDARY));
}
sb.append(getClosingBoundary(BOUNDARY));
return sb.toString();
}
/**
* Key:Value pair holder
*/
public class Part
{
private String mKey;
private String mValue;
/**
* Constructs a new part
* @param key value
* @param value item
*/
public Part(String key, String value)
{
mKey = key;
mValue = value;
}
public String getKey()
{
return mKey;
}
public String getValue()
{
return mValue;
}
}
}

View File

@@ -0,0 +1,135 @@
/*
* *****************************************************************************
* 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.audio.broadcast.rdioscanner;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
import io.github.dsheirer.audio.broadcast.BroadcastFormat;
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
* Streaming configuration for Rdio Scanner API.
*
* Note: this API is not a streaming audio service, rather a completed call push service. However, it fits nicely
* with the structure of the audio streaming subsystem in sdrtrunk
*/
public class RdioScannerConfiguration extends BroadcastConfiguration
{
private IntegerProperty mSystemID = new SimpleIntegerProperty();
private StringProperty mApiKey = new SimpleStringProperty();
/**
* Constructor for faster jackson
*/
public RdioScannerConfiguration()
{
this(BroadcastFormat.MP3);
}
/**
* Public constructor.
* @param format to use for audio recording (MP3)
*/
public RdioScannerConfiguration(BroadcastFormat format)
{
super(format);
//The parent class binds this property, so we unbind it and rebind it here
mValid.unbind();
mValid.bind(Bindings.and(Bindings.and(Bindings.greaterThan(mSystemID, 0), Bindings.isNotNull(mApiKey)),
Bindings.isNotNull(mHost)));
if(mHost.getValue() == null || mHost.getValue().isEmpty())
{
mHost.set("http://localhost");
}
}
/**
* System ID as a property
*/
public IntegerProperty systemIDProperty()
{
return mSystemID;
}
/**
* API key as a property
*/
public StringProperty apiKeyProperty()
{
return mApiKey;
}
/**
* API Key
*/
@JacksonXmlProperty(isAttribute = true, localName = "api_key")
public String getApiKey()
{
return mApiKey.get();
}
/**
* Sets the api key
* @param apiKey
*/
public void setApiKey(String apiKey)
{
mApiKey.setValue(apiKey);
}
/**
* System ID as provided by RdioScanner.com
*/
@JacksonXmlProperty(isAttribute = true, localName = "system_id")
public int getSystemID()
{
return mSystemID.get();
}
/**
* Sets the system ID provided by RdioScanner.com
*/
public void setSystemID(int systemID)
{
mSystemID.set(systemID);
}
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
@Override
public BroadcastServerType getBroadcastServerType()
{
return BroadcastServerType.RDIOSCANNER_CALL;
}
@Override
public BroadcastConfiguration copyOf()
{
RdioScannerConfiguration copy = new RdioScannerConfiguration();
copy.setSystemID(getSystemID());
return copy;
}
}

View File

@@ -0,0 +1,126 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.audio.broadcast.rdioscanner;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
import io.github.dsheirer.audio.broadcast.BroadcastFormat;
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import io.github.dsheirer.audio.broadcast.icecast.IcecastTCPConfiguration;
import io.github.dsheirer.rrapi.type.UserFeedBroadcast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RdioScannerFeedConfiguration extends IcecastTCPConfiguration
{
private final static Logger mLog = LoggerFactory.getLogger(RdioScannerFeedConfiguration.class);
private int mFeedID;
public RdioScannerFeedConfiguration()
{
//No-arg constructor for JAXB
this(BroadcastFormat.MP3);
}
/**
* RdioScanner configuration for Icecast 2.3.2 compatible servers
*
* @param format of output audio (MP3)
*/
public RdioScannerFeedConfiguration(BroadcastFormat format)
{
super(format);
setBitRate(16);
setChannels(1);
setSampleRate(8000);
setInline(true);
}
public static RdioScannerFeedConfiguration from(UserFeedBroadcast userFeedBroadcast)
{
RdioScannerFeedConfiguration config = new RdioScannerFeedConfiguration(BroadcastFormat.MP3);
config.setName(userFeedBroadcast.getDescription());
config.setHost(userFeedBroadcast.getHostname());
config.setMountPoint(userFeedBroadcast.getMount());
config.setFeedID(userFeedBroadcast.getFeedId());
config.setPassword(userFeedBroadcast.getPassword());
try
{
config.setPort(Integer.parseInt(userFeedBroadcast.getPort()));
}
catch(Exception e)
{
mLog.error("Error creating Rdio Scanner configuration from radio reference user feed instance");
}
return config;
}
@Override
public BroadcastConfiguration copyOf()
{
RdioScannerFeedConfiguration copy = new RdioScannerFeedConfiguration(getBroadcastFormat());
//Broadcast Configuration Parameters
copy.setName(getName());
copy.setHost(getHost());
copy.setPort(getPort());
copy.setInline(getInline());
copy.setPassword(getPassword());
copy.setDelay(getDelay());
copy.setEnabled(false);
//Icecast Configuration Parameters
copy.setUserName(getUserName());
copy.setMountPoint(getMountPoint());
copy.setDescription(getDescription());
copy.setGenre(getGenre());
copy.setPublic(isPublic());
copy.setURL(getURL());
//RdioScanner Configuration Parameters
copy.setFeedID(getFeedID());
return copy;
}
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
@Override
public BroadcastServerType getBroadcastServerType()
{
return BroadcastServerType.RDIOSCANNER_CALL;
}
@JacksonXmlProperty(isAttribute = true, localName = "feed_id")
public int getFeedID()
{
return mFeedID;
}
public void setFeedID(int feedID)
{
mFeedID = feedID;
}
}

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.audio.codec.mbe;
@@ -32,11 +29,6 @@ import io.github.dsheirer.message.IMessageListener;
import io.github.dsheirer.preference.PreferenceType;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.sample.Listener;
import jmbe.iface.IAudioCodec;
import jmbe.iface.IAudioCodecLibrary;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
@@ -44,15 +36,20 @@ import java.net.URLClassLoader;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import jmbe.iface.IAudioCodec;
import jmbe.iface.IAudioCodecLibrary;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class JmbeAudioModule extends AbstractAudioModule implements Listener<IMessage>, IMessageListener,
ISquelchStateListener
{
private static final Logger mLog = LoggerFactory.getLogger(JmbeAudioModule.class);
private static final String JMBE_AUDIO_LIBRARY = "JMBE";
private static List<String> mLibraryLoadStatusLogged = new ArrayList<>();
private static final List<String> mLibraryLoadStatusLogged = new ArrayList<>();
private IAudioCodec mAudioCodec;
private UserPreferences mUserPreferences;
private final UserPreferences mUserPreferences;
private static Class sLoadedJmbeAudioConverterClass;
public JmbeAudioModule(UserPreferences userPreferences, AliasList aliasList, int timeslot)
{
@@ -70,7 +67,7 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
mAudioCodec = null;
}
protected IAudioCodec getAudioCodec()
public IAudioCodec getAudioCodec()
{
return mAudioCodec;
}
@@ -110,38 +107,67 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
protected abstract String getCodecName();
/**
* Loads audio frame processing chain. Constructs an imbe targetdataline
* to receive the raw imbe frames. Adds an IMBE to 8k PCM format conversion
* stream wrapper. Finally, adds an upsampling (8k to 48k) stream wrapper.
* Loads JMBE audio converter library class and then instantiates new converter instances from the loaded class.
*/
protected void loadConverter()
{
IAudioCodec audioConverter = null;
Path path = mUserPreferences.getJmbeLibraryPreference().getPathJmbeLibrary();
if(path != null)
if(sLoadedJmbeAudioConverterClass == null)
{
try
Path path = mUserPreferences.getJmbeLibraryPreference().getPathJmbeLibrary();
if(path != null)
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
{
mLog.info("Loading JMBE library from [" + path.toString() + "]");
mLog.info("Loading JMBE library from [" + path + "]");
}
URLClassLoader childClassLoader = new URLClassLoader(new URL[]{path.toUri().toURL()},
this.getClass().getClassLoader());
Class classToLoad = Class.forName("jmbe.JMBEAudioLibrary", true, childClassLoader);
Object instance = classToLoad.getDeclaredConstructor().newInstance();
if(instance instanceof IAudioCodecLibrary)
try
{
IAudioCodecLibrary library = (IAudioCodecLibrary)instance;
URLClassLoader childClassLoader = new URLClassLoader(new URL[]{path.toUri().toURL()},
this.getClass().getClassLoader());
sLoadedJmbeAudioConverterClass = Class.forName("jmbe.JMBEAudioLibrary", true, childClassLoader);
}
catch(IllegalArgumentException iae)
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY + getCodecName()))
{
mLog.error("Couldn't load JMBE audio conversion library - " + iae.getMessage());
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY + getCodecName());
}
}
catch(MalformedURLException mue)
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
{
mLog.error("Couldn't load JMBE audio conversion library from path [" + path + "]");
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
}
}
catch(ClassNotFoundException e1)
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
{
mLog.error("Couldn't load JMBE audio conversion library - class not found");
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
}
}
}
}
if(sLoadedJmbeAudioConverterClass != null)
{
try
{
Object instance = sLoadedJmbeAudioConverterClass.getDeclaredConstructor().newInstance();
if(instance instanceof IAudioCodecLibrary library)
{
if((library.getMajorVersion() == 1 && library.getMinorVersion() >= 0 &&
library.getBuildVersion() >= 0) || library.getMajorVersion() >= 1)
library.getBuildVersion() >= 0) || library.getMajorVersion() >= 1)
{
audioConverter = library.getAudioConverter(getCodecName());
@@ -169,38 +195,6 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
}
}
}
catch(IllegalArgumentException iae)
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY + getCodecName()))
{
mLog.error("Couldn't load JMBE audio conversion library - " + iae.getMessage());
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY + getCodecName());
}
}
catch(NoSuchMethodException nsme)
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
{
mLog.error("Couldn't load JMBE audio conversion library - no such method exception");
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
}
}
catch(MalformedURLException mue)
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
{
mLog.error("Couldn't load JMBE audio conversion library from path [" + path + "]");
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
}
}
catch(ClassNotFoundException e1)
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
{
mLog.error("Couldn't load JMBE audio conversion library - class not found");
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
}
}
catch(InvocationTargetException ite)
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
@@ -225,6 +219,14 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
}
}
catch(NoSuchMethodException nsme)
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
{
mLog.error("Couldn't load JMBE audio conversion library - no such method exception");
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
}
}
}
else
{
@@ -235,13 +237,6 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
}
}
if(audioConverter != null)
{
mAudioCodec = audioConverter;
}
else
{
mAudioCodec = null;
}
mAudioCodec = audioConverter;
}
}

View File

@@ -1,27 +1,46 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.audio.codec.mbe;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.audio.AudioSegment;
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
import io.github.dsheirer.module.decode.p25.audio.P25P1AudioModule;
import io.github.dsheirer.module.decode.p25.audio.P25P1CallSequenceRecorder;
import io.github.dsheirer.module.decode.p25.audio.VoiceFrame;
import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier;
import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.record.AudioSegmentRecorder;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import jmbe.iface.IAudioCodec;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.DirectoryFileFilter;
import org.apache.commons.io.filefilter.SuffixFileFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -32,79 +51,130 @@ public class MBECallSequenceConverter
{
private final static Logger mLog = LoggerFactory.getLogger(MBECallSequenceConverter.class);
// public static void convert(Path input, Path output) throws IOException
// {
// InputStream inputStream = Files.newInputStream(input);
// ObjectMapper mapper = new ObjectMapper();
// MBECallSequence sequence = mapper.readValue(inputStream, MBECallSequence.class);
// convert(sequence, output);
// }
//
// public static void convert(MBECallSequence callSequence, Path outputPath)
// {
// if(callSequence == null || callSequence.isEncrypted())
// {
// throw new IllegalArgumentException("Cannot decode null or encrypted call sequence");
// }
//
// if(callSequence != null && !callSequence.isEncrypted())
// {
// ThumbDv.AudioProtocol protocol = ThumbDv.AudioProtocol.P25_PHASE2;
//
// AudioPacketWaveRecorder recorder = new AudioPacketWaveRecorder(outputPath);
// recorder.start();
//
// long delayMillis = 0;
//
// try(ThumbDv thumbDv = new ThumbDv(protocol, recorder))
// {
// thumbDv.start();
// for(VoiceFrame voiceFrame: callSequence.getVoiceFrames())
// {
// mLog.debug("Frame [" + voiceFrame.getFrame() + "] + Hex [" + AmbeResponse.toHex(voiceFrame.getFrameBytes()) + "]");
// thumbDv.decode(voiceFrame.getFrameBytes());
// delayMillis += 30;
// }
//
// if(delayMillis > 0)
// {
// delayMillis += 1000;
// try
// {
// Thread.sleep(delayMillis);
// }
// catch(InterruptedException ie)
// {
//
// }
// }
// }
// catch(IOException ioe)
// {
// mLog.error("Error", ioe);
// }
//
// recorder.stop(Paths.get(outputPath.toString().replace(".tmp", ".wav")), new WaveMetadata());
// }
// }
//
// public static void main(String[] args)
// {
// String mbe = "/home/denny/SDRTrunk/recordings/20190331085324_154250000_3_TS0_65084_6591007.mbe";
// String mbe = "/home/denny/SDRTrunk/recordings/20190331085324_154250000_2_TS1_65035.mbe";
//
// Path input = Paths.get(mbe);
// Path output = Paths.get(mbe.replace(".mbe", ".tmp"));
//
// mLog.info("Converting: " + mbe);
//
// try
// {
// MBECallSequenceConverter.convert(input, output);
// }
// catch(IOException ioe)
// {
// mLog.error("Error", ioe);
// }
// }
/**
* Converts the input MBE file to PCM audio and writes to the output wave file.
* @param input path to the MBE file.
* @param output path to write the WAVE recording.
* @throws IOException if there is an error.
*/
public static void convert(Path input, Path output) throws IOException
{
InputStream inputStream = Files.newInputStream(input);
ObjectMapper mapper = new ObjectMapper();
MBECallSequence sequence = mapper.readValue(inputStream, MBECallSequence.class);
convert(sequence, output);
}
public static void convert(MBECallSequence callSequence, Path outputPath)
{
if(callSequence == null || callSequence.isEncrypted())
{
throw new IllegalArgumentException("Cannot decode null or encrypted call sequence");
}
if(callSequence != null)
{
if(callSequence.getProtocol().equals(P25P1CallSequenceRecorder.PROTOCOL))
{
P25P1AudioModule audioModule = new P25P1AudioModule(new UserPreferences(), new AliasList("mbe generator"));
audioModule.setRecordAudio(true);
audioModule.start();
if(callSequence.getFromIdentifier() != null)
{
int from = 0;
try
{
from = Integer.parseInt(callSequence.getFromIdentifier());
audioModule.getIdentifierUpdateListener().receive(new IdentifierUpdateNotification(APCO25RadioIdentifier.createFrom(from),
IdentifierUpdateNotification.Operation.ADD, 0));
}
catch(Exception e)
{
mLog.error("Error parsing from identifier from value [" + callSequence.getFromIdentifier());
}
}
if(callSequence.getToIdentifier() != null)
{
int to = 0;
try
{
to = Integer.parseInt(callSequence.getToIdentifier());
audioModule.getIdentifierUpdateListener().receive(new IdentifierUpdateNotification(APCO25Talkgroup.create(to),
IdentifierUpdateNotification.Operation.ADD, 0));
}
catch(Exception e)
{
mLog.error("Error parsing from identifier from value [" + callSequence.getFromIdentifier());
}
}
IAudioCodec codec = audioModule.getAudioCodec();
for(VoiceFrame voiceFrame: callSequence.getVoiceFrames())
{
byte[] frameBytes = voiceFrame.getFrameBytes();
float[] audio = codec.getAudio(frameBytes);
audioModule.addAudio(audio);
}
AudioSegment audioSegment = audioModule.getAudioSegment();
try
{
AudioSegmentRecorder.recordWAVE(audioSegment, outputPath);
}
catch(IOException ioe)
{
mLog.error("Error writing audio segment, ioe");
}
audioModule.stop();
}
}
}
public static void main(String[] args)
{
boolean all = true;
String path = "/home/denny/SDRTrunk/recordings";
Path input = Paths.get(path);
if(all)
{
Collection<File> mbeFiles = FileUtils.listFiles(input.toFile(), new SuffixFileFilter(".mbe"), DirectoryFileFilter.DIRECTORY);
for(File inputFile: mbeFiles)
{
Path output = Paths.get(inputFile.getAbsolutePath().replace(".mbe", ".wav"));
mLog.info("Converting: " + inputFile);
try
{
MBECallSequenceConverter.convert(inputFile.toPath(), output);
}
catch(IOException ioe)
{
mLog.error("Error", ioe);
}
}
}
else
{
Path output = Paths.get(path.replace(".mbe", ".wav"));
mLog.info("Converting: " + path);
try
{
MBECallSequenceConverter.convert(input, output);
}
catch(IOException ioe)
{
mLog.error("Error", ioe);
}
}
}
}

View File

@@ -1,30 +1,27 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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.playback;
import com.google.common.eventbus.Subscribe;
import io.github.dsheirer.alias.id.priority.Priority;
import io.github.dsheirer.audio.AudioEvent;
import io.github.dsheirer.audio.AudioSegment;
import io.github.dsheirer.controller.NamingThreadFactory;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
@@ -33,11 +30,13 @@ import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.mixer.MixerChannel;
import io.github.dsheirer.util.ThreadPool;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import java.nio.ByteBuffer;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -51,10 +50,6 @@ import javax.sound.sampled.LineListener;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.SourceDataLine;
import java.nio.ByteBuffer;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* Audio output/playback channel for a single audio mixer channel. Providers support for playback of audio segments
@@ -67,7 +62,6 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
private int mBufferStopThreshold;
private Listener<IdentifierCollection> mIdentifierCollectionListener;
private Broadcaster<AudioEvent> mAudioEventBroadcaster = new Broadcaster<>();
private ScheduledFuture<?> mProcessorFuture;
private SourceDataLine mOutput;
private Mixer mMixer;
private MixerChannel mMixerChannel;
@@ -76,17 +70,18 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
private AudioEvent mAudioStartEvent;
private AudioEvent mAudioStopEvent;
private boolean mCanProcessAudio = false;
private LinkedTransferQueue<AudioSegment> mAudioSegmentQueue = new LinkedTransferQueue<>();
private AudioSegment mCurrentAudioSegment;
private AudioSegment mNextAudioSegment;
private ReentrantLock mLock = new ReentrantLock();
private int mCurrentBufferIndex = 0;
private UserPreferences mUserPreferences;
private BooleanProperty mEmptyProperty = new SimpleBooleanProperty(true);
private IntegerProperty mAudioPriority = new SimpleIntegerProperty(Priority.DEFAULT_PRIORITY);
private ByteBuffer mAudioSegmentStartTone;
private ByteBuffer mAudioSegmentPreemptTone;
private ByteBuffer mAudioSegmentDropTone;
private boolean mRunning = false;
private ScheduledExecutorService mScheduledExecutorService;
private ScheduledFuture<?> mProcessorFuture;
private boolean mDropDuplicates;
private long mOutputLastTimestamp = 0;
private static final long STALE_PLAYBACK_THRESHOLD_MS = 500;
/**
* Single audio channel playback with automatic starting and stopping of the
@@ -107,7 +102,10 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
{
mMixer = mixer;
mMixerChannel = mixerChannel;
mScheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new NamingThreadFactory(
"sdrtrunk audio output " + mixerChannel.name()));
mUserPreferences = userPreferences;
mDropDuplicates = mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled();
try
{
@@ -150,7 +148,7 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
}
//Run the queue processor task every 100 milliseconds or 10 times a second
mProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new AudioSegmentProcessor(),
mProcessorFuture = mScheduledExecutorService.scheduleAtFixedRate(new AudioSegmentProcessor(),
0, 100, TimeUnit.MILLISECONDS);
}
@@ -167,31 +165,22 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
updateToneInsertionAudioClips();
//Register to receive directory preference update notifications so we can update the preference items
//Register to receive preference update notifications so we can update the preference items
MyEventBus.getGlobalEventBus().register(this);
}
/**
* Boolean property that indicates if this audio output has an audio segment queued or in process of playback.
* Indicates if this audio output doesn't currently have any audio segments queued for playback.
* @return true if empty
*/
public BooleanProperty emptyProperty()
public boolean isEmpty()
{
return mEmptyProperty;
return mAudioSegmentQueue.isEmpty();
}
/**
* Audio playback priority of the current audio segment or default audio priority if nothing is currently loaded.
*/
public IntegerProperty audioPriorityProperty()
{
return mAudioPriority;
}
/**
* Schedules the audio segment for playback.
*
* Note: if a segment is currently playing and another audio segment is already queued for playback, invoking
* this method will overwrite the queued segment with the argument.
* Schedules the audio segment for playback. The audio segment user count should already be incremented by the
* calling entity.
*
* @param audioSegment to schedule for playback.
*/
@@ -199,23 +188,8 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
{
if(audioSegment != null)
{
mLock.lock();
if(mNextAudioSegment != null)
{
mNextAudioSegment.decrementConsumerCount();
mNextAudioSegment = null;
}
try
{
mNextAudioSegment = audioSegment;
mEmptyProperty.set(false);
}
finally
{
mLock.unlock();
}
//Audio segment use count has already been incremented by the external caller.
mAudioSegmentQueue.add(audioSegment);
}
}
@@ -254,6 +228,10 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
{
updateToneInsertionAudioClips();
}
else if(preferenceType == PreferenceType.DUPLICATE_CALL_DETECTION)
{
mDropDuplicates = mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled();
}
}
/**
@@ -262,7 +240,6 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
private void updateToneInsertionAudioClips()
{
mAudioSegmentStartTone = null;
mAudioSegmentPreemptTone = null;
mAudioSegmentDropTone = null;
float[] start = mUserPreferences.getPlaybackPreference().getStartTone();
@@ -272,13 +249,6 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
mAudioSegmentStartTone = convert(start);
}
float[] preempt = mUserPreferences.getPlaybackPreference().getPreemptTone();
if(preempt != null)
{
mAudioSegmentPreemptTone = convert(preempt);
}
float[] drop = mUserPreferences.getPlaybackPreference().getDropTone();
if(drop != null)
@@ -287,48 +257,6 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
}
}
/**
* Disposes of a processed audio segment and notifies the originator that processing of the segment is complete
* by decrementing the consumer count.
* @param audioSegment to dispose
*/
private void dispose(AudioSegment audioSegment)
{
mAudioPriority.unbind();
if(audioSegment != null)
{
audioSegment.decrementConsumerCount();
audioSegment.removeIdentifierUpdateNotificationListener(this);
}
}
/**
* Generates a tone indicating that a new audio segment is starting
*/
private ByteBuffer getAudioSegmentStartTone()
{
return mAudioSegmentStartTone;
}
/**
* Generates a tone indicating that the current audio segment playback has been preempted for a higher priority
* audio segment that is now starting.
*/
private ByteBuffer getAudioSegmentPreemptionTone()
{
return mAudioSegmentPreemptTone;
}
/**
* Generates a tone indicating that the current audio segment playback has been dropped because the audio segment
* has been flagged as Do Not Monitor after playback has started.
*/
private ByteBuffer getAudioSegmentDropTone()
{
return mAudioSegmentDropTone;
}
/**
* Writes the audio buffer data to the source data line. If the data line is not currently playing, write as
* much of the buffer to the data line as will fit, start the dataline, and then finish writing the residual
@@ -355,6 +283,7 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
//Top off the buffer and check if we can start it
wrote += mOutput.write(buffer.array(), 0, toWrite);
mOutputLastTimestamp = System.currentTimeMillis();
checkStart();
}
@@ -363,149 +292,146 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
{
//This will block until the buffer is fully written to the data line
mOutput.write(buffer.array(), wrote, buffer.array().length - wrote);
mOutputLastTimestamp = System.currentTimeMillis();
}
}
}
/**
* Dispose of the currently assigned audio segment.
*/
private void disposeCurrentAudioSegment()
{
if(mCurrentAudioSegment != null)
{
mCurrentAudioSegment.decrementConsumerCount();
mCurrentAudioSegment.removeIdentifierUpdateNotificationListener(this);
mCurrentAudioSegment = null;
broadcast(null);
}
}
/**
* Loads the next audio segment from the queue.
*/
private void loadNextAudioSegment()
{
AudioSegment audioSegment = mAudioSegmentQueue.poll();
boolean verificationInProgress = (audioSegment != null);
while(verificationInProgress)
{
if(audioSegment != null)
{
//Throw away the audio segment if it has been flagged as do not monitor or is duplicate
if(isThrowaway(audioSegment))
{
audioSegment.decrementConsumerCount();
audioSegment = mAudioSegmentQueue.poll();
}
else
{
verificationInProgress = false;
}
}
else
{
verificationInProgress = false;
}
}
mCurrentAudioSegment = audioSegment;
if(audioSegment != null)
{
mCurrentAudioSegment.addIdentifierUpdateNotificationListener(this);
broadcast(mCurrentAudioSegment.getIdentifierCollection());
}
mCurrentBufferIndex = 0;
}
/**
* Indicates if the audio segment should be thrown away because it is marked as duplicate or do-not-monitor.
* @param audioSegment to evaluate
* @return true if the audio should be thrown away.
*/
private boolean isThrowaway(AudioSegment audioSegment)
{
return audioSegment != null && (audioSegment.isDoNotMonitor() || mDropDuplicates && (audioSegment.isDuplicate()));
}
/**
* Manage audio segment playback and process audio segment buffers. This method is designed to be called
* by a threaded processor repeatedly to playback the current audio segment and check for and start a newly
* assigned audio segment. It also handles starting and stopping the playback source data line to avoid audio
* by a threaded processor repeatedly to playback the current audio segment and check for and start newly
* added audio segments. It also handles starting and stopping the playback source data line to avoid audio
* discontinuities due to buffer underruns.
*/
private void processAudio()
{
if(mNextAudioSegment != null)
if(mCurrentAudioSegment == null)
{
mLock.lock();
try
{
//Remove an assigned segment that's subsequently flagged as duplicate or do not monitor before playback
if(mNextAudioSegment.isDoNotMonitor() || (mNextAudioSegment.isDuplicate() &&
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled()))
{
mNextAudioSegment = null;
if(mCurrentAudioSegment == null)
{
mEmptyProperty.set(true);
}
}
//For linked audio segments, allow the linked segment to complete first before assigning the next
else if(mNextAudioSegment.isLinked())
{
if(mCurrentAudioSegment == null)
{
mCurrentAudioSegment = mNextAudioSegment;
mNextAudioSegment = null;
mCurrentBufferIndex = 0;
if(mCurrentAudioSegment != null)
{
mAudioPriority.bind(mCurrentAudioSegment.monitorPriorityProperty());
mCurrentAudioSegment.addIdentifierUpdateNotificationListener(this);
broadcast(mCurrentAudioSegment.getIdentifierCollection());
}
else
{
mAudioPriority.setValue(Priority.DEFAULT_PRIORITY);
}
}
}
else
{
//Insert audio segment start or audio priority preemption bonk tone
if(mCurrentAudioSegment == null)
{
playAudio(getAudioSegmentStartTone());
}
else if(mCurrentBufferIndex > 0 &&
(!mCurrentAudioSegment.completeProperty().get() ||
mCurrentBufferIndex < mCurrentAudioSegment.getAudioBufferCount()))
{
playAudio(getAudioSegmentPreemptionTone());
}
else
{
playAudio(getAudioSegmentStartTone());
}
//Close current audio segment
dispose(mCurrentAudioSegment);
mCurrentAudioSegment = mNextAudioSegment;
mNextAudioSegment = null;
mCurrentBufferIndex = 0;
if(mCurrentAudioSegment != null)
{
mAudioPriority.bind(mCurrentAudioSegment.monitorPriorityProperty());
mCurrentAudioSegment.addIdentifierUpdateNotificationListener(this);
broadcast(mCurrentAudioSegment.getIdentifierCollection());
}
else
{
mAudioPriority.setValue(Priority.DEFAULT_PRIORITY);
}
}
}
finally
{
mLock.unlock();
}
loadNextAudioSegment();
}
if(mCurrentAudioSegment != null)
//Evaluate current audio segment to see if the status has changed for duplicate or do-not-monitor.
while(isThrowaway(mCurrentAudioSegment))
{
//Check for completed audio segment or a segment flagged as duplicate or Do Not Monitor
if(mCurrentAudioSegment.isDoNotMonitor() ||
(mCurrentAudioSegment.isDuplicate() &&
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled()) ||
(mCurrentAudioSegment.completeProperty().get() && mCurrentBufferIndex >= mCurrentAudioSegment.getAudioBufferCount()))
if(mCurrentBufferIndex > 0)
{
if(mCurrentAudioSegment.isDoNotMonitor())
playAudio(mAudioSegmentDropTone);
}
disposeCurrentAudioSegment();
loadNextAudioSegment();
}
while(mCurrentAudioSegment != null && mCurrentBufferIndex < mCurrentAudioSegment.getAudioBufferCount())
{
//Continuously evaluate current audio segment to see if the status has changed for duplicate or do-not-monitor.
if(isThrowaway(mCurrentAudioSegment))
{
if(mCurrentBufferIndex > 0)
{
playAudio(getAudioSegmentDropTone());
playAudio(mAudioSegmentDropTone);
}
dispose(mCurrentAudioSegment);
mCurrentAudioSegment = null;
mLock.lock();
disposeCurrentAudioSegment();
}
else
{
if(mCurrentBufferIndex == 0)
{
playAudio(mAudioSegmentStartTone);
}
try
{
if(mNextAudioSegment == null)
float[] audioBuffer = mCurrentAudioSegment.getAudioBuffers().get(mCurrentBufferIndex++);
if(audioBuffer != null)
{
mEmptyProperty.set(true);
ByteBuffer audio = convert(audioBuffer);
//This call blocks until all audio bytes are dumped into the data line.
playAudio(audio);
}
}
finally
catch(Exception e)
{
mLock.unlock();
}
return;
}
//Process any new buffers that have been added to the audio segment. If a next audio segment gets assigned
//while processing, exit the loop so that we can evaluate the next for higher priority preempt. If the next
//segment is a linked segment, ignore it so that we can close out the current segment.
while(mCurrentAudioSegment != null && (mNextAudioSegment == null || mNextAudioSegment.isLinked()) &&
mCurrentBufferIndex < mCurrentAudioSegment.getAudioBufferCount() &&
!mCurrentAudioSegment.isDoNotMonitor() && !(mCurrentAudioSegment.isDuplicate() &&
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled()))
{
float[] audioBuffer = mCurrentAudioSegment.getAudioBuffers().get(mCurrentBufferIndex++);
if(audioBuffer != null)
{
ByteBuffer audio = convert(audioBuffer);
playAudio(audio);
mLog.error("Error while processing audio for [" + mMixerChannel.name() + "]", e);
}
}
}
//Check for completed and fully-played audio segment to closeout
if(mCurrentAudioSegment != null &&
mCurrentAudioSegment.isComplete() &&
(mCurrentBufferIndex >= mCurrentAudioSegment.getAudioBufferCount()))
{
disposeCurrentAudioSegment();
}
checkStop();
}
@@ -523,25 +449,7 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
}
mProcessorFuture = null;
mLock.lock();
try
{
if(mNextAudioSegment != null)
{
mNextAudioSegment.decrementConsumerCount();
mNextAudioSegment = null;
}
}
finally
{
mLock.unlock();
}
dispose(mCurrentAudioSegment);
mCurrentAudioSegment = null;
disposeCurrentAudioSegment();
mAudioEventBroadcaster.clear();
mIdentifierCollectionListener = null;
@@ -650,14 +558,28 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
if(mRunning)
{
//If the output buffer falls below the threshold then drain the output and stop playback
if(mOutput.isRunning() && mOutput.available() >= mBufferStopThreshold)
if(mOutput.isRunning())
{
mOutput.drain();
mOutput.stop();
mRunning = false;
if(mOutput.available() >= mBufferStopThreshold)
{
mOutput.drain();
mOutput.stop();
mRunning = false;
}
//Detect when we have a partially filled output that will neither start nor stop and the last write
//timestamp was more than the staleness threshold (500 ms) -- stop the output and discard stale contents
else if(mOutput.available() < mBufferStopThreshold &&
mOutput.available() > mBufferStartThreshold &&
(System.currentTimeMillis() - mOutputLastTimestamp >= STALE_PLAYBACK_THRESHOLD_MS))
{
mOutput.stop();
//Discard the stale buffer contents.
mOutput.flush();
mRunning = false;
}
}
//If output playback stopped on its own because the buffer emptied, then cleanup
else if(!mOutput.isRunning())
else
{
mRunning = false;
}
@@ -735,16 +657,23 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
*/
public class AudioSegmentProcessor implements Runnable
{
private AtomicBoolean mProcessing = new AtomicBoolean();
@Override
public void run()
{
try
if(mProcessing.compareAndSet(false, true))
{
processAudio();
}
catch(Throwable t)
{
mLog.error("Error while processing audio buffers", t);
try
{
processAudio();
}
catch(Throwable t)
{
mLog.error("Error while processing audio buffers", t);
}
mProcessing.set(false);
}
}
}

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* 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
@@ -15,7 +14,7 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.audio.playback;
@@ -24,6 +23,7 @@ import io.github.dsheirer.audio.AudioEvent;
import io.github.dsheirer.audio.AudioException;
import io.github.dsheirer.audio.AudioSegment;
import io.github.dsheirer.audio.IAudioController;
import io.github.dsheirer.controller.NamingThreadFactory;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.preference.PreferenceType;
import io.github.dsheirer.preference.UserPreferences;
@@ -31,17 +31,18 @@ import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.mixer.MixerChannel;
import io.github.dsheirer.source.mixer.MixerChannelConfiguration;
import io.github.dsheirer.util.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Manages scheduling and playback of audio segments to the local users audio system.
@@ -50,8 +51,6 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
{
private static final Logger mLog = LoggerFactory.getLogger(AudioPlaybackManager.class);
public static final String AUDIO_CHANNELS_PROPERTY = "audio.manager.channels";
public static final String AUDIO_MIXER_PROPERTY = "audio.manager.mixer";
public static final AudioEvent CONFIGURATION_CHANGE_STARTED =
new AudioEvent(AudioEvent.Type.AUDIO_CONFIGURATION_CHANGE_STARTED, null);
public static final AudioEvent CONFIGURATION_CHANGE_COMPLETE =
@@ -64,6 +63,8 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
private List<AudioSegment> mAudioSegments = new ArrayList<>();
private List<AudioSegment> mPendingAudioSegments = new ArrayList<>();
private LinkedTransferQueue<AudioSegment> mNewAudioSegmentQueue = new LinkedTransferQueue<>();
private ScheduledExecutorService mScheduledExecutorService =
Executors.newSingleThreadScheduledExecutor(new NamingThreadFactory("sdrtrunk audio manager"));
/**
* Constructs an instance.
@@ -202,29 +203,13 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
//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()));
//Assign empty audio outputs first
for(AudioOutput audioOutput: mAudioOutputs)
{
if(audioOutput.emptyProperty().get())
{
audioOutput.play(mAudioSegments.remove(0));
if(mAudioSegments.isEmpty())
{
return;
}
}
}
}
//Preempt ongoing audio segment playback when higher priority audio segments are available
if(!mAudioSegments.isEmpty())
{
for(AudioOutput audioOutput: mAudioOutputs)
{
if(mAudioSegments.get(0).monitorPriorityProperty().get() < audioOutput.audioPriorityProperty().get())
if(audioOutput.isEmpty())
{
audioOutput.play(mAudioSegments.remove(0));
@@ -284,7 +269,7 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
}
catch(AudioException ae)
{
mLog.error("Error changing audio output to [" + configuration.toString() + "]", ae);
mLog.error("Error changing audio output to [" + configuration + "]", ae);
}
}
}
@@ -332,7 +317,7 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
throw new AudioException("Unsupported mixer channel configuration: " + entry.getMixerChannel());
}
mProcessingTask = ThreadPool.SCHEDULED.scheduleAtFixedRate(new AudioSegmentProcessor(),
mProcessingTask = mScheduledExecutorService.scheduleAtFixedRate(new AudioSegmentProcessor(),
0, 100, TimeUnit.MILLISECONDS);
mControllerBroadcaster.broadcast(CONFIGURATION_CHANGE_COMPLETE);
mMixerChannelConfiguration = entry;
@@ -343,7 +328,7 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
* Current audio playback mixer channel configuration setting.
*/
@Override
public MixerChannelConfiguration getMixerChannelConfiguration() throws AudioException
public MixerChannelConfiguration getMixerChannelConfiguration()
{
return mMixerChannelConfiguration;
}
@@ -384,16 +369,23 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
*/
public class AudioSegmentProcessor implements Runnable
{
private AtomicBoolean mProcessing = new AtomicBoolean();
@Override
public void run()
{
try
if(mProcessing.compareAndSet(false, true))
{
processAudioSegments();
}
catch(Throwable t)
{
mLog.error("Encountered error while processing audio segments", t);
try
{
processAudioSegments();
}
catch(Throwable t)
{
mLog.error("Encountered error while processing audio segments", t);
}
mProcessing.set(false);
}
}
}

View File

@@ -33,6 +33,8 @@ public class BinaryMessage extends BitSet
private static final int[] CHARACTER_7_BIT = new int[]{0, 1, 2, 3, 4, 5, 6};
private static final int[] CHARACTER_8_BIT = new int[]{0, 1, 2, 3, 4, 5, 6, 7};
private static final String UTF_8 = "UTF-8";
private static final String GB2312 = "GB2312";
/**
* Logical (ie constructed) size of this bitset, despite the actual size of
@@ -274,8 +276,7 @@ public class BinaryMessage extends BitSet
}
else
{
throw new BitSetFullException("bitset is full -- contains " +
(mPointer + 1) + "bits");
throw new BitSetFullException("bitset is full -- contains [" + mPointer + "/" + size() + "] bits");
}
}
@@ -843,13 +844,18 @@ public class BinaryMessage extends BitSet
*/
public int getTwosComplement(int start, int end)
{
BinaryMessage sub = getSubMessage(start, end);
boolean negative = sub.get(start);
sub.flip(0, sub.size());
int value = sub.getInt(0, sub.size()) + 1;
value *= (negative ? -1 : 1);
return value;
if(get(start))
{
//Negative value - flip and add one
BinaryMessage fragment = getSubMessage(start, end);
fragment.flip(0, fragment.size());
return fragment.getInt(1, fragment.size()) + 1;
}
else
{
//Positive value - return the contents.
return getInt(start + 1, end);
}
}
@@ -1236,6 +1242,26 @@ public class BinaryMessage extends BitSet
return new String(bytes, UTF_8);
}
/**
* Parse GB2312 Simplified Chinese Characters from the message starting at the offset.
* @param offset where to start parsing.
* @param characterCount number of characters to parse
* @return parsed message.
* @throws UnsupportedEncodingException if GB2312 encoding is not supported.
*/
public String parseGB2312(int offset, int characterCount) throws UnsupportedEncodingException
{
int length = characterCount * 2;
byte[] bytes = new byte[length];
for(int x = 0; x < length; x++)
{
bytes[x] = getByte(CHARACTER_8_BIT, x * 8 + offset);
}
return new String(bytes, GB2312);
}
/**
* Parse 16-bit unicode characters from the message starting at the offset.
* @param offset where to start parsing.

View File

@@ -132,12 +132,10 @@ public enum SyncPattern
true, true, false, false //1100 0xC
}),
/* Sync (0x158) = 1101011000 */
/* Sync (0x158) = 101011000 */
PASSPORT( new boolean[]
{
true, //0001 0x1
false, true, false, true, //0101 0x5
true, false, false, false //1000 0x8
true,false,true,false,true,true,false,false,false
} ),
LTR_STANDARD_OSW( new boolean[]

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -84,7 +84,7 @@ public class DcCorrectionManager
{
mDcCalculationsRemaining--;
}
//
// mLog.info("DC: " + mDecimalFormat.format(mAverageDc) +
// " RES:" + mDecimalFormat.format(residualOffsetToRemove) +
// " REM:" + mDcCalculationsRemaining);

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -21,21 +21,16 @@ package io.github.dsheirer.buffer;
import java.util.Arrays;
/**
* Circular sample buffer - allocates a buffer and stores samples in a circular
* fashion, overwriting older samples with newly arrived samples. Initially
* fills buffer with 0-valued samples
*
* Can be used as a delay-type buffer, to delay samples by the 'size' amount
* Circular sample buffer - allocates a buffer and stores samples in a circular fashion, overwriting older samples with
* newly arrived samples. Initially fills buffer with 0-valued (default) samples or with a specified value.
*/
public class FloatCircularBuffer
{
private float[] mBuffer;
private int mBufferPointer = 0;
private float mOldestSample;
/**
* Creates a circular double buffer of the specified size and all entries filled with the specified initial value.
*
* @param size of the buffer
* @param initialFillValue to fill the buffer entries
*/
@@ -47,7 +42,6 @@ public class FloatCircularBuffer
/**
* Creates a circular double buffer of the specified size and all entries filled with an initial value of zero.
*
* @param size of the buffer
*/
public FloatCircularBuffer(int size)
@@ -55,33 +49,34 @@ public class FloatCircularBuffer
this(size, 0.0f);
}
public float[] getBuffer()
/**
* Resets the delay buffer to an all zeros state
*/
public void reset(float fillValue)
{
return mBuffer;
Arrays.fill(mBuffer, fillValue);
}
/**
* Gets the current buffer value and overwrites that value position in the buffer with the new value.
* Puts the new value into the buffer and returns the oldest buffer value that it replaced
*
* @param newValue to add to the buffer
* @return oldest value that was overwritten by the new value
*/
public float getAndPut(float newValue)
public float get(float newValue)
{
mOldestSample = mBuffer[mBufferPointer];
put(newValue);
return mOldestSample;
}
float oldestSample = mBuffer[mBufferPointer];
/**
* Puts the value into the buffer, updates the pointer and returns the buffer value at the pointer position.
* @param sample to add
* @return next pointed sample.
*/
public float putAndGet(float sample)
{
put(sample);
return mBuffer[mBufferPointer];
mBuffer[mBufferPointer] = newValue;
mBufferPointer++;
if(mBufferPointer >= mBuffer.length)
{
mBufferPointer = 0;
}
return oldestSample;
}
public float[] getAll()
@@ -105,13 +100,70 @@ public class FloatCircularBuffer
/**
* Places the new value into the buffer, overwriting the oldest value
*
* @param newValue to add to the buffer
*/
public void put(float newValue)
{
mBuffer[mBufferPointer] = newValue;
mBufferPointer++;
mBufferPointer %= mBuffer.length;
if(mBufferPointer >= mBuffer.length)
{
mBufferPointer = 0;
}
}
/**
* Returns the maximum from the values currently in the buffer
*/
public float max(float referenceValue)
{
float max = referenceValue;
for(int x = 0; x < mBuffer.length; x++)
{
if(mBuffer[x] > max)
{
max = mBuffer[x];
}
}
// System.out.println("Max [" + max + "] from " + Arrays.toString(mBuffer));
return max;
}
/**
* Returns the minimum from the values currently in the buffer
*/
public float min()
{
float min = 0.0f;
for(int x = 0; x < mBuffer.length; x++)
{
if(mBuffer[x] < min)
{
min = mBuffer[x];
}
}
return min;
}
/**
* Calculates the average/mean of the values contained in this buffer
*/
public float mean()
{
double accumulator = 0.0;
for(float value: mBuffer)
{
accumulator += value;
}
return (float)(accumulator / mBuffer.length);
}
}

View File

@@ -22,6 +22,8 @@ package io.github.dsheirer.buffer;
import io.github.dsheirer.sample.SampleUtils;
import io.github.dsheirer.sample.complex.ComplexSamples;
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
import java.util.Arrays;
import java.util.Iterator;
/**
@@ -30,6 +32,7 @@ import java.util.Iterator;
public class FloatNativeBuffer extends AbstractNativeBuffer
{
private float[] mInterleavedComplexSamples;
private static final int BUFFER_SIZE = 2048;
/**
* Constructs an instance
@@ -81,48 +84,51 @@ public class FloatNativeBuffer extends AbstractNativeBuffer
private class ComplexSamplesIterator implements Iterator<ComplexSamples>
{
private boolean mEmpty;
private int mBufferPointer = 0;
@Override
public boolean hasNext()
{
return !mEmpty;
return mBufferPointer < mInterleavedComplexSamples.length;
}
@Override
public ComplexSamples next()
{
if(mEmpty)
if(!hasNext())
{
throw new IllegalStateException("No more samples");
}
mEmpty = true;
return SampleUtils.deinterleave(mInterleavedComplexSamples, getTimestamp());
float[] chunk = Arrays.copyOfRange(mInterleavedComplexSamples, mBufferPointer, mBufferPointer + BUFFER_SIZE * 2);
mBufferPointer += BUFFER_SIZE * 2;
return SampleUtils.deinterleave(chunk, getTimestamp());
}
}
private class InterleavedComplexSamplesIterator implements Iterator<InterleavedComplexSamples>
{
private boolean mEmpty;
private int mBufferPointer = 0;
@Override
public boolean hasNext()
{
return !mEmpty;
return mBufferPointer < mInterleavedComplexSamples.length;
}
@Override
public InterleavedComplexSamples next()
{
if(mEmpty)
if(!hasNext())
{
throw new IllegalStateException("No more samples");
}
mEmpty = true;
return new InterleavedComplexSamples(mInterleavedComplexSamples, getTimestamp());
float[] chunk = Arrays.copyOfRange(mInterleavedComplexSamples, mBufferPointer, mBufferPointer + BUFFER_SIZE * 2);
mBufferPointer += BUFFER_SIZE * 2;
return new InterleavedComplexSamples(chunk, getTimestamp());
}
}
}

View File

@@ -1,124 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.buffer;
/**
* Circular sample buffer - allocates a buffer and stores samples in a circular
* fashion, overwriting older samples with newly arrived samples.
*
* Can be used as a delay-type buffer, to delay samples by the 'size' amount
*/
public class RealCircularBuffer
{
float[] mBuffer;
int mBufferPointer = 0;
public RealCircularBuffer(int size)
{
mBuffer = new float[size];
}
public int getSize()
{
return mBuffer.length;
}
public float[] getBuffer()
{
return mBuffer;
}
/**
* Puts the new value into the buffer and returns the oldest buffer value
* that it replaced
*
* @param newValue
* @return
*/
public float putAndGet(float newValue)
{
float oldestSample = mBuffer[mBufferPointer];
put(newValue);
return oldestSample;
}
/**
* Adds the new value to this buffer.
*/
public void put(float newValue)
{
mBuffer[mBufferPointer] = newValue;
mBufferPointer++;
if(mBufferPointer >= mBuffer.length)
{
mBufferPointer = 0;
}
}
/**
* Returns the sample at the specified index, where index 0 is the oldest sample and index (length - 1) is the
* newest sample.
* @param index in range of 0 to length-1
* @return value at indexed position
*/
public float get(int index)
{
if(index < mBuffer.length)
{
int pointer = mBufferPointer + index;
if(pointer >= mBuffer.length)
{
pointer -= mBuffer.length;
}
return mBuffer[pointer];
}
else
{
throw new ArrayIndexOutOfBoundsException("Index [" + index + "] is not valid for the buffer size of [" +
mBuffer.length + "]");
}
}
/**
* Returns the contents of the circular buffer unwrapped where index 0 is the oldest sample and index length-1
* is the newest sample.
*
* @return array of samples
*/
public float[] get()
{
float[] samples = new float[mBuffer.length];
System.arraycopy(mBuffer, mBufferPointer, samples, 0, mBuffer.length - mBufferPointer);
if(mBufferPointer != 0)
{
System.arraycopy(mBuffer, 0, samples, mBuffer.length - mBufferPointer, mBufferPointer);
}
return samples;
}
}

View File

@@ -0,0 +1,132 @@
/*
* *****************************************************************************
* 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.buffer.airspy.hf;
import io.github.dsheirer.buffer.AbstractNativeBuffer;
import io.github.dsheirer.sample.complex.ComplexSamples;
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
import java.util.Iterator;
/**
* Native buffer implementation for Airspy HF+ & Discovery tuners.
*/
public class AirspyHfNativeBuffer extends AbstractNativeBuffer
{
public static final float SCALE = 1.0f / 32768.0f;
private short[] mInterleavedSamples;
private float mAverageDc;
/**
* Constructs an instance.
* @param timestamp of the first sample of the buffer
* @param interleavedSamples pairs of 16-bit complex samples.
*/
public AirspyHfNativeBuffer(long timestamp, float samplesPerMillisecond, float averageDc, short[] interleavedSamples)
{
super(timestamp, samplesPerMillisecond);
mInterleavedSamples = interleavedSamples;
mAverageDc = averageDc;
}
@Override
public Iterator<ComplexSamples> iterator()
{
return new IteratorComplexSamples();
}
@Override
public Iterator<InterleavedComplexSamples> iteratorInterleaved()
{
return new IteratorInterleaved();
}
@Override
public int sampleCount()
{
return mInterleavedSamples.length / 2;
}
/**
* Scalar implementation of complex samples buffer iterator
*/
public class IteratorComplexSamples implements Iterator<ComplexSamples>
{
private boolean mHasNext = true;
@Override
public boolean hasNext()
{
return mHasNext;
}
@Override
public ComplexSamples next()
{
int length = mInterleavedSamples.length / 2;
float[] i = new float[length];
float[] q = new float[length];
int index = 0;
for(int x = 0; x < mInterleavedSamples.length; x += 2)
{
index = x / 2;
//Native ordering is in Q, I, Q, I ... reverse it to I, Q, I, Q
i[index] = (mInterleavedSamples[x + 1] * SCALE) - mAverageDc;
q[index] = (mInterleavedSamples[x] * SCALE) - mAverageDc;
}
mHasNext = false;
return new ComplexSamples(i, q, getTimestamp());
}
}
/**
* Scalar implementation of interleaved sample buffer iterator.
*/
public class IteratorInterleaved implements Iterator<InterleavedComplexSamples>
{
private boolean mHasNext = true;
@Override
public boolean hasNext()
{
return mHasNext;
}
@Override
public InterleavedComplexSamples next()
{
float[] samples = new float[mInterleavedSamples.length];
for(int x = 0; x < mInterleavedSamples.length; x += 2)
{
//Native ordering is in Q, I, Q, I ... reverse it to I, Q, I, Q
samples[x] = (mInterleavedSamples[x + 1] * SCALE) - mAverageDc;
samples[x + 1] = (mInterleavedSamples[x] * SCALE) - mAverageDc;
}
mHasNext = false;
return new InterleavedComplexSamples(samples, getTimestamp());
}
}
}

View File

@@ -0,0 +1,83 @@
/*
* *****************************************************************************
* 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.buffer.airspy.hf;
import io.github.dsheirer.buffer.AbstractNativeBufferFactory;
import io.github.dsheirer.buffer.DcCorrectionManager;
import io.github.dsheirer.buffer.INativeBuffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
/**
* Airspy HF+ native buffer factory.
*
* Note: the tuner provides 1024 samples in each transfer where samples are 16-bit interleaved complex.
*/
public class AirspyHfNativeBufferFactory extends AbstractNativeBufferFactory
{
private DcCorrectionManager mDcCorrectionManager = new DcCorrectionManager();
/**
* Constructs an instance
*/
public AirspyHfNativeBufferFactory()
{
}
/**
* Converts the samples byte buffer into a native buffer and calculates DC offset
* @param samples byte array copied from native memory
* @param timestamp of the samples
* @return constructed native buffer
*/
@Override
public INativeBuffer getBuffer(ByteBuffer samples, long timestamp)
{
ShortBuffer shortBuffer = samples.order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
short[] converted = new short[shortBuffer.capacity()];
shortBuffer.get(converted);
if(mDcCorrectionManager.shouldCalculateDc())
{
calculateDc(converted);
}
return new AirspyHfNativeBuffer(timestamp, getSamplesPerMillisecond(), mDcCorrectionManager.getAverageDc(), converted);
}
/**
* Calculates the average DC in the sample stream so that it can be subtracted from the samples when the
* native buffer is used.
* @param samples containing DC offset
*/
private void calculateDc(short[] samples)
{
float dcAccumulator = 0;
for(short sample: samples)
{
dcAccumulator += sample;
}
dcAccumulator /= samples.length;
mDcCorrectionManager.adjust(dcAccumulator * AirspyHfNativeBuffer.SCALE);
}
}

View File

@@ -35,11 +35,11 @@ import javax.swing.JPanel;
public class NowPlayingPanel extends JPanel
{
private ChannelMetadataPanel mChannelMetadataPanel;
private ChannelDetailPanel mChannelDetailPanel;
private DecodeEventPanel mDecodeEventPanel;
private MessageActivityPanel mMessageActivityPanel;
private ChannelPowerPanel mChannelPowerPanel;
private final ChannelMetadataPanel mChannelMetadataPanel;
private final ChannelDetailPanel mChannelDetailPanel;
private final DecodeEventPanel mDecodeEventPanel;
private final MessageActivityPanel mMessageActivityPanel;
private final ChannelPowerPanel mChannelPowerPanel;
/**
* GUI panel that combines the currently decoding channels metadata table and viewers for channel details,

View File

@@ -73,7 +73,7 @@ public class DecoderStateEvent
{
StringBuilder sb = new StringBuilder();
sb.append("Decoder State Event - source[").append(mSource.getClass())
sb.append("Decoder State Event - source[").append(mSource != null ? mSource.getClass() : "null")
.append("] event[").append(mEvent)
.append("] state[").append(mState)
.append("] timeslot[").append(mTimeslot)

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* 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
@@ -15,22 +14,13 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.controller.channel;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.util.ThreadPool;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
@@ -41,12 +31,21 @@ import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
public class ChannelAutoStartFrame extends JFrame
{
private final static Logger mLog = LoggerFactory.getLogger(ChannelAutoStartFrame.class);
private static final int COUNTDOWN_SECONDS = 10;
private Listener<ChannelEvent> mChannelEventListener;
private List<Channel> mChannels;
@@ -55,7 +54,7 @@ public class ChannelAutoStartFrame extends JFrame
private JButton mCancelButton;
private JTable mChannelTable;
private AtomicBoolean mChannelsStarted = new AtomicBoolean();
private int mAutoStartTimeoutSeconds;
private ScheduledFuture<?> mTimerFuture;
/**
@@ -66,10 +65,12 @@ public class ChannelAutoStartFrame extends JFrame
* @param listener to receive channel start/enable request(s)
* @param channels to auto-start
*/
public ChannelAutoStartFrame(Listener<ChannelEvent> listener, List<Channel> channels)
public ChannelAutoStartFrame(Listener<ChannelEvent> listener, List<Channel> channels, UserPreferences userPreferences)
{
mChannelEventListener = listener;
mChannels = channels;
mAutoStartTimeoutSeconds = userPreferences.getApplicationPreference().getChannelAutoStartTimeout();
init();
EventQueue.invokeLater(() -> {
@@ -100,7 +101,7 @@ public class ChannelAutoStartFrame extends JFrame
"[][][grow,fill][]"));
panel.add(new JLabel("The following channels will be automatically"), "span");
mCountdownLabel = new JLabel(getCountdownText(COUNTDOWN_SECONDS));
mCountdownLabel = new JLabel(getCountdownText(mAutoStartTimeoutSeconds));
panel.add(mCountdownLabel, "span");
panel.add(new JScrollPane(getChannelTable()), "span");
@@ -220,7 +221,7 @@ public class ChannelAutoStartFrame extends JFrame
*/
public class CountdownTimer implements Runnable
{
private int mCount = COUNTDOWN_SECONDS;
private int mCount = mAutoStartTimeoutSeconds;
@Override
public void run()

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -623,30 +623,27 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
Platform.runLater(() -> channel.setProcessing(false));
}
//Since the processing chain's source will block on stop(), throw it to the thread pool to run
ThreadPool.CACHED.submit(() -> {
try
{
processingChain.stop();
processingChain.removeEventLoggingModules();
processingChain.removeRecordingModules();
try
{
processingChain.stop();
processingChain.removeEventLoggingModules();
processingChain.removeRecordingModules();
//Deregister channel from receive frequency correction events to show in the spectral display (hack!)
processingChain.removeFrequencyChangeListener(channel);
channel.resetFrequencyCorrection();
//Deregister channel from receive frequency correction events to show in the spectral display (hack!)
processingChain.removeFrequencyChangeListener(channel);
channel.resetFrequencyCorrection();
mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, ChannelEvent.Event.NOTIFICATION_PROCESSING_STOP));
mChannelEventBroadcaster.removeListener(processingChain);
mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, ChannelEvent.Event.NOTIFICATION_PROCESSING_STOP));
mChannelEventBroadcaster.removeListener(processingChain);
//Unregister for event bus requests and notifications
processingChain.getEventBus().unregister(ChannelProcessingManager.this);
processingChain.dispose();
}
catch(Exception e)
{
mLog.error("Error during shutdown of processing chain for channel [" + channel.getName() + "}", e);
}
});
//Unregister for event bus requests and notifications
processingChain.getEventBus().unregister(ChannelProcessingManager.this);
processingChain.dispose();
}
catch(Exception e)
{
mLog.error("Error during shutdown of processing chain for channel [" + channel.getName() + "}", e);
}
}
}

View File

@@ -0,0 +1,286 @@
/*
* *****************************************************************************
* 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.am;
import io.github.dsheirer.dsp.magnitude.IMagnitudeCalculator;
import io.github.dsheirer.dsp.magnitude.MagnitudeFactory;
import io.github.dsheirer.vector.calibrate.Calibration;
import io.github.dsheirer.vector.calibrate.CalibrationException;
import io.github.dsheirer.vector.calibrate.CalibrationType;
import io.github.dsheirer.vector.calibrate.Implementation;
import org.apache.commons.math3.stat.descriptive.moment.Mean;
/**
* Calibrates AM demodulator options
*/
public class AmDemodulatorCalibration extends Calibration
{
private static final int BUFFER_SIZE = 2048;
private static final int ITERATION_DURATION_MS = 1000;
private static final int WARMUP_ITERATIONS = 5;
private static final int TEST_ITERATIONS = 5;
private IMagnitudeCalculator mMagnitudeCalculator = MagnitudeFactory.getMagnitudeCalculator();
private IAmDemodulator mScalarDemodulator = new ScalarAMDemodulator(500.0f);
private IAmDemodulator mVectorDemodulator64 = new VectorAMDemodulator64(500.0f);
private IAmDemodulator mVectorDemodulator128 = new VectorAMDemodulator128(500.0f);
private IAmDemodulator mVectorAMDemodulator256 = new VectorAMDemodulator256(500.0f);
private IAmDemodulator mVectorAMDemodulator512 = new VectorAMDemodulator512(500.0f);
/**
* Constructs an instance
*/
public AmDemodulatorCalibration()
{
super(CalibrationType.AM_DEMODULATOR);
}
@Override public void calibrate() throws CalibrationException
{
float[] magnitude = getFloatSamples(BUFFER_SIZE);
Mean scalarMean = new Mean();
for(int x = 0; x < WARMUP_ITERATIONS; x++)
{
long score = testScalar(magnitude);
scalarMean.increment(score);
}
mLog.info("AM DEMODULATOR WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
Mean vector64Mean = new Mean();
for(int x = 0; x < WARMUP_ITERATIONS; x++)
{
long score = testVector64(magnitude);
vector64Mean.increment(score);
}
mLog.info("AM DEMODULATOR WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult()));
Mean vector128Mean = new Mean();
for(int x = 0; x < WARMUP_ITERATIONS; x++)
{
long score = testVector128(magnitude);
vector128Mean.increment(score);
}
mLog.info("AM DEMODULATOR WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult()));
Mean vector256Mean = new Mean();
for(int x = 0; x < WARMUP_ITERATIONS; x++)
{
long score = testVector256(magnitude);
vector256Mean.increment(score);
}
mLog.info("AM DEMODULATOR WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult()));
Mean vector512Mean = new Mean();
for(int x = 0; x < WARMUP_ITERATIONS; x++)
{
long score = testVector512(magnitude);
vector512Mean.increment(score);
}
mLog.info("AM DEMODULATOR WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult()));
//Start tests
scalarMean.clear();
for(int x = 0; x < TEST_ITERATIONS; x++)
{
long score = testScalar(magnitude);
scalarMean.increment(score);
}
mLog.info("AM DEMODULATOR - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
vector64Mean.clear();
for(int x = 0; x < TEST_ITERATIONS; x++)
{
long score = testVector64(magnitude);
vector64Mean.increment(score);
}
mLog.info("AM DEMODULATOR - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult()));
vector128Mean.clear();
for(int x = 0; x < TEST_ITERATIONS; x++)
{
long score = testVector128(magnitude);
vector128Mean.increment(score);
}
mLog.info("AM DEMODULATOR - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult()));
vector256Mean.clear();
for(int x = 0; x < TEST_ITERATIONS; x++)
{
long score = testVector256(magnitude);
vector256Mean.increment(score);
}
mLog.info("AM DEMODULATOR - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult()));
vector512Mean.clear();
for(int x = 0; x < TEST_ITERATIONS; x++)
{
long score = testVector512(magnitude);
vector512Mean.increment(score);
}
mLog.info("AM DEMODULATOR - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult()));
Implementation optimal = Implementation.SCALAR;
double optimalResult = scalarMean.getResult();
if(vector64Mean.getResult() > optimalResult)
{
optimal = Implementation.VECTOR_SIMD_64;
optimalResult = vector64Mean.getResult();
}
if(vector128Mean.getResult() > optimalResult)
{
optimal = Implementation.VECTOR_SIMD_128;
optimalResult = vector128Mean.getResult();
}
if(vector256Mean.getResult() > optimalResult)
{
optimal = Implementation.VECTOR_SIMD_256;
optimalResult = vector256Mean.getResult();
}
if(vector512Mean.getResult() > optimalResult)
{
optimal = Implementation.VECTOR_SIMD_512;
}
setImplementation(optimal);
mLog.info("AM DEMODULATOR - SET OPTIMAL IMPLEMENTATION TO:" + getImplementation());
}
private long testScalar(float[] magnitude)
{
double accumulator = 0.0;
long count = 0;
long start = System.currentTimeMillis();
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
float[] demodulated = mScalarDemodulator.demodulateMagnitude(magnitude);
accumulator += demodulated[1];
count++;
}
return count + (long)(accumulator * 0);
}
private long testVector64(float[] magnitude)
{
double accumulator = 0.0;
long count = 0;
long start = System.currentTimeMillis();
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
float[] demodulated = mVectorDemodulator64.demodulateMagnitude(magnitude);
accumulator += demodulated[1];
count++;
}
return count + (long)(accumulator * 0);
}
private long testVector128(float[] magnitude)
{
double accumulator = 0.0;
long count = 0;
long start = System.currentTimeMillis();
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
float[] demodulated = mVectorDemodulator128.demodulateMagnitude(magnitude);
accumulator += demodulated[1];
count++;
}
return count + (long)(accumulator * 0);
}
private long testVector256(float[] magnitude)
{
double accumulator = 0.0;
long count = 0;
long start = System.currentTimeMillis();
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
float[] demodulated = mVectorAMDemodulator256.demodulateMagnitude(magnitude);
accumulator += demodulated[1];
count++;
}
return count + (long)(accumulator * 0);
}
private long testVector512(float[] magnitude)
{
double accumulator = 0.0;
long count = 0;
long start = System.currentTimeMillis();
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
{
float[] demodulated = mVectorAMDemodulator512.demodulateMagnitude(magnitude);
accumulator += demodulated[1];
count++;
}
return count + (long)(accumulator * 0);
}
public static void main(String[] args)
{
AmDemodulatorCalibration calibration = new AmDemodulatorCalibration();
try
{
calibration.calibrate();
}
catch(Exception e)
{
mLog.error("Error during calibration", e);
}
}
}

View File

@@ -0,0 +1,64 @@
/*
* *****************************************************************************
* 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.am;
import io.github.dsheirer.vector.calibrate.CalibrationManager;
import io.github.dsheirer.vector.calibrate.CalibrationType;
import io.github.dsheirer.vector.calibrate.Implementation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Factory for creating AM demodulators
*/
public class AmDemodulatorFactory
{
private static final Logger mLog = LoggerFactory.getLogger(AmDemodulatorFactory.class);
/**
* Creates the optimal AM demodulator using calibration data to select the optimal
* implementation from scalar and vector options.
* @return demodulator instance
*/
public static IAmDemodulator getAmDemodulator(float gain)
{
Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.AM_DEMODULATOR);
switch(implementation)
{
case VECTOR_SIMD_64:
return new VectorAMDemodulator64(gain);
case VECTOR_SIMD_128:
return new VectorAMDemodulator128(gain);
case VECTOR_SIMD_256:
return new VectorAMDemodulator256(gain);
case VECTOR_SIMD_512:
return new VectorAMDemodulator512(gain);
case VECTOR_SIMD_PREFERRED:
return new VectorAMDemodulator64(gain);
case SCALAR:
case UNCALIBRATED:
return new ScalarAMDemodulator(gain);
default:
mLog.warn("Unrecognized optimal operation for AM demodulator: " + implementation.name());
return new ScalarAMDemodulator(gain);
}
}
}

View File

@@ -1,3 +1,22 @@
/*
* *****************************************************************************
* 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.am;
/**
@@ -6,10 +25,9 @@ package io.github.dsheirer.dsp.am;
public interface IAmDemodulator
{
/**
* Demodulate the complex I & Q sample arrays
* @param i samples
* @param q samples
* @return demodulated samples.
* Demodulates an array of vector squared magnitudes.
* @param magnitude values that are squared.
* @return demodulated samples
*/
float[] demodulate(float[] i, float[] q);
float[] demodulateMagnitude(float[] magnitude);
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -23,7 +23,7 @@ import org.apache.commons.math3.util.FastMath;
/**
* Performs AM demodulation on baseband I/Q samples to produce demodulated float output.
*/
public class AMDemodulator implements IAmDemodulator
public class ScalarAMDemodulator implements IAmDemodulator
{
private float mGain;
@@ -32,7 +32,7 @@ public class AMDemodulator implements IAmDemodulator
*
* @param gain to apply to demodulated output samples.
*/
public AMDemodulator(float gain)
public ScalarAMDemodulator(float gain)
{
mGain = gain;
}
@@ -57,13 +57,13 @@ public class AMDemodulator implements IAmDemodulator
return (float) FastMath.sqrt((inphase * inphase) + (quadrature * quadrature)) * mGain;
}
@Override public float[] demodulate(float[] i, float[] q)
@Override
public float[] demodulateMagnitude(float[] magnitude)
{
float[] demodulated = new float[i.length];
for(int x = 0; x < i.length; x++)
float[] demodulated = new float[magnitude.length];
for(int x = 0; x < magnitude.length; x++)
{
demodulated[x] = (float)FastMath.sqrt((i[x] * i[x]) + (q[x] * q[x])) * mGain;
demodulated[x] = (float)FastMath.sqrt(magnitude[x]) * mGain;
}
return demodulated;

View File

@@ -0,0 +1,161 @@
/*
* *****************************************************************************
* 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.am;
import io.github.dsheirer.dsp.fm.ISquelchingDemodulator;
import io.github.dsheirer.dsp.magnitude.IMagnitudeCalculator;
import io.github.dsheirer.dsp.magnitude.MagnitudeFactory;
import io.github.dsheirer.dsp.squelch.AdaptiveSquelch;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.SourceEvent;
/**
* AM demodulator with integrated squelch control.
*/
public class SquelchingAMDemodulator implements ISquelchingDemodulator, Listener<SourceEvent>
{
private static final float ZERO = 0.0f;
private final IMagnitudeCalculator mMagnitudeCalculator = MagnitudeFactory.getMagnitudeCalculator();
private final IAmDemodulator mAmDemodulator;
private final AdaptiveSquelch mAdaptiveSquelch;
private boolean mSquelchChanged = false;
/**
* Constructs an instance
* @param gain to apply to the demodulated AM samples (e.g. 500.0f)
* @param alpha decay value of the single pole IIR filter in range: 0.0 - 1.0. The smaller the alpha value,
* the slower the squelch response.
* @param squelchThreshold in decibels. Signal power must exceed this threshold value for unsquelch.
* @param squelchAutoTrack to enable the squelch noise floor auto tracking feature.
*/
public SquelchingAMDemodulator(float gain, float alpha, float squelchThreshold, boolean squelchAutoTrack)
{
mAmDemodulator = AmDemodulatorFactory.getAmDemodulator(gain);
mAdaptiveSquelch = new AdaptiveSquelch(alpha, squelchThreshold, squelchAutoTrack);
}
/**
* Set or update the sample rate for the squelch to adjust the power level notification rate.
* @param sampleRate in hertz
*/
public void setSampleRate(int sampleRate)
{
mAdaptiveSquelch.setSampleRate(sampleRate);
}
/**
* Registers the listener to receive notifications of squelch change events from the power squelch.
*/
public void setSourceEventListener(Listener<SourceEvent> listener)
{
mAdaptiveSquelch.setSourceEventListener(listener);
}
/**
* Demodulates the complex sample arrays.
* @param i inphase sample array
* @param q quadrature sample array
* @return demodulated AM samples with gain applied.
*/
public float[] demodulate(float[] i, float[] q)
{
mAdaptiveSquelch.setSquelchChanged(false);
setSquelchChanged(false);
float[] magnitude = mMagnitudeCalculator.calculate(i, q);
float[] demodulated = mAmDemodulator.demodulateMagnitude(magnitude);
for(int x = 0; x < i.length; x++)
{
mAdaptiveSquelch.process(magnitude[x]);
if(!(mAdaptiveSquelch.isUnmuted()))
{
demodulated[x] = ZERO;
}
if(mAdaptiveSquelch.isSquelchChanged())
{
setSquelchChanged(true);
}
}
return demodulated;
}
/**
* Sets the threshold for squelch control
* @param threshold (dB)
*/
public void setSquelchThreshold(float threshold)
{
mAdaptiveSquelch.setSquelchThreshold(threshold);
}
@Override
public void setSquelchAutoTrack(boolean autoTrack)
{
mAdaptiveSquelch.setSquelchAutoTrack(autoTrack);
}
/**
* Indicates if the squelch state has changed during the processing of buffer(s)
*/
public boolean isSquelchChanged()
{
return mSquelchChanged;
}
/**
* Sets or resets the squelch changed flag.
*/
private void setSquelchChanged(boolean changed)
{
mSquelchChanged = changed;
}
/**
* Indicates if the squelch state is currently muted
*/
public boolean isMuted()
{
return mAdaptiveSquelch.isMuted();
}
/**
* Process source events initiated by the timer and end-user.
* @param sourceEvent to process.
*/
@Override
public void receive(SourceEvent sourceEvent)
{
switch(sourceEvent.getEvent())
{
//Only forward squelch threshold & auto-track request events
case REQUEST_CURRENT_SQUELCH_THRESHOLD:
case REQUEST_CHANGE_SQUELCH_THRESHOLD:
case REQUEST_CURRENT_SQUELCH_AUTO_TRACK:
case REQUEST_CHANGE_SQUELCH_AUTO_TRACK:
{
mAdaptiveSquelch.receive(sourceEvent);
}
}
}
}

View File

@@ -0,0 +1,83 @@
/*
* *****************************************************************************
* 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.am;
import io.github.dsheirer.dsp.magnitude.IMagnitudeCalculator;
import io.github.dsheirer.dsp.magnitude.MagnitudeFactory;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorSpecies;
import org.apache.commons.math3.util.FastMath;
/**
* Performs AM demodulation on baseband I/Q samples to produce demodulated float output.
*/
public class VectorAMDemodulator128 implements IAmDemodulator
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_128;
private float mGain;
private IMagnitudeCalculator mMagnitudeCalculator = MagnitudeFactory.getMagnitudeCalculator();
/**
* Constructs this demodulator where the specified gain is applied to demodulated output samples.
*
* @param gain to apply to demodulated output samples.
*/
public VectorAMDemodulator128(float gain)
{
mGain = gain;
}
/**
* Sets the gain to the specified level.
*/
public void setGain(float gain)
{
mGain = gain;
}
/**
* Demodulates the comples I/Q sample and returns the demodulated output sample.
*
* @param inphase sample
* @param quadrature sample
* @return AM demodulated sample
*/
public float demodulate(float inphase, float quadrature)
{
return (float) FastMath.sqrt((inphase * inphase) + (quadrature * quadrature)) * mGain;
}
@Override
public float[] demodulateMagnitude(float[] magnitude)
{
VectorUtilities.checkArrayLength(magnitude, VECTOR_SPECIES);
FloatVector gain = FloatVector.broadcast(VECTOR_SPECIES, mGain);
float[] demodulated = new float[magnitude.length];
FloatVector vector;
for(int x = 0; x < magnitude.length; x += VECTOR_SPECIES.length())
{
vector = FloatVector.fromArray(VECTOR_SPECIES, magnitude, x);
vector.sqrt().mul(gain).intoArray(demodulated, x);
}
return demodulated;
}
}

View File

@@ -0,0 +1,83 @@
/*
* *****************************************************************************
* 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.am;
import io.github.dsheirer.dsp.magnitude.IMagnitudeCalculator;
import io.github.dsheirer.dsp.magnitude.MagnitudeFactory;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorSpecies;
import org.apache.commons.math3.util.FastMath;
/**
* Performs AM demodulation on baseband I/Q samples to produce demodulated float output.
*/
public class VectorAMDemodulator256 implements IAmDemodulator
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_256;
private float mGain;
private IMagnitudeCalculator mMagnitudeCalculator = MagnitudeFactory.getMagnitudeCalculator();
/**
* Constructs this demodulator where the specified gain is applied to demodulated output samples.
*
* @param gain to apply to demodulated output samples.
*/
public VectorAMDemodulator256(float gain)
{
mGain = gain;
}
/**
* Sets the gain to the specified level.
*/
public void setGain(float gain)
{
mGain = gain;
}
/**
* Demodulates the comples I/Q sample and returns the demodulated output sample.
*
* @param inphase sample
* @param quadrature sample
* @return AM demodulated sample
*/
public float demodulate(float inphase, float quadrature)
{
return (float) FastMath.sqrt((inphase * inphase) + (quadrature * quadrature)) * mGain;
}
@Override
public float[] demodulateMagnitude(float[] magnitude)
{
VectorUtilities.checkArrayLength(magnitude, VECTOR_SPECIES);
FloatVector gain = FloatVector.broadcast(VECTOR_SPECIES, mGain);
float[] demodulated = new float[magnitude.length];
FloatVector vector;
for(int x = 0; x < magnitude.length; x += VECTOR_SPECIES.length())
{
vector = FloatVector.fromArray(VECTOR_SPECIES, magnitude, x);
vector.sqrt().mul(gain).intoArray(demodulated, x);
}
return demodulated;
}
}

View File

@@ -0,0 +1,83 @@
/*
* *****************************************************************************
* 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.am;
import io.github.dsheirer.dsp.magnitude.IMagnitudeCalculator;
import io.github.dsheirer.dsp.magnitude.MagnitudeFactory;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorSpecies;
import org.apache.commons.math3.util.FastMath;
/**
* Performs AM demodulation on baseband I/Q samples to produce demodulated float output.
*/
public class VectorAMDemodulator512 implements IAmDemodulator
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_512;
private float mGain;
private IMagnitudeCalculator mMagnitudeCalculator = MagnitudeFactory.getMagnitudeCalculator();
/**
* Constructs this demodulator where the specified gain is applied to demodulated output samples.
*
* @param gain to apply to demodulated output samples.
*/
public VectorAMDemodulator512(float gain)
{
mGain = gain;
}
/**
* Sets the gain to the specified level.
*/
public void setGain(float gain)
{
mGain = gain;
}
/**
* Demodulates the comples I/Q sample and returns the demodulated output sample.
*
* @param inphase sample
* @param quadrature sample
* @return AM demodulated sample
*/
public float demodulate(float inphase, float quadrature)
{
return (float) FastMath.sqrt((inphase * inphase) + (quadrature * quadrature)) * mGain;
}
@Override
public float[] demodulateMagnitude(float[] magnitude)
{
VectorUtilities.checkArrayLength(magnitude, VECTOR_SPECIES);
FloatVector gain = FloatVector.broadcast(VECTOR_SPECIES, mGain);
float[] demodulated = new float[magnitude.length];
FloatVector vector;
for(int x = 0; x < magnitude.length; x += VECTOR_SPECIES.length())
{
vector = FloatVector.fromArray(VECTOR_SPECIES, magnitude, x);
vector.sqrt().mul(gain).intoArray(demodulated, x);
}
return demodulated;
}
}

View File

@@ -0,0 +1,83 @@
/*
* *****************************************************************************
* 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.am;
import io.github.dsheirer.dsp.magnitude.IMagnitudeCalculator;
import io.github.dsheirer.dsp.magnitude.MagnitudeFactory;
import io.github.dsheirer.vector.VectorUtilities;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorSpecies;
import org.apache.commons.math3.util.FastMath;
/**
* Performs AM demodulation on baseband I/Q samples to produce demodulated float output.
*/
public class VectorAMDemodulator64 implements IAmDemodulator
{
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_64;
private float mGain;
private IMagnitudeCalculator mMagnitudeCalculator = MagnitudeFactory.getMagnitudeCalculator();
/**
* Constructs this demodulator where the specified gain is applied to demodulated output samples.
*
* @param gain to apply to demodulated output samples.
*/
public VectorAMDemodulator64(float gain)
{
mGain = gain;
}
/**
* Sets the gain to the specified level.
*/
public void setGain(float gain)
{
mGain = gain;
}
/**
* Demodulates the comples I/Q sample and returns the demodulated output sample.
*
* @param inphase sample
* @param quadrature sample
* @return AM demodulated sample
*/
public float demodulate(float inphase, float quadrature)
{
return (float) FastMath.sqrt((inphase * inphase) + (quadrature * quadrature)) * mGain;
}
@Override
public float[] demodulateMagnitude(float[] magnitude)
{
VectorUtilities.checkArrayLength(magnitude, VECTOR_SPECIES);
FloatVector gain = FloatVector.broadcast(VECTOR_SPECIES, mGain);
float[] demodulated = new float[magnitude.length];
FloatVector vector;
for(int x = 0; x < magnitude.length; x += VECTOR_SPECIES.length())
{
vector = FloatVector.fromArray(VECTOR_SPECIES, magnitude, x);
vector.sqrt().mul(gain).intoArray(demodulated, x);
}
return demodulated;
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -122,6 +122,11 @@ public abstract class AbstractComplexPolyphaseChannelizer implements Listener<In
mChannels.add(polyphaseChannelSource);
mSourceChangeBroadcaster.addListener(polyphaseChannelSource.getSourceEventListener());
}
else
{
mLog.error("Error adding polyphase channel source - " + (polyphaseChannelSource == null ? "source is null" :
"channel source is already added to this channelizer"));
}
}
/**
@@ -136,6 +141,11 @@ public abstract class AbstractComplexPolyphaseChannelizer implements Listener<In
mChannels.remove(polyphaseChannelSource);
mSourceChangeBroadcaster.removeListener(polyphaseChannelSource.getSourceEventListener());
}
else
{
mLog.error("Error removing polyphase channel source - " + (polyphaseChannelSource == null ? "source is null" :
"channel source was not previously added to this channelizer"));
}
}
/**

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -24,7 +24,6 @@ import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
import io.github.dsheirer.util.Dispatcher;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.commons.math3.util.FastMath;
import org.jtransforms.fft.FloatFFT_1D;
@@ -69,8 +68,8 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
*/
private static final int PROCESSED_CHANNEL_RESULTS_THRESHOLD = 1024;
//Sized at 152 buffers a second where max = 5 seconds
private IFFTProcessorDispatcher mIFFTProcessorDispatcher = new IFFTProcessorDispatcher(5 * 152);
//Sized to process 1/20th batch of 152 buffers a second, at 40 times per second
private IFFTProcessorDispatcher mIFFTProcessorDispatcher = new IFFTProcessorDispatcher(152 / 20, 25);
private FloatFFT_1D mFFT;
private float[] mInlineSamples;
private float[] mInlineFilter;
@@ -407,25 +406,35 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
*/
public class IFFTProcessorDispatcher extends Dispatcher<List<float[]>>
{
public IFFTProcessorDispatcher(int maximumSize)
public IFFTProcessorDispatcher(int batchSize, long interval)
{
super(maximumSize, "sdrtrunk polyphase ifft processor", Collections.emptyList());
super("sdrtrunk polyphase ifft processor", batchSize, interval);
//We create a listener interface to receive the batched channel results arrays from the scheduled thread pool
//dispatcher thread that is part of this continuous buffer processor. We perform an IFFT on each
//channel results array contained in each results buffer and then dispatch the buffer
//so that it can be distributed to each channel listener.
setListener(list -> {
List<float[]> processedChannelResults = new ArrayList<>();
for(float[] channelResults: list)
try
{
//Rotate each of the channels to the correct phase using the IFFT
mFFT.complexInverse(channelResults, true);
processedChannelResults.add(channelResults);
}
List<float[]> processedChannelResults = new ArrayList<>();
dispatch(processedChannelResults);
for(float[] channelResults: list)
{
if(channelResults != null)
{
//Rotate each of the channels to the correct phase using the IFFT
mFFT.complexInverse(channelResults, true);
processedChannelResults.add(channelResults);
}
}
dispatch(processedChannelResults);
}
catch(Throwable t)
{
mLog.error("Error during IFFT and dispatch of processed channel results", t);
}
});
}
}

View File

@@ -20,15 +20,14 @@ package io.github.dsheirer.dsp.filter.channelizer;
import io.github.dsheirer.buffer.INativeBuffer;
import io.github.dsheirer.buffer.INativeBufferProvider;
import io.github.dsheirer.buffer.NativeBufferPoisonPill;
import io.github.dsheirer.controller.channel.event.ChannelStopProcessingRequest;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.log.LoggingSuppressor;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.Source;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.TunerController;
@@ -44,7 +43,6 @@ import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -68,6 +66,7 @@ import org.slf4j.LoggerFactory;
public class PolyphaseChannelManager implements ISourceEventProcessor
{
private static final DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000");
private static final LoggingSuppressor LOGGING_SUPPRESSOR = new LoggingSuppressor(LoggerFactory.getLogger(PolyphaseChannelManager.class));
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class);
private static final double MINIMUM_CHANNEL_BANDWIDTH = 25000.0;
private static final double CHANNEL_OVERSAMPLING = 2.0;
@@ -111,8 +110,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
}
mChannelCalculator = new ChannelCalculator(sampleRate, channelCount, frequency, CHANNEL_OVERSAMPLING);
mBufferDispatcher = new Dispatcher(500, "sdrtrunk polyphase buffer processor",
new NativeBufferPoisonPill());
mBufferDispatcher = new Dispatcher("sdrtrunk polyphase buffer processor", 50, 10);
mBufferDispatcher.setListener(mNativeBufferReceiver);
}
@@ -178,14 +176,6 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
}
}
/**
* Current channel sample rate which is (2 * channel bandwidth).
*/
public double getChannelSampleRate()
{
return mChannelCalculator.getChannelSampleRate();
}
/**
* Current channel bandwidth/spacing.
*/
@@ -215,7 +205,8 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
}
catch(IllegalArgumentException iae)
{
mLog.debug("Couldn't design final output low pass filter for polyphase channel source");
LOGGING_SUPPRESSOR.error(iae.getMessage(), 3, "Couldn't allocate channel. " + iae.getMessage());
channelSource = null;
}
}
@@ -440,9 +431,10 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
}
break;
case REQUEST_STOP_SAMPLE_STREAM:
if(sourceEvent.hasSource() && sourceEvent.getSource() instanceof PolyphaseChannelSource)
if(sourceEvent.hasSource() && sourceEvent.getSource() instanceof PolyphaseChannelSource channelSource)
{
stopChannelSource((PolyphaseChannelSource)sourceEvent.getSource());
stopChannelSource(channelSource);
channelSource.dispose();
}
else
{
@@ -450,14 +442,6 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
(sourceEvent.hasSource() ? sourceEvent.getSource().getClass() : "null source"));
}
break;
case REQUEST_SOURCE_DISPOSE:
Source source = sourceEvent.getSource();
if(source instanceof PolyphaseChannelSource)
{
source.dispose();
}
break;
case NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED:
//Rebroadcast so that the tuner source can process this event
mSourceEventBroadcaster.broadcast(sourceEvent);
@@ -481,7 +465,6 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
public class NativeBufferReceiver implements Listener<INativeBuffer>
{
private boolean mOutputProcessorUpdateRequired = false;
private ReentrantLock mUpdateFlagLock = new ReentrantLock();
/**
* Processes tuner center frequency change source events to flag when output processors need updating.
@@ -493,18 +476,9 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
if(mChannelCalculator.getCenterFrequency() != frequency)
{
mUpdateFlagLock.lock();
try
{
//Update the channel calculator frequency so that it's ready when the output processor update occurs
mChannelCalculator.setCenterFrequency(frequency);
mOutputProcessorUpdateRequired = true;
}
finally
{
mUpdateFlagLock.unlock();
}
//Update the channel calculator frequency so that it's ready when the output processor update occurs
mChannelCalculator.setCenterFrequency(frequency);
mOutputProcessorUpdateRequired = true;
}
}
@@ -516,19 +490,17 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
@Override
public void receive(INativeBuffer nativeBuffer)
{
mUpdateFlagLock.lock();
try
if(mOutputProcessorUpdateRequired)
{
if(mOutputProcessorUpdateRequired)
try
{
updateOutputProcessors();
mOutputProcessorUpdateRequired = false;
}
}
finally
{
mUpdateFlagLock.unlock();
catch(Exception e)
{
mLog.error("Error updating polyphase channel output processors");
}
mOutputProcessorUpdateRequired = false;
}
if(mPolyphaseChannelizer != null)

View File

@@ -25,12 +25,10 @@ import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexSamples;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.tuner.channel.StreamProcessorWithHeartbeat;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -43,15 +41,13 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
{
private Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSource.class);
private IPolyphaseChannelOutputProcessor mPolyphaseChannelOutputProcessor;
private StreamProcessorWithHeartbeat<ComplexSamples> mStreamHeartbeatProcessor;
private Listener<ComplexSamples> mSamplesListener;
private double mChannelSampleRate;
private long mIndexCenterFrequency;
private ReentrantLock mOutputProcessorLock = new ReentrantLock();
private List<Integer> mOutputProcessorIndexes = new ArrayList<>();
private double mTunerSampleRate;
private double mTunerCenterFrequency;
private PendingOutputProcessorUpdate mPendingOutputProcessorUpdate;
/**
* Constructs an instance
@@ -67,8 +63,7 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
{
super(producerSourceEventListener, tunerChannel);
mChannelSampleRate = channelCalculator.getChannelSampleRate();
mStreamHeartbeatProcessor = new StreamProcessorWithHeartbeat<>(getHeartbeatManager(), HEARTBEAT_INTERVAL_MS);
updateOutputProcessor(channelCalculator, filterManager);
doUpdateOutputProcessor(channelCalculator, filterManager);
}
/**
@@ -111,8 +106,6 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
{
super.start();
mStreamHeartbeatProcessor.start();
if(mPolyphaseChannelOutputProcessor != null)
{
mPolyphaseChannelOutputProcessor.start();
@@ -128,8 +121,6 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
{
mPolyphaseChannelOutputProcessor.stop();
}
mStreamHeartbeatProcessor.stop();
}
/**
@@ -138,112 +129,129 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
@Override
public void setListener(final Listener<ComplexSamples> listener)
{
mStreamHeartbeatProcessor.setListener(listener);
mSamplesListener = listener;
mPolyphaseChannelOutputProcessor.setListener(this);
}
@Override
public void receive(ComplexSamples complexSamples)
{
mStreamHeartbeatProcessor.receive(complexSamples);
if(mSamplesListener != null)
{
try
{
mSamplesListener.receive(complexSamples);
}
catch(Throwable t)
{
mLog.error("Error dispatching complex samples to listener [" + mSamplesListener.getClass() + "]", t);
}
}
}
/**
* Updates the output processor whenever the source tuner's center frequency changes.
* Queues a request to update the output processor whenever the source tuner's center frequency changes.
* @param channelCalculator providing access to updated tuner center frequency, sample rate, etc.
* @param filterManager for designing and caching synthesis filters
*/
public void updateOutputProcessor(ChannelCalculator channelCalculator, SynthesisFilterManager filterManager) throws IllegalArgumentException
public void updateOutputProcessor(ChannelCalculator channelCalculator, SynthesisFilterManager filterManager)
throws IllegalArgumentException
{
mPendingOutputProcessorUpdate = new PendingOutputProcessorUpdate(channelCalculator, filterManager);
}
/**
* Performs the output processor update. This method must be invoked on the channel results calling thread to
* ensure we don't have thread contention on the output processor.
* @param channelCalculator for updates
* @param filterManager for updates
* @throws IllegalArgumentException if there's an issue.
*/
public void doUpdateOutputProcessor(ChannelCalculator channelCalculator, SynthesisFilterManager filterManager)
throws IllegalArgumentException
{
String errorMessage = null;
mOutputProcessorLock.lock();
//If a change in sample rate or center frequency makes this channel no longer viable, then the channel
//calculator will throw an IllegalArgException ... handled below
List<Integer> indexes = channelCalculator.getChannelIndexes(getTunerChannel());
try
mOutputProcessorIndexes.clear();
mOutputProcessorIndexes.addAll(indexes);
mTunerCenterFrequency = channelCalculator.getCenterFrequency();
mTunerSampleRate = channelCalculator.getSampleRate();
//The provided channels are necessarily aligned to the center frequency that this source is providing and an
//oscillator will mix the provided channels to bring the desired center frequency to baseband.
setFrequency(channelCalculator.getCenterFrequencyForIndexes(indexes));
if(mPolyphaseChannelOutputProcessor != null && mPolyphaseChannelOutputProcessor.getInputChannelCount() == indexes.size())
{
//If a change in sample rate or center frequency makes this channel no longer viable, then the channel
//calculator will throw an IllegalArgException ... handled below
List<Integer> indexes = channelCalculator.getChannelIndexes(getTunerChannel());
mPolyphaseChannelOutputProcessor.setPolyphaseChannelIndices(indexes);
mOutputProcessorIndexes.clear();
mOutputProcessorIndexes.addAll(indexes);
mTunerCenterFrequency = channelCalculator.getCenterFrequency();
mTunerSampleRate = channelCalculator.getSampleRate();
//The provided channels are necessarily aligned to the center frequency that this source is providing and an
//oscillator will mix the provided channels to bring the desired center frequency to baseband.
setFrequency(channelCalculator.getCenterFrequencyForIndexes(indexes));
if(mPolyphaseChannelOutputProcessor != null && mPolyphaseChannelOutputProcessor.getInputChannelCount() == indexes.size())
if(indexes.size() > 1)
{
mPolyphaseChannelOutputProcessor.setPolyphaseChannelIndices(indexes);
if(indexes.size() > 1)
try
{
float[] filter = filterManager.getFilter(channelCalculator.getChannelSampleRate(),
channelCalculator.getChannelBandwidth(), indexes.size());
mPolyphaseChannelOutputProcessor.setSynthesisFilter(filter);
}
catch(FilterDesignException fde)
{
mLog.error("Error creating an updated synthesis filter for the channel output processor");
errorMessage ="Cannot update output processor - unable to design synthesis filter for [" +
indexes.size() + "] channel indices - channel sample rate [" + channelCalculator.getChannelSampleRate() +
"] channel bandwidth [" + channelCalculator.getChannelBandwidth() + "]";
}
}
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
}
else //Create a new output processor.
{
if(mPolyphaseChannelOutputProcessor != null)
{
mPolyphaseChannelOutputProcessor.setListener(null);
mPolyphaseChannelOutputProcessor.stop();
}
mPolyphaseChannelOutputProcessor = null;
switch(indexes.size())
{
case 1:
mPolyphaseChannelOutputProcessor = new OneChannelOutputProcessor(channelCalculator.getChannelSampleRate(),
indexes, channelCalculator.getChannelCount(), getHeartbeatManager());
mPolyphaseChannelOutputProcessor.setListener(this);
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
mPolyphaseChannelOutputProcessor.start();
break;
case 2:
try
{
float[] filter = filterManager.getFilter(channelCalculator.getChannelSampleRate(),
channelCalculator.getChannelBandwidth(), indexes.size());
mPolyphaseChannelOutputProcessor.setSynthesisFilter(filter);
}
catch(FilterDesignException fde)
{
mLog.error("Error creating an updated synthesis filter for the channel output processor");
errorMessage ="Cannot update output processor - unable to design synthesis filter for [" +
indexes.size() + "] channel indices";
}
}
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
}
else //Create a new output processor.
{
if(mPolyphaseChannelOutputProcessor != null)
{
mPolyphaseChannelOutputProcessor.setListener(null);
}
mPolyphaseChannelOutputProcessor = null;
switch(indexes.size())
{
case 1:
mPolyphaseChannelOutputProcessor = new OneChannelOutputProcessor(channelCalculator.getChannelSampleRate(),
indexes, channelCalculator.getChannelCount());
channelCalculator.getChannelBandwidth(), 2);
mPolyphaseChannelOutputProcessor = new TwoChannelOutputProcessor(channelCalculator.getChannelSampleRate(),
indexes, filter, channelCalculator.getChannelCount(), getHeartbeatManager());
mPolyphaseChannelOutputProcessor.setListener(this);
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
mPolyphaseChannelOutputProcessor.start();
break;
case 2:
try
{
float[] filter = filterManager.getFilter(channelCalculator.getChannelSampleRate(),
channelCalculator.getChannelBandwidth(), 2);
mPolyphaseChannelOutputProcessor = new TwoChannelOutputProcessor(channelCalculator.getChannelSampleRate(),
indexes, filter, channelCalculator.getChannelCount());
mPolyphaseChannelOutputProcessor.setListener(this);
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
mPolyphaseChannelOutputProcessor.start();
}
catch(FilterDesignException fde)
{
errorMessage = "Cannot create new output processor - unable to design synthesis filter for [" +
indexes.size() + "] channel indices";
mLog.error("Error creating a synthesis filter for a new channel output processor");
}
break;
default:
mLog.error("Request to create an output processor for unexpected channel index size:" + indexes.size());
mLog.info(channelCalculator.toString());
errorMessage = "Unable to create new channel output processor - unexpected channel index size: " +
indexes.size();
}
}
catch(FilterDesignException fde)
{
errorMessage = "Cannot create new output processor - unable to design synthesis filter for [" +
indexes.size() + "] channel indices";
mLog.error("Error creating a synthesis filter for a new channel output processor");
}
break;
default:
mLog.error("Request to create an output processor for unexpected channel index size:" + indexes.size());
mLog.info(channelCalculator.toString());
errorMessage = "Unable to create new channel output processor - unexpected channel index size: " +
indexes.size();
}
}
finally
{
mOutputProcessorLock.unlock();
}
//Unlikely, but if we had an error designing a synthesis filter, throw an exception
if(errorMessage != null)
@@ -262,7 +270,13 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
*/
public void receiveChannelResults(List<float[]> channelResultsList, long currentSamplesTimestamp)
{
mOutputProcessorLock.lock();
if(mPendingOutputProcessorUpdate != null)
{
ChannelCalculator channelCalculator = mPendingOutputProcessorUpdate.getChannelCalculator();
SynthesisFilterManager filterManager = mPendingOutputProcessorUpdate.getSynthesisFilterManager();
mPendingOutputProcessorUpdate = null;
doUpdateOutputProcessor(channelCalculator, filterManager);
}
try
{
@@ -271,9 +285,9 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsList, currentSamplesTimestamp);
}
}
finally
catch(Exception e)
{
mOutputProcessorLock.unlock();
mLog.error("Error processing channel results", e);
}
}
@@ -342,4 +356,40 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
return "POLYPHASE [" + mPolyphaseChannelOutputProcessor.getInputChannelCount() + "] " +
getTunerChannel().getFrequency();
}
/**
* Support classes needed to update the output processor.
*/
public class PendingOutputProcessorUpdate
{
private final ChannelCalculator mChannelCalculator;
private final SynthesisFilterManager mSynthesisFilterManager;
/**
* Constructs an instance
* @param channelCalculator for support
* @param synthesisFilterManager for support
*/
public PendingOutputProcessorUpdate(ChannelCalculator channelCalculator, SynthesisFilterManager synthesisFilterManager)
{
mChannelCalculator = channelCalculator;
mSynthesisFilterManager = synthesisFilterManager;
}
/**
* Channel calculator
*/
public ChannelCalculator getChannelCalculator()
{
return mChannelCalculator;
}
/**
* Channel calculator
*/
public SynthesisFilterManager getSynthesisFilterManager()
{
return mSynthesisFilterManager;
}
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -20,8 +20,8 @@ package io.github.dsheirer.dsp.filter.channelizer.output;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexSamples;
import io.github.dsheirer.source.heartbeat.HeartbeatManager;
import io.github.dsheirer.util.Dispatcher;
import java.util.Collections;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -31,6 +31,7 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
private final static Logger mLog = LoggerFactory.getLogger(ChannelOutputProcessor.class);
private Dispatcher<List<float[]>> mChannelResultsDispatcher;
private HeartbeatManager mHeartbeatManager;
protected Listener<ComplexSamples> mComplexSamplesListener;
private int mInputChannelCount;
private long mCurrentSampleTimestamp = System.currentTimeMillis();
@@ -42,12 +43,25 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
* @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
*/
public ChannelOutputProcessor(int inputChannelCount, double sampleRate)
public ChannelOutputProcessor(int inputChannelCount, double sampleRate, HeartbeatManager heartbeatManager)
{
mInputChannelCount = inputChannelCount;
mChannelResultsDispatcher = new Dispatcher<>((int)sampleRate, "sdrtrunk polyphase channel", Collections.emptyList());
mChannelResultsDispatcher.setListener(floats -> process(floats));
//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", (int)(sampleRate / 10),
50, mHeartbeatManager);
mChannelResultsDispatcher.setListener(floats -> {
try
{
process(floats);
}
catch(Throwable t)
{
mLog.error("Error processing channel results", t);
}
});
}
/**

View File

@@ -19,6 +19,7 @@
package io.github.dsheirer.dsp.filter.channelizer.output;
import io.github.dsheirer.sample.complex.ComplexSamples;
import io.github.dsheirer.source.heartbeat.HeartbeatManager;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -36,10 +37,12 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
* @param sampleRate of the output sample stream.
* @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
*/
public OneChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float gain)
public OneChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float gain,
HeartbeatManager heartbeatManager)
{
super(1, sampleRate);
super(1, sampleRate, heartbeatManager);
setPolyphaseChannelIndices(channelIndexes);
mMixerAssembler = new OneChannelMixerAssembler(gain);
mMixerAssembler.getMixer().setSampleRate(sampleRate);

View File

@@ -19,6 +19,7 @@
package io.github.dsheirer.dsp.filter.channelizer.output;
import io.github.dsheirer.sample.complex.ComplexSamples;
import io.github.dsheirer.source.heartbeat.HeartbeatManager;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -37,12 +38,14 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
* @param sampleRate of the output sample stream.
* @param channelIndexes containing two channel indices.
* @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
*/
public TwoChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float[] filter, float gain)
public TwoChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float[] filter, float gain,
HeartbeatManager heartbeatManager)
{
//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);
super(2, sampleRate, heartbeatManager);
setPolyphaseChannelIndices(channelIndexes);
mMixerAssembler = new TwoChannelMixerAssembler(gain);
mMixerAssembler.getMixer().setSampleRate(sampleRate);

View File

@@ -1,3 +1,22 @@
/*
* *****************************************************************************
* 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.iir;
/**
@@ -40,7 +59,7 @@ public class SinglePoleIirFilter
* @param sample to process/filter
* @return filtered output
*/
public double filter(float sample)
public float filter(float sample)
{
mOutput = (mOutput * mOneMinusAlpha) + (mAlpha * sample);
return mOutput;
@@ -49,7 +68,7 @@ public class SinglePoleIirFilter
/**
* Current output value
*/
public double getValue()
public float getValue()
{
return mOutput;
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -37,40 +37,23 @@ public class FmDemodulatorFactory
* implementation from scalar and vector options.
* @return demodulator instance
*/
public static IFmDemodulator getFmDemodulator()
public static IDemodulator getFmDemodulator()
{
Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.FM_DEMODULATOR);
switch(implementation)
{
case VECTOR_SIMD_PREFERRED:
return new VectorFMDemodulator();
case VECTOR_SIMD_64:
return new VectorFMDemodulator64();
case VECTOR_SIMD_128:
return new VectorFMDemodulator128();
case VECTOR_SIMD_256:
return new VectorFMDemodulator256();
case VECTOR_SIMD_512:
return new VectorFMDemodulator512();
case SCALAR:
return new ScalarFMDemodulator();
default:
mLog.warn("Unrecognized optimal operation for FM demodulator: " + implementation.name());
return new ScalarFMDemodulator();
}
}
/**
* Creates the optimal Squelching FM demodulator using calibration data to select the optimal
* implementation from scalar and vector options.
* @return demodulator instance
*/
public static ISquelchingFmDemodulator getSquelchingFmDemodulator(float alpha, float threshold, int ramp)
{
Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.FM_DEMODULATOR);
switch(implementation)
{
case VECTOR_SIMD_PREFERRED:
return new VectorSquelchingFMDemodulator(alpha, threshold, ramp);
case SCALAR:
return new ScalarSquelchingFMDemodulator(alpha, threshold, ramp);
default:
mLog.warn("Unrecognized optimal operation for Squelching FM demodulator: " + implementation.name());
return new ScalarSquelchingFMDemodulator(alpha, threshold, ramp);
}
}
}

View File

@@ -0,0 +1,34 @@
/*
* *****************************************************************************
* 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.fm;
/**
* Interface for demodulator of complex sample arrays.
*/
public interface IDemodulator
{
/**
* Demodulate the complex sample array and return an array of real audio samples.
* @param i samples to demodulate
* @param q samples to demodulate
* @return demodulated samples.
*/
float[] demodulate(float[] i, float[] q);
}

View File

@@ -1,14 +0,0 @@
package io.github.dsheirer.dsp.fm;
/**
* Interface for FM demodulator
*/
public interface IFmDemodulator
{
/**
* Demodulate the complex sample array and return an array of real audio samples.
* @param samples to demodulate
* @return demodulated samples.
*/
float[] demodulate(float[] i, float[] q);
}

View File

@@ -0,0 +1,69 @@
/*
* *****************************************************************************
* 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.fm;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.SourceEvent;
/**
* Interface for FM demodulator that provides squelch control.
*/
public interface ISquelchingDemodulator extends IDemodulator
{
/**
* Sets the sample rate so that the squelch can adjust the channel power level broadcast notification rate
* @param sampleRate in hertz
*/
void setSampleRate(int sampleRate);
/**
* Indicates if the squelch state has changed.
*/
boolean isSquelchChanged();
/**
* Indicates if the squelch state is currently muted
*/
boolean isMuted();
/**
* Sets the threshold to use for power squelch
* @param threshold in decibels
*/
void setSquelchThreshold(float threshold);
/**
* Enables or disables the squelch auto-tracking feature where the squelch threshold is auto-adjusted based on the
* observed channel power when the signal is not present (ie squelch state).
* @param autoTrack true to enable
*/
void setSquelchAutoTrack(boolean autoTrack);
/**
* Registers the listener to receive notifications of squelch change events from the power squelch.
*/
void setSourceEventListener(Listener<SourceEvent> listener);
/**
* Receives a source event
*/
void receive(SourceEvent sourceEvent);
}

View File

@@ -1,42 +0,0 @@
package io.github.dsheirer.dsp.fm;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.SourceEvent;
/**
* Interface for FM demodulator that provides squelch control.
*/
public interface ISquelchingFmDemodulator extends IFmDemodulator
{
/**
* Indicates if the squelch state has changed.
*/
boolean isSquelchChanged();
/**
* Indicates if the squelch state is currently muted
*/
boolean isMuted();
/**
* Sets the threshold to use for power squelch
* @param threshold in decibels
*/
void setSquelchThreshold(double threshold);
/**
* Resets the demodulator
*/
void reset();
/**
* Registers the listener to receive notifications of squelch change events from the power squelch.
*/
void setSourceEventListener(Listener<SourceEvent> listener);
/**
* Receives a source event
*/
void receive(SourceEvent sourceEvent);
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -18,16 +18,15 @@
*/
package io.github.dsheirer.dsp.fm;
import io.github.dsheirer.sample.complex.Complex;
import org.apache.commons.math3.util.FastMath;
/**
* FM Demodulator for demodulating complex samples and producing demodulated floating point samples.
*/
public class ScalarFMDemodulator implements IFmDemodulator
public class ScalarFMDemodulator implements IDemodulator
{
private float mPreviousI = 0.0f;
private float mPreviousQ = 0.0f;
protected float mPreviousI = 0.0f;
protected float mPreviousQ = 0.0f;
protected float mGain;
/**
@@ -47,50 +46,6 @@ public class ScalarFMDemodulator implements IFmDemodulator
mGain = gain;
}
/**
* Demodulates the I/Q sample via complex sample multiplication by the complex conjugates of the most recently
* demodulated complex sample (ie previous sample). Each new sample overwrites the previously stored sample to
* allow future invocations of the method to simply use a new sample value.
*
* @param currentI of the sample
* @param currentQ of the sample
* @return demodulated sample
*/
public float demodulate(float currentI, float currentQ)
{
/**
* Multiply the current sample against the complex conjugate of the
* previous sample to derive the phase delta between the two samples
*
* Negating the previous sample quadrature produces the conjugate
*/
double inphase = (currentI * mPreviousI) - (currentQ * -mPreviousQ);
double quadrature = (currentQ * mPreviousI) + (currentI * -mPreviousQ);
double angle = 0.0f;
//Check for divide by zero
if(inphase != 0)
{
/**
* Use the arc-tangent of quadrature divided by inphase to
* get the phase angle (+/-) which was directly manipulated by the
* original message waveform during the modulation. This value now
* serves as the instantaneous amplitude of the demodulated signal
*/
double denominator = 1.0d / inphase;
angle = FastMath.atan((double)quadrature * denominator);
}
/**
* Store the current sample to use during the next iteration
*/
mPreviousI = currentI;
mPreviousQ = currentQ;
return (float)(angle * mGain);
}
public float[] demodulate(float[] i, float[] q)
{
float[] demodulated = new float[i.length];
@@ -135,35 +90,6 @@ public class ScalarFMDemodulator implements IFmDemodulator
return demodulated;
}
/**
* Demodulates the complex samples and returns the demodulated value.
* @param previous
* @param current
* @return
*/
public static double demodulate(Complex previous, Complex current)
{
double inphase = (current.inphase() * previous.inphase()) - (current.quadrature() * -previous.quadrature());
double quadrature = (current.quadrature() * previous.inphase()) + (current.inphase() * -previous.quadrature());
double angle = 0.0f;
//Check for divide by zero
if(inphase != 0)
{
/**
* Use the arc-tangent of quadrature divided by inphase to
* get the phase angle (+/-) which was directly manipulated by the
* original message waveform during the modulation. This value now
* serves as the instantaneous amplitude of the demodulated signal
*/
double denominator = 1.0d / inphase;
angle = FastMath.atan((double)quadrature * denominator);
}
return angle;
}
public void dispose()
{
//no-op

View File

@@ -1,98 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.dsp.fm;
import io.github.dsheirer.dsp.squelch.PowerSquelch;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.SourceEvent;
/**
* FM Demodulator for demodulating complex samples and producing demodulated floating point samples.
*
* Implements listener of source events to process runtime squelch threshold change request events
* which are forwarded to the power squelch control.
*/
public class ScalarSquelchingFMDemodulator extends ScalarFMDemodulator implements ISquelchingFmDemodulator, Listener<SourceEvent>
{
private PowerSquelch mPowerSquelch;
private boolean mSquelchChanged = false;
/**
* Creates an FM demodulator instance with a default gain of 1.0.
*/
public ScalarSquelchingFMDemodulator(float alpha, float threshold, int ramp)
{
mPowerSquelch = new PowerSquelch(alpha, threshold, ramp);
}
/**
* Registers the listener to receive notifications of squelch change events from the power squelch.
*/
public void setSourceEventListener(Listener<SourceEvent> listener)
{
mPowerSquelch.setSourceEventListener(listener);
}
/**
* Sets the threshold for squelch control
* @param threshold (dB)
*/
public void setSquelchThreshold(double threshold)
{
mPowerSquelch.setSquelchThreshold(threshold);
}
/**
* Indicates if the squelch state has changed during the processing of buffer(s)
*/
public boolean isSquelchChanged()
{
return mSquelchChanged;
}
/**
* Sets or resets the squelch changed flag.
*/
private void setSquelchChanged(boolean changed)
{
mSquelchChanged = changed;
}
/**
* Indicates if the squelch state is currently muted
*/
public boolean isMuted()
{
return mPowerSquelch.isMuted();
}
@Override
public void receive(SourceEvent sourceEvent)
{
//Only forward squelch threshold change request events
if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CHANGE_SQUELCH_THRESHOLD)
{
mPowerSquelch.receive(sourceEvent);
}
else if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CURRENT_SQUELCH_THRESHOLD)
{
mPowerSquelch.receive(sourceEvent);
}
}
}

View File

@@ -0,0 +1,161 @@
/*
* *****************************************************************************
* 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.fm;
import io.github.dsheirer.dsp.magnitude.IMagnitudeCalculator;
import io.github.dsheirer.dsp.magnitude.MagnitudeFactory;
import io.github.dsheirer.dsp.squelch.AdaptiveSquelch;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.SourceEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* FM Demodulator for demodulating complex samples and producing demodulated floating point samples.
*
* Implements listener of source events to process runtime squelch threshold change request events
* which are forwarded to the power squelch control.
*/
public class SquelchingFMDemodulator implements ISquelchingDemodulator, Listener<SourceEvent>
{
private static final Logger mLog = LoggerFactory.getLogger(SquelchingFMDemodulator.class);
private static final float ZERO = 0.0f;
private final AdaptiveSquelch mAdaptiveSquelch;
private final IMagnitudeCalculator mMagnitude = MagnitudeFactory.getMagnitudeCalculator();
private final IDemodulator mFmDemodulator = FmDemodulatorFactory.getFmDemodulator();
private boolean mSquelchChanged = false;
/**
* Creates an FM demodulator instance with a default gain of 1.0.
*
* @param alpha decay value of the single pole IIR filter in range: 0.0 - 1.0. The smaller the alpha value,
* the slower the squelch response.
* @param squelchThreshold in decibels. Signal power must exceed this threshold value for unsquelch.
* causes immediate mute and unmute. Set to higher count to prevent mute/unmute flapping.
* @param squelchAutoTrack to enable the squelch noise floor auto-tracking feature.
*/
public SquelchingFMDemodulator(float alpha, float squelchThreshold, boolean squelchAutoTrack)
{
mAdaptiveSquelch = new AdaptiveSquelch(alpha, squelchThreshold, squelchAutoTrack);
}
/**
* Set or update the sample rate for the squelch to adjust the power level notification rate.
* @param sampleRate in hertz
*/
public void setSampleRate(int sampleRate)
{
mAdaptiveSquelch.setSampleRate(sampleRate);
}
/**
* Registers the listener to receive notifications of squelch change events from the power squelch.
*/
public void setSourceEventListener(Listener<SourceEvent> listener)
{
mAdaptiveSquelch.setSourceEventListener(listener);
}
/**
* Demodulates the complex (I/Q) sample arrays
* @param i inphase samples
* @param q quadrature samples
* @return demodulated real samples
*/
@Override
public float[] demodulate(float[] i, float[] q)
{
setSquelchChanged(false);
float[] demodulated = mFmDemodulator.demodulate(i, q);
float[] magnitude = mMagnitude.calculate(i, q);
for(int x = 0; x < magnitude.length; x++)
{
mAdaptiveSquelch.process(magnitude[x]);
if(!(mAdaptiveSquelch.isUnmuted()))
{
demodulated[x] = ZERO;
}
if(mAdaptiveSquelch.isSquelchChanged())
{
setSquelchChanged(true);
}
}
return demodulated;
}
/**
* Sets the threshold for squelch control
* @param threshold (dB)
*/
public void setSquelchThreshold(float threshold)
{
mAdaptiveSquelch.setSquelchThreshold(threshold);
}
@Override
public void setSquelchAutoTrack(boolean autoTrack)
{
mAdaptiveSquelch.setSquelchAutoTrack(autoTrack);
}
/**
* Indicates if the squelch state has changed during the processing of buffer(s)
*/
public boolean isSquelchChanged()
{
return mSquelchChanged;
}
/**
* Sets or resets the squelch changed flag.
*/
private void setSquelchChanged(boolean changed)
{
mSquelchChanged = changed;
}
/**
* Indicates if the squelch state is currently muted
*/
public boolean isMuted()
{
return mAdaptiveSquelch.isMuted();
}
@Override
public void receive(SourceEvent sourceEvent)
{
switch(sourceEvent.getEvent())
{
//Only forward squelch threshold and auto-track current & change requests
case REQUEST_CURRENT_SQUELCH_THRESHOLD:
case REQUEST_CHANGE_SQUELCH_THRESHOLD:
case REQUEST_CURRENT_SQUELCH_AUTO_TRACK:
case REQUEST_CHANGE_SQUELCH_AUTO_TRACK:
mAdaptiveSquelch.receive(sourceEvent);
break;
}
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -26,17 +26,12 @@ import jdk.incubator.vector.VectorSpecies;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.Random;
/**
* FM demodulator that uses JDK 17+ SIMD vector intrinsics
*/
public class VectorFMDemodulator implements IFmDemodulator
public abstract class VectorFMDemodulator implements IDemodulator
{
private static final Logger mLog = LoggerFactory.getLogger(VectorFMDemodulator.class);
protected static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED;
protected static final float ZERO = 0.0f;
private static final int BUFFER_OVERLAP = 1;
@@ -48,9 +43,16 @@ public class VectorFMDemodulator implements IFmDemodulator
{
}
/**
* Vector species as specified by the implementing sub-class.
*/
protected abstract VectorSpecies<Float> getVectorSpecies();
@Override public float[] demodulate(float[] i, float[] q)
{
VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES);
// VectorSpecies<Float> species= getVectorSpecies();
VectorUtilities.checkComplexArrayLength(i, q, getVectorSpecies());
if(mIBuffer.length != (i.length + BUFFER_OVERLAP))
{
@@ -70,12 +72,12 @@ public class VectorFMDemodulator implements IFmDemodulator
FloatVector currentI, currentQ, previousI, previousQ, demod, demodI, demodQ;
for(int bufferPointer = 0; bufferPointer < mIBuffer.length - 1; bufferPointer += VECTOR_SPECIES.length())
for(int bufferPointer = 0; bufferPointer < mIBuffer.length - 1; bufferPointer += getVectorSpecies().length())
{
previousI = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, bufferPointer);
previousQ = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, bufferPointer);
currentI = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, bufferPointer + 1);
currentQ = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, bufferPointer + 1);
previousI = FloatVector.fromArray(getVectorSpecies(), mIBuffer, bufferPointer);
previousQ = FloatVector.fromArray(getVectorSpecies(), mQBuffer, bufferPointer);
currentI = FloatVector.fromArray(getVectorSpecies(), mIBuffer, bufferPointer + 1);
currentQ = FloatVector.fromArray(getVectorSpecies(), mQBuffer, bufferPointer + 1);
demodI = currentI.mul(previousI).sub(currentQ.mul(previousQ.neg()));
demodQ = currentQ.mul(previousI).add(currentI.mul(previousQ.neg()));
@@ -89,53 +91,4 @@ public class VectorFMDemodulator implements IFmDemodulator
return demodulated;
}
public static void main(String[] args)
{
int iterations = 3_000_000;
int sampleSize = 2048;
Random random = new Random();
float[] iSamples = new float[sampleSize];
float[] qSamples = new float[sampleSize];
for(int x = 0; x < iSamples.length; x++)
{
iSamples[x] = random.nextFloat() * 2.0f - 1.0f;
qSamples[x] = random.nextFloat() * 2.0f - 1.0f;
}
boolean validation = false;
ScalarFMDemodulator legacy = new ScalarFMDemodulator();
VectorFMDemodulator vector = new VectorFMDemodulator();
if(validation)
{
float[] legacySamples = legacy.demodulate(iSamples, qSamples);
float[] vectorSamples = vector.demodulate(iSamples, qSamples);
System.out.println("LEGACY:" + Arrays.toString(legacySamples));
System.out.println("VECTOR:" + Arrays.toString(vectorSamples));
}
else
{
System.out.println("Test Starting ...");
long start = System.currentTimeMillis();
double accumulator = 0.0;
for(int i = 0; i < iterations; i++)
{
float[] samples = legacy.demodulate(iSamples, qSamples);
// float[] samples = vector.demodulate(iSamples, qSamples);
accumulator += samples[3];
}
double elapsed = System.currentTimeMillis() - start;
DecimalFormat df = new DecimalFormat("0.000");
System.out.println("Accumulator: " + accumulator);
System.out.println("Test Complete. Elapsed Time: " + df.format(elapsed / 1000.0d) + " seconds");
}
}
}

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