mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2024-12-27 11:28:29 +01:00
Merge pull request #1310 from AlexandreRouma/master
New sink/stream system
This commit is contained in:
commit
84da183559
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -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.
|
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**
|
**Hardware**
|
||||||
|
5
.github/pull_request_template.md
vendored
5
.github/pull_request_template.md
vendored
@ -1,7 +1,4 @@
|
|||||||
# Important
|
# Important
|
||||||
|
|
||||||
Only minor bug fixes and bandplans are accepted.
|
Only bandplan, colormaps and themes are accepted. Code pull requests are **NOT welcome**.
|
||||||
|
|
||||||
Pull requests adding features or any bug fix that requires significant code changes will be automatically rejected.
|
|
||||||
|
|
||||||
Open an issue requesting a feature or discussing a possible bugfix instead.
|
Open an issue requesting a feature or discussing a possible bugfix instead.
|
95
.github/workflows/build_all.yml
vendored
95
.github/workflows/build_all.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
|||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Create Build Environment
|
- name: Create Build Environment
|
||||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||||
@ -34,7 +34,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Patch Pothos with earlier libusb version
|
- name: Patch Pothos with earlier libusb version
|
||||||
working-directory: ${{runner.workspace}}
|
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
|
- name: Download SDRPlay API
|
||||||
run: Invoke-WebRequest -Uri "https://drive.google.com/uc?id=12UHPMwkfa67A11QZDmpCT4iwHnyJHWuu&confirm=t" -OutFile ${{runner.workspace}}/SDRPlay.zip
|
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 .
|
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
|
- 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
|
- name: Prepare CMake
|
||||||
working-directory: ${{runner.workspace}}/build
|
working-directory: ${{runner.workspace}}/build
|
||||||
@ -79,16 +79,16 @@ jobs:
|
|||||||
run: '&($Env:GITHUB_WORKSPACE + "/make_windows_package.ps1") ./build ($Env:GITHUB_WORKSPACE + "/root")'
|
run: '&($Env:GITHUB_WORKSPACE + "/make_windows_package.ps1") ./build ($Env:GITHUB_WORKSPACE + "/root")'
|
||||||
|
|
||||||
- name: Save Archive
|
- name: Save Archive
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: sdrpp_windows_x64
|
name: sdrpp_windows_x64
|
||||||
path: ${{runner.workspace}}/sdrpp_windows_x64.zip
|
path: ${{runner.workspace}}/sdrpp_windows_x64.zip
|
||||||
|
|
||||||
build_macos:
|
build_macos:
|
||||||
runs-on: macos-11
|
runs-on: macos-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Create Build Environment
|
- name: Create Build Environment
|
||||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
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
|
run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf libbladerf codec2 zstd autoconf automake libtool && pip3 install mako
|
||||||
|
|
||||||
- name: Install volk
|
- 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
|
- 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
|
- 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
|
- 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
|
- 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
|
- 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 ..
|
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
|
- 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
|
- name: Prepare CMake
|
||||||
working-directory: ${{runner.workspace}}/build
|
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
|
- name: Build
|
||||||
working-directory: ${{runner.workspace}}/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
|
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
|
- name: Save Archive
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: sdrpp_macos_intel
|
name: sdrpp_macos_intel
|
||||||
path: ${{runner.workspace}}/sdrpp_macos_intel.zip
|
path: ${{runner.workspace}}/sdrpp_macos_intel.zip
|
||||||
@ -142,7 +142,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Create Docker Image
|
- name: Create Docker Image
|
||||||
run: cd $GITHUB_WORKSPACE/docker_builds/debian_buster && docker build . --tag sdrpp_build
|
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 ./
|
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||||
|
|
||||||
- name: Save Deb Archive
|
- name: Save Deb Archive
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: sdrpp_debian_buster_amd64
|
name: sdrpp_debian_buster_amd64
|
||||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||||
@ -164,7 +164,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Create Docker Image
|
- name: Create Docker Image
|
||||||
run: cd $GITHUB_WORKSPACE/docker_builds/debian_bullseye && docker build . --tag sdrpp_build
|
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 ./
|
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||||
|
|
||||||
- name: Save Deb Archive
|
- name: Save Deb Archive
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: sdrpp_debian_bullseye_amd64
|
name: sdrpp_debian_bullseye_amd64
|
||||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||||
@ -186,7 +186,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Create Docker Image
|
- name: Create Docker Image
|
||||||
run: cd $GITHUB_WORKSPACE/docker_builds/debian_bookworm && docker build . --tag sdrpp_build
|
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 ./
|
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||||
|
|
||||||
- name: Save Deb Archive
|
- name: Save Deb Archive
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: sdrpp_debian_bookworm_amd64
|
name: sdrpp_debian_bookworm_amd64
|
||||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||||
@ -208,7 +208,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Create Docker Image
|
- name: Create Docker Image
|
||||||
run: cd $GITHUB_WORKSPACE/docker_builds/debian_sid && docker build . --tag sdrpp_build
|
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 ./
|
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||||
|
|
||||||
- name: Save Deb Archive
|
- name: Save Deb Archive
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: sdrpp_debian_sid_amd64
|
name: sdrpp_debian_sid_amd64
|
||||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||||
@ -230,7 +230,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Create Docker Image
|
- name: Create Docker Image
|
||||||
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_focal && docker build . --tag sdrpp_build
|
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 ./
|
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||||
|
|
||||||
- name: Save Deb Archive
|
- name: Save Deb Archive
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: sdrpp_ubuntu_focal_amd64
|
name: sdrpp_ubuntu_focal_amd64
|
||||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||||
@ -252,7 +252,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Create Docker Image
|
- name: Create Docker Image
|
||||||
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_jammy && docker build . --tag sdrpp_build
|
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 ./
|
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||||
|
|
||||||
- name: Save Deb Archive
|
- name: Save Deb Archive
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: sdrpp_ubuntu_jammy_amd64
|
name: sdrpp_ubuntu_jammy_amd64
|
||||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
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:
|
build_raspios_bullseye_armhf:
|
||||||
runs-on: ARM
|
runs-on: ARM
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Create Build Environment
|
- name: Create Build Environment
|
||||||
run: rm -rf ${{runner.workspace}}/build && cmake -E make_directory ${{runner.workspace}}/build
|
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
|
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
|
- name: Save Deb Archive
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: sdrpp_raspios_bullseye_armhf
|
name: sdrpp_raspios_bullseye_armhf
|
||||||
path: ${{runner.workspace}}/sdrpp_debian_armhf.deb
|
path: ${{runner.workspace}}/sdrpp_debian_armhf.deb
|
||||||
@ -301,7 +323,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Fetch container
|
- name: Fetch container
|
||||||
working-directory: ${{runner.workspace}}
|
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
|
run: docker cp build:/root/SDRPlusPlus/android/app/build/outputs/apk/debug/app-debug.apk ./ && mv app-debug.apk sdrpp.apk
|
||||||
|
|
||||||
- name: Save APK
|
- name: Save APK
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: sdrpp_android
|
name: sdrpp_android
|
||||||
path: ${{runner.workspace}}/sdrpp.apk
|
path: ${{runner.workspace}}/sdrpp.apk
|
||||||
|
|
||||||
create_full_archive:
|
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
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Download All Builds
|
- name: Download All Builds
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v4
|
||||||
|
|
||||||
- name: Create Archive
|
- name: Create Archive
|
||||||
run: >
|
run: >
|
||||||
@ -343,10 +365,11 @@ jobs:
|
|||||||
mv sdrpp_debian_sid_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_sid_amd64.deb &&
|
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_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_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_raspios_bullseye_armhf/sdrpp_debian_armhf.deb sdrpp_all/sdrpp_raspios_bullseye_armhf.deb &&
|
||||||
mv sdrpp_android/sdrpp.apk sdrpp_all/sdrpp.apk
|
mv sdrpp_android/sdrpp.apk sdrpp_all/sdrpp.apk
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: sdrpp_all
|
name: sdrpp_all
|
||||||
path: sdrpp_all/
|
path: sdrpp_all/
|
||||||
@ -358,7 +381,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Download All Builds
|
- name: Download All Builds
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v4
|
||||||
|
|
||||||
- name: Update Nightly
|
- name: Update Nightly
|
||||||
run: gh release upload nightly sdrpp_all/* -R ${{github.repository}} --clobber
|
run: gh release upload nightly sdrpp_all/* -R ${{github.repository}} --clobber
|
||||||
@ -367,7 +390,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install codespell
|
- name: Install codespell
|
||||||
run: sudo apt update -y && sudo apt install -y codespell
|
run: sudo apt update -y && sudo apt install -y codespell
|
||||||
@ -379,7 +402,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Run check_clang_format
|
- name: Run check_clang_format
|
||||||
run: cd $GITHUB_WORKSPACE && chmod +x ./check_clang_format.sh && ./check_clang_format.sh || true
|
run: cd $GITHUB_WORKSPACE && chmod +x ./check_clang_format.sh && ./check_clang_format.sh || true
|
||||||
|
@ -1,65 +1,6 @@
|
|||||||
# Pull Requests
|
# Pull Requests
|
||||||
|
|
||||||
**I DO NOT ACCEPT PULL-REQUEST FOR FEATURES OR BUGFIXES REQUIRING SIGNIFICANT CODE/STRUCTURE CHANGES.**
|
Code pull requests are **NOT welcome**. Please open an issue discussing potential bugfixes or feature requests instead.
|
||||||
**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**
|
|
||||||
|
|
||||||
## Band Frequency Allocation
|
## 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.
|
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.
|
||||||
* Use VSCode for development, VS seems to cause issues.
|
|
||||||
* DO NOT use libboost for any code meant for this repository
|
**IMPORTANT: JSON File cannot contain comments, there are only in this example for clarity**
|
@ -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,
|
shift_register_t history_buffer_search(history_buffer *buf, const distance_t *distances,
|
||||||
unsigned int search_every) {
|
unsigned int search_every) {
|
||||||
shift_register_t bestpath;
|
shift_register_t bestpath = 0;
|
||||||
distance_t leasterror = USHRT_MAX;
|
distance_t leasterror = USHRT_MAX;
|
||||||
// search for a state with the least error
|
// search for a state with the least error
|
||||||
for (shift_register_t state = 0; state < buf->num_states; state += search_every) {
|
for (shift_register_t state = 0; state < buf->num_states; state += search_every) {
|
||||||
|
@ -88,7 +88,7 @@ int CommandArgsParser::parse(int argc, char* argv[]) {
|
|||||||
try {
|
try {
|
||||||
carg.ival = std::stoi(arg);
|
carg.ival = std::stoi(arg);
|
||||||
}
|
}
|
||||||
catch (std::exception e) {
|
catch (const std::exception& e) {
|
||||||
printf("Invalid argument, failed to parse integer\n");
|
printf("Invalid argument, failed to parse integer\n");
|
||||||
showHelp();
|
showHelp();
|
||||||
return -1;
|
return -1;
|
||||||
@ -98,7 +98,7 @@ int CommandArgsParser::parse(int argc, char* argv[]) {
|
|||||||
try {
|
try {
|
||||||
carg.fval = std::stod(arg);
|
carg.fval = std::stod(arg);
|
||||||
}
|
}
|
||||||
catch (std::exception e) {
|
catch (const std::exception& e) {
|
||||||
printf("Invalid argument, failed to parse float\n");
|
printf("Invalid argument, failed to parse float\n");
|
||||||
showHelp();
|
showHelp();
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -36,8 +36,8 @@ void ConfigManager::load(json def, bool lock) {
|
|||||||
file >> conf;
|
file >> conf;
|
||||||
file.close();
|
file.close();
|
||||||
}
|
}
|
||||||
catch (std::exception e) {
|
catch (const std::exception& e) {
|
||||||
flog::error("Config file '{0}' is corrupted, resetting it", path);
|
flog::error("Config file '{}' is corrupted, resetting it: {}", path, e.what());
|
||||||
conf = def;
|
conf = def;
|
||||||
save(false);
|
save(false);
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@ namespace dsp {
|
|||||||
void disableBlock(Processor<T, T>* block, Func onOutputChange) {
|
void disableBlock(Processor<T, T>* block, Func onOutputChange) {
|
||||||
// Check that the block is part of the chain
|
// Check that the block is part of the chain
|
||||||
if (!blockExists(block)) {
|
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
|
// If already disabled, don't do anything
|
||||||
@ -163,10 +163,12 @@ namespace dsp {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Processor<T, T>* blockBefore(Processor<T, T>* block) {
|
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) {
|
for (auto& ln : links) {
|
||||||
if (ln == block) { return NULL; }
|
if (ln == block) { return NULL; }
|
||||||
if (states[ln]) { return ln; }
|
if (states[ln]) { return ln; }
|
||||||
}
|
}
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
Processor<T, T>* blockAfter(Processor<T, T>* block) {
|
Processor<T, T>* blockAfter(Processor<T, T>* block) {
|
||||||
|
@ -49,6 +49,7 @@ namespace dsp::demod {
|
|||||||
audioFirTaps = taps::lowPass(15000.0, 4000.0, _samplerate);
|
audioFirTaps = taps::lowPass(15000.0, 4000.0, _samplerate);
|
||||||
alFir.init(NULL, audioFirTaps);
|
alFir.init(NULL, audioFirTaps);
|
||||||
arFir.init(NULL, audioFirTaps);
|
arFir.init(NULL, audioFirTaps);
|
||||||
|
xlator.init(NULL, -57000.0, samplerate);
|
||||||
rdsResamp.init(NULL, samplerate, 5000.0);
|
rdsResamp.init(NULL, samplerate, 5000.0);
|
||||||
|
|
||||||
lmr = buffer::alloc<float>(STREAM_BUFFER_SIZE);
|
lmr = buffer::alloc<float>(STREAM_BUFFER_SIZE);
|
||||||
@ -56,9 +57,9 @@ namespace dsp::demod {
|
|||||||
r = buffer::alloc<float>(STREAM_BUFFER_SIZE);
|
r = buffer::alloc<float>(STREAM_BUFFER_SIZE);
|
||||||
|
|
||||||
lprDelay.out.free();
|
lprDelay.out.free();
|
||||||
lmrDelay.out.free();
|
|
||||||
arFir.out.free();
|
arFir.out.free();
|
||||||
alFir.out.free();
|
alFir.out.free();
|
||||||
|
xlator.out.free();
|
||||||
rdsResamp.out.free();
|
rdsResamp.out.free();
|
||||||
|
|
||||||
base_type::init(in);
|
base_type::init(in);
|
||||||
@ -92,6 +93,7 @@ namespace dsp::demod {
|
|||||||
alFir.setTaps(audioFirTaps);
|
alFir.setTaps(audioFirTaps);
|
||||||
arFir.setTaps(audioFirTaps);
|
arFir.setTaps(audioFirTaps);
|
||||||
|
|
||||||
|
xlator.setOffset(-57000.0, samplerate);
|
||||||
rdsResamp.setInSamplerate(samplerate);
|
rdsResamp.setInSamplerate(samplerate);
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
@ -139,7 +141,7 @@ namespace dsp::demod {
|
|||||||
base_type::tempStart();
|
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
|
// Demodulate
|
||||||
demod.process(count, in, demod.out.writeBuf);
|
demod.process(count, in, demod.out.writeBuf);
|
||||||
if (_stereo) {
|
if (_stereo) {
|
||||||
@ -152,24 +154,24 @@ namespace dsp::demod {
|
|||||||
|
|
||||||
// Delay
|
// Delay
|
||||||
lprDelay.process(count, demod.out.writeBuf, demod.out.writeBuf);
|
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
|
// conjugate PLL output to down convert twice the L-R signal
|
||||||
math::Conjugate::process(count, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf);
|
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, lmrDelay.out.writeBuf, pilotPLL.out.writeBuf, lmrDelay.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);
|
||||||
|
|
||||||
// Do RDS demod
|
// Do RDS demod
|
||||||
if (_rdsOut) {
|
if (_rdsOut) {
|
||||||
// Since the PLL output is no longer needed after this, use it as the output
|
// Translate to 0Hz
|
||||||
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf);
|
xlator.process(count, rtoc.out.writeBuf, rtoc.out.writeBuf);
|
||||||
convert::ComplexToReal::process(count, pilotPLL.out.writeBuf, rdsout);
|
|
||||||
volk_32f_s32f_multiply_32f(rdsout, rdsout, 100.0, count);
|
// Resample to the output samplerate
|
||||||
rdsOutCount = rdsResamp.process(count, rdsout, rdsout);
|
rdsOutCount = rdsResamp.process(count, rtoc.out.writeBuf, rdsout);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert output back to real for further processing
|
// 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
|
// Amplify by 2x
|
||||||
volk_32f_s32f_multiply_32f(lmr, lmr, 2.0f, count);
|
volk_32f_s32f_multiply_32f(lmr, lmr, 2.0f, count);
|
||||||
@ -193,24 +195,11 @@ namespace dsp::demod {
|
|||||||
// Convert to complex
|
// Convert to complex
|
||||||
rtoc.process(count, demod.out.writeBuf, rtoc.out.writeBuf);
|
rtoc.process(count, demod.out.writeBuf, rtoc.out.writeBuf);
|
||||||
|
|
||||||
// Filter out pilot and run through PLL
|
// Translate to 0Hz
|
||||||
pilotFir.process(count, rtoc.out.writeBuf, pilotFir.out.writeBuf);
|
xlator.process(count, rtoc.out.writeBuf, rtoc.out.writeBuf);
|
||||||
pilotPLL.process(count, pilotFir.out.writeBuf, pilotPLL.out.writeBuf);
|
|
||||||
|
|
||||||
// Delay
|
// Resample to the output samplerate
|
||||||
lprDelay.process(count, demod.out.writeBuf, demod.out.writeBuf);
|
rdsOutCount = rdsResamp.process(count, rtoc.out.writeBuf, rdsout);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter if needed
|
// Filter if needed
|
||||||
@ -240,7 +229,7 @@ namespace dsp::demod {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
stream<float> rdsOut;
|
stream<complex_t> rdsOut;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
double _deviation;
|
double _deviation;
|
||||||
@ -253,13 +242,14 @@ namespace dsp::demod {
|
|||||||
tap<complex_t> pilotFirTaps;
|
tap<complex_t> pilotFirTaps;
|
||||||
filter::FIR<complex_t, complex_t> pilotFir;
|
filter::FIR<complex_t, complex_t> pilotFir;
|
||||||
convert::RealToComplex rtoc;
|
convert::RealToComplex rtoc;
|
||||||
|
channel::FrequencyXlator xlator;
|
||||||
loop::PLL pilotPLL;
|
loop::PLL pilotPLL;
|
||||||
math::Delay<float> lprDelay;
|
math::Delay<float> lprDelay;
|
||||||
math::Delay<complex_t> lmrDelay;
|
math::Delay<complex_t> lmrDelay;
|
||||||
tap<float> audioFirTaps;
|
tap<float> audioFirTaps;
|
||||||
filter::FIR<float, float> arFir;
|
filter::FIR<float, float> arFir;
|
||||||
filter::FIR<float, float> alFir;
|
filter::FIR<float, float> alFir;
|
||||||
multirate::RationalResampler<float> rdsResamp;
|
multirate::RationalResampler<dsp::complex_t> rdsResamp;
|
||||||
|
|
||||||
float* lmr;
|
float* lmr;
|
||||||
float* l;
|
float* l;
|
||||||
|
@ -110,7 +110,7 @@ namespace dsp::demod {
|
|||||||
else if (_mode == Mode::LSB) {
|
else if (_mode == Mode::LSB) {
|
||||||
return -_bandwidth / 2.0;
|
return -_bandwidth / 2.0;
|
||||||
}
|
}
|
||||||
else if (_mode == Mode::DSB) {
|
else {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,11 @@ namespace dsp::loop {
|
|||||||
if constexpr(CLAMP_PHASE) { clampPhase(); }
|
if constexpr(CLAMP_PHASE) { clampPhase(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void advancePhase() {
|
||||||
|
phase += freq;
|
||||||
|
if constexpr(CLAMP_PHASE) { clampPhase(); }
|
||||||
|
}
|
||||||
|
|
||||||
T freq;
|
T freq;
|
||||||
T phase;
|
T phase;
|
||||||
|
|
||||||
|
@ -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 {
|
namespace ImGui {
|
||||||
WaterFall::WaterFall() {
|
WaterFall::WaterFall() {
|
||||||
fftMin = -70.0;
|
fftMin = -70.0;
|
||||||
@ -586,7 +613,7 @@ namespace ImGui {
|
|||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
drawDataSize = (viewBandwidth / wholeBandwidth) * rawFFTSize;
|
drawDataSize = (viewBandwidth / wholeBandwidth) * rawFFTSize;
|
||||||
drawDataStart = (((double)rawFFTSize / 2.0) * (offsetRatio + 1)) - (drawDataSize / 2);
|
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++) {
|
for (int j = 0; j < dataWidth; j++) {
|
||||||
pixel = (std::clamp<float>(tempData[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange;
|
pixel = (std::clamp<float>(tempData[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange;
|
||||||
waterfallFb[(i * dataWidth) + j] = waterfallPallet[(int)(pixel * (WATERFALL_RESOLUTION - 1))];
|
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);
|
int drawDataStart = (((double)rawFFTSize / 2.0) * (offsetRatio + 1)) - (drawDataSize / 2);
|
||||||
|
|
||||||
if (waterfallVisible) {
|
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));
|
memmove(&waterfallFb[dataWidth], waterfallFb, dataWidth * (waterfallHeight - 1) * sizeof(uint32_t));
|
||||||
float pixel;
|
float pixel;
|
||||||
float dataRange = waterfallMax - waterfallMin;
|
float dataRange = waterfallMax - waterfallMin;
|
||||||
@ -879,7 +906,7 @@ namespace ImGui {
|
|||||||
waterfallUpdate = true;
|
waterfallUpdate = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
doZoom(drawDataStart, drawDataSize, dataWidth, rawFFTs, latestFFT);
|
doZoom(drawDataStart, drawDataSize, rawFFTSize, dataWidth, rawFFTs, latestFFT);
|
||||||
fftLines = 1;
|
fftLines = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,33 +90,6 @@ namespace ImGui {
|
|||||||
float* getFFTBuffer();
|
float* getFFTBuffer();
|
||||||
void pushFFT();
|
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 updatePallette(float colors[][3], int colorCount);
|
||||||
void updatePalletteFromArray(float* colors, int colorCount);
|
void updatePalletteFromArray(float* colors, int colorCount);
|
||||||
|
|
||||||
|
@ -86,14 +86,14 @@ namespace net {
|
|||||||
addr.sin_port = htons(port);
|
addr.sin_port = htons(port);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Address::getIPStr() {
|
std::string Address::getIPStr() const {
|
||||||
char buf[128];
|
char buf[128];
|
||||||
IP_t ip = getIP();
|
IP_t ip = getIP();
|
||||||
sprintf(buf, "%d.%d.%d.%d", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF);
|
sprintf(buf, "%d.%d.%d.%d", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF);
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
IP_t Address::getIP() {
|
IP_t Address::getIP() const {
|
||||||
return htonl(addr.sin_addr.s_addr);
|
return htonl(addr.sin_addr.s_addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,7 +101,7 @@ namespace net {
|
|||||||
addr.sin_addr.s_addr = htonl(ip);
|
addr.sin_addr.s_addr = htonl(ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
int Address::getPort() {
|
int Address::getPort() const {
|
||||||
return htons(addr.sin_port);
|
return htons(addr.sin_port);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,8 +160,8 @@ namespace net {
|
|||||||
|
|
||||||
// Set timeout
|
// Set timeout
|
||||||
timeval tv;
|
timeval tv;
|
||||||
tv.tv_sec = 0;
|
tv.tv_sec = timeout / 1000;
|
||||||
tv.tv_usec = timeout * 1000;
|
tv.tv_usec = (timeout - tv.tv_sec*1000) * 1000;
|
||||||
|
|
||||||
// Wait for data
|
// Wait for data
|
||||||
int err = select(sock+1, &set, NULL, &set, (timeout > 0) ? &tv : NULL);
|
int err = select(sock+1, &set, NULL, &set, (timeout > 0) ? &tv : NULL);
|
||||||
@ -225,8 +225,8 @@ namespace net {
|
|||||||
|
|
||||||
// Define timeout
|
// Define timeout
|
||||||
timeval tv;
|
timeval tv;
|
||||||
tv.tv_sec = 0;
|
tv.tv_sec = timeout / 1000;
|
||||||
tv.tv_usec = timeout * 1000;
|
tv.tv_usec = (timeout - tv.tv_sec*1000) * 1000;
|
||||||
|
|
||||||
// Wait for data or error
|
// Wait for data or error
|
||||||
if (timeout != NONBLOCKING) {
|
if (timeout != NONBLOCKING) {
|
||||||
@ -375,13 +375,25 @@ namespace net {
|
|||||||
return connect(Address(host, port));
|
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 library if needed
|
||||||
init();
|
init();
|
||||||
|
|
||||||
// Create socket
|
// Create socket
|
||||||
SockHandle_t s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
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
|
// Bind socket to local port
|
||||||
if (bind(s, (sockaddr*)&laddr.addr, sizeof(sockaddr_in))) {
|
if (bind(s, (sockaddr*)&laddr.addr, sizeof(sockaddr_in))) {
|
||||||
closeSocket(s);
|
closeSocket(s);
|
||||||
@ -393,15 +405,15 @@ namespace net {
|
|||||||
return std::make_shared<Socket>(s, &raddr);
|
return std::make_shared<Socket>(s, &raddr);
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
return openudp(Address(rhost, rport), laddr);
|
return openudp(Address(rhost, rport), laddr, allowBroadcast);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Socket> openudp(const Address& raddr, std::string lhost, int lport) {
|
std::shared_ptr<Socket> openudp(const Address& raddr, std::string lhost, int lport, bool allowBroadcast) {
|
||||||
return openudp(raddr, Address(lhost, lport));
|
return openudp(raddr, Address(lhost, lport), allowBroadcast);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Socket> openudp(std::string rhost, int rport, std::string lhost, int 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));
|
return openudp(Address(rhost, rport), Address(lhost, lport), allowBroadcast);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,13 +67,13 @@ namespace net {
|
|||||||
* Get the IP address.
|
* Get the IP address.
|
||||||
* @return IP address in standard string format.
|
* @return IP address in standard string format.
|
||||||
*/
|
*/
|
||||||
std::string getIPStr();
|
std::string getIPStr() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the IP address.
|
* Get the IP address.
|
||||||
* @return IP address in host byte order.
|
* @return IP address in host byte order.
|
||||||
*/
|
*/
|
||||||
IP_t getIP();
|
IP_t getIP() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the IP address.
|
* Set the IP address.
|
||||||
@ -85,7 +85,7 @@ namespace net {
|
|||||||
* Get the TCP/UDP port.
|
* Get the TCP/UDP port.
|
||||||
* @return TCP/UDP port number.
|
* @return TCP/UDP port number.
|
||||||
*/
|
*/
|
||||||
int getPort();
|
int getPort() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the TCP/UDP port.
|
* Set the TCP/UDP port.
|
||||||
@ -246,37 +246,37 @@ namespace net {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create UDP socket.
|
* 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.
|
* @param laddr Local address to bind the socket to.
|
||||||
* @return Socket instance on success, Throws runtime_error otherwise.
|
* @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.
|
* 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 rport Remote port.
|
||||||
* @param laddr Local address to bind the socket to.
|
* @param laddr Local address to bind the socket to.
|
||||||
* @return Socket instance on success, Throws runtime_error otherwise.
|
* @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.
|
* 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 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).
|
* @param lpost Local port used to bind the socket to (optional, 0 to allocate automatically).
|
||||||
* @return Socket instance on success, Throws runtime_error otherwise.
|
* @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.
|
* 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 rport Remote port.
|
||||||
* @param lhost Local hostname or IP used to bind the socket (optional, "0.0.0.0" for Any).
|
* @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).
|
* @param lpost Local port used to bind the socket to (optional, 0 to allocate automatically).
|
||||||
* @return Socket instance on success, Throws runtime_error otherwise.
|
* @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);
|
||||||
}
|
}
|
@ -320,7 +320,7 @@ namespace net {
|
|||||||
}
|
}
|
||||||
entry.handler(std::move(client), entry.ctx);
|
entry.handler(std::move(client), entry.ctx);
|
||||||
}
|
}
|
||||||
catch (std::exception e) {
|
catch (const std::exception& e) {
|
||||||
listening = false;
|
listening = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -258,6 +258,7 @@ namespace net::http {
|
|||||||
|
|
||||||
// Deserialize
|
// Deserialize
|
||||||
req.deserialize(respData);
|
req.deserialize(respData);
|
||||||
|
return 0; // Might wanna return size instead
|
||||||
}
|
}
|
||||||
|
|
||||||
int Client::sendResponseHeader(ResponseHeader& resp) {
|
int Client::sendResponseHeader(ResponseHeader& resp) {
|
||||||
@ -275,6 +276,7 @@ namespace net::http {
|
|||||||
|
|
||||||
// Deserialize
|
// Deserialize
|
||||||
resp.deserialize(respData);
|
resp.deserialize(respData);
|
||||||
|
return 0; // Might wanna return size instead
|
||||||
}
|
}
|
||||||
|
|
||||||
int Client::sendChunkHeader(ChunkHeader& chdr) {
|
int Client::sendChunkHeader(ChunkHeader& chdr) {
|
||||||
|
@ -7,6 +7,14 @@ namespace riff {
|
|||||||
const char* LIST_SIGNATURE = "LIST";
|
const char* LIST_SIGNATURE = "LIST";
|
||||||
const size_t RIFF_LABEL_SIZE = 4;
|
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]) {
|
bool Writer::open(std::string path, const char form[4]) {
|
||||||
std::lock_guard<std::recursive_mutex> lck(mtx);
|
std::lock_guard<std::recursive_mutex> lck(mtx);
|
||||||
|
|
||||||
|
@ -20,6 +20,10 @@ namespace riff {
|
|||||||
|
|
||||||
class Writer {
|
class Writer {
|
||||||
public:
|
public:
|
||||||
|
Writer() {}
|
||||||
|
// Writer(const Writer&& b);
|
||||||
|
~Writer();
|
||||||
|
|
||||||
bool open(std::string path, const char form[4]);
|
bool open(std::string path, const char form[4]);
|
||||||
bool isOpen();
|
bool isOpen();
|
||||||
void close();
|
void close();
|
||||||
@ -40,4 +44,23 @@ namespace riff {
|
|||||||
std::ofstream file;
|
std::ofstream file;
|
||||||
std::stack<ChunkDesc> chunks;
|
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;
|
||||||
|
// };
|
||||||
}
|
}
|
63
decoder_modules/atv_decoder/src/chroma_pll.h
Normal file
63
decoder_modules/atv_decoder/src/chroma_pll.h
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
239
decoder_modules/atv_decoder/src/chrominance_filter.h
Normal file
239
decoder_modules/atv_decoder/src/chrominance_filter.h
Normal file
@ -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)
|
193
decoder_modules/atv_decoder/src/linesync.h
Normal file
193
decoder_modules/atv_decoder/src/linesync.h
Normal file
@ -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;
|
||||||
|
};
|
@ -10,6 +10,15 @@
|
|||||||
|
|
||||||
#include <dsp/demod/quadrature.h>
|
#include <dsp/demod/quadrature.h>
|
||||||
#include <dsp/sink/handler_sink.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())
|
#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++",
|
/* Description: */ "ATV decoder for SDR++",
|
||||||
/* Author: */ "Ryzerth",
|
/* Author: */ "Ryzerth",
|
||||||
/* Version: */ 0, 1, 0,
|
/* Version: */ 0, 1, 0,
|
||||||
/* Max instances */ -1};
|
/* Max instances */ -1
|
||||||
|
};
|
||||||
|
|
||||||
#define SAMPLE_RATE (625.0f * 720.0f * 25.0f)
|
#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);
|
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);
|
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();
|
demod.start();
|
||||||
|
sync.start();
|
||||||
sink.start();
|
sink.start();
|
||||||
|
|
||||||
gui::menu.registerEntry(name, menuHandler, this, this);
|
gui::menu.registerEntry(name, menuHandler, this, this);
|
||||||
@ -47,9 +64,13 @@ class ATVDecoderModule : public ModuleManager::Instance {
|
|||||||
|
|
||||||
void postInit() {}
|
void postInit() {}
|
||||||
|
|
||||||
void enable() { enabled = true; }
|
void enable() {
|
||||||
|
enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
void disable() { enabled = false; }
|
void disable() {
|
||||||
|
enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
bool isEnabled() { return enabled; }
|
bool isEnabled() { return enabled; }
|
||||||
|
|
||||||
@ -61,6 +82,8 @@ class ATVDecoderModule : public ModuleManager::Instance {
|
|||||||
style::beginDisabled();
|
style::beginDisabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ideal width for testing: 750pixels
|
||||||
|
|
||||||
ImGui::FillWidth();
|
ImGui::FillWidth();
|
||||||
_this->img.draw();
|
_this->img.draw();
|
||||||
|
|
||||||
@ -76,6 +99,28 @@ class ATVDecoderModule : public ModuleManager::Instance {
|
|||||||
ImGui::FillWidth();
|
ImGui::FillWidth();
|
||||||
ImGui::SliderFloat("##spanLvl", &_this->spanLvl, 0, 1.0);
|
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) {
|
if (!_this->enabled) {
|
||||||
style::endDisabled();
|
style::endDisabled();
|
||||||
}
|
}
|
||||||
@ -84,70 +129,66 @@ class ATVDecoderModule : public ModuleManager::Instance {
|
|||||||
static void handler(float *data, int count, void *ctx) {
|
static void handler(float *data, int count, void *ctx) {
|
||||||
ATVDecoderModule *_this = (ATVDecoderModule *)ctx;
|
ATVDecoderModule *_this = (ATVDecoderModule *)ctx;
|
||||||
|
|
||||||
uint8_t *buf = (uint8_t *)_this->img.buffer;
|
// Convert line to complex
|
||||||
float val;
|
_this->r2c.process(720, data, _this->r2c.out.writeBuf);
|
||||||
float imval;
|
|
||||||
int pos = 0;
|
// 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++) {
|
for (int i = 0; i < count; i++) {
|
||||||
val = data[i];
|
//float imval = std::clamp<float>((data[i] - _this->minLvl) * 255.0 / _this->spanLvl, 0, 255);
|
||||||
// Sync
|
uint32_t re = std::clamp<float>((_this->pll.out.writeBuf[i].re - _this->minLvl) * 255.0 / _this->spanLvl, 0, 255);
|
||||||
if (val < _this->sync_level) {
|
uint32_t im = std::clamp<float>((_this->pll.out.writeBuf[i].im - _this->minLvl) * 255.0 / _this->spanLvl, 0, 255);
|
||||||
_this->sync_count++;
|
currentLine[i] = 0xFF000000 | (im << 8) | re;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
if (_this->sync_count >= 300) {
|
// Vertical scan logic
|
||||||
_this->short_sync = 0;
|
_this->ypos++;
|
||||||
}
|
bool rollover = _this->ypos >= 625;
|
||||||
else if (_this->sync_count >= 33) {
|
if (rollover) {
|
||||||
if (_this->short_sync == 5) {
|
{
|
||||||
_this->even_field = false;
|
std::lock_guard<std::mutex> lck(_this->evenFrameMtx);
|
||||||
_this->ypos = 0;
|
_this->evenFrame = !_this->evenFrame;
|
||||||
_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;
|
|
||||||
}
|
}
|
||||||
|
_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
|
// Save sync detection to history
|
||||||
imval = std::clamp<float>((val - _this->minLvl) * 255.0 / _this->spanLvl, 0, 255);
|
_this->syncHistory >>= 2;
|
||||||
if (_this->even_field) {
|
_this->syncHistory |= (((uint16_t)(sync1 < _this->sync_level)) << 9) | (((uint16_t)(sync0 < _this->sync_level)) << 8);
|
||||||
pos = ((720 * _this->ypos * 2) + _this->xpos) * 4;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
pos = ((720 * (_this->ypos * 2 + 1)) + _this->xpos) * 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf[pos] = imval;
|
// Trigger vsync in case one is detected
|
||||||
buf[pos + 1] = imval;
|
// TODO: Also sync with odd field
|
||||||
buf[pos + 2] = imval;
|
if (!rollover && _this->syncHistory == 0b0000011111) {
|
||||||
buf[pos + 3] = imval;
|
{
|
||||||
|
std::lock_guard<std::mutex> lck(_this->evenFrameMtx);
|
||||||
// Image logic
|
_this->evenFrame = !_this->evenFrame;
|
||||||
_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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
_this->ypos = 0;
|
||||||
|
_this->img.swap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,19 +197,27 @@ class ATVDecoderModule : public ModuleManager::Instance {
|
|||||||
|
|
||||||
VFOManager::VFO *vfo = NULL;
|
VFOManager::VFO *vfo = NULL;
|
||||||
dsp::demod::Quadrature demod;
|
dsp::demod::Quadrature demod;
|
||||||
|
LineSync sync;
|
||||||
dsp::sink::Handler<float> sink;
|
dsp::sink::Handler<float> sink;
|
||||||
|
dsp::convert::RealToComplex r2c;
|
||||||
int xpos = 0;
|
dsp::tap<dsp::complex_t> chromaTaps;
|
||||||
|
dsp::filter::FIR<dsp::complex_t, dsp::complex_t> fir;
|
||||||
|
dsp::loop::ChromaPLL pll;
|
||||||
int ypos = 0;
|
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 sync_count = 0;
|
||||||
int short_sync = 0;
|
int short_sync = 0;
|
||||||
|
|
||||||
float minLvl = 0.0f;
|
float minLvl = 0.0f;
|
||||||
float spanLvl = 1.0f;
|
float spanLvl = 1.0f;
|
||||||
|
|
||||||
|
bool lockedLines = 0;
|
||||||
|
uint16_t syncHistory = 0;
|
||||||
|
|
||||||
ImGui::ImageDisplay img;
|
ImGui::ImageDisplay img;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "../demod.h"
|
#include "../demod.h"
|
||||||
#include <dsp/demod/broadcast_fm.h>
|
#include <dsp/demod/broadcast_fm.h>
|
||||||
#include <dsp/clock_recovery/mm.h>
|
#include "../rds_demod.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 <gui/widgets/symbol_diagram.h>
|
#include <gui/widgets/symbol_diagram.h>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <rds.h>
|
#include <rds.h>
|
||||||
|
|
||||||
namespace demod {
|
namespace demod {
|
||||||
|
enum RDSRegion {
|
||||||
|
RDS_REGION_EUROPE,
|
||||||
|
RDS_REGION_NORTH_AMERICA
|
||||||
|
};
|
||||||
|
|
||||||
class WFM : public Demodulator {
|
class WFM : public Demodulator {
|
||||||
public:
|
public:
|
||||||
WFM() {}
|
WFM() : diag(0.5, 4096) {}
|
||||||
|
|
||||||
WFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) : diag(0.5, 4096) {
|
WFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) : diag(0.5, 4096) {
|
||||||
init(name, config, input, bandwidth, audioSR);
|
init(name, config, input, bandwidth, audioSR);
|
||||||
@ -29,10 +29,18 @@ namespace demod {
|
|||||||
this->name = name;
|
this->name = name;
|
||||||
_config = config;
|
_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.handler = fftRedraw;
|
||||||
fftRedrawHandler.ctx = this;
|
fftRedrawHandler.ctx = this;
|
||||||
gui::waterfall.onFFTRedraw.bindHandler(&fftRedrawHandler);
|
gui::waterfall.onFFTRedraw.bindHandler(&fftRedrawHandler);
|
||||||
|
|
||||||
|
// Default
|
||||||
|
std::string rdsRegionStr = "eu";
|
||||||
|
|
||||||
// Load config
|
// Load config
|
||||||
_config->acquire();
|
_config->acquire();
|
||||||
bool modified = false;
|
bool modified = false;
|
||||||
@ -45,33 +53,50 @@ namespace demod {
|
|||||||
if (config->conf[name][getName()].contains("rds")) {
|
if (config->conf[name][getName()].contains("rds")) {
|
||||||
_rds = config->conf[name][getName()]["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);
|
_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);
|
demod.init(input, bandwidth / 2.0f, getIFSampleRate(), _stereo, _lowPass, _rds);
|
||||||
recov.init(&demod.rdsOut, 5000.0 / 2375, omegaGain, muGain, 0.01);
|
rdsDemod.init(&demod.rdsOut, _rdsInfo);
|
||||||
slice.init(&recov.out);
|
hs.init(&rdsDemod.out, rdsHandler, this);
|
||||||
manch.init(&slice.out);
|
reshape.init(&rdsDemod.soft, 4096, (1187 / 30) - 4096);
|
||||||
diff.init(&manch.out, 2);
|
diagHandler.init(&reshape.out, _diagHandler, this);
|
||||||
hs.init(&diff.out, rdsHandler, this);
|
|
||||||
|
// Init RDS display
|
||||||
|
diag.lines.push_back(-0.8);
|
||||||
|
diag.lines.push_back(0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
demod.start();
|
demod.start();
|
||||||
recov.start();
|
rdsDemod.start();
|
||||||
slice.start();
|
|
||||||
manch.start();
|
|
||||||
diff.start();
|
|
||||||
hs.start();
|
hs.start();
|
||||||
|
reshape.start();
|
||||||
|
diagHandler.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
demod.stop();
|
demod.stop();
|
||||||
recov.stop();
|
rdsDemod.stop();
|
||||||
slice.stop();
|
|
||||||
manch.stop();
|
|
||||||
diff.stop();
|
|
||||||
hs.stop();
|
hs.stop();
|
||||||
|
reshape.stop();
|
||||||
|
diagHandler.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void showMenu() {
|
void showMenu() {
|
||||||
@ -94,14 +119,129 @@ namespace demod {
|
|||||||
_config->release(true);
|
_config->release(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (_rds) {
|
// TODO: This might break when the entire radio module is disabled
|
||||||
// if (rdsDecode.countryCodeValid()) { ImGui::Text("Country code: %d", rdsDecode.getCountryCode()); }
|
if (!_rds) { ImGui::BeginDisabled(); }
|
||||||
// if (rdsDecode.programCoverageValid()) { ImGui::Text("Program coverage: %d", rdsDecode.getProgramCoverage()); }
|
if (ImGui::Checkbox(("Advanced RDS Info##_radio_wfm_rds_info_" + name).c_str(), &_rdsInfo)) {
|
||||||
// if (rdsDecode.programRefNumberValid()) { ImGui::Text("Reference number: %d", rdsDecode.getProgramRefNumber()); }
|
setAdvancedRds(_rdsInfo);
|
||||||
// if (rdsDecode.programTypeValid()) { ImGui::Text("Program type: %d", rdsDecode.getProgramType()); }
|
_config->acquire();
|
||||||
// if (rdsDecode.PSNameValid()) { ImGui::Text("Program name: [%s]", rdsDecode.getPSName().c_str()); }
|
_config->conf[name][getName()]["rdsInfo"] = _rdsInfo;
|
||||||
// if (rdsDecode.radioTextValid()) { ImGui::Text("Radiotext: [%s]", rdsDecode.getRadioText().c_str()); }
|
_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) {
|
void setBandwidth(double bandwidth) {
|
||||||
@ -137,12 +277,24 @@ namespace demod {
|
|||||||
demod.setStereo(_stereo);
|
demod.setStereo(_stereo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setAdvancedRds(bool enabled) {
|
||||||
|
rdsDemod.setSoftEnabled(enabled);
|
||||||
|
_rdsInfo = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void rdsHandler(uint8_t* data, int count, void* ctx) {
|
static void rdsHandler(uint8_t* data, int count, void* ctx) {
|
||||||
WFM* _this = (WFM*)ctx;
|
WFM* _this = (WFM*)ctx;
|
||||||
_this->rdsDecode.process(data, count);
|
_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) {
|
static void fftRedraw(ImGui::WaterFall::FFTRedrawArgs args, void* ctx) {
|
||||||
WFM* _this = (WFM*)ctx;
|
WFM* _this = (WFM*)ctx;
|
||||||
if (!_this->_rds) { return; }
|
if (!_this->_rds) { return; }
|
||||||
@ -184,23 +336,31 @@ namespace demod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dsp::demod::BroadcastFM demod;
|
dsp::demod::BroadcastFM demod;
|
||||||
dsp::clock_recovery::FD recov;
|
RDSDemod rdsDemod;
|
||||||
dsp::digital::BinarySlicer slice;
|
|
||||||
dsp::digital::ManchesterDecoder manch;
|
|
||||||
dsp::digital::DifferentialDecoder diff;
|
|
||||||
dsp::sink::Handler<uint8_t> hs;
|
dsp::sink::Handler<uint8_t> hs;
|
||||||
EventHandler<ImGui::WaterFall::FFTRedrawArgs> fftRedrawHandler;
|
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;
|
ConfigManager* _config = NULL;
|
||||||
|
|
||||||
bool _stereo = false;
|
bool _stereo = false;
|
||||||
bool _lowPass = true;
|
bool _lowPass = true;
|
||||||
bool _rds = false;
|
bool _rds = false;
|
||||||
|
bool _rdsInfo = false;
|
||||||
float muGain = 0.01;
|
float muGain = 0.01;
|
||||||
float omegaGain = (0.01*0.01)/4.0;
|
float omegaGain = (0.01*0.01)/4.0;
|
||||||
|
|
||||||
|
int rdsRegionId = 0;
|
||||||
|
RDSRegion rdsRegion = RDS_REGION_EUROPE;
|
||||||
|
|
||||||
|
OptionList<std::string, RDSRegion> rdsRegions;
|
||||||
|
|
||||||
|
|
||||||
std::string name;
|
std::string name;
|
||||||
};
|
};
|
||||||
}
|
}
|
@ -3,6 +3,8 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <utils/flog.h>
|
||||||
|
|
||||||
namespace rds {
|
namespace rds {
|
||||||
std::map<uint16_t, BlockType> SYNDROMES = {
|
std::map<uint16_t, BlockType> SYNDROMES = {
|
||||||
{ 0b1111011000, BLOCK_TYPE_A },
|
{ 0b1111011000, BLOCK_TYPE_A },
|
||||||
@ -20,6 +22,98 @@ namespace rds {
|
|||||||
{ BLOCK_TYPE_D, 0b0110110100 }
|
{ 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
|
// 9876543210
|
||||||
const uint16_t LFSR_POLY = 0b0110111001;
|
const uint16_t LFSR_POLY = 0b0110111001;
|
||||||
const uint16_t IN_POLY = 0b1100011011;
|
const uint16_t IN_POLY = 0b1100011011;
|
||||||
@ -28,7 +122,7 @@ namespace rds {
|
|||||||
const int DATA_LEN = 16;
|
const int DATA_LEN = 16;
|
||||||
const int POLY_LEN = 10;
|
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++) {
|
for (int i = 0; i < count; i++) {
|
||||||
// Shift in the bit
|
// Shift in the bit
|
||||||
shiftReg = ((shiftReg << 1) & 0x3FFFFFF) | (symbols[i] & 1);
|
shiftReg = ((shiftReg << 1) & 0x3FFFFFF) | (symbols[i] & 1);
|
||||||
@ -54,18 +148,26 @@ namespace rds {
|
|||||||
type = (BlockType)((lastType + 1) % _BLOCK_TYPE_COUNT);
|
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]);
|
blocks[type] = correctErrors(shiftReg, type, blockAvail[type]);
|
||||||
|
|
||||||
// Update continous group count
|
// If block type is A, decode it directly, otherwise, update continous count
|
||||||
if (type == BLOCK_TYPE_A) { contGroup = 1; }
|
if (type == BLOCK_TYPE_A) {
|
||||||
else if (type == BLOCK_TYPE_B && lastType == BLOCK_TYPE_A) { contGroup++; }
|
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_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 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 we've got an entire group, process it
|
||||||
if (contGroup >= 4) {
|
if (contGroup >= 3) {
|
||||||
contGroup = 0;
|
contGroup = 0;
|
||||||
decodeGroup();
|
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;
|
uint16_t syn = 0;
|
||||||
|
|
||||||
// Calculate the syndrome using a LFSR
|
// Calculate the syndrome using a LFSR
|
||||||
@ -95,7 +197,7 @@ namespace rds {
|
|||||||
return syn;
|
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
|
// Subtract the offset from block
|
||||||
block ^= (uint32_t)OFFSETS[type];
|
block ^= (uint32_t)OFFSETS[type];
|
||||||
uint32_t out = block;
|
uint32_t out = block;
|
||||||
@ -124,96 +226,259 @@ namespace rds {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RDSDecoder::decodeGroup() {
|
void Decoder::decodeBlockA() {
|
||||||
std::lock_guard<std::mutex> lck(groupMtx);
|
// Acquire lock
|
||||||
auto now = std::chrono::high_resolution_clock::now();
|
std::lock_guard<std::mutex> lck(blockAMtx);
|
||||||
anyGroupLastUpdate = now;
|
|
||||||
|
|
||||||
// Make sure blocks A and B are available
|
// If it didn't decode properly return
|
||||||
if (!blockAvail[BLOCK_TYPE_A] || !blockAvail[BLOCK_TYPE_B]) { return; }
|
if (!blockAvail[BLOCK_TYPE_A]) { return; }
|
||||||
|
|
||||||
// Decode PI code
|
// Decode PI code
|
||||||
|
piCode = (blocks[BLOCK_TYPE_A] >> 10) & 0xFFFF;
|
||||||
countryCode = (blocks[BLOCK_TYPE_A] >> 22) & 0xF;
|
countryCode = (blocks[BLOCK_TYPE_A] >> 22) & 0xF;
|
||||||
programCoverage = (AreaCoverage)((blocks[BLOCK_TYPE_A] >> 18) & 0xF);
|
programCoverage = (AreaCoverage)((blocks[BLOCK_TYPE_A] >> 18) & 0xF);
|
||||||
programRefNumber = (blocks[BLOCK_TYPE_A] >> 10) & 0xFF;
|
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
|
// Decode group type and version
|
||||||
uint8_t groupType = (blocks[BLOCK_TYPE_B] >> 22) & 0xF;
|
groupType = (blocks[BLOCK_TYPE_B] >> 22) & 0xF;
|
||||||
GroupVersion groupVer = (GroupVersion)((blocks[BLOCK_TYPE_B] >> 21) & 1);
|
groupVer = (GroupVersion)((blocks[BLOCK_TYPE_B] >> 21) & 1);
|
||||||
|
|
||||||
// Decode traffic program and program type
|
// Decode traffic program and program type
|
||||||
trafficProgram = (blocks[BLOCK_TYPE_B] >> 20) & 1;
|
trafficProgram = (blocks[BLOCK_TYPE_B] >> 20) & 1;
|
||||||
programType = (ProgramType)((blocks[BLOCK_TYPE_B] >> 15) & 0x1F);
|
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]) {
|
// Update timeout
|
||||||
alternateFrequency = (blocks[BLOCK_TYPE_C] >> 10) & 0xFFFF;
|
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]) {
|
if (blockAvail[BLOCK_TYPE_D]) {
|
||||||
programServiceName[psOffset] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
|
radioText[rtOffset + 2] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
|
||||||
programServiceName[psOffset + 1] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
|
radioText[rtOffset + 3] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (groupType == 2) {
|
else {
|
||||||
group2LastUpdate = now;
|
uint8_t rtOffset = offset * 2;
|
||||||
// Get char offset and write chars in the Radiotext
|
if (blockAvail[BLOCK_TYPE_D]) {
|
||||||
bool nAB = (blocks[BLOCK_TYPE_B] >> 14) & 1;
|
radioText[rtOffset] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
|
||||||
uint8_t offset = (blocks[BLOCK_TYPE_B] >> 10) & 0xF;
|
radioText[rtOffset + 1] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
|
||||||
|
|
||||||
// Clear text field if the A/B flag changed
|
|
||||||
if (nAB != rtAB) {
|
|
||||||
radioText = " ";
|
|
||||||
}
|
}
|
||||||
rtAB = nAB;
|
}
|
||||||
|
|
||||||
// Write char at offset in Radiotext
|
// Update timeout
|
||||||
if (groupVer == GROUP_VER_A) {
|
group2LastUpdate = std::chrono::high_resolution_clock::now();
|
||||||
uint8_t rtOffset = offset * 4;
|
}
|
||||||
if (blockAvail[BLOCK_TYPE_C]) {
|
|
||||||
radioText[rtOffset] = (blocks[BLOCK_TYPE_C] >> 18) & 0xFF;
|
void Decoder::decodeGroup10() {
|
||||||
radioText[rtOffset + 1] = (blocks[BLOCK_TYPE_C] >> 10) & 0xFF;
|
// Acquire lock
|
||||||
}
|
std::lock_guard<std::mutex> lck(group10Mtx);
|
||||||
if (blockAvail[BLOCK_TYPE_D]) {
|
|
||||||
radioText[rtOffset + 2] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
|
// Check if the text needs to be cleared
|
||||||
radioText[rtOffset + 3] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
else {
|
||||||
uint8_t rtOffset = offset * 2;
|
return "Not Assigned";
|
||||||
if (blockAvail[BLOCK_TYPE_D]) {
|
|
||||||
radioText[rtOffset] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
|
|
||||||
radioText[rtOffset + 1] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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();
|
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();
|
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();
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,6 +4,12 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <mutex>
|
#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 {
|
namespace rds {
|
||||||
enum BlockType {
|
enum BlockType {
|
||||||
BLOCK_TYPE_A,
|
BLOCK_TYPE_A,
|
||||||
@ -20,22 +26,42 @@ namespace rds {
|
|||||||
};
|
};
|
||||||
|
|
||||||
enum AreaCoverage {
|
enum AreaCoverage {
|
||||||
AREA_COVERAGE_LOCAL,
|
AREA_COVERAGE_INVALID = -1,
|
||||||
AREA_COVERAGE_INTERNATIONAL,
|
AREA_COVERAGE_LOCAL = 0,
|
||||||
AREA_COVERAGE_NATIONAL,
|
AREA_COVERAGE_INTERNATIONAL = 1,
|
||||||
AREA_COVERAGE_SUPRA_NATIONAL,
|
AREA_COVERAGE_NATIONAL = 2,
|
||||||
AREA_COVERAGE_REGIONAL1,
|
AREA_COVERAGE_SUPRA_NATIONAL = 3,
|
||||||
AREA_COVERAGE_REGIONAL2,
|
AREA_COVERAGE_REGIONAL1 = 4,
|
||||||
AREA_COVERAGE_REGIONAL3,
|
AREA_COVERAGE_REGIONAL2 = 5,
|
||||||
AREA_COVERAGE_REGIONAL4,
|
AREA_COVERAGE_REGIONAL3 = 6,
|
||||||
AREA_COVERAGE_REGIONAL5,
|
AREA_COVERAGE_REGIONAL4 = 7,
|
||||||
AREA_COVERAGE_REGIONAL6,
|
AREA_COVERAGE_REGIONAL5 = 8,
|
||||||
AREA_COVERAGE_REGIONAL7,
|
AREA_COVERAGE_REGIONAL6 = 9,
|
||||||
AREA_COVERAGE_REGIONAL8,
|
AREA_COVERAGE_REGIONAL7 = 10,
|
||||||
AREA_COVERAGE_REGIONAL9,
|
AREA_COVERAGE_REGIONAL8 = 11,
|
||||||
AREA_COVERAGE_REGIONAL10,
|
AREA_COVERAGE_REGIONAL9 = 12,
|
||||||
AREA_COVERAGE_REGIONAL11,
|
AREA_COVERAGE_REGIONAL10 = 13,
|
||||||
AREA_COVERAGE_REGIONAL12
|
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 {
|
enum ProgramType {
|
||||||
@ -108,6 +134,76 @@ namespace rds {
|
|||||||
PROGRAM_TYPE_EU_ALARM = 31
|
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 {
|
enum DecoderIdentification {
|
||||||
DECODER_IDENT_STEREO = (1 << 0),
|
DECODER_IDENT_STEREO = (1 << 0),
|
||||||
DECODER_IDENT_ARTIFICIAL_HEAD = (1 << 1),
|
DECODER_IDENT_ARTIFICIAL_HEAD = (1 << 1),
|
||||||
@ -115,35 +211,49 @@ namespace rds {
|
|||||||
DECODER_IDENT_VARIABLE_PTY = (1 << 0)
|
DECODER_IDENT_VARIABLE_PTY = (1 << 0)
|
||||||
};
|
};
|
||||||
|
|
||||||
class RDSDecoder {
|
class Decoder {
|
||||||
public:
|
public:
|
||||||
void process(uint8_t* symbols, int count);
|
void process(uint8_t* symbols, int count);
|
||||||
|
|
||||||
bool countryCodeValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); }
|
bool piCodeValid() { std::lock_guard<std::mutex> lck(blockAMtx); return blockAValid(); }
|
||||||
uint8_t getCountryCode() { std::lock_guard<std::mutex> lck(groupMtx); return countryCode; }
|
uint16_t getPICode() { std::lock_guard<std::mutex> lck(blockAMtx); return piCode; }
|
||||||
bool programCoverageValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); }
|
uint8_t getCountryCode() { std::lock_guard<std::mutex> lck(blockAMtx); return countryCode; }
|
||||||
uint8_t getProgramCoverage() { std::lock_guard<std::mutex> lck(groupMtx); return programCoverage; }
|
uint8_t getProgramCoverage() { std::lock_guard<std::mutex> lck(blockAMtx); return programCoverage; }
|
||||||
bool programRefNumberValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); }
|
uint8_t getProgramRefNumber() { std::lock_guard<std::mutex> lck(blockAMtx); return programRefNumber; }
|
||||||
uint8_t getProgramRefNumber() { std::lock_guard<std::mutex> lck(groupMtx); return programRefNumber; }
|
std::string getCallsign() { std::lock_guard<std::mutex> lck(blockAMtx); return callsign; }
|
||||||
bool programTypeValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); }
|
|
||||||
ProgramType getProgramType() { std::lock_guard<std::mutex> lck(groupMtx); return programType; }
|
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 musicValid() { std::lock_guard<std::mutex> lck(group0Mtx); return group0Valid(); }
|
||||||
bool getMusic() { std::lock_guard<std::mutex> lck(groupMtx); return music; }
|
bool getMusic() { std::lock_guard<std::mutex> lck(group0Mtx); return music; }
|
||||||
bool PSNameValid() { std::lock_guard<std::mutex> lck(groupMtx); return group0Valid(); }
|
bool PSNameValid() { std::lock_guard<std::mutex> lck(group0Mtx); return group0Valid(); }
|
||||||
std::string getPSName() { std::lock_guard<std::mutex> lck(groupMtx); return programServiceName; }
|
std::string getPSName() { std::lock_guard<std::mutex> lck(group0Mtx); return programServiceName; }
|
||||||
|
|
||||||
bool radioTextValid() { std::lock_guard<std::mutex> lck(groupMtx); return group2Valid(); }
|
bool radioTextValid() { std::lock_guard<std::mutex> lck(group2Mtx); return group2Valid(); }
|
||||||
std::string getRadioText() { std::lock_guard<std::mutex> lck(groupMtx); return radioText; }
|
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:
|
private:
|
||||||
static uint16_t calcSyndrome(uint32_t block);
|
static uint16_t calcSyndrome(uint32_t block);
|
||||||
static uint32_t correctErrors(uint32_t block, BlockType type, bool& recovered);
|
static uint32_t correctErrors(uint32_t block, BlockType type, bool& recovered);
|
||||||
|
void decodeBlockA();
|
||||||
|
void decodeBlockB();
|
||||||
|
void decodeGroup0();
|
||||||
|
void decodeGroup2();
|
||||||
|
void decodeGroup10();
|
||||||
void decodeGroup();
|
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 group0Valid();
|
||||||
bool group2Valid();
|
bool group2Valid();
|
||||||
|
bool group10Valid();
|
||||||
|
|
||||||
// State machine
|
// State machine
|
||||||
uint32_t shiftReg = 0;
|
uint32_t shiftReg = 0;
|
||||||
@ -154,17 +264,26 @@ namespace rds {
|
|||||||
uint32_t blocks[_BLOCK_TYPE_COUNT];
|
uint32_t blocks[_BLOCK_TYPE_COUNT];
|
||||||
bool blockAvail[_BLOCK_TYPE_COUNT];
|
bool blockAvail[_BLOCK_TYPE_COUNT];
|
||||||
|
|
||||||
// All groups
|
// Block A (All groups)
|
||||||
std::mutex groupMtx;
|
std::mutex blockAMtx;
|
||||||
std::chrono::time_point<std::chrono::high_resolution_clock> anyGroupLastUpdate;
|
std::chrono::time_point<std::chrono::high_resolution_clock> blockALastUpdate{}; // 1970-01-01
|
||||||
|
uint16_t piCode;
|
||||||
uint8_t countryCode;
|
uint8_t countryCode;
|
||||||
AreaCoverage programCoverage;
|
AreaCoverage programCoverage;
|
||||||
uint8_t programRefNumber;
|
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;
|
bool trafficProgram;
|
||||||
ProgramType programType;
|
ProgramType programType;
|
||||||
|
|
||||||
// Group type 0
|
// 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 trafficAnnouncement;
|
||||||
bool music;
|
bool music;
|
||||||
uint8_t decoderIdent;
|
uint8_t decoderIdent;
|
||||||
@ -172,9 +291,16 @@ namespace rds {
|
|||||||
std::string programServiceName = " ";
|
std::string programServiceName = " ";
|
||||||
|
|
||||||
// Group type 2
|
// 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;
|
bool rtAB = false;
|
||||||
std::string radioText = " ";
|
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 = " ";
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
102
decoder_modules/radio/src/rds_demod.h
Normal file
102
decoder_modules/radio/src/rds_demod.h
Normal file
@ -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;
|
||||||
|
};
|
@ -9,10 +9,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev
|
|||||||
libcodec2-dev autoconf libtool xxd
|
libcodec2-dev autoconf libtool xxd
|
||||||
|
|
||||||
# Install SDRPlay libraries
|
# Install SDRPlay libraries
|
||||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run
|
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.12.1.run
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.07.1.run
|
7z x ./SDRplay_RSP_API-Linux-3.12.1.run
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.07.1
|
7z x ./SDRplay_RSP_API-Linux-3.12.1
|
||||||
cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so
|
cp x86_64/libsdrplay_api.so.3.12 /usr/lib/libsdrplay_api.so
|
||||||
cp inc/* /usr/include/
|
cp inc/* /usr/include/
|
||||||
|
|
||||||
# Install libperseus
|
# Install libperseus
|
||||||
|
@ -9,10 +9,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev
|
|||||||
libcodec2-dev autoconf libtool xxd
|
libcodec2-dev autoconf libtool xxd
|
||||||
|
|
||||||
# Install SDRPlay libraries
|
# Install SDRPlay libraries
|
||||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run
|
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.12.1.run
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.07.1.run
|
7z x ./SDRplay_RSP_API-Linux-3.12.1.run
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.07.1
|
7z x ./SDRplay_RSP_API-Linux-3.12.1
|
||||||
cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so
|
cp x86_64/libsdrplay_api.so.3.12 /usr/lib/libsdrplay_api.so
|
||||||
cp inc/* /usr/include/
|
cp inc/* /usr/include/
|
||||||
|
|
||||||
# Install libperseus
|
# Install libperseus
|
||||||
|
@ -9,10 +9,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk1-dev
|
|||||||
libcodec2-dev autoconf libtool xxd
|
libcodec2-dev autoconf libtool xxd
|
||||||
|
|
||||||
# Install SDRPlay libraries
|
# Install SDRPlay libraries
|
||||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run
|
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.12.1.run
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.07.1.run
|
7z x ./SDRplay_RSP_API-Linux-3.12.1.run
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.07.1
|
7z x ./SDRplay_RSP_API-Linux-3.12.1
|
||||||
cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so
|
cp x86_64/libsdrplay_api.so.3.12 /usr/lib/libsdrplay_api.so
|
||||||
cp inc/* /usr/include/
|
cp inc/* /usr/include/
|
||||||
|
|
||||||
# Install libperseus
|
# Install libperseus
|
||||||
|
@ -9,10 +9,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk-dev l
|
|||||||
libcodec2-dev autoconf libtool xxd
|
libcodec2-dev autoconf libtool xxd
|
||||||
|
|
||||||
# Install SDRPlay libraries
|
# Install SDRPlay libraries
|
||||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run
|
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.12.1.run
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.07.1.run
|
7z x ./SDRplay_RSP_API-Linux-3.12.1.run
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.07.1
|
7z x ./SDRplay_RSP_API-Linux-3.12.1
|
||||||
cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so
|
cp x86_64/libsdrplay_api.so.3.12 /usr/lib/libsdrplay_api.so
|
||||||
cp inc/* /usr/include/
|
cp inc/* /usr/include/
|
||||||
|
|
||||||
# Install libperseus
|
# Install libperseus
|
||||||
|
@ -15,10 +15,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk1-dev
|
|||||||
libcodec2-dev libudev-dev autoconf libtool xxd
|
libcodec2-dev libudev-dev autoconf libtool xxd
|
||||||
|
|
||||||
# Install SDRPlay libraries
|
# Install SDRPlay libraries
|
||||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run
|
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.12.1.run
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.07.1.run
|
7z x ./SDRplay_RSP_API-Linux-3.12.1.run
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.07.1
|
7z x ./SDRplay_RSP_API-Linux-3.12.1
|
||||||
cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so
|
cp x86_64/libsdrplay_api.so.3.12 /usr/lib/libsdrplay_api.so
|
||||||
cp inc/* /usr/include/
|
cp inc/* /usr/include/
|
||||||
|
|
||||||
# Install a more recent libusb version
|
# Install a more recent libusb version
|
||||||
|
@ -9,10 +9,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev
|
|||||||
libcodec2-dev autoconf libtool xxd
|
libcodec2-dev autoconf libtool xxd
|
||||||
|
|
||||||
# Install SDRPlay libraries
|
# Install SDRPlay libraries
|
||||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run
|
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.12.1.run
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.07.1.run
|
7z x ./SDRplay_RSP_API-Linux-3.12.1.run
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.07.1
|
7z x ./SDRplay_RSP_API-Linux-3.12.1
|
||||||
cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so
|
cp x86_64/libsdrplay_api.so.3.12 /usr/lib/libsdrplay_api.so
|
||||||
cp inc/* /usr/include/
|
cp inc/* /usr/include/
|
||||||
|
|
||||||
# Install libperseus
|
# Install libperseus
|
||||||
|
@ -9,10 +9,10 @@ apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev
|
|||||||
libcodec2-dev autoconf libtool xxd
|
libcodec2-dev autoconf libtool xxd
|
||||||
|
|
||||||
# Install SDRPlay libraries
|
# Install SDRPlay libraries
|
||||||
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run
|
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.12.1.run
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.07.1.run
|
7z x ./SDRplay_RSP_API-Linux-3.12.1.run
|
||||||
7z x ./SDRplay_RSP_API-Linux-3.07.1
|
7z x ./SDRplay_RSP_API-Linux-3.12.1
|
||||||
cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so
|
cp x86_64/libsdrplay_api.so.3.12 /usr/lib/libsdrplay_api.so
|
||||||
cp inc/* /usr/include/
|
cp inc/* /usr/include/
|
||||||
|
|
||||||
# Install libperseus
|
# Install libperseus
|
||||||
|
4
docker_builds/ubuntu_mantic/Dockerfile
Normal file
4
docker_builds/ubuntu_mantic/Dockerfile
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
FROM ubuntu:mantic
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
COPY do_build.sh /root
|
||||||
|
RUN chmod +x /root/do_build.sh
|
35
docker_builds/ubuntu_mantic/do_build.sh
Normal file
35
docker_builds/ubuntu_mantic/do_build.sh
Normal file
@ -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'
|
@ -531,7 +531,7 @@ private:
|
|||||||
ImGui::TableSetColumnIndex(0);
|
ImGui::TableSetColumnIndex(0);
|
||||||
if (ImGui::Button(("Import##_freq_mgr_imp_" + _this->name).c_str(), ImVec2(ImGui::GetContentRegionAvail().x, 0)) && !_this->importOpen) {
|
if (ImGui::Button(("Import##_freq_mgr_imp_" + _this->name).c_str(), ImVec2(ImGui::GetContentRegionAvail().x, 0)) && !_this->importOpen) {
|
||||||
_this->importOpen = true;
|
_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);
|
ImGui::TableSetColumnIndex(1);
|
||||||
@ -544,7 +544,7 @@ private:
|
|||||||
}
|
}
|
||||||
config.release();
|
config.release();
|
||||||
_this->exportOpen = true;
|
_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(); }
|
if (selectedNames.size() == 0 && _this->selectedListName != "") { style::endDisabled(); }
|
||||||
ImGui::EndTable();
|
ImGui::EndTable();
|
||||||
@ -787,7 +787,7 @@ private:
|
|||||||
|
|
||||||
void exportBookmarks(std::string path) {
|
void exportBookmarks(std::string path) {
|
||||||
std::ofstream fs(path);
|
std::ofstream fs(path);
|
||||||
exportedBookmarks >> fs;
|
fs << exportedBookmarks;
|
||||||
fs.close();
|
fs.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,8 +80,8 @@ public:
|
|||||||
try {
|
try {
|
||||||
client = net::rigctl::connect(host, port);
|
client = net::rigctl::connect(host, port);
|
||||||
}
|
}
|
||||||
catch (std::exception e) {
|
catch (const std::exception& e) {
|
||||||
flog::error("Could not connect: {0}", e.what());
|
flog::error("Could not connect: {}", e.what());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,8 +200,8 @@ private:
|
|||||||
listener = net::listen(hostname, port);
|
listener = net::listen(hostname, port);
|
||||||
listener->acceptAsync(clientHandler, this);
|
listener->acceptAsync(clientHandler, this);
|
||||||
}
|
}
|
||||||
catch (std::exception e) {
|
catch (const std::exception& e) {
|
||||||
flog::error("Could not start rigctl server: {0}", e.what());
|
flog::error("Could not start rigctl server: {}", e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ SDR++ is a cross-platform and open source SDR software with the aim of being blo
|
|||||||
* Multi VFO
|
* Multi VFO
|
||||||
* Wide hardware support (both through SoapySDR and dedicated modules)
|
* Wide hardware support (both through SoapySDR and dedicated modules)
|
||||||
* SIMD accelerated DSP
|
* 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
|
* Full waterfall update when possible. Makes browsing signals easier and more pleasant
|
||||||
* Modular design (easily write your own plugins)
|
* 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:
|
Then, run:
|
||||||
|
|
||||||
```sh
|
```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
|
sudo dpkg -i sdrpp_debian_amd64.deb
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -103,6 +103,24 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"start": 283500,
|
"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,
|
"end": 526500,
|
||||||
"type": "aviation",
|
"type": "aviation",
|
||||||
"name": "Aeronautical Radionavigation / Maritime"
|
"name": "Aeronautical Radionavigation / Maritime"
|
||||||
@ -284,7 +302,7 @@
|
|||||||
{
|
{
|
||||||
"start": 3754500,
|
"start": 3754500,
|
||||||
"end": 3757500,
|
"end": 3757500,
|
||||||
"type": "amateur1",
|
"type": "utility",
|
||||||
"name": "The Pip (Night)"
|
"name": "The Pip (Night)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -729,7 +747,7 @@
|
|||||||
"start": 5330500,
|
"start": 5330500,
|
||||||
"end": 5333400,
|
"end": 5333400,
|
||||||
"type": "amateur",
|
"type": "amateur",
|
||||||
"name": "|60m Ham Band Ch. 1 (Not NL)"
|
"name": "60m Ham Band Ch. 1 (Not NL)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"start": 5333400,
|
"start": 5333400,
|
||||||
@ -744,19 +762,31 @@
|
|||||||
"name": "Ch. 2 (60m) (Not NL)"
|
"name": "Ch. 2 (60m) (Not NL)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"start": 5351500,
|
"start": 5349400,
|
||||||
"end": 5366500,
|
"end": 5351500,
|
||||||
"type": "amateur1",
|
"type": "amateur1",
|
||||||
"name": "60m Ham Band NL"
|
"name": "60m Ham Band (Not NL)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"start": 5357000,
|
"start": 5351500,
|
||||||
"end": 5359900,
|
"end": 5354000,
|
||||||
"type": "amateur",
|
"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,
|
"end": 5371500,
|
||||||
"type": "amateur1",
|
"type": "amateur1",
|
||||||
"name": "60m Ham Band (Not NL)"
|
"name": "60m Ham Band (Not NL)"
|
||||||
@ -777,7 +807,7 @@
|
|||||||
"start": 5403500,
|
"start": 5403500,
|
||||||
"end": 5406400,
|
"end": 5406400,
|
||||||
"type": "amateur",
|
"type": "amateur",
|
||||||
"name": "Ch. 5 60m Ham Band (Not NL)|"
|
"name": "Ch. 5 60m Ham Band (Not NL)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"start": 5406400,
|
"start": 5406400,
|
||||||
@ -3047,17 +3077,11 @@
|
|||||||
"type": "utility",
|
"type": "utility",
|
||||||
"name": "Radio Astronomy"
|
"name": "Radio Astronomy"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"start": 2300000000,
|
|
||||||
"end": 2310000000,
|
|
||||||
"type": "amateur",
|
|
||||||
"name": "|13cm Ham Band"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"start": 2320000000,
|
"start": 2320000000,
|
||||||
"end": 2332500000,
|
"end": 2332500000,
|
||||||
"type": "satellite",
|
"type": "amateur",
|
||||||
"name": "Satellite Digital Audio Radio Service (SDARS)"
|
"name": "|13cm Ham Band Satellite Digital Audio Radio Service (SDARS)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"start": 2332500000,
|
"start": 2332500000,
|
||||||
@ -3067,21 +3091,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"start": 2345000000,
|
"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,
|
"end": 2393750000,
|
||||||
"type": "amateur",
|
"type": "amateur",
|
||||||
"name": "13cm Ham Band (Upper)| Analog and Digital"
|
"name": "13cm Ham Band Analog and Digital"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"start": 2393750000,
|
"start": 2393750000,
|
||||||
@ -3093,25 +3105,25 @@
|
|||||||
"start": 2394750000,
|
"start": 2394750000,
|
||||||
"end": 2400000000,
|
"end": 2400000000,
|
||||||
"type": "amateur",
|
"type": "amateur",
|
||||||
"name": "Analog and Digital 13 cm HAM Band|"
|
"name": "Analog and Digital 13cm HAM Band"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"start": 2400000000,
|
"start": 2400000000,
|
||||||
"end": 2401000000,
|
"end": 2401000000,
|
||||||
"type": "utility",
|
"type": "utility",
|
||||||
"name": "Shared Satellite and 13cm ISM"
|
"name": "Shared Satellite, 13cm ISM and Ham"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"start": 2401000000,
|
"start": 2401000000,
|
||||||
"end": 2410000000,
|
"end": 2410000000,
|
||||||
"type": "utility",
|
"type": "utility",
|
||||||
"name": "WiFi shared with Satellite and 13cm ISM"
|
"name": "WiFi shared with Satellite, 13cm ISM and Ham"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"start": 2410000000,
|
"start": 2410000000,
|
||||||
"end": 2411900000,
|
"end": 2411900000,
|
||||||
"type": "utility",
|
"type": "utility",
|
||||||
"name": "WiFi shared with Broadband Modes and 13cm ISM"
|
"name": "WiFi shared with Broadband Modes, 13cm ISM and Ham"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"start": 2411900000,
|
"start": 2411900000,
|
||||||
@ -3122,8 +3134,8 @@
|
|||||||
{
|
{
|
||||||
"start": 2412100000,
|
"start": 2412100000,
|
||||||
"end": 2450000000,
|
"end": 2450000000,
|
||||||
"type": "utility",
|
"type": "amateur",
|
||||||
"name": "WiFi shared with Broadband Modes 13cm ISM |13cm Ham Band"
|
"name": "WiFi shared with Broadband Modes 13cm ISM, 13cm Ham Band|"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"start": 2450000000,
|
"start": 2450000000,
|
||||||
@ -3172,6 +3184,366 @@
|
|||||||
"end": 2500000000,
|
"end": 2500000000,
|
||||||
"type": "utility",
|
"type": "utility",
|
||||||
"name": "ISM Band (13cm)"
|
"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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -35,6 +35,10 @@ public:
|
|||||||
monoPacker.init(&s2m.out, 512);
|
monoPacker.init(&s2m.out, 512);
|
||||||
stereoPacker.init(stream, 512);
|
stereoPacker.init(stream, 512);
|
||||||
|
|
||||||
|
#if RTAUDIO_VERSION_MAJOR >= 6
|
||||||
|
audio.setErrorCallback(&errorCallback);
|
||||||
|
#endif
|
||||||
|
|
||||||
#if RTAUDIO_VERSION_MAJOR >= 6
|
#if RTAUDIO_VERSION_MAJOR >= 6
|
||||||
audio.setErrorCallback(&errorCallback);
|
audio.setErrorCallback(&errorCallback);
|
||||||
#endif
|
#endif
|
||||||
@ -56,16 +60,23 @@ public:
|
|||||||
// config.release(modified);
|
// config.release(modified);
|
||||||
|
|
||||||
RtAudio::DeviceInfo info;
|
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++) {
|
for (int i = 0; i < count; i++) {
|
||||||
|
#endif
|
||||||
try {
|
try {
|
||||||
info = audio.getDeviceInfo(i);
|
info = audio.getDeviceInfo(i);
|
||||||
|
#if !defined(RTAUDIO_VERSION_MAJOR) || RTAUDIO_VERSION_MAJOR < 6
|
||||||
if (!info.probed) { continue; }
|
if (!info.probed) { continue; }
|
||||||
|
#endif
|
||||||
if (info.outputChannels == 0) { continue; }
|
if (info.outputChannels == 0) { continue; }
|
||||||
if (info.isDefaultOutput) { defaultDevId = devList.size(); }
|
if (info.isDefaultOutput) { defaultDevId = devList.size(); }
|
||||||
devList.define(i, info.name, info);
|
devList.define(i, info.name, info);
|
||||||
}
|
}
|
||||||
catch (std::exception e) {
|
catch (const std::exception& e) {
|
||||||
flog::error("AudioSinkModule Error getting audio device info: {0}", e.what());
|
flog::error("AudioSinkModule Error getting audio device ({}) info: {}", i, e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,6 +202,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:
|
private:
|
||||||
bool doStart() {
|
bool doStart() {
|
||||||
RtAudio::StreamParameters parameters;
|
RtAudio::StreamParameters parameters;
|
||||||
@ -207,8 +234,8 @@ private:
|
|||||||
audio.startStream();
|
audio.startStream();
|
||||||
stereoPacker.start();
|
stereoPacker.start();
|
||||||
}
|
}
|
||||||
catch (RtAudioError& e) {
|
catch (const std::exception& e) {
|
||||||
flog::error("Could not open audio device");
|
flog::error("Could not open audio device {0}", e.what());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,10 +141,10 @@ public:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (std::exception e) {
|
catch (const std::exception& e) {
|
||||||
char buf[1024];
|
char buf[1024];
|
||||||
sprintf(buf, "%016" PRIX64, serial);
|
sprintf(buf, "%016" PRIX64, serial);
|
||||||
flog::error("Could not open Airspy {0}", buf);
|
flog::error("Could not open Airspy {}", buf);
|
||||||
}
|
}
|
||||||
selectedSerial = serial;
|
selectedSerial = serial;
|
||||||
|
|
||||||
|
@ -144,10 +144,10 @@ public:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (std::exception e) {
|
catch (const std::exception& e) {
|
||||||
char buf[1024];
|
char buf[1024];
|
||||||
sprintf(buf, "%016" PRIX64, serial);
|
sprintf(buf, "%016" PRIX64, serial);
|
||||||
flog::error("Could not open Airspy HF+ {0}", buf);
|
flog::error("Could not open Airspy HF+ {}", buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedSerial = serial;
|
selectedSerial = serial;
|
||||||
|
@ -35,6 +35,10 @@ public:
|
|||||||
AudioSourceModule(std::string name) {
|
AudioSourceModule(std::string name) {
|
||||||
this->name = name;
|
this->name = name;
|
||||||
|
|
||||||
|
#if RTAUDIO_VERSION_MAJOR >= 6
|
||||||
|
audio.setErrorCallback(&errorCallback);
|
||||||
|
#endif
|
||||||
|
|
||||||
sampleRate = 48000.0;
|
sampleRate = 48000.0;
|
||||||
|
|
||||||
handler.ctx = this;
|
handler.ctx = this;
|
||||||
@ -83,21 +87,28 @@ public:
|
|||||||
void refresh() {
|
void refresh() {
|
||||||
devices.clear();
|
devices.clear();
|
||||||
|
|
||||||
|
#if RTAUDIO_VERSION_MAJOR >= 6
|
||||||
|
for (int i : audio.getDeviceIds()) {
|
||||||
|
#else
|
||||||
int count = audio.getDeviceCount();
|
int count = audio.getDeviceCount();
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
|
#endif
|
||||||
try {
|
try {
|
||||||
// Get info
|
// Get info
|
||||||
auto info = audio.getDeviceInfo(i);
|
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
|
// Check that it has a stereo input
|
||||||
if (info.probed && info.inputChannels < 2) { continue; }
|
if (info.inputChannels < 2) { continue; }
|
||||||
|
|
||||||
// Save info
|
// Save info
|
||||||
DeviceInfo dinfo = { info, i };
|
DeviceInfo dinfo = { info, i };
|
||||||
devices.define(info.name, info.name, dinfo);
|
devices.define(info.name, info.name, dinfo);
|
||||||
}
|
}
|
||||||
catch (std::exception e) {
|
catch (const std::exception& e) {
|
||||||
flog::error("Error getting audio device info: {0}", e.what());
|
flog::error("Error getting audio device ({}) info: {}", i, e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -189,11 +200,11 @@ private:
|
|||||||
_this->audio.startStream();
|
_this->audio.startStream();
|
||||||
_this->running = true;
|
_this->running = true;
|
||||||
}
|
}
|
||||||
catch (std::exception e) {
|
catch (const std::exception& e) {
|
||||||
flog::error("Error opening audio device: {0}", e.what());
|
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) {
|
static void stop(void* ctx) {
|
||||||
@ -254,6 +265,22 @@ private:
|
|||||||
return 0;
|
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;
|
std::string name;
|
||||||
bool enabled = true;
|
bool enabled = true;
|
||||||
dsp::stream<dsp::complex_t> stream;
|
dsp::stream<dsp::complex_t> stream;
|
||||||
@ -290,4 +317,4 @@ MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) {
|
|||||||
MOD_EXPORT void _END_() {
|
MOD_EXPORT void _END_() {
|
||||||
config.disableAutoSave();
|
config.disableAutoSave();
|
||||||
config.save();
|
config.save();
|
||||||
}
|
}
|
||||||
|
@ -347,7 +347,7 @@ private:
|
|||||||
static void start(void* ctx) {
|
static void start(void* ctx) {
|
||||||
BladeRFSourceModule* _this = (BladeRFSourceModule*)ctx;
|
BladeRFSourceModule* _this = (BladeRFSourceModule*)ctx;
|
||||||
if (_this->running) { return; }
|
if (_this->running) { return; }
|
||||||
if (_this->devCount == 0) { return; }
|
if (_this->devCount <= 0) { return; }
|
||||||
|
|
||||||
// Open device
|
// Open device
|
||||||
bladerf_devinfo info = _this->devInfoList[_this->devId];
|
bladerf_devinfo info = _this->devInfoList[_this->devId];
|
||||||
|
@ -139,8 +139,8 @@ private:
|
|||||||
//gui::freqSelect.maxFreq = _this->centerFreq + (_this->sampleRate/2);
|
//gui::freqSelect.maxFreq = _this->centerFreq + (_this->sampleRate/2);
|
||||||
//gui::freqSelect.limitFreq = true;
|
//gui::freqSelect.limitFreq = true;
|
||||||
}
|
}
|
||||||
catch (std::exception& e) {
|
catch (const std::exception& e) {
|
||||||
flog::error("Error: {0}", e.what());
|
flog::error("Error: {}", e.what());
|
||||||
}
|
}
|
||||||
config.acquire();
|
config.acquire();
|
||||||
config.conf["path"] = _this->fileSelect.path;
|
config.conf["path"] = _this->fileSelect.path;
|
||||||
|
@ -137,6 +137,10 @@ public:
|
|||||||
hackrf_device_list_t* _devList = hackrf_device_list();
|
hackrf_device_list_t* _devList = hackrf_device_list();
|
||||||
|
|
||||||
for (int i = 0; i < _devList->devicecount; i++) {
|
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]);
|
devList.push_back(_devList->serial_numbers[i]);
|
||||||
devListTxt += (char*)(_devList->serial_numbers[i] + 16);
|
devListTxt += (char*)(_devList->serial_numbers[i] + 16);
|
||||||
devListTxt += '\0';
|
devListTxt += '\0';
|
||||||
|
@ -2,6 +2,13 @@
|
|||||||
#include <utils/flog.h>
|
#include <utils/flog.h>
|
||||||
|
|
||||||
namespace hermes {
|
namespace hermes {
|
||||||
|
const int SAMPLERATE_LIST[] = {
|
||||||
|
48000,
|
||||||
|
96000,
|
||||||
|
192000,
|
||||||
|
384000
|
||||||
|
};
|
||||||
|
|
||||||
Client::Client(std::shared_ptr<net::Socket> sock) {
|
Client::Client(std::shared_ptr<net::Socket> sock) {
|
||||||
this->sock = sock;
|
this->sock = sock;
|
||||||
|
|
||||||
@ -33,6 +40,7 @@ namespace hermes {
|
|||||||
|
|
||||||
void Client::setSamplerate(HermesLiteSamplerate samplerate) {
|
void Client::setSamplerate(HermesLiteSamplerate samplerate) {
|
||||||
writeReg(0, (uint32_t)samplerate << 24);
|
writeReg(0, (uint32_t)samplerate << 24);
|
||||||
|
blockSize = SAMPLERATE_LIST[samplerate] / 200;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::setFrequency(double freq) {
|
void Client::setFrequency(double freq) {
|
||||||
@ -157,12 +165,15 @@ namespace hermes {
|
|||||||
void Client::worker() {
|
void Client::worker() {
|
||||||
uint8_t rbuf[2048];
|
uint8_t rbuf[2048];
|
||||||
MetisUSBPacket* pkt = (MetisUSBPacket*)rbuf;
|
MetisUSBPacket* pkt = (MetisUSBPacket*)rbuf;
|
||||||
|
int sampleCount = 0;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
// Wait for a packet or exit if connection closed
|
// Wait for a packet or exit if connection closed
|
||||||
int len = sock->recv(rbuf, 2048);
|
int len = sock->recv(rbuf, 2048);
|
||||||
if (len <= 0) { break; }
|
if (len <= 0) { break; }
|
||||||
|
|
||||||
// Ignore anything that's not a USB packet
|
// 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) {
|
if (htons(pkt->hdr.signature) != HERMES_METIS_SIGNATURE || pkt->hdr.type != METIS_PKT_USB) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -183,9 +194,10 @@ namespace hermes {
|
|||||||
flog::warn("Got response! Reg={0}, Seq={1}", reg, (uint32_t)htonl(pkt->seq));
|
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];
|
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
|
// 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 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];
|
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;
|
sq = (sq << 8) >> 8;
|
||||||
|
|
||||||
// Convert to float (IQ swapped for some reason)
|
// Convert to float (IQ swapped for some reason)
|
||||||
out.writeBuf[i].im = (float)si / (float)0x1000000;
|
writeBuf[i].im = (float)si / (float)0x1000000;
|
||||||
out.writeBuf[i].re = (float)sq / (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() {
|
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?
|
// 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("0.0.0.0", 1024);
|
auto sock = net::openudp("255.255.255.255", 1024, "0.0.0.0", 0, true);
|
||||||
|
|
||||||
// Build discovery packet
|
// Build discovery packet
|
||||||
uint8_t discoveryPkt[64];
|
uint8_t discoveryPkt[64];
|
||||||
@ -225,6 +242,7 @@ namespace hermes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Await all responses
|
||||||
std::vector<Info> devices;
|
std::vector<Info> devices;
|
||||||
while (true) {
|
while (true) {
|
||||||
// Wait for a response
|
// Wait for a response
|
||||||
@ -258,7 +276,9 @@ namespace hermes {
|
|||||||
|
|
||||||
devices.push_back(info);
|
devices.push_back(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close broadcast socket
|
||||||
|
sock->close();
|
||||||
|
|
||||||
return devices;
|
return devices;
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,12 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#define HERMES_METIS_REPEAT 5
|
#define HERMES_METIS_REPEAT 5
|
||||||
#define HERMES_METIS_TIMEOUT 1000
|
#define HERMES_METIS_TIMEOUT 1000
|
||||||
#define HERMES_METIS_SIGNATURE 0xEFFE
|
#define HERMES_METIS_SIGNATURE 0xEFFE
|
||||||
#define HERMES_HPSDR_USB_SYNC 0x7F
|
#define HERMES_HPSDR_USB_SYNC 0x7F
|
||||||
#define HERMES_I2C_DELAY 50
|
#define HERMES_I2C_DELAY 50
|
||||||
|
#define HERMES_SAMPLES_PER_FRAME 63
|
||||||
|
|
||||||
namespace hermes {
|
namespace hermes {
|
||||||
enum MetisPacketType {
|
enum MetisPacketType {
|
||||||
@ -140,7 +141,7 @@ namespace hermes {
|
|||||||
|
|
||||||
dsp::stream<dsp::complex_t> out;
|
dsp::stream<dsp::complex_t> out;
|
||||||
|
|
||||||
//private:
|
private:
|
||||||
void sendMetisUSB(uint8_t endpoint, void* frame0, void* frame1 = NULL);
|
void sendMetisUSB(uint8_t endpoint, void* frame0, void* frame1 = NULL);
|
||||||
void sendMetisControl(MetisControl ctrl);
|
void sendMetisControl(MetisControl ctrl);
|
||||||
|
|
||||||
@ -149,12 +150,12 @@ namespace hermes {
|
|||||||
|
|
||||||
void writeI2C(I2CPort port, uint8_t addr, uint8_t reg, uint8_t data);
|
void writeI2C(I2CPort port, uint8_t addr, uint8_t reg, uint8_t data);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void worker();
|
void worker();
|
||||||
|
|
||||||
double freq = 0;
|
double freq = 0;
|
||||||
|
|
||||||
|
int blockSize = 63;
|
||||||
|
|
||||||
std::thread workerThread;
|
std::thread workerThread;
|
||||||
std::shared_ptr<net::Socket> sock;
|
std::shared_ptr<net::Socket> sock;
|
||||||
uint32_t usbSeq = 0;
|
uint32_t usbSeq = 0;
|
||||||
|
@ -17,7 +17,7 @@ SDRPP_MOD_INFO{
|
|||||||
/* Name: */ "hermes_source",
|
/* Name: */ "hermes_source",
|
||||||
/* Description: */ "Hermes Lite 2 source module for SDR++",
|
/* Description: */ "Hermes Lite 2 source module for SDR++",
|
||||||
/* Author: */ "Ryzerth",
|
/* Author: */ "Ryzerth",
|
||||||
/* Version: */ 0, 1, 0,
|
/* Version: */ 0, 1, 1,
|
||||||
/* Max instances */ 1
|
/* Max instances */ 1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -7,7 +7,9 @@
|
|||||||
#include <gui/smgui.h>
|
#include <gui/smgui.h>
|
||||||
#include <iio.h>
|
#include <iio.h>
|
||||||
#include <ad9361.h>
|
#include <ad9361.h>
|
||||||
|
#include <utils/optionlist.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <regex>
|
||||||
|
|
||||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||||
|
|
||||||
@ -15,16 +17,10 @@ SDRPP_MOD_INFO{
|
|||||||
/* Name: */ "plutosdr_source",
|
/* Name: */ "plutosdr_source",
|
||||||
/* Description: */ "PlutoSDR source module for SDR++",
|
/* Description: */ "PlutoSDR source module for SDR++",
|
||||||
/* Author: */ "Ryzerth",
|
/* Author: */ "Ryzerth",
|
||||||
/* Version: */ 0, 1, 0,
|
/* Version: */ 0, 2, 0,
|
||||||
/* Max instances */ 1
|
/* Max instances */ 1
|
||||||
};
|
};
|
||||||
|
|
||||||
const char* gainModes[] = {
|
|
||||||
"manual", "fast_attack", "slow_attack", "hybrid"
|
|
||||||
};
|
|
||||||
|
|
||||||
const char* gainModesTxt = "Manual\0Fast Attack\0Slow Attack\0Hybrid\0";
|
|
||||||
|
|
||||||
ConfigManager config;
|
ConfigManager config;
|
||||||
|
|
||||||
class PlutoSDRSourceModule : public ModuleManager::Instance {
|
class PlutoSDRSourceModule : public ModuleManager::Instance {
|
||||||
@ -32,34 +28,34 @@ public:
|
|||||||
PlutoSDRSourceModule(std::string name) {
|
PlutoSDRSourceModule(std::string name) {
|
||||||
this->name = 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();
|
config.acquire();
|
||||||
std::string _ip = config.conf["IP"];
|
devDesc = config.conf["device"];
|
||||||
strcpy(&ip[3], _ip.c_str());
|
|
||||||
sampleRate = config.conf["sampleRate"];
|
|
||||||
gainMode = config.conf["gainMode"];
|
|
||||||
gain = config.conf["gain"];
|
|
||||||
config.release();
|
config.release();
|
||||||
|
select(devDesc);
|
||||||
|
|
||||||
// Generate the samplerate list and find srId
|
// Register source
|
||||||
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];
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.ctx = this;
|
handler.ctx = this;
|
||||||
handler.selectHandler = menuSelected;
|
handler.selectHandler = menuSelected;
|
||||||
handler.deselectHandler = menuDeselected;
|
handler.deselectHandler = menuDeselected;
|
||||||
@ -105,9 +101,157 @@ private:
|
|||||||
return std::string(buf);
|
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) {
|
static void menuSelected(void* ctx) {
|
||||||
PlutoSDRSourceModule* _this = (PlutoSDRSourceModule*)ctx;
|
PlutoSDRSourceModule* _this = (PlutoSDRSourceModule*)ctx;
|
||||||
core::setInputSampleRate(_this->sampleRate);
|
core::setInputSampleRate(_this->samplerate);
|
||||||
flog::info("PlutoSDRSourceModule '{0}': Menu Select!", _this->name);
|
flog::info("PlutoSDRSourceModule '{0}': Menu Select!", _this->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,12 +264,17 @@ private:
|
|||||||
PlutoSDRSourceModule* _this = (PlutoSDRSourceModule*)ctx;
|
PlutoSDRSourceModule* _this = (PlutoSDRSourceModule*)ctx;
|
||||||
if (_this->running) { return; }
|
if (_this->running) { return; }
|
||||||
|
|
||||||
// TODO: INIT CONTEXT HERE
|
// If no device is selected, give up
|
||||||
_this->ctx = iio_create_context_from_uri(_this->ip);
|
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) {
|
if (_this->ctx == NULL) {
|
||||||
flog::error("Could not open pluto");
|
flog::error("Could not open pluto ({})", _this->uri);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get phy and device handle
|
||||||
_this->phy = iio_context_find_device(_this->ctx, "ad9361-phy");
|
_this->phy = iio_context_find_device(_this->ctx, "ad9361-phy");
|
||||||
if (_this->phy == NULL) {
|
if (_this->phy == NULL) {
|
||||||
flog::error("Could not connect to pluto phy");
|
flog::error("Could not connect to pluto phy");
|
||||||
@ -139,17 +288,27 @@ private:
|
|||||||
return;
|
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, "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");
|
// Configure RX channel
|
||||||
iio_channel_attr_write_longlong(iio_device_find_channel(_this->phy, "altvoltage0", true), "frequency", round(_this->freq)); // Freq
|
iio_channel_attr_write(_this->rxChan, "rf_port_select", "A_BALANCED");
|
||||||
iio_channel_attr_write_longlong(iio_device_find_channel(_this->phy, "voltage0", false), "sampling_frequency", round(_this->sampleRate)); // Sample rate
|
iio_channel_attr_write_longlong(_this->rxLO, "frequency", round(_this->freq)); // Freq
|
||||||
iio_channel_attr_write(iio_device_find_channel(_this->phy, "voltage0", false), "gain_control_mode", gainModes[_this->gainMode]); // manual gain
|
iio_channel_attr_write_bool(_this->rxChan, "filter_fir_en", true); // Digital filter
|
||||||
iio_channel_attr_write_longlong(iio_device_find_channel(_this->phy, "voltage0", false), "hardwaregain", round(_this->gain)); // gain
|
iio_channel_attr_write_longlong(_this->rxChan, "sampling_frequency", round(_this->samplerate)); // Sample rate
|
||||||
ad9361_set_bb_rate(_this->phy, round(_this->sampleRate));
|
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->running = true;
|
||||||
_this->workerThread = std::thread(worker, _this);
|
_this->workerThread = std::thread(worker, _this);
|
||||||
flog::info("PlutoSDRSourceModule '{0}': Start!", _this->name);
|
flog::info("PlutoSDRSourceModule '{0}': Start!", _this->name);
|
||||||
@ -158,12 +317,14 @@ private:
|
|||||||
static void stop(void* ctx) {
|
static void stop(void* ctx) {
|
||||||
PlutoSDRSourceModule* _this = (PlutoSDRSourceModule*)ctx;
|
PlutoSDRSourceModule* _this = (PlutoSDRSourceModule*)ctx;
|
||||||
if (!_this->running) { return; }
|
if (!_this->running) { return; }
|
||||||
|
|
||||||
|
// Stop worker thread
|
||||||
_this->running = false;
|
_this->running = false;
|
||||||
_this->stream.stopWriter();
|
_this->stream.stopWriter();
|
||||||
_this->workerThread.join();
|
_this->workerThread.join();
|
||||||
_this->stream.clearWriteStop();
|
_this->stream.clearWriteStop();
|
||||||
|
|
||||||
// DESTROY CONTEXT HERE
|
// Close device
|
||||||
if (_this->ctx != NULL) {
|
if (_this->ctx != NULL) {
|
||||||
iio_context_destroy(_this->ctx);
|
iio_context_destroy(_this->ctx);
|
||||||
_this->ctx = NULL;
|
_this->ctx = NULL;
|
||||||
@ -176,8 +337,8 @@ private:
|
|||||||
PlutoSDRSourceModule* _this = (PlutoSDRSourceModule*)ctx;
|
PlutoSDRSourceModule* _this = (PlutoSDRSourceModule*)ctx;
|
||||||
_this->freq = freq;
|
_this->freq = freq;
|
||||||
if (_this->running) {
|
if (_this->running) {
|
||||||
// SET PLUTO FREQ HERE
|
// Tune device
|
||||||
iio_channel_attr_write_longlong(iio_device_find_channel(_this->phy, "altvoltage0", true), "frequency", round(freq));
|
iio_channel_attr_write_longlong(_this->rxLO, "frequency", round(freq));
|
||||||
}
|
}
|
||||||
flog::info("PlutoSDRSourceModule '{0}': Tune: {1}!", _this->name, freq);
|
flog::info("PlutoSDRSourceModule '{0}': Tune: {1}!", _this->name, freq);
|
||||||
}
|
}
|
||||||
@ -186,120 +347,184 @@ private:
|
|||||||
PlutoSDRSourceModule* _this = (PlutoSDRSourceModule*)ctx;
|
PlutoSDRSourceModule* _this = (PlutoSDRSourceModule*)ctx;
|
||||||
|
|
||||||
if (_this->running) { SmGui::BeginDisabled(); }
|
if (_this->running) { SmGui::BeginDisabled(); }
|
||||||
SmGui::LeftLabel("IP");
|
|
||||||
SmGui::FillWidth();
|
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.acquire();
|
||||||
config.conf["IP"] = &_this->ip[3];
|
config.conf["device"] = _this->devices.key(_this->devId);
|
||||||
config.release(true);
|
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();
|
SmGui::FillWidth();
|
||||||
if (SmGui::Combo(CONCAT("##_pluto_sr_", _this->name), &_this->srId, _this->sampleRatesTxt.c_str())) {
|
SmGui::ForceSync();
|
||||||
_this->sampleRate = _this->sampleRates[_this->srId];
|
if (SmGui::Button(CONCAT("Refresh##_pluto_refr_", _this->name))) {
|
||||||
core::setInputSampleRate(_this->sampleRate);
|
_this->refresh();
|
||||||
config.acquire();
|
_this->select(_this->devDesc);
|
||||||
config.conf["sampleRate"] = _this->sampleRate;
|
|
||||||
config.release(true);
|
|
||||||
}
|
}
|
||||||
if (_this->running) { SmGui::EndDisabled(); }
|
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::LeftLabel("Gain Mode");
|
||||||
SmGui::FillWidth();
|
SmGui::FillWidth();
|
||||||
SmGui::ForceSync();
|
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) {
|
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");
|
SmGui::LeftLabel("Gain");
|
||||||
if (_this->gainMode) { SmGui::BeginDisabled(); }
|
if (_this->gmId) { SmGui::BeginDisabled(); }
|
||||||
SmGui::FillWidth();
|
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) {
|
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) {
|
static void worker(void* ctx) {
|
||||||
PlutoSDRSourceModule* _this = (PlutoSDRSourceModule*)ctx;
|
PlutoSDRSourceModule* _this = (PlutoSDRSourceModule*)ctx;
|
||||||
int blockSize = _this->sampleRate / 200.0f;
|
int blockSize = _this->samplerate / 200.0f;
|
||||||
|
|
||||||
struct iio_channel *rx0_i, *rx0_q;
|
// Acquire channels
|
||||||
struct iio_buffer* rxbuf;
|
iio_channel* rx0_i = iio_device_find_channel(_this->dev, "voltage0", 0);
|
||||||
|
iio_channel* rx0_q = iio_device_find_channel(_this->dev, "voltage1", 0);
|
||||||
rx0_i = iio_device_find_channel(_this->dev, "voltage0", 0);
|
if (!rx0_i || !rx0_q) {
|
||||||
rx0_q = iio_device_find_channel(_this->dev, "voltage1", 0);
|
flog::error("Failed to acquire RX channels");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start streaming
|
||||||
iio_channel_enable(rx0_i);
|
iio_channel_enable(rx0_i);
|
||||||
iio_channel_enable(rx0_q);
|
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) {
|
if (!rxbuf) {
|
||||||
flog::error("Could not create RX buffer");
|
flog::error("Could not create RX buffer");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Receive loop
|
||||||
while (true) {
|
while (true) {
|
||||||
// Read samples here
|
// Read samples
|
||||||
// TODO: RECEIVE HERE
|
|
||||||
iio_buffer_refill(rxbuf);
|
iio_buffer_refill(rxbuf);
|
||||||
|
|
||||||
|
// Get buffer pointer
|
||||||
int16_t* buf = (int16_t*)iio_buffer_first(rxbuf, rx0_i);
|
int16_t* buf = (int16_t*)iio_buffer_first(rxbuf, rx0_i);
|
||||||
|
if (!buf) { break; }
|
||||||
|
|
||||||
for (int i = 0; i < blockSize; i++) {
|
// Convert samples to CF32
|
||||||
_this->stream.writeBuf[i].re = (float)buf[i * 2] / 32768.0f;
|
|
||||||
_this->stream.writeBuf[i].im = (float)buf[(i * 2) + 1] / 32768.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
volk_16i_s32f_convert_32f((float*)_this->stream.writeBuf, buf, 32768.0f, blockSize * 2);
|
volk_16i_s32f_convert_32f((float*)_this->stream.writeBuf, buf, 32768.0f, blockSize * 2);
|
||||||
|
|
||||||
|
// Send out the samples
|
||||||
if (!_this->stream.swap(blockSize)) { break; };
|
if (!_this->stream.swap(blockSize)) { break; };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop streaming
|
||||||
|
iio_channel_disable(rx0_i);
|
||||||
|
iio_channel_disable(rx0_q);
|
||||||
|
|
||||||
|
// Free buffer
|
||||||
iio_buffer_destroy(rxbuf);
|
iio_buffer_destroy(rxbuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string name;
|
std::string name;
|
||||||
bool enabled = true;
|
bool enabled = true;
|
||||||
dsp::stream<dsp::complex_t> stream;
|
dsp::stream<dsp::complex_t> stream;
|
||||||
float sampleRate;
|
|
||||||
SourceManager::SourceHandler handler;
|
SourceManager::SourceHandler handler;
|
||||||
std::thread workerThread;
|
std::thread workerThread;
|
||||||
struct iio_context* ctx = NULL;
|
iio_context* ctx = NULL;
|
||||||
struct iio_device* phy = NULL;
|
iio_device* phy = NULL;
|
||||||
struct iio_device* dev = NULL;
|
iio_device* dev = NULL;
|
||||||
|
iio_channel* rxLO = NULL;
|
||||||
|
iio_channel* rxChan = NULL;
|
||||||
bool running = false;
|
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 devDesc = "";
|
||||||
std::string sampleRatesTxt;
|
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_() {
|
MOD_EXPORT void _INIT_() {
|
||||||
json defConf;
|
json defConf = {};
|
||||||
defConf["IP"] = "192.168.2.1";
|
defConf["device"] = "";
|
||||||
defConf["sampleRate"] = 4000000.0f;
|
defConf["devices"] = {};
|
||||||
defConf["gainMode"] = 0;
|
|
||||||
defConf["gain"] = 0.0f;
|
|
||||||
config.setPath(core::args["root"].s() + "/plutosdr_source_config.json");
|
config.setPath(core::args["root"].s() + "/plutosdr_source_config.json");
|
||||||
config.load(defConf);
|
config.load(defConf);
|
||||||
config.enableAutoSave();
|
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) {
|
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
|
||||||
|
@ -17,7 +17,7 @@ SDRPP_MOD_INFO{
|
|||||||
/* Name: */ "rfspace_source",
|
/* Name: */ "rfspace_source",
|
||||||
/* Description: */ "RFspace source module for SDR++",
|
/* Description: */ "RFspace source module for SDR++",
|
||||||
/* Author: */ "Ryzerth",
|
/* Author: */ "Ryzerth",
|
||||||
/* Version: */ 0, 1, 0,
|
/* Version: */ 0, 1, 1,
|
||||||
/* Max instances */ 1
|
/* Max instances */ 1
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -154,8 +154,8 @@ private:
|
|||||||
_this->client = rfspace::connect(_this->hostname, _this->port, &_this->stream);
|
_this->client = rfspace::connect(_this->hostname, _this->port, &_this->stream);
|
||||||
_this->deviceInit();
|
_this->deviceInit();
|
||||||
}
|
}
|
||||||
catch (std::exception e) {
|
catch (const std::exception& e) {
|
||||||
flog::error("Could not connect to SDR: {0}", e.what());
|
flog::error("Could not connect to SDR: {}", e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (connected && SmGui::Button("Disconnect##rfspace_source")) {
|
else if (connected && SmGui::Button("Disconnect##rfspace_source")) {
|
||||||
@ -231,7 +231,7 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create samplerate list
|
// Create samplerate list
|
||||||
auto srs = client->getValidSampleRates();
|
auto srs = client->getSamplerates();
|
||||||
sampleRates.clear();
|
sampleRates.clear();
|
||||||
for (auto& sr : srs) {
|
for (auto& sr : srs) {
|
||||||
sampleRates.define(sr, getBandwdithScaled(sr), sr);
|
sampleRates.define(sr, getBandwdithScaled(sr), sr);
|
||||||
@ -317,7 +317,7 @@ private:
|
|||||||
dsp::stream<dsp::complex_t> stream;
|
dsp::stream<dsp::complex_t> stream;
|
||||||
SourceManager::SourceHandler handler;
|
SourceManager::SourceHandler handler;
|
||||||
|
|
||||||
rfspace::RFspaceClient client;
|
std::shared_ptr<rfspace::Client> client;
|
||||||
};
|
};
|
||||||
|
|
||||||
MOD_EXPORT void _INIT_() {
|
MOD_EXPORT void _INIT_() {
|
||||||
|
@ -6,15 +6,13 @@
|
|||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
namespace rfspace {
|
namespace rfspace {
|
||||||
RFspaceClientClass::RFspaceClientClass(net::Conn conn, net::Conn udpConn, dsp::stream<dsp::complex_t>* out) {
|
Client::Client(std::shared_ptr<net::Socket> tcp, std::shared_ptr<net::Socket> udp, dsp::stream<dsp::complex_t>* out) {
|
||||||
client = std::move(conn);
|
this->tcp = tcp;
|
||||||
udpClient = std::move(udpConn);
|
this->udp = udp;
|
||||||
output = out;
|
output = out;
|
||||||
|
|
||||||
// Allocate buffers
|
// Allocate buffers
|
||||||
rbuffer = new uint8_t[RFSPACE_MAX_SIZE];
|
|
||||||
sbuffer = 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
|
// Clear write stop of stream just in case
|
||||||
output->clearWriteStop();
|
output->clearWriteStop();
|
||||||
@ -22,9 +20,9 @@ namespace rfspace {
|
|||||||
// Send UDP packet so that a router opens the port
|
// Send UDP packet so that a router opens the port
|
||||||
sendDummyUDP();
|
sendDummyUDP();
|
||||||
|
|
||||||
// Start readers
|
// Start workers
|
||||||
client->readAsync(sizeof(tcpHeader), (uint8_t*)&tcpHeader, tcpHandler, this);
|
tcpWorkerThread = std::thread(&Client::tcpWorker, this);
|
||||||
udpClient->readAsync(RFSPACE_MAX_SIZE, ubuffer, udpHandler, this);
|
udpWorkerThread = std::thread(&Client::udpWorker, this);
|
||||||
|
|
||||||
// Get device ID and wait for response
|
// Get device ID and wait for response
|
||||||
getControlItem(RFSPACE_CTRL_ITEM_PROD_ID, NULL, 0);
|
getControlItem(RFSPACE_CTRL_ITEM_PROD_ID, NULL, 0);
|
||||||
@ -43,22 +41,20 @@ namespace rfspace {
|
|||||||
setPort(RFSPACE_RF_PORT_1);
|
setPort(RFSPACE_RF_PORT_1);
|
||||||
|
|
||||||
// Start heartbeat
|
// Start heartbeat
|
||||||
heartBeatThread = std::thread(&RFspaceClientClass::heartBeatWorker, this);
|
heartBeatThread = std::thread(&Client::heartBeatWorker, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
RFspaceClientClass::~RFspaceClientClass() {
|
Client::~Client() {
|
||||||
close();
|
close();
|
||||||
delete[] rbuffer;
|
|
||||||
delete[] sbuffer;
|
delete[] sbuffer;
|
||||||
delete[] ubuffer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RFspaceClientClass::sendDummyUDP() {
|
void Client::sendDummyUDP() {
|
||||||
uint8_t dummy = 0x5A;
|
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
|
// Build packet
|
||||||
uint16_t* header = (uint16_t*)&sbuffer[0];
|
uint16_t* header = (uint16_t*)&sbuffer[0];
|
||||||
uint16_t* item_val = (uint16_t*)&sbuffer[2];
|
uint16_t* item_val = (uint16_t*)&sbuffer[2];
|
||||||
@ -66,12 +62,12 @@ namespace rfspace {
|
|||||||
*item_val = item;
|
*item_val = item;
|
||||||
|
|
||||||
// Send packet
|
// Send packet
|
||||||
client->write(4, sbuffer);
|
tcp->send(sbuffer, 4);
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RFspaceClientClass::setControlItem(ControlItem item, void* param, int len) {
|
void Client::setControlItem(ControlItem item, void* param, int len) {
|
||||||
// Build packet
|
// Build packet
|
||||||
uint16_t* header = (uint16_t*)&sbuffer[0];
|
uint16_t* header = (uint16_t*)&sbuffer[0];
|
||||||
uint16_t* item_val = (uint16_t*)&sbuffer[2];
|
uint16_t* item_val = (uint16_t*)&sbuffer[2];
|
||||||
@ -80,10 +76,10 @@ namespace rfspace {
|
|||||||
memcpy(&sbuffer[4], param, len);
|
memcpy(&sbuffer[4], param, len);
|
||||||
|
|
||||||
// Send packet
|
// 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
|
// Build packet
|
||||||
uint16_t* header = (uint16_t*)&sbuffer[0];
|
uint16_t* header = (uint16_t*)&sbuffer[0];
|
||||||
uint16_t* item_val = (uint16_t*)&sbuffer[2];
|
uint16_t* item_val = (uint16_t*)&sbuffer[2];
|
||||||
@ -94,10 +90,10 @@ namespace rfspace {
|
|||||||
memcpy(&sbuffer[5], param, len);
|
memcpy(&sbuffer[5], param, len);
|
||||||
|
|
||||||
// Send packet
|
// 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;
|
std::vector<uint32_t> sr;
|
||||||
|
|
||||||
switch (deviceId) {
|
switch (deviceId) {
|
||||||
@ -119,92 +115,145 @@ namespace rfspace {
|
|||||||
return sr;
|
return sr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RFspaceClientClass::setFrequency(uint64_t freq) {
|
void Client::setFrequency(uint64_t freq) {
|
||||||
setControlItemWithChanID(RFSPACE_CTRL_ITEM_NCO_FREQUENCY, 0, &freq, 5);
|
setControlItemWithChanID(RFSPACE_CTRL_ITEM_NCO_FREQUENCY, 0, &freq, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RFspaceClientClass::setPort(RFPort port) {
|
void Client::setPort(RFPort port) {
|
||||||
uint8_t value = port;
|
uint8_t value = port;
|
||||||
setControlItemWithChanID(RFSPACE_CTRL_ITEM_RF_PORT, 0, &value, sizeof(value));
|
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));
|
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));
|
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 };
|
uint8_t args[4] = { (uint8_t)sampleFormat, (uint8_t)RFSPACE_STATE_RUN, (uint8_t)sampleDepth, 0 };
|
||||||
setControlItem(RFSPACE_CTRL_ITEM_STATE, args, sizeof(args));
|
setControlItem(RFSPACE_CTRL_ITEM_STATE, args, sizeof(args));
|
||||||
}
|
}
|
||||||
|
|
||||||
void RFspaceClientClass::stop() {
|
void Client::stop() {
|
||||||
uint8_t args[4] = { 0, RFSPACE_STATE_IDLE, 0, 0 };
|
uint8_t args[4] = { 0, RFSPACE_STATE_IDLE, 0, 0 };
|
||||||
setControlItem(RFSPACE_CTRL_ITEM_STATE, args, sizeof(args));
|
setControlItem(RFSPACE_CTRL_ITEM_STATE, args, sizeof(args));
|
||||||
}
|
}
|
||||||
|
|
||||||
void RFspaceClientClass::close() {
|
void Client::close() {
|
||||||
|
// Stop UDP worker
|
||||||
output->stopWriter();
|
output->stopWriter();
|
||||||
|
udp->close();
|
||||||
|
if (udpWorkerThread.joinable()) { udpWorkerThread.join(); }
|
||||||
|
output->clearWriteStop();
|
||||||
|
|
||||||
|
// Stop heartbeat worker
|
||||||
stopHeartBeat = true;
|
stopHeartBeat = true;
|
||||||
heartBeatCnd.notify_all();
|
heartBeatCnd.notify_all();
|
||||||
if (heartBeatThread.joinable()) { heartBeatThread.join(); }
|
if (heartBeatThread.joinable()) { heartBeatThread.join(); }
|
||||||
client->close();
|
|
||||||
udpClient->close();
|
// Stop TCP worker
|
||||||
output->clearWriteStop();
|
tcp->close();
|
||||||
|
if (tcpWorkerThread.joinable()) { tcpWorkerThread.join(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RFspaceClientClass::isOpen() {
|
bool Client::isOpen() {
|
||||||
return client->isOpen();
|
return tcp->isOpen() || udp->isOpen();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RFspaceClientClass::tcpHandler(int count, uint8_t* buf, void* ctx) {
|
void Client::tcpWorker() {
|
||||||
RFspaceClientClass* _this = (RFspaceClientClass*)ctx;
|
// Allocate receive buffer
|
||||||
uint8_t type = _this->tcpHeader >> 13;
|
uint8_t* buffer = new uint8_t[RFSPACE_MAX_SIZE];
|
||||||
uint16_t size = _this->tcpHeader & 0b1111111111111;
|
|
||||||
|
|
||||||
// Read the rest of the data
|
// Receive loop
|
||||||
if (size > 2) {
|
while (true) {
|
||||||
_this->client->read(size - 2, &_this->rbuffer[2]);
|
// 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
|
// Receive data
|
||||||
uint16_t* controlItem = (uint16_t*)&_this->rbuffer[2];
|
if (tcp->recv(buffer, size - 2, true, RFSPACE_TIMEOUT_MS) <= 0) { break; }
|
||||||
if (type == RFSPACE_MSG_TYPE_T2H_SET_CTRL_ITEM_RESP && *controlItem == RFSPACE_CTRL_ITEM_PROD_ID) {
|
|
||||||
{
|
// Check for a device ID
|
||||||
std::lock_guard<std::mutex> lck(_this->devIdMtx);
|
uint16_t* controlItem = (uint16_t*)&buffer[0];
|
||||||
_this->deviceId = (DeviceID)*(uint32_t*)&_this->rbuffer[4];
|
if (type == RFSPACE_MSG_TYPE_T2H_SET_CTRL_ITEM_RESP && *controlItem == RFSPACE_CTRL_ITEM_PROD_ID) {
|
||||||
_this->devIdAvailable = true;
|
{
|
||||||
|
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
|
// Free receive buffer
|
||||||
_this->client->readAsync(sizeof(_this->tcpHeader), (uint8_t*)&_this->tcpHeader, tcpHandler, _this);
|
delete[] buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RFspaceClientClass::udpHandler(int count, uint8_t* buf, void* ctx) {
|
void Client::udpWorker() {
|
||||||
RFspaceClientClass* _this = (RFspaceClientClass*)ctx;
|
// Allocate receive buffer
|
||||||
uint16_t hdr = (uint16_t)buf[0] | ((uint16_t)buf[1] << 8);
|
uint8_t* buffer = new uint8_t[RFSPACE_MAX_SIZE];
|
||||||
uint8_t type = hdr >> 13;
|
uint16_t* header = (uint16_t*)&buffer[0];
|
||||||
uint16_t size = hdr & 0b1111111111111;
|
|
||||||
|
|
||||||
if (type == RFSPACE_MSG_TYPE_T2H_DATA_ITEM_0) {
|
// Receive loop
|
||||||
int16_t* samples = (int16_t*)&buf[4];
|
while (true) {
|
||||||
int sampCount = (size - 4) / (2 * sizeof(int16_t));
|
// Receive datagram
|
||||||
volk_16i_s32f_convert_32f((float*)_this->output->writeBuf, samples, 32768.0f, sampCount * 2);
|
int rsize = udp->recv(buffer, RFSPACE_MAX_SIZE);
|
||||||
_this->output->swap(sampCount);
|
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
|
// Free receive buffer
|
||||||
_this->udpClient->readAsync(RFSPACE_MAX_SIZE, _this->ubuffer, udpHandler, _this);
|
delete[] buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RFspaceClientClass::heartBeatWorker() {
|
void Client::heartBeatWorker() {
|
||||||
uint8_t dummy[4];
|
uint8_t dummy[4];
|
||||||
while (true) {
|
while (true) {
|
||||||
getControlItem(RFSPACE_CTRL_ITEM_STATE, dummy, sizeof(dummy));
|
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) {
|
std::shared_ptr<Client> connect(std::string host, uint16_t port, dsp::stream<dsp::complex_t>* out) {
|
||||||
net::Conn conn = net::connect(host, port);
|
auto tcp = net::connect(host, port);
|
||||||
if (!conn) { return NULL; }
|
auto udp = net::openudp(host, port, "0.0.0.0", port);
|
||||||
net::Conn udpConn = net::openUDP("0.0.0.0", port, host, port, true);
|
return std::make_shared<Client>(tcp, udp, out);
|
||||||
if (!udpConn) { return NULL; }
|
|
||||||
return RFspaceClient(new RFspaceClientClass(std::move(conn), std::move(udpConn), out));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <utils/networking.h>
|
#include <utils/net.h>
|
||||||
#include <dsp/stream.h>
|
#include <dsp/stream.h>
|
||||||
#include <dsp/types.h>
|
#include <dsp/types.h>
|
||||||
#include <atomic>
|
#include <thread>
|
||||||
#include <queue>
|
#include <vector>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
#define RFSPACE_MAX_SIZE 8192
|
#define RFSPACE_MAX_SIZE 8192
|
||||||
#define RFSPACE_HEARTBEAT_INTERVAL_MS 1000
|
#define RFSPACE_HEARTBEAT_INTERVAL_MS 1000
|
||||||
@ -96,10 +97,10 @@ namespace rfspace {
|
|||||||
RFSPACE_CTRL_ITEM_ERROR_LOG = 0x0410
|
RFSPACE_CTRL_ITEM_ERROR_LOG = 0x0410
|
||||||
};
|
};
|
||||||
|
|
||||||
class RFspaceClientClass {
|
class Client {
|
||||||
public:
|
public:
|
||||||
RFspaceClientClass(net::Conn conn, net::Conn udpConn, dsp::stream<dsp::complex_t>* out);
|
Client(std::shared_ptr<net::Socket> tcp, std::shared_ptr<net::Socket> udp, dsp::stream<dsp::complex_t>* out);
|
||||||
~RFspaceClientClass();
|
~Client();
|
||||||
|
|
||||||
void sendDummyUDP();
|
void sendDummyUDP();
|
||||||
|
|
||||||
@ -107,7 +108,7 @@ namespace rfspace {
|
|||||||
void setControlItem(ControlItem item, void* param, int len);
|
void setControlItem(ControlItem item, void* param, int len);
|
||||||
void setControlItemWithChanID(ControlItem item, uint8_t chanId, 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 setFrequency(uint64_t freq);
|
||||||
void setPort(RFPort port);
|
void setPort(RFPort port);
|
||||||
@ -123,21 +124,22 @@ namespace rfspace {
|
|||||||
DeviceID deviceId;
|
DeviceID deviceId;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void tcpHandler(int count, uint8_t* buf, void* ctx);
|
void tcpWorker();
|
||||||
static void udpHandler(int count, uint8_t* buf, void* ctx);
|
void udpWorker();
|
||||||
void heartBeatWorker();
|
void heartBeatWorker();
|
||||||
|
|
||||||
net::Conn client;
|
std::shared_ptr<net::Socket> tcp;
|
||||||
net::Conn udpClient;
|
std::shared_ptr<net::Socket> udp;
|
||||||
|
|
||||||
dsp::stream<dsp::complex_t>* output;
|
dsp::stream<dsp::complex_t>* output;
|
||||||
|
|
||||||
uint16_t tcpHeader;
|
uint16_t tcpHeader;
|
||||||
uint16_t udpHeader;
|
uint16_t udpHeader;
|
||||||
|
|
||||||
uint8_t* rbuffer = NULL;
|
|
||||||
uint8_t* sbuffer = NULL;
|
uint8_t* sbuffer = NULL;
|
||||||
uint8_t* ubuffer = NULL;
|
|
||||||
|
std::thread tcpWorkerThread;
|
||||||
|
std::thread udpWorkerThread;
|
||||||
|
|
||||||
std::thread heartBeatThread;
|
std::thread heartBeatThread;
|
||||||
std::mutex heartBeatMtx;
|
std::mutex heartBeatMtx;
|
||||||
@ -147,10 +149,12 @@ namespace rfspace {
|
|||||||
bool devIdAvailable = false;
|
bool devIdAvailable = false;
|
||||||
std::condition_variable devIdCnd;
|
std::condition_variable devIdCnd;
|
||||||
std::mutex devIdMtx;
|
std::mutex devIdMtx;
|
||||||
|
|
||||||
|
std::mutex bufferMtx;
|
||||||
|
int blockSize = 256;
|
||||||
|
int inBuffer = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::unique_ptr<RFspaceClientClass> RFspaceClient;
|
std::shared_ptr<Client> connect(std::string host, uint16_t port, dsp::stream<dsp::complex_t>* out);
|
||||||
|
|
||||||
RFspaceClient connect(std::string host, uint16_t port, dsp::stream<dsp::complex_t>* out);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -171,7 +171,7 @@ public:
|
|||||||
#ifndef __ANDROID__
|
#ifndef __ANDROID__
|
||||||
int oret = rtlsdr_open(&openDev, id);
|
int oret = rtlsdr_open(&openDev, id);
|
||||||
#else
|
#else
|
||||||
int oret = rtlsdr_open_fd(&openDev, devFd);
|
int oret = rtlsdr_open_sys_dev(&openDev, devFd);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (oret < 0) {
|
if (oret < 0) {
|
||||||
@ -285,7 +285,7 @@ private:
|
|||||||
#ifndef __ANDROID__
|
#ifndef __ANDROID__
|
||||||
int oret = rtlsdr_open(&_this->openDev, _this->devId);
|
int oret = rtlsdr_open(&_this->openDev, _this->devId);
|
||||||
#else
|
#else
|
||||||
int oret = rtlsdr_open_fd(&_this->openDev, _this->devFd);
|
int oret = rtlsdr_open_sys_dev(&_this->openDev, _this->devFd);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (oret < 0) {
|
if (oret < 0) {
|
||||||
@ -523,8 +523,8 @@ private:
|
|||||||
RTLSDRSourceModule* _this = (RTLSDRSourceModule*)ctx;
|
RTLSDRSourceModule* _this = (RTLSDRSourceModule*)ctx;
|
||||||
int sampCount = len / 2;
|
int sampCount = len / 2;
|
||||||
for (int i = 0; i < sampCount; i++) {
|
for (int i = 0; i < sampCount; i++) {
|
||||||
_this->stream.writeBuf[i].re = (float)(buf[i * 2] - 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) / 128.0f;
|
_this->stream.writeBuf[i].im = ((float)buf[(i * 2) + 1] - 127.4) / 128.0f;
|
||||||
}
|
}
|
||||||
if (!_this->stream.swap(sampCount)) { return; }
|
if (!_this->stream.swap(sampCount)) { return; }
|
||||||
}
|
}
|
||||||
|
@ -132,8 +132,8 @@ private:
|
|||||||
try {
|
try {
|
||||||
_this->client = rtltcp::connect(&_this->stream, _this->ip, _this->port);
|
_this->client = rtltcp::connect(&_this->stream, _this->ip, _this->port);
|
||||||
}
|
}
|
||||||
catch (std::exception e) {
|
catch (const std::exception& e) {
|
||||||
flog::error("Could connect to RTL-TCP server: {0}", e.what());
|
flog::error("Could connect to RTL-TCP server: {}", e.what());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ SDRPP_MOD_INFO{
|
|||||||
/* Name: */ "sdrpp_server_source",
|
/* Name: */ "sdrpp_server_source",
|
||||||
/* Description: */ "SDR++ Server source module for SDR++",
|
/* Description: */ "SDR++ Server source module for SDR++",
|
||||||
/* Author: */ "Ryzerth",
|
/* Author: */ "Ryzerth",
|
||||||
/* Version: */ 0, 1, 0,
|
/* Version: */ 0, 2, 0,
|
||||||
/* Max instances */ 1
|
/* Max instances */ 1
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -109,10 +109,10 @@ private:
|
|||||||
SDRPPServerSourceModule* _this = (SDRPPServerSourceModule*)ctx;
|
SDRPPServerSourceModule* _this = (SDRPPServerSourceModule*)ctx;
|
||||||
if (_this->running) { return; }
|
if (_this->running) { return; }
|
||||||
|
|
||||||
// Try to connect if not already connected
|
// Try to connect if not already connected (Play button is locked anyway so not sure why I put this here)
|
||||||
if (!_this->client) {
|
if (!_this->connected()) {
|
||||||
_this->tryConnect();
|
_this->tryConnect();
|
||||||
if (!_this->client) { return; }
|
if (!_this->connected()) { return; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set configuration
|
// Set configuration
|
||||||
@ -127,7 +127,7 @@ private:
|
|||||||
SDRPPServerSourceModule* _this = (SDRPPServerSourceModule*)ctx;
|
SDRPPServerSourceModule* _this = (SDRPPServerSourceModule*)ctx;
|
||||||
if (!_this->running) { return; }
|
if (!_this->running) { return; }
|
||||||
|
|
||||||
if (_this->client) { _this->client->stop(); }
|
if (_this->connected()) { _this->client->stop(); }
|
||||||
|
|
||||||
_this->running = false;
|
_this->running = false;
|
||||||
flog::info("SDRPPServerSourceModule '{0}': Stop!", _this->name);
|
flog::info("SDRPPServerSourceModule '{0}': Stop!", _this->name);
|
||||||
@ -135,7 +135,7 @@ private:
|
|||||||
|
|
||||||
static void tune(double freq, void* ctx) {
|
static void tune(double freq, void* ctx) {
|
||||||
SDRPPServerSourceModule* _this = (SDRPPServerSourceModule*)ctx;
|
SDRPPServerSourceModule* _this = (SDRPPServerSourceModule*)ctx;
|
||||||
if (_this->running && _this->client) {
|
if (_this->running && _this->connected()) {
|
||||||
_this->client->setFrequency(freq);
|
_this->client->setFrequency(freq);
|
||||||
}
|
}
|
||||||
_this->freq = freq;
|
_this->freq = freq;
|
||||||
@ -146,7 +146,7 @@ private:
|
|||||||
SDRPPServerSourceModule* _this = (SDRPPServerSourceModule*)ctx;
|
SDRPPServerSourceModule* _this = (SDRPPServerSourceModule*)ctx;
|
||||||
float menuWidth = ImGui::GetContentRegionAvail().x;
|
float menuWidth = ImGui::GetContentRegionAvail().x;
|
||||||
|
|
||||||
bool connected = (_this->client && _this->client->isOpen());
|
bool connected = _this->connected();
|
||||||
gui::mainWindow.playButtonLocked = !connected;
|
gui::mainWindow.playButtonLocked = !connected;
|
||||||
|
|
||||||
ImGui::GenericDialog("##sdrpp_srv_src_err_dialog", _this->serverBusy, GENERIC_DIALOG_BUTTONS_OK, [=](){
|
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() {
|
void tryConnect() {
|
||||||
try {
|
try {
|
||||||
if (client) { client.reset(); }
|
if (client) { client.reset(); }
|
||||||
client = server::connect(hostname, port, &stream);
|
client = server::connect(hostname, port, &stream);
|
||||||
deviceInit();
|
deviceInit();
|
||||||
}
|
}
|
||||||
catch (std::exception e) {
|
catch (const std::exception& e) {
|
||||||
flog::error("Could not connect to SDR: {0}", e.what());
|
flog::error("Could not connect to SDR: {}", e.what());
|
||||||
if (!strcmp(e.what(), "Server busy")) { serverBusy = true; }
|
if (!strcmp(e.what(), "Server busy")) { serverBusy = true; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -281,7 +285,7 @@ private:
|
|||||||
int sampleTypeId;
|
int sampleTypeId;
|
||||||
bool compression = false;
|
bool compression = false;
|
||||||
|
|
||||||
server::Client client;
|
std::shared_ptr<server::Client> client;
|
||||||
};
|
};
|
||||||
|
|
||||||
MOD_EXPORT void _INIT_() {
|
MOD_EXPORT void _INIT_() {
|
||||||
|
@ -7,8 +7,8 @@
|
|||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
namespace server {
|
namespace server {
|
||||||
ClientClass::ClientClass(net::Conn conn, dsp::stream<dsp::complex_t>* out) {
|
Client::Client(std::shared_ptr<net::Socket> sock, dsp::stream<dsp::complex_t>* out) {
|
||||||
client = std::move(conn);
|
this->sock = sock;
|
||||||
output = out;
|
output = out;
|
||||||
|
|
||||||
// Allocate buffers
|
// Allocate buffers
|
||||||
@ -37,8 +37,8 @@ namespace server {
|
|||||||
decomp.start();
|
decomp.start();
|
||||||
link.start();
|
link.start();
|
||||||
|
|
||||||
// Start readers
|
// Start worker thread
|
||||||
client->readAsync(sizeof(PacketHeader), rbuffer, tcpHandler, this);
|
workerThread = std::thread(&Client::worker, this);
|
||||||
|
|
||||||
// Ask for a UI
|
// Ask for a UI
|
||||||
int res = getUI();
|
int res = getUI();
|
||||||
@ -46,14 +46,14 @@ namespace server {
|
|||||||
else if (res == -2) { throw std::runtime_error("Server busy"); }
|
else if (res == -2) { throw std::runtime_error("Server busy"); }
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientClass::~ClientClass() {
|
Client::~Client() {
|
||||||
close();
|
close();
|
||||||
ZSTD_freeDCtx(dctx);
|
ZSTD_freeDCtx(dctx);
|
||||||
delete[] rbuffer;
|
delete[] rbuffer;
|
||||||
delete[] sbuffer;
|
delete[] sbuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientClass::showMenu() {
|
void Client::showMenu() {
|
||||||
std::string diffId = "";
|
std::string diffId = "";
|
||||||
SmGui::DrawListElem diffValue;
|
SmGui::DrawListElem diffValue;
|
||||||
bool syncRequired = false;
|
bool syncRequired = false;
|
||||||
@ -96,8 +96,8 @@ namespace server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientClass::setFrequency(double freq) {
|
void Client::setFrequency(double freq) {
|
||||||
if (!client || !client->isOpen()) { return; }
|
if (!isOpen()) { return; }
|
||||||
*(double*)s_cmd_data = freq;
|
*(double*)s_cmd_data = freq;
|
||||||
sendCommand(COMMAND_SET_FREQUENCY, sizeof(double));
|
sendCommand(COMMAND_SET_FREQUENCY, sizeof(double));
|
||||||
auto waiter = awaitCommandAck(COMMAND_SET_FREQUENCY);
|
auto waiter = awaitCommandAck(COMMAND_SET_FREQUENCY);
|
||||||
@ -105,119 +105,126 @@ namespace server {
|
|||||||
waiter->handled();
|
waiter->handled();
|
||||||
}
|
}
|
||||||
|
|
||||||
double ClientClass::getSampleRate() {
|
double Client::getSampleRate() {
|
||||||
return currentSampleRate;
|
return currentSampleRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientClass::setSampleType(dsp::compression::PCMType type) {
|
void Client::setSampleType(dsp::compression::PCMType type) {
|
||||||
|
if (!isOpen()) { return; }
|
||||||
s_cmd_data[0] = type;
|
s_cmd_data[0] = type;
|
||||||
sendCommand(COMMAND_SET_SAMPLE_TYPE, 1);
|
sendCommand(COMMAND_SET_SAMPLE_TYPE, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientClass::setCompression(bool enabled) {
|
void Client::setCompression(bool enabled) {
|
||||||
|
if (!isOpen()) { return; }
|
||||||
s_cmd_data[0] = enabled;
|
s_cmd_data[0] = enabled;
|
||||||
sendCommand(COMMAND_SET_COMPRESSION, 1);
|
sendCommand(COMMAND_SET_COMPRESSION, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientClass::start() {
|
void Client::start() {
|
||||||
if (!client || !client->isOpen()) { return; }
|
if (!isOpen()) { return; }
|
||||||
sendCommand(COMMAND_START, 0);
|
sendCommand(COMMAND_START, 0);
|
||||||
getUI();
|
getUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientClass::stop() {
|
void Client::stop() {
|
||||||
if (!client || !client->isOpen()) { return; }
|
if (!isOpen()) { return; }
|
||||||
sendCommand(COMMAND_STOP, 0);
|
sendCommand(COMMAND_STOP, 0);
|
||||||
getUI();
|
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();
|
decomp.stop();
|
||||||
link.stop();
|
link.stop();
|
||||||
decompIn.stopWriter();
|
|
||||||
client->close();
|
|
||||||
decompIn.clearWriteStop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ClientClass::isOpen() {
|
bool Client::isOpen() {
|
||||||
return client->isOpen();
|
return sock && sock->isOpen();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientClass::tcpHandler(int count, uint8_t* buf, void* ctx) {
|
void Client::worker() {
|
||||||
ClientClass* _this = (ClientClass*)ctx;
|
while (true) {
|
||||||
|
// Receive header
|
||||||
// Read the rest of the data (TODO: CHECK SIZE OR SHIT WILL BE FUCKED)
|
if (sock->recv(rbuffer, sizeof(PacketHeader), true) <= 0) {
|
||||||
int len = 0;
|
break;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
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;
|
std::vector<PacketWaiter*> toBeRemoved;
|
||||||
for (auto& [waiter, cmd] : _this->commandAckWaiters) {
|
for (auto& [waiter, cmd] : commandAckWaiters) {
|
||||||
waiter->cancel();
|
if (cmd != r_cmd_hdr->cmd) { continue; }
|
||||||
|
waiter->notify();
|
||||||
toBeRemoved.push_back(waiter);
|
toBeRemoved.push_back(waiter);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove handled waiters
|
// Remove handled waiters
|
||||||
for (auto& waiter : toBeRemoved) {
|
for (auto& waiter : toBeRemoved) {
|
||||||
_this->commandAckWaiters.erase(waiter);
|
commandAckWaiters.erase(waiter);
|
||||||
delete waiter;
|
delete waiter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
else if (r_pkt_hdr->type == PACKET_TYPE_BASEBAND) {
|
||||||
else if (_this->r_pkt_hdr->type == PACKET_TYPE_COMMAND_ACK) {
|
memcpy(decompIn.writeBuf, &rbuffer[sizeof(PacketHeader)], r_pkt_hdr->size - sizeof(PacketHeader));
|
||||||
// Notify waiters
|
if (!decompIn.swap(r_pkt_hdr->size - sizeof(PacketHeader))) { break; }
|
||||||
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_COMPRESSED) {
|
||||||
// Remove handled waiters
|
size_t outCount = ZSTD_decompressDCtx(dctx, decompIn.writeBuf, STREAM_BUFFER_SIZE, r_pkt_data, r_pkt_hdr->size - sizeof(PacketHeader));
|
||||||
for (auto& waiter : toBeRemoved) {
|
if (outCount) {
|
||||||
_this->commandAckWaiters.erase(waiter);
|
if (!decompIn.swap(outCount)) { break; }
|
||||||
delete waiter;
|
};
|
||||||
|
}
|
||||||
|
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);
|
auto waiter = awaitCommandAck(COMMAND_GET_UI);
|
||||||
sendCommand(COMMAND_GET_UI, 0);
|
sendCommand(COMMAND_GET_UI, 0);
|
||||||
if (waiter->await(PROTOCOL_TIMEOUT_MS)) {
|
if (waiter->await(PROTOCOL_TIMEOUT_MS)) {
|
||||||
@ -233,37 +240,35 @@ namespace server {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientClass::sendPacket(PacketType type, int len) {
|
void Client::sendPacket(PacketType type, int len) {
|
||||||
s_pkt_hdr->type = type;
|
s_pkt_hdr->type = type;
|
||||||
s_pkt_hdr->size = sizeof(PacketHeader) + len;
|
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;
|
s_cmd_hdr->cmd = cmd;
|
||||||
sendPacket(PACKET_TYPE_COMMAND, sizeof(CommandHeader) + len);
|
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;
|
s_cmd_hdr->cmd = cmd;
|
||||||
sendPacket(PACKET_TYPE_COMMAND_ACK, sizeof(CommandHeader) + len);
|
sendPacket(PACKET_TYPE_COMMAND_ACK, sizeof(CommandHeader) + len);
|
||||||
}
|
}
|
||||||
|
|
||||||
PacketWaiter* ClientClass::awaitCommandAck(Command cmd) {
|
PacketWaiter* Client::awaitCommandAck(Command cmd) {
|
||||||
PacketWaiter* waiter = new PacketWaiter;
|
PacketWaiter* waiter = new PacketWaiter;
|
||||||
commandAckWaiters[waiter] = cmd;
|
commandAckWaiters[waiter] = cmd;
|
||||||
return waiter;
|
return waiter;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientClass::dHandler(dsp::complex_t *data, int count, void *ctx) {
|
void Client::dHandler(dsp::complex_t *data, int count, void *ctx) {
|
||||||
ClientClass* _this = (ClientClass*)ctx;
|
Client* _this = (Client*)ctx;
|
||||||
memcpy(_this->output->writeBuf, data, count * sizeof(dsp::complex_t));
|
memcpy(_this->output->writeBuf, data, count * sizeof(dsp::complex_t));
|
||||||
_this->output->swap(count);
|
_this->output->swap(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
net::Conn conn = net::connect(host, port);
|
return std::make_shared<Client>(net::connect(host, port), out);
|
||||||
if (!conn) { return NULL; }
|
|
||||||
return Client(new ClientClass(std::move(conn), out));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <utils/networking.h>
|
#include <utils/net.h>
|
||||||
#include <dsp/stream.h>
|
#include <dsp/stream.h>
|
||||||
#include <dsp/types.h>
|
#include <dsp/types.h>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
@ -13,10 +13,6 @@
|
|||||||
#include <dsp/routing/stream_link.h>
|
#include <dsp/routing/stream_link.h>
|
||||||
#include <zstd.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
|
#define PROTOCOL_TIMEOUT_MS 10000
|
||||||
|
|
||||||
namespace server {
|
namespace server {
|
||||||
@ -75,10 +71,10 @@ namespace server {
|
|||||||
std::mutex handledMtx;
|
std::mutex handledMtx;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ClientClass {
|
class Client {
|
||||||
public:
|
public:
|
||||||
ClientClass(net::Conn conn, dsp::stream<dsp::complex_t>* out);
|
Client(std::shared_ptr<net::Socket> sock, dsp::stream<dsp::complex_t>* out);
|
||||||
~ClientClass();
|
~Client();
|
||||||
|
|
||||||
void showMenu();
|
void showMenu();
|
||||||
|
|
||||||
@ -98,7 +94,7 @@ namespace server {
|
|||||||
bool serverBusy = false;
|
bool serverBusy = false;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void tcpHandler(int count, uint8_t* buf, void* ctx);
|
void worker();
|
||||||
|
|
||||||
int getUI();
|
int getUI();
|
||||||
|
|
||||||
@ -112,7 +108,7 @@ namespace server {
|
|||||||
|
|
||||||
static void dHandler(dsp::complex_t *data, int count, void *ctx);
|
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::stream<uint8_t> decompIn;
|
||||||
dsp::compression::SampleStreamDecompressor decomp;
|
dsp::compression::SampleStreamDecompressor decomp;
|
||||||
@ -137,10 +133,10 @@ namespace server {
|
|||||||
|
|
||||||
ZSTD_DCtx* dctx;
|
ZSTD_DCtx* dctx;
|
||||||
|
|
||||||
|
std::thread workerThread;
|
||||||
|
|
||||||
double currentSampleRate = 1000000.0;
|
double currentSampleRate = 1000000.0;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::unique_ptr<ClientClass> Client;
|
std::shared_ptr<Client> connect(std::string host, uint16_t port, dsp::stream<dsp::complex_t>* out);
|
||||||
|
|
||||||
Client connect(std::string host, uint16_t port, dsp::stream<dsp::complex_t>* out);
|
|
||||||
}
|
}
|
||||||
|
@ -283,8 +283,8 @@ private:
|
|||||||
flog::info("Connected to server");
|
flog::info("Connected to server");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (std::exception e) {
|
catch (const std::exception& e) {
|
||||||
flog::error("Could not connect to spyserver {0}", e.what());
|
flog::error("Could not connect to spyserver {}", e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user