Porównaj commity

...

94 Commity

Autor SHA1 Wiadomość Data
AlexandreRouma c4b0cb37a6
Merge pull request #1315 from AlexandreRouma/master
keep new_sinks branch up to date
2024-02-02 14:51:34 +01:00
AlexandreRouma 7ab743d05b finish iq exporter and fix network lib send not closing socket on error 2024-02-02 04:11:29 +01:00
AlexandreRouma 122e67ef65 finished VFO mode of the iq exporter 2024-02-01 21:38:13 +01:00
AlexandreRouma fbeb2195da fix make_windows_package.ps1 issue 2024-02-01 18:54:18 +01:00
AlexandreRouma 1f2b50c9bb add beginning of IQ exporter module 2024-02-01 18:36:25 +01:00
AlexandreRouma f486c657c1 fix cmake to prevent always enabling the pager decoder 2024-02-01 01:12:51 +01:00
AlexandreRouma f1f04d59fe add missing files 2024-02-01 00:55:36 +01:00
AlexandreRouma ef42ea01d8 add flex decoder menu entry and fix pocsag decoding 2024-02-01 00:55:17 +01:00
AlexandreRouma 3fc893568a beginning of pager decoder 2024-01-31 23:34:40 +01:00
AlexandreRouma 4b6835141e fix low PI RDS callsign decoding 2024-01-30 22:18:18 +01:00
AlexandreRouma cd7dabda51 convert network sink to new stream system 2024-01-30 22:04:36 +01:00
AlexandreRouma 139f63ad25 limit stream configuration to creator 2024-01-30 20:28:32 +01:00
AlexandreRouma 75800e0ca2 fix merge issue 2024-01-30 18:09:59 +01:00
AlexandreRouma 84da183559
Merge pull request #1310 from AlexandreRouma/master
New sink/stream system
2024-01-30 18:08:41 +01:00
AlexandreRouma 0144d8e8ce resolve conflict in radio 2024-01-30 18:08:17 +01:00
AlexandreRouma 7ef88f23a8 resolve conflict in audio_sink 2 2024-01-30 18:06:31 +01:00
AlexandreRouma c934e41a61 resolve conflict in audio_sink 2024-01-30 18:05:28 +01:00
AlexandreRouma 9dd6c8546d resolve conflict in spectran_http_client.cpp 2024-01-30 18:02:16 +01:00
AlexandreRouma a9e59bdf3c removed useless logging again 2024-01-30 00:01:10 +01:00
AlexandreRouma f0bd17f9f4 fix north americal RDS callsign decoding 2024-01-29 23:57:23 +01:00
AlexandreRouma a8ed213ed3 remove useless debug logging 2024-01-29 21:49:13 +01:00
AlexandreRouma f8183739f7 add rds region selection 2024-01-29 21:28:43 +01:00
AlexandreRouma 120745de19 add rds program type name decoding 2024-01-29 21:00:23 +01:00
AlexandreRouma 05ab17add3
Merge pull request #1307 from AlexandreRouma/new_rds
New rds demod and decode
2024-01-29 19:43:06 +01:00
AlexandreRouma 2ef8ee3629 disable rds symbol diagram data stream when not visible 2024-01-29 19:15:45 +01:00
AlexandreRouma 14cb839863 clean up rds code and fix use before init 2024-01-29 18:43:46 +01:00
AlexandreRouma 9501371c6c
Merge pull request #1302 from AlexandreRouma/master
keep new_rds branch updated
2024-01-29 01:45:32 +01:00
AlexandreRouma ff23d7e43f fix warnings 2024-01-29 01:40:20 +01:00
AlexandreRouma f541328e5c
Merge pull request #1301 from AlexandreRouma/new_plutosdr_enum
New plutosdr enum
2024-01-28 23:39:33 +01:00
AlexandreRouma be8edbfa9e revamp sdr++ server source networking code 2024-01-28 21:46:54 +01:00
AlexandreRouma 11a7c382e8 update more github action versions 2024-01-28 17:28:07 +01:00
AlexandreRouma 54276177ae update github action version 2024-01-28 17:25:27 +01:00
AlexandreRouma bc77bab45f improved plutosdr device naming 2024-01-28 17:23:18 +01:00
AlexandreRouma 97d0a07ec7 fix plutosdr commit persistence 2024-01-28 15:26:34 +01:00
AlexandreRouma 6b5de78e80 fix plutosdr source not updating samplerate on select 2024-01-28 14:39:01 +01:00
AlexandreRouma 1cd8c2510a fix plutosdr source error checking 2024-01-28 14:38:36 +01:00
AlexandreRouma 32cbd726fd implement enumeration and settings for plutosdr source 2024-01-28 02:16:20 +01:00
AlexandreRouma 175992b081 add android workaround 2024-01-28 00:25:46 +01:00
AlexandreRouma 31c9e5767e beginning of PlutoSDR context enumeration 2024-01-28 00:03:04 +01:00
AlexandreRouma e6a02a3944 fix bad plutosdr bandwidth selection at very high samplerates 2024-01-27 22:52:11 +01:00
AlexandreRouma 00e6832055 add plutosdr bandwidth selection for #563 2024-01-27 22:49:39 +01:00
AlexandreRouma bc8baca190 clean up plutosdr source code 2024-01-27 21:35:13 +01:00
AlexandreRouma 08e75b6d14 fix gain mode selection not always applying 2024-01-27 21:30:06 +01:00
AlexandreRouma e9ec79f6ef modernise gain mode selection for plutosdr 2024-01-27 21:29:44 +01:00
AlexandreRouma cd996292bc revamp plutosdr samplerate selection code 2024-01-27 21:12:26 +01:00
AlexandreRouma 06b7ad5c98 remove old useless debug code from audio sink 2024-01-27 16:34:54 +01:00
AlexandreRouma 38a95b4011 update SDRplay API in MacOS CI 2024-01-26 19:31:32 +01:00
AlexandreRouma f6052d913a bump version of updated modules 2024-01-26 19:00:36 +01:00
AlexandreRouma 2b00370cf3 Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2024-01-26 18:47:18 +01:00
AlexandreRouma 09f4071803 Switch SDRplay API in Linux nightly builds to version 3.12 2024-01-26 18:47:13 +01:00
AlexandreRouma e61ef29e0f
Merge pull request #1292 from daviderud/aligned_master
additions and corrections to NL band plan
2024-01-26 17:56:54 +01:00
AlexandreRouma eb36f86d41 added missing include 2024-01-26 17:21:51 +01:00
AlexandreRouma 29889a289f Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2024-01-26 16:57:19 +01:00
AlexandreRouma 859af77bd3 revamp RFspace source to use new networking library and fix buffering 2024-01-26 16:57:11 +01:00
Davide Rovelli 0a3d1de02f removed tabs and unnecessary pipes 2024-01-26 07:43:01 +01:00
AlexandreRouma db3fbd2975
update issue template to reflect that fact that filling it out is not optional 2024-01-26 01:40:17 +01:00
AlexandreRouma f5adc7c587 fix hermes lite buffering 2024-01-25 23:28:03 +01:00
AlexandreRouma 255988ee46 fix hermes lite enumeration issues and fix copy paste fail in network library 2024-01-25 22:27:59 +01:00
AlexandreRouma 68bf2fc16f fix bad network lib broadcast implementation 2024-01-25 21:34:13 +01:00
AlexandreRouma 3aa167701e fix networking library to allow multicast 2024-01-25 20:27:04 +01:00
AlexandreRouma 97c1a132a5 move zoom waterfall's zoom function in the cpp file to avoid annoying recomps 2024-01-25 19:48:04 +01:00
AlexandreRouma 118e56897c Add ubuntu mantic CI build 2024-01-25 15:41:23 +01:00
AlexandreRouma af8c085d43 switch osx to macos 2024-01-24 18:14:29 +01:00
Davide Rovelli 708f74e179
additions and corrections 2024-01-24 06:14:00 +01:00
AlexandreRouma eab4264604
Merge pull request #1291 from AlexandreRouma/citest
Allow MacOS build to run on Catalina and newer
2024-01-22 11:32:25 -08:00
AlexandreRouma 4b77d8c395 prepare for merge back part 2, electric boogaloo 2024-01-22 20:31:18 +01:00
AlexandreRouma 854ed89b82 prepare for merge back 2024-01-22 20:30:53 +01:00
AlexandreRouma 27ab5bf3c1 Fix exceptions referenced in #1287 2024-01-22 19:46:01 +01:00
AlexandreRouma 159f59b858 fix return warning for #1234 2024-01-22 19:34:56 +01:00
AlexandreRouma 93cafe7109 fix typo referenced in #1083 2024-01-22 19:22:26 +01:00
AlexandreRouma 74ae8a45d9 fix incorrect formatting again for #1288 2024-01-22 18:17:23 +01:00
AlexandreRouma 691216a298 fix incorrect formatting #1288 this time in the right branch... 2024-01-22 17:31:09 +01:00
AlexandreRouma 3e58d4ba31 fix missing return statement 2024-01-22 02:10:48 +01:00
AlexandreRouma 86dcec7495
Merge pull request #1284 from hzeller/20240121-adapt-rtaudio
Make compatible with rtaudio API 5 and 6.
2024-01-21 16:31:31 -08:00
Henner Zeller 5a003e99d2 Address review comments. 2024-01-21 15:36:41 -08:00
AlexandreRouma f197cf6bd9
Fix styling in the other file 2024-01-21 23:49:23 +01:00
AlexandreRouma 8cefeadbd4
Fix styling, modern computers can display more than 40 columns 2024-01-21 23:48:04 +01:00
Henner Zeller 23ae66151b Make compatible with rtaudio API 5 and 6.
Recent rtaudio changed the API to not throw exceptions anymore and
also have DeviceIDs not queried by index but IDs that are provided
separately ( https://github.com/thestk/rtaudio/releases ).

Adapt the code-base to be compatible with the old and the new API
as we have to exepect that in a transition period both APIs are
common on various build platforms.
2024-01-21 14:37:30 -08:00
AlexandreRouma fa76b4e865 Made the contributing policy more clear 2024-01-21 20:19:46 +01:00
AlexandreRouma 5e195a0d43 testing something for macos (do not use this CI build) 2024-01-19 20:53:25 +01:00
AlexandreRouma 5e299d9d23 testing something silly 2 2024-01-19 20:01:05 +01:00
AlexandreRouma fd5813df6d testing something silly 2024-01-19 19:43:31 +01:00
AlexandreRouma eabb842b6b switched MacOS CI to debug mode 2024-01-19 19:11:01 +01:00
AlexandreRouma 5a1945f779 allow running code on older macos version (test) 2024-01-17 07:07:24 +01:00
AlexandreRouma 052167962d fix the rtlsdr source on android again 2024-01-17 06:52:08 +01:00
AlexandreRouma 6fc41a81a7 fix build on android due to new librtlsdr function name 2024-01-17 06:41:25 +01:00
AlexandreRouma e710b6c6dc Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2024-01-17 06:29:41 +01:00
AlexandreRouma a91434c5fe fix windows CI (hopefully) and rebuild with Android BlogV4 support 2024-01-17 06:28:16 +01:00
AlexandreRouma 193580caf3
Merge pull request #1270 from AlexandreRouma/master
merge
2024-01-08 06:18:00 +01:00
AlexandreRouma ead2ac6128
Merge pull request #1267 from clouserw/readme-fix
add additional library to debian install instructions
2024-01-05 00:20:29 +01:00
Wil Clouser 505cbb0ba2
add additional library to debian install instructions 2024-01-04 14:01:00 -08:00
AlexandreRouma b1030cbdfb Implement #1260 2023-12-27 03:53:43 +01:00
AlexandreRouma f3c5b2c31f Fixed #1259 2023-12-26 23:10:44 +01:00
AlexandreRouma 2432390600 Completely redid the RDS demod 2023-12-13 23:25:46 +01:00
80 zmienionych plików z 4360 dodań i 1073 usunięć

Wyświetl plik

@ -7,6 +7,8 @@ assignees: ''
---
# WARNING: Filling out the template below is NOT optional. Issues not filling out this template will be closed without review.
FIRST: Before reporting any bug, make sure that the bug you are reporting has not been reported before. Also, try to use the [nightly version](https://www.sdrpp.org/nightly) if possible in case I've already fixed the bug.
**Hardware**

Wyświetl plik

@ -1,7 +1,4 @@
# Important
Only minor bug fixes and bandplans are accepted.
Pull requests adding features or any bug fix that requires significant code changes will be automatically rejected.
Only bandplan, colormaps and themes are accepted. Code pull requests are **NOT welcome**.
Open an issue requesting a feature or discussing a possible bugfix instead.

Wyświetl plik

@ -18,7 +18,7 @@ jobs:
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Create Build Environment
run: cmake -E make_directory ${{runner.workspace}}/build
@ -34,7 +34,7 @@ jobs:
- name: Patch Pothos with earlier libusb version
working-directory: ${{runner.workspace}}
run: 7z x libusb.7z -olibusb_old ; rm "C:/Program Files/PothosSDR/bin/libusb-1.0.dll" ; cp "libusb_old/MS64/dll/libusb-1.0.dll" "C:/Program Files/PothosSDR/bin/"
run: 7z x libusb.7z -olibusb_old ; rm "C:/Program Files/PothosSDR/bin/libusb-1.0.dll" ; cp "libusb_old/MS64/dll/libusb-1.0.dll" "C:/Program Files/PothosSDR/bin/" ; rm "C:/Program Files/PothosSDR/lib/libusb-1.0.lib" ; cp "libusb_old/MS64/dll/libusb-1.0.lib" "C:/Program Files/PothosSDR/lib/"
- name: Download SDRPlay API
run: Invoke-WebRequest -Uri "https://drive.google.com/uc?id=12UHPMwkfa67A11QZDmpCT4iwHnyJHWuu&confirm=t" -OutFile ${{runner.workspace}}/SDRPlay.zip
@ -64,7 +64,7 @@ jobs:
run: git clone https://github.com/thestk/rtaudio ; cd rtaudio ; git checkout 2f2fca4502d506abc50f6d4473b2836d24cfb1e3 ; mkdir build ; cd build ; cmake .. ; cmake --build . --config Release ; cmake --install .
- name: Install libperseus-sdr
run: git clone https://github.com/AlexandreRouma/libperseus-sdr ; cd libperseus-sdr ; mkdir build ; cd build ; cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" ; cmake --build . --config Release ; mkdir "C:/Program Files/PothosSDR/include/perseus-sdr" ; cp Release/perseus-sdr.dll "C:/Program Files/PothosSDR/bin" ; cp Release/perseus-sdr.lib "C:/Program Files/PothosSDR/bin" ; cd .. ; xcopy "src" "C:/Program Files/PothosSDR/include/perseus-sdr"
run: git clone https://github.com/AlexandreRouma/libperseus-sdr ; cd libperseus-sdr ; mkdir build ; cd build ; cmake "-DLIBUSB_LIBRARIES=C:/Program Files/PothosSDR/lib/libusb-1.0.lib" "-DLIBUSB_INCLUDE_DIRS=C:/Program Files/PothosSDR/include/libusb-1.0" .. "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" ; cmake --build . --config Release ; mkdir "C:/Program Files/PothosSDR/include/perseus-sdr" ; cp Release/perseus-sdr.dll "C:/Program Files/PothosSDR/bin" ; cp Release/perseus-sdr.lib "C:/Program Files/PothosSDR/bin" ; cd .. ; xcopy "src" "C:/Program Files/PothosSDR/include/perseus-sdr"
- name: Prepare CMake
working-directory: ${{runner.workspace}}/build
@ -79,16 +79,16 @@ jobs:
run: '&($Env:GITHUB_WORKSPACE + "/make_windows_package.ps1") ./build ($Env:GITHUB_WORKSPACE + "/root")'
- name: Save Archive
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sdrpp_windows_x64
path: ${{runner.workspace}}/sdrpp_windows_x64.zip
build_macos:
runs-on: macos-11
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Create Build Environment
run: cmake -E make_directory ${{runner.workspace}}/build
@ -100,29 +100,29 @@ jobs:
run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf libbladerf codec2 zstd autoconf automake libtool && pip3 install mako
- name: Install volk
run: git clone --recursive https://github.com/gnuradio/volk && cd volk && mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
run: git clone --recursive https://github.com/gnuradio/volk && cd volk && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
- name: Install SDRplay API
run: wget https://www.sdrplay.com/software/SDRplay_RSP_API-MacOSX-3.07.3.pkg && sudo installer -pkg SDRplay_RSP_API-MacOSX-3.07.3.pkg -target /
run: wget https://www.sdrplay.com/software/SDRplayAPI-macos-installer-universal-3.12.1.pkg && sudo installer -pkg SDRplayAPI-macos-installer-universal-3.12.1.pkg -target /
- name: Install libiio
run: wget https://github.com/analogdevicesinc/libiio/archive/refs/tags/v0.25.zip && 7z x v0.25.zip && cd libiio-0.25 && mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
run: wget https://github.com/analogdevicesinc/libiio/archive/refs/tags/v0.25.zip && 7z x v0.25.zip && cd libiio-0.25 && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
- name: Install libad9361
run: git clone https://github.com/analogdevicesinc/libad9361-iio && cd libad9361-iio && mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
run: git clone https://github.com/analogdevicesinc/libad9361-iio && cd libad9361-iio && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
- name: Install LimeSuite
run: git clone https://github.com/myriadrf/LimeSuite && cd LimeSuite && mkdir builddir && cd builddir && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
run: git clone https://github.com/myriadrf/LimeSuite && cd LimeSuite && mkdir builddir && cd builddir && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
- name: Install libperseus
run: git clone https://github.com/Microtelecom/libperseus-sdr && cd libperseus-sdr && autoreconf -i && ./configure --prefix=/usr/local && make && make install && cd ..
- name: Install more recent librtlsdr
run: git clone https://github.com/osmocom/rtl-sdr && cd rtl-sdr && mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j3 LIBRARY_PATH=$(pkg-config --libs-only-L libusb-1.0 | sed 's/\-L//') && sudo make install && cd ../../
run: git clone https://github.com/osmocom/rtl-sdr && cd rtl-sdr && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 LIBRARY_PATH=$(pkg-config --libs-only-L libusb-1.0 | sed 's/\-L//') && sudo make install && cd ../../
- name: Prepare CMake
working-directory: ${{runner.workspace}}/build
run: cmake $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_SOAPY_SOURCE=OFF -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_AUDIO_SOURCE=OFF -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
run: cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_SOAPY_SOURCE=OFF -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_AUDIO_SOURCE=OFF -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
- name: Build
working-directory: ${{runner.workspace}}/build
@ -133,7 +133,7 @@ jobs:
run: cd $GITHUB_WORKSPACE && sh make_macos_bundle.sh ${{runner.workspace}}/build ./SDR++.app && zip -r ${{runner.workspace}}/sdrpp_macos_intel.zip SDR++.app
- name: Save Archive
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sdrpp_macos_intel
path: ${{runner.workspace}}/sdrpp_macos_intel.zip
@ -142,7 +142,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_buster && docker build . --tag sdrpp_build
@ -155,7 +155,7 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sdrpp_debian_buster_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
@ -164,7 +164,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_bullseye && docker build . --tag sdrpp_build
@ -177,7 +177,7 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sdrpp_debian_bullseye_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
@ -186,7 +186,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_bookworm && docker build . --tag sdrpp_build
@ -199,7 +199,7 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sdrpp_debian_bookworm_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
@ -208,7 +208,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_sid && docker build . --tag sdrpp_build
@ -221,7 +221,7 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sdrpp_debian_sid_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
@ -230,7 +230,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_focal && docker build . --tag sdrpp_build
@ -243,7 +243,7 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sdrpp_ubuntu_focal_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
@ -252,7 +252,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_jammy && docker build . --tag sdrpp_build
@ -265,16 +265,38 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sdrpp_ubuntu_jammy_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_mantic:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_mantic && docker build . --tag sdrpp_build
- name: Run Container
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
- name: Recover Deb Archive
working-directory: ${{runner.workspace}}
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v4
with:
name: sdrpp_ubuntu_mantic_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_raspios_bullseye_armhf:
runs-on: ARM
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Create Build Environment
run: rm -rf ${{runner.workspace}}/build && cmake -E make_directory ${{runner.workspace}}/build
@ -292,7 +314,7 @@ jobs:
run: sh $GITHUB_WORKSPACE/make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk2-dev, librtaudio-dev' && mv sdrpp_debian_amd64.deb sdrpp_debian_armhf.deb
- name: Save Deb Archive
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sdrpp_raspios_bullseye_armhf
path: ${{runner.workspace}}/sdrpp_debian_armhf.deb
@ -301,7 +323,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Fetch container
working-directory: ${{runner.workspace}}
@ -319,18 +341,18 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/android/app/build/outputs/apk/debug/app-debug.apk ./ && mv app-debug.apk sdrpp.apk
- name: Save APK
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sdrpp_android
path: ${{runner.workspace}}/sdrpp.apk
create_full_archive:
needs: ['build_windows', 'build_macos', 'build_debian_buster', 'build_debian_bullseye', 'build_debian_bookworm', 'build_debian_sid', 'build_ubuntu_focal', 'build_ubuntu_jammy', 'build_raspios_bullseye_armhf', 'build_android']
needs: ['build_windows', 'build_macos', 'build_debian_buster', 'build_debian_bullseye', 'build_debian_bookworm', 'build_debian_sid', 'build_ubuntu_focal', 'build_ubuntu_jammy', 'build_ubuntu_mantic', 'build_raspios_bullseye_armhf', 'build_android']
runs-on: ubuntu-latest
steps:
- name: Download All Builds
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
- name: Create Archive
run: >
@ -343,10 +365,11 @@ jobs:
mv sdrpp_debian_sid_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_sid_amd64.deb &&
mv sdrpp_ubuntu_focal_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_focal_amd64.deb &&
mv sdrpp_ubuntu_jammy_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_jammy_amd64.deb &&
mv sdrpp_ubuntu_mantic_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_mantic_amd64.deb &&
mv sdrpp_raspios_bullseye_armhf/sdrpp_debian_armhf.deb sdrpp_all/sdrpp_raspios_bullseye_armhf.deb &&
mv sdrpp_android/sdrpp.apk sdrpp_all/sdrpp.apk
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: sdrpp_all
path: sdrpp_all/
@ -358,7 +381,7 @@ jobs:
steps:
- name: Download All Builds
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
- name: Update Nightly
run: gh release upload nightly sdrpp_all/* -R ${{github.repository}} --clobber
@ -367,7 +390,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install codespell
run: sudo apt update -y && sudo apt install -y codespell
@ -379,7 +402,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Run check_clang_format
run: cd $GITHUB_WORKSPACE && chmod +x ./check_clang_format.sh && ./check_clang_format.sh || true

Wyświetl plik

@ -43,12 +43,14 @@ option(OPT_BUILD_FALCON9_DECODER "Build the falcon9 live decoder (Dependencies:
option(OPT_BUILD_KG_SSTV_DECODER "Build the KG SSTV (KG-STV) decoder module (no dependencies required)" OFF)
option(OPT_BUILD_M17_DECODER "Build the M17 decoder module (Dependencies: codec2)" OFF)
option(OPT_BUILD_METEOR_DEMODULATOR "Build the meteor demodulator module (no dependencies required)" ON)
option(OPT_BUILD_PAGER_DECODER "Build the pager decoder module (no dependencies required)" OFF)
option(OPT_BUILD_RADIO "Main audio modulation decoder (AM, FM, SSB, etc...)" ON)
option(OPT_BUILD_WEATHER_SAT_DECODER "Build the HRPT decoder module (no dependencies required)" OFF)
# Misc
option(OPT_BUILD_DISCORD_PRESENCE "Build the Discord Rich Presence module" ON)
option(OPT_BUILD_FREQUENCY_MANAGER "Build the Frequency Manager module" ON)
option(OPT_BUILD_IQ_EXPORTER "Build the IQ Exporter module" OFF)
option(OPT_BUILD_RECORDER "Audio and baseband recorder" ON)
option(OPT_BUILD_RIGCTL_CLIENT "Rigctl client to make SDR++ act as a panadapter" ON)
option(OPT_BUILD_RIGCTL_SERVER "Rigctl backend for controlling SDR++ with software like gpredict" ON)
@ -234,6 +236,10 @@ if (OPT_BUILD_METEOR_DEMODULATOR)
add_subdirectory("decoder_modules/meteor_demodulator")
endif (OPT_BUILD_METEOR_DEMODULATOR)
if (OPT_BUILD_PAGER_DECODER)
add_subdirectory("decoder_modules/pager_decoder")
endif (OPT_BUILD_PAGER_DECODER)
if (OPT_BUILD_RADIO)
add_subdirectory("decoder_modules/radio")
endif (OPT_BUILD_RADIO)
@ -252,6 +258,10 @@ if (OPT_BUILD_FREQUENCY_MANAGER)
add_subdirectory("misc_modules/frequency_manager")
endif (OPT_BUILD_FREQUENCY_MANAGER)
if (OPT_BUILD_IQ_EXPORTER)
add_subdirectory("misc_modules/iq_exporter")
endif (OPT_BUILD_IQ_EXPORTER)
if (OPT_BUILD_RECORDER)
add_subdirectory("misc_modules/recorder")
endif (OPT_BUILD_RECORDER)
@ -302,7 +312,7 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
add_custom_target(do_always ALL cp \"$<TARGET_FILE_DIR:sdrpp_core>/libsdrpp_core.dylib\" \"$<TARGET_FILE_DIR:sdrpp>\")
endif ()
# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/dev/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_SCANNER=ON -DOPT_BUILD_SCHEDULER=ON -DOPT_BUILD_USRP_SOURCE=ON
# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/dev/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_SCANNER=ON -DOPT_BUILD_SCHEDULER=ON -DOPT_BUILD_USRP_SOURCE=ON -DOPT_BUILD_PAGER_DECODER=ON
# Create module cmake file
configure_file(${CMAKE_SOURCE_DIR}/sdrpp_module.cmake ${CMAKE_CURRENT_BINARY_DIR}/sdrpp_module.cmake @ONLY)

Wyświetl plik

@ -1,65 +1,6 @@
# Pull Requests
**I DO NOT ACCEPT PULL-REQUEST FOR FEATURES OR BUGFIXES REQUIRING SIGNIFICANT CODE/STRUCTURE CHANGES.**
**SUCH PULL REQUESTS WILL BE CLOSED AUTOMATICALLY. OPEN AN ISSUE DETAILING FEATURE REQUESTS OR POTENTIAL BUGFIX INSTEAD.**
# Code Style
## Naming Convention
- Files: `snake_case.h` `snake_case.cpp`
- Namespaces: `CamelCase`
- Classes: `CamelCase`
- Structs: `CamelCase_t`
- Members: `camelCase`
- Enum: `SNAKE_CASE`
- Macros: `SNAKE_CASE`
## Brace Style
```c++
int myFunction() {
if (shortIf) { shortFunctionName(); }
if (longIf) {
longFunction();
otherStuff();
myLongFunction();
}
}
```
Note: If it makes the code cleaner, remember to use the `?` keyword instead of a `if else` statement.
## Pointers
Please use `type* name` for pointers.
## Structure
Headers and their associated C++ files shall be in the same directory. All headers must use `#pragma once` instead of other include guards. Only include files in a header that are being used in that header. Include the rest in the associated C++ file.
# Modules
## Module Naming Convention
All modules names must be `snake_case`. If the module is a source, it must end with `_source`. If it is a sink, it must end with `_sink`.
For example, lets take the module named `cool_source`:
- Directory: `cool_source`
- Class: `CoolSourceModule`
- Binary: `cool_source.<os dynlib extension>`
## Integration into main repository
If the module meets the code quality requirements, it may be added to the official repository. A module that doesn't require any external dependencies that the core doesn't already use may be enabled for build by default. Otherwise, they must be disabled for build by default with a `OPT_BUILD_MODULE_NAME` variable set to `OFF`.
# JSON Formatting
The ability to add new radio band allocation identifiers and color maps relies on JSON files. Proper formatting of these JSON files is important for reference and readability. The following guides will show you how to properly format the JSON files for their respective uses.
**IMPORTANT: JSON File cannot contain comments, there are only in this example for clarity**
Code pull requests are **NOT welcome**. Please open an issue discussing potential bugfixes or feature requests instead.
## Band Frequency Allocation
@ -119,8 +60,8 @@ Please follow this guide to properly format the JSON files for custom color maps
}
```
# Best Practices
# JSON Formatting
* All additions and/or bug fixes to the core must not add additional dependencies.
* Use VSCode for development, VS seems to cause issues.
* DO NOT use libboost for any code meant for this repository
The ability to add new radio band allocation identifiers and color maps relies on JSON files. Proper formatting of these JSON files is important for reference and readability. The following guides will show you how to properly format the JSON files for their respective uses.
**IMPORTANT: JSON File cannot contain comments, there are only in this example for clarity**

Wyświetl plik

@ -45,7 +45,7 @@ uint8_t *history_buffer_get_slice(history_buffer *buf) { return buf->history[buf
shift_register_t history_buffer_search(history_buffer *buf, const distance_t *distances,
unsigned int search_every) {
shift_register_t bestpath;
shift_register_t bestpath = 0;
distance_t leasterror = USHRT_MAX;
// search for a state with the least error
for (shift_register_t state = 0; state < buf->num_states; state += search_every) {

Wyświetl plik

@ -88,7 +88,7 @@ int CommandArgsParser::parse(int argc, char* argv[]) {
try {
carg.ival = std::stoi(arg);
}
catch (std::exception e) {
catch (const std::exception& e) {
printf("Invalid argument, failed to parse integer\n");
showHelp();
return -1;
@ -98,7 +98,7 @@ int CommandArgsParser::parse(int argc, char* argv[]) {
try {
carg.fval = std::stod(arg);
}
catch (std::exception e) {
catch (const std::exception& e) {
printf("Invalid argument, failed to parse float\n");
showHelp();
return -1;

Wyświetl plik

@ -36,8 +36,8 @@ void ConfigManager::load(json def, bool lock) {
file >> conf;
file.close();
}
catch (std::exception e) {
flog::error("Config file '{0}' is corrupted, resetting it", path);
catch (const std::exception& e) {
flog::error("Config file '{}' is corrupted, resetting it: {}", path, e.what());
conf = def;
save(false);
}

Wyświetl plik

@ -93,7 +93,7 @@ namespace dsp {
void disableBlock(Processor<T, T>* block, Func onOutputChange) {
// Check that the block is part of the chain
if (!blockExists(block)) {
throw std::runtime_error("[chain] Tried to enable a block that isn't part of the chain");
throw std::runtime_error("[chain] Tried to disable a block that isn't part of the chain");
}
// If already disabled, don't do anything
@ -163,10 +163,12 @@ namespace dsp {
private:
Processor<T, T>* blockBefore(Processor<T, T>* block) {
// TODO: This is wrong and must be fixed when I get more time
for (auto& ln : links) {
if (ln == block) { return NULL; }
if (states[ln]) { return ln; }
}
return NULL;
}
Processor<T, T>* blockAfter(Processor<T, T>* block) {

Wyświetl plik

@ -49,6 +49,7 @@ namespace dsp::demod {
audioFirTaps = taps::lowPass(15000.0, 4000.0, _samplerate);
alFir.init(NULL, audioFirTaps);
arFir.init(NULL, audioFirTaps);
xlator.init(NULL, -57000.0, samplerate);
rdsResamp.init(NULL, samplerate, 5000.0);
lmr = buffer::alloc<float>(STREAM_BUFFER_SIZE);
@ -56,9 +57,9 @@ namespace dsp::demod {
r = buffer::alloc<float>(STREAM_BUFFER_SIZE);
lprDelay.out.free();
lmrDelay.out.free();
arFir.out.free();
alFir.out.free();
xlator.out.free();
rdsResamp.out.free();
base_type::init(in);
@ -92,6 +93,7 @@ namespace dsp::demod {
alFir.setTaps(audioFirTaps);
arFir.setTaps(audioFirTaps);
xlator.setOffset(-57000.0, samplerate);
rdsResamp.setInSamplerate(samplerate);
reset();
@ -139,7 +141,7 @@ namespace dsp::demod {
base_type::tempStart();
}
inline int process(int count, complex_t* in, stereo_t* out, int& rdsOutCount, float* rdsout = NULL) {
inline int process(int count, complex_t* in, stereo_t* out, int& rdsOutCount, complex_t* rdsout = NULL) {
// Demodulate
demod.process(count, in, demod.out.writeBuf);
if (_stereo) {
@ -152,24 +154,24 @@ namespace dsp::demod {
// Delay
lprDelay.process(count, demod.out.writeBuf, demod.out.writeBuf);
lmrDelay.process(count, rtoc.out.writeBuf, rtoc.out.writeBuf);
lmrDelay.process(count, rtoc.out.writeBuf, lmrDelay.out.writeBuf);
// conjugate PLL output to down convert twice the L-R signal
math::Conjugate::process(count, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf);
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, rtoc.out.writeBuf);
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, rtoc.out.writeBuf);
math::Multiply<dsp::complex_t>::process(count, lmrDelay.out.writeBuf, pilotPLL.out.writeBuf, lmrDelay.out.writeBuf);
math::Multiply<dsp::complex_t>::process(count, lmrDelay.out.writeBuf, pilotPLL.out.writeBuf, lmrDelay.out.writeBuf);
// Do RDS demod
if (_rdsOut) {
// Since the PLL output is no longer needed after this, use it as the output
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf);
convert::ComplexToReal::process(count, pilotPLL.out.writeBuf, rdsout);
volk_32f_s32f_multiply_32f(rdsout, rdsout, 100.0, count);
rdsOutCount = rdsResamp.process(count, rdsout, rdsout);
// Translate to 0Hz
xlator.process(count, rtoc.out.writeBuf, rtoc.out.writeBuf);
// Resample to the output samplerate
rdsOutCount = rdsResamp.process(count, rtoc.out.writeBuf, rdsout);
}
// Convert output back to real for further processing
convert::ComplexToReal::process(count, rtoc.out.writeBuf, lmr);
convert::ComplexToReal::process(count, lmrDelay.out.writeBuf, lmr);
// Amplify by 2x
volk_32f_s32f_multiply_32f(lmr, lmr, 2.0f, count);
@ -193,24 +195,11 @@ namespace dsp::demod {
// Convert to complex
rtoc.process(count, demod.out.writeBuf, rtoc.out.writeBuf);
// Filter out pilot and run through PLL
pilotFir.process(count, rtoc.out.writeBuf, pilotFir.out.writeBuf);
pilotPLL.process(count, pilotFir.out.writeBuf, pilotPLL.out.writeBuf);
// Translate to 0Hz
xlator.process(count, rtoc.out.writeBuf, rtoc.out.writeBuf);
// Delay
lprDelay.process(count, demod.out.writeBuf, demod.out.writeBuf);
lmrDelay.process(count, rtoc.out.writeBuf, rtoc.out.writeBuf);
// conjugate PLL output to down convert twice the L-R signal
math::Conjugate::process(count, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf);
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, rtoc.out.writeBuf);
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, rtoc.out.writeBuf);
// Since the PLL output is no longer needed after this, use it as the output
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf);
convert::ComplexToReal::process(count, pilotPLL.out.writeBuf, rdsout);
volk_32f_s32f_multiply_32f(rdsout, rdsout, 100.0, count);
rdsOutCount = rdsResamp.process(count, rdsout, rdsout);
// Resample to the output samplerate
rdsOutCount = rdsResamp.process(count, rtoc.out.writeBuf, rdsout);
}
// Filter if needed
@ -240,7 +229,7 @@ namespace dsp::demod {
return count;
}
stream<float> rdsOut;
stream<complex_t> rdsOut;
protected:
double _deviation;
@ -253,13 +242,14 @@ namespace dsp::demod {
tap<complex_t> pilotFirTaps;
filter::FIR<complex_t, complex_t> pilotFir;
convert::RealToComplex rtoc;
channel::FrequencyXlator xlator;
loop::PLL pilotPLL;
math::Delay<float> lprDelay;
math::Delay<complex_t> lmrDelay;
tap<float> audioFirTaps;
filter::FIR<float, float> arFir;
filter::FIR<float, float> alFir;
multirate::RationalResampler<float> rdsResamp;
multirate::RationalResampler<dsp::complex_t> rdsResamp;
float* lmr;
float* l;

Wyświetl plik

@ -110,7 +110,7 @@ namespace dsp::demod {
else if (_mode == Mode::LSB) {
return -_bandwidth / 2.0;
}
else if (_mode == Mode::DSB) {
else {
return 0.0;
}
}

Wyświetl plik

@ -65,6 +65,11 @@ namespace dsp::loop {
if constexpr(CLAMP_PHASE) { clampPhase(); }
}
inline void advancePhase() {
phase += freq;
if constexpr(CLAMP_PHASE) { clampPhase(); }
}
T freq;
T phase;

Wyświetl plik

@ -17,6 +17,8 @@ namespace streamsmenu {
std::map<std::string, int> selectedSinkTypeId;
std::map<std::string, int> addSinkTypeId;
int addType = 0;
void updateSinkTypeList(const std::string& removed = "") {
std::lock_guard<std::recursive_mutex> lck1(sinkTypesMtx);
auto lck2 = sigpath::streamManager.getSinkTypesLock();
@ -32,6 +34,9 @@ namespace streamsmenu {
// Update the list
updateSinkTypeList();
// Update the ID of the Add dropdown
// TODO
// Update the selected ID of each drop down
// TODO
}
@ -40,6 +45,9 @@ namespace streamsmenu {
// Update the list
updateSinkTypeList(type);
// Update the ID of the Add dropdown
// TODO
// Update the selected ID of each drop down
// TODO
}
@ -137,18 +145,16 @@ namespace streamsmenu {
float tableWidth = ImGui::GetContentRegionAvail().x;
ImGui::Spacing();
int ssds = 0;
int startCur = ImGui::GetCursorPosX();
{
std::lock_guard<std::recursive_mutex> lck(sinkTypesMtx);
ImGui::Combo(CONCAT("##sdrpp_streams_add_type_", name), &ssds, sinkTypes.txt);
ImGui::Combo(CONCAT("##sdrpp_streams_add_type_", name), &addType, sinkTypes.txt);
ImGui::SameLine();
if (ImGui::Button(CONCAT("Add##sdrpp_streams_add_btn_", name), ImVec2(tableWidth - (ImGui::GetCursorPosX() - startCur), 0))) {
stream->addSink(sinkTypes.value(addType));
}
ImGui::Spacing();
}
ImGui::SameLine();
if (ImGui::Button(CONCAT("Add##sdrpp_streams_add_btn_", name), ImVec2(tableWidth - (ImGui::GetCursorPosX() - startCur), 0))) {
stream->addSink("Audio");
}
ImGui::Spacing();
ImGui::EndTable();

Wyświetl plik

@ -62,6 +62,33 @@ inline void printAndScale(double freq, char* buf) {
}
}
inline void doZoom(int offset, int width, int inSize, int outSize, float* in, float* out) {
// NOTE: REMOVE THAT SHIT, IT'S JUST A HACKY FIX
if (offset < 0) {
offset = 0;
}
if (width > 524288) {
width = 524288;
}
float factor = (float)width / (float)outSize;
float sFactor = ceilf(factor);
float uFactor;
float id = offset;
float maxVal;
int sId;
for (int i = 0; i < outSize; i++) {
maxVal = -INFINITY;
sId = (int)id;
uFactor = (sId + sFactor > inSize) ? sFactor - ((sId + sFactor) - inSize) : sFactor;
for (int j = 0; j < uFactor; j++) {
if (in[sId + j] > maxVal) { maxVal = in[sId + j]; }
}
out[i] = maxVal;
id += factor;
}
}
namespace ImGui {
WaterFall::WaterFall() {
fftMin = -70.0;
@ -586,7 +613,7 @@ namespace ImGui {
for (int i = 0; i < count; i++) {
drawDataSize = (viewBandwidth / wholeBandwidth) * rawFFTSize;
drawDataStart = (((double)rawFFTSize / 2.0) * (offsetRatio + 1)) - (drawDataSize / 2);
doZoom(drawDataStart, drawDataSize, dataWidth, &rawFFTs[((i + currentFFTLine) % waterfallHeight) * rawFFTSize], tempData);
doZoom(drawDataStart, drawDataSize, rawFFTSize, dataWidth, &rawFFTs[((i + currentFFTLine) % waterfallHeight) * rawFFTSize], tempData);
for (int j = 0; j < dataWidth; j++) {
pixel = (std::clamp<float>(tempData[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange;
waterfallFb[(i * dataWidth) + j] = waterfallPallet[(int)(pixel * (WATERFALL_RESOLUTION - 1))];
@ -867,7 +894,7 @@ namespace ImGui {
int drawDataStart = (((double)rawFFTSize / 2.0) * (offsetRatio + 1)) - (drawDataSize / 2);
if (waterfallVisible) {
doZoom(drawDataStart, drawDataSize, dataWidth, &rawFFTs[currentFFTLine * rawFFTSize], latestFFT);
doZoom(drawDataStart, drawDataSize, rawFFTSize, dataWidth, &rawFFTs[currentFFTLine * rawFFTSize], latestFFT);
memmove(&waterfallFb[dataWidth], waterfallFb, dataWidth * (waterfallHeight - 1) * sizeof(uint32_t));
float pixel;
float dataRange = waterfallMax - waterfallMin;
@ -879,7 +906,7 @@ namespace ImGui {
waterfallUpdate = true;
}
else {
doZoom(drawDataStart, drawDataSize, dataWidth, rawFFTs, latestFFT);
doZoom(drawDataStart, drawDataSize, rawFFTSize, dataWidth, rawFFTs, latestFFT);
fftLines = 1;
}

Wyświetl plik

@ -90,33 +90,6 @@ namespace ImGui {
float* getFFTBuffer();
void pushFFT();
inline void doZoom(int offset, int width, int outWidth, float* data, float* out) {
// NOTE: REMOVE THAT SHIT, IT'S JUST A HACKY FIX
if (offset < 0) {
offset = 0;
}
if (width > 524288) {
width = 524288;
}
float factor = (float)width / (float)outWidth;
float sFactor = ceilf(factor);
float uFactor;
float id = offset;
float maxVal;
int sId;
for (int i = 0; i < outWidth; i++) {
maxVal = -INFINITY;
sId = (int)id;
uFactor = (sId + sFactor > rawFFTSize) ? sFactor - ((sId + sFactor) - rawFFTSize) : sFactor;
for (int j = 0; j < uFactor; j++) {
if (data[sId + j] > maxVal) { maxVal = data[sId + j]; }
}
out[i] = maxVal;
id += factor;
}
}
void updatePallette(float colors[][3], int colorCount);
void updatePalletteFromArray(float* colors, int colorCount);

Wyświetl plik

@ -11,7 +11,7 @@ Sink::Sink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::str
void Sink::showMenu() {}
SinkEntry::SinkEntry(StreamManager* manager, AudioStream* parentStream, const std::string& type, SinkID id, double inputSamplerate) :
SinkEntry::SinkEntry(StreamManager* manager, Stream* parentStream, const std::string& type, SinkID id, double inputSamplerate) :
manager(manager),
parentStream(parentStream),
id(id)
@ -156,7 +156,7 @@ std::string SinkEntry::getStringID() const {
return stringId;
}
AudioStream::AudioStream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate) :
Stream::Stream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate) :
manager(manager),
name(name)
{
@ -166,7 +166,7 @@ AudioStream::AudioStream(StreamManager* manager, const std::string& name, dsp::s
split.init(stream);
}
AudioStream::~AudioStream() {
Stream::~Stream() {
// Copy sink IDs
std::vector<SinkID> ids;
for (auto& [id, sink] : sinks) {
@ -179,75 +179,11 @@ AudioStream::~AudioStream() {
}
}
void AudioStream::setInput(dsp::stream<dsp::stereo_t>* stream, double samplerate) {
std::unique_lock<std::shared_mutex> lck(sinksMtx);
// If all that's needed is to set the input, do it and return
if (samplerate == 0.0) {
split.setInput(stream);
return;
}
// Update samplerate
this->samplerate = samplerate;
// Stop DSP
if (running) {
split.stop();
for (auto& [id, sink] : sinks) {
sink->stopDSP();
}
}
// Set input and samplerate
split.setInput(stream);
for (auto& [id, sink] : sinks) {
sink->setInputSamplerate(samplerate);
}
// Start DSP
if (running) {
for (auto& [id, sink] : sinks) {
sink->startDSP();
}
split.start();
}
}
void AudioStream::setSamplerate(double samplerate) {
std::unique_lock<std::shared_mutex> lck(sinksMtx);
// Update samplerate
this->samplerate = samplerate;
// Stop DSP
if (running) {
split.stop();
for (auto& [id, sink] : sinks) {
sink->stopDSP();
}
}
// Set samplerate
for (auto& [id, sink] : sinks) {
sink->setInputSamplerate(samplerate);
}
// Start DSP
if (running) {
for (auto& [id, sink] : sinks) {
sink->startDSP();
}
split.start();
}
}
const std::string& AudioStream::getName() const {
const std::string& Stream::getName() const {
return name;
}
SinkID AudioStream::addSink(const std::string& type, SinkID id) {
SinkID Stream::addSink(const std::string& type, SinkID id) {
std::unique_lock<std::shared_mutex> lck(sinksMtx);
// Find a free ID if not provided
@ -289,7 +225,7 @@ SinkID AudioStream::addSink(const std::string& type, SinkID id) {
return id;
}
void AudioStream::removeSink(SinkID id, bool forgetSettings) {
void Stream::removeSink(SinkID id, bool forgetSettings) {
// Acquire shared lock
std::shared_ptr<SinkEntry> sink;
{
@ -333,15 +269,85 @@ void AudioStream::removeSink(SinkID id, bool forgetSettings) {
}
}
std::shared_lock<std::shared_mutex> AudioStream::getSinksLock() {
std::shared_lock<std::shared_mutex> Stream::getSinksLock() {
return std::shared_lock<std::shared_mutex>(sinksMtx);
}
const std::map<SinkID, std::shared_ptr<SinkEntry>>& AudioStream::getSinks() const {
const std::map<SinkID, std::shared_ptr<SinkEntry>>& Stream::getSinks() const {
return sinks;
}
void AudioStream::startDSP() {
MasterStream::MasterStream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate) :
Stream(manager, name, stream, samplerate)
{}
void MasterStream::setInput(dsp::stream<dsp::stereo_t>* stream, double samplerate) {
std::unique_lock<std::shared_mutex> lck(sinksMtx);
// If all that's needed is to set the input, do it and return
if (samplerate == 0.0) {
split.setInput(stream);
return;
}
// Update samplerate
this->samplerate = samplerate;
// Stop DSP
if (running) {
split.stop();
for (auto& [id, sink] : sinks) {
sink->stopDSP();
}
}
// Set input and samplerate
split.setInput(stream);
for (auto& [id, sink] : sinks) {
sink->setInputSamplerate(samplerate);
}
// Start DSP
if (running) {
for (auto& [id, sink] : sinks) {
sink->startDSP();
}
split.start();
}
}
void MasterStream::setSamplerate(double samplerate) {
std::unique_lock<std::shared_mutex> lck(sinksMtx);
// Update samplerate
this->samplerate = samplerate;
// TODO: Maybe simply disallow while running?
// Stop DSP if it was running
if (running) {
split.stop();
for (auto& [id, sink] : sinks) {
sink->stopDSP();
}
}
// Set samplerate
for (auto& [id, sink] : sinks) {
sink->setInputSamplerate(samplerate);
}
// Start DSP if it was running
if (running) {
for (auto& [id, sink] : sinks) {
sink->startDSP();
}
split.start();
}
}
void MasterStream::startDSP() {
// TODO: Maybe add a different mutex for the stream?
std::unique_lock<std::shared_mutex> lck(sinksMtx);
@ -356,7 +362,7 @@ void AudioStream::startDSP() {
running = true;
}
void AudioStream::stopDSP() {
void MasterStream::stopDSP() {
// TODO: Maybe add a different mutex for the stream?
std::unique_lock<std::shared_mutex> lck(sinksMtx);
@ -371,7 +377,7 @@ void AudioStream::stopDSP() {
running = false;
}
std::shared_ptr<AudioStream> StreamManager::createStream(const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate) {
std::shared_ptr<MasterStream> StreamManager::createStream(const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate) {
std::unique_lock<std::shared_mutex> lck(streamsMtx);
// Check that no stream with that name already exists
@ -381,7 +387,7 @@ std::shared_ptr<AudioStream> StreamManager::createStream(const std::string& name
}
// Create and save stream
auto newStream = std::make_shared<AudioStream>(this, name, stream, samplerate);
std::shared_ptr<MasterStream> newStream(new MasterStream(this, name, stream, samplerate));
streams[name] = newStream;
// Release lock and emit event
@ -391,7 +397,7 @@ std::shared_ptr<AudioStream> StreamManager::createStream(const std::string& name
return newStream;
}
void StreamManager::destroyStream(std::shared_ptr<AudioStream>& stream) {
void StreamManager::destroyStream(std::shared_ptr<MasterStream>& stream) {
// Emit event
onStreamDestroy(stream);
@ -400,7 +406,7 @@ void StreamManager::destroyStream(std::shared_ptr<AudioStream>& stream) {
std::unique_lock<std::shared_mutex> lck(streamsMtx);
// Get iterator of the stream
auto it = std::find_if(streams.begin(), streams.end(), [&stream](std::pair<const std::string, std::shared_ptr<AudioStream>> e) {
auto it = std::find_if(streams.begin(), streams.end(), [&stream](std::pair<const std::string, std::shared_ptr<Stream>> e) {
return e.second == stream;
});
if (it == streams.end()) {
@ -421,7 +427,7 @@ std::shared_lock<std::shared_mutex> StreamManager::getStreamsLock() {
return std::shared_lock<std::shared_mutex>(streamsMtx);
}
const std::map<std::string, std::shared_ptr<AudioStream>>& StreamManager::getStreams() const {
const std::map<std::string, std::shared_ptr<Stream>>& StreamManager::getStreams() const {
return streams;
}

Wyświetl plik

@ -11,12 +11,13 @@
#include <shared_mutex>
#include <stdexcept>
class AudioStream;
class SinkEntry;
class Stream;
class MasterStream;
class StreamManager;
using SinkID = int;
class SinkEntry;
class Sink {
public:
Sink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId);
@ -58,14 +59,13 @@ public:
SinkEntryCreateException(const char* what) : std::runtime_error(what) {}
};
class StreamManager;
// TODO: Would be cool to have data and audio sinks instead of just audio.
class SinkEntry {
friend AudioStream;
friend Sink;
friend Stream;
friend MasterStream;
public:
SinkEntry(StreamManager* manager, AudioStream* parentStream, const std::string& type, SinkID id, double inputSamplerate);
SinkEntry(StreamManager* manager, Stream* parentStream, const std::string& type, SinkID id, double inputSamplerate);
/**
* Get the type of the sink.
@ -165,7 +165,7 @@ private:
std::string type;
const SinkID id;
double inputSamplerate;
AudioStream* const parentStream;
Stream* const parentStream;
StreamManager* const manager;
std::string stringId;
@ -175,28 +175,11 @@ private:
float panning = 0.0f;
};
class StreamManager;
class AudioStream {
friend StreamManager;
class Stream {
protected:
Stream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate);
public:
AudioStream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate);
~AudioStream();
/**
* Set DSP stream input.
* @param stream DSP stream.
* @param samplerate New samplerate (optional, 0.0 if not used).
*/
void setInput(dsp::stream<dsp::stereo_t>* stream, double samplerate = 0.0);
// TODO: There must be a way to pre-stop things to avoid having weird shit happen
/**
* Set the samplerate of the input stream.
* @param samplerate Samplerate in Hz.
*/
void setSamplerate(double samplerate);
~Stream();
/**
* Get the name of the stream.
@ -231,7 +214,40 @@ public:
*/
const std::map<SinkID, std::shared_ptr<SinkEntry>>& getSinks() const;
// TODO: This should only be callable by the module that created the stream
// Emitted when the samplerate of the stream was changed
NewEvent<double> onSamplerateChanged;
// Emitted when a sink was added
NewEvent<std::shared_ptr<SinkEntry>> onSinkAdded;
// Emitted when a sink is being removed
NewEvent<std::shared_ptr<SinkEntry>> onSinkRemove;
protected:
StreamManager* const manager;
const std::string name;
double samplerate;
dsp::routing::Splitter<dsp::stereo_t> split;
bool running = false;
std::map<SinkID, std::shared_ptr<SinkEntry>> sinks;
std::shared_mutex sinksMtx;
};
class MasterStream : public Stream {
friend StreamManager;
MasterStream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate);
public:
/**
* Set DSP stream input.
* @param stream DSP stream.
* @param samplerate New samplerate (optional, 0.0 if not used).
*/
void setInput(dsp::stream<dsp::stereo_t>* stream, double samplerate = 0.0);
/**
* Set the samplerate of the input stream.
* @param samplerate Samplerate in Hz.
*/
void setSamplerate(double samplerate);
/**
* Start the DSP.
@ -242,23 +258,6 @@ public:
* Stop the DSP.
*/
void stopDSP();
// Emitted when the samplerate of the stream was changed
NewEvent<double> onSamplerateChanged;
// Emitted when a sink was added
NewEvent<std::shared_ptr<SinkEntry>> onSinkAdded;
// Emitted when a sink is being removed
NewEvent<std::shared_ptr<SinkEntry>> onSinkRemove;
private:
StreamManager* const manager;
const std::string name;
double samplerate;
dsp::routing::Splitter<dsp::stereo_t> split;
bool running = false;
std::map<SinkID, std::shared_ptr<SinkEntry>> sinks;
std::shared_mutex sinksMtx;
};
class StreamManager {
@ -271,13 +270,13 @@ public:
* @param samplerate Samplerate of the audio data.
* @return Audio stream instance.
*/
std::shared_ptr<AudioStream> createStream(const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate);
std::shared_ptr<MasterStream> createStream(const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate);
/**
* Destroy an audio stream.
* @param stream Stream to destroy. The passed shared pointer will be automatically reset.
*/
void destroyStream(std::shared_ptr<AudioStream>& stream);
void destroyStream(std::shared_ptr<MasterStream>& stream);
/**
* Aquire a lock for the stream list.
@ -289,7 +288,7 @@ public:
* Get a list of streams and their associated names.
* @return Map of names to stream instance.
*/
const std::map<std::string, std::shared_ptr<AudioStream>>& getStreams() const;
const std::map<std::string, std::shared_ptr<Stream>>& getStreams() const;
/**
* Register a sink provider.
@ -317,16 +316,16 @@ public:
const std::vector<std::string>& getSinkTypes() const;
// Emitted when a stream was created
NewEvent<std::shared_ptr<AudioStream>> onStreamCreated;
NewEvent<std::shared_ptr<Stream>> onStreamCreated;
// Emitted when a stream is about to be destroyed
NewEvent<std::shared_ptr<AudioStream>> onStreamDestroy;
NewEvent<std::shared_ptr<Stream>> onStreamDestroy;
// Emitted when a sink provider was registered
NewEvent<const std::string&> onSinkProviderRegistered;
// Emitted when a sink provider is about to be unregistered
NewEvent<const std::string&> onSinkProviderUnregister;
private:
std::map<std::string, std::shared_ptr<AudioStream>> streams;
std::map<std::string, std::shared_ptr<Stream>> streams;
std::shared_mutex streamsMtx;
std::map<std::string, SinkProvider*> providers;

Wyświetl plik

@ -86,14 +86,14 @@ namespace net {
addr.sin_port = htons(port);
}
std::string Address::getIPStr() {
std::string Address::getIPStr() const {
char buf[128];
IP_t ip = getIP();
sprintf(buf, "%d.%d.%d.%d", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF);
return buf;
}
IP_t Address::getIP() {
IP_t Address::getIP() const {
return htonl(addr.sin_addr.s_addr);
}
@ -101,7 +101,7 @@ namespace net {
addr.sin_addr.s_addr = htonl(ip);
}
int Address::getPort() {
int Address::getPort() const {
return htons(addr.sin_port);
}
@ -138,7 +138,16 @@ namespace net {
}
int Socket::send(const uint8_t* data, size_t len, const Address* dest) {
return sendto(sock, (const char*)data, len, 0, (sockaddr*)(dest ? &dest->addr : (raddr ? &raddr->addr : NULL)), sizeof(sockaddr_in));
// Send data
int err = sendto(sock, (const char*)data, len, 0, (sockaddr*)(dest ? &dest->addr : (raddr ? &raddr->addr : NULL)), sizeof(sockaddr_in));
// On error, close socket
if (err <= 0 && !WOULD_BLOCK) {
close();
return err;
}
return err;
}
int Socket::sendstr(const std::string& str, const Address* dest) {
@ -160,8 +169,8 @@ namespace net {
// Set timeout
timeval tv;
tv.tv_sec = 0;
tv.tv_usec = timeout * 1000;
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout - tv.tv_sec*1000) * 1000;
// Wait for data
int err = select(sock+1, &set, NULL, &set, (timeout > 0) ? &tv : NULL);
@ -225,8 +234,8 @@ namespace net {
// Define timeout
timeval tv;
tv.tv_sec = 0;
tv.tv_usec = timeout * 1000;
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout - tv.tv_sec*1000) * 1000;
// Wait for data or error
if (timeout != NONBLOCKING) {
@ -375,13 +384,25 @@ namespace net {
return connect(Address(host, port));
}
std::shared_ptr<Socket> openudp(const Address& raddr, const Address& laddr) {
std::shared_ptr<Socket> openudp(const Address& raddr, const Address& laddr, bool allowBroadcast) {
// Init library if needed
init();
// Create socket
SockHandle_t s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
// If the remote address is multicast, allow multicast connections
#ifdef _WIN32
const char enable = allowBroadcast;
#else
int enable = allowBroadcast;
#endif
if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &enable, sizeof(int)) < 0) {
closeSocket(s);
throw std::runtime_error("Could not enable broadcast on socket");
return NULL;
}
// Bind socket to local port
if (bind(s, (sockaddr*)&laddr.addr, sizeof(sockaddr_in))) {
closeSocket(s);
@ -393,15 +414,15 @@ namespace net {
return std::make_shared<Socket>(s, &raddr);
}
std::shared_ptr<Socket> openudp(std::string rhost, int rport, const Address& laddr) {
return openudp(Address(rhost, rport), laddr);
std::shared_ptr<Socket> openudp(std::string rhost, int rport, const Address& laddr, bool allowBroadcast) {
return openudp(Address(rhost, rport), laddr, allowBroadcast);
}
std::shared_ptr<Socket> openudp(const Address& raddr, std::string lhost, int lport) {
return openudp(raddr, Address(lhost, lport));
std::shared_ptr<Socket> openudp(const Address& raddr, std::string lhost, int lport, bool allowBroadcast) {
return openudp(raddr, Address(lhost, lport), allowBroadcast);
}
std::shared_ptr<Socket> openudp(std::string rhost, int rport, std::string lhost, int lport) {
return openudp(Address(rhost, rport), Address(lhost, lport));
std::shared_ptr<Socket> openudp(std::string rhost, int rport, std::string lhost, int lport, bool allowBroadcast) {
return openudp(Address(rhost, rport), Address(lhost, lport), allowBroadcast);
}
}

Wyświetl plik

@ -67,13 +67,13 @@ namespace net {
* Get the IP address.
* @return IP address in standard string format.
*/
std::string getIPStr();
std::string getIPStr() const;
/**
* Get the IP address.
* @return IP address in host byte order.
*/
IP_t getIP();
IP_t getIP() const;
/**
* Set the IP address.
@ -85,7 +85,7 @@ namespace net {
* Get the TCP/UDP port.
* @return TCP/UDP port number.
*/
int getPort();
int getPort() const;
/**
* Set the TCP/UDP port.
@ -246,37 +246,37 @@ namespace net {
/**
* Create UDP socket.
* @param raddr Remote address.
* @param raddr Remote address. Set to a multicast address to allow multicast.
* @param laddr Local address to bind the socket to.
* @return Socket instance on success, Throws runtime_error otherwise.
*/
std::shared_ptr<Socket> openudp(const Address& raddr, const Address& laddr);
std::shared_ptr<Socket> openudp(const Address& raddr, const Address& laddr, bool allowBroadcast = false);
/**
* Create UDP socket.
* @param rhost Remote hostname or IP address.
* @param rhost Remote hostname or IP address. Set to a multicast address to allow multicast.
* @param rport Remote port.
* @param laddr Local address to bind the socket to.
* @return Socket instance on success, Throws runtime_error otherwise.
*/
std::shared_ptr<Socket> openudp(std::string rhost, int rport, const Address& laddr);
std::shared_ptr<Socket> openudp(std::string rhost, int rport, const Address& laddr, bool allowBroadcast = false);
/**
* Create UDP socket.
* @param raddr Remote address.
* @param raddr Remote address. Set to a multicast or broadcast address to allow multicast.
* @param lhost Local hostname or IP used to bind the socket (optional, "0.0.0.0" for Any).
* @param lpost Local port used to bind the socket to (optional, 0 to allocate automatically).
* @return Socket instance on success, Throws runtime_error otherwise.
*/
std::shared_ptr<Socket> openudp(const Address& raddr, std::string lhost = "0.0.0.0", int lport = 0);
std::shared_ptr<Socket> openudp(const Address& raddr, std::string lhost = "0.0.0.0", int lport = 0, bool allowBroadcast = false);
/**
* Create UDP socket.
* @param rhost Remote hostname or IP address.
* @param rhost Remote hostname or IP address. Set to a multicast or broadcast address to allow multicast.
* @param rport Remote port.
* @param lhost Local hostname or IP used to bind the socket (optional, "0.0.0.0" for Any).
* @param lpost Local port used to bind the socket to (optional, 0 to allocate automatically).
* @return Socket instance on success, Throws runtime_error otherwise.
*/
std::shared_ptr<Socket> openudp(std::string rhost, int rport, std::string lhost = "0.0.0.0", int lport = 0);
std::shared_ptr<Socket> openudp(std::string rhost, int rport, std::string lhost = "0.0.0.0", int lport = 0, bool allowBroadcast = false);
}

Wyświetl plik

@ -320,7 +320,7 @@ namespace net {
}
entry.handler(std::move(client), entry.ctx);
}
catch (std::exception e) {
catch (const std::exception& e) {
listening = false;
return;
}

Wyświetl plik

@ -258,6 +258,7 @@ namespace net::http {
// Deserialize
req.deserialize(respData);
return 0; // Might wanna return size instead
}
int Client::sendResponseHeader(ResponseHeader& resp) {
@ -275,6 +276,7 @@ namespace net::http {
// Deserialize
resp.deserialize(respData);
return 0; // Might wanna return size instead
}
int Client::sendChunkHeader(ChunkHeader& chdr) {

Wyświetl plik

@ -7,6 +7,14 @@ namespace riff {
const char* LIST_SIGNATURE = "LIST";
const size_t RIFF_LABEL_SIZE = 4;
// Writer::Writer(const Writer&& b) {
// //file = std::move(b.file);
// }
Writer::~Writer() {
close();
}
bool Writer::open(std::string path, const char form[4]) {
std::lock_guard<std::recursive_mutex> lck(mtx);

Wyświetl plik

@ -20,6 +20,10 @@ namespace riff {
class Writer {
public:
Writer() {}
// Writer(const Writer&& b);
~Writer();
bool open(std::string path, const char form[4]);
bool isOpen();
void close();
@ -40,4 +44,23 @@ namespace riff {
std::ofstream file;
std::stack<ChunkDesc> chunks;
};
// class Reader {
// public:
// Reader();
// Reader(const Reader&& b);
// ~Reader();
// bool open(std::string path);
// bool isOpen();
// void close();
// const std::string& form();
// private:
// std::string _form;
// std::recursive_mutex mtx;
// std::ofstream file;
// };
}

Wyświetl plik

@ -0,0 +1,63 @@
#pragma once
#include <dsp/loop/pll.h>
#include "chrominance_filter.h"
// TODO: Should be 60 but had to try something
#define BURST_START (63+CHROMA_FIR_DELAY)
#define BURST_END (BURST_START+28)
#define A_PHASE ((135.0/180.0)*FL_M_PI)
#define B_PHASE ((-135.0/180.0)*FL_M_PI)
namespace dsp::loop {
class ChromaPLL : public PLL {
using base_type = PLL;
public:
ChromaPLL() {}
ChromaPLL(stream<complex_t>* in, double bandwidth, double initPhase = 0.0, double initFreq = 0.0, double minFreq = -FL_M_PI, double maxFreq = FL_M_PI) {
base_type::init(in, bandwidth, initFreq, initPhase, minFreq, maxFreq);
}
inline int process(int count, complex_t* in, complex_t* out, bool aphase = false) {
// Process the pre-burst section
for (int i = 0; i < BURST_START; i++) {
out[i] = in[i] * math::phasor(-pcl.phase);
pcl.advancePhase();
}
// Process the burst itself
if (aphase) {
for (int i = BURST_START; i < BURST_END; i++) {
complex_t outVal = in[i] * math::phasor(-pcl.phase);
out[i] = outVal;
pcl.advance(math::normalizePhase(outVal.phase() - A_PHASE));
}
}
else {
for (int i = BURST_START; i < BURST_END; i++) {
complex_t outVal = in[i] * math::phasor(-pcl.phase);
out[i] = outVal;
pcl.advance(math::normalizePhase(outVal.phase() - B_PHASE));
}
}
// Process the post-burst section
for (int i = BURST_END; i < count; i++) {
out[i] = in[i] * math::phasor(-pcl.phase);
pcl.advancePhase();
}
return count;
}
inline int processBlank(int count, complex_t* in, complex_t* out) {
for (int i = 0; i < count; i++) {
out[i] = in[i] * math::phasor(-pcl.phase);
pcl.advancePhase();
}
return count;
}
};
}

Wyświetl plik

@ -0,0 +1,239 @@
#pragma once
#include <dsp/types.h>
inline const dsp::complex_t CHROMA_FIR[] = {
{-0.000005461290583903, -0.000011336784355655},
{ 0.000020060944485414, 0.000009851315045203},
{-0.000034177222729438, 0.000007245841504981},
{ 0.000027694034878705, -0.000033114740542635},
{-0.000001217597841648, 0.000039141482370942},
{-0.000008324593371228, -0.000011315001355976},
{-0.000038085228233509, -0.000010585909953738},
{ 0.000114833396071141, -0.000047778708840608},
{-0.000115428390169113, 0.000205816198882814},
{-0.000055467806072871, -0.000356692479491626},
{ 0.000349316846854190, 0.000326162940234916},
{-0.000558465829929114, -0.000048001521408724},
{ 0.000488176200631416, -0.000319593757302922},
{-0.000169437838021935, 0.000501610900725908},
{-0.000131793335799502, -0.000373003580727547},
{ 0.000166817395492786, 0.000105930895534474},
{ 0.000030499908326112, -0.000003048682668943},
{-0.000174999505027919, 0.000168008090089458},
{ 0.000054431163395030, -0.000385174790951272},
{ 0.000215876012859739, 0.000372695852521209},
{-0.000325534912280750, -0.000130173041693966},
{ 0.000154951430569290, -0.000045395998708328},
{ 0.000054324657659002, -0.000076028700470037},
{ 0.000015664427565764, 0.000348002612845696},
{-0.000345943017888332, -0.000402175417043307},
{ 0.000568731727879741, 0.000112347863435682},
{-0.000416485880859085, 0.000211750352828909},
{ 0.000087462353623011, -0.000188197153014309},
{-0.000032082305030264, -0.000136804226080664},
{ 0.000379089999045955, 0.000303466839685362},
{-0.000726760198519770, -0.000007022279302816},
{ 0.000619888661818195, -0.000476871323359809},
{-0.000151885493742993, 0.000595641190573181},
{-0.000100626407015494, -0.000227947144491108},
{-0.000201935458823941, -0.000107628631934340},
{ 0.000680260922139900, -0.000120771182888852},
{-0.000666108629277491, 0.000744775901128973},
{ 0.000067236591919755, -0.001044125966364420},
{ 0.000447037274751822, 0.000651912509450913},
{-0.000262675893448686, -0.000082499729563337},
{-0.000349821460486320, 0.000132102793530818},
{ 0.000507024815168287, -0.000837598610490618},
{ 0.000163814255478652, 0.001346530693477834},
{-0.000970457632383793, -0.000968411010101160},
{ 0.000974834882891140, 0.000116507082762032},
{-0.000225464280571542, 0.000137131865995708},
{-0.000211542240694642, 0.000563783548428947},
{-0.000414412310798766, -0.001309793399193736},
{ 0.001497010004594478, 0.001021907858926259},
{-0.001752019159639658, 0.000116536066154131},
{ 0.000872822027879430, -0.000783952720205569},
{-0.000032439446797970, 0.000184988059956734},
{ 0.000446259382722895, 0.000833040920509238},
{-0.001741577737284306, -0.000764423771425237},
{ 0.002306569133792772, -0.000593352416441601},
{-0.001336084746214192, 0.001744394557524181},
{-0.000015810020735495, -0.001342809547658260},
{ 0.000007636494885364, 0.000009498318627546},
{ 0.001403876768349702, 0.000326101441888391},
{-0.002351020828600226, 0.001098649819278302},
{ 0.001389314639579544, -0.002746943712072884},
{ 0.000526319899588909, 0.002635084366837732},
{-0.001109526585744687, -0.000950323796527721},
{-0.000307792427984886, -0.000013203419520794},
{ 0.001737955094951111, -0.001247368808692850},
{-0.000974502437588420, 0.003352512117661680},
{-0.001462571137390936, -0.003635296917435679},
{ 0.002783459090201693, 0.001604420226187745},
{-0.001471518558760170, 0.000211117948702137},
{-0.000575340825070194, 0.000601820846100026},
{ 0.000302090333345692, -0.003088058972305493},
{ 0.002496092353182990, 0.003912508340989065},
{-0.004645661091012423, -0.001630427298020200},
{ 0.003556824805628799, -0.001209822327859352},
{-0.000744999556260706, 0.001143238699138109},
{ 0.000144278726929409, 0.001638049051599065},
{-0.003025291044450178, -0.003226370992887968},
{ 0.006047866290490120, 0.000927406808799887},
{-0.005338456415106141, 0.003008811999350399},
{ 0.001642959659014839, -0.003972384205231079},
{ 0.000273874932822212, 0.000977326273749033},
{ 0.002315022846573390, 0.001695671268241410},
{-0.006240953957978884, 0.000207330368698293},
{ 0.006164252120861735, -0.005177351717451013},
{-0.001560310257561104, 0.007437030759707700},
{-0.002131333814462852, -0.004317129694157112},
{ 0.000280518918541908, 0.000134405998842553},
{ 0.004612116481180659, -0.001024468120657814},
{-0.005599300279638699, 0.006828277067771868},
{ 0.000228879728552504, -0.010675998154712657},
{ 0.005692081512980654, 0.007582243186569848},
{-0.005100500569859509, -0.001364751685737153},
{-0.000902490398043454, 0.000385770160220703},
{ 0.003673858819546609, -0.006701685283451640},
{ 0.002079056046131593, 0.012568579063417429},
{-0.010730008156911677, -0.009826454574016218},
{ 0.012092401380903161, 0.000921764172237851},
{-0.004714530989129091, 0.003151948807627123},
{-0.001055930168838909, 0.003228576712467020},
{-0.004343270165991213, -0.011924332879354394},
{ 0.016499994418955999, 0.010255324919126899},
{-0.021047239750251585, 0.002309419513135448},
{ 0.011855513874047341, -0.011604071033866310},
{-0.000777842281358575, 0.005916341648175263},
{ 0.004380939277688377, 0.007397670455730446},
{-0.021891594662401131, -0.008509480947490166},
{ 0.032787638290674201, -0.009950745850861956},
{-0.021022579272463194, 0.030030850567389102},
{-0.001508145650189953, -0.027571914870304640},
{ 0.004056649693022923, 0.004624901687718579},
{ 0.025728742586666287, 0.004824671348397606},
{-0.058002700931665603, 0.030198618296813803},
{ 0.043631619628438784, -0.096308304333327280},
{ 0.033451363423624300, 0.136687079396426990},
{-0.129387018420204200, -0.101540513046619400},
{ 0.172881344826560730, -0.000000000000005297},
{-0.129387018420198010, 0.101540513046627330},
{ 0.033451363423615862, -0.136687079396429050},
{ 0.043631619628444723, 0.096308304333324601},
{-0.058002700931667456, -0.030198618296810247},
{ 0.025728742586665992, -0.004824671348399184},
{ 0.004056649693022639, -0.004624901687718827},
{-0.001508145650188251, 0.027571914870304734},
{-0.021022579272465047, -0.030030850567387805},
{ 0.032787638290674812, 0.009950745850859947},
{-0.021891594662400610, 0.008509480947491507},
{ 0.004380939277687923, -0.007397670455730714},
{-0.000777842281358940, -0.005916341648175215},
{ 0.011855513874048058, 0.011604071033865578},
{-0.021047239750251731, -0.002309419513134139},
{ 0.016499994418955360, -0.010255324919127926},
{-0.004343270165990471, 0.011924332879354665},
{-0.001055930168839110, -0.003228576712466955},
{-0.004714530989129287, -0.003151948807626830},
{ 0.012092401380903103, -0.000921764172238603},
{-0.010730008156911072, 0.009826454574016881},
{ 0.002079056046130817, -0.012568579063417559},
{ 0.003673858819547020, 0.006701685283451416},
{-0.000902490398043478, -0.000385770160220647},
{-0.005100500569859424, 0.001364751685737466},
{ 0.005692081512980187, -0.007582243186570198},
{ 0.000228879728553163, 0.010675998154712643},
{-0.005599300279639117, -0.006828277067771524},
{ 0.004612116481180722, 0.001024468120657532},
{ 0.000280518918541900, -0.000134405998842571},
{-0.002131333814462586, 0.004317129694157243},
{-0.001560310257561563, -0.007437030759707604},
{ 0.006164252120862052, 0.005177351717450635},
{-0.006240953957978898, -0.000207330368697911},
{ 0.002315022846573286, -0.001695671268241552},
{ 0.000273874932822152, -0.000977326273749050},
{ 0.001642959659015084, 0.003972384205230976},
{-0.005338456415106324, -0.003008811999350072},
{ 0.006047866290490063, -0.000927406808800258},
{-0.003025291044449980, 0.003226370992888153},
{ 0.000144278726929308, -0.001638049051599074},
{-0.000744999556260777, -0.001143238699138063},
{ 0.003556824805628873, 0.001209822327859134},
{-0.004645661091012323, 0.001630427298020484},
{ 0.002496092353182751, -0.003912508340989219},
{ 0.000302090333345882, 0.003088058972305475},
{-0.000575340825070231, -0.000601820846099991},
{-0.001471518558760183, -0.000211117948702046},
{ 0.002783459090201593, -0.001604420226187919},
{-0.001462571137390710, 0.003635296917435769},
{-0.000974502437588628, -0.003352512117661619},
{ 0.001737955094951189, 0.001247368808692742},
{-0.000307792427984885, 0.000013203419520814},
{-0.001109526585744628, 0.000950323796527789},
{ 0.000526319899588746, -0.002635084366837765},
{ 0.001389314639579712, 0.002746943712072799},
{-0.002351020828600294, -0.001098649819278158},
{ 0.001403876768349682, -0.000326101441888477},
{ 0.000007636494885364, -0.000009498318627546},
{-0.000015810020735412, 0.001342809547658261},
{-0.001336084746214299, -0.001744394557524099},
{ 0.002306569133792808, 0.000593352416441460},
{-0.001741577737284259, 0.000764423771425344},
{ 0.000446259382722843, -0.000833040920509266},
{-0.000032439446797982, -0.000184988059956732},
{ 0.000872822027879478, 0.000783952720205515},
{-0.001752019159639665, -0.000116536066154024},
{ 0.001497010004594416, -0.001021907858926351},
{-0.000414412310798685, 0.001309793399193761},
{-0.000211542240694677, -0.000563783548428934},
{-0.000225464280571550, -0.000137131865995694},
{ 0.000974834882891133, -0.000116507082762092},
{-0.000970457632383734, 0.000968411010101219},
{ 0.000163814255478569, -0.001346530693477844},
{ 0.000507024815168339, 0.000837598610490586},
{-0.000349821460486328, -0.000132102793530797},
{-0.000262675893448681, 0.000082499729563353},
{ 0.000447037274751782, -0.000651912509450940},
{ 0.000067236591919819, 0.001044125966364416},
{-0.000666108629277537, -0.000744775901128932},
{ 0.000680260922139908, 0.000120771182888810},
{-0.000201935458823935, 0.000107628631934352},
{-0.000100626407015480, 0.000227947144491114},
{-0.000151885493743030, -0.000595641190573172},
{ 0.000619888661818225, 0.000476871323359771},
{-0.000726760198519770, 0.000007022279302861},
{ 0.000379089999045936, -0.000303466839685386},
{-0.000032082305030256, 0.000136804226080666},
{ 0.000087462353623023, 0.000188197153014303},
{-0.000416485880859098, -0.000211750352828883},
{ 0.000568731727879734, -0.000112347863435717},
{-0.000345943017888307, 0.000402175417043329},
{ 0.000015664427565742, -0.000348002612845697},
{ 0.000054324657659007, 0.000076028700470034},
{ 0.000154951430569292, 0.000045395998708319},
{-0.000325534912280742, 0.000130173041693986},
{ 0.000215876012859716, -0.000372695852521222},
{ 0.000054431163395054, 0.000385174790951269},
{-0.000174999505027930, -0.000168008090089447},
{ 0.000030499908326113, 0.000003048682668941},
{ 0.000166817395492779, -0.000105930895534485},
{-0.000131793335799479, 0.000373003580727555},
{-0.000169437838021966, -0.000501610900725898},
{ 0.000488176200631435, 0.000319593757302892},
{-0.000558465829929111, 0.000048001521408758},
{ 0.000349316846854170, -0.000326162940234938},
{-0.000055467806072849, 0.000356692479491629},
{-0.000115428390169126, -0.000205816198882806},
{ 0.000114833396071144, 0.000047778708840601},
{-0.000038085228233508, 0.000010585909953741},
{-0.000008324593371228, 0.000011315001355977},
{-0.000001217597841650, -0.000039141482370942},
{ 0.000027694034878707, 0.000033114740542633},
{-0.000034177222729439, -0.000007245841504979},
{ 0.000020060944485413, -0.000009851315045204},
{-0.000005461290583903, 0.000011336784355656},
};
#define CHROMA_FIR_SIZE (sizeof(CHROMA_FIR)/sizeof(dsp::complex_t))
#define CHROMA_FIR_DELAY ((CHROMA_FIR_SIZE-1)/2)

Wyświetl plik

@ -0,0 +1,193 @@
#pragma once
#include <dsp/processor.h>
#include <dsp/loop/phase_control_loop.h>
#include <dsp/taps/windowed_sinc.h>
#include <dsp/multirate/polyphase_bank.h>
#include <dsp/math/step.h>
class LineSync : public dsp::Processor<float, float> {
using base_type = dsp::Processor<float, float>;
public:
LineSync() {}
LineSync(dsp::stream<float>* in, double omega, double omegaGain, double muGain, double omegaRelLimit, int interpPhaseCount = 128, int interpTapCount = 8) { init(in, omega, omegaGain, muGain, omegaRelLimit, interpPhaseCount, interpTapCount); }
~LineSync() {
if (!base_type::_block_init) { return; }
base_type::stop();
dsp::multirate::freePolyphaseBank(interpBank);
dsp::buffer::free(buffer);
}
void init(dsp::stream<float>* in, double omega, double omegaGain, double muGain, double omegaRelLimit, int interpPhaseCount = 128, int interpTapCount = 8) {
_omega = omega;
_omegaGain = omegaGain;
_muGain = muGain;
_omegaRelLimit = omegaRelLimit;
_interpPhaseCount = interpPhaseCount;
_interpTapCount = interpTapCount;
pcl.init(_muGain, _omegaGain, 0.0, 0.0, 1.0, _omega, _omega * (1.0 - omegaRelLimit), _omega * (1.0 + omegaRelLimit));
generateInterpTaps();
buffer = dsp::buffer::alloc<float>(STREAM_BUFFER_SIZE + _interpTapCount);
bufStart = &buffer[_interpTapCount - 1];
base_type::init(in);
}
void setOmegaGain(double omegaGain) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_omegaGain = omegaGain;
pcl.setCoefficients(_muGain, _omegaGain);
}
void setMuGain(double muGain) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_muGain = muGain;
pcl.setCoefficients(_muGain, _omegaGain);
}
void setOmegaRelLimit(double omegaRelLimit) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_omegaRelLimit = omegaRelLimit;
pcl.setFreqLimits(_omega * (1.0 - _omegaRelLimit), _omega * (1.0 + _omegaRelLimit));
}
void setSyncLevel(float level) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
syncLevel = level;
}
void setInterpParams(int interpPhaseCount, int interpTapCount) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_interpPhaseCount = interpPhaseCount;
_interpTapCount = interpTapCount;
dsp::multirate::freePolyphaseBank(interpBank);
dsp::buffer::free(buffer);
generateInterpTaps();
buffer = dsp::buffer::alloc<float>(STREAM_BUFFER_SIZE + _interpTapCount);
bufStart = &buffer[_interpTapCount - 1];
base_type::tempStart();
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
offset = 0;
pcl.phase = 0.0f;
pcl.freq = _omega;
base_type::tempStart();
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
// Copy data to work buffer
memcpy(bufStart, base_type::_in->readBuf, count * sizeof(float));
if (test2) {
test2 = false;
offset += 5;
}
// Process all samples
while (offset < count) {
// Calculate new output value
int phase = std::clamp<int>(floorf(pcl.phase * (float)_interpPhaseCount), 0, _interpPhaseCount - 1);
float outVal;
volk_32f_x2_dot_prod_32f(&outVal, &buffer[offset], interpBank.phases[phase], _interpTapCount);
base_type::out.writeBuf[outCount++] = outVal;
// If the end of the line is reached, process it and determin error
float error = 0;
if (outCount >= 720) {
// Compute averages.
float left = 0.0f, right = 0.0f;
for (int i = (720-17); i < 720; i++) {
left += base_type::out.writeBuf[i];
}
for (int i = 0; i < 27; i++) {
left += base_type::out.writeBuf[i];
}
for (int i = 27; i < (54+17); i++) {
right += base_type::out.writeBuf[i];
}
left *= (1.0f/44.0f);
right *= (1.0f/44.0f);
// If the sync is present, compute error
if ((left < syncLevel && right < syncLevel) && !forceLock) {
error = (left + syncBias - right);
locked = true;
}
else {
locked = false;
}
if (++counter >= 100) {
counter = 0;
//flog::warn("Left: {}, Right: {}, Error: {}, Freq: {}, Phase: {}", left, right, error, pcl.freq, pcl.phase);
}
// Output line
if (!base_type::out.swap(outCount)) { break; }
outCount = 0;
}
// Advance symbol offset and phase
pcl.advance(error);
float delta = floorf(pcl.phase);
offset += delta;
pcl.phase -= delta;
}
offset -= count;
// Update delay buffer
memmove(buffer, &buffer[count], (_interpTapCount - 1) * sizeof(float));
// Swap if some data was generated
base_type::_in->flush();
return outCount;
}
bool locked = false;
bool test2 = false;
float syncBias = 0.0f;
bool forceLock = false;
int counter = 0;
protected:
void generateInterpTaps() {
double bw = 0.5 / (double)_interpPhaseCount;
dsp::tap<float> lp = dsp::taps::windowedSinc<float>(_interpPhaseCount * _interpTapCount, dsp::math::hzToRads(bw, 1.0), dsp::window::nuttall, _interpPhaseCount);
interpBank = dsp::multirate::buildPolyphaseBank<float>(_interpPhaseCount, lp);
dsp::taps::free(lp);
}
dsp::multirate::PolyphaseBank<float> interpBank;
dsp::loop::PhaseControlLoop<double, false> pcl;
double _omega;
double _omegaGain;
double _muGain;
double _omegaRelLimit;
int _interpPhaseCount;
int _interpTapCount;
int offset = 0;
int outCount = 0;
float* buffer;
float* bufStart;
float syncLevel = -0.03f;
};

Wyświetl plik

@ -10,6 +10,15 @@
#include <dsp/demod/quadrature.h>
#include <dsp/sink/handler_sink.h>
#include "linesync.h"
#include <dsp/loop/pll.h>
#include <dsp/convert/real_to_complex.h>
#include <dsp/filter/fir.h>
#include <dsp/taps/from_array.h>
#include "chrominance_filter.h"
#include "chroma_pll.h"
#define CONCAT(a, b) ((std::string(a) + b).c_str())
@ -17,7 +26,8 @@ SDRPP_MOD_INFO{/* Name: */ "atv_decoder",
/* Description: */ "ATV decoder for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ -1};
/* Max instances */ -1
};
#define SAMPLE_RATE (625.0f * 720.0f * 25.0f)
@ -29,9 +39,16 @@ class ATVDecoderModule : public ModuleManager::Instance {
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 8000000.0f, SAMPLE_RATE, SAMPLE_RATE, SAMPLE_RATE, true);
demod.init(vfo->output, SAMPLE_RATE, SAMPLE_RATE / 2.0f);
sink.init(&demod.out, handler, this);
sync.init(&demod.out, 1.0f, 1e-6, 1.0, 0.05);
sink.init(&sync.out, handler, this);
r2c.init(NULL);
chromaTaps = dsp::taps::fromArray(CHROMA_FIR_SIZE, CHROMA_FIR);
fir.init(NULL, chromaTaps);
pll.init(NULL, 0.01, 0.0, dsp::math::hzToRads(4433618.75, SAMPLE_RATE), dsp::math::hzToRads(4433618.75*0.90, SAMPLE_RATE), dsp::math::hzToRads(4433618.75*1.1, SAMPLE_RATE));
demod.start();
sync.start();
sink.start();
gui::menu.registerEntry(name, menuHandler, this, this);
@ -47,9 +64,13 @@ class ATVDecoderModule : public ModuleManager::Instance {
void postInit() {}
void enable() { enabled = true; }
void enable() {
enabled = true;
}
void disable() { enabled = false; }
void disable() {
enabled = false;
}
bool isEnabled() { return enabled; }
@ -61,6 +82,8 @@ class ATVDecoderModule : public ModuleManager::Instance {
style::beginDisabled();
}
// Ideal width for testing: 750pixels
ImGui::FillWidth();
_this->img.draw();
@ -76,6 +99,28 @@ class ATVDecoderModule : public ModuleManager::Instance {
ImGui::FillWidth();
ImGui::SliderFloat("##spanLvl", &_this->spanLvl, 0, 1.0);
ImGui::LeftLabel("Sync Bias");
ImGui::FillWidth();
ImGui::SliderFloat("##syncBias", &_this->sync.syncBias,-0.1, 0.1);
if (ImGui::Button("Test2")) {
_this->sync.test2 = true;
}
if (ImGui::Button("Switch frame")) {
std::lock_guard<std::mutex> lck(_this->evenFrameMtx);
_this->evenFrame = !_this->evenFrame;
}
if (_this->sync.locked) {
ImGui::TextColored(ImVec4(0, 1, 0, 1), "Locked");
}
else {
ImGui::TextUnformatted("Not locked");
}
ImGui::Checkbox("Force Lock", &_this->sync.forceLock);
if (!_this->enabled) {
style::endDisabled();
}
@ -84,70 +129,66 @@ class ATVDecoderModule : public ModuleManager::Instance {
static void handler(float *data, int count, void *ctx) {
ATVDecoderModule *_this = (ATVDecoderModule *)ctx;
uint8_t *buf = (uint8_t *)_this->img.buffer;
float val;
float imval;
int pos = 0;
// Convert line to complex
_this->r2c.process(720, data, _this->r2c.out.writeBuf);
// Isolate the chroma subcarrier
_this->fir.process(720, _this->r2c.out.writeBuf, _this->fir.out.writeBuf);
// Run chroma carrier through the PLL
_this->pll.process(720, _this->fir.out.writeBuf, _this->pll.out.writeBuf, ((_this->ypos%2)==1) ^ _this->evenFrame);
// Render line to the image without color
//int lypos = _this->ypos - 1;
//if (lypos < 0) { lypos = 624; }
//uint32_t* lastLine = &((uint32_t *)_this->img.buffer)[(lypos < 313) ? (lypos*720*2) : ((((lypos - 313)*2)+1)*720) ];
//uint32_t* currentLine = &((uint32_t *)_this->img.buffer)[(_this->ypos < 313) ? (_this->ypos*720*2) : ((((_this->ypos - 313)*2)+1)*720) ];
uint32_t* currentLine = &((uint32_t *)_this->img.buffer)[_this->ypos*720];
for (int i = 0; i < count; i++) {
val = data[i];
// Sync
if (val < _this->sync_level) {
_this->sync_count++;
}
else {
if (_this->sync_count >= 300) {
_this->short_sync = 0;
}
else if (_this->sync_count >= 33) {
if (_this->short_sync == 5) {
_this->even_field = false;
_this->ypos = 0;
_this->img.swap();
buf = (uint8_t *)_this->img.buffer;
}
else if (_this->short_sync == 4) {
_this->even_field = true;
_this->ypos = 0;
}
_this->xpos = 0;
_this->short_sync = 0;
}
else if (_this->sync_count >= 15) {
_this->short_sync++;
}
_this->sync_count = 0;
//float imval = std::clamp<float>((data[i] - _this->minLvl) * 255.0 / _this->spanLvl, 0, 255);
uint32_t re = std::clamp<float>((_this->pll.out.writeBuf[i].re - _this->minLvl) * 255.0 / _this->spanLvl, 0, 255);
uint32_t im = std::clamp<float>((_this->pll.out.writeBuf[i].im - _this->minLvl) * 255.0 / _this->spanLvl, 0, 255);
currentLine[i] = 0xFF000000 | (im << 8) | re;
}
// Vertical scan logic
_this->ypos++;
bool rollover = _this->ypos >= 625;
if (rollover) {
{
std::lock_guard<std::mutex> lck(_this->evenFrameMtx);
_this->evenFrame = !_this->evenFrame;
}
_this->ypos = 0;
_this->img.swap();
}
// Measure vsync levels
float sync0 = 0.0f, sync1 = 0.0f;
for (int i = 0; i < 306; i++) {
sync0 += data[i];
}
for (int i = (720/2); i < ((720/2)+306); i++) {
sync1 += data[i];
}
sync0 *= (1.0f/305.0f);
sync1 *= (1.0f/305.0f);
// Draw
imval = std::clamp<float>((val - _this->minLvl) * 255.0 / _this->spanLvl, 0, 255);
if (_this->even_field) {
pos = ((720 * _this->ypos * 2) + _this->xpos) * 4;
}
else {
pos = ((720 * (_this->ypos * 2 + 1)) + _this->xpos) * 4;
}
// Save sync detection to history
_this->syncHistory >>= 2;
_this->syncHistory |= (((uint16_t)(sync1 < _this->sync_level)) << 9) | (((uint16_t)(sync0 < _this->sync_level)) << 8);
buf[pos] = imval;
buf[pos + 1] = imval;
buf[pos + 2] = imval;
buf[pos + 3] = imval;
// Image logic
_this->xpos++;
if (_this->xpos >= 720) {
_this->ypos++;
_this->xpos = 0;
}
if (_this->ypos >= 312) {
_this->ypos = 0;
_this->xpos = 0;
_this->even_field = !_this->even_field;
if (_this->even_field) {
_this->img.swap();
buf = (uint8_t *)_this->img.buffer;
}
// Trigger vsync in case one is detected
// TODO: Also sync with odd field
if (!rollover && _this->syncHistory == 0b0000011111) {
{
std::lock_guard<std::mutex> lck(_this->evenFrameMtx);
_this->evenFrame = !_this->evenFrame;
}
_this->ypos = 0;
_this->img.swap();
}
}
@ -156,19 +197,27 @@ class ATVDecoderModule : public ModuleManager::Instance {
VFOManager::VFO *vfo = NULL;
dsp::demod::Quadrature demod;
LineSync sync;
dsp::sink::Handler<float> sink;
int xpos = 0;
dsp::convert::RealToComplex r2c;
dsp::tap<dsp::complex_t> chromaTaps;
dsp::filter::FIR<dsp::complex_t, dsp::complex_t> fir;
dsp::loop::ChromaPLL pll;
int ypos = 0;
bool even_field = false;
float sync_level = -0.3f;
bool evenFrame = false;
std::mutex evenFrameMtx;
float sync_level = -0.06f;
int sync_count = 0;
int short_sync = 0;
float minLvl = 0.0f;
float spanLvl = 1.0f;
bool lockedLines = 0;
uint16_t syncHistory = 0;
ImGui::ImageDisplay img;
};

Wyświetl plik

@ -0,0 +1,8 @@
cmake_minimum_required(VERSION 3.13)
project(pager_decoder)
file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c")
include(${SDRPP_MODULE_CMAKE})
target_include_directories(pager_decoder PRIVATE "src/")

Wyświetl plik

@ -0,0 +1,11 @@
#pragma once
#include <signal_path/vfo_manager.h>
class Decoder {
public:
virtual ~Decoder() {}
virtual void showMenu() {};
virtual void setVFO(VFOManager::VFO* vfo) = 0;
virtual void start() = 0;
virtual void stop() = 0;
};

Wyświetl plik

@ -0,0 +1,96 @@
#pragma once
#include "../decoder.h"
#include <signal_path/vfo_manager.h>
#include <utils/optionlist.h>
#include <gui/widgets/symbol_diagram.h>
#include <gui/style.h>
#include <dsp/sink/handler_sink.h>
#include "flex.h"
class FLEXDecoder : public Decoder {
dsp::stream<float> dummy1;
dsp::stream<uint8_t> dummy2;
public:
FLEXDecoder(const std::string& name, VFOManager::VFO* vfo) : diag(0.6, 1600) {
this->name = name;
this->vfo = vfo;
// Define baudrate options
baudrates.define(1600, "1600 Baud", 1600);
baudrates.define(3200, "3200 Baud", 3200);
baudrates.define(6400, "6400 Baud", 6400);
// Init DSP
vfo->setBandwidthLimits(12500, 12500, true);
vfo->setSampleRate(16000, 12500);
reshape.init(&dummy1, 1600.0, (1600 / 30.0) - 1600.0);
dataHandler.init(&dummy2, _dataHandler, this);
diagHandler.init(&reshape.out, _diagHandler, this);
}
~FLEXDecoder() {
stop();
}
void showMenu() {
ImGui::LeftLabel("Baudrate");
ImGui::FillWidth();
if (ImGui::Combo(("##pager_decoder_flex_br_" + name).c_str(), &brId, baudrates.txt)) {
// TODO
}
ImGui::FillWidth();
diag.draw();
}
void setVFO(VFOManager::VFO* vfo) {
this->vfo = vfo;
vfo->setBandwidthLimits(12500, 12500, true);
vfo->setSampleRate(24000, 12500);
// dsp.setInput(vfo->output);
}
void start() {
flog::debug("FLEX start");
// dsp.start();
reshape.start();
dataHandler.start();
diagHandler.start();
}
void stop() {
flog::debug("FLEX stop");
// dsp.stop();
reshape.stop();
dataHandler.stop();
diagHandler.stop();
}
private:
static void _dataHandler(uint8_t* data, int count, void* ctx) {
FLEXDecoder* _this = (FLEXDecoder*)ctx;
// _this->decoder.process(data, count);
}
static void _diagHandler(float* data, int count, void* ctx) {
FLEXDecoder* _this = (FLEXDecoder*)ctx;
float* buf = _this->diag.acquireBuffer();
memcpy(buf, data, count * sizeof(float));
_this->diag.releaseBuffer();
}
std::string name;
VFOManager::VFO* vfo;
dsp::buffer::Reshaper<float> reshape;
dsp::sink::Handler<uint8_t> dataHandler;
dsp::sink::Handler<float> diagHandler;
flex::Decoder decoder;
ImGui::SymbolDiagram diag;
int brId = 0;
OptionList<int, int> baudrates;
};

Wyświetl plik

@ -0,0 +1,5 @@
#include "flex.h"
namespace flex {
// TODO
}

Wyświetl plik

@ -0,0 +1,11 @@
#pragma once
namespace flex {
class Decoder {
public:
// TODO
private:
// TODO
};
}

Wyświetl plik

@ -0,0 +1,172 @@
#include <imgui.h>
#include <config.h>
#include <core.h>
#include <gui/style.h>
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#include <module.h>
#include <gui/widgets/folder_select.h>
#include <utils/optionlist.h>
#include "decoder.h"
#include "pocsag/decoder.h"
#include "flex/decoder.h"
#define CONCAT(a, b) ((std::string(a) + b).c_str())
SDRPP_MOD_INFO{
/* Name: */ "pager_decoder",
/* Description: */ "POCSAG and Flex Pager Decoder"
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ -1
};
ConfigManager config;
enum Protocol {
PROTOCOL_INVALID = -1,
PROTOCOL_POCSAG,
PROTOCOL_FLEX
};
class PagerDecoderModule : public ModuleManager::Instance {
public:
PagerDecoderModule(std::string name) {
this->name = name;
// Define protocols
protocols.define("POCSAG", PROTOCOL_POCSAG);
protocols.define("FLEX", PROTOCOL_FLEX);
// Initialize VFO with default values
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 12500, 24000, 12500, 12500, true);
vfo->setSnapInterval(1);
// Select the protocol
selectProtocol(PROTOCOL_POCSAG);
gui::menu.registerEntry(name, menuHandler, this, this);
}
~PagerDecoderModule() {
gui::menu.removeEntry(name);
// Stop DSP
if (enabled) {
decoder->stop();
decoder.reset();
sigpath::vfoManager.deleteVFO(vfo);
}
sigpath::sinkManager.unregisterStream(name);
}
void postInit() {}
void enable() {
double bw = gui::waterfall.getBandwidth();
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, std::clamp<double>(0, -bw / 2.0, bw / 2.0), 12500, 24000, 12500, 12500, true);
vfo->setSnapInterval(1);
decoder->setVFO(vfo);
decoder->start();
enabled = true;
}
void disable() {
decoder->stop();
sigpath::vfoManager.deleteVFO(vfo);
enabled = false;
}
bool isEnabled() {
return enabled;
}
void selectProtocol(Protocol newProto) {
// Cannot change while disabled
if (!enabled) { return; }
// If the protocol hasn't changed, no need to do anything
if (newProto == proto) { return; }
// Delete current decoder
decoder.reset();
// Create a new decoder
switch (newProto) {
case PROTOCOL_POCSAG:
decoder = std::make_unique<POCSAGDecoder>(name, vfo);
break;
case PROTOCOL_FLEX:
decoder = std::make_unique<FLEXDecoder>(name, vfo);
break;
default:
flog::error("Tried to select unknown pager protocol");
return;
}
// Start the new decoder
decoder->start();
// Save selected protocol
proto = newProto;
}
private:
static void menuHandler(void* ctx) {
PagerDecoderModule* _this = (PagerDecoderModule*)ctx;
float menuWidth = ImGui::GetContentRegionAvail().x;
if (!_this->enabled) { style::beginDisabled(); }
ImGui::LeftLabel("Protocol");
ImGui::FillWidth();
if (ImGui::Combo(("##pager_decoder_proto_" + _this->name).c_str(), &_this->protoId, _this->protocols.txt)) {
_this->selectProtocol(_this->protocols.value(_this->protoId));
}
if (_this->decoder) { _this->decoder->showMenu(); }
ImGui::Button(("Record##pager_decoder_show_" + _this->name).c_str(), ImVec2(menuWidth, 0));
ImGui::Button(("Show Messages##pager_decoder_show_" + _this->name).c_str(), ImVec2(menuWidth, 0));
if (!_this->enabled) { style::endDisabled(); }
}
std::string name;
bool enabled = true;
Protocol proto = PROTOCOL_INVALID;
int protoId = 0;
OptionList<std::string, Protocol> protocols;
// DSP Chain
VFOManager::VFO* vfo;
std::unique_ptr<Decoder> decoder;
bool showLines = false;
};
MOD_EXPORT void _INIT_() {
// Create default recording directory
json def = json({});
config.setPath(core::args["root"].s() + "/pager_decoder_config.json");
config.load(def);
config.enableAutoSave();
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new PagerDecoderModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
delete (PagerDecoderModule*)instance;
}
MOD_EXPORT void _END_() {
config.disableAutoSave();
config.save();
}

Wyświetl plik

@ -0,0 +1,111 @@
#pragma once
#include "../decoder.h"
#include <signal_path/vfo_manager.h>
#include <utils/optionlist.h>
#include <gui/widgets/symbol_diagram.h>
#include <gui/style.h>
#include <dsp/sink/handler_sink.h>
#include "dsp.h"
#include "pocsag.h"
const char* msgTypes[] = {
"Numeric",
"Unknown (0b01)",
"Unknown (0b10)",
"Alphanumeric",
};
class POCSAGDecoder : public Decoder {
public:
POCSAGDecoder(const std::string& name, VFOManager::VFO* vfo) : diag(0.6, 2400) {
this->name = name;
this->vfo = vfo;
// Define baudrate options
baudrates.define(512, "512 Baud", 512);
baudrates.define(1200, "1200 Baud", 1200);
baudrates.define(2400, "2400 Baud", 2400);
// Init DSP
vfo->setBandwidthLimits(12500, 12500, true);
vfo->setSampleRate(24000, 12500);
dsp.init(vfo->output, 24000, 2400);
reshape.init(&dsp.soft, 2400.0, (2400 / 30.0) - 2400.0);
dataHandler.init(&dsp.out, _dataHandler, this);
diagHandler.init(&reshape.out, _diagHandler, this);
// Init decoder
decoder.onMessage.bind(&POCSAGDecoder::messageHandler, this);
}
~POCSAGDecoder() {
stop();
}
void showMenu() {
ImGui::LeftLabel("Baudrate");
ImGui::FillWidth();
if (ImGui::Combo(("##pager_decoder_pocsag_br_" + name).c_str(), &brId, baudrates.txt)) {
// TODO
}
ImGui::FillWidth();
diag.draw();
}
void setVFO(VFOManager::VFO* vfo) {
this->vfo = vfo;
vfo->setBandwidthLimits(12500, 12500, true);
vfo->setSampleRate(24000, 12500);
dsp.setInput(vfo->output);
}
void start() {
flog::debug("POCSAG start");
dsp.start();
reshape.start();
dataHandler.start();
diagHandler.start();
}
void stop() {
flog::debug("POCSAG stop");
dsp.stop();
reshape.stop();
dataHandler.stop();
diagHandler.stop();
}
private:
static void _dataHandler(uint8_t* data, int count, void* ctx) {
POCSAGDecoder* _this = (POCSAGDecoder*)ctx;
_this->decoder.process(data, count);
}
static void _diagHandler(float* data, int count, void* ctx) {
POCSAGDecoder* _this = (POCSAGDecoder*)ctx;
float* buf = _this->diag.acquireBuffer();
memcpy(buf, data, count * sizeof(float));
_this->diag.releaseBuffer();
}
void messageHandler(pocsag::Address addr, pocsag::MessageType type, const std::string& msg) {
flog::debug("[{}]: '{}'", (uint32_t)addr, msg);
}
std::string name;
VFOManager::VFO* vfo;
POCSAGDSP dsp;
dsp::buffer::Reshaper<float> reshape;
dsp::sink::Handler<uint8_t> dataHandler;
dsp::sink::Handler<float> diagHandler;
pocsag::Decoder decoder;
ImGui::SymbolDiagram diag;
int brId = 2;
OptionList<int, int> baudrates;
};

Wyświetl plik

@ -0,0 +1,71 @@
#pragma once
#include <dsp/stream.h>
#include <dsp/buffer/reshaper.h>
#include <dsp/multirate/rational_resampler.h>
#include <dsp/sink/handler_sink.h>
#include <dsp/demod/quadrature.h>
#include <dsp/clock_recovery/mm.h>
#include <dsp/taps/root_raised_cosine.h>
#include <dsp/correction/dc_blocker.h>
#include <dsp/loop/fast_agc.h>
#include <dsp/digital/binary_slicer.h>
#include <dsp/routing/doubler.h>
class POCSAGDSP : public dsp::Processor<dsp::complex_t, uint8_t> {
using base_type = dsp::Processor<dsp::complex_t, uint8_t>;
public:
POCSAGDSP() {}
POCSAGDSP(dsp::stream<dsp::complex_t>* in, double samplerate, double baudrate) { init(in, samplerate, baudrate); }
void init(dsp::stream<dsp::complex_t>* in, double samplerate, double baudrate) {
// Save settings
// TODO
// Configure blocks
demod.init(NULL, -4500.0, samplerate);
dcBlock.init(NULL, 0.001);
float taps[] = { 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f };
shape = dsp::taps::fromArray<float>(10, taps);
fir.init(NULL, shape);
recov.init(NULL, samplerate/baudrate, 1e5, 0.1, 0.05);
// Free useless buffers
dcBlock.out.free();
fir.out.free();
recov.out.free();
// Init base
base_type::init(in);
}
int process(int count, dsp::complex_t* in, float* softOut, uint8_t* out) {
count = demod.process(count, in, demod.out.readBuf);
count = dcBlock.process(count, demod.out.readBuf, demod.out.readBuf);
count = fir.process(count, demod.out.readBuf, demod.out.readBuf);
count = recov.process(count, demod.out.readBuf, softOut);
dsp::digital::BinarySlicer::process(count, softOut, out);
return count;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
count = process(count, base_type::_in->readBuf, soft.writeBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
if (!soft.swap(count)) { return -1; }
return count;
}
dsp::stream<float> soft;
private:
dsp::demod::Quadrature demod;
dsp::correction::DCBlocker<float> dcBlock;
dsp::tap<float> shape;
dsp::filter::FIR<float, float> fir;
dsp::clock_recovery::MM<float> recov;
};

Wyświetl plik

@ -0,0 +1,140 @@
#include "pocsag.h"
#include <string.h>
#include <utils/flog.h>
#define POCSAG_FRAME_SYNC_CODEWORD ((uint32_t)(0b01111100110100100001010111011000))
#define POCSAG_IDLE_CODEWORD_DATA ((uint32_t)(0b011110101100100111000))
#define POCSAG_BATCH_BIT_COUNT (POCSAG_BATCH_CODEWORD_COUNT*32)
#define POCSAG_GEN_POLY ((uint32_t)(0b11101101001))
namespace pocsag {
const char NUMERIC_CHARSET[] = {
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'*',
'U',
' ',
'-',
']',
'['
};
void Decoder::process(uint8_t* symbols, int count) {
for (int i = 0; i < count; i++) {
// Get symbol
uint32_t s = symbols[i];
// If not sync, try to acquire sync (TODO: sync confidence)
if (!synced) {
// Append new symbol to sync shift register
syncSR = (syncSR << 1) | s;
// Test for sync
synced = (distance(syncSR, POCSAG_FRAME_SYNC_CODEWORD) <= POCSAG_SYNC_DIST);
// Go to next symbol
continue;
}
// TODO: Flush message on desync
// Append bit to batch
batch[batchOffset >> 5] |= (s << (31 - (batchOffset & 0b11111)));
batchOffset++;
// On end of batch, decode and reset
if (batchOffset >= POCSAG_BATCH_BIT_COUNT) {
decodeBatch();
batchOffset = 0;
synced = false;
memset(batch, 0, sizeof(batch));
}
}
}
int Decoder::distance(uint32_t a, uint32_t b) {
uint32_t diff = a ^ b;
int dist = 0;
for (int i = 0; i < 32; i++) {
dist += (diff >> i ) & 1;
}
return dist;
}
bool Decoder::correctCodeword(Codeword in, Codeword& out) {
return true; // TODO
}
void Decoder::flushMessage() {
if (!msg.empty()) {
onMessage(addr, msgType, msg);
msg.clear();
}
}
void Decoder::decodeBatch() {
for (int i = 0; i < POCSAG_BATCH_CODEWORD_COUNT; i++) {
// Get codeword
Codeword cw = batch[i];
// Correct errors. If corrupted, skip
if (!correctCodeword(cw, cw)) { continue; }
// TODO: End message if two consecutive are corrupt
// Get codeword type
CodewordType type = (CodewordType)((cw >> 31) & 1);
if (type == CODEWORD_TYPE_ADDRESS && (cw >> 11) == POCSAG_IDLE_CODEWORD_DATA) {
type = CODEWORD_TYPE_IDLE;
}
// Decode codeword
if (type == CODEWORD_TYPE_IDLE) {
// If a non-empty message is available, send it out and clear
flushMessage();
flog::debug("[{}:{}]: IDLE", (i >> 1), i&1);
}
else if (type == CODEWORD_TYPE_ADDRESS) {
// If a non-empty message is available, send it out and clear
flushMessage();
// Decode message type
msgType = (MessageType)((cw >> 11) & 0b11);
// Decode address and append lower 8 bits from position
addr = ((cw >> 13) & 0b111111111111111111) << 3;
addr |= (i >> 1);
}
else if (type == CODEWORD_TYPE_MESSAGE) {
// Extract the 20 data bits
uint32_t data = (cw >> 11) & 0b11111111111111111111;
// Decode data depending on message type
if (msgType == MESSAGE_TYPE_NUMERIC) {
// Numeric messages pack 5 characters per message codeword
msg += NUMERIC_CHARSET[(data >> 16) & 0b1111];
msg += NUMERIC_CHARSET[(data >> 12) & 0b1111];
msg += NUMERIC_CHARSET[(data >> 8) & 0b1111];
msg += NUMERIC_CHARSET[(data >> 4) & 0b1111];
msg += NUMERIC_CHARSET[data & 0b1111];
}
else if (msgType == MESSAGE_TYPE_ALPHANUMERIC) {
}
// Save last data
lastMsgData = data;
}
}
}
}

Wyświetl plik

@ -0,0 +1,48 @@
#pragma once
#include <string>
#include <stdint.h>
#include <utils/new_event.h>
#define POCSAG_SYNC_DIST 4
#define POCSAG_BATCH_CODEWORD_COUNT 16
namespace pocsag {
enum CodewordType {
CODEWORD_TYPE_IDLE = -1,
CODEWORD_TYPE_ADDRESS = 0,
CODEWORD_TYPE_MESSAGE = 1
};
enum MessageType {
MESSAGE_TYPE_NUMERIC = 0b00,
MESSAGE_TYPE_ALPHANUMERIC = 0b11
};
using Codeword = uint32_t;
using Address = uint32_t;
class Decoder {
public:
void process(uint8_t* symbols, int count);
NewEvent<Address, MessageType, const std::string&> onMessage;
private:
static int distance(uint32_t a, uint32_t b);
bool correctCodeword(Codeword in, Codeword& out);
void flushMessage();
void decodeBatch();
uint32_t syncSR = 0;
bool synced = false;
int batchOffset = 0;
Codeword batch[POCSAG_BATCH_CODEWORD_COUNT];
Address addr;
MessageType msgType;
std::string msg;
uint32_t lastMsgData;
};
}

Wyświetl plik

@ -1,22 +1,22 @@
#pragma once
#include "../demod.h"
#include <dsp/demod/broadcast_fm.h>
#include <dsp/clock_recovery/mm.h>
#include <dsp/clock_recovery/fd.h>
#include <dsp/taps/root_raised_cosine.h>
#include <dsp/digital/binary_slicer.h>
#include <dsp/digital/manchester_decoder.h>
#include <dsp/digital/differential_decoder.h>
#include "../rds_demod.h"
#include <gui/widgets/symbol_diagram.h>
#include <fstream>
#include <rds.h>
namespace demod {
enum RDSRegion {
RDS_REGION_EUROPE,
RDS_REGION_NORTH_AMERICA
};
class WFM : public Demodulator {
public:
WFM() {}
WFM() : diag(0.5, 4096) {}
WFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
WFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) : diag(0.5, 4096) {
init(name, config, input, bandwidth);
}
@ -29,10 +29,18 @@ namespace demod {
this->name = name;
_config = config;
// Define RDS regions
rdsRegions.define("eu", "Europe", RDS_REGION_EUROPE);
rdsRegions.define("na", "North America", RDS_REGION_NORTH_AMERICA);
// Register FFT draw handler
fftRedrawHandler.handler = fftRedraw;
fftRedrawHandler.ctx = this;
gui::waterfall.onFFTRedraw.bindHandler(&fftRedrawHandler);
// Default
std::string rdsRegionStr = "eu";
// Load config
_config->acquire();
bool modified = false;
@ -45,33 +53,50 @@ namespace demod {
if (config->conf[name][getName()].contains("rds")) {
_rds = config->conf[name][getName()]["rds"];
}
if (config->conf[name][getName()].contains("rdsInfo")) {
_rdsInfo = config->conf[name][getName()]["rdsInfo"];
}
if (config->conf[name][getName()].contains("rdsRegion")) {
rdsRegionStr = config->conf[name][getName()]["rdsRegion"];
}
_config->release(modified);
// Define structure
// Load RDS region
if (rdsRegions.keyExists(rdsRegionStr)) {
rdsRegionId = rdsRegions.keyId(rdsRegionStr);
rdsRegion = rdsRegions.value(rdsRegionId);
}
else {
rdsRegion = RDS_REGION_EUROPE;
rdsRegionId = rdsRegions.valueId(rdsRegion);
}
// Init DSP
demod.init(input, bandwidth / 2.0f, getIFSampleRate(), _stereo, _lowPass, _rds);
recov.init(&demod.rdsOut, 5000.0 / 2375, omegaGain, muGain, 0.01);
slice.init(&recov.out);
manch.init(&slice.out);
diff.init(&manch.out, 2);
hs.init(&diff.out, rdsHandler, this);
rdsDemod.init(&demod.rdsOut, _rdsInfo);
hs.init(&rdsDemod.out, rdsHandler, this);
reshape.init(&rdsDemod.soft, 4096, (1187 / 30) - 4096);
diagHandler.init(&reshape.out, _diagHandler, this);
// Init RDS display
diag.lines.push_back(-0.8);
diag.lines.push_back(0.8);
}
void start() {
demod.start();
recov.start();
slice.start();
manch.start();
diff.start();
rdsDemod.start();
hs.start();
reshape.start();
diagHandler.start();
}
void stop() {
demod.stop();
recov.stop();
slice.stop();
manch.stop();
diff.stop();
rdsDemod.stop();
hs.stop();
reshape.stop();
diagHandler.stop();
}
void showMenu() {
@ -94,14 +119,129 @@ namespace demod {
_config->release(true);
}
// if (_rds) {
// if (rdsDecode.countryCodeValid()) { ImGui::Text("Country code: %d", rdsDecode.getCountryCode()); }
// if (rdsDecode.programCoverageValid()) { ImGui::Text("Program coverage: %d", rdsDecode.getProgramCoverage()); }
// if (rdsDecode.programRefNumberValid()) { ImGui::Text("Reference number: %d", rdsDecode.getProgramRefNumber()); }
// if (rdsDecode.programTypeValid()) { ImGui::Text("Program type: %d", rdsDecode.getProgramType()); }
// if (rdsDecode.PSNameValid()) { ImGui::Text("Program name: [%s]", rdsDecode.getPSName().c_str()); }
// if (rdsDecode.radioTextValid()) { ImGui::Text("Radiotext: [%s]", rdsDecode.getRadioText().c_str()); }
// }
// TODO: This might break when the entire radio module is disabled
if (!_rds) { ImGui::BeginDisabled(); }
if (ImGui::Checkbox(("Advanced RDS Info##_radio_wfm_rds_info_" + name).c_str(), &_rdsInfo)) {
setAdvancedRds(_rdsInfo);
_config->acquire();
_config->conf[name][getName()]["rdsInfo"] = _rdsInfo;
_config->release(true);
}
ImGui::SameLine();
ImGui::FillWidth();
if (ImGui::Combo(("##_radio_wfm_rds_region_" + name).c_str(), &rdsRegionId, rdsRegions.txt)) {
rdsRegion = rdsRegions.value(rdsRegionId);
_config->acquire();
_config->conf[name][getName()]["rdsRegion"] = rdsRegions.key(rdsRegionId);
_config->release(true);
}
if (!_rds) { ImGui::EndDisabled(); }
float menuWidth = ImGui::GetContentRegionAvail().x;
if (_rds && _rdsInfo) {
ImGui::BeginTable(("##radio_wfm_rds_info_tbl_" + name).c_str(), 2, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders);
if (rdsDecode.piCodeValid()) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("PI Code");
ImGui::TableSetColumnIndex(1);
if (rdsRegion == RDS_REGION_NORTH_AMERICA) {
ImGui::Text("0x%04X (%s)", rdsDecode.getPICode(), rdsDecode.getCallsign().c_str());
}
else {
ImGui::Text("0x%04X", rdsDecode.getPICode());
}
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Country Code");
ImGui::TableSetColumnIndex(1);
ImGui::Text("%d", rdsDecode.getCountryCode());
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Program Coverage");
ImGui::TableSetColumnIndex(1);
ImGui::Text("%s (%d)", rds::AREA_COVERAGE_TO_STR[rdsDecode.getProgramCoverage()], rdsDecode.getProgramCoverage());
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Reference Number");
ImGui::TableSetColumnIndex(1);
ImGui::Text("%d", rdsDecode.getProgramRefNumber());
}
else {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("PI Code");
ImGui::TableSetColumnIndex(1);
if (rdsRegion == RDS_REGION_NORTH_AMERICA) {
ImGui::TextUnformatted("0x---- (----)");
}
else {
ImGui::TextUnformatted("0x----");
}
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Country Code");
ImGui::TableSetColumnIndex(1);
ImGui::TextUnformatted("--"); // TODO: String
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Program Coverage");
ImGui::TableSetColumnIndex(1);
ImGui::TextUnformatted("------- (--)");
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Reference Number");
ImGui::TableSetColumnIndex(1);
ImGui::TextUnformatted("--");
}
if (rdsDecode.programTypeValid()) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Program Type");
ImGui::TableSetColumnIndex(1);
if (rdsRegion == RDS_REGION_NORTH_AMERICA) {
ImGui::Text("%s (%d)", rds::PROGRAM_TYPE_US_TO_STR[rdsDecode.getProgramType()], rdsDecode.getProgramType());
}
else {
ImGui::Text("%s (%d)", rds::PROGRAM_TYPE_EU_TO_STR[rdsDecode.getProgramType()], rdsDecode.getProgramType());
}
}
else {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Program Type");
ImGui::TableSetColumnIndex(1);
ImGui::TextUnformatted("------- (--)"); // TODO: String
}
if (rdsDecode.musicValid()) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Music");
ImGui::TableSetColumnIndex(1);
ImGui::Text("%s", rdsDecode.getMusic() ? "Yes":"No");
}
else {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Music");
ImGui::TableSetColumnIndex(1);
ImGui::TextUnformatted("---");
}
ImGui::EndTable();
ImGui::SetNextItemWidth(menuWidth);
diag.draw();
}
}
void setBandwidth(double bandwidth) {
@ -137,12 +277,24 @@ namespace demod {
demod.setStereo(_stereo);
}
void setAdvancedRds(bool enabled) {
rdsDemod.setSoftEnabled(enabled);
_rdsInfo = enabled;
}
private:
static void rdsHandler(uint8_t* data, int count, void* ctx) {
WFM* _this = (WFM*)ctx;
_this->rdsDecode.process(data, count);
}
static void _diagHandler(float* data, int count, void* ctx) {
WFM* _this = (WFM*)ctx;
float* buf = _this->diag.acquireBuffer();
memcpy(buf, data, count * sizeof(float));
_this->diag.releaseBuffer();
}
static void fftRedraw(ImGui::WaterFall::FFTRedrawArgs args, void* ctx) {
WFM* _this = (WFM*)ctx;
if (!_this->_rds) { return; }
@ -184,23 +336,31 @@ namespace demod {
}
dsp::demod::BroadcastFM demod;
dsp::clock_recovery::FD recov;
dsp::digital::BinarySlicer slice;
dsp::digital::ManchesterDecoder manch;
dsp::digital::DifferentialDecoder diff;
RDSDemod rdsDemod;
dsp::sink::Handler<uint8_t> hs;
EventHandler<ImGui::WaterFall::FFTRedrawArgs> fftRedrawHandler;
rds::RDSDecoder rdsDecode;
dsp::buffer::Reshaper<float> reshape;
dsp::sink::Handler<float> diagHandler;
ImGui::SymbolDiagram diag;
rds::Decoder rdsDecode;
ConfigManager* _config = NULL;
bool _stereo = false;
bool _lowPass = true;
bool _rds = false;
bool _rdsInfo = false;
float muGain = 0.01;
float omegaGain = (0.01*0.01)/4.0;
int rdsRegionId = 0;
RDSRegion rdsRegion = RDS_REGION_EUROPE;
OptionList<std::string, RDSRegion> rdsRegions;
std::string name;
};
}

Wyświetl plik

@ -634,7 +634,7 @@ private:
dsp::chain<dsp::stereo_t> afChain;
dsp::filter::Deemphasis<dsp::stereo_t> deemp;
std::shared_ptr<AudioStream> stream;
std::shared_ptr<MasterStream> stream;
demod::Demodulator* selectedDemod = NULL;

Wyświetl plik

@ -3,6 +3,8 @@
#include <map>
#include <algorithm>
#include <utils/flog.h>
namespace rds {
std::map<uint16_t, BlockType> SYNDROMES = {
{ 0b1111011000, BLOCK_TYPE_A },
@ -20,6 +22,98 @@ namespace rds {
{ BLOCK_TYPE_D, 0b0110110100 }
};
std::map<uint16_t, const char*> THREE_LETTER_CALLS = {
{ 0x99A5, "KBW" },
{ 0x99A6, "KCY" },
{ 0x9990, "KDB" },
{ 0x99A7, "KDF" },
{ 0x9950, "KEX" },
{ 0x9951, "KFH" },
{ 0x9952, "KFI" },
{ 0x9953, "KGA" },
{ 0x9991, "KGB" },
{ 0x9954, "KGO" },
{ 0x9955, "KGU" },
{ 0x9956, "KGW" },
{ 0x9957, "KGY" },
{ 0x99AA, "KHQ" },
{ 0x9958, "KID" },
{ 0x9959, "KIT" },
{ 0x995A, "KJR" },
{ 0x995B, "KLO" },
{ 0x995C, "KLZ" },
{ 0x995D, "KMA" },
{ 0x995E, "KMJ" },
{ 0x995F, "KNX" },
{ 0x9960, "KOA" },
{ 0x99AB, "KOB" },
{ 0x9992, "KOY" },
{ 0x9993, "KPQ" },
{ 0x9964, "KQV" },
{ 0x9994, "KSD" },
{ 0x9965, "KSL" },
{ 0x9966, "KUJ" },
{ 0x9995, "KUT" },
{ 0x9967, "KVI" },
{ 0x9968, "KWG" },
{ 0x9996, "KXL" },
{ 0x9997, "KXO" },
{ 0x996B, "KYW" },
{ 0x9999, "WBT" },
{ 0x996D, "WBZ" },
{ 0x996E, "WDZ" },
{ 0x996F, "WEW" },
{ 0x999A, "WGH" },
{ 0x9971, "WGL" },
{ 0x9972, "WGN" },
{ 0x9973, "WGR" },
{ 0x999B, "WGY" },
{ 0x9975, "WHA" },
{ 0x9976, "WHB" },
{ 0x9977, "WHK" },
{ 0x9978, "WHO" },
{ 0x999C, "WHP" },
{ 0x999D, "WIL" },
{ 0x997A, "WIP" },
{ 0x99B3, "WIS" },
{ 0x997B, "WJR" },
{ 0x99B4, "WJW" },
{ 0x99B5, "WJZ" },
{ 0x997C, "WKY" },
{ 0x997D, "WLS" },
{ 0x997E, "WLW" },
{ 0x999E, "WMC" },
{ 0x999F, "WMT" },
{ 0x9981, "WOC" },
{ 0x99A0, "WOI" },
{ 0x9983, "WOL" },
{ 0x9984, "WOR" },
{ 0x99A1, "WOW" },
{ 0x99B9, "WRC" },
{ 0x99A2, "WRR" },
{ 0x99A3, "WSB" },
{ 0x99A4, "WSM" },
{ 0x9988, "WWJ" },
{ 0x9989, "WWL" }
};
std::map<uint16_t, const char*> NAT_LOC_LINKED_STATIONS = {
{ 0xB01, "NPR-1" },
{ 0xB02, "CBC - Radio One" },
{ 0xB03, "CBC - Radio Two" },
{ 0xB04, "Radio-Canada - Première Chaîne" },
{ 0xB05, "Radio-Canada - Espace Musique" },
{ 0xB06, "CBC" },
{ 0xB07, "CBC" },
{ 0xB08, "CBC" },
{ 0xB09, "CBC" },
{ 0xB0A, "NPR-2" },
{ 0xB0B, "NPR-3" },
{ 0xB0C, "NPR-4" },
{ 0xB0D, "NPR-5" },
{ 0xB0E, "NPR-6" }
};
// 9876543210
const uint16_t LFSR_POLY = 0b0110111001;
const uint16_t IN_POLY = 0b1100011011;
@ -28,7 +122,7 @@ namespace rds {
const int DATA_LEN = 16;
const int POLY_LEN = 10;
void RDSDecoder::process(uint8_t* symbols, int count) {
void Decoder::process(uint8_t* symbols, int count) {
for (int i = 0; i < count; i++) {
// Shift in the bit
shiftReg = ((shiftReg << 1) & 0x3FFFFFF) | (symbols[i] & 1);
@ -54,18 +148,26 @@ namespace rds {
type = (BlockType)((lastType + 1) % _BLOCK_TYPE_COUNT);
}
// Save block while correcting errors (NOT YET)
// Save block while correcting errors (NOT YET) <- idk why the "not yet is here", TODO: find why
blocks[type] = correctErrors(shiftReg, type, blockAvail[type]);
// Update continous group count
if (type == BLOCK_TYPE_A) { contGroup = 1; }
else if (type == BLOCK_TYPE_B && lastType == BLOCK_TYPE_A) { contGroup++; }
// If block type is A, decode it directly, otherwise, update continous count
if (type == BLOCK_TYPE_A) {
decodeBlockA();
}
else if (type == BLOCK_TYPE_B) { contGroup = 1; }
else if ((type == BLOCK_TYPE_C || type == BLOCK_TYPE_CP) && lastType == BLOCK_TYPE_B) { contGroup++; }
else if (type == BLOCK_TYPE_D && (lastType == BLOCK_TYPE_C || lastType == BLOCK_TYPE_CP)) { contGroup++; }
else { contGroup = 0; }
else {
// If block B is available, decode it alone.
if (contGroup == 1) {
decodeBlockB();
}
contGroup = 0;
}
// If we've got an entire group, process it
if (contGroup >= 4) {
if (contGroup >= 3) {
contGroup = 0;
decodeGroup();
}
@ -76,7 +178,7 @@ namespace rds {
}
}
uint16_t RDSDecoder::calcSyndrome(uint32_t block) {
uint16_t Decoder::calcSyndrome(uint32_t block) {
uint16_t syn = 0;
// Calculate the syndrome using a LFSR
@ -95,7 +197,7 @@ namespace rds {
return syn;
}
uint32_t RDSDecoder::correctErrors(uint32_t block, BlockType type, bool& recovered) {
uint32_t Decoder::correctErrors(uint32_t block, BlockType type, bool& recovered) {
// Subtract the offset from block
block ^= (uint32_t)OFFSETS[type];
uint32_t out = block;
@ -124,96 +226,264 @@ namespace rds {
return out;
}
void RDSDecoder::decodeGroup() {
std::lock_guard<std::mutex> lck(groupMtx);
auto now = std::chrono::high_resolution_clock::now();
anyGroupLastUpdate = now;
void Decoder::decodeBlockA() {
// Acquire lock
std::lock_guard<std::mutex> lck(blockAMtx);
// Make sure blocks A and B are available
if (!blockAvail[BLOCK_TYPE_A] || !blockAvail[BLOCK_TYPE_B]) { return; }
// If it didn't decode properly return
if (!blockAvail[BLOCK_TYPE_A]) { return; }
// Decode PI code
piCode = (blocks[BLOCK_TYPE_A] >> 10) & 0xFFFF;
countryCode = (blocks[BLOCK_TYPE_A] >> 22) & 0xF;
programCoverage = (AreaCoverage)((blocks[BLOCK_TYPE_A] >> 18) & 0xF);
programRefNumber = (blocks[BLOCK_TYPE_A] >> 10) & 0xFF;
callsign = decodeCallsign(piCode);
// Update timeout
blockALastUpdate = std::chrono::high_resolution_clock::now();;
}
void Decoder::decodeBlockB() {
// Acquire lock
std::lock_guard<std::mutex> lck(blockBMtx);
// If it didn't decode properly return (TODO: Make sure this is not needed)
if (!blockAvail[BLOCK_TYPE_B]) { return; }
// Decode group type and version
uint8_t groupType = (blocks[BLOCK_TYPE_B] >> 22) & 0xF;
GroupVersion groupVer = (GroupVersion)((blocks[BLOCK_TYPE_B] >> 21) & 1);
groupType = (blocks[BLOCK_TYPE_B] >> 22) & 0xF;
groupVer = (GroupVersion)((blocks[BLOCK_TYPE_B] >> 21) & 1);
// Decode traffic program and program type
trafficProgram = (blocks[BLOCK_TYPE_B] >> 20) & 1;
programType = (ProgramType)((blocks[BLOCK_TYPE_B] >> 15) & 0x1F);
if (groupType == 0) {
group0LastUpdate = now;
trafficAnnouncement = (blocks[BLOCK_TYPE_B] >> 14) & 1;
music = (blocks[BLOCK_TYPE_B] >> 13) & 1;
uint8_t diBit = (blocks[BLOCK_TYPE_B] >> 12) & 1;
uint8_t offset = ((blocks[BLOCK_TYPE_B] >> 10) & 0b11);
uint8_t diOffset = 3 - offset;
uint8_t psOffset = offset * 2;
if (groupVer == GROUP_VER_A && blockAvail[BLOCK_TYPE_C]) {
alternateFrequency = (blocks[BLOCK_TYPE_C] >> 10) & 0xFFFF;
// Update timeout
blockBLastUpdate = std::chrono::high_resolution_clock::now();
}
void Decoder::decodeGroup0() {
// Acquire lock
std::lock_guard<std::mutex> lck(group0Mtx);
// Decode Block B data
trafficAnnouncement = (blocks[BLOCK_TYPE_B] >> 14) & 1;
music = (blocks[BLOCK_TYPE_B] >> 13) & 1;
uint8_t diBit = (blocks[BLOCK_TYPE_B] >> 12) & 1;
uint8_t offset = ((blocks[BLOCK_TYPE_B] >> 10) & 0b11);
uint8_t diOffset = 3 - offset;
uint8_t psOffset = offset * 2;
// Decode Block C data
if (groupVer == GROUP_VER_A && blockAvail[BLOCK_TYPE_C]) {
alternateFrequency = (blocks[BLOCK_TYPE_C] >> 10) & 0xFFFF;
}
// Write DI bit to the decoder identification
decoderIdent &= ~(1 << diOffset);
decoderIdent |= (diBit << diOffset);
// Write chars at offset the PSName
if (blockAvail[BLOCK_TYPE_D]) {
programServiceName[psOffset] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
programServiceName[psOffset + 1] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
}
// Update timeout
group0LastUpdate = std::chrono::high_resolution_clock::now();
}
void Decoder::decodeGroup2() {
// Acquire lock
std::lock_guard<std::mutex> lck(group2Mtx);
// Get char offset and write chars in the Radiotext
bool nAB = (blocks[BLOCK_TYPE_B] >> 14) & 1;
uint8_t offset = (blocks[BLOCK_TYPE_B] >> 10) & 0xF;
// Clear text field if the A/B flag changed
if (nAB != rtAB) {
radioText = " ";
}
rtAB = nAB;
// Write char at offset in Radiotext
if (groupVer == GROUP_VER_A) {
uint8_t rtOffset = offset * 4;
if (blockAvail[BLOCK_TYPE_C]) {
radioText[rtOffset] = (blocks[BLOCK_TYPE_C] >> 18) & 0xFF;
radioText[rtOffset + 1] = (blocks[BLOCK_TYPE_C] >> 10) & 0xFF;
}
// Write DI bit to the decoder identification
decoderIdent &= ~(1 << diOffset);
decoderIdent |= (diBit << diOffset);
// Write chars at offset the PSName
if (blockAvail[BLOCK_TYPE_D]) {
programServiceName[psOffset] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
programServiceName[psOffset + 1] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
radioText[rtOffset + 2] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
radioText[rtOffset + 3] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
}
}
else if (groupType == 2) {
group2LastUpdate = now;
// Get char offset and write chars in the Radiotext
bool nAB = (blocks[BLOCK_TYPE_B] >> 14) & 1;
uint8_t offset = (blocks[BLOCK_TYPE_B] >> 10) & 0xF;
// Clear text field if the A/B flag changed
if (nAB != rtAB) {
radioText = " ";
else {
uint8_t rtOffset = offset * 2;
if (blockAvail[BLOCK_TYPE_D]) {
radioText[rtOffset] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
radioText[rtOffset + 1] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
}
rtAB = nAB;
}
// Write char at offset in Radiotext
if (groupVer == GROUP_VER_A) {
uint8_t rtOffset = offset * 4;
if (blockAvail[BLOCK_TYPE_C]) {
radioText[rtOffset] = (blocks[BLOCK_TYPE_C] >> 18) & 0xFF;
radioText[rtOffset + 1] = (blocks[BLOCK_TYPE_C] >> 10) & 0xFF;
}
if (blockAvail[BLOCK_TYPE_D]) {
radioText[rtOffset + 2] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
radioText[rtOffset + 3] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
}
// Update timeout
group2LastUpdate = std::chrono::high_resolution_clock::now();
}
void Decoder::decodeGroup10() {
// Acquire lock
std::lock_guard<std::mutex> lck(group10Mtx);
// Check if the text needs to be cleared
bool ab = (blocks[BLOCK_TYPE_B] >> 14) & 1;
if (ab != ptnAB) {
programTypeName = " ";
}
ptnAB = ab;
// Decode segment address
bool addr = (blocks[BLOCK_TYPE_B] >> 10) & 1;
// Save text depending on address
if (addr) {
if (blockAvail[BLOCK_TYPE_C]) {
programTypeName[4] = (blocks[BLOCK_TYPE_C] >> 18) & 0xFF;
programTypeName[5] = (blocks[BLOCK_TYPE_C] >> 10) & 0xFF;
}
if (blockAvail[BLOCK_TYPE_D]) {
programTypeName[6] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
programTypeName[7] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
}
}
else {
if (blockAvail[BLOCK_TYPE_C]) {
programTypeName[0] = (blocks[BLOCK_TYPE_C] >> 18) & 0xFF;
programTypeName[1] = (blocks[BLOCK_TYPE_C] >> 10) & 0xFF;
}
if (blockAvail[BLOCK_TYPE_D]) {
programTypeName[2] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
programTypeName[3] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
}
}
// Update timeout
group10LastUpdate = std::chrono::high_resolution_clock::now();
}
void Decoder::decodeGroup() {
// Make sure blocks B is available
if (!blockAvail[BLOCK_TYPE_B]) { return; }
// Decode block B
decodeBlockB();
// Decode depending on group type
switch (groupType) {
case 0:
decodeGroup0();
break;
case 2:
decodeGroup2();
break;
case 10:
decodeGroup10();
break;
default:
break;
}
}
std::string Decoder::base26ToCall(uint16_t pi) {
// Determin first better based on offset
bool w = (pi >= 21672);
std::string callsign(w ? "W" : "K");
// Base25 decode the rest
std::string restStr;
int rest = pi - (w ? 21672 : 4096);
while (rest) {
restStr += 'A' + (rest % 26);
rest /= 26;
}
// Pad with As
while (restStr.size() < 3) {
restStr += 'A';
}
// Reorder chars
for (int i = restStr.size() - 1; i >= 0; i--) {
callsign += restStr[i];
}
return callsign;
}
std::string Decoder::decodeCallsign(uint16_t pi) {
if ((pi >> 8) == 0xAF) {
// AFXY -> XY00
return base26ToCall((pi & 0xFF) << 8);
}
else if ((pi >> 12) == 0xA) {
// AXYZ -> X0YZ
return base26ToCall((((pi >> 8) & 0xF) << 12) | (pi & 0xFF));
}
else if (pi >= 0x9950 && pi <= 0x9EFF) {
// 3 letter callsigns
if (THREE_LETTER_CALLS.find(pi) != THREE_LETTER_CALLS.end()) {
return THREE_LETTER_CALLS[pi];
}
else {
uint8_t rtOffset = offset * 2;
if (blockAvail[BLOCK_TYPE_D]) {
radioText[rtOffset] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
radioText[rtOffset + 1] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
}
return "Not Assigned";
}
}
else if (pi >= 0x1000 && pi <= 0x994F) {
// Normal encoding
if ((pi & 0xFF) == 0 || ((pi >> 8) & 0xF) == 0) {
return "Not Assigned";
}
else {
return base26ToCall(pi);
}
}
else if (pi >= 0xB000 && pi <= 0xEFFF) {
uint16_t _pi = ((pi >> 12) << 8) | (pi & 0xFF);
if (NAT_LOC_LINKED_STATIONS.find(_pi) != NAT_LOC_LINKED_STATIONS.end()) {
return NAT_LOC_LINKED_STATIONS[_pi];
}
else {
return "Not Assigned";
}
}
else {
return "Not Assigned";
}
}
bool RDSDecoder::anyGroupValid() {
bool Decoder::blockAValid() {
auto now = std::chrono::high_resolution_clock::now();
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - anyGroupLastUpdate)).count() < 5000.0;
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - blockALastUpdate)).count() < RDS_BLOCK_A_TIMEOUT_MS;
}
bool RDSDecoder::group0Valid() {
bool Decoder::blockBValid() {
auto now = std::chrono::high_resolution_clock::now();
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - group0LastUpdate)).count() < 5000.0;
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - blockBLastUpdate)).count() < RDS_BLOCK_B_TIMEOUT_MS;
}
bool RDSDecoder::group2Valid() {
bool Decoder::group0Valid() {
auto now = std::chrono::high_resolution_clock::now();
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - group2LastUpdate)).count() < 5000.0;
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - group0LastUpdate)).count() < RDS_GROUP_0_TIMEOUT_MS;
}
bool Decoder::group2Valid() {
auto now = std::chrono::high_resolution_clock::now();
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - group2LastUpdate)).count() < RDS_GROUP_2_TIMEOUT_MS;
}
bool Decoder::group10Valid() {
auto now = std::chrono::high_resolution_clock::now();
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - group10LastUpdate)).count() < RDS_GROUP_10_TIMEOUT_MS;
}
}

Wyświetl plik

@ -4,6 +4,12 @@
#include <chrono>
#include <mutex>
#define RDS_BLOCK_A_TIMEOUT_MS 5000.0
#define RDS_BLOCK_B_TIMEOUT_MS 5000.0
#define RDS_GROUP_0_TIMEOUT_MS 5000.0
#define RDS_GROUP_2_TIMEOUT_MS 5000.0
#define RDS_GROUP_10_TIMEOUT_MS 5000.0
namespace rds {
enum BlockType {
BLOCK_TYPE_A,
@ -20,22 +26,42 @@ namespace rds {
};
enum AreaCoverage {
AREA_COVERAGE_LOCAL,
AREA_COVERAGE_INTERNATIONAL,
AREA_COVERAGE_NATIONAL,
AREA_COVERAGE_SUPRA_NATIONAL,
AREA_COVERAGE_REGIONAL1,
AREA_COVERAGE_REGIONAL2,
AREA_COVERAGE_REGIONAL3,
AREA_COVERAGE_REGIONAL4,
AREA_COVERAGE_REGIONAL5,
AREA_COVERAGE_REGIONAL6,
AREA_COVERAGE_REGIONAL7,
AREA_COVERAGE_REGIONAL8,
AREA_COVERAGE_REGIONAL9,
AREA_COVERAGE_REGIONAL10,
AREA_COVERAGE_REGIONAL11,
AREA_COVERAGE_REGIONAL12
AREA_COVERAGE_INVALID = -1,
AREA_COVERAGE_LOCAL = 0,
AREA_COVERAGE_INTERNATIONAL = 1,
AREA_COVERAGE_NATIONAL = 2,
AREA_COVERAGE_SUPRA_NATIONAL = 3,
AREA_COVERAGE_REGIONAL1 = 4,
AREA_COVERAGE_REGIONAL2 = 5,
AREA_COVERAGE_REGIONAL3 = 6,
AREA_COVERAGE_REGIONAL4 = 7,
AREA_COVERAGE_REGIONAL5 = 8,
AREA_COVERAGE_REGIONAL6 = 9,
AREA_COVERAGE_REGIONAL7 = 10,
AREA_COVERAGE_REGIONAL8 = 11,
AREA_COVERAGE_REGIONAL9 = 12,
AREA_COVERAGE_REGIONAL10 = 13,
AREA_COVERAGE_REGIONAL11 = 14,
AREA_COVERAGE_REGIONAL12 = 15
};
inline const char* AREA_COVERAGE_TO_STR[] = {
"Local",
"International",
"National",
"Supra-National",
"Regional 1",
"Regional 2",
"Regional 3",
"Regional 4",
"Regional 5",
"Regional 6",
"Regional 7",
"Regional 8",
"Regional 9",
"Regional 10",
"Regional 11",
"Regional 12",
};
enum ProgramType {
@ -108,6 +134,76 @@ namespace rds {
PROGRAM_TYPE_EU_ALARM = 31
};
inline const char* PROGRAM_TYPE_EU_TO_STR[] = {
"None",
"News",
"Current Affairs",
"Information",
"Sports",
"Education",
"Drama",
"Culture",
"Science",
"Varied",
"Pop Music",
"Rock Music",
"Easy Listening Music",
"Light Classical",
"Serious Classical",
"Other Music",
"Weather",
"Finance",
"Children Program",
"Social Affairs",
"Religion",
"Phone-in",
"Travel",
"Leisure",
"Jazz Music",
"Country Music",
"National Music",
"Oldies Music",
"Folk Music",
"Documentary",
"Alarm Test",
"Alarm",
};
inline const char* PROGRAM_TYPE_US_TO_STR[] = {
"None",
"News",
"Information",
"Sports",
"Talk",
"Rock",
"Classic Rock",
"Adult Hits",
"Soft Rock",
"Top 40",
"Country",
"Oldies",
"Soft",
"Nostalgia",
"Jazz",
"Classical",
"Rythm and Blues",
"Soft Rythm and Blues",
"Foreign Language",
"Religious Music",
"Religious Talk",
"Personality",
"Public",
"College",
"Unassigned",
"Unassigned",
"Unassigned",
"Unassigned",
"Unassigned",
"Weather",
"Emergency Test",
"Emergency",
};
enum DecoderIdentification {
DECODER_IDENT_STEREO = (1 << 0),
DECODER_IDENT_ARTIFICIAL_HEAD = (1 << 1),
@ -115,35 +211,49 @@ namespace rds {
DECODER_IDENT_VARIABLE_PTY = (1 << 0)
};
class RDSDecoder {
class Decoder {
public:
void process(uint8_t* symbols, int count);
bool countryCodeValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); }
uint8_t getCountryCode() { std::lock_guard<std::mutex> lck(groupMtx); return countryCode; }
bool programCoverageValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); }
uint8_t getProgramCoverage() { std::lock_guard<std::mutex> lck(groupMtx); return programCoverage; }
bool programRefNumberValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); }
uint8_t getProgramRefNumber() { std::lock_guard<std::mutex> lck(groupMtx); return programRefNumber; }
bool programTypeValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); }
ProgramType getProgramType() { std::lock_guard<std::mutex> lck(groupMtx); return programType; }
bool piCodeValid() { std::lock_guard<std::mutex> lck(blockAMtx); return blockAValid(); }
uint16_t getPICode() { std::lock_guard<std::mutex> lck(blockAMtx); return piCode; }
uint8_t getCountryCode() { std::lock_guard<std::mutex> lck(blockAMtx); return countryCode; }
uint8_t getProgramCoverage() { std::lock_guard<std::mutex> lck(blockAMtx); return programCoverage; }
uint8_t getProgramRefNumber() { std::lock_guard<std::mutex> lck(blockAMtx); return programRefNumber; }
std::string getCallsign() { std::lock_guard<std::mutex> lck(blockAMtx); return callsign; }
bool programTypeValid() { std::lock_guard<std::mutex> lck(blockBMtx); return blockBValid(); }
ProgramType getProgramType() { std::lock_guard<std::mutex> lck(blockBMtx); return programType; }
bool musicValid() { std::lock_guard<std::mutex> lck(groupMtx); return group0Valid(); }
bool getMusic() { std::lock_guard<std::mutex> lck(groupMtx); return music; }
bool PSNameValid() { std::lock_guard<std::mutex> lck(groupMtx); return group0Valid(); }
std::string getPSName() { std::lock_guard<std::mutex> lck(groupMtx); return programServiceName; }
bool musicValid() { std::lock_guard<std::mutex> lck(group0Mtx); return group0Valid(); }
bool getMusic() { std::lock_guard<std::mutex> lck(group0Mtx); return music; }
bool PSNameValid() { std::lock_guard<std::mutex> lck(group0Mtx); return group0Valid(); }
std::string getPSName() { std::lock_guard<std::mutex> lck(group0Mtx); return programServiceName; }
bool radioTextValid() { std::lock_guard<std::mutex> lck(groupMtx); return group2Valid(); }
std::string getRadioText() { std::lock_guard<std::mutex> lck(groupMtx); return radioText; }
bool radioTextValid() { std::lock_guard<std::mutex> lck(group2Mtx); return group2Valid(); }
std::string getRadioText() { std::lock_guard<std::mutex> lck(group2Mtx); return radioText; }
bool programTypeNameValid() { std::lock_guard<std::mutex> lck(group10Mtx); return group10Valid(); }
std::string getProgramTypeName() { std::lock_guard<std::mutex> lck(group10Mtx); return programTypeName; }
private:
static uint16_t calcSyndrome(uint32_t block);
static uint32_t correctErrors(uint32_t block, BlockType type, bool& recovered);
void decodeBlockA();
void decodeBlockB();
void decodeGroup0();
void decodeGroup2();
void decodeGroup10();
void decodeGroup();
bool anyGroupValid();
static std::string base26ToCall(uint16_t pi);
static std::string decodeCallsign(uint16_t pi);
bool blockAValid();
bool blockBValid();
bool group0Valid();
bool group2Valid();
bool group10Valid();
// State machine
uint32_t shiftReg = 0;
@ -154,17 +264,26 @@ namespace rds {
uint32_t blocks[_BLOCK_TYPE_COUNT];
bool blockAvail[_BLOCK_TYPE_COUNT];
// All groups
std::mutex groupMtx;
std::chrono::time_point<std::chrono::high_resolution_clock> anyGroupLastUpdate;
// Block A (All groups)
std::mutex blockAMtx;
std::chrono::time_point<std::chrono::high_resolution_clock> blockALastUpdate{}; // 1970-01-01
uint16_t piCode;
uint8_t countryCode;
AreaCoverage programCoverage;
uint8_t programRefNumber;
std::string callsign;
// Block B (All groups)
std::mutex blockBMtx;
std::chrono::time_point<std::chrono::high_resolution_clock> blockBLastUpdate{}; // 1970-01-01
uint8_t groupType;
GroupVersion groupVer;
bool trafficProgram;
ProgramType programType;
// Group type 0
std::chrono::time_point<std::chrono::high_resolution_clock> group0LastUpdate;
std::mutex group0Mtx;
std::chrono::time_point<std::chrono::high_resolution_clock> group0LastUpdate{}; // 1970-01-01
bool trafficAnnouncement;
bool music;
uint8_t decoderIdent;
@ -172,9 +291,16 @@ namespace rds {
std::string programServiceName = " ";
// Group type 2
std::chrono::time_point<std::chrono::high_resolution_clock> group2LastUpdate;
std::mutex group2Mtx;
std::chrono::time_point<std::chrono::high_resolution_clock> group2LastUpdate{}; // 1970-01-01
bool rtAB = false;
std::string radioText = " ";
// Group type 10
std::mutex group10Mtx;
std::chrono::time_point<std::chrono::high_resolution_clock> group10LastUpdate{}; // 1970-01-01
bool ptnAB = false;
std::string programTypeName = " ";
};
}

Wyświetl plik

@ -0,0 +1,102 @@
#pragma once
#include <dsp/processor.h>
#include <dsp/loop/fast_agc.h>
#include <dsp/loop/costas.h>
#include <dsp/taps/band_pass.h>
#include <dsp/filter/fir.h>
#include <dsp/convert/complex_to_real.h>
#include <dsp/clock_recovery/mm.h>
#include <dsp/digital/binary_slicer.h>
#include <dsp/digital/differential_decoder.h>
class RDSDemod : public dsp::Processor<dsp::complex_t, uint8_t> {
using base_type = dsp::Processor<dsp::complex_t, uint8_t>;
public:
RDSDemod() {}
RDSDemod(dsp::stream<dsp::complex_t>* in, bool enableSoft) { init(in, enableSoft); }
~RDSDemod() {}
void init(dsp::stream<dsp::complex_t>* in, bool enableSoft) {
// Save config
this->enableSoft = enableSoft;
// Initialize the DSP
agc.init(NULL, 1.0, 1e6, 0.1);
costas.init(NULL, 0.005f);
taps = dsp::taps::bandPass<dsp::complex_t>(0, 2375, 100, 5000);
fir.init(NULL, taps);
double baudfreq = dsp::math::hzToRads(2375.0/2.0, 5000);
costas2.init(NULL, 0.01, 0.0, baudfreq, baudfreq - (baudfreq*0.1), baudfreq + (baudfreq*0.1));
recov.init(NULL, 5000.0 / (2375.0 / 2.0), 1e-6, 0.01, 0.01);
diff.init(NULL, 2);
// Free useless buffers
agc.out.free();
fir.out.free();
costas2.out.free();
recov.out.free();
// Init the rest
base_type::init(in);
}
void setSoftEnabled(bool enable) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
enableSoft = enable;
base_type::tempStart();
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
agc.reset();
costas.reset();
fir.reset();
costas2.reset();
recov.reset();
diff.reset();
base_type::tempStart();
}
inline int process(int count, dsp::complex_t* in, float* softOut, uint8_t* hardOut) {
count = agc.process(count, in, costas.out.readBuf);
count = costas.process(count, costas.out.readBuf, costas.out.writeBuf);
count = fir.process(count, costas.out.writeBuf, costas.out.writeBuf);
count = costas2.process(count, costas.out.writeBuf, costas.out.readBuf);
count = dsp::convert::ComplexToReal::process(count, costas.out.readBuf, softOut);
count = recov.process(count, softOut, softOut);
count = dsp::digital::BinarySlicer::process(count, softOut, diff.out.readBuf);
count = diff.process(count, diff.out.readBuf, hardOut);
return count;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
count = process(count, base_type::_in->readBuf, soft.writeBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
if (enableSoft) {
if (!soft.swap(count)) { return -1; }
}
return count;
}
dsp::stream<float> soft;
private:
bool enableSoft = false;
dsp::loop::FastAGC<dsp::complex_t> agc;
dsp::loop::Costas<2> costas;
dsp::tap<dsp::complex_t> taps;
dsp::filter::FIR<dsp::complex_t, dsp::complex_t> fir;
dsp::loop::Costas<2> costas2;
dsp::clock_recovery::MM<float> recov;
dsp::digital::DifferentialDecoder diff;
};

Wyświetl plik

@ -9,10 +9,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev
libcodec2-dev autoconf libtool xxd
# Install SDRPlay libraries
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run
7z x ./SDRplay_RSP_API-Linux-3.07.1.run
7z x ./SDRplay_RSP_API-Linux-3.07.1
cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.12.1.run
7z x ./SDRplay_RSP_API-Linux-3.12.1.run
7z x ./SDRplay_RSP_API-Linux-3.12.1
cp x86_64/libsdrplay_api.so.3.12 /usr/lib/libsdrplay_api.so
cp inc/* /usr/include/
# Install libperseus

Wyświetl plik

@ -9,10 +9,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev
libcodec2-dev autoconf libtool xxd
# Install SDRPlay libraries
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run
7z x ./SDRplay_RSP_API-Linux-3.07.1.run
7z x ./SDRplay_RSP_API-Linux-3.07.1
cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.12.1.run
7z x ./SDRplay_RSP_API-Linux-3.12.1.run
7z x ./SDRplay_RSP_API-Linux-3.12.1
cp x86_64/libsdrplay_api.so.3.12 /usr/lib/libsdrplay_api.so
cp inc/* /usr/include/
# Install libperseus

Wyświetl plik

@ -9,10 +9,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk1-dev
libcodec2-dev autoconf libtool xxd
# Install SDRPlay libraries
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run
7z x ./SDRplay_RSP_API-Linux-3.07.1.run
7z x ./SDRplay_RSP_API-Linux-3.07.1
cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.12.1.run
7z x ./SDRplay_RSP_API-Linux-3.12.1.run
7z x ./SDRplay_RSP_API-Linux-3.12.1
cp x86_64/libsdrplay_api.so.3.12 /usr/lib/libsdrplay_api.so
cp inc/* /usr/include/
# Install libperseus

Wyświetl plik

@ -9,10 +9,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk-dev l
libcodec2-dev autoconf libtool xxd
# Install SDRPlay libraries
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run
7z x ./SDRplay_RSP_API-Linux-3.07.1.run
7z x ./SDRplay_RSP_API-Linux-3.07.1
cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.12.1.run
7z x ./SDRplay_RSP_API-Linux-3.12.1.run
7z x ./SDRplay_RSP_API-Linux-3.12.1
cp x86_64/libsdrplay_api.so.3.12 /usr/lib/libsdrplay_api.so
cp inc/* /usr/include/
# Install libperseus

Wyświetl plik

@ -15,10 +15,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk1-dev
libcodec2-dev libudev-dev autoconf libtool xxd
# Install SDRPlay libraries
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run
7z x ./SDRplay_RSP_API-Linux-3.07.1.run
7z x ./SDRplay_RSP_API-Linux-3.07.1
cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.12.1.run
7z x ./SDRplay_RSP_API-Linux-3.12.1.run
7z x ./SDRplay_RSP_API-Linux-3.12.1
cp x86_64/libsdrplay_api.so.3.12 /usr/lib/libsdrplay_api.so
cp inc/* /usr/include/
# Install a more recent libusb version

Wyświetl plik

@ -9,10 +9,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev
libcodec2-dev autoconf libtool xxd
# Install SDRPlay libraries
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run
7z x ./SDRplay_RSP_API-Linux-3.07.1.run
7z x ./SDRplay_RSP_API-Linux-3.07.1
cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.12.1.run
7z x ./SDRplay_RSP_API-Linux-3.12.1.run
7z x ./SDRplay_RSP_API-Linux-3.12.1
cp x86_64/libsdrplay_api.so.3.12 /usr/lib/libsdrplay_api.so
cp inc/* /usr/include/
# Install libperseus

Wyświetl plik

@ -9,10 +9,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev
libcodec2-dev autoconf libtool xxd
# Install SDRPlay libraries
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run
7z x ./SDRplay_RSP_API-Linux-3.07.1.run
7z x ./SDRplay_RSP_API-Linux-3.07.1
cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.12.1.run
7z x ./SDRplay_RSP_API-Linux-3.12.1.run
7z x ./SDRplay_RSP_API-Linux-3.12.1
cp x86_64/libsdrplay_api.so.3.12 /usr/lib/libsdrplay_api.so
cp inc/* /usr/include/
# Install libperseus

Wyświetl plik

@ -0,0 +1,4 @@
FROM ubuntu:mantic
ENV DEBIAN_FRONTEND=noninteractive
COPY do_build.sh /root
RUN chmod +x /root/do_build.sh

Wyświetl plik

@ -0,0 +1,35 @@
#!/bin/bash
set -e
cd /root
# Install dependencies and tools
apt update
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk-dev libzstd-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
libcodec2-dev autoconf libtool xxd
# Install SDRPlay libraries
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.12.1.run
7z x ./SDRplay_RSP_API-Linux-3.12.1.run
7z x ./SDRplay_RSP_API-Linux-3.12.1
cp x86_64/libsdrplay_api.so.3.12 /usr/lib/libsdrplay_api.so
cp inc/* /usr/include/
# Install libperseus
git clone https://github.com/Microtelecom/libperseus-sdr
cd libperseus-sdr
autoreconf -i
./configure
make
make install
ldconfig
cd ..
cd SDRPlusPlus
mkdir build
cd build
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
make VERBOSE=1 -j2
cd ..
sh make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk-dev, librtaudio-dev, libzstd-dev'

Wyświetl plik

@ -531,7 +531,7 @@ private:
ImGui::TableSetColumnIndex(0);
if (ImGui::Button(("Import##_freq_mgr_imp_" + _this->name).c_str(), ImVec2(ImGui::GetContentRegionAvail().x, 0)) && !_this->importOpen) {
_this->importOpen = true;
_this->importDialog = new pfd::open_file("Import bookmarks", "", { "JSON Files (*.json)", "*.json", "All Files", "*" }, true);
_this->importDialog = new pfd::open_file("Import bookmarks", "", { "JSON Files (*.json)", "*.json", "All Files", "*" }, pfd::opt::multiselect);
}
ImGui::TableSetColumnIndex(1);
@ -544,7 +544,7 @@ private:
}
config.release();
_this->exportOpen = true;
_this->exportDialog = new pfd::save_file("Export bookmarks", "", { "JSON Files (*.json)", "*.json", "All Files", "*" }, true);
_this->exportDialog = new pfd::save_file("Export bookmarks", "", { "JSON Files (*.json)", "*.json", "All Files", "*" });
}
if (selectedNames.size() == 0 && _this->selectedListName != "") { style::endDisabled(); }
ImGui::EndTable();
@ -787,7 +787,7 @@ private:
void exportBookmarks(std::string path) {
std::ofstream fs(path);
exportedBookmarks >> fs;
fs << exportedBookmarks;
fs.close();
}

Wyświetl plik

@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.13)
project(iq_exporter)
file(GLOB SRC "src/*.cpp")
include(${SDRPP_MODULE_CMAKE})

Wyświetl plik

@ -0,0 +1,589 @@
#include <utils/net.h>
#include <imgui.h>
#include <module.h>
#include <gui/gui.h>
#include <gui/style.h>
#include <utils/optionlist.h>
#include <algorithm>
#include <dsp/sink/handler_sink.h>
#include <volk/volk.h>
#include <signal_path/signal_path.h>
#include <dsp/buffer/reshaper.h>
#include <gui/dialogs/dialog_box.h>
#include <core.h>
SDRPP_MOD_INFO{
/* Name: */ "iq_exporter",
/* Description: */ "Export raw IQ through TCP or UDP",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ -1
};
ConfigManager config;
enum Mode {
MODE_NONE = -1,
MODE_BASEBAND,
MODE_VFO
};
enum Protocol {
PROTOCOL_TCP_SERVER,
PROTOCOL_TCP_CLIENT,
PROTOCOL_UDP
};
enum SampleType {
SAMPLE_TYPE_INT8,
SAMPLE_TYPE_INT16,
SAMPLE_TYPE_INT32,
SAMPLE_TYPE_FLOAT32
};
class IQExporterModule : public ModuleManager::Instance {
public:
IQExporterModule(std::string name) {
this->name = name;
// Define operating modes
modes.define("Baseband", MODE_BASEBAND);
modes.define("VFO", MODE_VFO);
// Define VFO samplerates
for (int i = 3000; i <= 192000; i <<= 1) {
samplerates.define(i, getSrScaled(i), i);
}
for (int i = 250000; i < 1000000; i += 250000) {
samplerates.define(i, getSrScaled(i), i);
}
for (int i = 1000000; i < 10000000; i += 500000) {
samplerates.define(i, getSrScaled(i), i);
}
for (int i = 10000000; i <= 100000000; i += 5000000) {
samplerates.define(i, getSrScaled(i), i);
}
// Define protocols
protocols.define("TCP (Server)", PROTOCOL_TCP_SERVER);
protocols.define("TCP (Client)", PROTOCOL_TCP_CLIENT);
protocols.define("UDP", PROTOCOL_UDP);
// Define sample types
sampleTypes.define("Int8", SAMPLE_TYPE_INT8);
sampleTypes.define("Int16", SAMPLE_TYPE_INT16);
sampleTypes.define("Int32", SAMPLE_TYPE_INT32);
sampleTypes.define("Float32", SAMPLE_TYPE_FLOAT32);
// Define packet sizes
for (int i = 8; i <= 32768; i <<= 1) {
char buf[16];
sprintf(buf, "%d Bytes", i);
packetSizes.define(i, buf, i);
}
// Load config
bool autoStart = false;
Mode nMode = MODE_BASEBAND;
config.acquire();
if (config.conf[name].contains("mode")) {
std::string modeStr = config.conf[name]["mode"];
if (modes.keyExists(modeStr)) { nMode = modes.value(modes.keyId(modeStr)); }
}
if (config.conf[name].contains("samplerate")) {
int sr = config.conf[name]["samplerate"];
if (samplerates.keyExists(sr)) { samplerate = samplerates.value(samplerates.keyId(sr)); }
}
if (config.conf[name].contains("protocol")) {
std::string protoStr = config.conf[name]["protocol"];
if (protocols.keyExists(protoStr)) { proto = protocols.value(protocols.keyId(protoStr)); }
}
if (config.conf[name].contains("sampleType")) {
std::string sampTypeStr = config.conf[name]["sampleType"];
if (sampleTypes.keyExists(sampTypeStr)) { sampType = sampleTypes.value(sampleTypes.keyId(sampTypeStr)); }
}
if (config.conf[name].contains("packetSize")) {
int size = config.conf[name]["packetSize"];
if (packetSizes.keyExists(size)) { packetSize = packetSizes.value(packetSizes.keyId(size)); }
}
if (config.conf[name].contains("host")) {
std::string hostStr = config.conf[name]["host"];
strcpy(hostname, hostStr.c_str());
}
if (config.conf[name].contains("port")) {
port = config.conf[name]["port"];
port = std::clamp<int>(port, 1, 65535);
}
if (config.conf[name].contains("running")) {
autoStart = config.conf[name]["running"];
}
config.release();
// Set menu IDs
modeId = modes.valueId(nMode);
srId = samplerates.valueId(samplerate);
protoId = protocols.valueId(proto);
sampTypeId = sampleTypes.valueId(sampType);
packetSizeId = packetSizes.valueId(packetSize);
// Allocate buffer
buffer = dsp::buffer::alloc<uint8_t>(STREAM_BUFFER_SIZE * sizeof(dsp::complex_t));
// Init DSP
reshape.init(&iqStream, packetSize/sampleSize(), 0);
handler.init(&reshape.out, dataHandler, this);
// Set operating mode
setMode(nMode);
// Start if needed
if (autoStart) { start(); }
// Register menu entry
gui::menu.registerEntry(name, menuHandler, this, this);
}
~IQExporterModule() {
// Un-register menu entry
gui::menu.removeEntry(name);
// Stop networking
stop();
// Stop DSP
setMode(MODE_NONE);
// Free buffer
dsp::buffer::free(buffer);
}
void postInit() {}
void enable() {
// Rebind streams and start DSP
setMode(mode, true);
// Restart networking if it was running
if (wasRunning) { start(); }
// Mark as running
enabled = true;
}
void disable() {
// Save running state
wasRunning = running;
// Stop networking
stop();
// Stop the DSP and unbind streams
setMode(MODE_NONE);
// Mark as disabled
enabled = false;
}
bool isEnabled() {
return enabled;
}
void start() {
if (running) { return; }
// Acquire lock on the socket
std::lock_guard lck1(sockMtx);
// Start listening or open UDP socket
try {
if (proto == PROTOCOL_TCP_SERVER) {
// Create listener
listener = net::listen(hostname, port);
// Start listen worker
listenWorkerThread = std::thread(&IQExporterModule::listenWorker, this);
}
else if (proto == PROTOCOL_TCP_CLIENT) {
// Connect to TCP server
sock = net::connect(hostname, port);
}
else {
// Open UDP socket
sock = net::openudp(hostname, port, "0.0.0.0", 0, true);
}
}
catch (const std::exception& e) {
flog::error("[IQExporter] Could not start socket: {}", e.what());
errorStr = e.what();
showError = true;
return;
}
running = true;
}
void stop() {
if (!running) { return; }
// Acquire lock on the socket
std::lock_guard lck1(sockMtx);
// Stop listening or close UDP socket
if (proto == PROTOCOL_TCP_SERVER) {
// Stop listener
if (listener) {
listener->stop();
}
// Wait for worker to stop
if (listenWorkerThread.joinable()) { listenWorkerThread.join(); }
// Free listener
listener.reset();
// Close socket and free it
if (sock) {
sock->close();
sock.reset();
}
}
else {
// Close socket and free it
if (sock) {
sock->close();
sock.reset();
}
}
running = false;
}
private:
std::string getSrScaled(double sr) {
char buf[1024];
if (sr >= 1000000.0) {
sprintf(buf, "%.1lf MS/s", sr / 1000000.0);
}
else if (sr >= 1000.0) {
sprintf(buf, "%.1lf KS/s", sr / 1000.0);
}
else {
sprintf(buf, "%.1lf S/s", sr);
}
return std::string(buf);
}
static void menuHandler(void* ctx) {
IQExporterModule* _this = (IQExporterModule*)ctx;
float menuWidth = ImGui::GetContentRegionAvail().x;
// Error message box
ImGui::GenericDialog("##iq_exporter_err_", _this->showError, GENERIC_DIALOG_BUTTONS_OK, [=](){
ImGui::Text("Error: %s", _this->errorStr.c_str());
});
if (!_this->enabled) { ImGui::BeginDisabled(); }
if (_this->running) { ImGui::BeginDisabled(); }
// Mode selector
ImGui::LeftLabel("Mode");
ImGui::FillWidth();
if (ImGui::Combo(("##iq_exporter_mode_" + _this->name).c_str(), &_this->modeId, _this->modes.txt)) {
_this->setMode(_this->modes.value(_this->modeId));
config.acquire();
config.conf[_this->name]["mode"] = _this->modes.key(_this->modeId);
config.release(true);
}
// In VFO mode, show samplerate selector
if (_this->mode == MODE_VFO) {
ImGui::LeftLabel("Samplerate");
ImGui::FillWidth();
if (ImGui::Combo(("##iq_exporter_sr_" + _this->name).c_str(), &_this->srId, _this->samplerates.txt)) {
_this->samplerate = _this->samplerates.value(_this->srId);
if (_this->vfo) {
_this->vfo->setBandwidthLimits(_this->samplerate, _this->samplerate, true);
_this->vfo->setSampleRate(_this->samplerate, _this->samplerate);
}
config.acquire();
config.conf[_this->name]["samplerate"] = _this->samplerates.key(_this->srId);
config.release(true);
}
}
// Mode protocol selector
ImGui::LeftLabel("Protocol");
ImGui::FillWidth();
if (ImGui::Combo(("##iq_exporter_proto_" + _this->name).c_str(), &_this->protoId, _this->protocols.txt)) {
_this->proto = _this->protocols.value(_this->protoId);
config.acquire();
config.conf[_this->name]["protocol"] = _this->protocols.key(_this->protoId);
config.release(true);
}
// Sample type selector
ImGui::LeftLabel("Sample type");
ImGui::FillWidth();
if (ImGui::Combo(("##iq_exporter_samp_" + _this->name).c_str(), &_this->sampTypeId, _this->sampleTypes.txt)) {
_this->sampType = _this->sampleTypes.value(_this->sampTypeId);
_this->reshape.setKeep(_this->packetSize/_this->sampleSize());
config.acquire();
config.conf[_this->name]["sampleType"] = _this->sampleTypes.key(_this->sampTypeId);
config.release(true);
}
// Packet size selector
ImGui::LeftLabel("Packet size");
ImGui::FillWidth();
if (ImGui::Combo(("##iq_exporter_pkt_sz_" + _this->name).c_str(), &_this->packetSizeId, _this->packetSizes.txt)) {
_this->packetSize = _this->packetSizes.value(_this->packetSizeId);
_this->reshape.setKeep(_this->packetSize/_this->sampleSize());
config.acquire();
config.conf[_this->name]["packetSize"] = _this->packetSizes.key(_this->packetSizeId);
config.release(true);
}
// Hostname and port field
if (ImGui::InputText(("##iq_exporter_host_" + _this->name).c_str(), _this->hostname, sizeof(_this->hostname))) {
config.acquire();
config.conf[_this->name]["host"] = _this->hostname;
config.release(true);
}
ImGui::SameLine();
ImGui::FillWidth();
if (ImGui::InputInt(("##iq_exporter_port_" + _this->name).c_str(), &_this->port, 0, 0)) {
_this->port = std::clamp<int>(_this->port, 1, 65535);
config.acquire();
config.conf[_this->name]["port"] = _this->port;
config.release(true);
}
if (_this->running) { ImGui::EndDisabled(); }
// Start/Stop buttons
if (_this->running || (!_this->enabled && _this->wasRunning)) {
if (ImGui::Button(("Stop##iq_exporter_stop_" + _this->name).c_str(), ImVec2(menuWidth, 0))) {
_this->stop();
config.acquire();
config.conf[_this->name]["running"] = false;
config.release(true);
}
}
else {
if (ImGui::Button(("Start##iq_exporter_start_" + _this->name).c_str(), ImVec2(menuWidth, 0))) {
_this->start();
config.acquire();
config.conf[_this->name]["running"] = true;
config.release(true);
}
}
// Check if the socket is open by attempting a read
bool sockOpen;
{
uint8_t dummy;
sockOpen = !(!_this->sock || !_this->sock->isOpen() || (_this->proto != PROTOCOL_UDP && _this->sock->recv(&dummy, 1, false, net::NONBLOCKING) == 0));
}
// Status text
ImGui::TextUnformatted("Status:");
ImGui::SameLine();
if (sockOpen) {
ImGui::TextColored(ImVec4(0.0, 1.0, 0.0, 1.0), (_this->proto == PROTOCOL_TCP_SERVER || _this->proto == PROTOCOL_TCP_CLIENT) ? "Connected" : "Sending");
}
else if (_this->listener && _this->listener->listening()) {
ImGui::TextColored(ImVec4(1.0, 1.0, 0.0, 1.0), "Listening");
}
else if (!_this->enabled) {
ImGui::TextUnformatted("Disabled");
}
else {
// If we're idle and still supposed to be running, the server has closed the connection (TODO: kinda jank...)
if (_this->running) { _this->stop(); }
ImGui::TextUnformatted("Idle");
}
if (!_this->enabled) { ImGui::EndDisabled(); }
}
void setMode(Mode newMode, bool fromDisabled = false) {
// If there is no mode to change, do nothing
if (!fromDisabled && mode == newMode) { return; }
// Stop the DSP
reshape.stop();
handler.stop();
// Delete VFO or unbind IQ stream
if (vfo) {
sigpath::vfoManager.deleteVFO(vfo);
vfo = NULL;
}
if (mode == MODE_BASEBAND && !fromDisabled) {
sigpath::iqFrontEnd.unbindIQStream(&iqStream);
}
// If the mode was none, we're done
if (newMode == MODE_NONE) {
return;
}
// Create VFO or bind IQ stream
if (newMode == MODE_VFO) {
// Create VFO
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, samplerate, samplerate, samplerate, samplerate, true);
// Set its output as the input to the DSP
reshape.setInput(vfo->output);
}
else {
// Bind IQ stream
sigpath::iqFrontEnd.bindIQStream(&iqStream);
// Set its output as the input to the DSP
reshape.setInput(&iqStream);
}
// Start DSP
reshape.start();
handler.start();
// Update mode
mode = newMode;
modeId = modes.valueId(newMode);
}
void listenWorker() {
while (true) {
// Accept a client
auto newSock = listener->accept();
if (!newSock) { break; }
// Update socket
{
std::lock_guard lck(sockMtx);
sock = newSock;
}
}
}
int sampleSize() {
switch (sampType) {
case SAMPLE_TYPE_INT8:
return sizeof(int8_t)*2;
case SAMPLE_TYPE_INT16:
return sizeof(int16_t)*2;
case SAMPLE_TYPE_INT32:
return sizeof(int32_t)*2;
case SAMPLE_TYPE_FLOAT32:
return sizeof(dsp::complex_t);
default:
return -1;
}
}
static void dataHandler(dsp::complex_t* data, int count, void* ctx) {
IQExporterModule* _this = (IQExporterModule*)ctx;
// Try to cquire lock on socket
if (!_this->sockMtx.try_lock()) { return; }
// If not valid or open, give uo
if (!_this->sock || !_this->sock->isOpen()) {
// Unlock socket mutex
_this->sockMtx.unlock();
return;
}
// Convert the samples or send directory for float32
int size;
switch (_this->sampType) {
case SAMPLE_TYPE_INT8:
volk_32f_s32f_convert_8i((int8_t*)_this->buffer, (float*)data, 128.0f, count*2);
size = sizeof(int8_t)*2;
break;
case SAMPLE_TYPE_INT16:
volk_32f_s32f_convert_16i((int16_t*)_this->buffer, (float*)data, 32768.0f, count*2);
size = sizeof(int16_t)*2;
break;
case SAMPLE_TYPE_INT32:
volk_32f_s32f_convert_32i((int32_t*)_this->buffer, (float*)data, (float)2147483647.0f, count*2);
size = sizeof(int32_t)*2;
break;
case SAMPLE_TYPE_FLOAT32:
_this->sock->send((uint8_t*)data, count*sizeof(dsp::complex_t));
default:
// Unlock socket mutex
_this->sockMtx.unlock();
return;
}
// Send converted samples
_this->sock->send(_this->buffer, count*size);
// Unlock socket mutex
_this->sockMtx.unlock();
}
std::string name;
bool enabled = true;
Mode mode = MODE_NONE;
int modeId;
int samplerate = 1000000.0;
int srId;
Protocol proto = PROTOCOL_TCP_SERVER;
int protoId;
SampleType sampType = SAMPLE_TYPE_INT16;
int sampTypeId;
int packetSize = 1024;
int packetSizeId;
char hostname[1024] = "localhost";
int port = 1234;
bool running = false;
bool wasRunning = false;
bool showError = false;
std::string errorStr = "";
OptionList<std::string, Mode> modes;
OptionList<int, int> samplerates;
OptionList<std::string, Protocol> protocols;
OptionList<std::string, SampleType> sampleTypes;
OptionList<int, int> packetSizes;
VFOManager::VFO* vfo = NULL;
dsp::stream<dsp::complex_t> iqStream;
dsp::buffer::Reshaper<dsp::complex_t> reshape;
dsp::sink::Handler<dsp::complex_t> handler;
uint8_t* buffer = NULL;
std::thread listenWorkerThread;
std::mutex sockMtx;
std::shared_ptr<net::Socket> sock;
std::shared_ptr<net::Listener> listener;
};
MOD_EXPORT void _INIT_() {
json def = json({});
std::string root = (std::string)core::args["root"];
config.setPath(root + "/iq_exporter_config_config.json");
config.load(def);
config.enableAutoSave();
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new IQExporterModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
delete (IQExporterModule*)instance;
}
MOD_EXPORT void _END_() {
config.disableAutoSave();
config.save();
}

Wyświetl plik

@ -80,8 +80,8 @@ public:
try {
client = net::rigctl::connect(host, port);
}
catch (std::exception e) {
flog::error("Could not connect: {0}", e.what());
catch (const std::exception& e) {
flog::error("Could not connect: {}", e.what());
return;
}

Wyświetl plik

@ -200,8 +200,8 @@ private:
listener = net::listen(hostname, port);
listener->acceptAsync(clientHandler, this);
}
catch (std::exception e) {
flog::error("Could not start rigctl server: {0}", e.what());
catch (const std::exception& e) {
flog::error("Could not start rigctl server: {}", e.what());
}
}

Wyświetl plik

@ -15,7 +15,7 @@ SDR++ is a cross-platform and open source SDR software with the aim of being blo
* Multi VFO
* Wide hardware support (both through SoapySDR and dedicated modules)
* SIMD accelerated DSP
* Cross-platform (Windows, Linux, OSX and BSD)
* Cross-platform (Windows, Linux, MacOS and BSD)
* Full waterfall update when possible. Makes browsing signals easier and more pleasant
* Modular design (easily write your own plugins)
@ -44,7 +44,7 @@ Download the latest release from [the Releases page](https://github.com/Alexandr
Then, run:
```sh
sudo apt install libfftw3-dev libglfw3-dev libvolk2-dev libsoapysdr-dev libairspyhf-dev libiio-dev libad9361-dev librtaudio-dev libhackrf-dev
sudo apt install libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libsoapysdr-dev libairspyhf-dev libiio-dev libad9361-dev librtaudio-dev libhackrf-dev
sudo dpkg -i sdrpp_debian_amd64.deb
```
@ -334,7 +334,7 @@ Modules in beta are still included in releases for the most part but not enabled
| hackrf_source | Working | libhackrf | OPT_BUILD_HACKRF_SOURCE | ✅ | ✅ | ✅ |
| hermes_source | Beta | - | OPT_BUILD_HERMES_SOURCE | ✅ | ✅ | ✅ |
| limesdr_source | Working | liblimesuite | OPT_BUILD_LIMESDR_SOURCE | ⛔ | ✅ | ✅ |
| perseus_source | Beta | libperseus-sdr | OPT_BUILD_PERSEUS_SOURCE | ⛔ | ⛔ | ⛔ |
| perseus_source | Beta | libperseus-sdr | OPT_BUILD_PERSEUS_SOURCE | ⛔ | ✅ | ✅ |
| plutosdr_source | Working | libiio, libad9361 | OPT_BUILD_PLUTOSDR_SOURCE | ✅ | ✅ | ✅ |
| rfspace_source | Working | - | OPT_BUILD_RFSPACE_SOURCE | ✅ | ✅ | ✅ |
| rtl_sdr_source | Working | librtlsdr | OPT_BUILD_RTL_SDR_SOURCE | ✅ | ✅ | ✅ |
@ -367,6 +367,7 @@ Modules in beta are still included in releases for the most part but not enabled
| kgsstv_decoder | Unfinished | - | OPT_BUILD_KGSSTV_DECODER | ⛔ | ⛔ | ⛔ |
| m17_decoder | Beta | - | OPT_BUILD_M17_DECODER | ⛔ | ✅ | ⛔ |
| meteor_demodulator | Working | - | OPT_BUILD_METEOR_DEMODULATOR | ✅ | ✅ | ⛔ |
| pager_decoder | Unfinished | - | OPT_BUILD_PAGER_DECODER | ⛔ | ⛔ | ⛔ |
| radio | Working | - | OPT_BUILD_RADIO | ✅ | ✅ | ✅ |
| weather_sat_decoder | Unfinished | - | OPT_BUILD_WEATHER_SAT_DECODER | ⛔ | ⛔ | ⛔ |
@ -376,6 +377,7 @@ Modules in beta are still included in releases for the most part but not enabled
|---------------------|------------|--------------|-----------------------------|:----------------:|:----------------:|:---------------------------:|
| discord_integration | Working | - | OPT_BUILD_DISCORD_PRESENCE | ✅ | ✅ | ⛔ |
| frequency_manager | Working | - | OPT_BUILD_FREQUENCY_MANAGER | ✅ | ✅ | ✅ |
| iq_exporter | Unfinished | - | OPT_BUILD_IQ_EXPORTER | ⛔ | ⛔ | ⛔ |
| recorder | Working | - | OPT_BUILD_RECORDER | ✅ | ✅ | ✅ |
| rigctl_client | Unfinished | - | OPT_BUILD_RIGCTL_CLIENT | ✅ | ✅ | ⛔ |
| rigctl_server | Working | - | OPT_BUILD_RIGCTL_SERVER | ✅ | ✅ | ✅ |

Wyświetl plik

@ -103,6 +103,24 @@
},
{
"start": 283500,
"end": 472000,
"type": "aviation",
"name": "Aeronautical Radionavigation / Maritime"
},
{
"start": 472000,
"end": 475000,
"type": "amateur",
"name": "635m Ham Band CW"
},
{
"start": 475000,
"end": 479000,
"type": "amateur1",
"name": "635m Ham Band CW, Digimodes"
},
{
"start": 479000,
"end": 526500,
"type": "aviation",
"name": "Aeronautical Radionavigation / Maritime"
@ -284,7 +302,7 @@
{
"start": 3754500,
"end": 3757500,
"type": "amateur1",
"type": "utility",
"name": "The Pip (Night)"
},
{
@ -729,7 +747,7 @@
"start": 5330500,
"end": 5333400,
"type": "amateur",
"name": "|60m Ham Band Ch. 1 (Not NL)"
"name": "60m Ham Band Ch. 1 (Not NL)"
},
{
"start": 5333400,
@ -744,19 +762,31 @@
"name": "Ch. 2 (60m) (Not NL)"
},
{
"start": 5351500,
"end": 5366500,
"start": 5349400,
"end": 5351500,
"type": "amateur1",
"name": "60m Ham Band NL"
"name": "60m Ham Band (Not NL)"
},
{
"start": 5357000,
"end": 5359900,
"start": 5351500,
"end": 5354000,
"type": "amateur",
"name": "Ch. 3 (60m) (Not NL)"
"name": "60m Ham Band CW"
},
{
"start": 5359900,
"start": 5354000,
"end": 5366000,
"type": "amateur1",
"name": "60m HAM Alle modes (USB)"
},
{
"start": 5366000,
"end": 5366500,
"type": "amateur",
"name": "60m HAM Weak NB modes"
},
{
"start": 5366500,
"end": 5371500,
"type": "amateur1",
"name": "60m Ham Band (Not NL)"
@ -777,7 +807,7 @@
"start": 5403500,
"end": 5406400,
"type": "amateur",
"name": "Ch. 5 60m Ham Band (Not NL)|"
"name": "Ch. 5 60m Ham Band (Not NL)"
},
{
"start": 5406400,
@ -3047,17 +3077,11 @@
"type": "utility",
"name": "Radio Astronomy"
},
{
"start": 2300000000,
"end": 2310000000,
"type": "amateur",
"name": "|13cm Ham Band"
},
{
"start": 2320000000,
"end": 2332500000,
"type": "satellite",
"name": "Satellite Digital Audio Radio Service (SDARS)"
"type": "amateur",
"name": "|13cm Ham Band Satellite Digital Audio Radio Service (SDARS)"
},
{
"start": 2332500000,
@ -3067,21 +3091,9 @@
},
{
"start": 2345000000,
"end": 2360000000,
"type": "aviation",
"name": "Aviation Service and the Wireless Communications Service (WCS)"
},
{
"start": 2360000000,
"end": 2390000000,
"type": "aviation",
"name": "Aviation"
},
{
"start": 2390000000,
"end": 2393750000,
"type": "amateur",
"name": "13cm Ham Band (Upper)| Analog and Digital"
"name": "13cm Ham Band Analog and Digital"
},
{
"start": 2393750000,
@ -3093,25 +3105,25 @@
"start": 2394750000,
"end": 2400000000,
"type": "amateur",
"name": "Analog and Digital 13 cm HAM Band|"
"name": "Analog and Digital 13cm HAM Band"
},
{
"start": 2400000000,
"end": 2401000000,
"type": "utility",
"name": "Shared Satellite and 13cm ISM"
"name": "Shared Satellite, 13cm ISM and Ham"
},
{
"start": 2401000000,
"end": 2410000000,
"type": "utility",
"name": "WiFi shared with Satellite and 13cm ISM"
"name": "WiFi shared with Satellite, 13cm ISM and Ham"
},
{
"start": 2410000000,
"end": 2411900000,
"type": "utility",
"name": "WiFi shared with Broadband Modes and 13cm ISM"
"name": "WiFi shared with Broadband Modes, 13cm ISM and Ham"
},
{
"start": 2411900000,
@ -3122,8 +3134,8 @@
{
"start": 2412100000,
"end": 2450000000,
"type": "utility",
"name": "WiFi shared with Broadband Modes 13cm ISM |13cm Ham Band"
"type": "amateur",
"name": "WiFi shared with Broadband Modes 13cm ISM, 13cm Ham Band|"
},
{
"start": 2450000000,
@ -3172,6 +3184,366 @@
"end": 2500000000,
"type": "utility",
"name": "ISM Band (13cm)"
},
{
"name": "IMT",
"type": "cellular",
"start": 2500000000,
"end": 2544500000
},
{
"name": "Radioastronomy",
"type": "utility",
"start": 2690000000,
"end": 2700000000
},
{
"name": "Radar meteo",
"type": "military",
"start": 2700000000,
"end": 2900000000
},
{
"name": "Maritime Radar",
"type": "marine",
"start": 2900000000,
"end": 3400000000
},
{
"name": "Radio Ham 9 cm band",
"type": "amateur",
"start": 3400000000,
"end": 3475000000
},
{
"name": "Digital Networks",
"type": "utility",
"start": 3475000000,
"end": 4200000000
},
{
"name": "Altimeters",
"type": "aviation",
"start": 4200000000,
"end": 4400000000
},
{
"name": "Feeder link",
"type": "satellite",
"start": 5150000000,
"end": 5250000000
},
{
"name": "Digital networks and 802.11",
"type": "utility",
"start": 5250000000,
"end": 5650000000
},
{
"name": "Radio Ham 6 cm band",
"type": "amateur",
"start": 5650000000,
"end": 5850000000
},
{
"name": "Digital Networks and LPR",
"type": "utility",
"start": 5925000000,
"end": 7750000000
},
{
"name": "LPR",
"type": "utility",
"start": 7750000000,
"end": 7975000000
},
{
"name": "Remote sensing",
"type": "utility",
"start": 7975000000,
"end": 8215000000
},
{
"name": "TLPR and SRD",
"type": "utility",
"start": 8215000000,
"end": 8650000000
},
{
"name": "Radar Doppler",
"type": "aviation",
"start": 8650000000,
"end": 8850000000
},
{
"name": "Maritime Radar",
"type": "marine",
"start": 8850000000,
"end": 9000000000
},
{
"name": "Radar and transponder SART",
"type": "marine",
"start": 9000000000,
"end": 9500000000
},
{
"name": "TLPR and SRD",
"type": "utility",
"start": 9500000000,
"end": 10000000000
},
{
"name": "Radio Ham 3 cm band",
"type": "amateur",
"start": 10000000000,
"end": 10500000000
},
{
"name": "Point to point TV networks",
"type": "utility",
"start": 10500000000,
"end": 10680000000
},
{
"name": "Digital network (fixed)",
"type": "utility",
"start": 10680000000,
"end": 11700000000
},
{
"name": "TV satellite",
"type": "satellite",
"start": 11700000000,
"end": 12500000000
},
{
"name": "Digital network (fixed)",
"type": "utility",
"start": 12500000000,
"end": 13250000000
},
{
"name": "Satellite Uplink",
"type": "satellite",
"start": 14000000000,
"end": 14500000000
},
{
"name": "Digital network (fixed)",
"type": "utility",
"start": 14500000000,
"end": 14620000000
},
{
"name": "Digital network (fixed)",
"type": "utility",
"start": 15230000000,
"end": 15350000000
},
{
"name": "Digital network (poit to point)",
"type": "utility",
"start": 17100000000,
"end": 19300000000
},
{
"name": "Feeder link",
"type": "satellite",
"start": 19300000000,
"end": 19700000000
},
{
"name": "HEST, LEST, ESIM, ESOMP",
"type": "satellite",
"start": 19700000000,
"end": 20200000000
},
{
"name": "Digital network (fixed)",
"type": "utility",
"start": 22000000000,
"end": 22330000000
},
{
"name": "Digital network (fixed)",
"type": "utility",
"start": 22674750000,
"end": 2283350000
},
{
"name": "Digital network (fixed), SAP/SAB",
"type": "utility",
"start": 22926750000,
"end": 23150000000
},
{
"name": "Digital network (fixed)",
"type": "utility",
"start": 23150000000,
"end": 23338000000
},
{
"name": "ISM, SRD and LPR",
"type": "utility",
"start": 24000000000,
"end": 24450000000
},
{
"name": "Digital network (point to point, multipoint)",
"type": "utility",
"start": 24450000000,
"end": 25109000000
},
{
"name": "LPR, SRD and SRR",
"type": "utility",
"start": 25109000000,
"end": 2544500000
},
{
"name": "Network (point to point, multipoint)",
"type": "utility",
"start": 2544500000,
"end": 2611700000
},
{
"name": "LPR, SRD and SRR",
"type": "utility",
"start": 2611700000,
"end": 2650000000
},
{
"name": "Terrestrial electric utility",
"type": "utility",
"start": 26500000000,
"end": 27500000000
},
{
"name": "Network (point to point, multipoint)",
"type": "utility",
"start": 27500000000,
"end": 29100000000
},
{
"name": "Network (point to point, multipoint)",
"type": "utility",
"start": 29100000000,
"end": 29500000000
},
{
"name": "Network (point to point, multipoint)",
"type": "utility",
"start": 31000000000,
"end": 31300000000
},
{
"name": "Network (high density, fixed)",
"type": "utility",
"start": 31983000000,
"end": 32599000000
},
{
"name": "Network (high density, fixed)",
"type": "utility",
"start": 32795000000,
"end": 33400000000
},
{
"name": "Network (high density, fixed)",
"type": "utility",
"start": 37338000000,
"end": 38300000000
},
{
"name": "Network (high density, fixed)",
"type": "utility",
"start": 38590000000,
"end": 39500000000
},
{
"name": "FWS systems (fixed)",
"type": "utility",
"start": 40500000000,
"end": 43500000000
},
{
"name": "Radio Ham 6 mm band",
"type": "amateur",
"start": 47000000000,
"end": 47200000000
},
{
"name": "Network (high density, fixed)",
"type": "utility",
"start": 51400000000,
"end": 52600000000
},
{
"name": "Network (high density, fixed)",
"type": "utility",
"start": 55780000000,
"end": 61000000000
},
{
"name": "ISM",
"type": "utility",
"start": 61000000000,
"end": 64000000000
},
{
"name": "Network (high density, fixed)",
"type": "utility",
"start": 64000000000,
"end": 66000000000
},
{
"name": "Links (high density, fixed)",
"type": "utility",
"start": 71000000000,
"end": 74000000000
},
{
"name": "LPR, SRD, SRR, TLPR, vehichle radar",
"type": "utility",
"start": 74000000000,
"end": 76500000000
},
{
"name": "Radio Ham 4 mm band",
"type": "amateur",
"start": 76500000000,
"end": 81500000000
},
{
"name": "Links (high density, fixed)",
"type": "utility",
"start": 84000000000,
"end": 86000000000
},
{
"name": "ISM",
"type": "utility",
"start": 120200000000,
"end": 122250000000
},
{
"name": "Radio Ham 2.5 mm band",
"type": "amateur",
"start": 122250000000,
"end": 123000000000
},
{
"name": "Radio Ham 2 mm band",
"type": "amateur",
"start": 134000000000,
"end": 141000000000
},
{
"name": "Radio Ham 1 mm band",
"type": "amateur",
"start": 241000000000,
"end": 250000000000
}
]
}

Wyświetl plik

@ -16,7 +16,7 @@ SDRPP_MOD_INFO{
/* Name: */ "audio_sink",
/* Description: */ "Audio sink module for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Version: */ 0, 2, 0,
/* Max instances */ 1
};
@ -35,8 +35,16 @@ public:
monoPacker.init(&s2m.out, 512);
stereoPacker.init(stream, 512);
// Convert config to the new format and get selected device
bool modified = false;
#if RTAUDIO_VERSION_MAJOR >= 6
audio.setErrorCallback(&errorCallback);
#endif
#if RTAUDIO_VERSION_MAJOR >= 6
audio.setErrorCallback(&errorCallback);
#endif
// Load config (TODO)
bool created = false;
std::string device = "";
// config.acquire();
// if (config.conf.contains(streamName)) {
@ -53,18 +61,24 @@ public:
// config.release(modified);
// List devices
int count = audio.getDeviceCount();
RtAudio::DeviceInfo info;
#if RTAUDIO_VERSION_MAJOR >= 6
for (int i : audio.getDeviceIds()) {
#else
int count = audio.getDeviceCount();
for (int i = 0; i < count; i++) {
#endif
try {
info = audio.getDeviceInfo(i);
#if !defined(RTAUDIO_VERSION_MAJOR) || RTAUDIO_VERSION_MAJOR < 6
if (!info.probed) { continue; }
#endif
if (info.outputChannels == 0) { continue; }
if (info.isDefaultOutput) { defaultDevId = devList.size(); }
devList.define(i, info.name, info);
}
catch (std::exception e) {
flog::error("AudioSinkModule Error getting audio device info: {0}", e.what());
catch (const std::exception& e) {
flog::error("AudioSinkModule Error getting audio device ({}) info: {}", i, e.what());
}
}
@ -190,6 +204,22 @@ public:
}
}
#if RTAUDIO_VERSION_MAJOR >= 6
static void errorCallback(RtAudioErrorType type, const std::string& errorText) {
switch (type) {
case RtAudioErrorType::RTAUDIO_NO_ERROR:
return;
case RtAudioErrorType::RTAUDIO_WARNING:
case RtAudioErrorType::RTAUDIO_NO_DEVICES_FOUND:
case RtAudioErrorType::RTAUDIO_DEVICE_DISCONNECT:
flog::warn("AudioSinkModule Warning: {} ({})", errorText, (int)type);
break;
default:
throw std::runtime_error(errorText);
}
}
#endif
private:
bool doStart() {
RtAudio::StreamParameters parameters;
@ -206,8 +236,8 @@ private:
audio.startStream();
stereoPacker.start();
}
catch (RtAudioError& e) {
flog::error("Could not open audio device");
catch (const std::exception& e) {
flog::error("Could not open audio device {0}", e.what());
return false;
}

Wyświetl plik

@ -3,13 +3,14 @@
#include <module.h>
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#include <signal_path/sink.h>
#include <signal_path/stream.h>
#include <dsp/buffer/packer.h>
#include <dsp/convert/stereo_to_mono.h>
#include <dsp/sink/handler_sink.h>
#include <utils/flog.h>
#include <config.h>
#include <gui/style.h>
#include <utils/optionlist.h>
#include <core.h>
#define CONCAT(a, b) ((std::string(a) + b).c_str())
@ -18,84 +19,97 @@ SDRPP_MOD_INFO{
/* Name: */ "network_sink",
/* Description: */ "Network sink module for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Version: */ 0, 2, 0,
/* Max instances */ 1
};
ConfigManager config;
enum {
enum SinkMode {
SINK_MODE_TCP,
SINK_MODE_UDP
};
const char* sinkModesTxt = "TCP\0UDP\0";
class NetworkSink : SinkManager::Sink {
class NetworkSink : public Sink {
public:
NetworkSink(SinkManager::Stream* stream, std::string streamName) {
_stream = stream;
_streamName = streamName;
NetworkSink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) :
Sink(entry, stream, name, id, stringId)
{
// Define modes
modes.define("TCP", SINK_MODE_TCP);
modes.define("UDP", SINK_MODE_UDP);
// Load config
config.acquire();
if (!config.conf.contains(_streamName)) {
config.conf[_streamName]["hostname"] = "localhost";
config.conf[_streamName]["port"] = 7355;
config.conf[_streamName]["protocol"] = SINK_MODE_UDP; // UDP
config.conf[_streamName]["sampleRate"] = 48000.0;
config.conf[_streamName]["stereo"] = false;
config.conf[_streamName]["listening"] = false;
// Create a list of sample rates
std::vector<int> srList;
for (int sr = 12000; sr <= 200000; sr += 12000) {
srList.push_back(sr);
}
for (int sr = 11025; sr <= 192000; sr += 11025) {
srList.push_back(sr);
}
std::string host = config.conf[_streamName]["hostname"];
strcpy(hostname, host.c_str());
port = config.conf[_streamName]["port"];
modeId = config.conf[_streamName]["protocol"];
sampleRate = config.conf[_streamName]["sampleRate"];
stereo = config.conf[_streamName]["stereo"];
bool startNow = config.conf[_streamName]["listening"];
config.release(true);
// Sort sample rate list
std::sort(srList.begin(), srList.end(), [](double a, double b) { return (a < b); });
// Define samplerate options
for (int sr : srList) {
char buf[16];
sprintf(buf, "%d", sr);
samplerates.define(sr, buf, sr);
}
// Allocate buffer
netBuf = new int16_t[STREAM_BUFFER_SIZE];
packer.init(_stream->sinkOut, 512);
// Init DSP
packer.init(stream, 512);
s2m.init(&packer.out);
monoSink.init(&s2m.out, monoHandler, this);
stereoSink.init(&packer.out, stereoHandler, this);
// Create a list of sample rates
for (int sr = 12000; sr < 200000; sr += 12000) {
sampleRates.push_back(sr);
// Load config
config.acquire();
bool startNow = false;
if (config.conf[stringId].contains("hostname")) {
std::string host = config.conf[stringId]["hostname"];
strcpy(hostname, host.c_str());
}
for (int sr = 11025; sr < 192000; sr += 11025) {
sampleRates.push_back(sr);
if (config.conf[stringId].contains("port")) {
port = config.conf[stringId]["port"];
}
// Sort sample rate list
std::sort(sampleRates.begin(), sampleRates.end(), [](double a, double b) { return (a < b); });
// Generate text list for UI
char buffer[128];
int id = 0;
int _48kId;
bool found = false;
for (auto sr : sampleRates) {
sprintf(buffer, "%d", (int)sr);
sampleRatesTxt += buffer;
sampleRatesTxt += '\0';
if (sr == sampleRate) {
srId = id;
found = true;
if (config.conf[stringId].contains("mode")) {
std::string modeStr = config.conf[stringId]["mode"];
if (modes.keyExists(modeStr)) {
mode = modes.value(modes.keyId(modeStr));
}
else {
mode = SINK_MODE_TCP;
}
if (sr == 48000.0) { _48kId = id; }
id++;
}
if (!found) {
srId = _48kId;
sampleRate = 48000.0;
if (config.conf[stringId].contains("samplerate")) {
int nSr = config.conf[stringId]["samplerate"];
if (samplerates.keyExists(nSr)) {
sampleRate = samplerates.value(samplerates.keyId(nSr));
}
else {
sampleRate = 48000;
}
}
_stream->setSampleRate(sampleRate);
if (config.conf[stringId].contains("stereo")) {
stereo = config.conf[stringId]["stereo"];
}
if (config.conf[stringId].contains("running")) {
startNow = config.conf[stringId]["running"];
}
config.release();
// Set mode ID
modeId = modes.valueId(mode);
// Set samplerate ID
srId = samplerates.valueId(sampleRate);
// Start if needed
if (startNow) { startServer(); }
@ -122,30 +136,30 @@ public:
running = false;
}
void menuHandler() {
void showMenu() {
float menuWidth = ImGui::GetContentRegionAvail().x;
bool listening = (listener && listener->isListening()) || (conn && conn->isOpen());
if (listening) { style::beginDisabled(); }
if (ImGui::InputText(CONCAT("##_network_sink_host_", _streamName), hostname, 1023)) {
if (ImGui::InputText(CONCAT("##_network_sink_host_", stringId), hostname, 1023)) {
config.acquire();
config.conf[_streamName]["hostname"] = hostname;
config.conf[stringId]["hostname"] = hostname;
config.release(true);
}
ImGui::SameLine();
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::InputInt(CONCAT("##_network_sink_port_", _streamName), &port, 0, 0)) {
if (ImGui::InputInt(CONCAT("##_network_sink_port_", stringId), &port, 0, 0)) {
config.acquire();
config.conf[_streamName]["port"] = port;
config.conf[stringId]["port"] = port;
config.release(true);
}
ImGui::LeftLabel("Protocol");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::Combo(CONCAT("##_network_sink_mode_", _streamName), &modeId, sinkModesTxt)) {
if (ImGui::Combo(CONCAT("##_network_sink_mode_", stringId), &modeId, sinkModesTxt)) {
config.acquire();
config.conf[_streamName]["protocol"] = modeId;
config.conf[stringId]["mode"] = modeId;
config.release(true);
}
@ -153,33 +167,33 @@ public:
ImGui::LeftLabel("Samplerate");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::Combo(CONCAT("##_network_sink_sr_", _streamName), &srId, sampleRatesTxt.c_str())) {
sampleRate = sampleRates[srId];
_stream->setSampleRate(sampleRate);
if (ImGui::Combo(CONCAT("##_network_sink_sr_", stringId), &srId, samplerates.txt)) {
sampleRate = samplerates.value(srId);
entry->setSamplerate(sampleRate);
packer.setSampleCount(sampleRate / 60);
config.acquire();
config.conf[_streamName]["sampleRate"] = sampleRate;
config.conf[stringId]["samplerate"] = sampleRate;
config.release(true);
}
if (ImGui::Checkbox(CONCAT("Stereo##_network_sink_stereo_", _streamName), &stereo)) {
if (ImGui::Checkbox(CONCAT("Stereo##_network_sink_stereo_", stringId), &stereo)) {
stop();
start();
config.acquire();
config.conf[_streamName]["stereo"] = stereo;
config.conf[stringId]["stereo"] = stereo;
config.release(true);
}
if (listening && ImGui::Button(CONCAT("Stop##_network_sink_stop_", _streamName), ImVec2(menuWidth, 0))) {
if (listening && ImGui::Button(CONCAT("Stop##_network_sink_stop_", stringId), ImVec2(menuWidth, 0))) {
stopServer();
config.acquire();
config.conf[_streamName]["listening"] = false;
config.conf[stringId]["running"] = false;
config.release(true);
}
else if (!listening && ImGui::Button(CONCAT("Start##_network_sink_stop_", _streamName), ImVec2(menuWidth, 0))) {
else if (!listening && ImGui::Button(CONCAT("Start##_network_sink_stop_", stringId), ImVec2(menuWidth, 0))) {
startServer();
config.acquire();
config.conf[_streamName]["listening"] = true;
config.conf[stringId]["running"] = true;
config.release(true);
}
@ -271,47 +285,40 @@ private:
_this->listener->acceptAsync(clientHandler, _this);
}
SinkManager::Stream* _stream;
// DSP
dsp::buffer::Packer<dsp::stereo_t> packer;
dsp::convert::StereoToMono s2m;
dsp::sink::Handler<float> monoSink;
dsp::sink::Handler<dsp::stereo_t> stereoSink;
std::string _streamName;
int srId = 0;
bool running = false;
OptionList<std::string, SinkMode> modes;
OptionList<int, double> samplerates;
char hostname[1024];
int port = 4242;
int modeId = 1;
std::vector<unsigned int> sampleRates;
std::string sampleRatesTxt;
unsigned int sampleRate = 48000;
int port = 7355;
SinkMode mode = SINK_MODE_TCP;
int modeId;
int sampleRate = 48000;
int srId;
bool stereo = false;
bool running = false;
int16_t* netBuf;
net::Listener listener;
net::Conn conn;
std::mutex connMtx;
};
class NetworkSinkModule : public ModuleManager::Instance {
class NetworkSinkModule : public ModuleManager::Instance, SinkProvider {
public:
NetworkSinkModule(std::string name) {
this->name = name;
provider.create = create_sink;
provider.ctx = this;
sigpath::sinkManager.registerSinkProvider("Network", provider);
// Register self as provider
sigpath::streamManager.registerSinkProvider("Network", this);
}
~NetworkSinkModule() {
// Unregister sink, this will automatically stop and delete all instances of the audio sink
sigpath::sinkManager.unregisterSinkProvider("Network");
// Unregister self
sigpath::streamManager.unregisterSinkProvider(this);
}
void postInit() {}
@ -328,14 +335,13 @@ public:
return enabled;
}
private:
static SinkManager::Sink* create_sink(SinkManager::Stream* stream, std::string streamName, void* ctx) {
return (SinkManager::Sink*)(new NetworkSink(stream, streamName));
std::unique_ptr<Sink> createSink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) {
return std::make_unique<NetworkSink>(entry, stream, name, id, stringId);
}
private:
std::string name;
bool enabled = true;
SinkManager::SinkProvider provider;
};
MOD_EXPORT void _INIT_() {

Wyświetl plik

@ -141,10 +141,10 @@ public:
return;
}
}
catch (std::exception e) {
catch (const std::exception& e) {
char buf[1024];
sprintf(buf, "%016" PRIX64, serial);
flog::error("Could not open Airspy {0}", buf);
flog::error("Could not open Airspy {}", buf);
}
selectedSerial = serial;

Wyświetl plik

@ -144,10 +144,10 @@ public:
return;
}
}
catch (std::exception e) {
catch (const std::exception& e) {
char buf[1024];
sprintf(buf, "%016" PRIX64, serial);
flog::error("Could not open Airspy HF+ {0}", buf);
flog::error("Could not open Airspy HF+ {}", buf);
}
selectedSerial = serial;

Wyświetl plik

@ -35,6 +35,10 @@ public:
AudioSourceModule(std::string name) {
this->name = name;
#if RTAUDIO_VERSION_MAJOR >= 6
audio.setErrorCallback(&errorCallback);
#endif
sampleRate = 48000.0;
handler.ctx = this;
@ -83,21 +87,28 @@ public:
void refresh() {
devices.clear();
#if RTAUDIO_VERSION_MAJOR >= 6
for (int i : audio.getDeviceIds()) {
#else
int count = audio.getDeviceCount();
for (int i = 0; i < count; i++) {
#endif
try {
// Get info
auto info = audio.getDeviceInfo(i);
#if !defined(RTAUDIO_VERSION_MAJOR) || RTAUDIO_VERSION_MAJOR < 6
if (!info.probed) { continue; }
#endif
// Check that it has a stereo input
if (info.probed && info.inputChannels < 2) { continue; }
if (info.inputChannels < 2) { continue; }
// Save info
DeviceInfo dinfo = { info, i };
devices.define(info.name, info.name, dinfo);
}
catch (std::exception e) {
flog::error("Error getting audio device info: {0}", e.what());
catch (const std::exception& e) {
flog::error("Error getting audio device ({}) info: {}", i, e.what());
}
}
}
@ -189,11 +200,11 @@ private:
_this->audio.startStream();
_this->running = true;
}
catch (std::exception e) {
flog::error("Error opening audio device: {0}", e.what());
catch (const std::exception& e) {
flog::error("Error opening audio device: {}", e.what());
}
flog::info("AudioSourceModule '{0}': Start!", _this->name);
flog::info("AudioSourceModule '{}': Start!", _this->name);
}
static void stop(void* ctx) {
@ -254,6 +265,22 @@ private:
return 0;
}
#if RTAUDIO_VERSION_MAJOR >= 6
static void errorCallback(RtAudioErrorType type, const std::string& errorText) {
switch (type) {
case RtAudioErrorType::RTAUDIO_NO_ERROR:
return;
case RtAudioErrorType::RTAUDIO_WARNING:
case RtAudioErrorType::RTAUDIO_NO_DEVICES_FOUND:
case RtAudioErrorType::RTAUDIO_DEVICE_DISCONNECT:
flog::warn("AudioSourceModule Warning: {} ({})", errorText, (int)type);
break;
default:
throw std::runtime_error(errorText);
}
}
#endif
std::string name;
bool enabled = true;
dsp::stream<dsp::complex_t> stream;
@ -290,4 +317,4 @@ MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) {
MOD_EXPORT void _END_() {
config.disableAutoSave();
config.save();
}
}

Wyświetl plik

@ -347,7 +347,7 @@ private:
static void start(void* ctx) {
BladeRFSourceModule* _this = (BladeRFSourceModule*)ctx;
if (_this->running) { return; }
if (_this->devCount == 0) { return; }
if (_this->devCount <= 0) { return; }
// Open device
bladerf_devinfo info = _this->devInfoList[_this->devId];

Wyświetl plik

@ -139,8 +139,8 @@ private:
//gui::freqSelect.maxFreq = _this->centerFreq + (_this->sampleRate/2);
//gui::freqSelect.limitFreq = true;
}
catch (std::exception& e) {
flog::error("Error: {0}", e.what());
catch (const std::exception& e) {
flog::error("Error: {}", e.what());
}
config.acquire();
config.conf["path"] = _this->fileSelect.path;

Wyświetl plik

@ -137,6 +137,10 @@ public:
hackrf_device_list_t* _devList = hackrf_device_list();
for (int i = 0; i < _devList->devicecount; i++) {
// Skip devices that are in use
if (_devList->serial_numbers[i] == NULL) { continue; }
// Save the device serial number
devList.push_back(_devList->serial_numbers[i]);
devListTxt += (char*)(_devList->serial_numbers[i] + 16);
devListTxt += '\0';

Wyświetl plik

@ -2,6 +2,13 @@
#include <utils/flog.h>
namespace hermes {
const int SAMPLERATE_LIST[] = {
48000,
96000,
192000,
384000
};
Client::Client(std::shared_ptr<net::Socket> sock) {
this->sock = sock;
@ -33,6 +40,7 @@ namespace hermes {
void Client::setSamplerate(HermesLiteSamplerate samplerate) {
writeReg(0, (uint32_t)samplerate << 24);
blockSize = SAMPLERATE_LIST[samplerate] / 200;
}
void Client::setFrequency(double freq) {
@ -157,12 +165,15 @@ namespace hermes {
void Client::worker() {
uint8_t rbuf[2048];
MetisUSBPacket* pkt = (MetisUSBPacket*)rbuf;
int sampleCount = 0;
while (true) {
// Wait for a packet or exit if connection closed
int len = sock->recv(rbuf, 2048);
if (len <= 0) { break; }
// Ignore anything that's not a USB packet
// TODO: Gotta check the endpoint
if (htons(pkt->hdr.signature) != HERMES_METIS_SIGNATURE || pkt->hdr.type != METIS_PKT_USB) {
continue;
}
@ -183,9 +194,10 @@ namespace hermes {
flog::warn("Got response! Reg={0}, Seq={1}", reg, (uint32_t)htonl(pkt->seq));
}
// Decode and send IQ to stream
// Decode and save IQ to buffer
uint8_t* iq = &frame[8];
for (int i = 0; i < 63; i++) {
dsp::complex_t* writeBuf = &out.writeBuf[sampleCount];
for (int i = 0; i < HERMES_SAMPLES_PER_FRAME; i++) {
// Convert to 32bit
int32_t si = ((uint32_t)iq[(i*8) + 0] << 16) | ((uint32_t)iq[(i*8) + 1] << 8) | (uint32_t)iq[(i*8) + 2];
int32_t sq = ((uint32_t)iq[(i*8) + 3] << 16) | ((uint32_t)iq[(i*8) + 4] << 8) | (uint32_t)iq[(i*8) + 5];
@ -195,18 +207,23 @@ namespace hermes {
sq = (sq << 8) >> 8;
// Convert to float (IQ swapped for some reason)
out.writeBuf[i].im = (float)si / (float)0x1000000;
out.writeBuf[i].re = (float)sq / (float)0x1000000;
writeBuf[i].im = (float)si / (float)0x1000000;
writeBuf[i].re = (float)sq / (float)0x1000000;
}
sampleCount += HERMES_SAMPLES_PER_FRAME;
// If enough samples are in the buffer, send to stream
if (sampleCount >= blockSize) {
out.swap(sampleCount);
sampleCount = 0;
}
out.swap(63);
// TODO: Buffer the data to avoid having a very high DSP frame rate
}
}
}
std::vector<Info> discover() {
// TODO: Maybe try to instead detect on each interface as a work around for 0.0.0.0 not receiving anything?
auto sock = net::openudp("0.0.0.0", 1024);
// Open a UDP broadcast socket (TODO: Figure out why 255.255.255.255 doesn't work on windows with local = 0.0.0.0)
auto sock = net::openudp("255.255.255.255", 1024, "0.0.0.0", 0, true);
// Build discovery packet
uint8_t discoveryPkt[64];
@ -225,6 +242,7 @@ namespace hermes {
}
}
// Await all responses
std::vector<Info> devices;
while (true) {
// Wait for a response
@ -258,7 +276,9 @@ namespace hermes {
devices.push_back(info);
}
// Close broadcast socket
sock->close();
return devices;
}

Wyświetl plik

@ -7,11 +7,12 @@
#include <string>
#include <thread>
#define HERMES_METIS_REPEAT 5
#define HERMES_METIS_TIMEOUT 1000
#define HERMES_METIS_SIGNATURE 0xEFFE
#define HERMES_HPSDR_USB_SYNC 0x7F
#define HERMES_I2C_DELAY 50
#define HERMES_METIS_REPEAT 5
#define HERMES_METIS_TIMEOUT 1000
#define HERMES_METIS_SIGNATURE 0xEFFE
#define HERMES_HPSDR_USB_SYNC 0x7F
#define HERMES_I2C_DELAY 50
#define HERMES_SAMPLES_PER_FRAME 63
namespace hermes {
enum MetisPacketType {
@ -140,7 +141,7 @@ namespace hermes {
dsp::stream<dsp::complex_t> out;
//private:
private:
void sendMetisUSB(uint8_t endpoint, void* frame0, void* frame1 = NULL);
void sendMetisControl(MetisControl ctrl);
@ -149,12 +150,12 @@ namespace hermes {
void writeI2C(I2CPort port, uint8_t addr, uint8_t reg, uint8_t data);
void worker();
double freq = 0;
int blockSize = 63;
std::thread workerThread;
std::shared_ptr<net::Socket> sock;
uint32_t usbSeq = 0;

Wyświetl plik

@ -17,7 +17,7 @@ SDRPP_MOD_INFO{
/* Name: */ "hermes_source",
/* Description: */ "Hermes Lite 2 source module for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Version: */ 0, 1, 1,
/* Max instances */ 1
};

Wyświetl plik

@ -7,7 +7,9 @@
#include <gui/smgui.h>
#include <iio.h>
#include <ad9361.h>
#include <utils/optionlist.h>
#include <algorithm>
#include <regex>
#define CONCAT(a, b) ((std::string(a) + b).c_str())
@ -15,16 +17,10 @@ SDRPP_MOD_INFO{
/* Name: */ "plutosdr_source",
/* Description: */ "PlutoSDR source module for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Version: */ 0, 2, 0,
/* Max instances */ 1
};
const char* gainModes[] = {
"manual", "fast_attack", "slow_attack", "hybrid"
};
const char* gainModesTxt = "Manual\0Fast Attack\0Slow Attack\0Hybrid\0";
ConfigManager config;
class PlutoSDRSourceModule : public ModuleManager::Instance {
@ -32,34 +28,34 @@ public:
PlutoSDRSourceModule(std::string name) {
this->name = name;
// Define valid samplerates
for (int sr = 1000000; sr <= 61440000; sr += 500000) {
samplerates.define(sr, getBandwdithScaled(sr), sr);
}
samplerates.define(61440000, getBandwdithScaled(61440000.0), 61440000.0);
// Define valid bandwidths
bandwidths.define(0, "Auto", 0);
for (int bw = 1000000.0; bw <= 52000000; bw += 500000) {
bandwidths.define(bw, getBandwdithScaled(bw), bw);
}
// Define gain modes
gainModes.define("manual", "Manual", "manual");
gainModes.define("fast_attack", "Fast Attack", "fast_attack");
gainModes.define("slow_attack", "Slow Attack", "slow_attack");
gainModes.define("hybrid", "Hybrid", "hybrid");
// Enumerate devices
refresh();
// Select device
config.acquire();
std::string _ip = config.conf["IP"];
strcpy(&ip[3], _ip.c_str());
sampleRate = config.conf["sampleRate"];
gainMode = config.conf["gainMode"];
gain = config.conf["gain"];
devDesc = config.conf["device"];
config.release();
select(devDesc);
// Generate the samplerate list and find srId
bool found = false;
int id = 0;
for (double sr = 1000000; sr <= 20000000; sr += 500000) {
sampleRates.push_back(sr);
sampleRatesTxt += getBandwdithScaled(sr);
sampleRatesTxt += '\0';
if (sr == sampleRate) {
found = true;
srId = id;
}
id++;
}
if (!found) {
srId = 0;
sampleRate = sampleRates[0];
}
// Register source
handler.ctx = this;
handler.selectHandler = menuSelected;
handler.deselectHandler = menuDeselected;
@ -105,9 +101,157 @@ private:
return std::string(buf);
}
void refresh() {
// Clear device list
devices.clear();
// Create scan context
iio_scan_context* sctx = iio_create_scan_context(NULL, 0);
if (!sctx) {
flog::error("Failed get scan context");
return;
}
// Create parsing regexes
std::regex backendRgx(".+(?=:)", std::regex::ECMAScript);
std::regex modelRgx("\\(.+(?=\\),)", std::regex::ECMAScript);
std::regex serialRgx("serial=[0-9A-Za-z]+", std::regex::ECMAScript);
// Enumerate devices
iio_context_info** ctxInfoList;
ssize_t count = iio_scan_context_get_info_list(sctx, &ctxInfoList);
if (count < 0) {
flog::error("Failed to enumerate contexts");
return;
}
for (ssize_t i = 0; i < count; i++) {
iio_context_info* info = ctxInfoList[i];
std::string desc = iio_context_info_get_description(info);
std::string duri = iio_context_info_get_uri(info);
// If the device is not a plutosdr, don't include it
if (desc.find("PlutoSDR") == std::string::npos) {
flog::warn("Ignored IIO device: [{}] {}", duri, desc);
continue;
}
// Extract the backend
std::string backend = "unknown";
std::smatch backendMatch;
if (std::regex_search(duri, backendMatch, backendRgx)) {
backend = backendMatch[0];
}
// Extract the model
std::string model = "Unknown";
std::smatch modelMatch;
if (std::regex_search(desc, modelMatch, modelRgx)) {
model = modelMatch[0];
int parenthPos = model.find('(');
if (parenthPos != std::string::npos) {
model = model.substr(parenthPos+1);
}
}
// Extract the serial
std::string serial = "unknown";
std::smatch serialMatch;
if (std::regex_search(desc, serialMatch, serialRgx)) {
serial = serialMatch[0].str().substr(7);
}
// Construct the device name
std::string devName = '(' + backend + ") " + model + " [" + serial + ']';
// Save device
devices.define(desc, devName, duri);
}
iio_context_info_list_free(ctxInfoList);
// Destroy scan context
iio_scan_context_destroy(sctx);
#ifdef __ANDROID__
// On Android, a default IP entry must be made (TODO: This is not ideal since the IP cannot be changed)
const char* androidURI = "ip:192.168.2.1";
const char* androidName = "Default (192.168.2.1)";
devices.define(androidName, androidName, androidURI);
#endif
}
void select(const std::string& desc) {
// If no device is available, give up
if (devices.empty()) {
devDesc.clear();
return;
}
// If the device is not available, select the first one
if (!devices.keyExists(desc)) {
select(devices.key(0));
}
// Update URI
devDesc = desc;
uri = devices.value(devices.keyId(desc));
// TODO: Enumerate capabilities
// Load defaults
samplerate = 4000000;
bandwidth = 0;
gmId = 0;
gain = -1.0f;
// Load device config
config.acquire();
if (config.conf["devices"][devDesc].contains("samplerate")) {
samplerate = config.conf["devices"][devDesc]["samplerate"];
}
if (config.conf["devices"][devDesc].contains("bandwidth")) {
bandwidth = config.conf["devices"][devDesc]["bandwidth"];
}
if (config.conf["devices"][devDesc].contains("gainMode")) {
// Select given gain mode or default if invalid
std::string gm = config.conf["devices"][devDesc]["gainMode"];
if (gainModes.keyExists(gm)) {
gmId = gainModes.keyId(gm);
}
else {
gmId = 0;
}
}
if (config.conf["devices"][devDesc].contains("gain")) {
gain = config.conf["devices"][devDesc]["gain"];
gain = std::clamp<int>(gain, -1.0f, 73.0f);
}
config.release();
// Update samplerate ID
if (samplerates.keyExists(samplerate)) {
srId = samplerates.keyId(samplerate);
}
else {
srId = 0;
samplerate = samplerates.value(srId);
}
// Update bandwidth ID
if (bandwidths.keyExists(bandwidth)) {
bwId = bandwidths.keyId(bandwidth);
}
else {
bwId = 0;
bandwidth = bandwidths.value(bwId);
}
// Update core samplerate
core::setInputSampleRate(samplerate);
}
static void menuSelected(void* ctx) {
PlutoSDRSourceModule* _this = (PlutoSDRSourceModule*)ctx;
core::setInputSampleRate(_this->sampleRate);
core::setInputSampleRate(_this->samplerate);
flog::info("PlutoSDRSourceModule '{0}': Menu Select!", _this->name);
}
@ -120,12 +264,17 @@ private:
PlutoSDRSourceModule* _this = (PlutoSDRSourceModule*)ctx;
if (_this->running) { return; }
// TODO: INIT CONTEXT HERE
_this->ctx = iio_create_context_from_uri(_this->ip);
// If no device is selected, give up
if (_this->devDesc.empty() || _this->uri.empty()) { return; }
// Open context
_this->ctx = iio_create_context_from_uri(_this->uri.c_str());
if (_this->ctx == NULL) {
flog::error("Could not open pluto");
flog::error("Could not open pluto ({})", _this->uri);
return;
}
// Get phy and device handle
_this->phy = iio_context_find_device(_this->ctx, "ad9361-phy");
if (_this->phy == NULL) {
flog::error("Could not connect to pluto phy");
@ -139,17 +288,27 @@ private:
return;
}
// Configure pluto
// Get RX channels
_this->rxChan = iio_device_find_channel(_this->phy, "voltage0", false);
_this->rxLO = iio_device_find_channel(_this->phy, "altvoltage0", true);
// Enable RX LO and disable TX
iio_channel_attr_write_bool(iio_device_find_channel(_this->phy, "altvoltage1", true), "powerdown", true);
iio_channel_attr_write_bool(iio_device_find_channel(_this->phy, "altvoltage0", true), "powerdown", false);
iio_channel_attr_write_bool(_this->rxLO, "powerdown", false);
iio_channel_attr_write(iio_device_find_channel(_this->phy, "voltage0", false), "rf_port_select", "A_BALANCED");
iio_channel_attr_write_longlong(iio_device_find_channel(_this->phy, "altvoltage0", true), "frequency", round(_this->freq)); // Freq
iio_channel_attr_write_longlong(iio_device_find_channel(_this->phy, "voltage0", false), "sampling_frequency", round(_this->sampleRate)); // Sample rate
iio_channel_attr_write(iio_device_find_channel(_this->phy, "voltage0", false), "gain_control_mode", gainModes[_this->gainMode]); // manual gain
iio_channel_attr_write_longlong(iio_device_find_channel(_this->phy, "voltage0", false), "hardwaregain", round(_this->gain)); // gain
ad9361_set_bb_rate(_this->phy, round(_this->sampleRate));
// Configure RX channel
iio_channel_attr_write(_this->rxChan, "rf_port_select", "A_BALANCED");
iio_channel_attr_write_longlong(_this->rxLO, "frequency", round(_this->freq)); // Freq
iio_channel_attr_write_bool(_this->rxChan, "filter_fir_en", true); // Digital filter
iio_channel_attr_write_longlong(_this->rxChan, "sampling_frequency", round(_this->samplerate)); // Sample rate
iio_channel_attr_write_double(_this->rxChan, "hardwaregain", _this->gain); // Gain
iio_channel_attr_write(_this->rxChan, "gain_control_mode", _this->gainModes.value(_this->gmId).c_str()); // Gain mode
_this->setBandwidth(_this->bandwidth);
// Configure the ADC filters
ad9361_set_bb_rate(_this->phy, round(_this->samplerate));
// Start worker thread
_this->running = true;
_this->workerThread = std::thread(worker, _this);
flog::info("PlutoSDRSourceModule '{0}': Start!", _this->name);
@ -158,12 +317,14 @@ private:
static void stop(void* ctx) {
PlutoSDRSourceModule* _this = (PlutoSDRSourceModule*)ctx;
if (!_this->running) { return; }
// Stop worker thread
_this->running = false;
_this->stream.stopWriter();
_this->workerThread.join();
_this->stream.clearWriteStop();
// DESTROY CONTEXT HERE
// Close device
if (_this->ctx != NULL) {
iio_context_destroy(_this->ctx);
_this->ctx = NULL;
@ -176,8 +337,8 @@ private:
PlutoSDRSourceModule* _this = (PlutoSDRSourceModule*)ctx;
_this->freq = freq;
if (_this->running) {
// SET PLUTO FREQ HERE
iio_channel_attr_write_longlong(iio_device_find_channel(_this->phy, "altvoltage0", true), "frequency", round(freq));
// Tune device
iio_channel_attr_write_longlong(_this->rxLO, "frequency", round(freq));
}
flog::info("PlutoSDRSourceModule '{0}': Tune: {1}!", _this->name, freq);
}
@ -186,120 +347,184 @@ private:
PlutoSDRSourceModule* _this = (PlutoSDRSourceModule*)ctx;
if (_this->running) { SmGui::BeginDisabled(); }
SmGui::LeftLabel("IP");
SmGui::FillWidth();
if (SmGui::InputText(CONCAT("##_pluto_ip_", _this->name), &_this->ip[3], 16)) {
SmGui::ForceSync();
if (SmGui::Combo("##plutosdr_dev_sel", &_this->devId, _this->devices.txt)) {
_this->select(_this->devices.key(_this->devId));
config.acquire();
config.conf["IP"] = &_this->ip[3];
config.conf["device"] = _this->devices.key(_this->devId);
config.release(true);
}
SmGui::LeftLabel("Samplerate");
if (SmGui::Combo(CONCAT("##_pluto_sr_", _this->name), &_this->srId, _this->samplerates.txt)) {
_this->samplerate = _this->samplerates.value(_this->srId);
core::setInputSampleRate(_this->samplerate);
if (!_this->devDesc.empty()) {
config.acquire();
config.conf["devices"][_this->devDesc]["samplerate"] = _this->samplerate;
config.release(true);
}
}
// Refresh button
SmGui::SameLine();
SmGui::FillWidth();
if (SmGui::Combo(CONCAT("##_pluto_sr_", _this->name), &_this->srId, _this->sampleRatesTxt.c_str())) {
_this->sampleRate = _this->sampleRates[_this->srId];
core::setInputSampleRate(_this->sampleRate);
config.acquire();
config.conf["sampleRate"] = _this->sampleRate;
config.release(true);
SmGui::ForceSync();
if (SmGui::Button(CONCAT("Refresh##_pluto_refr_", _this->name))) {
_this->refresh();
_this->select(_this->devDesc);
}
if (_this->running) { SmGui::EndDisabled(); }
SmGui::LeftLabel("Bandwidth");
SmGui::FillWidth();
if (SmGui::Combo(CONCAT("##_pluto_bw_", _this->name), &_this->bwId, _this->bandwidths.txt)) {
_this->bandwidth = _this->bandwidths.value(_this->bwId);
if (_this->running) {
_this->setBandwidth(_this->bandwidth);
}
if (!_this->devDesc.empty()) {
config.acquire();
config.conf["devices"][_this->devDesc]["bandwidth"] = _this->bandwidth;
config.release(true);
}
}
SmGui::LeftLabel("Gain Mode");
SmGui::FillWidth();
SmGui::ForceSync();
if (SmGui::Combo(CONCAT("##_gainmode_select_", _this->name), &_this->gainMode, gainModesTxt)) {
if (SmGui::Combo(CONCAT("##_pluto_gainmode_select_", _this->name), &_this->gmId, _this->gainModes.txt)) {
if (_this->running) {
iio_channel_attr_write(iio_device_find_channel(_this->phy, "voltage0", false), "gain_control_mode", gainModes[_this->gainMode]);
iio_channel_attr_write(_this->rxChan, "gain_control_mode", _this->gainModes.value(_this->gmId).c_str());
}
if (!_this->devDesc.empty()) {
config.acquire();
config.conf["devices"][_this->devDesc]["gainMode"] = _this->gainModes.key(_this->gmId);
config.release(true);
}
config.acquire();
config.conf["gainMode"] = _this->gainMode;
config.release(true);
}
SmGui::LeftLabel("PGA Gain");
if (_this->gainMode) { SmGui::BeginDisabled(); }
SmGui::LeftLabel("Gain");
if (_this->gmId) { SmGui::BeginDisabled(); }
SmGui::FillWidth();
if (SmGui::SliderFloat(CONCAT("##_gain_select_", _this->name), &_this->gain, 0, 76)) {
if (SmGui::SliderFloatWithSteps(CONCAT("##_pluto_gain__", _this->name), &_this->gain, -1.0f, 73.0f, 1.0f, SmGui::FMT_STR_FLOAT_DB_NO_DECIMAL)) {
if (_this->running) {
iio_channel_attr_write_longlong(iio_device_find_channel(_this->phy, "voltage0", false), "hardwaregain", round(_this->gain));
iio_channel_attr_write_double(_this->rxChan, "hardwaregain", _this->gain);
}
if (!_this->devDesc.empty()) {
config.acquire();
config.conf["devices"][_this->devDesc]["gain"] = _this->gain;
config.release(true);
}
config.acquire();
config.conf["gain"] = _this->gain;
config.release(true);
}
if (_this->gainMode) { SmGui::EndDisabled(); }
if (_this->gmId) { SmGui::EndDisabled(); }
}
void setBandwidth(int bw) {
if (bw > 0) {
iio_channel_attr_write_longlong(rxChan, "rf_bandwidth", bw);
}
else {
iio_channel_attr_write_longlong(rxChan, "rf_bandwidth", std::min<int>(samplerate, 52000000));
}
}
static void worker(void* ctx) {
PlutoSDRSourceModule* _this = (PlutoSDRSourceModule*)ctx;
int blockSize = _this->sampleRate / 200.0f;
int blockSize = _this->samplerate / 200.0f;
struct iio_channel *rx0_i, *rx0_q;
struct iio_buffer* rxbuf;
rx0_i = iio_device_find_channel(_this->dev, "voltage0", 0);
rx0_q = iio_device_find_channel(_this->dev, "voltage1", 0);
// Acquire channels
iio_channel* rx0_i = iio_device_find_channel(_this->dev, "voltage0", 0);
iio_channel* rx0_q = iio_device_find_channel(_this->dev, "voltage1", 0);
if (!rx0_i || !rx0_q) {
flog::error("Failed to acquire RX channels");
return;
}
// Start streaming
iio_channel_enable(rx0_i);
iio_channel_enable(rx0_q);
rxbuf = iio_device_create_buffer(_this->dev, blockSize, false);
// Allocate buffer
iio_buffer* rxbuf = iio_device_create_buffer(_this->dev, blockSize, false);
if (!rxbuf) {
flog::error("Could not create RX buffer");
return;
}
// Receive loop
while (true) {
// Read samples here
// TODO: RECEIVE HERE
// Read samples
iio_buffer_refill(rxbuf);
// Get buffer pointer
int16_t* buf = (int16_t*)iio_buffer_first(rxbuf, rx0_i);
if (!buf) { break; }
for (int i = 0; i < blockSize; i++) {
_this->stream.writeBuf[i].re = (float)buf[i * 2] / 32768.0f;
_this->stream.writeBuf[i].im = (float)buf[(i * 2) + 1] / 32768.0f;
}
// Convert samples to CF32
volk_16i_s32f_convert_32f((float*)_this->stream.writeBuf, buf, 32768.0f, blockSize * 2);
// Send out the samples
if (!_this->stream.swap(blockSize)) { break; };
}
// Stop streaming
iio_channel_disable(rx0_i);
iio_channel_disable(rx0_q);
// Free buffer
iio_buffer_destroy(rxbuf);
}
std::string name;
bool enabled = true;
dsp::stream<dsp::complex_t> stream;
float sampleRate;
SourceManager::SourceHandler handler;
std::thread workerThread;
struct iio_context* ctx = NULL;
struct iio_device* phy = NULL;
struct iio_device* dev = NULL;
iio_context* ctx = NULL;
iio_device* phy = NULL;
iio_device* dev = NULL;
iio_channel* rxLO = NULL;
iio_channel* rxChan = NULL;
bool running = false;
bool ipMode = true;
double freq;
char ip[1024] = "ip:192.168.2.1";
int gainMode = 0;
float gain = 0;
int srId = 0;
std::vector<double> sampleRates;
std::string sampleRatesTxt;
std::string devDesc = "";
std::string uri = "";
double freq;
int samplerate = 4000000;
int bandwidth = 0;
float gain = -1;
int devId = 0;
int srId = 0;
int bwId = 0;
int gmId = 0;
OptionList<std::string, std::string> devices;
OptionList<int, double> samplerates;
OptionList<int, double> bandwidths;
OptionList<std::string, std::string> gainModes;
};
MOD_EXPORT void _INIT_() {
json defConf;
defConf["IP"] = "192.168.2.1";
defConf["sampleRate"] = 4000000.0f;
defConf["gainMode"] = 0;
defConf["gain"] = 0.0f;
json defConf = {};
defConf["device"] = "";
defConf["devices"] = {};
config.setPath(core::args["root"].s() + "/plutosdr_source_config.json");
config.load(defConf);
config.enableAutoSave();
// Reset the configuration if the old format is still used
config.acquire();
if (!config.conf.contains("device") || !config.conf.contains("devices")) {
config.conf = defConf;
config.release(true);
}
else {
config.release();
}
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {

Wyświetl plik

@ -17,7 +17,7 @@ SDRPP_MOD_INFO{
/* Name: */ "rfspace_source",
/* Description: */ "RFspace source module for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Version: */ 0, 1, 1,
/* Max instances */ 1
};
@ -154,8 +154,8 @@ private:
_this->client = rfspace::connect(_this->hostname, _this->port, &_this->stream);
_this->deviceInit();
}
catch (std::exception e) {
flog::error("Could not connect to SDR: {0}", e.what());
catch (const std::exception& e) {
flog::error("Could not connect to SDR: {}", e.what());
}
}
else if (connected && SmGui::Button("Disconnect##rfspace_source")) {
@ -231,7 +231,7 @@ private:
}
// Create samplerate list
auto srs = client->getValidSampleRates();
auto srs = client->getSamplerates();
sampleRates.clear();
for (auto& sr : srs) {
sampleRates.define(sr, getBandwdithScaled(sr), sr);
@ -317,7 +317,7 @@ private:
dsp::stream<dsp::complex_t> stream;
SourceManager::SourceHandler handler;
rfspace::RFspaceClient client;
std::shared_ptr<rfspace::Client> client;
};
MOD_EXPORT void _INIT_() {

Wyświetl plik

@ -6,15 +6,13 @@
using namespace std::chrono_literals;
namespace rfspace {
RFspaceClientClass::RFspaceClientClass(net::Conn conn, net::Conn udpConn, dsp::stream<dsp::complex_t>* out) {
client = std::move(conn);
udpClient = std::move(udpConn);
Client::Client(std::shared_ptr<net::Socket> tcp, std::shared_ptr<net::Socket> udp, dsp::stream<dsp::complex_t>* out) {
this->tcp = tcp;
this->udp = udp;
output = out;
// Allocate buffers
rbuffer = new uint8_t[RFSPACE_MAX_SIZE];
sbuffer = new uint8_t[RFSPACE_MAX_SIZE];
ubuffer = new uint8_t[RFSPACE_MAX_SIZE];
// Clear write stop of stream just in case
output->clearWriteStop();
@ -22,9 +20,9 @@ namespace rfspace {
// Send UDP packet so that a router opens the port
sendDummyUDP();
// Start readers
client->readAsync(sizeof(tcpHeader), (uint8_t*)&tcpHeader, tcpHandler, this);
udpClient->readAsync(RFSPACE_MAX_SIZE, ubuffer, udpHandler, this);
// Start workers
tcpWorkerThread = std::thread(&Client::tcpWorker, this);
udpWorkerThread = std::thread(&Client::udpWorker, this);
// Get device ID and wait for response
getControlItem(RFSPACE_CTRL_ITEM_PROD_ID, NULL, 0);
@ -43,22 +41,20 @@ namespace rfspace {
setPort(RFSPACE_RF_PORT_1);
// Start heartbeat
heartBeatThread = std::thread(&RFspaceClientClass::heartBeatWorker, this);
heartBeatThread = std::thread(&Client::heartBeatWorker, this);
}
RFspaceClientClass::~RFspaceClientClass() {
Client::~Client() {
close();
delete[] rbuffer;
delete[] sbuffer;
delete[] ubuffer;
}
void RFspaceClientClass::sendDummyUDP() {
void Client::sendDummyUDP() {
uint8_t dummy = 0x5A;
udpClient->write(1, &dummy);
udp->send(&dummy, 1);
}
int RFspaceClientClass::getControlItem(ControlItem item, void* param, int len) {
int Client::getControlItem(ControlItem item, void* param, int len) {
// Build packet
uint16_t* header = (uint16_t*)&sbuffer[0];
uint16_t* item_val = (uint16_t*)&sbuffer[2];
@ -66,12 +62,12 @@ namespace rfspace {
*item_val = item;
// Send packet
client->write(4, sbuffer);
tcp->send(sbuffer, 4);
return -1;
}
void RFspaceClientClass::setControlItem(ControlItem item, void* param, int len) {
void Client::setControlItem(ControlItem item, void* param, int len) {
// Build packet
uint16_t* header = (uint16_t*)&sbuffer[0];
uint16_t* item_val = (uint16_t*)&sbuffer[2];
@ -80,10 +76,10 @@ namespace rfspace {
memcpy(&sbuffer[4], param, len);
// Send packet
client->write(len + 4, sbuffer);
tcp->send(sbuffer, len + 4);
}
void RFspaceClientClass::setControlItemWithChanID(ControlItem item, uint8_t chanId, void* param, int len) {
void Client::setControlItemWithChanID(ControlItem item, uint8_t chanId, void* param, int len) {
// Build packet
uint16_t* header = (uint16_t*)&sbuffer[0];
uint16_t* item_val = (uint16_t*)&sbuffer[2];
@ -94,10 +90,10 @@ namespace rfspace {
memcpy(&sbuffer[5], param, len);
// Send packet
client->write(len + 5, sbuffer);
tcp->send(sbuffer, len + 5);
}
std::vector<uint32_t> RFspaceClientClass::getValidSampleRates() {
std::vector<uint32_t> Client::getSamplerates() {
std::vector<uint32_t> sr;
switch (deviceId) {
@ -119,92 +115,145 @@ namespace rfspace {
return sr;
}
void RFspaceClientClass::setFrequency(uint64_t freq) {
void Client::setFrequency(uint64_t freq) {
setControlItemWithChanID(RFSPACE_CTRL_ITEM_NCO_FREQUENCY, 0, &freq, 5);
}
void RFspaceClientClass::setPort(RFPort port) {
void Client::setPort(RFPort port) {
uint8_t value = port;
setControlItemWithChanID(RFSPACE_CTRL_ITEM_RF_PORT, 0, &value, sizeof(value));
}
void RFspaceClientClass::setGain(int8_t gain) {
void Client::setGain(int8_t gain) {
setControlItemWithChanID(RFSPACE_CTRL_ITEM_RF_GAIN, 0, &gain, sizeof(gain));
}
void RFspaceClientClass::setSampleRate(uint32_t sampleRate) {
void Client::setSampleRate(uint32_t sampleRate) {
// Acquire the buffer variables
std::lock_guard<std::mutex> lck(bufferMtx);
// Update block size
blockSize = sampleRate / 200;
// Send samplerate to device
setControlItemWithChanID(RFSPACE_CTRL_ITEM_IQ_SAMP_RATE, 0, &sampleRate, sizeof(sampleRate));
}
void RFspaceClientClass::start(SampleFormat sampleFormat, SampleDepth sampleDepth) {
void Client::start(SampleFormat sampleFormat, SampleDepth sampleDepth) {
// Acquire the buffer variables
std::lock_guard<std::mutex> lck(bufferMtx);
// Reset buffer
inBuffer = 0;
// Start device
uint8_t args[4] = { (uint8_t)sampleFormat, (uint8_t)RFSPACE_STATE_RUN, (uint8_t)sampleDepth, 0 };
setControlItem(RFSPACE_CTRL_ITEM_STATE, args, sizeof(args));
}
void RFspaceClientClass::stop() {
void Client::stop() {
uint8_t args[4] = { 0, RFSPACE_STATE_IDLE, 0, 0 };
setControlItem(RFSPACE_CTRL_ITEM_STATE, args, sizeof(args));
}
void RFspaceClientClass::close() {
void Client::close() {
// Stop UDP worker
output->stopWriter();
udp->close();
if (udpWorkerThread.joinable()) { udpWorkerThread.join(); }
output->clearWriteStop();
// Stop heartbeat worker
stopHeartBeat = true;
heartBeatCnd.notify_all();
if (heartBeatThread.joinable()) { heartBeatThread.join(); }
client->close();
udpClient->close();
output->clearWriteStop();
// Stop TCP worker
tcp->close();
if (tcpWorkerThread.joinable()) { tcpWorkerThread.join(); }
}
bool RFspaceClientClass::isOpen() {
return client->isOpen();
bool Client::isOpen() {
return tcp->isOpen() || udp->isOpen();
}
void RFspaceClientClass::tcpHandler(int count, uint8_t* buf, void* ctx) {
RFspaceClientClass* _this = (RFspaceClientClass*)ctx;
uint8_t type = _this->tcpHeader >> 13;
uint16_t size = _this->tcpHeader & 0b1111111111111;
void Client::tcpWorker() {
// Allocate receive buffer
uint8_t* buffer = new uint8_t[RFSPACE_MAX_SIZE];
// Read the rest of the data
if (size > 2) {
_this->client->read(size - 2, &_this->rbuffer[2]);
}
// Receive loop
while (true) {
// Receive header
uint16_t header;
if (tcp->recv((uint8_t*)&header, sizeof(uint16_t), true) <= 0) { break; }
// flog::warn("TCP received: {0} {1}", type, size);
// Decode header
uint8_t type = header >> 13;
uint16_t size = header & 0b1111111111111;
// Check for a device ID
uint16_t* controlItem = (uint16_t*)&_this->rbuffer[2];
if (type == RFSPACE_MSG_TYPE_T2H_SET_CTRL_ITEM_RESP && *controlItem == RFSPACE_CTRL_ITEM_PROD_ID) {
{
std::lock_guard<std::mutex> lck(_this->devIdMtx);
_this->deviceId = (DeviceID)*(uint32_t*)&_this->rbuffer[4];
_this->devIdAvailable = true;
// Receive data
if (tcp->recv(buffer, size - 2, true, RFSPACE_TIMEOUT_MS) <= 0) { break; }
// Check for a device ID
uint16_t* controlItem = (uint16_t*)&buffer[0];
if (type == RFSPACE_MSG_TYPE_T2H_SET_CTRL_ITEM_RESP && *controlItem == RFSPACE_CTRL_ITEM_PROD_ID) {
{
std::lock_guard<std::mutex> lck(devIdMtx);
deviceId = (DeviceID)*(uint32_t*)&buffer[2];
devIdAvailable = true;
}
devIdCnd.notify_all();
}
_this->devIdCnd.notify_all();
}
// Restart an async read
_this->client->readAsync(sizeof(_this->tcpHeader), (uint8_t*)&_this->tcpHeader, tcpHandler, _this);
// Free receive buffer
delete[] buffer;
}
void RFspaceClientClass::udpHandler(int count, uint8_t* buf, void* ctx) {
RFspaceClientClass* _this = (RFspaceClientClass*)ctx;
uint16_t hdr = (uint16_t)buf[0] | ((uint16_t)buf[1] << 8);
uint8_t type = hdr >> 13;
uint16_t size = hdr & 0b1111111111111;
void Client::udpWorker() {
// Allocate receive buffer
uint8_t* buffer = new uint8_t[RFSPACE_MAX_SIZE];
uint16_t* header = (uint16_t*)&buffer[0];
if (type == RFSPACE_MSG_TYPE_T2H_DATA_ITEM_0) {
int16_t* samples = (int16_t*)&buf[4];
int sampCount = (size - 4) / (2 * sizeof(int16_t));
volk_16i_s32f_convert_32f((float*)_this->output->writeBuf, samples, 32768.0f, sampCount * 2);
_this->output->swap(sampCount);
// Receive loop
while (true) {
// Receive datagram
int rsize = udp->recv(buffer, RFSPACE_MAX_SIZE);
if (rsize <= 0) { break; }
// Decode header
uint8_t type = (*header) >> 13;
uint16_t size = (*header) & 0b1111111111111;
if (rsize != size) {
flog::error("Datagram size mismatch: {} vs {}", rsize, size);
continue;
}
// Check for a sample packet
if (type == RFSPACE_MSG_TYPE_T2H_DATA_ITEM_0) {
// Acquire the buffer variables
std::lock_guard<std::mutex> lck(bufferMtx);
// Convert samples to complex float
int16_t* samples = (int16_t*)&buffer[4];
int sampCount = (size - 4) / (2 * sizeof(int16_t));
volk_16i_s32f_convert_32f((float*)&output->writeBuf[inBuffer], samples, 32768.0f, sampCount * 2);
inBuffer += sampCount;
// Send out samples if enough are buffered
if (inBuffer >= blockSize) {
if (!output->swap(inBuffer)) { break; };
inBuffer = 0;
}
}
}
// Restart an async read
_this->udpClient->readAsync(RFSPACE_MAX_SIZE, _this->ubuffer, udpHandler, _this);
// Free receive buffer
delete[] buffer;
}
void RFspaceClientClass::heartBeatWorker() {
void Client::heartBeatWorker() {
uint8_t dummy[4];
while (true) {
getControlItem(RFSPACE_CTRL_ITEM_STATE, dummy, sizeof(dummy));
@ -216,11 +265,9 @@ namespace rfspace {
}
}
RFspaceClient connect(std::string host, uint16_t port, dsp::stream<dsp::complex_t>* out) {
net::Conn conn = net::connect(host, port);
if (!conn) { return NULL; }
net::Conn udpConn = net::openUDP("0.0.0.0", port, host, port, true);
if (!udpConn) { return NULL; }
return RFspaceClient(new RFspaceClientClass(std::move(conn), std::move(udpConn), out));
std::shared_ptr<Client> connect(std::string host, uint16_t port, dsp::stream<dsp::complex_t>* out) {
auto tcp = net::connect(host, port);
auto udp = net::openudp(host, port, "0.0.0.0", port);
return std::make_shared<Client>(tcp, udp, out);
}
}

Wyświetl plik

@ -1,9 +1,10 @@
#pragma once
#include <utils/networking.h>
#include <utils/net.h>
#include <dsp/stream.h>
#include <dsp/types.h>
#include <atomic>
#include <queue>
#include <thread>
#include <vector>
#include <mutex>
#define RFSPACE_MAX_SIZE 8192
#define RFSPACE_HEARTBEAT_INTERVAL_MS 1000
@ -96,10 +97,10 @@ namespace rfspace {
RFSPACE_CTRL_ITEM_ERROR_LOG = 0x0410
};
class RFspaceClientClass {
class Client {
public:
RFspaceClientClass(net::Conn conn, net::Conn udpConn, dsp::stream<dsp::complex_t>* out);
~RFspaceClientClass();
Client(std::shared_ptr<net::Socket> tcp, std::shared_ptr<net::Socket> udp, dsp::stream<dsp::complex_t>* out);
~Client();
void sendDummyUDP();
@ -107,7 +108,7 @@ namespace rfspace {
void setControlItem(ControlItem item, void* param, int len);
void setControlItemWithChanID(ControlItem item, uint8_t chanId, void* param, int len);
std::vector<uint32_t> getValidSampleRates();
std::vector<uint32_t> getSamplerates();
void setFrequency(uint64_t freq);
void setPort(RFPort port);
@ -123,21 +124,22 @@ namespace rfspace {
DeviceID deviceId;
private:
static void tcpHandler(int count, uint8_t* buf, void* ctx);
static void udpHandler(int count, uint8_t* buf, void* ctx);
void tcpWorker();
void udpWorker();
void heartBeatWorker();
net::Conn client;
net::Conn udpClient;
std::shared_ptr<net::Socket> tcp;
std::shared_ptr<net::Socket> udp;
dsp::stream<dsp::complex_t>* output;
uint16_t tcpHeader;
uint16_t udpHeader;
uint8_t* rbuffer = NULL;
uint8_t* sbuffer = NULL;
uint8_t* ubuffer = NULL;
std::thread tcpWorkerThread;
std::thread udpWorkerThread;
std::thread heartBeatThread;
std::mutex heartBeatMtx;
@ -147,10 +149,12 @@ namespace rfspace {
bool devIdAvailable = false;
std::condition_variable devIdCnd;
std::mutex devIdMtx;
std::mutex bufferMtx;
int blockSize = 256;
int inBuffer = 0;
};
typedef std::unique_ptr<RFspaceClientClass> RFspaceClient;
RFspaceClient connect(std::string host, uint16_t port, dsp::stream<dsp::complex_t>* out);
std::shared_ptr<Client> connect(std::string host, uint16_t port, dsp::stream<dsp::complex_t>* out);
}

Wyświetl plik

@ -171,7 +171,7 @@ public:
#ifndef __ANDROID__
int oret = rtlsdr_open(&openDev, id);
#else
int oret = rtlsdr_open_fd(&openDev, devFd);
int oret = rtlsdr_open_sys_dev(&openDev, devFd);
#endif
if (oret < 0) {
@ -285,7 +285,7 @@ private:
#ifndef __ANDROID__
int oret = rtlsdr_open(&_this->openDev, _this->devId);
#else
int oret = rtlsdr_open_fd(&_this->openDev, _this->devFd);
int oret = rtlsdr_open_sys_dev(&_this->openDev, _this->devFd);
#endif
if (oret < 0) {
@ -523,8 +523,8 @@ private:
RTLSDRSourceModule* _this = (RTLSDRSourceModule*)ctx;
int sampCount = len / 2;
for (int i = 0; i < sampCount; i++) {
_this->stream.writeBuf[i].re = (float)(buf[i * 2] - 127) / 128.0f;
_this->stream.writeBuf[i].im = (float)(buf[(i * 2) + 1] - 127) / 128.0f;
_this->stream.writeBuf[i].re = ((float)buf[i * 2] - 127.4) / 128.0f;
_this->stream.writeBuf[i].im = ((float)buf[(i * 2) + 1] - 127.4) / 128.0f;
}
if (!_this->stream.swap(sampCount)) { return; }
}

Wyświetl plik

@ -132,8 +132,8 @@ private:
try {
_this->client = rtltcp::connect(&_this->stream, _this->ip, _this->port);
}
catch (std::exception e) {
flog::error("Could connect to RTL-TCP server: {0}", e.what());
catch (const std::exception& e) {
flog::error("Could connect to RTL-TCP server: {}", e.what());
return;
}

Wyświetl plik

@ -17,7 +17,7 @@ SDRPP_MOD_INFO{
/* Name: */ "sdrpp_server_source",
/* Description: */ "SDR++ Server source module for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Version: */ 0, 2, 0,
/* Max instances */ 1
};
@ -109,10 +109,10 @@ private:
SDRPPServerSourceModule* _this = (SDRPPServerSourceModule*)ctx;
if (_this->running) { return; }
// Try to connect if not already connected
if (!_this->client) {
// Try to connect if not already connected (Play button is locked anyway so not sure why I put this here)
if (!_this->connected()) {
_this->tryConnect();
if (!_this->client) { return; }
if (!_this->connected()) { return; }
}
// Set configuration
@ -127,7 +127,7 @@ private:
SDRPPServerSourceModule* _this = (SDRPPServerSourceModule*)ctx;
if (!_this->running) { return; }
if (_this->client) { _this->client->stop(); }
if (_this->connected()) { _this->client->stop(); }
_this->running = false;
flog::info("SDRPPServerSourceModule '{0}': Stop!", _this->name);
@ -135,7 +135,7 @@ private:
static void tune(double freq, void* ctx) {
SDRPPServerSourceModule* _this = (SDRPPServerSourceModule*)ctx;
if (_this->running && _this->client) {
if (_this->running && _this->connected()) {
_this->client->setFrequency(freq);
}
_this->freq = freq;
@ -146,7 +146,7 @@ private:
SDRPPServerSourceModule* _this = (SDRPPServerSourceModule*)ctx;
float menuWidth = ImGui::GetContentRegionAvail().x;
bool connected = (_this->client && _this->client->isOpen());
bool connected = _this->connected();
gui::mainWindow.playButtonLocked = !connected;
ImGui::GenericDialog("##sdrpp_srv_src_err_dialog", _this->serverBusy, GENERIC_DIALOG_BUTTONS_OK, [=](){
@ -227,14 +227,18 @@ private:
}
}
bool connected() {
return client && client->isOpen();
}
void tryConnect() {
try {
if (client) { client.reset(); }
client = server::connect(hostname, port, &stream);
deviceInit();
}
catch (std::exception e) {
flog::error("Could not connect to SDR: {0}", e.what());
catch (const std::exception& e) {
flog::error("Could not connect to SDR: {}", e.what());
if (!strcmp(e.what(), "Server busy")) { serverBusy = true; }
}
}
@ -281,7 +285,7 @@ private:
int sampleTypeId;
bool compression = false;
server::Client client;
std::shared_ptr<server::Client> client;
};
MOD_EXPORT void _INIT_() {

Wyświetl plik

@ -7,8 +7,8 @@
using namespace std::chrono_literals;
namespace server {
ClientClass::ClientClass(net::Conn conn, dsp::stream<dsp::complex_t>* out) {
client = std::move(conn);
Client::Client(std::shared_ptr<net::Socket> sock, dsp::stream<dsp::complex_t>* out) {
this->sock = sock;
output = out;
// Allocate buffers
@ -37,8 +37,8 @@ namespace server {
decomp.start();
link.start();
// Start readers
client->readAsync(sizeof(PacketHeader), rbuffer, tcpHandler, this);
// Start worker thread
workerThread = std::thread(&Client::worker, this);
// Ask for a UI
int res = getUI();
@ -46,14 +46,14 @@ namespace server {
else if (res == -2) { throw std::runtime_error("Server busy"); }
}
ClientClass::~ClientClass() {
Client::~Client() {
close();
ZSTD_freeDCtx(dctx);
delete[] rbuffer;
delete[] sbuffer;
}
void ClientClass::showMenu() {
void Client::showMenu() {
std::string diffId = "";
SmGui::DrawListElem diffValue;
bool syncRequired = false;
@ -96,8 +96,8 @@ namespace server {
}
}
void ClientClass::setFrequency(double freq) {
if (!client || !client->isOpen()) { return; }
void Client::setFrequency(double freq) {
if (!isOpen()) { return; }
*(double*)s_cmd_data = freq;
sendCommand(COMMAND_SET_FREQUENCY, sizeof(double));
auto waiter = awaitCommandAck(COMMAND_SET_FREQUENCY);
@ -105,119 +105,126 @@ namespace server {
waiter->handled();
}
double ClientClass::getSampleRate() {
double Client::getSampleRate() {
return currentSampleRate;
}
void ClientClass::setSampleType(dsp::compression::PCMType type) {
void Client::setSampleType(dsp::compression::PCMType type) {
if (!isOpen()) { return; }
s_cmd_data[0] = type;
sendCommand(COMMAND_SET_SAMPLE_TYPE, 1);
}
void ClientClass::setCompression(bool enabled) {
void Client::setCompression(bool enabled) {
if (!isOpen()) { return; }
s_cmd_data[0] = enabled;
sendCommand(COMMAND_SET_COMPRESSION, 1);
}
void ClientClass::start() {
if (!client || !client->isOpen()) { return; }
void Client::start() {
if (!isOpen()) { return; }
sendCommand(COMMAND_START, 0);
getUI();
}
void ClientClass::stop() {
if (!client || !client->isOpen()) { return; }
void Client::stop() {
if (!isOpen()) { return; }
sendCommand(COMMAND_STOP, 0);
getUI();
}
void ClientClass::close() {
void Client::close() {
// Stop worker
decompIn.stopWriter();
if (sock) { sock->close(); }
if (workerThread.joinable()) { workerThread.join(); }
decompIn.clearWriteStop();
// Stop DSP
decomp.stop();
link.stop();
decompIn.stopWriter();
client->close();
decompIn.clearWriteStop();
}
bool ClientClass::isOpen() {
return client->isOpen();
bool Client::isOpen() {
return sock && sock->isOpen();
}
void ClientClass::tcpHandler(int count, uint8_t* buf, void* ctx) {
ClientClass* _this = (ClientClass*)ctx;
// Read the rest of the data (TODO: CHECK SIZE OR SHIT WILL BE FUCKED)
int len = 0;
int read = 0;
int goal = _this->r_pkt_hdr->size - sizeof(PacketHeader);
while (len < goal) {
read = _this->client->read(goal - len, &buf[sizeof(PacketHeader) + len]);
if (read < 0) {
return;
};
len += read;
}
_this->bytes += _this->r_pkt_hdr->size;
if (_this->r_pkt_hdr->type == PACKET_TYPE_COMMAND) {
// TODO: Move to command handler
if (_this->r_cmd_hdr->cmd == COMMAND_SET_SAMPLERATE && _this->r_pkt_hdr->size == sizeof(PacketHeader) + sizeof(CommandHeader) + sizeof(double)) {
_this->currentSampleRate = *(double*)_this->r_cmd_data;
core::setInputSampleRate(_this->currentSampleRate);
void Client::worker() {
while (true) {
// Receive header
if (sock->recv(rbuffer, sizeof(PacketHeader), true) <= 0) {
break;
}
else if (_this->r_cmd_hdr->cmd == COMMAND_DISCONNECT) {
flog::error("Asked to disconnect by the server");
_this->serverBusy = true;
// Cancel waiters
// Receive remaining data
if (sock->recv(&rbuffer[sizeof(PacketHeader)], r_pkt_hdr->size - sizeof(PacketHeader), true, PROTOCOL_TIMEOUT_MS) <= 0) {
break;
}
// Increment data counter
bytes += r_pkt_hdr->size;
// Decode packet
if (r_pkt_hdr->type == PACKET_TYPE_COMMAND) {
// TODO: Move to command handler
if (r_cmd_hdr->cmd == COMMAND_SET_SAMPLERATE && r_pkt_hdr->size == sizeof(PacketHeader) + sizeof(CommandHeader) + sizeof(double)) {
currentSampleRate = *(double*)r_cmd_data;
core::setInputSampleRate(currentSampleRate);
}
else if (r_cmd_hdr->cmd == COMMAND_DISCONNECT) {
flog::error("Asked to disconnect by the server");
serverBusy = true;
// Cancel waiters
std::vector<PacketWaiter*> toBeRemoved;
for (auto& [waiter, cmd] : commandAckWaiters) {
waiter->cancel();
toBeRemoved.push_back(waiter);
}
// Remove handled waiters
for (auto& waiter : toBeRemoved) {
commandAckWaiters.erase(waiter);
delete waiter;
}
}
}
else if (r_pkt_hdr->type == PACKET_TYPE_COMMAND_ACK) {
// Notify waiters
std::vector<PacketWaiter*> toBeRemoved;
for (auto& [waiter, cmd] : _this->commandAckWaiters) {
waiter->cancel();
for (auto& [waiter, cmd] : commandAckWaiters) {
if (cmd != r_cmd_hdr->cmd) { continue; }
waiter->notify();
toBeRemoved.push_back(waiter);
}
// Remove handled waiters
for (auto& waiter : toBeRemoved) {
_this->commandAckWaiters.erase(waiter);
commandAckWaiters.erase(waiter);
delete waiter;
}
}
}
else if (_this->r_pkt_hdr->type == PACKET_TYPE_COMMAND_ACK) {
// Notify waiters
std::vector<PacketWaiter*> toBeRemoved;
for (auto& [waiter, cmd] : _this->commandAckWaiters) {
if (cmd != _this->r_cmd_hdr->cmd) { continue; }
waiter->notify();
toBeRemoved.push_back(waiter);
else if (r_pkt_hdr->type == PACKET_TYPE_BASEBAND) {
memcpy(decompIn.writeBuf, &rbuffer[sizeof(PacketHeader)], r_pkt_hdr->size - sizeof(PacketHeader));
if (!decompIn.swap(r_pkt_hdr->size - sizeof(PacketHeader))) { break; }
}
// Remove handled waiters
for (auto& waiter : toBeRemoved) {
_this->commandAckWaiters.erase(waiter);
delete waiter;
else if (r_pkt_hdr->type == PACKET_TYPE_BASEBAND_COMPRESSED) {
size_t outCount = ZSTD_decompressDCtx(dctx, decompIn.writeBuf, STREAM_BUFFER_SIZE, r_pkt_data, r_pkt_hdr->size - sizeof(PacketHeader));
if (outCount) {
if (!decompIn.swap(outCount)) { break; }
};
}
else if (r_pkt_hdr->type == PACKET_TYPE_ERROR) {
flog::error("SDR++ Server Error: {0}", rbuffer[sizeof(PacketHeader)]);
}
else {
flog::error("Invalid packet type: {0}", r_pkt_hdr->type);
}
}
else if (_this->r_pkt_hdr->type == PACKET_TYPE_BASEBAND) {
memcpy(_this->decompIn.writeBuf, &buf[sizeof(PacketHeader)], _this->r_pkt_hdr->size - sizeof(PacketHeader));
_this->decompIn.swap(_this->r_pkt_hdr->size - sizeof(PacketHeader));
}
else if (_this->r_pkt_hdr->type == PACKET_TYPE_BASEBAND_COMPRESSED) {
size_t outCount = ZSTD_decompressDCtx(_this->dctx, _this->decompIn.writeBuf, STREAM_BUFFER_SIZE, _this->r_pkt_data, _this->r_pkt_hdr->size - sizeof(PacketHeader));
if (outCount) { _this->decompIn.swap(outCount); };
}
else if (_this->r_pkt_hdr->type == PACKET_TYPE_ERROR) {
flog::error("SDR++ Server Error: {0}", buf[sizeof(PacketHeader)]);
}
else {
flog::error("Invalid packet type: {0}", _this->r_pkt_hdr->type);
}
// Restart an async read
_this->client->readAsync(sizeof(PacketHeader), _this->rbuffer, tcpHandler, _this);
}
int ClientClass::getUI() {
int Client::getUI() {
if (!isOpen()) { return -1; }
auto waiter = awaitCommandAck(COMMAND_GET_UI);
sendCommand(COMMAND_GET_UI, 0);
if (waiter->await(PROTOCOL_TIMEOUT_MS)) {
@ -233,37 +240,35 @@ namespace server {
return 0;
}
void ClientClass::sendPacket(PacketType type, int len) {
void Client::sendPacket(PacketType type, int len) {
s_pkt_hdr->type = type;
s_pkt_hdr->size = sizeof(PacketHeader) + len;
client->write(s_pkt_hdr->size, sbuffer);
sock->send(sbuffer, s_pkt_hdr->size);
}
void ClientClass::sendCommand(Command cmd, int len) {
void Client::sendCommand(Command cmd, int len) {
s_cmd_hdr->cmd = cmd;
sendPacket(PACKET_TYPE_COMMAND, sizeof(CommandHeader) + len);
}
void ClientClass::sendCommandAck(Command cmd, int len) {
void Client::sendCommandAck(Command cmd, int len) {
s_cmd_hdr->cmd = cmd;
sendPacket(PACKET_TYPE_COMMAND_ACK, sizeof(CommandHeader) + len);
}
PacketWaiter* ClientClass::awaitCommandAck(Command cmd) {
PacketWaiter* Client::awaitCommandAck(Command cmd) {
PacketWaiter* waiter = new PacketWaiter;
commandAckWaiters[waiter] = cmd;
return waiter;
}
void ClientClass::dHandler(dsp::complex_t *data, int count, void *ctx) {
ClientClass* _this = (ClientClass*)ctx;
void Client::dHandler(dsp::complex_t *data, int count, void *ctx) {
Client* _this = (Client*)ctx;
memcpy(_this->output->writeBuf, data, count * sizeof(dsp::complex_t));
_this->output->swap(count);
}
Client connect(std::string host, uint16_t port, dsp::stream<dsp::complex_t>* out) {
net::Conn conn = net::connect(host, port);
if (!conn) { return NULL; }
return Client(new ClientClass(std::move(conn), out));
std::shared_ptr<Client> connect(std::string host, uint16_t port, dsp::stream<dsp::complex_t>* out) {
return std::make_shared<Client>(net::connect(host, port), out);
}
}

Wyświetl plik

@ -1,5 +1,5 @@
#pragma once
#include <utils/networking.h>
#include <utils/net.h>
#include <dsp/stream.h>
#include <dsp/types.h>
#include <atomic>
@ -13,10 +13,6 @@
#include <dsp/routing/stream_link.h>
#include <zstd.h>
#define RFSPACE_MAX_SIZE 8192
#define RFSPACE_HEARTBEAT_INTERVAL_MS 1000
#define RFSPACE_TIMEOUT_MS 3000
#define PROTOCOL_TIMEOUT_MS 10000
namespace server {
@ -75,10 +71,10 @@ namespace server {
std::mutex handledMtx;
};
class ClientClass {
class Client {
public:
ClientClass(net::Conn conn, dsp::stream<dsp::complex_t>* out);
~ClientClass();
Client(std::shared_ptr<net::Socket> sock, dsp::stream<dsp::complex_t>* out);
~Client();
void showMenu();
@ -98,7 +94,7 @@ namespace server {
bool serverBusy = false;
private:
static void tcpHandler(int count, uint8_t* buf, void* ctx);
void worker();
int getUI();
@ -112,7 +108,7 @@ namespace server {
static void dHandler(dsp::complex_t *data, int count, void *ctx);
net::Conn client;
std::shared_ptr<net::Socket> sock;
dsp::stream<uint8_t> decompIn;
dsp::compression::SampleStreamDecompressor decomp;
@ -137,10 +133,10 @@ namespace server {
ZSTD_DCtx* dctx;
std::thread workerThread;
double currentSampleRate = 1000000.0;
};
typedef std::unique_ptr<ClientClass> Client;
Client connect(std::string host, uint16_t port, dsp::stream<dsp::complex_t>* out);
std::shared_ptr<Client> connect(std::string host, uint16_t port, dsp::stream<dsp::complex_t>* out);
}

Wyświetl plik

@ -54,7 +54,7 @@ void SpectranHTTPClient::setCenterFrequency(uint64_t freq) {
sprintf(buf, "{\"frequencyCenter\":%" PRIu64 ",\"frequencySpan\":%" PRIu64 ",\"type\":\"capture\"}", freq, _samplerate);
std::string data = buf;
char lenBuf[16];
sprintf(lenBuf, "%" PRIu64, data.size());
sprintf(lenBuf, "%" PRIu64, (uint64_t)data.size());
rqhdr.setField("Content-Length", lenBuf);
controlHttp.sendRequestHeader(rqhdr);
controlSock->sendstr(data);

Wyświetl plik

@ -283,8 +283,8 @@ private:
flog::info("Connected to server");
}
}
catch (std::exception e) {
flog::error("Could not connect to spyserver {0}", e.what());
catch (const std::exception& e) {
flog::error("Could not connect to spyserver {}", e.what());
}
}