diff --git a/.gitignore b/.gitignore index 6615edfb7..e8a7ef5ed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,22 @@ -CMakeLists.txt.user* -build* -qtbuild/* -sdriq/* -presets/* -LOCAL/* -sdrangelove.supp -.cproject -.project -.pydevproject -.settings/ -*.cs -*.pro.user -.idea/* -debian/sdrangel/* -debian/sdrangel.substvars -debian/files -debian/sdrangel.debhelper.log -debian/debhelper-build-stamp -obj-x86_64-linux-gnu/* +CMakeLists.txt.user* +build* +qtbuild/* +sdriq/* +presets/* +LOCAL/* +sdrangelove.supp +.cproject +.project +.pydevproject +.vscode/ +.settings/ +*.cs +*.pro.user +.idea/* +rescuesdriq/rescuesdriq +debian/sdrangel/* +debian/sdrangel.substvars +debian/files +debian/sdrangel.debhelper.log +debian/debhelper-build-stamp +obj-x86_64-linux-gnu/* diff --git a/CMakeLists.txt b/CMakeLists.txt index 96d1b840a..0c76bd2be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,7 @@ set(CMAKE_SKIP_BUILD_RPATH FALSE) # when building, don't use the install RPATH already # (but later on when installing) -set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) +set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") @@ -30,6 +30,7 @@ option(DEBUG_OUTPUT "Print debug messages" OFF) option(SANITIZE_ADDRESS "Activate memory address sanitization" OFF) option(HOST_RPI "Compiling on RPi" OFF) option(RX_SAMPLE_24BIT "Internal 24 bit Rx DSP" OFF) +option(NO_DSP_SIMD "Do not use SIMD instructions for DSP even if available" OFF) list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/Modules) @@ -55,12 +56,11 @@ set(CMAKE_AUTOMOC ON) find_package(Qt5Core 5.0 REQUIRED) find_package(Qt5Widgets 5.0 REQUIRED) find_package(Qt5Multimedia 5.0 REQUIRED) -#find_package(QT5OpenGL 5.0 REQUIRED) +find_package(Qt5OpenGL 5.0 REQUIRED) find_package(OpenGL REQUIRED) find_package(PkgConfig) find_package(Boost REQUIRED) find_package(FFTW3F) -find_package(JRTPLib) if (NOT BUILD_DEBIAN) find_package(LibDSDcc) @@ -106,7 +106,7 @@ if (${ARCHITECTURE} MATCHES "x86_64|AMD64|x86") EXECUTE_PROCESS( COMMAND grep flags /proc/cpuinfo OUTPUT_VARIABLE CPU_FLAGS ) # if (${CPU_FLAGS} MATCHES "avx2") # set(HAS_AVX2 ON CACHE BOOL "Architecture has AVX2 SIMD enabled") -# if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANGXX) +# if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")) # set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -mavx2" ) # set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -mavx2" ) # message(STATUS "Use AVX2 SIMD instructions") @@ -117,7 +117,7 @@ if (${ARCHITECTURE} MATCHES "x86_64|AMD64|x86") # endif() if (${CPU_FLAGS} MATCHES "sse4_1") set(HAS_SSE4_1 ON CACHE BOOL "Architecture has SSE 4.1 SIMD enabled") - if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANGXX) + if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")) set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -msse4.1" ) set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -msse4.1" ) message(STATUS "Use SSE 4.1 SIMD instructions") @@ -134,7 +134,7 @@ if (${ARCHITECTURE} MATCHES "x86_64|AMD64|x86") endif() if (${CPU_FLAGS} MATCHES "ssse3") set(HAS_SSSE3 ON CACHE BOOL "Architecture has SSSE3 SIMD enabled") - if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANGXX) + if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")) set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -mssse3" ) set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -mssse3" ) message(STATUS "Use SSSE3 SIMD instructions") @@ -151,7 +151,7 @@ if (${ARCHITECTURE} MATCHES "x86_64|AMD64|x86") endif() if (${CPU_FLAGS} MATCHES "sse2") set(HAS_SSE2 ON CACHE BOOL "Architecture has SSE2 SIMD enabled") - if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANGXX) + if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")) set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -msse2" ) set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -msse2" ) message(STATUS "Use SSE2 SIMD instructions") @@ -170,7 +170,7 @@ elseif (${ARCHITECTURE} MATCHES "armv7l") EXECUTE_PROCESS( COMMAND grep Features /proc/cpuinfo OUTPUT_VARIABLE CPU_FLAGS ) if (${CPU_FLAGS} MATCHES "neon") set(HAS_NEON ON CACHE BOOL "Architecture has NEON SIMD enabled") - if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANGXX) + if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")) set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -mfpu=neon" ) set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -mfpu=neon" ) message(STATUS "Use NEON SIMD instructions") @@ -181,7 +181,7 @@ elseif (${ARCHITECTURE} MATCHES "armv7l") endif() elseif (${ARCHITECTURE} MATCHES "aarch64") set(HAS_NEON ON CACHE BOOL FORCE "Architecture has NEON SIMD enabled") - if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANGXX) + if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")) message(STATUS "Aarch64 always has NEON SIMD instructions") add_definitions(-DUSE_NEON) endif() @@ -204,13 +204,29 @@ if (SANITIZE_ADDRESS) set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS} -fsanitize=address") endif() -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -fmax-errors=10 -ffast-math -ftree-vectorize ${EXTRA_FLAGS}") +if (NO_DSP_SIMD) + message(STATUS "Not compiling with SIMD instructions for DSP even if available") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNO_DSP_SIMD") +else() + message(STATUS "Compiling with SIMD instructions for DSP if available") +endif() + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wvla -Woverloaded-virtual -ffast-math -ftree-vectorize ${EXTRA_FLAGS}") + +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + if (BUILD_DEBIAN) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fmax-errors=1") + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fmax-errors=10") + endif() +endif() ############################################################################## # base libraries add_subdirectory(sdrbase) add_subdirectory(sdrgui) add_subdirectory(sdrsrv) +add_subdirectory(sdrbench) add_subdirectory(httpserver) add_subdirectory(logging) add_subdirectory(qrtplib) @@ -218,6 +234,7 @@ add_subdirectory(swagger) include_directories( ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_SOURCE_DIR}/sdrbase ${CMAKE_SOURCE_DIR}/sdrgui ${CMAKE_SOURCE_DIR}/logging @@ -239,7 +256,7 @@ set(sdrangel_SOURCES ) if(WIN32) - SET(sdrangel_SOURCES ${sdrangel_SOURCES} sdrbase/resources/sdrangel.rc) + SET(sdrangel_SOURCES ${sdrangel_SOURCES} sdrgui/resources/sdrangel.rc) endif(WIN32) add_executable(sdrangel @@ -263,7 +280,7 @@ if(WIN32) set_target_properties(sdrangel PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:windows /ENTRY:mainCRTStartup") endif(WIN32) -qt5_use_modules(sdrangel Widgets Multimedia) +target_link_libraries(sdrangel Qt5::Widgets Qt5::Multimedia) ############################################################################## # main server application @@ -287,7 +304,31 @@ target_link_libraries(sdrangelsrv ${QT_LIBRARIES} ) -qt5_use_modules(sdrangelsrv Multimedia) +target_link_libraries(sdrangelsrv Qt5::Multimedia) + +############################################################################## +# main benchmark application + +set(sdrangelbench_SOURCES + appbench/main.cpp +) + +add_executable(sdrangelbench + ${sdrangelbench_SOURCES} +) + +target_include_directories(sdrangelbench + PUBLIC ${CMAKE_SOURCE_DIR}/sdrbench +) + +target_link_libraries(sdrangelbench + sdrbench + logging + ${QT_LIBRARIES} +) + +target_compile_features(sdrangelbench PRIVATE cxx_generalized_initializers) # cmake >= 3.1.0 +target_link_libraries(sdrangelbench Qt5::Multimedia) ############################################################################## @@ -321,6 +362,7 @@ endif(LIBUSB_FOUND AND UNIX) #install targets install(TARGETS sdrangel DESTINATION bin) install(TARGETS sdrangelsrv DESTINATION bin) +install(TARGETS sdrangelbench DESTINATION bin) #install(TARGETS sdrbase DESTINATION lib) #install files and directories diff --git a/Readme.md b/Readme.md index c14375423..39c9c1931 100644 --- a/Readme.md +++ b/Readme.md @@ -1,10 +1,16 @@ ![SDR Angel banner](doc/img/sdrangel_banner.png) -**SDRangel** is an Open Source Qt5 / OpenGL 3.0+ (Linux) SDR and signal analyzer frontend to various hardware. +**SDRangel** is an Open Source Qt5 / OpenGL 3.0+ SDR and signal analyzer frontend to various hardware. **Check the discussion group** [here](https://groups.io/g/sdrangel) -**⚠ Warning**: Windows distribution is provided as a by product of the Qt toolchain. The platform of choice to run SDRangel is definitely Linux. You are encouraged to use the group to seek help from other Windows users but the author cannot give help or any support for problems related to running the software on Windows. Issues specific to Windows problems opened on Github will be closed systematically. Windows distribution may be discontinued in the future. +⚠ SDRangel is intended for the power user. We expect you to already have some experience with SDR applications and digital signal processing in general. SDRangel might be a bit overwhelming for you however you are encouraged to use the discussion group above to look for help. You can also find more information in the [Wiki](https://github.com/f4exb/sdrangel/wiki). + +

Hardware requirements

+ +SDRangel is a near real time application that is demanding on CPU power and clock speeds for low latency. Recent (2015 or later) Core i7 class CPU is recommended preferably with 4 HT CPU cores (8 logical CPUs or more) with nominal clock over 2 GHz and at least 8 GB RAM. Modern Intel processors will include a GPU suitable for proper OpenGL support. On the other hand SDRangel is not as demanding as recent computer games for graphics and CPU integrated graphics are perfectly fine. USB-3 ports are also preferable for high speed, low latency USB communication. It may run on beefy SBCs and was tested successfully on a Udoo Ultra. + +The server variant does not need a graphical display and therefore OpenGL support. It can run on smaller devices check the Wiki for hardware requirements and a list of successful cases.

Source code

@@ -12,7 +18,7 @@ - master: the production branch - dev: the development branch -- legacy: the modified code from the parent application [hexameron rtl-sdrangelove](https://github.com/hexameron/rtl-sdrangelove) before a major redeisign of the code was carried out and sync was lost. +- legacy: the modified code from the parent application [hexameron rtl-sdrangelove](https://github.com/hexameron/rtl-sdrangelove) before a major redesign of the code was carried out and sync was lost.

Untested plugins

@@ -21,31 +27,37 @@ These plugins come from the parent code base and have been maintained so that th channelrx: - demodlora - - tcpsrc (although it has evolved please use the udpsrc plugin instead) - -

Deprecated plugins

- -These plugins are still present at least in the code but have been superceded: - - - chanalyzer: the Channel Analyzer channel plugin is superceded by the "new generation" Channel Analyzer NG (chanalyzerng)

Specific features

Multiple device support

-From version 2 SDRangel can integrate more than one hardware device running concurrently. +Since version 2 SDRangel can integrate more than one hardware device running concurrently.

Transmission support

-From version 3 transmission or signal generation is supported for BladeRF, HackRF (since version 3.1), LimeSDR (since version 3.4) and PlutoSDR (since version 3.7.8) using a sample sink plugin. These plugins are: +Since version 3 transmission or signal generation is supported for BladeRF, HackRF (since version 3.1), LimeSDR (since version 3.4) and PlutoSDR (since version 3.7.8) using a sample sink plugin. These plugins are: - - [BladeRF output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/bladerfoutput) limited support in Windows + - [BladeRF1 output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/bladerf1output) + - [BladeRF2 output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/bladerf2output) - [HackRF output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/hackrfoutput) - [LimeSDR output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/limesdroutput) - [PlutoSDR output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/plutosdroutput) - [File output or file sink plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/filesink) - [Remote device via Network with SDRdaemon](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/sdrdaemonsink) Linux only +

REST API

+ +Since version 4 a REST API is available to interact with the SDRangel application. More details are provided in the server instance documentation in the `sdrsrv` folder. + +

Server instance

+ +Since version 4 the `sdrangelsrv` binary launches a server mode SDRangel instance that runs wihout the GUI. More information is provided in the Readme file of the `sdrsrv` folder. + +

Detached RF head server (SDRdaemon)

+ +Since version 4.1 the previously separated project SDRdaemon has been modified and included in SDRangel. The `sdrangelsrv` headless variant can be used for this purpose using the Daemon source or sink channels. +

Notes on pulseaudio setup

The audio devices with Qt are supported through pulseaudio and unless you are using a single sound chip (or card) with a single output port or you are an expert with pulseaudio config files you may get into trouble when trying to route the audio to a different output port. These notes are a follow-up of issue #31 with my own experiments with HDMI audio output on the Udoo x86 board. So using this example of HDMI output you can do the following: @@ -58,7 +70,7 @@ The audio devices with Qt are supported through pulseaudio and unless you are us - Select HDMI from the profiles list in the 'Configuration' tab - Then in the 'Output devices' tab the HDMI / display port is selected as it is normally the only one. Just double check this - In SDRangel's Preferences/Audio menu make sure something like `alsa_output.pci-0000_00_1b.0.hdmi-stereo` is selected. The default might also work after the pulseaudio configuration you just made. - + In case you cannot see anything related to HDMI or your desired audio device in pavucontrol just restart pulseaudio with `pulseaudio -k` (`-k` kills the previous instance before restarting) and do the above steps again.

Supported hardware

@@ -87,14 +99,30 @@ It is recommended to add `-DRX_SAMPLE_24BIT=ON` on the cmake command line to act ☞ From version 3.12.0 the Linux binaries are built with the 24 bit Rx option. -

BladeRF

+

BladeRF classic (v.1)

-[BladeRF](https://www.nuand.com/) is supported through the libbladerf library that should be installed in your system for proper build of the software and operation support. Add `libbladerf-dev` to the list of dependencies to install. +[BladeRF1](https://www.nuand.com/bladerf-1) is supported through the libbladeRF library that should be installed in your system for proper build of the software and operation support. Add `libbladerf-dev` to the list of dependencies to install. Note that libbladeRF v2 is used since version 4.2.0 (git tag 2018.08). -If you use your own location for libbladeRF install directory you need to specify library and include locations. Example with `/opt/install/libbladerf` with the following defines on `cmake` command line: +If you compile and use your own location for libbladeRF install directory you need to specify library and include locations. Example with `/opt/install/libbladerf` with the following defines on `cmake` command line: `-DLIBBLADERF_LIBRARIES=/opt/install/libbladeRF/lib/libbladeRF.so -DLIBBLADERF_INCLUDE_DIR=/opt/install/libbladeRF/include` +☞ Please note that if you use your own library the FPGA image `hostedx40.rbf` or `hostedx115.rbf` shoud be placed in e.g. `/opt/install/libbladeRF/share/Nuand/bladeRF` unless you have flashed the FPGA image inside the BladeRF. + +The plugins used to support BladeRF classic are specific to this version of the BladeRF: + - [BladeRF1 input](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesource/bladerf1input) + - [BladeRF1 output](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/bladerf1output) + +

BladeRF micro (v.2)

+ +From version 4.2.0. + +[BladeRF 2 micro](https://www.nuand.com/bladerf-2-0-micro/) is also supported using libbladeRF library that should be installed and configured in the same way as for BladeRF1. + +The plugins used to support BladeRF 2 micro are specific to this version of the BladeRF: + - [BladeRF2 input](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesource/bladerf2input) + - [BladeRF2 output](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/bladerf2output) +

FunCube Dongle

Linux only. @@ -119,11 +147,9 @@ HackRF is better used with a sampling rate of 4.8 MS/s and above. The 2.4 and 3. [LimeSDR](https://myriadrf.org/projects/limesdr/) and its smaller clone LimeSDR Mini are supported using LimeSuite library (see next). -

⚠ The plugins should work normally when running as single instances. Support of many Rx and/or Tx instances running concurrently is considered experimental. At least you should always have one of the streams running.

+

⚠ Version 18.10.0 of LimeSuite is used in the builds and corresponding gateware loaded in the LimeSDR should be is used. If you compile from source version 18.10.0 of LimeSuite must be used.

-

⚠ It seems LimeSDR mini has trouble working with host sample rates lower than 2.5 MS/s particularly in Tx mode.

- -You will need a minimal installation of LimeSuite. Presently commit 04b57e0 or later should be used with its corresponding firmware (v4) and gateware (v2.12) installed in the LimeSDR device: +

⚠ LimeSDR-Mini seems to have problems with Zadig driver therefore it is supported in Linux only.

- `sudo apt-get install libsqlite3-dev` - `git clone https://github.com/myriadrf/LimeSuite.git` @@ -154,7 +180,7 @@ If you use your own location for libperseus-sdr install directory you need to sp [PlutoSDR](https://wiki.analog.com/university/tools/pluto) is supported with the libiio interface. This library should be installed in your system for proper build of the software and operation support. Add `libiio-dev` to the list of dependencies to install. Be aware that version 0.10 is needed and is not available yet in all distributions. You may have to compile it from source instead. -If you use your own location for libiio install directory you need to specify library and include locations. Example with `/opt/install/libiio` with the following defines on `cmake` command line: `-DLIBIIO_INCLUDE_DIR=/opt/install/libiio/include -DLIBIIO_LIBRARY=/opt/install/libiio/lib/libiio.so`. In openSuse the lib directory path would be: `-DLIBIIO_LIBRARY=/opt/install/libiio/lib64/libiio.so`. +If you use your own location for libiio install directory you need to specify library and include locations. Example with `/opt/install/libiio` with the following defines on `cmake` command line: `-DLIBIIO_INCLUDE_DIR=/opt/install/libiio/include -DLIBIIO_LIBRARY=/opt/install/libiio/lib/libiio.so`. In openSUSE the lib directory path would be: `-DLIBIIO_LIBRARY=/opt/install/libiio/lib64/libiio.so`.

RTL-SDR

@@ -184,9 +210,9 @@ These plugins do not use any hardware device connected to your system. The [File source plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesource/filesource) allows the playback of a recorded IQ file. Such a file is obtained using the recording feature. Click on the record button on the left of the main frequency dial to toggle recording. The file has a fixed name `test_.sdriq` created in the current directory where `` is the device tab index. -Note that this plugin does not require any of the hardware support libraries nor the libusb library. It is alwasys available in the list of devices as `FileSource[0]` even if no physical device is connected. +Note that this plugin does not require any of the hardware support libraries nor the libusb library. It is always available in the list of devices as `FileSource[0]` even if no physical device is connected. -The `.sdriq` format produced are the 2x2 bytes I/Q samples with a header containing the center frequency of the baseband, the sample rate and the timestamp of the recording start. Note that this header length is a multiple of the sample size so the file can be read with a simple 2x2 bytes I/Q reader such as a GNU Radio file source block. It will just produce a short glitch at the beginning corresponding to the header data. +The `.sdriq` format produced are the 2x2 bytes I/Q samples with a header containing the center frequency of the baseband, the sample rate and the timestamp of the recording start. Note that this header length is a multiple of the sample size so the file can be read with a simple 2x2 bytes I/Q reader such as a GNU Radio file source block. It will just produce a short glitch at the beginning corresponding to the header data.

File output

@@ -196,19 +222,19 @@ Note that this plugin does not require any of the hardware support libraries nor

Test source

-The [Test source plugin](https://github.com/f4exb/sdrangel/tree/master/plugins/samplesource/testsource) is an internal continuous wave generator that can be used to carry out test of software internals. +The [Test source plugin](https://github.com/f4exb/sdrangel/tree/master/plugins/samplesource/testsource) is an internal continuous wave generator that can be used to carry out test of software internals.

SDRdaemon receiver input

Linux only. -The [SDRdaemon source input plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesource/sdrdaemonsource) is the client side of the SDRdaemon receiver server `sdrdaemonrx`. See the [SDRdaemon](https://github.com/f4exb/sdrdaemon) project in this Github repository. You must specify the local address and UDP port to which the remote server connects and samples will flow into the SDRangel application (default is `127.0.0.1`port `9090`). It uses the meta data to retrieve the sample flow characteristics such as sample rate and receiveng center frequency. It also opens a TCP link to another port to send service messages such as setting parameters specific to the hadrware device connected to the server (default port is `9091`). The `libnanomsg` library is used to support this messaging. +The [SDRdaemon source input plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesource/sdrdaemonsource) is the client side of an instance of SDRangel running the Daemon Sink channel plugin. On the "Data" line you must specify the local address and UDP port to which the remote server connects and samples will flow into the SDRangel application (default is `127.0.0.1`port `9090`). It uses the meta data to retrieve the sample flow characteristics such as sample rate and receiving center frequency. The remote is entirely controlled by the REST API. On the "API" line you can specify the address and port at which the remote REST API listens. However it is used just to display basic information about the remote. The data blocks transmitted via UDP are protected against loss with a Cauchy MDS block erasure codec. This makes the transmission more robust in particular with WiFi links. -There is an automated skew rate compensation in place. During rate readjustemnt streaming can be suspended or signal glitches can occur for about one second. +There is an automated skew rate compensation in place. During rate readjustment streaming can be suspended or signal glitches can occur for about one second. -This plugin will be built only if the libnanomsg and the [CM256cc library](https://github.com/f4exb/cm256cc) are installed in your system. libnanomsg is available as a dev package in most distributions For CM256cc if you install it in a non standard directory you will then have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_INCLUDE_DIR=/opt/install/cm256cc/include/cm256cc -DCM256CC_LIBRARIES=/opt/install/cm256cc/lib/libcm256cc.so` to the cmake commands. +This plugin will be built only if the [CM256cc library](https://github.com/f4exb/cm256cc) is installed in your system. For CM256cc if you install it in a non standard directory you will then have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_INCLUDE_DIR=/opt/install/cm256cc/include/cm256cc -DCM256CC_LIBRARIES=/opt/install/cm256cc/lib/libcm256cc.so` to the cmake commands. Note that this plugin does not require any of the hardware support libraries nor the libusb library. It is always available in the list of devices as `SDRdaemonSource[0]` even if no physical device is connected. @@ -216,13 +242,11 @@ Note that this plugin does not require any of the hardware support libraries nor Linux only. -The [SDRdaemon sink output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/sdrdaemonsink) is the client side of the SDRdaemon transmitter server `sdrdaemontx`. See the [SDRdaemon](https://github.com/f4exb/sdrdaemon) project in this Github repository. You must specify the distant address and UDP port to which the plugin connects and samples from the SDRangel application will flow into the transmitter server (default is `127.0.0.1`port `9092`). It also opens a TCP link to another port to exchange service messages such as setting the center frequency or getting status information from the server (default port is `9093`). The `libnanomsg` library is used to support this messaging. +The [SDRdaemon sink output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/sdrdaemonsink) is the client side of and instance of SDRangel running the Daemon Source channel plugin. On the "Data" line you must specify the distant address and UDP port to which the plugin connects and samples from the SDRangel application will flow into the transmitter server (default is `127.0.0.1`port `9090`). The remote is entirely controlled by the REST API. On the "API" line you can specify the address and port at which the remote REST API listens. The API is pinged regularly to retrieve the status of the data blocks queue and allow rate control to stabilize the queue length. Therefore it is important to connect to the API properly (The status line must return "API OK" and the API label should be lit in green). The data blocks sent via UDP are protected against loss with a Cauchy MDS block erasure codec. This makes the transmission more robust in particular with WiFi links. -There is an automated skew rate compensation in place so that the generator throttling is adjusted to match the actual sample rate of the distant device. This is based on the number of buffer blocks sent back from the distant server using the TCP link. - -This plugin will be built only if the libnanomsg and the [CM256cc library](https://github.com/f4exb/cm256cc) are installed in your system. libnanomsg is available as a dev package in most distributions For CM256cc if you install it in a non standard directory you will then have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_INCLUDE_DIR=/opt/install/cm256cc/include/cm256cc -DCM256CC_LIBRARIES=/opt/install/cm256cc/lib/libcm256cc.so` to the cmake commands. +This plugin will be built only if the [CM256cc library](https://github.com/f4exb/cm256cc) IS installed in your system. For CM256cc if you install it in a non standard directory you will then have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_INCLUDE_DIR=/opt/install/cm256cc/include/cm256cc -DCM256CC_LIBRARIES=/opt/install/cm256cc/lib/libcm256cc.so` to the cmake commands. Note that this plugin does not require any of the hardware support libraries nor the libusb library. It is always available in the list of devices as `SDRdaemonSink[0]` even if no physical device is connected. @@ -236,21 +260,13 @@ This is the `demoddsd` plugin. At present it can be used to decode the following - dPMR - D-Star - Yaesu System Fusion (YSF) + - NXDN It is based on the [DSDcc](https://github.com/f4exb/dsdcc) C++ library which is a rewrite of the original [DSD](https://github.com/szechyjs/dsd) program. So you will need to have DSDcc installed in your system. Please follow instructions in [DSDcc readme](https://github.com/f4exb/dsdcc/blob/master/Readme.md) to build and install DSDcc. If you install it in a custom location say `/opt/install/dsdcc` you will need to add these defines to the cmake command: `-DLIBDSDCC_INCLUDE_DIR=/opt/install/dsdcc/include/dsdcc -DLIBDSDCC_LIBRARIES=/opt/install/dsdcc/lib/libdsdcc.so` -If you have one or more serial devices interfacing the AMBE3000 chip in packet mode you can use them to decode AMBE voice frames. For that purpose you will need to compile with [SerialDV](https://github.com/f4exb/serialDV) support. Please refer to this project Readme.md to compile and install SerialDV. If you install it in a custom location say `/opt/install/serialdv` you will need to add these defines to the cmake command: `-DLIBSERIALDV_INCLUDE_DIR=/opt/install/serialdv/include/serialdv -DLIBSERIALDV_LIBRARY=/opt/install/serialdv/lib/libserialdv.so` Also your user must be a member of group `dialout` to be able to use the dongle. +If you have one or more serial devices interfacing the AMBE3000 chip in packet mode you can use them to decode AMBE voice frames. For that purpose you will need to compile with [SerialDV](https://github.com/f4exb/serialDV) support. Please refer to this project Readme.md to compile and install SerialDV. If you install it in a custom location say `/opt/install/serialdv` you will need to add these defines to the cmake command: `-DLIBSERIALDV_INCLUDE_DIR=/opt/install/serialdv/include/serialdv -DLIBSERIALDV_LIBRARY=/opt/install/serialdv/lib/libserialdv.so` Also your user must be a member of group `dialout` (Ubuntu/Debian) or `uucp` (Arch) to be able to use the dongle. -Although such serial devices work with a serial interface at 400 kb in practice maybe for other reasons they are capable of handling only one conversation at a time. The software will allocate the device dynamically to a conversation with an inactivity timeout of 1 second so that conversations do not get interrupted constantly making the audio output too choppy. In practice you will have to have as many devices connected to your system as the number of conversations you would like to be handled in parallel. - -Note that this is not supported in Windows because of trouble with COM port support (contributors welcome!). - ---- -⚠ Since kernel 4.4.52 the default for FTDI devices (that is in the ftdi_sio kernel module) is not to set it as low latency. This results in the ThumbDV dongle not working anymore because its response is too slow to sustain the normal AMBE packets flow. The solution is to force low latency by changing the variable for your device (ex: /dev/ttyUSB0) as follows: - -`echo 1 | sudo tee /sys/bus/usb-serial/devices/ttyUSB0/latency_timer` or `sudo setserial /dev/ttyUSB0 low_latency` - ---- +Although such serial devices work with a serial interface at 400 kb in practice maybe for other reasons they are capable of handling only one conversation at a time. The software will allocate the device dynamically to a conversation with an inactivity timeout of 1 second so that conversations do not get interrupted constantly making the audio output too choppy. In practice you will have to have as many devices connected to your system as the number of conversations you would like to be handled in parallel. Alternatively you can use [mbelib](https://github.com/szechyjs/mbelib) but mbelib comes with some copyright issues (see next). If you have mbelib installed in a custom location, say `/opt/install/mbelib` you will need to add these defines to the cmake command: `-DLIBMBE_INCLUDE_DIR=/opt/install/mbelib/include -DLIBMBE_LIBRARY=/opt/install/mbelib/lib/libmbe.so` @@ -267,14 +283,15 @@ If you are not comfortable with this just do not install DSDcc and/or mbelib and

Software distributions

-In the [releases](https://github.com/f4exb/sdrangel/releases) section one can find binary distributions for some common systems: +In the [releases](https://github.com/f4exb/sdrangel/releases) section one can find binary distributions for some Debian based distributions: - - Debian x86_64 (Ubuntu 16.04, Ubuntu 17.10, Debian Stretch) - - Windows 32 bit (runs also in 64 bit Windows) + - Ubuntu 18.04 (Bionic) + - Ubuntu 16.04 (Xenial) + - Debian Stretch

Debian distributions

-It is provided in the form of .deb packages for x86_64 architectures with SSE 4.1 support. +It is provided in the form of .deb packages for x86_64 architectures with SSE 4.1 support. Install it as usual for .deb packages: @@ -286,20 +303,27 @@ Prior to apt-get v 1.1 (before Ubuntu 16.04) in a terminal do: - `sudo apt-get upgrade` - `sudo dpkg -i sdrangel_vx.y.z-1_amd64.deb` where x.y.z is the version number - `sudo apt-get -f install` this will install missing dependencies - + Since apt-get v 1.1 installation is possible from a local file: - cd to where the archive has been downloaded - - `sudo apt-get install ./sdrangel_vx.y.z-1_amd64.deb` where x.y.z is the version number - + - `sudo apt-get install ./sdrangel_vx.y.z-1_amd64.deb` where x.y.z is the version number + - `sudo apt-get -f install` this will install missing dependencies + The software is installed in `/opt/sdrangel` you can start it from the command line with: - `/opt/sdrangel/bin/sdrangel` - + +**⚠** The udev rules are not set by the package installation so you will have to set it manually in order to be able to access the various SDR hardware. The `udev-rules` folder contains the rules file and the `install.sh` script that you can run as sudo to install all rules files. You may also adapt the script to copy only the required files. + +

Ubuntu 18.04

+ +The default CPU governor is now `powersave` which exhibits excessive CPU usage when running SDRangel. In the case of benchmarking and maybe high throughput usage it is recommended to switch to `performance` before running SDRangel by running the command: `sudo cpupower frequency-set --governor performance`. You can turn it back to `powersave` any time by running: `sudo cpupower frequency-set --governor powersave`. It is normal that with a lower CPU frequency the relative CPU usage rises for the same actual load. If not impairing operation this is normal and overall beneficial for heat and power consumption. +

Windows distribution

This is the archive of the complete binary distribution that expands to the `sdrangel` directory. You can install it anywhere you like and click on `sdrangel.exe` to start. -⚠ Windows distribution is provided as a by product thanks to the Qt toolchain. The platform of choice to run SDRangel is definitely Linux and very little support can be given for the Windows distribution. +⚠ Windows distribution is provided as a by product thanks to the Qt toolchain. The platform of choice to run SDRangel is definitely Linux and very little support can be given for this Windows distribution.

Software build

@@ -307,7 +331,7 @@ This is the archive of the complete binary distribution that expands to the `sdr To be sure you will need at least Qt version 5.5. It definitely does not work with versions earlier than 5.3 but neither 5.3 nor 5.4 were tested. - - Linux builds are made with 5.5.1 (Xenial) and 5.9 (Artful, Stretch) + - Linux builds are made with 5.5.1 (Xenial), 5.9 (Artful, Stretch, Bionic) and 5.11 (Cosmic) - Windows build is made with 5.10.1 in 32 bit mode and has Qt ANGLE support (OpenGL emulation with DirectX)

24 bit DSP

@@ -336,16 +360,23 @@ Install cmake version 3: - `sudo apt-get remove cmake` (if already installed) - `sudo apt-get install cmake` +

Prerequisites for 16.04 LTS

+ +For DATV demodulator support you need to install the ffmpeg v.3 suite. Therefore you will need to add this PPA to the sources list using this command: +`sudo add-apt-repository ppa:jonathonf/ffmpeg-3` + +Then do `sudo apt-get update` and go to the next step. Alternatively if you have an older version of ffmpeg suite already installed just do `sudo apt-get dist-upgrde`. +

With newer versions just do:

- - `sudo apt-get install cmake g++ pkg-config libfftw3-dev libqt5multimedia5-plugins qtmultimedia5-dev qttools5-dev qttools5-dev-tools libqt5opengl5-dev qtbase5-dev libusb-1.0 librtlsdr-dev libboost-all-dev libasound2-dev pulseaudio libnanomsg-dev libopencv-dev libsqlite3-dev libxml2-dev bison flex` + - `sudo apt-get install cmake g++ pkg-config libfftw3-dev libqt5multimedia5-plugins qtmultimedia5-dev qttools5-dev qttools5-dev-tools libqt5opengl5-dev qtbase5-dev libusb-1.0 librtlsdr-dev libboost-all-dev libasound2-dev pulseaudio libopencv-dev libsqlite3-dev libxml2-dev bison flex ffmpeg libavcodec-dev libavformat-dev` - `mkdir build && cd build && cmake ../ && make` `librtlsdr-dev` is in the `universe` repo. (utopic 14.10 amd64.)

Mint

-Tested with Cinnamon 17.2. Since it is based on Ubuntu 14.04 LTS pleae follow instructions for this distribution (paragraph just above). +Tested with Cinnamon 17.2. Since it is based on Ubuntu 14.04 LTS please follow instructions for this distribution (paragraph just above).

Debian

@@ -355,24 +386,25 @@ Debian 7 "wheezy" uses Qt4. Qt5 is available from the "wheezy-backports" repo, b For Debian Jessie or Stretch: -`sudo apt-get install cmake g++ pkg-config libfftw3-dev libusb-1.0-0-dev libusb-dev qt5-default qtbase5-dev qtchooser libqt5multimedia5-plugins qtmultimedia5-dev qttools5-dev qttools5-dev-tools libqt5opengl5-dev qtbase5-dev librtlsdr-dev libboost-all-dev libasound2-dev pulseaudio libopencv-dev libsqlite3-dev libxml2-dev bison flex` +`sudo apt-get install cmake g++ pkg-config libfftw3-dev libusb-1.0-0-dev libusb-dev qt5-default qtbase5-dev qtchooser libqt5multimedia5-plugins qtmultimedia5-dev qttools5-dev qttools5-dev-tools libqt5opengl5-dev qtbase5-dev librtlsdr-dev libboost-all-dev libasound2-dev pulseaudio libopencv-dev libsqlite3-dev libxml2-dev bison flex ffmpeg libavcodec-dev libavformat-dev` `mkdir build && cd build && cmake ../ && make`

openSUSE

-This has been tested with the bleeding edge "Thumbleweed" distribution: +This has been tested with the Leap 42.3 distribution: -`sudo zypper install Mesa-libGL1 Mesa-libEGL-devel Mesa-libGL-devel Mesa-libGLESv1_CM-devel Mesa-libGLESv2-devel Mesa-libGLESv3-devel Mesa-libglapi-devel libOSMesa-devel` +`sudo zypper install Mesa-libGL1 Mesa-libEGL-devel Mesa-libGL-devel Mesa-libGLESv1_CM-devel Mesa-libGLESv2-devel Mesa-libGLESv3-devel Mesa-libglapi-devel libOSMesa-devel` `sudo zypper install cmake fftw3-devel gcc-c++ libusb-1_0-devel libqt5-qtbase-devel libQt5OpenGL-devel libqt5-qtmultimedia-devel libqt5-qttools-devel libQt5Network-devel libQt5Widgets-devel boost-devel alsa-devel pulseaudio opencv-devel` Then you should be all set to build the software with `cmake` and `make` as discussed earlier. - - Note1: if you are on Leap you will need a more recent g++ compiler so in place of `gcc-c++` use `gcc5-c++` or `gcc6-c++` then add the following in the cmake command: `-DCMAKE_C_COMPILER=/usr/bin/gcc-6 -DCMAKE_CXX_COMPILER=/usr/bin/g++-6` (for gcc 6) and then `-DCMAKE_INSTALL_PREFIX:PATH=...` for the custom install path (not `-DCMAKE_INSTALL_PREFIX=...`) - - Note2: On Leap and aarch64 architectures you will need to build and install `libnanomsg` from [source](https://github.com/nanomsg/nanomsg) - - Note3 for udev rules: installed udev rules for BladeRF and HackRF are targetted at Debian or Ubuntu systems that have a plugdev group for USB hotplug devices. This is not the case in openSUSE. To make the udev rules file compatible just remove the `GROUP` parameter on all lines and change `MODE` parameter to `666`. - - Note4: A package has been created in OpenSUSE thanks to Martin, see: [sdrangel](https://build.opensuse.org/package/show/hardware:sdr/sdrangel). It is based on the latest release on master branch. + - Note1: if you are on Leap you will need a more recent g++ compiler so in place of `gcc-c++` use `gcc6-c++` or `gcc7-c++` then add the following in the cmake command: `-DCMAKE_C_COMPILER=/usr/bin/gcc-7 -DCMAKE_CXX_COMPILER=/usr/bin/g++-7` (for gcc 7) and then `-DCMAKE_INSTALL_PREFIX:PATH=...` for the custom install path (not `-DCMAKE_INSTALL_PREFIX=...`) + - Note2 for udev rules: installed udev rules for BladeRF and HackRF are targeted at Debian or Ubuntu systems that have a plugdev group for USB hotplug devices. This is not the case in openSUSE. To fix it you can either: + - make the udev rules file compatible just remove the `GROUP` parameter on all lines and change `MODE` parameter to `666`. + - create a `plugdev` group and add it tou your user group list: `sudo groupadd plugdev` then `sudo usermod -G plugdev -a ` + - Note3: A package has been created in openSUSE thanks to Martin, see: [sdrangel](https://build.opensuse.org/package/show/hardware:sdr/sdrangel). It is based on the latest release on master branch.

Fedora

@@ -386,16 +418,16 @@ Then you should be all set to build the software with `cmake` and `make` as disc - Note for udev rules: the same as for openSUSE applies. This is detailed in the previous paragraph for openSUSE. -

Manjaro

+

Arch Linux / Manjaro

-Tested with the 15.09 version with LXDE desktop (community supported). The exact desktop environment should not matter anyway. Since Manjaro is Arch Linux based prerequisites should be similar for Arch and all derivatives. +Tested with the 15.09 version with LXDE desktop (community supported). The exact desktop environment should not matter anyway. Prerequisites should be similar for Arch and all derivatives. `sudo pacman -S cmake pkg-config fftw qt5-multimedia qt5-tools qt5-base libusb boost boost-libs pulseaudio` Then you should be all set to build the software with `cmake` and `make` as discussed earlier. - Note1 for udev rules: the same as for openSUSE and Fedora applies. - - Note2: A package has been created in the AUR (thanks Mikos!), see: [sdrangel-git](https://aur.archlinux.org/packages/sdrangel-git). It is based on the `205fee6` commit of 8th December 2015. + - Note2: Two package are avaliable in the AUR (thanks Mikos!), [sdrangel](https://aur.archlinux.org/packages/sdrangel), which provides the lastest tagged release (stable), and [sdrangel-git](https://aur.archlinux.org/packages/sdrangel-git), which builds the latest commit from this repository (unstable).

Windows

@@ -428,14 +460,13 @@ You can uninstall the software with `make uninstall` or `sudo make uninstall` fr

Limitations

- Your hardware. Still SDRangel is relatively conservative on computer resources. - - OpenGL 3+ (Linux) - - OpenGL 4.3+ (Windows) for OpenGL native support however the Qt Angle framework may be able to make it work on systems supporting Direct-X only. + - OpenGL 3+

Features

Changes from SDRangelove

-See the v1.0.1 first official relase [release notes](https://github.com/f4exb/sdrangel/releases/tag/v1.0.1) +See the v1.0.1 first official release [release notes](https://github.com/f4exb/sdrangel/releases/tag/v1.0.1)

To Do

@@ -451,4 +482,4 @@ Other ideas:

Developer's notes

-Please check the developper's specific [readme](./ReadmeDeveloper.md) +Please check the developer's specific [readme](./ReadmeDeveloper.md) diff --git a/ReadmeDeveloper.md b/ReadmeDeveloper.md index 27b15416b..09785d3a1 100644 --- a/ReadmeDeveloper.md +++ b/ReadmeDeveloper.md @@ -19,7 +19,7 @@ You can add `-Wno-dev` on the `cmake` command line to avoid warnings. ![SDRangel code map](./doc/img/SDRangelFlow.png) -The existing receiving flow is represented with green boxes. The futrue Tx flow with red boxes. Some classes and file paths in the Rx part were renamed to avoid collision with future Tx names in this case the old name appears below the present name in italics. +The existing receiving flow is represented with green boxes. The future Tx flow with red boxes. Some classes and file paths in the Rx part were renamed to avoid collision with future Tx names in this case the old name appears below the present name in italics.

Rx path

@@ -33,22 +33,22 @@ The existing receiving flow is represented with green boxes. The futrue Tx flow At present the following plugins are available: - - `AirspyXxx` classes in `plugins/samplesource/airspy`: Inteface with Airspy devices - - `BladeRFXxx` classes in `plugins/samplesource/bladerf`: Inteface with BladeRF devices - - `BladeRFXxx` classes in `plugins/samplesource/bladerf`: Inteface with BladeRF devices - - `FCDProXxx` classes in `plugins/samplesource/fcdpro`: Inteface with Funcube Pro devices - - `FCDProPlusXxx` classes in `plugins/samplesource/fcdproplus`: Inteface with Funcube Pro+ devices - - `HackRFXxx` classes in `plugins/samplesource/hackrf`: Inteface with HackRF devices - - `RTLSDRXxx` classes in `plugins/samplesource/rtlsdr`: Inteface with RTL-SDR devices - - `SDRDaemonXxx` classes in `plugins/samplesource/sdrdaemon`: Special inteface collecting I/Q samples from an UDP flow sent by a remote device using [SDRdaemon](https://github.com/f4exb/sdrdaemon). - - `SDRDaemonFECXxx` classes in `plugins/samplesource/sdrdaemonfec`: Special inteface collecting I/Q samples from an UDP flow sent by a remote device using [SDRdaemon](https://github.com/f4exb/sdrdaemon) with FEC protection of blocks. - - `FileSource` classes in `plugins/samplesource/filesource`: Special inteface reading I/Q samples from a file directly into the baseband skipping the downsampling block + - `AirspyXxx` classes in `plugins/samplesource/airspy`: Interface with Airspy devices + - `BladeRFXxx` classes in `plugins/samplesource/bladerf`: Interface with BladeRF devices + - `BladeRFXxx` classes in `plugins/samplesource/bladerf`: Interface with BladeRF devices + - `FCDProXxx` classes in `plugins/samplesource/fcdpro`: Interface with Funcube Pro devices + - `FCDProPlusXxx` classes in `plugins/samplesource/fcdproplus`: Interface with Funcube Pro+ devices + - `HackRFXxx` classes in `plugins/samplesource/hackrf`: Interface with HackRF devices + - `RTLSDRXxx` classes in `plugins/samplesource/rtlsdr`: Interface with RTL-SDR devices + - `SDRDaemonXxx` classes in `plugins/samplesource/sdrdaemon`: Special interface collecting I/Q samples from an UDP flow sent by a remote device using [SDRdaemon](https://github.com/f4exb/sdrdaemon). + - `SDRDaemonFECXxx` classes in `plugins/samplesource/sdrdaemonfec`: Special interface collecting I/Q samples from an UDP flow sent by a remote device using [SDRdaemon](https://github.com/f4exb/sdrdaemon) with FEC protection of blocks. + - `FileSource` classes in `plugins/samplesource/filesource`: Special interface reading I/Q samples from a file directly into the baseband skipping the downsampling block

Device sample sink plugins

At present the following plugins are available: - - `FileSink` classes in `plugins/samplesink/filesink`: Special inteface writing baseband I/Q samples to a file skipping the final upsampling block + - `FileSink` classes in `plugins/samplesink/filesink`: Special interface writing baseband I/Q samples to a file skipping the final upsampling block

Channel receiver (Rx) plugins

@@ -62,8 +62,7 @@ At present the following plugins are available: - `NFMDemodXxx` classes in `plugins/channelrx/demodnfm`: Narrowband FM demodulator with audio output. - `SSBDemodXxx` classes in `plugins/channelrx/demodssb`: SSB/DSB/CW demodulator with audio output. - `WFMDemodXxx` classes in `plugins/channelrx/demodwfm`: Wideband FM demodulator with audio output. This is a basic demodulator. - - `TCPSrcXxx` classes in `plugins/channelrx/tcpsrc`: Sends channel I/Q samples via TCP - - `UDPSrcXxx` classes in `plugins/channelrx/udpsrc`: Sends channel I/Q or FM demodulated samples via UDP + - `UDPSinkXxx` classes in `plugins/channelrx/udpsink`: Sends channel I/Q or FM demodulated samples via UDP

Channel transmitter (Tx) plugins

@@ -104,11 +103,11 @@ The `plugins` subdirectory contains the associated plugins used to manage device - `xxxanalyzergui.h/cpp` : Analyzer GUI - `xxxanalyzerplugin.h/cpp` : Analyzer plugin manager - `xxxanalyzer.pro` : Qt .pro file for Windows/Android build - - `xxxsrc` : Interface to the outside (e.g xxx = udp): - - `xxxsrc.h/cpp` : Inteface core - - `xxxsrcgui.h/cpp` : Interface GUI - - `xxxsrcplugin/h/cpp` : Interface plugin manager - - `xxxsrc.pro` : Qt .pro file for Windows/Android build + - `xxxsink` : Interface to the outside (e.g xxx = udp): + - `xxxsink.h/cpp` : Interface core + - `xxxsinkgui.h/cpp` : Interface GUI + - `xxxsinkplugin/h/cpp` : Interface plugin manager + - `xxxsink.pro` : Qt .pro file for Windows/Android build - Transmitter functions (Tx): - `samplesink`: Device managers: @@ -130,11 +129,11 @@ The `plugins` subdirectory contains the associated plugins used to manage device - `xxxgeneratorgui.h/cpp` : Generator GUI - `xxxgeneratorplugin.h/cpp` : Generator plugin manager - `xxxgenerator.pro` : Qt .pro file for Windows/Android build - - `xxxsink` : Interface to the outside (e.g xxx = udp): - - `xxxsink.h/cpp` : Inteface core - - `xxxsinkgui.h/cpp` : Interface GUI - - `xxxsinklugin/h/cpp` : Interface plugin manager - - `xxxsink.pro` : Qt .pro file for Windows/Android build + - `xxxsource` : Interface to the outside (e.g xxx = udp): + - `xxxsource.h/cpp` : Interface core + - `xxxsourcegui.h/cpp` : Interface GUI + - `xxxsourceplugin/h/cpp` : Interface plugin manager + - `xxxsource.pro` : Qt .pro file for Windows/Android build

Device interface and GUI lifecycle

@@ -153,9 +152,9 @@ The lifecycle of the GUI is controlled from the "Sampling Device Control" device - Remove the current device API from the relevant buddies lists. Buddies list are effective only for physical devices with SISO or MIMO architecture (more on that later) - Create the new device API - Add the new device API to the relevant devices APIs buddies list - - creates tne new GUI and hence new device interface. This will always open the physical device unless the physical device has a SISO or MIMO architecture + - Creates the new GUI and hence new device interface. This will always open the physical device unless the physical device has a SISO or MIMO architecture -Here is the relevant par ot the code (source side) in the `MainWindow::on_sampleSource_confirmClicked` method: +Here is the relevant part of the code (source side) in the `MainWindow::on_sampleSource_confirmClicked` method: deviceUI->m_deviceSourceAPI->stopAcquisition(); deviceUI->m_deviceSourceAPI->setSampleSourcePluginInstanceUI(0); // deletes old UI and input object @@ -182,11 +181,11 @@ Note that the following would also work for multiple sample channels Rx or Tx on In SDRangel there is a complete receiver or transmitter per I/Q sample flow. These transmitters and receivers are visually represented by the Rn and Tn tabs in the main window. They are created and disposed in the way explained in the previous paragraph using the source or sink selection confirmation button. In fact it acts as if each receiver or transmitter was controlled independently. In single input or single output (none at the moment) devices this is a true independence but with SISO or MIMO devices this cannot be the case and although each receiver or transmitter looks like it was handled independently there are things in common that have to be taken into account. For example in all cases the device handle should be unique and opening and closing the device has to be done only once per physical device usage cycle. -This is where the "buddies list" come into play. Receivers exhibit a generic interface in the form of the DeviceSourceAPI class and transmitter have the DeviceSinkAPI with the same role. Through these APIs some information and control can flow between receivers and trasmitters. The point is that all receivers and/or transmitters pertaining to the same physical device must "know" each other in order to be able to exchange information or control each other. For this purpose the DeviceSourceAPI or DeviceSinkAPI maintain a list of DeviceSourceAPI siblings and a list of DeviceSinkAPI siblings called "buddies". Thus any multi flow Rx/Tx configuration can be handled. +This is where the "buddies list" come into play. Receivers exhibit a generic interface in the form of the DeviceSourceAPI class and transmitter have the DeviceSinkAPI with the same role. Through these APIs some information and control can flow between receivers and transmitters. The point is that all receivers and/or transmitters pertaining to the same physical device must "know" each other in order to be able to exchange information or control each other. For this purpose the DeviceSourceAPI or DeviceSinkAPI maintain a list of DeviceSourceAPI siblings and a list of DeviceSinkAPI siblings called "buddies". Thus any multi flow Rx/Tx configuration can be handled. -The exact behaviour of these coupled receivers and/or transmitters is dependent on the hardware therefore a generic pointer attached to the API can be used to convey any kind of class or structure taylored for the exact hardware use case. Through this structure the state of the receiver or transmitter can be exposed therefore there is one structure per receiver and transmitter in the device interface. This structure may contain pointers to common areas (dynamically allocated) related to the physical device. One such "area" is the device handle which is present in all cases. +The exact behaviour of these coupled receivers and/or transmitters is dependent on the hardware therefore a generic pointer attached to the API can be used to convey any kind of class or structure tailored for the exact hardware use case. Through this structure the state of the receiver or transmitter can be exposed therefore there is one structure per receiver and transmitter in the device interface. This structure may contain pointers to common areas (dynamically allocated) related to the physical device. One such "area" is the device handle which is present in all cases. -Normally the first buddy would create the common areas (through new) and the last would delete them (through delete) and the indovidual structure (superstructure) would be on the stack of each buddy. Thus by copying this superstructure a buddy would gain access to common areas from another (already present) buddy along with static information from the other buddy (such as which hadrware Rx or Tx channel it uses in a MIMO architecture). Exchange of dynamic information between buddies is done using message passing. +Normally the first buddy would create the common areas (through new) and the last would delete them (through delete) and the individual structure (superstructure) would be on the stack of each buddy. Thus by copying this superstructure a buddy would gain access to common areas from another (already present) buddy along with static information from the other buddy (such as which hardware Rx or Tx channel it uses in a MIMO architecture). Exchange of dynamic information between buddies is done using message passing. The degree of entanglement between the different coupled flows in a single hardware can be very different: @@ -194,7 +193,7 @@ The degree of entanglement between the different coupled flows in a single hardw - independent Rx and Tx sample rates - independent Rx and Tx center frequencies - independent Gain, bandwidth, ... - - only the device handle and indication of the presence of the XB200 accesory board is common + - only the device handle and indication of the presence of the XB200 accessory board is common - HackRF: this is a half duplex device. Rx and Tx might appear as tightly coupled but since you can use only one or the other then in fact you can control them differently as this is done in sequence. In fact only the common device handle has to be taken care of diff --git a/ReadmeMacOS.md b/ReadmeMacOS.md index bc8523b79..3a916ce6a 100644 --- a/ReadmeMacOS.md +++ b/ReadmeMacOS.md @@ -16,7 +16,6 @@ SDRangel-3.x: + cm256cc + dsdcc + mbelib - + nanomsg + boost_1_64_0/ ### Environment preparation @@ -47,13 +46,6 @@ git clone https://github.com/f4exb/dsdcc.git ``` -##### nanomsg: -``` -git clone https://github.com/nanomsg/nanomsg.git -mkdir build && cd build -cmake -DCMAKE_INSTALL_PREFIX=/opt/local .. -cmake --build . && sudo cmake --build . --target install -``` ## Build Release build configuration with QT Creator diff --git a/ReadmeWindowsBuild.md b/ReadmeWindowsBuild.md index 65f05307b..97c9d92de 100644 --- a/ReadmeWindowsBuild.md +++ b/ReadmeWindowsBuild.md @@ -8,7 +8,7 @@ You should take note that the Windows scheduler is just a piece of crap and not There are no plugins for both flavours of Funcubes since it uses Alsa interface which is Linux exclusively. Changing for the Qt audio portable interface instead could be a solution that will be investigated in the future. -The SDRdaemon plug-in is present only in the 64 bit build version since version 1.1.4. The messaging system based on nanomsg works only in the 64 bit environment. However please be aware that the SDRdaemon plugin is not working well mainly due to the fact that it needs an OS with a decent scheduler and Windows is definitely not this sort of OS (see my previous warning). In fact depending on the case your mileage may vary however the Linux version works always beautifully so you know the options if you really want to use it! +Please be aware that the SDRdaemon plugin is not working well mainly due to the fact that it needs an OS with a decent scheduler and Windows is definitely not this sort of OS (see my previous warning). In fact depending on the case your mileage may vary however the Linux version works always beautifully so you know the options if you really want to use it!

Build environment

diff --git a/app/app.pro b/app/app.pro index 5a21d95e2..586909537 100644 --- a/app/app.pro +++ b/app/app.pro @@ -10,6 +10,7 @@ QMAKE_CXXFLAGS += -std=c++11 TEMPLATE = app TARGET = sdrangel +INCLUDEPATH += $$PWD/../exports INCLUDEPATH += $$PWD/../sdrbase INCLUDEPATH += $$PWD/../sdrgui INCLUDEPATH += $$PWD/../logging diff --git a/app/main.cpp b/app/main.cpp index 2ca68d006..8d1f8de47 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -35,7 +35,11 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo */ QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangel"); - QCoreApplication::setApplicationVersion("3.12.0"); + QCoreApplication::setApplicationVersion("4.3.0"); +#if QT_VERSION >= 0x050600 + QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); // DPI support + QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); //HiDPI pixmaps +#endif #if 1 qApp->setStyle(QStyleFactory::create("fusion")); diff --git a/appbench/main.cpp b/appbench/main.cpp new file mode 100644 index 000000000..1bb7f1f5f --- /dev/null +++ b/appbench/main.cpp @@ -0,0 +1,110 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// Swagger server adapter interface // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#include +#include +#include + +#include "loggerwithfile.h" +#include "mainbench.h" +#include "dsp/dsptypes.h" + +void handler(int sig) { + fprintf(stderr, "quit the application by signal(%d).\n", sig); + QCoreApplication::quit(); +} + +void catchUnixSignals(const std::vector& quitSignals) { + sigset_t blocking_mask; + sigemptyset(&blocking_mask); + + for (std::vector::const_iterator it = quitSignals.begin(); it != quitSignals.end(); ++it) { + sigaddset(&blocking_mask, *it); + } + + struct sigaction sa; + sa.sa_handler = handler; + sa.sa_mask = blocking_mask; + sa.sa_flags = 0; + + for (std::vector::const_iterator it = quitSignals.begin(); it != quitSignals.end(); ++it) { + sigaction(*it, &sa, 0); + } +} + +static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *logger) +{ + QCoreApplication a(argc, argv); + + QCoreApplication::setOrganizationName("f4exb"); + QCoreApplication::setApplicationName("SDRangelBench"); + QCoreApplication::setApplicationVersion("4.3.0"); + + int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; + std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); + catchUnixSignals(vsig); + + ParserBench parser; + parser.parse(a); + +#if QT_VERSION >= 0x050400 + qInfo("%s %s Qt %s %db %s %s DSP Rx:%db Tx:%db PID %lld", + qPrintable(QCoreApplication::applicationName()), + qPrintable(QCoreApplication::applicationVersion()), + qPrintable(QString(QT_VERSION_STR)), + QT_POINTER_SIZE*8, + qPrintable(QSysInfo::currentCpuArchitecture()), + qPrintable(QSysInfo::prettyProductName()), + SDR_RX_SAMP_SZ, + SDR_TX_SAMP_SZ, + QCoreApplication::applicationPid()); +#else + qInfo("%s %s Qt %s %db DSP Rx:%db Tx:%db PID %lld", + qPrintable(QCoreApplication::applicationName()), + qPrintable((QCoreApplication::>applicationVersion()), + qPrintable(QString(QT_VERSION_STR)), + QT_POINTER_SIZE*8, + SDR_RX_SAMP_SZ, + SDR_TX_SAMP_SZ, + QCoreApplication::applicationPid()); +#endif + + MainBench m(logger, parser, &a); + + // This will cause the application to exit when the main core is finished + QObject::connect(&m, SIGNAL(finished()), &a, SLOT(quit())); + // This will run the task from the application event loop + QTimer::singleShot(0, &m, SLOT(run())); + + return a.exec(); +} + +int main(int argc, char* argv[]) +{ + qtwebapp::LoggerWithFile *logger = new qtwebapp::LoggerWithFile(qApp); + logger->installMsgHandler(); + int res = runQtApplication(argc, argv, logger); + qWarning("SDRangel quit."); + return res; +} + + diff --git a/apple/deploy.sh b/apple/deploy.sh index d08248924..1837832e7 100755 --- a/apple/deploy.sh +++ b/apple/deploy.sh @@ -28,7 +28,6 @@ for f in `find plugins/samplesink/ -name '*.dylib'`; do cp -v $f "${APP_PLUGINS} for f in `find plugins/samplesource/ -name '*.dylib'`; do cp -v $f "${APP_PLUGINS}/samplesource/"; done cd $APP_LIB -cp /opt/local/lib/libnanomsg.5.0.0.dylib . ln -s libdsdcc.dylib libdsdcc.1.dylib ln -s libdevices.dylib libdevices.1.dylib ln -s libsdrbase.dylib libsdrbase.1.dylib diff --git a/appsrv/main.cpp b/appsrv/main.cpp index 3febc1a5c..e11724111 100644 --- a/appsrv/main.cpp +++ b/appsrv/main.cpp @@ -56,7 +56,7 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo QCoreApplication::setOrganizationName("f4exb"); QCoreApplication::setApplicationName("SDRangelSrv"); - QCoreApplication::setApplicationVersion("3.12.0"); + QCoreApplication::setApplicationVersion("4.3.0"); int catchSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; std::vector vsig(catchSignals, catchSignals + sizeof(catchSignals) / sizeof(int)); diff --git a/cm256cc/cm256cc.pro b/cm256cc/cm256cc.pro index bf9cb836c..26a533757 100644 --- a/cm256cc/cm256cc.pro +++ b/cm256cc/cm256cc.pro @@ -9,8 +9,8 @@ QT += core TEMPLATE = lib TARGET = cm256cc -CONFIG(MINGW32):LIBCM256CCSRC = "D:\softs\cm256cc" -CONFIG(MINGW64):LIBCM256CCSRC = "D:\softs\cm256cc" +CONFIG(MINGW32):LIBCM256CCSRC = "C:\softs\cm256cc" +CONFIG(MINGW64):LIBCM256CCSRC = "C:\softs\cm256cc" CONFIG(macx):LIBCM256CCSRC = "../../deps/cm256cc" INCLUDEPATH += $$LIBCM256CCSRC diff --git a/cmake/Modules/FindFFmpeg.cmake b/cmake/Modules/FindFFmpeg.cmake new file mode 100644 index 000000000..f97cfbd58 --- /dev/null +++ b/cmake/Modules/FindFFmpeg.cmake @@ -0,0 +1,151 @@ +# vim: ts=2 sw=2 +# - Try to find the required ffmpeg components(default: AVFORMAT, AVUTIL, AVCODEC) +# +# Once done this will define +# FFMPEG_FOUND - System has the all required components. +# FFMPEG_INCLUDE_DIRS - Include directory necessary for using the required components headers. +# FFMPEG_LIBRARIES - Link these to use the required ffmpeg components. +# FFMPEG_DEFINITIONS - Compiler switches required for using the required ffmpeg components. +# +# For each of the components it will additionally set. +# - AVCODEC +# - AVDEVICE +# - AVFORMAT +# - AVFILTER +# - AVUTIL +# - POSTPROC +# - SWSCALE +# the following variables will be defined +# _FOUND - System has +# _INCLUDE_DIRS - Include directory necessary for using the headers +# _LIBRARIES - Link these to use +# _DEFINITIONS - Compiler switches required for using +# _VERSION - The components version +# +# Copyright (c) 2006, Matthias Kretz, +# Copyright (c) 2008, Alexander Neundorf, +# Copyright (c) 2011, Michael Jansen, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +include(FindPackageHandleStandardArgs) + +# The default components were taken from a survey over other FindFFMPEG.cmake files +if (NOT FFmpeg_FIND_COMPONENTS) + set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL) +endif () + +# +### Macro: set_component_found +# +# Marks the given component as found if both *_LIBRARIES AND *_INCLUDE_DIRS is present. +# +macro(set_component_found _component ) + if (${_component}_LIBRARIES AND ${_component}_INCLUDE_DIRS) + # message(STATUS " - ${_component} found.") + set(${_component}_FOUND TRUE) + else () + # message(STATUS " - ${_component} not found.") + endif () +endmacro() + +# +### Macro: find_component +# +# Checks for the given component by invoking pkgconfig and then looking up the libraries and +# include directories. +# +macro(find_component _component _pkgconfig _library _header) + + if (NOT WIN32) + # use pkg-config to get the directories and then use these values + # in the FIND_PATH() and FIND_LIBRARY() calls + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_check_modules(PC_${_component} ${_pkgconfig}) + endif () + endif (NOT WIN32) + + find_path(${_component}_INCLUDE_DIRS ${_header} + HINTS + ${PC_LIB${_component}_INCLUDEDIR} + ${PC_LIB${_component}_INCLUDE_DIRS} + PATH_SUFFIXES + ffmpeg + ) + + find_library(${_component}_LIBRARIES NAMES ${_library} + HINTS + ${PC_LIB${_component}_LIBDIR} + ${PC_LIB${_component}_LIBRARY_DIRS} + ) + + set(${_component}_DEFINITIONS ${PC_${_component}_CFLAGS_OTHER} CACHE STRING "The ${_component} CFLAGS.") + set(${_component}_VERSION ${PC_${_component}_VERSION} CACHE STRING "The ${_component} version number.") + + set_component_found(${_component}) + + mark_as_advanced( + ${_component}_INCLUDE_DIRS + ${_component}_LIBRARIES + ${_component}_DEFINITIONS + ${_component}_VERSION) + +endmacro() + + +# Check for cached results. If there are skip the costly part. +if (NOT FFMPEG_LIBRARIES) + + # Check for all possible component. + find_component(AVCODEC libavcodec avcodec libavcodec/avcodec.h) + find_component(AVFORMAT libavformat avformat libavformat/avformat.h) + find_component(AVDEVICE libavdevice avdevice libavdevice/avdevice.h) + find_component(AVUTIL libavutil avutil libavutil/avutil.h) + find_component(AVFILTER libavfilter avfilter libavfilter/avfilter.h) + find_component(SWSCALE libswscale swscale libswscale/swscale.h) + find_component(POSTPROC libpostproc postproc libpostproc/postprocess.h) + find_component(SWRESAMPLE libswresample swresample libswresample/swresample.h) + + # Check if the required components were found and add their stuff to the FFMPEG_* vars. + foreach (_component ${FFmpeg_FIND_COMPONENTS}) + if (${_component}_FOUND) + # message(STATUS "Required component ${_component} present.") + set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${${_component}_LIBRARIES}) + set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} ${${_component}_DEFINITIONS}) + list(APPEND FFMPEG_INCLUDE_DIRS ${${_component}_INCLUDE_DIRS}) + else () + # message(STATUS "Required component ${_component} missing.") + endif () + endforeach () + + # Build the include path with duplicates removed. + if (FFMPEG_INCLUDE_DIRS) + list(REMOVE_DUPLICATES FFMPEG_INCLUDE_DIRS) + endif () + + # cache the vars. + set(FFMPEG_INCLUDE_DIRS ${FFMPEG_INCLUDE_DIRS} CACHE STRING "The FFmpeg include directories." FORCE) + set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} CACHE STRING "The FFmpeg libraries." FORCE) + set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} CACHE STRING "The FFmpeg cflags." FORCE) + + mark_as_advanced(FFMPEG_INCLUDE_DIRS + FFMPEG_LIBRARIES + FFMPEG_DEFINITIONS) + +endif () + +# Now set the noncached _FOUND vars for the components. +foreach (_component AVCODEC AVDEVICE AVFORMAT AVUTIL POSTPROCESS SWSCALE) + set_component_found(${_component}) +endforeach () + +# Compile the list of required vars +set(_FFmpeg_REQUIRED_VARS FFMPEG_LIBRARIES FFMPEG_INCLUDE_DIRS) +foreach (_component ${FFmpeg_FIND_COMPONENTS}) + list(APPEND _FFmpeg_REQUIRED_VARS ${_component}_LIBRARIES ${_component}_INCLUDE_DIRS) +endforeach () + +# Give a nice error message if some of the required vars are missing. +find_package_handle_standard_args(FFmpeg DEFAULT_MSG ${_FFmpeg_REQUIRED_VARS}) \ No newline at end of file diff --git a/cmake/Modules/FindLibHACKRF.cmake b/cmake/Modules/FindLibHACKRF.cmake index 7d0b3ddc7..fd84f91ca 100644 --- a/cmake/Modules/FindLibHACKRF.cmake +++ b/cmake/Modules/FindLibHACKRF.cmake @@ -1,6 +1,6 @@ if(NOT LIBHACKRF_FOUND) - pkg_check_modules (LIBHACKRF_PKG libairspy) + pkg_check_modules (LIBHACKRF_PKG libhackrf) find_path(LIBHACKRF_INCLUDE_DIR NAMES libhackrf/hackrf.h PATHS ${LIBHACKRF_PKG_INCLUDE_DIRS} diff --git a/cmake/Modules/FindLibNANOMSG.cmake b/cmake/Modules/FindLibNANOMSG.cmake deleted file mode 100644 index 63b8504e3..000000000 --- a/cmake/Modules/FindLibNANOMSG.cmake +++ /dev/null @@ -1,28 +0,0 @@ -if(NOT LIBNANOMSG_FOUND) - - pkg_check_modules (LIBNANOMSG_PKG libnanomsg) - find_path(LIBNANOMSG_INCLUDE_DIR NAMES nanomsg/nn.h - PATHS - ${LIBNANOMSG_PKG_INCLUDE_DIRS} - /usr/include - /usr/local/include - ) - - find_library(LIBNANOMSG_LIBRARIES NAMES nanomsg - PATHS - ${LIBNANOMSG_PKG_LIBRARY_DIRS} - /usr/lib - /usr/local/lib - ) - - if(LIBNANOMSG_INCLUDE_DIR AND LIBNANOMSG_LIBRARIES) - set(LIBNANOMSG_FOUND TRUE CACHE INTERNAL "libnanomsg found") - message(STATUS "Found libnanomsg: ${LIBNANOMSG_INCLUDE_DIR}, ${LIBNANOMSG_LIBRARIES}") - else(LIBNANOMSG_INCLUDE_DIR AND LIBNANOMSG_LIBRARIES) - set(LIBNANOMSG_FOUND FALSE CACHE INTERNAL "libnanomsg found") - message(STATUS "libnanomsg not found.") - endif(LIBNANOMSG_INCLUDE_DIR AND LIBNANOMSG_LIBRARIES) - - mark_as_advanced(LIBNANOMSG_INCLUDE_DIR LIBNANOMSG_LIBRARIES) - -endif(NOT LIBNANOMSG_FOUND) diff --git a/cmake/Modules/FindSoapySDR.cmake b/cmake/Modules/FindSoapySDR.cmake new file mode 100644 index 000000000..cf8e274c9 --- /dev/null +++ b/cmake/Modules/FindSoapySDR.cmake @@ -0,0 +1,34 @@ +# Find Soapy SDR + +if (NOT SOAPYSDR_INCLUDE_DIR) + FIND_PATH (SOAPYSDR_INCLUDE_DIR + NAMES SoapySDR/Version.h + PATHS + /usr/include + /usr/local/include + ) +endif() + +if (NOT SOAPYSDR_LIBRARY) + FIND_LIBRARY (SOAPYSDR_LIBRARY + NAMES SoapySDR + HINTS ${CMAKE_INSTALL_PREFIX}/lib + ${CMAKE_INSTALL_PREFIX}/lib64 + PATHS /usr/local/lib + /usr/local/lib64 + /usr/lib + /usr/lib64 + ) +endif() + +if (SOAPYSDR_INCLUDE_DIR AND SOAPYSDR_LIBRARY) + SET(SOAPYSDR_FOUND TRUE) +endif() + +if (SOAPYSDR_FOUND) + MESSAGE (STATUS "Found SoapySDR: ${SOAPYSDR_INCLUDE_DIR}, ${SOAPYSDR_LIBRARY}") +else() + MESSAGE (STATUS "Could not find SoapySDR") +endif() + +mark_as_advanced(SOAPYSDR_INCLUDE_DIR SOAPYSDR_LIBRARY) diff --git a/debian/changelog b/debian/changelog index 5978e8a35..91461fb1d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,10 +1,220 @@ +sdrangel (4.3.0-1) unstable; urgency=medium + + * SoapySDR support + * BladeRF2 corrections + + -- Edouard Griffiths, F4EXB Sun, 18 Nov 2018 21:14:18 +0100 + +sdrangel (4.2.4-1) unstable; urgency=medium + + * LimeSDR: use LimeSuite 18.10.0 for builds + * DSD demod: use 1 dB steps for squelch + * Scope: fixed some trigger issues. Fixes issue #233 + * Scope: implemented trigger holdoff. May fix more trigger issues. + + -- Edouard Griffiths, F4EXB Sat, 27 Oct 2018 21:14:18 +0200 + +sdrangel (4.2.3-1) unstable; urgency=medium + + * Scope: fixed channel rate affecting scope in memory mode. Issue #227 + * Spectrum: limit depth to 1000 when in moving average mode to avoid RAM exhaustion + * Spectrum: reworked phosphor display controls. Re-implements issue #207 + + -- Edouard Griffiths, F4EXB Fri, 19 Oct 2018 21:14:18 +0200 + +sdrangel (4.2.2-1) unstable; urgency=medium + + * Spectrum: option to get max over a number of FFTs. Implements issue #207 + * File Input: fixed wrong times displays due to 32 bit integer ovevlow. Issue #206 + * File Input: implemented play loop and playback acceleration + + -- Edouard Griffiths, F4EXB Sun, 14 Oct 2018 21:14:18 +0200 + +sdrangel (4.2.1-1) unstable; urgency=medium + + * FileRecord improvement with robust header and some fixes. Fixes issue #206 + * BladeRF2 MO Tx fix so that the two channels are used effectively. Fixes issue #225 + * NFM demod: set squelch step to 1 dB + + -- Edouard Griffiths, F4EXB Wed, 10 Oct 2018 21:14:18 +0200 + +sdrangel (4.2.0-1) unstable; urgency=medium + + * LibbladeRF 2.0 support with BladeRF Micro + * Scope: corrected trace memory index position + + -- Edouard Griffiths, F4EXB Sun, 7 Oct 2018 21:14:18 +0200 + +sdrangel (4.1.0-1) unstable; urgency=medium + + * Integrated SDRdaemon with a pair of new channel plugins + * Exchanged UDP sink and source names for better consistency + * Fixed AudioFifo to prevent deadlocks. Fixes issue #210 + + -- Edouard Griffiths, F4EXB Sun, 16 Sep 2018 21:14:18 +0200 + +sdrangel (4.0.7-1) unstable; urgency=medium + + * Scope: removed old scope objects + * Web API: reduced HTTP server debug messages + * Sink plugins: corrected name getters and setters + + -- Edouard Griffiths, F4EXB Sun, 19 Aug 2018 21:14:18 +0200 + +sdrangel (4.0.6-1) unstable; urgency=medium + + * Web API: RTL-SDR: fixed RF bandwidth setting + * Web API: enhanced DV serial and AM demod interfaces + * Web API: fixed bug in PUT/PATCH of modulators not setting differentially + * Fixed power display going to floor value in some demods + * SSB modulator: fixed sample not reset when no modulation is present + + -- Edouard Griffiths, F4EXB Tue, 07 Aug 2018 19:14:18 +0200 + +sdrangel (4.0.5-1) unstable; urgency=medium + + * Web API: handle pre-flight requests + + -- Edouard Griffiths, F4EXB Sun, 22 Jul 2018 09:14:18 +0200 + +sdrangel (4.0.4-1) unstable; urgency=medium + + * Fixed PlutoSDR output sample width. Fixes issue #198 + * Web API: implemented CORS + * Fix preset group delete not removing presets from the preset window + + -- Edouard Griffiths, F4EXB Wed, 18 Jul 2018 19:14:18 +0200 + +sdrangel (4.0.3-1) unstable; urgency=medium + + * Spectrum: linear mode for spectrum + * Scope: fixed power display overlay + + -- Edouard Griffiths, F4EXB Sun, 08 Jul 2018 15:14:18 +0200 + +sdrangel (4.0.2-1) unstable; urgency=medium + + * Spectrum: added averaging + + -- Edouard Griffiths, F4EXB Sun, 01 Jul 2018 21:14:18 +0200 + +sdrangel (4.0.1-1) unstable; urgency=medium + + * DSD demod: added NXDN support + * DATV demod: include it only if FFmpeg > 3.1 is installed + * Fixes for Arch. Manual merge of pull request #183 + * Scope: new magnitude squared projection mainly for radioastronomy + + -- Edouard Griffiths, F4EXB Sat, 23 Jun 2018 09:14:18 +0200 + +sdrangel (4.0.0-1) unstable; urgency=medium + + * Finalization of REST API and server instance + * Removal of old ChannelAnalyzer and TCPSrc plugins + * Renamed Channel Analyzer NG to Channel Analyzer + * DATV demod: added missing AVUTIL cmake variables + + -- Edouard Griffiths, F4EXB Sat, 09 Jun 2018 20:14:18 +0200 + +sdrangel (3.14.7-1) unstable; urgency=medium + + * ChanelAnalyzerNG: added PLL option and source selection with auto correlation + * RTL-SDR: fixed inf/sup decimators + * AM demod: syncrhronous AM detection option + + -- Edouard Griffiths, F4EXB Sun, 20 May 2018 20:14:18 +0200 + +sdrangel (3.14.6-1) unstable; urgency=medium + + * Fixed keyboard input for negative values on realtive integer value dials + * Get rid of ugly native dialogs + * Inf/Sup frequency shift scheme change to be closer to device center frequency + * PlutoSDR input: fixed Inf/Sup frequency shift calculation + * File record default file name with ISO datetime stamp + + -- Edouard Griffiths, F4EXB Fri, 11 May 2018 20:14:18 +0200 + +sdrangel (3.14.5-1) unstable; urgency=medium + + * DSD demod: allow audio rates integer multiples of 8k other than 48k + * Added a benchmark program testing decimators + * Optimization of decimators using even/odd technique + * SSB mod: fixed channel unregistration + * AM demod: fixed delayed squelch + + -- Edouard Griffiths, F4EXB Sun, 06 May 2018 20:14:18 +0200 + +sdrangel (3.14.4-1) unstable; urgency=medium + + * AM demod: squelch buffer to open at start of valid squelch + * NFM demod: same as AM with squelch noise tail cut + * SSB demod: squelch buffer to cut squelch noise tail + * DSD demod: squelch buffer to open at start of valid squelch not loosing any samples + + -- Edouard Griffiths, F4EXB Sun, 22 Apr 2018 17:14:18 +0200 + +sdrangel (3.14.3-1) unstable; urgency=medium + + * LimeSDR: compiled with LimeSuite release 18.04.1 + * LimeSDR: implemented transverter dialog (issue #157) + * UDP source and sink: make sure audio samples are always on 16 bits + * UDP source and sink: dialog elements for address and port + * Reviewed FFT destruction in many channel sources and sinks (issue #159) + + -- Edouard Griffiths, F4EXB Fri, 20 Apr 2018 20:14:18 +0200 + +sdrangel (3.14.2-1) unstable; urgency=medium + + * Web API: settings and report for all channel Tx plugins + * Server: AirspyHF, BladeRF and all channel Tx plugins support + * PVS-Studio static analysis corrections (4) + * NFM demod: fixed AF squelch and audio sample rate handling + * BFM demod: fixed segfault in RDS parser + + -- Edouard Griffiths, F4EXB Sun, 15 Apr 2018 12:14:18 +0200 + +sdrangel (3.14.1-1) unstable; urgency=medium + + * NFM: fixed lowpass filter initialization (CTCSS) + * DSD demod: set FM deviation independent from RF bandwidth + * DSD demod: implemented DMR negative with DSDcc v1.7.5 + * DSD demod: implemented dialog to view the log of status text messages + + -- Edouard Griffiths, F4EXB Sun, 01 Apr 2018 12:14:18 +0200 + +sdrangel (3.14.0-1) unstable; urgency=medium + + * New audio devices management + * DATV demod: fixed message handling and thus screen initialization issue + * Removed UDP/RTP copy audio from channel sink plugins entirely + * Removed UDP address and port from Channel marker + + -- Edouard Griffiths, F4EXB Fri, 30 Mar 2018 16:14:18 +0200 + +sdrangel (3.13.1-1) unstable; urgency=medium + + * Web API: settings and report enry points for AM demod and AirspyHF + * Web API: client Python script scanner example + * LimeSDR: fixed channelA/B frequency setting with latest LimeSuite + + -- Edouard Griffiths, F4EXB Sun, 25 Mar 2018 06:14:18 +0100 + +sdrangel (3.13.0-1) unstable; urgency=medium + + * DATV (Digital Amateur TV) demodulator. + * Option to use RTP protocol for UDP audio for AM, NFM, SSB, WFM. + * LimeSDR: show NCO and center frequency actual values + * DSD demod: new simplified symbol scope display. Reworked GUI. + + -- Edouard Griffiths, F4EXB Sun, 11 Mar 2018 06:14:18 +0100 + sdrangel (3.12.0-1) unstable; urgency=medium * Perseus support. * 24 bit Rx DSP Debian builds * DC and IQ correction fixes * AirspyHF: fall back to official library support - * Test source: implemented phase imbalance + * Test source: implemented phase imbalance -- Edouard Griffiths, F4EXB Sun, 11 Feb 2018 12:14:18 +0100 @@ -17,7 +227,7 @@ sdrangel (3.11.1-1) unstable; urgency=medium sdrangel (3.11.0-1) unstable; urgency=medium * AirspyHF: support - * Refactored 8 bit samples shifting during decimation (RTL-SDR and HackRF Rx) + * Refactored 8 bit samples shifting during decimation (RTL-SDR and HackRF Rx) * RTL-SDR: implemented RF filter control (tuner bandwidth) * Airspy, BladeRF, HackRF, PlutoSDR, RTLSDR, SDRPlay: fix for no decimation * Test source input plugin for test of software internals @@ -41,7 +251,7 @@ sdrangel (3.10.0-1) unstable; urgency=medium * AM, SSB demodulators and SSB modulator: fix sample rate handling * Enhancements to presets processing and GUI * Improved build and system info logging - * Web API: added function to set device set focus (GUI only) + * Web API: added function to set device set focus (GUI only) -- Edouard Griffiths, F4EXB Sun, 07 Jan 2018 09:14:18 +0100 @@ -58,7 +268,7 @@ sdrangel (3.9.0-1) unstable; urgency=medium * Server: proof of concept * DSD demodulator: added optional high pass filter on audio (uese dsdcc v1.7.3) - * Down/Up channelizers: enqeue MsgChannelizerNotification to sample sink/source + * Down/Up channelizers: enqeue MsgChannelizerNotification to sample sink/source * Separate channel sample rate and offset frequency this data from settings * Use specific method to apply channelizer sample rate and frequency offset changes @@ -181,14 +391,14 @@ sdrangel (3.7.1-1) unstable; urgency=medium sdrangel (3.7.0-1) unstable; urgency=medium - * PlutoSDR: Rx support + * PlutoSDR: Rx support * GUI segregation: preliminary works -- Edouard Griffiths, F4EXB Thu, 17 Sep 2017 23:14:18 +0200 sdrangel (3.6.1-1) unstable; urgency=medium - * Basic channel settings dialog with title+color update and UDP parameters + * Basic channel settings dialog with title+color update and UDP parameters * Applied to UDPSink, UDPSource, DSDDemod, AMDemod, BFMDemod, NFMDemod * DSD, AM, NFM, BFM demods: added possibility to send AF via UDP @@ -227,7 +437,7 @@ sdrangel (3.5.3-1) unstable; urgency=medium sdrangel (3.5.2-1) unstable; urgency=medium * HackRF: stop Rx before start Tx automatically and vice versa - * HackRF: added option on Rx to drive Tx frequency change + * HackRF: added option on Rx to drive Tx frequency change * SSB mod and demod: make UI displays consistent with DSB, USB and LSB modes -- Edouard Griffiths, F4EXB Sat, 22 Jul 2017 09:14:18 +0200 @@ -250,13 +460,13 @@ sdrangel (3.5.0-1) unstable; urgency=medium * Changed frequency thumbweels color scheme * Activated compiler warnings and fixed warnings * Lots of little GUI fixes - + -- Edouard Griffiths, F4EXB Mon, 11 Jun 2017 19:14:18 +0200 sdrangel (3.4.5-1) unstable; urgency=medium - * Removed default constuctors in Moving average and AGC classes - + * Removed default constuctors in Moving average and AGC classes + -- Edouard Griffiths, F4EXB Mon, 11 May 2017 21:14:18 +0100 sdrangel (3.4.4-1) unstable; urgency=medium @@ -266,14 +476,14 @@ sdrangel (3.4.4-1) unstable; urgency=medium * LimeSDR: Windows 64 build * LimeSDR: integrated Debian build * cmake modules: search lib64 libraries - + -- Edouard Griffiths, F4EXB Mon, 08 May 2017 21:14:18 +0100 sdrangel (3.4.3-1) unstable; urgency=medium * DSD demod: use version 1.7.1 of dsdcc with PLL for symbol synchronization as an option * LimeSDR: fixed antenna selection in both input and output plugins - + -- Edouard Griffiths, F4EXB Mon, 08 May 2017 23:14:18 +0100 sdrangel (3.4.2-1) unstable; urgency=medium @@ -281,7 +491,7 @@ sdrangel (3.4.2-1) unstable; urgency=medium * DSD demod: use version 1.7.0 of dsdcc with PLL for symbol synchronization * DSD demod: kernel >= 4.4.52 workaround for SerialDV * Code cleanup: cppchack and Eclipse warnings - + -- Edouard Griffiths, F4EXB Wed, 26 Apr 2017 23:14:18 +0100 sdrangel (3.4.1-1) unstable; urgency=medium @@ -290,7 +500,7 @@ sdrangel (3.4.1-1) unstable; urgency=medium * HackRF support: fixed start/stop sequence * WFM Demod enhancement * CW Keyer: specifiy char signedness to fix error with some compilers - + -- Edouard Griffiths, F4EXB Wed, 26 Apr 2017 23:14:18 +0100 sdrangel (3.4.0-1) unstable; urgency=medium diff --git a/debian/control b/debian/control index 5ae42f3a4..2da95a2eb 100644 --- a/debian/control +++ b/debian/control @@ -10,7 +10,7 @@ Homepage: https://github.com/f4exb/sdrangel Package: sdrangel Architecture: any -Depends: libc6, libasound2, libfftw3-single3, libgcc1, libgl1-mesa-glx, libnanomsg0|libnanomsg4, libqt5core5a, libqt5gui5, libqt5multimedia5, libqt5network5, libqt5opengl5, libqt5widgets5, libqt5multimedia5-plugins, libstdc++6, libusb-1.0-0, libopencv-dev, libsqlite3-dev, pulseaudio, libxml2, ${shlibs:Depends}, ${misc:Depends} +Depends: libc6, libasound2, libfftw3-single3, libgcc1, libgl1-mesa-glx, libqt5core5a, libqt5gui5, libqt5multimedia5, libqt5network5, libqt5opengl5, libqt5widgets5, libqt5multimedia5-plugins, libstdc++6, libusb-1.0-0, libopencv-dev, pulseaudio, libxml2, ffmpeg, libavcodec-dev, libavformat-dev, ${shlibs:Depends}, ${misc:Depends} Description: SDR/Analyzer/Generator front-end for various hardware SDR/Analyzer/Generator front-end for Airspy, BladeRF, HackRF, RTL-SDR, FunCube, LimeSDR, PlutoSDR. Also File source and sink for I/Q samples, network I/Q sources with SDRDaemon. diff --git a/devices/CMakeLists.txt b/devices/CMakeLists.txt index 66429d0c1..9626e5db9 100644 --- a/devices/CMakeLists.txt +++ b/devices/CMakeLists.txt @@ -3,15 +3,18 @@ project(devices) find_package(LibUSB) if (BUILD_DEBIAN) - add_subdirectory(bladerf) + add_subdirectory(bladerf1) + add_subdirectory(bladerf2) add_subdirectory(hackrf) add_subdirectory(limesdr) add_subdirectory(perseus) add_subdirectory(plutosdr) + add_subdirectory(soapysdr) else(BUILD_DEBIAN) find_package(LibBLADERF) if(LIBUSB_FOUND AND LIBBLADERF_FOUND) - add_subdirectory(bladerf) + add_subdirectory(bladerf1) + add_subdirectory(bladerf2) endif(LIBUSB_FOUND AND LIBBLADERF_FOUND) find_package(LibHACKRF) @@ -34,4 +37,8 @@ else(BUILD_DEBIAN) add_subdirectory(perseus) endif() + find_package(SoapySDR) + if(LIBUSB_FOUND AND SOAPYSDR_FOUND) + add_subdirectory(soapysdr) + endif() endif (BUILD_DEBIAN) diff --git a/devices/bladerf1/CMakeLists.txt b/devices/bladerf1/CMakeLists.txt new file mode 100644 index 000000000..ddb327d5f --- /dev/null +++ b/devices/bladerf1/CMakeLists.txt @@ -0,0 +1,50 @@ +project(bladerf1device) + +set(bladerf1device_SOURCES + devicebladerf1.cpp + devicebladerf1values.cpp + devicebladerf1shared.cpp +) + +set(bladerf1device_HEADERS + devicebladerf1.h + devicebladerf1values.h + devicebladerf1param.h + devicebladerf1shared.h +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${LIBBLADERFLIBSRC}/include + ${LIBBLADERFLIBSRC}/src +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${LIBBLADERF_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +#add_definitions(${QT_DEFINITIONS}) +#add_definitions(-DQT_SHARED) + +add_library(bladerf1device SHARED + ${bladerf1device_SOURCES} +) + +if (BUILD_DEBIAN) +target_link_libraries(bladerf1device + bladerf + sdrbase +) +else (BUILD_DEBIAN) +target_link_libraries(bladerf1device + ${LIBBLADERF_LIBRARIES} + sdrbase +) +endif (BUILD_DEBIAN) + +install(TARGETS bladerf1device DESTINATION lib) diff --git a/devices/bladerf/devicebladerf.cpp b/devices/bladerf1/devicebladerf1.cpp similarity index 66% rename from devices/bladerf/devicebladerf.cpp rename to devices/bladerf1/devicebladerf1.cpp index ffa065cb4..14ab43374 100644 --- a/devices/bladerf/devicebladerf.cpp +++ b/devices/bladerf1/devicebladerf1.cpp @@ -14,17 +14,20 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include "../bladerf1/devicebladerf1.h" + +#include + #include #include -#include "devicebladerf.h" -bool DeviceBladeRF::open_bladerf(struct bladerf **dev, const char *serial) +bool DeviceBladeRF1::open_bladerf(struct bladerf **dev, const char *serial) { int fpga_loaded; if ((*dev = open_bladerf_from_serial(serial)) == 0) { - fprintf(stderr, "DeviceBladeRF::open_bladerf: could not open BladeRF\n"); + qCritical("DeviceBladeRF::open_bladerf: could not open BladeRF"); return false; } @@ -32,20 +35,20 @@ bool DeviceBladeRF::open_bladerf(struct bladerf **dev, const char *serial) if (fpga_loaded < 0) { - fprintf(stderr, "DeviceBladeRF::open_bladerf: failed to check FPGA state: %s\n", - bladerf_strerror(fpga_loaded)); + qCritical("DeviceBladeRF::open_bladerf: failed to check FPGA state: %s", + bladerf_strerror(fpga_loaded)); return false; } else if (fpga_loaded == 0) { - fprintf(stderr, "BladerfOutput::start: the device's FPGA is not loaded.\n"); + qCritical("BladerfOutput::start: the device's FPGA is not loaded."); return false; } return true; } -struct bladerf *DeviceBladeRF::open_bladerf_from_serial(const char *serial) +struct bladerf *DeviceBladeRF1::open_bladerf_from_serial(const char *serial) { int status; struct bladerf *dev; @@ -69,12 +72,12 @@ struct bladerf *DeviceBladeRF::open_bladerf_from_serial(const char *serial) if (status == BLADERF_ERR_NODEV) { - fprintf(stderr, "DeviceBladeRF::open_bladerf_from_serial: No devices available with serial=%s\n", serial); + qCritical("DeviceBladeRF::open_bladerf_from_serial: No devices available with serial %s", serial); return 0; } else if (status != 0) { - fprintf(stderr, "DeviceBladeRF::open_bladerf_from_serial: Failed to open device with serial=%s (%s)\n", + qCritical("DeviceBladeRF::open_bladerf_from_serial: Failed to open device with serial %s (%s)", serial, bladerf_strerror(status)); return 0; } @@ -84,61 +87,6 @@ struct bladerf *DeviceBladeRF::open_bladerf_from_serial(const char *serial) } } -const unsigned int BladerfSampleRates::m_nb_rates = 22; -const unsigned int BladerfSampleRates::m_rates[BladerfSampleRates::m_nb_rates] = { - 1536000, - 1600000, - 2000000, - 2304000, - 2400000, - 3072000, - 3200000, - 4333333, // for GSM - 4608000, - 4800000, - 6144000, - 7680000, - 9216000, - 9600000, - 10752000, - 12288000, - 18432000, - 19200000, - 24576000, - 30720000, - 36864000, - 39936000}; - -unsigned int BladerfSampleRates::getRate(unsigned int rate_index) -{ - if (rate_index < m_nb_rates) - { - return m_rates[rate_index]; - } - else - { - return m_rates[0]; - } -} - -unsigned int BladerfSampleRates::getRateIndex(unsigned int rate) -{ - for (unsigned int i=0; i < m_nb_rates; i++) - { - if (rate == m_rates[i]) - { - return i; - } - } - - return 0; -} - -unsigned int BladerfSampleRates::getNbRates() -{ - return BladerfSampleRates::m_nb_rates; -} - const unsigned int BladerfBandwidths::m_nb_halfbw = 16; const unsigned int BladerfBandwidths::m_halfbw[BladerfBandwidths::m_nb_halfbw] = { 750, diff --git a/devices/bladerf/devicebladerf.h b/devices/bladerf1/devicebladerf1.h similarity index 86% rename from devices/bladerf/devicebladerf.h rename to devices/bladerf1/devicebladerf1.h index 5ef31f3b8..f8a9d1cad 100644 --- a/devices/bladerf/devicebladerf.h +++ b/devices/bladerf1/devicebladerf1.h @@ -19,7 +19,9 @@ #include -class DeviceBladeRF +#include "export.h" + +class DEVICES_API DeviceBladeRF1 { public: static bool open_bladerf(struct bladerf **dev, const char *serial); @@ -28,16 +30,6 @@ private: static struct bladerf *open_bladerf_from_serial(const char *serial); }; -class BladerfSampleRates { -public: - static unsigned int getRate(unsigned int rate_index); - static unsigned int getRateIndex(unsigned int rate); - static unsigned int getNbRates(); -private: - static const unsigned int m_nb_rates; - static const unsigned int m_rates[]; -}; - class BladerfBandwidths { public: static unsigned int getBandwidth(unsigned int bandwidth_index); diff --git a/devices/bladerf/devicebladerfparam.h b/devices/bladerf1/devicebladerf1param.h similarity index 89% rename from devices/bladerf/devicebladerfparam.h rename to devices/bladerf1/devicebladerf1param.h index 8bd4349ec..ad76a67f9 100644 --- a/devices/bladerf/devicebladerfparam.h +++ b/devices/bladerf1/devicebladerf1param.h @@ -14,8 +14,8 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef DEVICES_BLADERF_DEVICEBLADERFPARAM_H_ -#define DEVICES_BLADERF_DEVICEBLADERFPARAM_H_ +#ifndef DEVICES_BLADERF1_DEVICEBLADERF1PARAM_H_ +#define DEVICES_BLADERF1_DEVICEBLADERF1PARAM_H_ #include @@ -23,16 +23,16 @@ * This structure is owned by each of the parties sharing the same physical device * It allows exchange of information on the common resources */ -struct DeviceBladeRFParams +struct DeviceBladeRF1Params { struct bladerf *m_dev; //!< device handle if the party has ownership else 0 bool m_xb200Attached; //!< true if XB200 is attached and owned by the party - DeviceBladeRFParams() : + DeviceBladeRF1Params() : m_dev(0), m_xb200Attached(false) { } }; -#endif /* DEVICES_BLADERF_DEVICEBLADERFPARAM_H_ */ +#endif /* DEVICES_BLADERF1_DEVICEBLADERF1PARAM_H_ */ diff --git a/devices/bladerf/devicebladerfshared.cpp b/devices/bladerf1/devicebladerf1shared.cpp similarity index 80% rename from devices/bladerf/devicebladerfshared.cpp rename to devices/bladerf1/devicebladerf1shared.cpp index 636318a00..361e430ec 100644 --- a/devices/bladerf/devicebladerfshared.cpp +++ b/devices/bladerf1/devicebladerf1shared.cpp @@ -14,8 +14,8 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "devicebladerfshared.h" +#include "../bladerf1/devicebladerf1shared.h" -const float DeviceBladeRFShared::m_sampleFifoLengthInSeconds = 0.25; -const int DeviceBladeRFShared::m_sampleFifoMinSize = 75000; // 300 kS/s knee -const int DeviceBladeRFShared::m_sampleFifoMinSize32 = 150000; // Fixed for interpolation by 32 +const float DeviceBladeRF1Shared::m_sampleFifoLengthInSeconds = 0.25; +const int DeviceBladeRF1Shared::m_sampleFifoMinSize = 75000; // 300 kS/s knee +const int DeviceBladeRF1Shared::m_sampleFifoMinSize32 = 150000; // Fixed for interpolation by 32 diff --git a/devices/bladerf/devicebladerfshared.h b/devices/bladerf1/devicebladerf1shared.h similarity index 96% rename from devices/bladerf/devicebladerfshared.h rename to devices/bladerf1/devicebladerf1shared.h index 4ad706744..cc893c77a 100644 --- a/devices/bladerf/devicebladerfshared.h +++ b/devices/bladerf1/devicebladerf1shared.h @@ -18,8 +18,9 @@ #define DEVICES_BLADERF_DEVICEHACKRFSHARED_H_ #include "util/message.h" +#include "export.h" -class DeviceBladeRFShared +class DEVICES_API DeviceBladeRF1Shared { public: static const float m_sampleFifoLengthInSeconds; diff --git a/devices/bladerf/devicebladerfvalues.cpp b/devices/bladerf1/devicebladerf1values.cpp similarity index 80% rename from devices/bladerf/devicebladerfvalues.cpp rename to devices/bladerf1/devicebladerf1values.cpp index fb44ad6c2..522953ae0 100644 --- a/devices/bladerf/devicebladerfvalues.cpp +++ b/devices/bladerf1/devicebladerf1values.cpp @@ -14,11 +14,11 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "devicebladerfvalues.h" +#include "../bladerf1/devicebladerf1values.h" -unsigned int DeviceBladeRFBandwidths::m_nb_halfbw = 16; -unsigned int DeviceBladeRFBandwidths::m_halfbw[] = { +unsigned int DeviceBladeRF1Bandwidths::m_nb_halfbw = 16; +unsigned int DeviceBladeRF1Bandwidths::m_halfbw[] = { 750, 875, 1250, @@ -36,7 +36,7 @@ unsigned int DeviceBladeRFBandwidths::m_halfbw[] = { 10000, 14000}; -unsigned int DeviceBladeRFBandwidths::getBandwidth(unsigned int bandwidth_index) +unsigned int DeviceBladeRF1Bandwidths::getBandwidth(unsigned int bandwidth_index) { if (bandwidth_index < m_nb_halfbw) { @@ -48,7 +48,7 @@ unsigned int DeviceBladeRFBandwidths::getBandwidth(unsigned int bandwidth_index) } } -unsigned int DeviceBladeRFBandwidths::getBandwidthIndex(unsigned int bandwidth) +unsigned int DeviceBladeRF1Bandwidths::getBandwidthIndex(unsigned int bandwidth) { for (unsigned int i=0; i < m_nb_halfbw; i++) { @@ -61,9 +61,9 @@ unsigned int DeviceBladeRFBandwidths::getBandwidthIndex(unsigned int bandwidth) return 0; } -unsigned int DeviceBladeRFBandwidths::getNbBandwidths() +unsigned int DeviceBladeRF1Bandwidths::getNbBandwidths() { - return DeviceBladeRFBandwidths::m_nb_halfbw; + return DeviceBladeRF1Bandwidths::m_nb_halfbw; } diff --git a/devices/bladerf/devicebladerfvalues.h b/devices/bladerf1/devicebladerf1values.h similarity index 87% rename from devices/bladerf/devicebladerfvalues.h rename to devices/bladerf1/devicebladerf1values.h index 40efc736c..83afa961b 100644 --- a/devices/bladerf/devicebladerfvalues.h +++ b/devices/bladerf1/devicebladerf1values.h @@ -14,11 +14,12 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef DEVICES_BLADERF_DEVICEBLADERFVALUES_H_ -#define DEVICES_BLADERF_DEVICEBLADERFVALUES_H_ +#ifndef DEVICES_BLADERF1_DEVICEBLADERF1VALUES_H_ +#define DEVICES_BLADERF1_DEVICEBLADERF1VALUES_H_ +#include "export.h" -class DeviceBladeRFBandwidths { +class DEVICES_API DeviceBladeRF1Bandwidths { public: static unsigned int getBandwidth(unsigned int bandwidth_index); static unsigned int getBandwidthIndex(unsigned int bandwidth); @@ -28,4 +29,4 @@ private: static unsigned int m_nb_halfbw; }; -#endif /* DEVICES_BLADERF_DEVICEBLADERFVALUES_H_ */ +#endif /* DEVICES_BLADERF1_DEVICEBLADERF1VALUES_H_ */ diff --git a/devices/bladerf/CMakeLists.txt b/devices/bladerf2/CMakeLists.txt similarity index 52% rename from devices/bladerf/CMakeLists.txt rename to devices/bladerf2/CMakeLists.txt index 1d698cbdd..2b0c96490 100644 --- a/devices/bladerf/CMakeLists.txt +++ b/devices/bladerf2/CMakeLists.txt @@ -1,16 +1,13 @@ -project(bladerfdevice) +project(bladerf2device) -set(bladerfdevice_SOURCES - devicebladerf.cpp - devicebladerfvalues.cpp - devicebladerfshared.cpp +set(bladerf2device_SOURCES + devicebladerf2.cpp + devicebladerf2shared.cpp ) -set(bladerfdevice_HEADERS - devicebladerf.h - devicebladerfvalues.h - devicebladerfparam.h - devicebladerfshared.h +set(bladerf2device_HEADERS + devicebladerf2.h + devicebladerf2shared.h ) if (BUILD_DEBIAN) @@ -31,20 +28,20 @@ endif (BUILD_DEBIAN) #add_definitions(${QT_DEFINITIONS}) #add_definitions(-DQT_SHARED) -add_library(bladerfdevice SHARED - ${bladerfdevice_SOURCES} +add_library(bladerf2device SHARED + ${bladerf2device_SOURCES} ) if (BUILD_DEBIAN) -target_link_libraries(bladerfdevice +target_link_libraries(bladerf2device bladerf sdrbase ) else (BUILD_DEBIAN) -target_link_libraries(bladerfdevice +target_link_libraries(bladerf2device ${LIBBLADERF_LIBRARIES} sdrbase ) endif (BUILD_DEBIAN) -install(TARGETS bladerfdevice DESTINATION lib) +install(TARGETS bladerf2device DESTINATION lib) diff --git a/devices/bladerf2/devicebladerf2.cpp b/devices/bladerf2/devicebladerf2.cpp new file mode 100644 index 000000000..e0db04adf --- /dev/null +++ b/devices/bladerf2/devicebladerf2.cpp @@ -0,0 +1,504 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016-2017 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#include + +#include "devicebladerf2.h" + +DeviceBladeRF2::DeviceBladeRF2() : + m_dev(0), + m_nbRxChannels(0), + m_nbTxChannels(0), + m_rxOpen(0), + m_txOpen(0) +{} + +DeviceBladeRF2::~DeviceBladeRF2() +{ + if (m_dev) + { + bladerf_close(m_dev); + m_dev = 0; + } + + if (m_rxOpen) { + delete[] m_rxOpen; + } + + if (m_txOpen) { + delete[] m_txOpen; + } +} + +bool DeviceBladeRF2::open(const char *serial) +{ + int fpga_loaded; + + if ((m_dev = open_bladerf_from_serial(serial)) == 0) + { + qCritical("DeviceBladeRF2::open: could not open BladeRF"); + return false; + } + + fpga_loaded = bladerf_is_fpga_configured(m_dev); + + if (fpga_loaded < 0) + { + qCritical("DeviceBladeRF2::open: failed to check FPGA state: %s", + bladerf_strerror(fpga_loaded)); + return false; + } + else if (fpga_loaded == 0) + { + qCritical("DeviceBladeRF2::open: the device's FPGA is not loaded."); + return false; + } + + m_nbRxChannels = bladerf_get_channel_count(m_dev, BLADERF_RX); + m_nbTxChannels = bladerf_get_channel_count(m_dev, BLADERF_TX); + + m_rxOpen = new bool[m_nbRxChannels]; + m_txOpen = new bool[m_nbTxChannels]; + std::fill(m_rxOpen, m_rxOpen + m_nbRxChannels, false); + std::fill(m_txOpen, m_txOpen + m_nbTxChannels, false); + + return true; +} + +void DeviceBladeRF2::close() +{ + if (m_dev) + { + bladerf_close(m_dev); + m_dev = 0; + } +} + +struct bladerf *DeviceBladeRF2::open_bladerf_from_serial(const char *serial) +{ + int status; + struct bladerf *dev; + struct bladerf_devinfo info; + + /* Initialize all fields to "don't care" wildcard values. + * + * Immediately passing this to bladerf_open_with_devinfo() would cause + * libbladeRF to open any device on any available backend. */ + bladerf_init_devinfo(&info); + + /* Specify the desired device's serial number, while leaving all other + * fields in the info structure wildcard values */ + if (serial != 0) + { + strncpy(info.serial, serial, BLADERF_SERIAL_LENGTH - 1); + info.serial[BLADERF_SERIAL_LENGTH - 1] = '\0'; + } + + status = bladerf_open_with_devinfo(&dev, &info); + + if (status == BLADERF_ERR_NODEV) + { + qCritical("DeviceBladeRF2::open_bladerf_from_serial: No devices available with serial %s", serial); + return 0; + } + else if (status != 0) + { + qCritical("DeviceBladeRF2::open_bladerf_from_serial: Failed to open device with serial %s (%s)", + serial, bladerf_strerror(status)); + return 0; + } + else + { + return dev; + } +} + +bool DeviceBladeRF2::openRx(int channel) +{ + if (!m_dev) { + return false; + } + + if ((channel < 0) || (channel >= m_nbRxChannels)) + { + qCritical("DeviceBladeRF2::openRx: invalid Rx channel index %d", channel); + return false; + } + + int status; + + if (!m_rxOpen[channel]) + { + status = bladerf_enable_module(m_dev, BLADERF_CHANNEL_RX(channel), true); + + if (status < 0) + { + qCritical("DeviceBladeRF2::openRx: failed to enable Rx channel %d: %s", + channel, bladerf_strerror(status)); + return false; + } + else + { + qDebug("DeviceBladeRF2::openRx: Rx channel %d enabled", channel); + m_rxOpen[channel] = true; + return true; + } + } + else + { + qDebug("DeviceBladeRF2::openRx: Rx channel %d already opened", channel); + return true; + } +} + +bool DeviceBladeRF2::openTx(int channel) +{ + if (!m_dev) { + return false; + } + + if ((channel < 0) || (channel >= m_nbTxChannels)) + { + qCritical("DeviceBladeRF2::openTx: invalid Tx channel index %d", channel); + return false; + } + + int status; + + if (!m_txOpen[channel]) + { + status = bladerf_enable_module(m_dev, BLADERF_CHANNEL_TX(channel), true); + + if (status < 0) + { + qCritical("DeviceBladeRF2::openTx: Failed to enable Tx channel %d: %s", + channel, bladerf_strerror(status)); + return false; + } + else + { + qDebug("DeviceBladeRF2::openTx: Tx channel %d enabled", channel); + m_txOpen[channel] = true; + return true; + } + } + else + { + qDebug("DeviceBladeRF2::openTx: Tx channel %d already opened", channel); + return true; + } +} + +void DeviceBladeRF2::closeRx(int channel) +{ + if (!m_dev) { + return; + } + + if ((channel < 0) || (channel >= m_nbRxChannels)) + { + qCritical("DeviceBladeRF2::closeRx: invalid Rx channel index %d", channel); + return; + } + + if (m_rxOpen[channel]) + { + int status = bladerf_enable_module(m_dev, BLADERF_CHANNEL_RX(channel), false); + m_rxOpen[channel] = false; + + if (status < 0) { + qCritical("DeviceBladeRF2::closeRx: failed to disable Rx channel %d: %s", channel, bladerf_strerror(status)); + } else { + qDebug("DeviceBladeRF2::closeRx: Rx channel %d disabled", channel); + } + } + else + { + qDebug("DeviceBladeRF2::closeRx: Rx channel %d already closed", channel); + } +} + +void DeviceBladeRF2::closeTx(int channel) +{ + if (!m_dev) { + return; + } + + if ((channel < 0) || (channel >= m_nbTxChannels)) + { + qCritical("DeviceBladeRF2::closeTx: invalid Tx channel index %d", channel); + return; + } + + if (m_txOpen[channel]) + { + int status = bladerf_enable_module(m_dev, BLADERF_CHANNEL_TX(channel), false); + m_txOpen[channel] = false; + + if (status < 0) { + qCritical("DeviceBladeRF2::closeTx: failed to disable Tx channel %d: %s", channel, bladerf_strerror(status)); + } else { + qDebug("DeviceBladeRF2::closeTx: Tx channel %d disabled", channel); + } + } + else + { + qDebug("DeviceBladeRF2::closeTx: Rx channel %d already closed", channel); + } +} + +void DeviceBladeRF2::getFrequencyRangeRx(uint64_t& min, uint64_t& max, int& step) +{ + if (m_dev) + { + const struct bladerf_range *range; + int status; + + status = bladerf_get_frequency_range(m_dev, BLADERF_CHANNEL_RX(0), &range); + + if (status < 0) + { + qCritical("DeviceBladeRF2::getFrequencyRangeRx: Failed to get Rx frequency range: %s", + bladerf_strerror(status)); + } + else + { + min = range->min; + max = range->max; + step = range->step; + } + } +} + +void DeviceBladeRF2::getFrequencyRangeTx(uint64_t& min, uint64_t& max, int& step) +{ + if (m_dev) + { + const struct bladerf_range *range; + int status; + + status = bladerf_get_frequency_range(m_dev, BLADERF_CHANNEL_TX(0), &range); + + if (status < 0) + { + qCritical("DeviceBladeRF2::getFrequencyRangeTx: Failed to get Tx frequency range: %s", + bladerf_strerror(status)); + } + else + { + min = range->min; + max = range->max; + step = range->step; + } + } +} + +void DeviceBladeRF2::getSampleRateRangeRx(int& min, int& max, int& step) +{ + if (m_dev) + { + const struct bladerf_range *range; + int status; + + status = bladerf_get_sample_rate_range(m_dev, BLADERF_CHANNEL_RX(0), &range); + + if (status < 0) + { + qCritical("DeviceBladeRF2::getSampleRateRangeRx: Failed to get Rx sample rate range: %s", + bladerf_strerror(status)); + } + else + { + min = range->min; + max = range->max; + step = range->step; + } + } +} + +void DeviceBladeRF2::getSampleRateRangeTx(int& min, int& max, int& step) +{ + if (m_dev) + { + const struct bladerf_range *range; + int status; + + status = bladerf_get_sample_rate_range(m_dev, BLADERF_CHANNEL_TX(0), &range); + + if (status < 0) + { + qCritical("DeviceBladeRF2::getSampleRateRangeTx: Failed to get Tx sample rate range: %s", + bladerf_strerror(status)); + } + else + { + min = range->min; + max = range->max; + step = range->step; + } + } + +} + +void DeviceBladeRF2::getBandwidthRangeRx(int& min, int& max, int& step) +{ + if (m_dev) + { + const struct bladerf_range *range; + int status; + + status = bladerf_get_bandwidth_range(m_dev, BLADERF_CHANNEL_RX(0), &range); + + if (status < 0) + { + qCritical("DeviceBladeRF2::getBandwidthRangeRx: Failed to get Rx bandwidth range: %s", + bladerf_strerror(status)); + } + else + { + min = range->min; + max = range->max; + step = range->step; + } + } +} + +void DeviceBladeRF2::getBandwidthRangeTx(int& min, int& max, int& step) +{ + if (m_dev) + { + const struct bladerf_range *range; + int status; + + status = bladerf_get_bandwidth_range(m_dev, BLADERF_CHANNEL_TX(0), &range); + + if (status < 0) + { + qCritical("DeviceBladeRF2::getBandwidthRangeTx: Failed to get Tx bandwidth range: %s", + bladerf_strerror(status)); + } + else + { + min = range->min; + max = range->max; + step = range->step; + } + } +} + +void DeviceBladeRF2::getGlobalGainRangeRx(int& min, int& max, int& step) +{ + if (m_dev) + { + const struct bladerf_range *range; + int status; + + status = bladerf_get_gain_range(m_dev, BLADERF_CHANNEL_RX(0), &range); + + if (status < 0) + { + qCritical("DeviceBladeRF2::getGlobalGainRangeRx: Failed to get Rx global gain range: %s", + bladerf_strerror(status)); + } + else + { + min = range->min; + max = range->max; + step = range->step; + } + } +} + +void DeviceBladeRF2::getGlobalGainRangeTx(int& min, int& max, int& step) +{ + if (m_dev) + { + const struct bladerf_range *range; + int status; + + status = bladerf_get_gain_range(m_dev, BLADERF_CHANNEL_TX(0), &range); + + if (status < 0) + { + qCritical("DeviceBladeRF2::getGlobalGainRangeTx: Failed to get Tx global gain range: %s", + bladerf_strerror(status)); + } + else + { + min = range->min; + max = range->max; + step = range->step; + } + } +} + +int DeviceBladeRF2::getGainModesRx(const bladerf_gain_modes **modes) +{ + if (m_dev) + { + int n = bladerf_get_gain_modes(m_dev, BLADERF_CHANNEL_RX(0), 0); + + if (n < 0) + { + qCritical("DeviceBladeRF2::getGainModesRx: Failed to get the number of Rx gain modes: %s", bladerf_strerror(n)); + return 0; + } + + int status = bladerf_get_gain_modes(m_dev, BLADERF_CHANNEL_RX(0), modes); + + if (status < 0) + { + qCritical("DeviceBladeRF2::getGainModesRx: Failed to get Rx gain modes: %s", bladerf_strerror(status)); + return 0; + } + else + { + return n; + } + } + else + { + return 0; + } +} + +void DeviceBladeRF2::setBiasTeeRx(bool enable) +{ + if (m_dev) + { + int status = bladerf_set_bias_tee(m_dev, BLADERF_CHANNEL_RX(0), enable); + + if (status < 0) { + qCritical("DeviceBladeRF2::setBiasTeeRx: Failed to set Rx bias tee: %s", bladerf_strerror(status)); + } + } +} + +void DeviceBladeRF2::setBiasTeeTx(bool enable) +{ + if (m_dev) + { + int status = bladerf_set_bias_tee(m_dev, BLADERF_CHANNEL_TX(0), enable); + + if (status < 0) { + qCritical("DeviceBladeRF2::setBiasTeeTx: Failed to set Tx bias tee: %s", bladerf_strerror(status)); + } + } +} diff --git a/devices/bladerf2/devicebladerf2.h b/devices/bladerf2/devicebladerf2.h new file mode 100644 index 000000000..a19147909 --- /dev/null +++ b/devices/bladerf2/devicebladerf2.h @@ -0,0 +1,67 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef DEVICES_BLADERF2_DEVICEBLADERF2_H_ +#define DEVICES_BLADERF2_DEVICEBLADERF2_H_ + +#include +#include + +#include "export.h" + +class DEVICES_API DeviceBladeRF2 +{ +public: + DeviceBladeRF2(); + ~DeviceBladeRF2(); + + bool open(const char *serial); + void close(); + + bladerf *getDev() { return m_dev; } + + bool openRx(int channel); + bool openTx(int channel); + void closeRx(int channel); + void closeTx(int channel); + + void getFrequencyRangeRx(uint64_t& min, uint64_t& max, int& step); + void getFrequencyRangeTx(uint64_t& min, uint64_t& max, int& step); + void getSampleRateRangeRx(int& min, int& max, int& step); + void getSampleRateRangeTx(int& min, int& max, int& step); + void getBandwidthRangeRx(int& min, int& max, int& step); + void getBandwidthRangeTx(int& min, int& max, int& step); + void getGlobalGainRangeRx(int& min, int& max, int& step); + void getGlobalGainRangeTx(int& min, int& max, int& step); + int getGainModesRx(const bladerf_gain_modes**); + void setBiasTeeRx(bool enable); + void setBiasTeeTx(bool enable); + + static const unsigned int blockSize = (1<<14); + +private: + bladerf *m_dev; + int m_nbRxChannels; + int m_nbTxChannels; + bool *m_rxOpen; + bool *m_txOpen; + + static struct bladerf *open_bladerf_from_serial(const char *serial); +}; + + + +#endif /* DEVICES_BLADERF2_DEVICEBLADERF2_H_ */ diff --git a/devices/bladerf2/devicebladerf2shared.cpp b/devices/bladerf2/devicebladerf2shared.cpp new file mode 100644 index 000000000..95a305f70 --- /dev/null +++ b/devices/bladerf2/devicebladerf2shared.cpp @@ -0,0 +1,36 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "devicebladerf2shared.h" + +MESSAGE_CLASS_DEFINITION(DeviceBladeRF2Shared::MsgReportBuddyChange, Message) + +const float DeviceBladeRF2Shared::m_sampleFifoLengthInSeconds = 0.25; +const int DeviceBladeRF2Shared::m_sampleFifoMinSize = 75000; // 300 kS/s knee +const int DeviceBladeRF2Shared::m_sampleFifoMinSize32 = 150000; // Fixed for interpolation by 32 + +DeviceBladeRF2Shared::DeviceBladeRF2Shared() : + m_dev(0), + m_channel(-1), + m_source(0), + m_sink(0) +{} + +DeviceBladeRF2Shared::~DeviceBladeRF2Shared() +{} + + + diff --git a/devices/bladerf2/devicebladerf2shared.h b/devices/bladerf2/devicebladerf2shared.h new file mode 100644 index 000000000..f4664e6fb --- /dev/null +++ b/devices/bladerf2/devicebladerf2shared.h @@ -0,0 +1,96 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef DEVICES_BLADERF2_DEVICEBLADERF2SHARED_H_ +#define DEVICES_BLADERF2_DEVICEBLADERF2SHARED_H_ + +#include "util/message.h" +#include "devicebladerf2.h" + +class SampleSinkFifo; +class SampleSourceFifo; +class BladeRF2Input; +class BladeRF2Output; + +/** + * Structure shared by a buddy with other buddies + */ +class DEVICES_API DeviceBladeRF2Shared +{ +public: + class MsgReportBuddyChange : public Message { + MESSAGE_CLASS_DECLARATION + + public: + uint64_t getCenterFrequency() const { return m_centerFrequency; } + int getLOppmTenths() const { return m_LOppmTenths; } + int getFcPos() const { return m_fcPos; } + int getDevSampleRate() const { return m_devSampleRate; } + bool getRxElseTx() const { return m_rxElseTx; } + + static MsgReportBuddyChange* create( + uint64_t centerFrequency, + int LOppmTenths, + int fcPos, + int devSampleRate, + bool rxElseTx) + { + return new MsgReportBuddyChange( + centerFrequency, + LOppmTenths, + fcPos, + devSampleRate, + rxElseTx); + } + + private: + uint64_t m_centerFrequency; //!< Center frequency + int m_LOppmTenths; //!< LO soft correction in tenths of ppm + int m_fcPos; //!< Center frequency position + int m_devSampleRate; //!< device/host sample rate + bool m_rxElseTx; //!< tells which side initiated the message + + MsgReportBuddyChange( + uint64_t centerFrequency, + int LOppmTenths, + int fcPos, + int devSampleRate, + bool rxElseTx) : + Message(), + m_centerFrequency(centerFrequency), + m_LOppmTenths(LOppmTenths), + m_fcPos(fcPos), + m_devSampleRate(devSampleRate), + m_rxElseTx(rxElseTx) + { } + }; + + DeviceBladeRF2Shared(); + ~DeviceBladeRF2Shared(); + + DeviceBladeRF2 *m_dev; + int m_channel; //!< allocated channel (-1 if none) + BladeRF2Input *m_source; + BladeRF2Output *m_sink; + + static const float m_sampleFifoLengthInSeconds; + static const int m_sampleFifoMinSize; + static const int m_sampleFifoMinSize32; +}; + + + +#endif /* DEVICES_BLADERF2_DEVICEBLADERF2SHARED_H_ */ diff --git a/devices/devices.pro b/devices/devices.pro index 2de4b16d1..0199ff713 100644 --- a/devices/devices.pro +++ b/devices/devices.pro @@ -1,108 +1,175 @@ -#-------------------------------------------------------- -# -# Pro file for Android and Windows builds with Qt Creator -# -#-------------------------------------------------------- - -QT += core gui multimedia opengl - -TEMPLATE = lib -TARGET = devices - -DEFINES += USE_SSE2=1 -QMAKE_CXXFLAGS += -msse2 -DEFINES += USE_SSSE3=1 -QMAKE_CXXFLAGS += -mssse3 -DEFINES += USE_SSE4_1=1 -QMAKE_CXXFLAGS += -msse4.1 -QMAKE_CXXFLAGS += -std=c++11 -macx:QMAKE_LFLAGS += -F/Library/Frameworks - -CONFIG(MINGW32):LIBBLADERFSRC = "D:\softs\bladeRF\host\libraries\libbladeRF\include" -CONFIG(MINGW64):LIBBLADERFSRC = "D:\softs\bladeRF\host\libraries\libbladeRF\include" -CONFIG(macx):LIBHACKRFSRC = "/opt/local/include" -CONFIG(MINGW32):LIBHACKRFSRC = "D:\softs\hackrf\host" -CONFIG(MINGW64):LIBHACKRFSRC = "D:\softs\hackrf\host" -CONFIG(macx):LIBLIMESUITESRC = "../../../LimeSuite-17.12.0" -CONFIG(MINGW32):LIBLIMESUITESRC = "D:\softs\LimeSuite" -CONFIG(MINGW64):LIBLIMESUITESRC = "D:\softs\LimeSuite" -CONFIG(MINGW32):LIBPERSEUSSRC = "D:\softs\libperseus-sdr" -CONFIG(macx):LIBIIOSRC = "../../../libiio" -CONFIG(MINGW32):LIBIIOSRC = "D:\softs\libiio" -CONFIG(MINGW64):LIBIIOSRC = "D:\softs\libiio" - -INCLUDEPATH += $$PWD -INCLUDEPATH += ../sdrbase -INCLUDEPATH += $$LIBBLADERFSRC -INCLUDEPATH += $$LIBHACKRFSRC -INCLUDEPATH += "D:\boost_1_58_0" -INCLUDEPATH += "D:\softs\libusb-1.0.20\include" -INCLUDEPATH += ../liblimesuite/srcmw -INCLUDEPATH += $$LIBLIMESUITESRC/src -INCLUDEPATH += $$LIBLIMESUITESRC/src/ADF4002 -INCLUDEPATH += $$LIBLIMESUITESRC/src/ConnectionRegistry -INCLUDEPATH += $$LIBLIMESUITESRC/src/FPGA_common -INCLUDEPATH += $$LIBLIMESUITESRC/src/GFIR -INCLUDEPATH += $$LIBLIMESUITESRC/src/lms7002m -INCLUDEPATH += $$LIBLIMESUITESRC/src/lms7002m_mcu -INCLUDEPATH += $$LIBLIMESUITESRC/src/Si5351C -INCLUDEPATH += $$LIBLIMESUITESRC/src/protocols -INCLUDEPATH += $$LIBLIMESUITESRC/external/cpp-feather-ini-parser -INCLUDEPATH += $$LIBPERSEUSSRC -!macx:INCLUDEPATH += $$LIBIIOSRC - -CONFIG(Release):build_subdir = release -CONFIG(Debug):build_subdir = debug - -!macx:SOURCES += bladerf/devicebladerf.cpp\ - bladerf/devicebladerfvalues.cpp\ - bladerf/devicebladerfshared.cpp - -SOURCES += hackrf/devicehackrf.cpp\ - hackrf/devicehackrfvalues.cpp\ - hackrf/devicehackrfshared.cpp - -SOURCES += limesdr/devicelimesdr.cpp\ - limesdr/devicelimesdrparam.cpp\ - limesdr/devicelimesdrshared.cpp - -!macx:SOURCES += plutosdr/deviceplutosdr.cpp\ - plutosdr/deviceplutosdrbox.cpp\ - plutosdr/deviceplutosdrparams.cpp\ - plutosdr/deviceplutosdrscan.cpp\ - plutosdr/deviceplutosdrshared.cpp - -!macx:HEADERS -= bladerf/devicebladerf.h\ - bladerf/devicebladerfparam.h\ - bladerf/devicebladerfvalues.h\ - bladerf/devicebladerfshared.h - -HEADERS += hackrf/devicehackrf.h\ - hackrf/devicehackrfparam.h\ - hackrf/devicehackrfvalues.h\ - hackrf/devicehackrfshared.h - -HEADERS += limesdr/devicelimesdr.h\ - limesdr/devicelimesdrparam.h\ - limesdr/devicelimesdrshared.h - -HEADERS += plutosdr/deviceplutosdr.h\ - plutosdr/deviceplutosdrbox.h\ - plutosdr/deviceplutosdrparams.h\ - plutosdr/deviceplutosdrscan.h\ - plutosdr/deviceplutosdrshared.h - -LIBS += -L../sdrbase/$${build_subdir} -lsdrbase -!macx { - LIBS += -L../libbladerf/$${build_subdir} -llibbladerf - LIBS += -L../libhackrf/$${build_subdir} -llibhackrf - LIBS += -L../liblimesuite/$${build_subdir} -lliblimesuite - LIBS += -L../libiio/$${build_subdir} -llibiio -} -macx { - LIBS -= -L../libbladerf/$${build_subdir} -llibbladerf - LIBS -= -L../libhackrf/$${build_subdir} -llibhackrf - LIBS += -L/opt/local/lib -lhackrf - LIBS += -L/usr/local/lib -lLimeSuite - LIBS += -framework iio -} +#-------------------------------------------------------- +# +# Pro file for Android and Windows builds with Qt Creator +# +#-------------------------------------------------------- + +QT += core gui multimedia opengl + +TEMPLATE = lib +TARGET = devices + +DEFINES += USE_SSE2=1 +QMAKE_CXXFLAGS += -msse2 +DEFINES += USE_SSSE3=1 +QMAKE_CXXFLAGS += -mssse3 +DEFINES += USE_SSE4_1=1 +QMAKE_CXXFLAGS += -msse4.1 +QMAKE_CXXFLAGS += -std=c++11 +macx:QMAKE_LFLAGS += -F/Library/Frameworks + +CONFIG(MSVC):DEFINES += devices_EXPORTS + +CONFIG(MINGW32):LIBBLADERF = "C:\Programs\bladeRF" +CONFIG(MINGW64):LIBBLADERF = "C:\Programs\bladeRF" + +CONFIG(macx):LIBHACKRFSRC = "/opt/local/include" +CONFIG(MINGW32):LIBHACKRFSRC = "C:\softs\hackrf\host" +CONFIG(MINGW64):LIBHACKRFSRC = "C:\softs\hackrf\host" +CONFIG(MSVC):LIBHACKRFSRC = "C:\softs\hackrf\host" + +CONFIG(macx):LIBLIMESUITESRC = "../../../LimeSuite-17.12.0" +CONFIG(MINGW32):LIBLIMESUITESRC = "C:\softs\LimeSuite" +CONFIG(MINGW64):LIBLIMESUITESRC = "C:\softs\LimeSuite" + +CONFIG(MINGW32):LIBPERSEUSSRC = "C:\softs\libperseus-sdr" + +CONFIG(macx):LIBIIOSRC = "../../../libiio" +CONFIG(MINGW32):LIBIIOSRC = "C:\softs\libiio" +CONFIG(MINGW64):LIBIIOSRC = "C:\softs\libiio" + +INCLUDEPATH += $$PWD +INCLUDEPATH += ../exports +INCLUDEPATH += ../sdrbase +INCLUDEPATH += "C:\softs\boost_1_66_0" +INCLUDEPATH += "C:\softs\libusb-1.0.22\include" +INCLUDEPATH += $$LIBBLADERF/include +INCLUDEPATH += $$LIBHACKRFSRC + +MINGW32 || MINGW64 || macx { + INCLUDEPATH += ../liblimesuite/srcmw + INCLUDEPATH += $$LIBLIMESUITESRC/src + INCLUDEPATH += $$LIBLIMESUITESRC/src/ADF4002 + INCLUDEPATH += $$LIBLIMESUITESRC/src/ConnectionRegistry + INCLUDEPATH += $$LIBLIMESUITESRC/src/FPGA_common + INCLUDEPATH += $$LIBLIMESUITESRC/src/GFIR + INCLUDEPATH += $$LIBLIMESUITESRC/src/lms7002m + INCLUDEPATH += $$LIBLIMESUITESRC/src/lms7002m_mcu + INCLUDEPATH += $$LIBLIMESUITESRC/src/Si5351C + INCLUDEPATH += $$LIBLIMESUITESRC/src/protocols + INCLUDEPATH += $$LIBLIMESUITESRC/external/cpp-feather-ini-parser +} + +MSVC { + INCLUDEPATH += "C:\softs\PothosSDR\include" +} + +INCLUDEPATH += $$LIBPERSEUSSRC +!macx:INCLUDEPATH += $$LIBIIOSRC + +CONFIG(Release):build_subdir = release +CONFIG(Debug):build_subdir = debug + +MINGW32 || MINGW64 { + SOURCES += bladerf1/devicebladerf1.cpp\ + bladerf1/devicebladerf1values.cpp\ + bladerf1/devicebladerf1shared.cpp + + SOURCES += bladerf2/devicebladerf2.cpp\ + bladerf2/devicebladerf2shared.cpp + + SOURCES += hackrf/devicehackrf.cpp\ + hackrf/devicehackrfvalues.cpp\ + hackrf/devicehackrfshared.cpp + + SOURCES += limesdr/devicelimesdr.cpp\ + limesdr/devicelimesdrparam.cpp\ + limesdr/devicelimesdrshared.cpp + + SOURCES += plutosdr/deviceplutosdr.cpp\ + plutosdr/deviceplutosdrbox.cpp\ + plutosdr/deviceplutosdrparams.cpp\ + plutosdr/deviceplutosdrscan.cpp\ + plutosdr/deviceplutosdrshared.cpp + + HEADERS += bladerf2/devicebladerf2.h\ + bladerf2/devicebladerf2shared.h + + HEADERS += bladerf1/devicebladerf1.h\ + bladerf1/devicebladerf1param.h\ + bladerf1/devicebladerf1values.h\ + bladerf1/devicebladerf1shared.h + + HEADERS += hackrf/devicehackrf.h\ + hackrf/devicehackrfparam.h\ + hackrf/devicehackrfvalues.h\ + hackrf/devicehackrfshared.h + + HEADERS += limesdr/devicelimesdr.h\ + limesdr/devicelimesdrparam.h\ + limesdr/devicelimesdrshared.h + + HEADERS += plutosdr/deviceplutosdr.h\ + plutosdr/deviceplutosdrbox.h\ + plutosdr/deviceplutosdrparams.h\ + plutosdr/deviceplutosdrscan.h\ + plutosdr/deviceplutosdrshared.h +} + +macx { + SOURCES += hackrf/devicehackrf.cpp\ + hackrf/devicehackrfvalues.cpp\ + hackrf/devicehackrfshared.cpp + + SOURCES += limesdr/devicelimesdr.cpp\ + limesdr/devicelimesdrparam.cpp\ + limesdr/devicelimesdrshared.cpp + + HEADERS += hackrf/devicehackrf.h\ + hackrf/devicehackrfparam.h\ + hackrf/devicehackrfvalues.h\ + hackrf/devicehackrfshared.h + + HEADERS += limesdr/devicelimesdr.h\ + limesdr/devicelimesdrparam.h\ + limesdr/devicelimesdrshared.h +} + +MSVC { + SOURCES += hackrf/devicehackrf.cpp\ + hackrf/devicehackrfvalues.cpp\ + hackrf/devicehackrfshared.cpp + + SOURCES += limesdr/devicelimesdr.cpp\ + limesdr/devicelimesdrparam.cpp\ + limesdr/devicelimesdrshared.cpp + + HEADERS += hackrf/devicehackrf.h\ + hackrf/devicehackrfparam.h\ + hackrf/devicehackrfvalues.h\ + hackrf/devicehackrfshared.h + + HEADERS += limesdr/devicelimesdr.h\ + limesdr/devicelimesdrparam.h\ + limesdr/devicelimesdrshared.h +} + +LIBS += -L../sdrbase/$${build_subdir} -lsdrbase + +MINGW32 || MINGW64 { + LIBS += -L$$LIBBLADERF/lib -lbladeRF + LIBS += -L../libhackrf/$${build_subdir} -llibhackrf + LIBS += -L../liblimesuite/$${build_subdir} -lliblimesuite + LIBS += -L../libiio/$${build_subdir} -llibiio +} + +macx { + LIBS += -L/opt/local/lib -lhackrf + LIBS += -L/usr/local/lib -lLimeSuite + LIBS += -framework iio +} + +MSVC { + LIBS += -L../libhackrf/$${build_subdir} -llibhackrf + LIBS += -LC:\softs\PothosSDR\lib -lLimeSuite +} diff --git a/devices/hackrf/devicehackrf.h b/devices/hackrf/devicehackrf.h index 483f6999f..291e617b6 100644 --- a/devices/hackrf/devicehackrf.h +++ b/devices/hackrf/devicehackrf.h @@ -19,7 +19,9 @@ #include "libhackrf/hackrf.h" -class DeviceHackRF +#include "export.h" + +class DEVICES_API DeviceHackRF { public: static DeviceHackRF& instance(); @@ -28,7 +30,11 @@ public: protected: DeviceHackRF(); DeviceHackRF(const DeviceHackRF&) {} +#ifdef _MSC_VER + DeviceHackRF& operator=(const DeviceHackRF& other) { return *this; } +#else DeviceHackRF& operator=(const DeviceHackRF& other __attribute__((unused))) { return *this; } +#endif ~DeviceHackRF(); private: static hackrf_device *open_hackrf_from_sequence(int sequence); diff --git a/devices/hackrf/devicehackrfshared.h b/devices/hackrf/devicehackrfshared.h index f9106a8d3..249c30852 100644 --- a/devices/hackrf/devicehackrfshared.h +++ b/devices/hackrf/devicehackrfshared.h @@ -18,8 +18,9 @@ #define DEVICES_HACKRF_DEVICEHACKRFSHARED_H_ #include "util/message.h" +#include "export.h" -class DeviceHackRFShared +class DEVICES_API DeviceHackRFShared { public: class MsgConfigureFrequencyDelta : public Message diff --git a/devices/hackrf/devicehackrfvalues.h b/devices/hackrf/devicehackrfvalues.h index 3059473c8..5f95ed859 100644 --- a/devices/hackrf/devicehackrfvalues.h +++ b/devices/hackrf/devicehackrfvalues.h @@ -17,7 +17,9 @@ #ifndef DEVICES_HACKRF_DEVICEHACKRFVALUES_H_ #define DEVICES_HACKRF_DEVICEHACKRFVALUES_H_ -class HackRFBandwidths { +#include "export.h" + +class DEVICES_API HackRFBandwidths { public: static unsigned int getBandwidth(unsigned int bandwidth_index); static unsigned int getBandwidthIndex(unsigned int bandwidth); diff --git a/devices/limesdr/devicelimesdr.cpp b/devices/limesdr/devicelimesdr.cpp index 659e7fad1..25d42c359 100644 --- a/devices/limesdr/devicelimesdr.cpp +++ b/devices/limesdr/devicelimesdr.cpp @@ -50,7 +50,7 @@ bool DeviceLimeSDR::setNCOFrequency(lms_device_t *device, bool dir_tx, std::size return false; } - if (LMS_SetNCOIndex(device, dir_tx, chan, 0, !positive) < 0) + if (LMS_SetNCOIndex(device, dir_tx, chan, 0, dir_tx^positive) < 0) { fprintf(stderr, "DeviceLimeSDR::setNCOFrequency: cannot set conversion direction %sfreq\n", positive ? "+" : "-"); return false; @@ -180,10 +180,10 @@ bool DeviceLimeSDR::SetRBBPGA_dB(lms_device_t *device, std::size_t chan, float v int rcc_ctl_pga_rbb = (430.0*pow(0.65, (g_pga_rbb/10.0))-110.35)/20.4516 + 16; int c_ctl_pga_rbb = 0; - if (0 <= g_pga_rbb && g_pga_rbb < 8) c_ctl_pga_rbb = 3; - if (8 <= g_pga_rbb && g_pga_rbb < 13) c_ctl_pga_rbb = 2; - if (13 <= g_pga_rbb && g_pga_rbb < 21) c_ctl_pga_rbb = 1; - if (21 <= g_pga_rbb) c_ctl_pga_rbb = 0; + if (g_pga_rbb < 8) { c_ctl_pga_rbb = 3; } + if (8 <= g_pga_rbb && g_pga_rbb < 13) { c_ctl_pga_rbb = 2; } + if (13 <= g_pga_rbb && g_pga_rbb < 21) { c_ctl_pga_rbb = 1; } + if (21 <= g_pga_rbb) { c_ctl_pga_rbb = 0; } if (LMS_WriteParam(device, LMS7param(RCC_CTL_PGA_RBB), rcc_ctl_pga_rbb) < 0) { diff --git a/devices/limesdr/devicelimesdr.h b/devices/limesdr/devicelimesdr.h index 01421277f..ed748457e 100644 --- a/devices/limesdr/devicelimesdr.h +++ b/devices/limesdr/devicelimesdr.h @@ -19,7 +19,9 @@ #include "lime/LimeSuite.h" -class DeviceLimeSDR +#include "export.h" + +class DEVICES_API DeviceLimeSDR { public: enum PathRxRFE diff --git a/devices/limesdr/devicelimesdrparam.h b/devices/limesdr/devicelimesdrparam.h index 34dfd8925..353b256e0 100644 --- a/devices/limesdr/devicelimesdrparam.h +++ b/devices/limesdr/devicelimesdrparam.h @@ -19,6 +19,8 @@ #include "lime/LimeSuite.h" +#include "export.h" + /** * This structure refers to one physical device shared among parties (logical devices represented by * the DeviceSinkAPI or DeviceSourceAPI). @@ -26,7 +28,7 @@ * There is only one copy that is constructed by the first participant and destroyed by the last. * A participant knows it is the first or last by checking the lists of buddies (Rx + Tx). */ -struct DeviceLimeSDRParams +struct DEVICES_API DeviceLimeSDRParams { lms_device_t *m_dev; //!< device handle uint32_t m_nbRxChannels; //!< number of Rx channels (normally 2, we'll see if we really use it...) @@ -53,6 +55,29 @@ struct DeviceLimeSDRParams m_rxFrequency(1e6), m_txFrequency(1e6) { + m_lpfRangeRx.max = 0.0f; + m_lpfRangeRx.min = 0.0f; + m_lpfRangeRx.step = 0.0f; + + m_lpfRangeTx.max = 0.0f; + m_lpfRangeTx.min = 0.0f; + m_lpfRangeTx.step = 0.0f; + + m_loRangeRx.max = 0.0f; + m_loRangeRx.min = 0.0f; + m_loRangeRx.step = 0.0f; + + m_loRangeTx.max = 0.0f; + m_loRangeTx.min = 0.0f; + m_loRangeTx.step = 0.0f; + + m_srRangeRx.max = 0.0f; + m_srRangeRx.min = 0.0f; + m_srRangeRx.step = 0.0f; + + m_srRangeTx.max = 0.0f; + m_srRangeTx.min = 0.0f; + m_srRangeTx.step = 0.0f; } /** diff --git a/devices/limesdr/devicelimesdrshared.h b/devices/limesdr/devicelimesdrshared.h index d23894399..c8df559e2 100644 --- a/devices/limesdr/devicelimesdrshared.h +++ b/devices/limesdr/devicelimesdrshared.h @@ -20,11 +20,12 @@ #include #include "devicelimesdrparam.h" #include "util/message.h" +#include "export.h" /** * Structure shared by a buddy with other buddies */ -class DeviceLimeSDRShared +class DEVICES_API DeviceLimeSDRShared { public: class MsgReportBuddyChange : public Message { diff --git a/devices/perseus/deviceperseus.h b/devices/perseus/deviceperseus.h index b4eb8d8f0..1ac30fcdb 100644 --- a/devices/perseus/deviceperseus.h +++ b/devices/perseus/deviceperseus.h @@ -19,7 +19,9 @@ #include "deviceperseusscan.h" -class DevicePerseus +#include "export.h" + +class DEVICES_API DevicePerseus { public: static DevicePerseus& instance(); diff --git a/devices/perseus/deviceperseusscan.h b/devices/perseus/deviceperseusscan.h index 8eb85677a..17bc8aca3 100644 --- a/devices/perseus/deviceperseusscan.h +++ b/devices/perseus/deviceperseusscan.h @@ -23,8 +23,9 @@ #include #include +#include "export.h" -class DevicePerseusScan +class DEVICES_API DevicePerseusScan { public: struct DeviceScan diff --git a/devices/plutosdr/deviceplutosdr.h b/devices/plutosdr/deviceplutosdr.h index 60795f755..bef2503ab 100644 --- a/devices/plutosdr/deviceplutosdr.h +++ b/devices/plutosdr/deviceplutosdr.h @@ -22,7 +22,9 @@ #include "deviceplutosdrscan.h" #include "deviceplutosdrbox.h" -class DevicePlutoSDR +#include "export.h" + +class DEVICES_API DevicePlutoSDR { public: static DevicePlutoSDR& instance(); diff --git a/devices/plutosdr/deviceplutosdrbox.h b/devices/plutosdr/deviceplutosdrbox.h index 0e2edb2f9..4941ff30b 100644 --- a/devices/plutosdr/deviceplutosdrbox.h +++ b/devices/plutosdr/deviceplutosdrbox.h @@ -22,7 +22,9 @@ #include #include "deviceplutosdrscan.h" -class DevicePlutoSDRBox +#include "export.h" + +class DEVICES_API DevicePlutoSDRBox { public: typedef enum diff --git a/devices/plutosdr/deviceplutosdrparams.h b/devices/plutosdr/deviceplutosdrparams.h index 946106159..113b05752 100644 --- a/devices/plutosdr/deviceplutosdrparams.h +++ b/devices/plutosdr/deviceplutosdrparams.h @@ -19,7 +19,9 @@ #include -class DevicePlutoSDRBox; +#include "export.h" + +class DEVICES_API DevicePlutoSDRBox; /** * This structure refers to one physical device shared among parties (logical devices represented by @@ -28,7 +30,7 @@ class DevicePlutoSDRBox; * There is only one copy that is constructed by the first participant and destroyed by the last. * A participant knows it is the first or last by checking the lists of buddies (Rx + Tx). */ -struct DevicePlutoSDRParams +class DevicePlutoSDRParams { public: DevicePlutoSDRParams(); diff --git a/devices/plutosdr/deviceplutosdrscan.h b/devices/plutosdr/deviceplutosdrscan.h index 1cb7c607a..9d1b24988 100644 --- a/devices/plutosdr/deviceplutosdrscan.h +++ b/devices/plutosdr/deviceplutosdrscan.h @@ -21,7 +21,9 @@ #include #include -class DevicePlutoSDRScan +#include "export.h" + +class DEVICES_API DevicePlutoSDRScan { public: struct DeviceScan diff --git a/devices/plutosdr/deviceplutosdrshared.h b/devices/plutosdr/deviceplutosdrshared.h index 211da350c..9d98c66a0 100644 --- a/devices/plutosdr/deviceplutosdrshared.h +++ b/devices/plutosdr/deviceplutosdrshared.h @@ -20,13 +20,14 @@ #include #include "util/message.h" +#include "export.h" class DevicePlutoSDRParams; /** * Structure shared by a buddy with other buddies */ -class DevicePlutoSDRShared +class DEVICES_API DevicePlutoSDRShared { public: /** diff --git a/devices/readme.md b/devices/readme.md index 13ecf2f02..21e334eff 100644 --- a/devices/readme.md +++ b/devices/readme.md @@ -2,15 +2,19 @@ This folder contains classes and methods that can be used by different plugins that work with a common physical device or via network. Thus this can be one of the following devices: - - BladeRF: one Rx and one Tx full duplex. Plugins are: - - bladerfinput - - bladerfoutput + - BladeRF1: one Rx and one Tx full duplex. Plugins are: + - bladerf1input + - bladerf1output + + - BladeRF2: 2 Rx and 2 Tx full duplex (BladeRF 2.0 micro). Plugins are: + - bladerf2input + - bladerf2output - HackRF: one Rx and one Tx half duplex. Plugins are: - hackrfinput - hackrfoutput - - LimeSDR: 2 Rx and 2 Tx full duplex. Plugins are + - LimeSDR: 2 Rx and 2 Tx full duplex (Lime-USB). 1 Rx and 1 Tx full duplex (Lime-Mini). Plugins are - limesdrinput - limesdroutput @@ -18,5 +22,6 @@ This folder contains classes and methods that can be used by different plugins t - plutosdrinput - plutosdroutput - - SDRdaemon: sends or receive samples to/from device remotely through the network. Used on the Tx plugin only - - sdrdaemonsink \ No newline at end of file + - SoapySDR: Soapy SDR virtual device + - soapysdrinput + - soapysdroutput \ No newline at end of file diff --git a/devices/soapysdr/CMakeLists.txt b/devices/soapysdr/CMakeLists.txt new file mode 100644 index 000000000..55ef5369b --- /dev/null +++ b/devices/soapysdr/CMakeLists.txt @@ -0,0 +1,52 @@ +project(soapysdrdevice) + +set (CMAKE_CXX_STANDARD 11) + +set(soapysdrdevice_SOURCES + devicesoapysdr.cpp + devicesoapysdrscan.cpp + devicesoapysdrshared.cpp + devicesoapysdrparams.cpp +) + +set(soapysdrdevice_HEADERS + devicesoapysdr.h + devicesoapysdrscan.h + devicesoapysdrshared.h + devicesoapysdrparams.h +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${SOAPYSDRSRC} +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${SOAPYSDR_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +#add_definitions(${QT_DEFINITIONS}) +#add_definitions(-DQT_SHARED) + +add_library(soapysdrdevice SHARED + ${soapysdrdevice_SOURCES} +) + +if (BUILD_DEBIAN) +target_link_libraries(soapysdrdevice + soapysdr + sdrbase +) +else (BUILD_DEBIAN) +target_link_libraries(soapysdrdevice + ${SOAPYSDR_LIBRARY} + sdrbase +) +endif (BUILD_DEBIAN) + +install(TARGETS soapysdrdevice DESTINATION lib) diff --git a/devices/soapysdr/devicesoapysdr.cpp b/devices/soapysdr/devicesoapysdr.cpp new file mode 100644 index 000000000..2f5e18a1b --- /dev/null +++ b/devices/soapysdr/devicesoapysdr.cpp @@ -0,0 +1,73 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "devicesoapysdr.h" + +DeviceSoapySDR::DeviceSoapySDR() +{ + m_scanner.scan(); +} + +DeviceSoapySDR::~DeviceSoapySDR() +{} + +DeviceSoapySDR& DeviceSoapySDR::instance() +{ + static DeviceSoapySDR inst; + return inst; +} + +SoapySDR::Device *DeviceSoapySDR::openSoapySDR(uint32_t sequence) +{ + instance(); + return openopenSoapySDRFromSequence(sequence); +} + +void DeviceSoapySDR::closeSoapySdr(SoapySDR::Device *device) +{ + SoapySDR::Device::unmake(device); +} + +SoapySDR::Device *DeviceSoapySDR::openopenSoapySDRFromSequence(uint32_t sequence) +{ + if (sequence > m_scanner.getNbDevices()) + { + return 0; + } + else + { + const DeviceSoapySDRScan::SoapySDRDeviceEnum& deviceEnum = m_scanner.getDevicesEnumeration()[sequence]; + + try + { + SoapySDR::Kwargs kwargs; + kwargs["driver"] = deviceEnum.m_driverName.toStdString(); + + if (deviceEnum.m_idKey.size() > 0) { + kwargs[deviceEnum.m_idKey.toStdString()] = deviceEnum.m_idValue.toStdString(); + } + + SoapySDR::Device *device = SoapySDR::Device::make(kwargs); + return device; + } + catch (const std::exception &ex) + { + qWarning("DeviceSoapySDR::openopenSoapySDRFromSequence: %s cannot be opened: %s", + deviceEnum.m_label.toStdString().c_str(), ex.what()); + return 0; + } + } +} diff --git a/devices/soapysdr/devicesoapysdr.h b/devices/soapysdr/devicesoapysdr.h new file mode 100644 index 000000000..c8be84557 --- /dev/null +++ b/devices/soapysdr/devicesoapysdr.h @@ -0,0 +1,49 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef DEVICES_SOAPYSDR_DEVICESOAPYSDR_H_ +#define DEVICES_SOAPYSDR_DEVICESOAPYSDR_H_ + +#include +#include + +#include "export.h" +#include "devicesoapysdrscan.h" + +class DEVICES_API DeviceSoapySDR +{ +public: + static DeviceSoapySDR& instance(); + SoapySDR::Device *openSoapySDR(uint32_t sequence); + void closeSoapySdr(SoapySDR::Device *device); + + uint32_t getNbDevices() const { return m_scanner.getNbDevices(); } + const std::vector& getDevicesEnumeration() const { return m_scanner.getDevicesEnumeration(); } + + static const unsigned int blockSize = (1<<14); + +protected: + DeviceSoapySDR(); + DeviceSoapySDR(const DeviceSoapySDR&) {} + DeviceSoapySDR& operator=(const DeviceSoapySDR& other __attribute__((unused))) { return *this; } + ~DeviceSoapySDR(); + +private: + SoapySDR::Device *openopenSoapySDRFromSequence(uint32_t sequence); + DeviceSoapySDRScan m_scanner; +}; + +#endif /* DEVICES_SOAPYSDR_DEVICESOAPYSDR_H_ */ diff --git a/devices/soapysdr/devicesoapysdrparams.cpp b/devices/soapysdr/devicesoapysdrparams.cpp new file mode 100644 index 000000000..0e5d25c03 --- /dev/null +++ b/devices/soapysdr/devicesoapysdrparams.cpp @@ -0,0 +1,298 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include + +#include "devicesoapysdrparams.h" + +DeviceSoapySDRParams::DeviceSoapySDRParams(SoapySDR::Device *device) : + m_device(device) +{ + fillParams(); + printParams(); +} + +DeviceSoapySDRParams::~DeviceSoapySDRParams() +{} + +std::string DeviceSoapySDRParams::getRxChannelMainTunableElementName(uint32_t index) +{ + if (index < m_nbRx) + { + return std::string("RF"); + } + else + { + const ChannelSettings& channelSettings = m_RxChannelsSettings[index]; + + if (channelSettings.m_frequencySettings.size() > 0) { + return channelSettings.m_frequencySettings.front().m_name; + } else { + return std::string("RF"); + } + } +} + +std::string DeviceSoapySDRParams::getTxChannelMainTunableElementName(uint32_t index) +{ + if (index < m_nbRx) + { + return std::string("RF"); + } + else + { + const ChannelSettings& channelSettings = m_RxChannelsSettings[index]; + + if (channelSettings.m_frequencySettings.size() > 0) { + return channelSettings.m_frequencySettings.front().m_name; + } else { + return std::string("RF"); + } + } +} + +void DeviceSoapySDRParams::fillParams() +{ + m_deviceSettingsArgs = m_device->getSettingInfo(); + m_nbRx = m_device->getNumChannels(SOAPY_SDR_RX); + m_nbTx = m_device->getNumChannels(SOAPY_SDR_TX); + + for (unsigned int ichan = 0; ichan < m_nbRx; ichan++) { + fillChannelParams(m_RxChannelsSettings, SOAPY_SDR_RX, ichan); + } + + for (unsigned int ichan = 0; ichan < m_nbTx; ichan++) { + fillChannelParams(m_TxChannelsSettings, SOAPY_SDR_TX, ichan); + } +} + +void DeviceSoapySDRParams::fillChannelParams(std::vector& channelSettings, int direction, unsigned int ichan) +{ + channelSettings.push_back(ChannelSettings()); + + channelSettings.back().m_streamSettingsArgs = m_device->getStreamArgsInfo(direction, ichan); + channelSettings.back().m_antennas = m_device->listAntennas(direction, ichan); + channelSettings.back().m_hasDCAutoCorrection = m_device->hasDCOffsetMode(direction, ichan); + channelSettings.back().m_hasDCOffsetValue = m_device->hasDCOffset(direction, ichan); + channelSettings.back().m_hasIQBalanceValue = m_device->hasIQBalance(direction, ichan); + channelSettings.back().m_hasFrequencyCorrectionValue = m_device->hasFrequencyCorrection(direction, ichan); + + // gains + + channelSettings.back().m_hasAGC = m_device->hasGainMode(direction, ichan); + channelSettings.back().m_gainRange = m_device->getGainRange(direction, ichan); + std::vector gainsList = m_device->listGains(direction, ichan); + + for (const auto &it : gainsList) + { + channelSettings.back().m_gainSettings.push_back(GainSetting()); + channelSettings.back().m_gainSettings.back().m_name = it; + channelSettings.back().m_gainSettings.back().m_range = m_device->getGainRange(direction, ichan, it); + } + + // frequencies + + std::vector freqsList = m_device->listFrequencies(direction, ichan); + + for (const auto &it : freqsList) + { + channelSettings.back().m_frequencySettings.push_back(FrequencySetting()); + channelSettings.back().m_frequencySettings.back().m_name = it; + channelSettings.back().m_frequencySettings.back().m_ranges = m_device->getFrequencyRange(direction, ichan, it); + } + + channelSettings.back().m_frequencySettingsArgs = m_device->getFrequencyArgsInfo(direction, ichan); + + // sample rates + channelSettings.back().m_ratesRanges = m_device->getSampleRateRange(direction, ichan); + + // bandwidths + channelSettings.back().m_bandwidthsRanges = m_device->getBandwidthRange(direction, ichan); +} + +void DeviceSoapySDRParams::printParams() +{ + qDebug() << "DeviceSoapySDRParams::printParams: m_deviceSettingsArgs:\n" << argInfoListToString(m_deviceSettingsArgs).c_str(); + int ichan = 0; + + for (const auto &it : m_RxChannelsSettings) + { + qDebug() << "DeviceSoapySDRParams::printParams: Rx channel " << ichan; + printChannelParams(it); + ichan++; + } + + ichan = 0; + + for (const auto &it : m_TxChannelsSettings) + { + qDebug() << "DeviceSoapySDRParams::printParams: Tx channel " << ichan; + printChannelParams(it); + ichan++; + } +} + +void DeviceSoapySDRParams::printChannelParams(const ChannelSettings& channelSetting) +{ + qDebug() << "DeviceSoapySDRParams::printParams: m_streamSettingsArgs:\n" << argInfoListToString(channelSetting.m_streamSettingsArgs).c_str(); + qDebug() << "DeviceSoapySDRParams::printParams:" + << " m_hasDCAutoCorrection: " << channelSetting.m_hasDCAutoCorrection + << " m_hasDCOffsetValue: " << channelSetting.m_hasDCOffsetValue + << " m_hasIQBalanceValue: " << channelSetting.m_hasIQBalanceValue + << " m_hasFrequencyCorrectionValue: " << channelSetting.m_hasFrequencyCorrectionValue + << " m_hasAGC: " << channelSetting.m_hasAGC; + qDebug() << "DeviceSoapySDRParams::printParams: m_antennas: " << vectorToString(channelSetting.m_antennas).c_str(); + qDebug() << "DeviceSoapySDRParams::printParams: m_gainRange: " << rangeToString(channelSetting.m_gainRange).c_str(); + + qDebug() << "DeviceSoapySDRParams::printParams: individual gains..."; + + for (const auto &gainIt : channelSetting.m_gainSettings) + { + qDebug() << "DeviceSoapySDRParams::printParams: m_name: " << gainIt.m_name.c_str(); + qDebug() << "DeviceSoapySDRParams::printParams: m_range: " << rangeToString(gainIt.m_range).c_str(); + } + + qDebug() << "DeviceSoapySDRParams::printParams: tunable elements..."; + + for (const auto &freqIt : channelSetting.m_frequencySettings) + { + qDebug() << "DeviceSoapySDRParams::printParams: m_name: " << freqIt.m_name.c_str(); + qDebug() << "DeviceSoapySDRParams::printParams: m_range (kHz): " << rangeListToString(freqIt.m_ranges, 1e3).c_str(); + } + + qDebug() << "DeviceSoapySDRParams::printParams: m_frequencySettingsArgs:\n" << argInfoListToString(channelSetting.m_frequencySettingsArgs).c_str(); + qDebug() << "DeviceSoapySDRParams::printParams: m_ratesRanges (kHz): " << rangeListToString(channelSetting.m_ratesRanges, 1e3).c_str(); + qDebug() << "DeviceSoapySDRParams::printParams: m_bandwidthsRanges (kHz): " << rangeListToString(channelSetting.m_bandwidthsRanges, 1e3).c_str(); +} + +std::string DeviceSoapySDRParams::argInfoToString(const SoapySDR::ArgInfo &argInfo, const std::string indent) +{ + std::stringstream ss; + + //name, or use key if missing + std::string name = argInfo.name; + if (argInfo.name.empty()) name = argInfo.key; + ss << indent << " * " << name; + + //optional description + std::string desc = argInfo.description; + const std::string replace("\n"+indent+" "); + + for (std::size_t pos = 0; (pos=desc.find("\n", pos)) != std::string::npos; pos+=replace.size()) { + desc.replace(pos, 1, replace); + } + + if (not desc.empty()) { + ss << " - " << desc << std::endl << indent << " "; + } + + //other fields + ss << " [key=" << argInfo.key; + + if (not argInfo.units.empty()) { + ss << ", units=" << argInfo.units; + } + + if (not argInfo.value.empty()) { + ss << ", default=" << argInfo.value; + } + + //type + switch (argInfo.type) + { + case SoapySDR::ArgInfo::BOOL: + ss << ", type=bool"; + break; + case SoapySDR::ArgInfo::INT: + ss << ", type=int"; + break; + case SoapySDR::ArgInfo::FLOAT: + ss << ", type=float"; + break; + case SoapySDR::ArgInfo::STRING: + ss << ", type=string"; + break; + } + + //optional range/enumeration + if (argInfo.range.minimum() < argInfo.range.maximum()) { + ss << ", range=" << rangeToString(argInfo.range); + } + + if (not argInfo.options.empty()) { + ss << ", options=(" << vectorToString(argInfo.options) << ")"; + } + + ss << "]"; + + return ss.str(); +} + +std::string DeviceSoapySDRParams::argInfoListToString(const SoapySDR::ArgInfoList &argInfos) +{ + std::stringstream ss; + + for (std::size_t i = 0; i < argInfos.size(); i++) { + ss << argInfoToString(argInfos[i]) << std::endl; + } + + return ss.str(); +} + +std::string DeviceSoapySDRParams::rangeToString(const SoapySDR::Range &range) +{ + std::stringstream ss; + ss << "[" << range.minimum() << ", " << range.maximum(); + + if (range.step() != 0.0) { + ss << ", " << range.step(); + } + + ss << "]"; + return ss.str(); +} + +std::string DeviceSoapySDRParams::rangeListToString(const SoapySDR::RangeList &range, const double scale) +{ + std::stringstream ss; + + for (std::size_t i = 0; i < range.size(); i++) + { + if (not ss.str().empty()) { + ss << ", "; + } + + if (range[i].minimum() == range[i].maximum()) + { + ss << (range[i].minimum()/scale); + } + else + { + ss << "[" << (range[i].minimum()/scale) << ", " << (range[i].maximum()/scale); + + if (range[i].step() != 0.0) { + ss << ", " << (range[i].step()/scale); + } + + ss << "]"; + } + } + + return ss.str(); +} diff --git a/devices/soapysdr/devicesoapysdrparams.h b/devices/soapysdr/devicesoapysdrparams.h new file mode 100644 index 000000000..2489b8f85 --- /dev/null +++ b/devices/soapysdr/devicesoapysdrparams.h @@ -0,0 +1,134 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef DEVICES_SOAPYSDR_DEVICESOAPYSDRPARAMS_H_ +#define DEVICES_SOAPYSDR_DEVICESOAPYSDRPARAMS_H_ + +#include +#include +#include + +#include + +#include "export.h" + +/** + * This structure refers to one physical device shared among parties (logical devices represented by + * the DeviceSinkAPI or DeviceSourceAPI). + * It allows storing information on the common resources in one place and is shared among participants. + * There is only one copy that is constructed by the first participant and destroyed by the last. + * A participant knows it is the first or last by checking the lists of buddies (Rx + Tx). + */ + +class DEVICES_API DeviceSoapySDRParams +{ +public: + struct GainSetting + { + std::string m_name; //!< Name of the gain element + SoapySDR::Range m_range; //!< Gain range + }; + + struct FrequencySetting + { + std::string m_name; //!< Name of the tunable element + SoapySDR::RangeList m_ranges; //!< List of ranges of the tunable element + }; + + struct ChannelSettings + { + SoapySDR::ArgInfoList m_streamSettingsArgs; //!< common stream parameters + bool m_hasDCAutoCorrection; //!< DC offset auto correction flag + bool m_hasDCOffsetValue; //!< DC offset value flag + bool m_hasIQBalanceValue; //!< IQ correction value flag + bool m_hasFrequencyCorrectionValue; //!< Frequency correction value flag + std::vector m_antennas; //!< Antenna ports names + bool m_hasAGC; //!< AGC flag + SoapySDR::Range m_gainRange; //!< Global gain range + std::vector m_gainSettings; //!< gain elements settings + std::vector m_frequencySettings; //!< tunable elements settings + SoapySDR::ArgInfoList m_frequencySettingsArgs; //!< common tuning parameters + SoapySDR::RangeList m_ratesRanges; //!< list of ranges of sample rates + SoapySDR::RangeList m_bandwidthsRanges; //!< list of ranges of bandwidths + }; + + DeviceSoapySDRParams(SoapySDR::Device *device); + ~DeviceSoapySDRParams(); + + const ChannelSettings* getRxChannelSettings(uint32_t index) + { + if (index < m_nbRx) { + return &m_RxChannelsSettings[index]; + } else { + return 0; + } + } + + const ChannelSettings* getTxChannelSettings(uint32_t index) + { + if (index < m_nbTx) { + return &m_TxChannelsSettings[index]; + } else { + return 0; + } + } + + std::string getRxChannelMainTunableElementName(uint32_t index); + std::string getTxChannelMainTunableElementName(uint32_t index); + +private: + void fillParams(); + void fillChannelParams(std::vector& channelSettings, int direction, unsigned int ichan); + void printParams(); + void printChannelParams(const ChannelSettings& channelSetting); + + // Printing functions copied from SoapySDR's SoapySDRProbe.cpp + std::string argInfoToString(const SoapySDR::ArgInfo &argInfo, const std::string indent = " "); + std::string argInfoListToString(const SoapySDR::ArgInfoList &argInfos); + std::string rangeToString(const SoapySDR::Range &range); + std::string rangeListToString(const SoapySDR::RangeList &range, const double scale); + + template + std::string vectorToString(const std::vector &options) + { + std::stringstream ss; + + if (options.empty()) { + return ""; + } + + for (std::size_t i = 0; i < options.size(); i++) + { + if (not ss.str().empty()) { + ss << ", "; + } + + ss << options[i]; + } + + return ss.str(); + } + + SoapySDR::Device *m_device; + SoapySDR::ArgInfoList m_deviceSettingsArgs; //!< list (vector) of device settings arguments + uint32_t m_nbRx; //!< number of Rx channels + uint32_t m_nbTx; //!< number of Tx channels + std::vector m_RxChannelsSettings; + std::vector m_TxChannelsSettings; +}; + + +#endif /* DEVICES_SOAPYSDR_DEVICESOAPYSDRPARAMS_H_ */ diff --git a/devices/soapysdr/devicesoapysdrscan.cpp b/devices/soapysdr/devicesoapysdrscan.cpp new file mode 100644 index 000000000..61b44d0c2 --- /dev/null +++ b/devices/soapysdr/devicesoapysdrscan.cpp @@ -0,0 +1,130 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#include +#include + +#include "devicesoapysdrscan.h" + +void DeviceSoapySDRScan::scan() +{ + qDebug("DeviceSoapySDRScan::scan: Lib Version: v%s", SoapySDR::getLibVersion().c_str()); + qDebug("DeviceSoapySDRScan::scan: API Version: v%s", SoapySDR::getAPIVersion().c_str()); + qDebug("DeviceSoapySDRScan::scan: ABI Version: v%s", SoapySDR::getABIVersion().c_str()); + qDebug("DeviceSoapySDRScan::scan: Install root: %s", SoapySDR::getRootPath().c_str()); + + const std::vector& modules = SoapySDR::listModules(); + + for (const auto &it : modules) + { + const auto &errMsg = SoapySDR::loadModule(it); + + if (not errMsg.empty()) { + qWarning("DeviceSoapySDRScan::scan: cannot load module %s: %s", it.c_str(), errMsg.c_str()); + } else { + qDebug("DeviceSoapySDRScan::scan: loaded module: %s", it.c_str()); + } + } + + SoapySDR::FindFunctions findFunctions = SoapySDR::Registry::listFindFunctions(); + SoapySDR::Kwargs kwargs; + m_deviceEnums.clear(); + + for (const auto &it : findFunctions) // for each driver + { + qDebug("DeviceSoapySDRScan::scan: driver: %s", it.first.c_str()); + kwargs["driver"] = it.first; + + SoapySDR::KwargsList kwargsList = SoapySDR::Device::enumerate(kwargs); + SoapySDR::KwargsList::const_iterator kit = kwargsList.begin(); + + for (int deviceSeq = 0; kit != kwargsList.end(); ++kit, deviceSeq++) // for each device + { + m_deviceEnums.push_back(SoapySDRDeviceEnum()); + m_deviceEnums.back().m_driverName = QString(it.first.c_str()); + m_deviceEnums.back().m_sequence = deviceSeq; + + // collect identification information + + SoapySDR::Kwargs::const_iterator kargIt; + + if ((kargIt = kit->find("label")) != kit->end()) + { + m_deviceEnums.back().m_label = QString(kargIt->second.c_str()); + qDebug("DeviceSoapySDRScan::scan: %s #%u %s", + m_deviceEnums.back().m_driverName.toStdString().c_str(), + deviceSeq, + kargIt->second.c_str()); + } + + if ((kargIt = kit->find("serial")) != kit->end()) + { + m_deviceEnums.back().m_idKey = QString(kargIt->first.c_str()); + m_deviceEnums.back().m_idValue = QString(kargIt->second.c_str()); + } + else if ((kargIt = kit->find("device_id")) != kit->end()) + { + m_deviceEnums.back().m_idKey = QString(kargIt->first.c_str()); + m_deviceEnums.back().m_idValue = QString(kargIt->second.c_str()); + } + else if ((kargIt = kit->find("addr")) != kit->end()) + { + m_deviceEnums.back().m_idKey = QString(kargIt->first.c_str()); + m_deviceEnums.back().m_idValue = QString(kargIt->second.c_str()); + } + + // access the device to get the number of Rx and Tx channels and at the same time probe + // whether it is available for Soapy + + try + { + SoapySDR::Device *device; + SoapySDR::Kwargs kwargs; + kwargs["driver"] = m_deviceEnums.back().m_driverName.toStdString(); + + if (m_deviceEnums.back().m_idKey.size() > 0) { + kwargs[m_deviceEnums.back().m_idKey.toStdString()] = m_deviceEnums.back().m_idValue.toStdString(); + } + + device = SoapySDR::Device::make(kwargs); + m_deviceEnums.back().m_nbRx = device->getNumChannels(SOAPY_SDR_RX); + m_deviceEnums.back().m_nbTx = device->getNumChannels(SOAPY_SDR_TX); + qDebug("DeviceSoapySDRScan::scan: %s #%u driver=%s hardware=%s #Rx=%u #Tx=%u", + m_deviceEnums.back().m_driverName.toStdString().c_str(), + deviceSeq, + device->getDriverKey().c_str(), + device->getHardwareKey().c_str(), + m_deviceEnums.back().m_nbRx, + m_deviceEnums.back().m_nbTx); + + SoapySDR::Device::unmake(device); + } + catch (const std::exception &ex) + { + qWarning("DeviceSoapySDRScan::scan: %s #%u cannot be opened: %s", + m_deviceEnums.back().m_driverName.toStdString().c_str(), + deviceSeq, + ex.what()); + m_deviceEnums.pop_back(); + } + } // for each device + } +} diff --git a/devices/soapysdr/devicesoapysdrscan.h b/devices/soapysdr/devicesoapysdrscan.h new file mode 100644 index 000000000..b4cc59a0d --- /dev/null +++ b/devices/soapysdr/devicesoapysdrscan.h @@ -0,0 +1,52 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef DEVICES_SOAPYSDR_DEVICESOAPYSDRSCAN_H_ +#define DEVICES_SOAPYSDR_DEVICESOAPYSDRSCAN_H_ + +#include + +#include + +#include "export.h" + +class DEVICES_API DeviceSoapySDRScan +{ +public: + struct SoapySDRDeviceEnum + { + QString m_driverName; + uint32_t m_sequence; //!< device sequence for this driver + QString m_label; //!< the label key for display should always be present + QString m_idKey; //!< key to uniquely identify device + QString m_idValue; //!< value for the above key + uint32_t m_nbRx; + uint32_t m_nbTx; + + SoapySDRDeviceEnum() : m_sequence(0), m_nbRx(0), m_nbTx(0) + {} + }; + + void scan(); + uint32_t getNbDevices() const { return m_deviceEnums.size(); } + const std::vector& getDevicesEnumeration() const { return m_deviceEnums; } + +private: + std::vector m_deviceEnums; +}; + + +#endif /* DEVICES_SOAPYSDR_DEVICESOAPYSDRSCAN_H_ */ diff --git a/devices/soapysdr/devicesoapysdrshared.cpp b/devices/soapysdr/devicesoapysdrshared.cpp new file mode 100644 index 000000000..e7ef10f70 --- /dev/null +++ b/devices/soapysdr/devicesoapysdrshared.cpp @@ -0,0 +1,33 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "devicesoapysdrshared.h" + +MESSAGE_CLASS_DEFINITION(DeviceSoapySDRShared::MsgReportBuddyChange, Message) + +const float DeviceSoapySDRShared::m_sampleFifoLengthInSeconds = 0.25; +const int DeviceSoapySDRShared::m_sampleFifoMinSize = 75000; // 300 kS/s knee +const int DeviceSoapySDRShared::m_sampleFifoMinSize32 = 150000; // Fixed for interpolation by 32 + +DeviceSoapySDRShared::DeviceSoapySDRShared() : + m_device(0), + m_channel(-1), + m_source(0), + m_sink(0) +{} + +DeviceSoapySDRShared::~DeviceSoapySDRShared() +{} diff --git a/devices/soapysdr/devicesoapysdrshared.h b/devices/soapysdr/devicesoapysdrshared.h new file mode 100644 index 000000000..85adc878a --- /dev/null +++ b/devices/soapysdr/devicesoapysdrshared.h @@ -0,0 +1,97 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef DEVICES_SOAPYSDR_DEVICESOAPYSDRSHARED_H_ +#define DEVICES_SOAPYSDR_DEVICESOAPYSDRSHARED_H_ + +#include + +#include "util/message.h" +#include "export.h" +#include "devicesoapysdrparams.h" + +class SoapySDRInput; +class SoapySDROutput; + +/** + * Structure shared by a buddy with other buddies + */ +class DEVICES_API DeviceSoapySDRShared +{ +public: + class MsgReportBuddyChange : public Message { + MESSAGE_CLASS_DECLARATION + + public: + uint64_t getCenterFrequency() const { return m_centerFrequency; } + int getLOppmTenths() const { return m_LOppmTenths; } + int getFcPos() const { return m_fcPos; } + int getDevSampleRate() const { return m_devSampleRate; } + bool getRxElseTx() const { return m_rxElseTx; } + + static MsgReportBuddyChange* create( + uint64_t centerFrequency, + int LOppmTenths, + int fcPos, + int devSampleRate, + bool rxElseTx) + { + return new MsgReportBuddyChange( + centerFrequency, + LOppmTenths, + fcPos, + devSampleRate, + rxElseTx); + } + + private: + uint64_t m_centerFrequency; //!< Center frequency + int m_LOppmTenths; //!< LO soft correction in tenths of ppm + int m_fcPos; //!< Center frequency position + int m_devSampleRate; //!< device/host sample rate + bool m_rxElseTx; //!< tells which side initiated the message + + MsgReportBuddyChange( + uint64_t centerFrequency, + int LOppmTenths, + int fcPos, + int devSampleRate, + bool rxElseTx) : + Message(), + m_centerFrequency(centerFrequency), + m_LOppmTenths(LOppmTenths), + m_fcPos(fcPos), + m_devSampleRate(devSampleRate), + m_rxElseTx(rxElseTx) + { } + }; + + DeviceSoapySDRShared(); + ~DeviceSoapySDRShared(); + + SoapySDR::Device *m_device; + DeviceSoapySDRParams *m_deviceParams; + int m_channel; //!< allocated channel (-1 if none) + SoapySDRInput *m_source; + SoapySDROutput *m_sink; + + static const float m_sampleFifoLengthInSeconds; + static const int m_sampleFifoMinSize; + static const int m_sampleFifoMinSize32; +}; + + +#endif /* DEVICES_SOAPYSDR_DEVICESOAPYSDRSHARED_H_ */ diff --git a/doc/img/AMDemod_plugin.png b/doc/img/AMDemod_plugin.png index 9c280fad6..0a20eddfc 100644 Binary files a/doc/img/AMDemod_plugin.png and b/doc/img/AMDemod_plugin.png differ diff --git a/doc/img/AMDemod_plugin.xcf b/doc/img/AMDemod_plugin.xcf index e6ecfdcf8..1c398b3d9 100644 Binary files a/doc/img/AMDemod_plugin.xcf and b/doc/img/AMDemod_plugin.xcf differ diff --git a/doc/img/APIdocHome.png b/doc/img/APIdocHome.png new file mode 100644 index 000000000..0aca0bbe6 Binary files /dev/null and b/doc/img/APIdocHome.png differ diff --git a/doc/img/AudioDialog_input.png b/doc/img/AudioDialog_input.png new file mode 100644 index 000000000..dc67ae4ea Binary files /dev/null and b/doc/img/AudioDialog_input.png differ diff --git a/doc/img/AudioDialog_input.xcf b/doc/img/AudioDialog_input.xcf new file mode 100644 index 000000000..976a2b931 Binary files /dev/null and b/doc/img/AudioDialog_input.xcf differ diff --git a/doc/img/AudioDialog_output.png b/doc/img/AudioDialog_output.png new file mode 100644 index 000000000..e5067dfae Binary files /dev/null and b/doc/img/AudioDialog_output.png differ diff --git a/doc/img/AudioDialog_output.xcf b/doc/img/AudioDialog_output.xcf new file mode 100644 index 000000000..b1cff7976 Binary files /dev/null and b/doc/img/AudioDialog_output.xcf differ diff --git a/doc/img/AudioDialog_select.png b/doc/img/AudioDialog_select.png new file mode 100644 index 000000000..9b7023102 Binary files /dev/null and b/doc/img/AudioDialog_select.png differ diff --git a/doc/img/AudioDialog_select.xcf b/doc/img/AudioDialog_select.xcf new file mode 100644 index 000000000..cae47271d Binary files /dev/null and b/doc/img/AudioDialog_select.xcf differ diff --git a/doc/img/BasicChannelSettings.png b/doc/img/BasicChannelSettings.png index cc73247c8..14d38bdeb 100644 Binary files a/doc/img/BasicChannelSettings.png and b/doc/img/BasicChannelSettings.png differ diff --git a/doc/img/BasicChannelSettings.xcf b/doc/img/BasicChannelSettings.xcf index 75ad2da9b..9a5e73aa9 100644 Binary files a/doc/img/BasicChannelSettings.xcf and b/doc/img/BasicChannelSettings.xcf differ diff --git a/doc/img/BladeRFInput_plugin.png b/doc/img/BladeRF1Input_plugin.png similarity index 100% rename from doc/img/BladeRFInput_plugin.png rename to doc/img/BladeRF1Input_plugin.png diff --git a/doc/img/BladeRFInput_plugin.xcf b/doc/img/BladeRF1Input_plugin.xcf similarity index 100% rename from doc/img/BladeRFInput_plugin.xcf rename to doc/img/BladeRF1Input_plugin.xcf diff --git a/doc/img/BladeRFOutput_plugin.png b/doc/img/BladeRF1Output_plugin.png similarity index 100% rename from doc/img/BladeRFOutput_plugin.png rename to doc/img/BladeRF1Output_plugin.png diff --git a/doc/img/BladeRFOutput_plugin.xcf b/doc/img/BladeRF1Output_plugin.xcf similarity index 100% rename from doc/img/BladeRFOutput_plugin.xcf rename to doc/img/BladeRF1Output_plugin.xcf diff --git a/doc/img/BladeRFOutput_plugin_fifodly_32.png b/doc/img/BladeRF1Output_plugin_fifodly_32.png similarity index 100% rename from doc/img/BladeRFOutput_plugin_fifodly_32.png rename to doc/img/BladeRF1Output_plugin_fifodly_32.png diff --git a/doc/img/BladeRFOutput_plugin_fifodly_other.png b/doc/img/BladeRF1Output_plugin_fifodly_other.png similarity index 100% rename from doc/img/BladeRFOutput_plugin_fifodly_other.png rename to doc/img/BladeRF1Output_plugin_fifodly_other.png diff --git a/doc/img/BladeRF2Input_plugin.png b/doc/img/BladeRF2Input_plugin.png new file mode 100644 index 000000000..bfb2eb03d Binary files /dev/null and b/doc/img/BladeRF2Input_plugin.png differ diff --git a/doc/img/BladeRF2Input_plugin.xcf b/doc/img/BladeRF2Input_plugin.xcf new file mode 100644 index 000000000..55872d3eb Binary files /dev/null and b/doc/img/BladeRF2Input_plugin.xcf differ diff --git a/doc/img/BladeRF2Output_plugin.png b/doc/img/BladeRF2Output_plugin.png new file mode 100644 index 000000000..ed9813b13 Binary files /dev/null and b/doc/img/BladeRF2Output_plugin.png differ diff --git a/doc/img/BladeRF2Output_plugin.xcf b/doc/img/BladeRF2Output_plugin.xcf new file mode 100644 index 000000000..890f4087a Binary files /dev/null and b/doc/img/BladeRF2Output_plugin.xcf differ diff --git a/doc/img/ChAnalyzerNG_plugin.png b/doc/img/ChAnalyzerNG_plugin.png index 79d52dc8a..0e0d37d82 100644 Binary files a/doc/img/ChAnalyzerNG_plugin.png and b/doc/img/ChAnalyzerNG_plugin.png differ diff --git a/doc/img/ChAnalyzerNG_plugin.xcf b/doc/img/ChAnalyzerNG_plugin.xcf index e76c7c0ae..1651fede5 100644 Binary files a/doc/img/ChAnalyzerNG_plugin.xcf and b/doc/img/ChAnalyzerNG_plugin.xcf differ diff --git a/doc/img/ChAnalyzerNG_plugin_overlay_lin.png b/doc/img/ChAnalyzerNG_plugin_overlay_lin.png new file mode 100644 index 000000000..23706118d Binary files /dev/null and b/doc/img/ChAnalyzerNG_plugin_overlay_lin.png differ diff --git a/doc/img/ChAnalyzerNG_plugin_overlay_lin.xcf b/doc/img/ChAnalyzerNG_plugin_overlay_lin.xcf new file mode 100644 index 000000000..e4bcb58d8 Binary files /dev/null and b/doc/img/ChAnalyzerNG_plugin_overlay_lin.xcf differ diff --git a/doc/img/ChAnalyzerNG_plugin_scope1.png b/doc/img/ChAnalyzerNG_plugin_scope1.png index 810a357c7..fb5c7b807 100644 Binary files a/doc/img/ChAnalyzerNG_plugin_scope1.png and b/doc/img/ChAnalyzerNG_plugin_scope1.png differ diff --git a/doc/img/ChAnalyzerNG_plugin_scope1.xcf b/doc/img/ChAnalyzerNG_plugin_scope1.xcf index 151d06301..93c815c43 100644 Binary files a/doc/img/ChAnalyzerNG_plugin_scope1.xcf and b/doc/img/ChAnalyzerNG_plugin_scope1.xcf differ diff --git a/doc/img/ChAnalyzerNG_plugin_scope2.png b/doc/img/ChAnalyzerNG_plugin_scope2.png index dcf1d2826..cefd35fe0 100644 Binary files a/doc/img/ChAnalyzerNG_plugin_scope2.png and b/doc/img/ChAnalyzerNG_plugin_scope2.png differ diff --git a/doc/img/ChAnalyzerNG_plugin_scope2.xcf b/doc/img/ChAnalyzerNG_plugin_scope2.xcf index 8b46b1140..f3354046d 100644 Binary files a/doc/img/ChAnalyzerNG_plugin_scope2.xcf and b/doc/img/ChAnalyzerNG_plugin_scope2.xcf differ diff --git a/doc/img/ChAnalyzerNG_plugin_scope3.png b/doc/img/ChAnalyzerNG_plugin_scope3.png index 87781e7d6..09b8e1af2 100644 Binary files a/doc/img/ChAnalyzerNG_plugin_scope3.png and b/doc/img/ChAnalyzerNG_plugin_scope3.png differ diff --git a/doc/img/ChAnalyzerNG_plugin_scope3.xcf b/doc/img/ChAnalyzerNG_plugin_scope3.xcf index ae2a5e1a7..f4db77e17 100644 Binary files a/doc/img/ChAnalyzerNG_plugin_scope3.xcf and b/doc/img/ChAnalyzerNG_plugin_scope3.xcf differ diff --git a/doc/img/ChAnalyzerNG_plugin_settings.png b/doc/img/ChAnalyzerNG_plugin_settings.png index 9a1f435fd..7d6feeaae 100644 Binary files a/doc/img/ChAnalyzerNG_plugin_settings.png and b/doc/img/ChAnalyzerNG_plugin_settings.png differ diff --git a/doc/img/ChAnalyzerNG_plugin_settings.xcf b/doc/img/ChAnalyzerNG_plugin_settings.xcf index 6ab93483a..56d69b377 100644 Binary files a/doc/img/ChAnalyzerNG_plugin_settings.xcf and b/doc/img/ChAnalyzerNG_plugin_settings.xcf differ diff --git a/doc/img/ChAnalyzerNG_plugin_tetra.png b/doc/img/ChAnalyzerNG_plugin_tetra.png new file mode 100644 index 000000000..aa18960ce Binary files /dev/null and b/doc/img/ChAnalyzerNG_plugin_tetra.png differ diff --git a/doc/img/ChAnalyzerNG_plugin_tetra.xcf b/doc/img/ChAnalyzerNG_plugin_tetra.xcf new file mode 100644 index 000000000..462a0aa43 Binary files /dev/null and b/doc/img/ChAnalyzerNG_plugin_tetra.xcf differ diff --git a/doc/img/DATVDemod_plugin.png b/doc/img/DATVDemod_plugin.png new file mode 100644 index 000000000..cd048bfb2 Binary files /dev/null and b/doc/img/DATVDemod_plugin.png differ diff --git a/doc/img/DATVDemod_plugin.xcf b/doc/img/DATVDemod_plugin.xcf new file mode 100644 index 000000000..966306ffa Binary files /dev/null and b/doc/img/DATVDemod_plugin.xcf differ diff --git a/doc/img/DATVDemod_pluginDATV.png b/doc/img/DATVDemod_pluginDATV.png new file mode 100644 index 000000000..dfbe59ec1 Binary files /dev/null and b/doc/img/DATVDemod_pluginDATV.png differ diff --git a/doc/img/DATVDemod_pluginDATV.xcf b/doc/img/DATVDemod_pluginDATV.xcf new file mode 100644 index 000000000..b013125ae Binary files /dev/null and b/doc/img/DATVDemod_pluginDATV.xcf differ diff --git a/doc/img/DATVDemod_pluginDATV2.png b/doc/img/DATVDemod_pluginDATV2.png new file mode 100644 index 000000000..bfb75641a Binary files /dev/null and b/doc/img/DATVDemod_pluginDATV2.png differ diff --git a/doc/img/DATVDemod_pluginDATV2.xcf b/doc/img/DATVDemod_pluginDATV2.xcf new file mode 100644 index 000000000..9606321f7 Binary files /dev/null and b/doc/img/DATVDemod_pluginDATV2.xcf differ diff --git a/doc/img/DATVDemod_pluginRF.png b/doc/img/DATVDemod_pluginRF.png new file mode 100644 index 000000000..9c93430ff Binary files /dev/null and b/doc/img/DATVDemod_pluginRF.png differ diff --git a/doc/img/DATVDemod_pluginRF.xcf b/doc/img/DATVDemod_pluginRF.xcf new file mode 100644 index 000000000..f28239b49 Binary files /dev/null and b/doc/img/DATVDemod_pluginRF.xcf differ diff --git a/doc/img/DATVDemod_pluginVideo.png b/doc/img/DATVDemod_pluginVideo.png new file mode 100644 index 000000000..de1117a7f Binary files /dev/null and b/doc/img/DATVDemod_pluginVideo.png differ diff --git a/doc/img/DATVDemod_pluginVideo.xcf b/doc/img/DATVDemod_pluginVideo.xcf new file mode 100644 index 000000000..447c20bfd Binary files /dev/null and b/doc/img/DATVDemod_pluginVideo.xcf differ diff --git a/doc/img/DSDdemod_plugin.old.xcf b/doc/img/DSDdemod_plugin.old.xcf deleted file mode 100644 index d6eb120c9..000000000 Binary files a/doc/img/DSDdemod_plugin.old.xcf and /dev/null differ diff --git a/doc/img/DSDdemod_plugin.png b/doc/img/DSDdemod_plugin.png index e834a3e24..1e6d12cb2 100644 Binary files a/doc/img/DSDdemod_plugin.png and b/doc/img/DSDdemod_plugin.png differ diff --git a/doc/img/DSDdemod_plugin.xcf b/doc/img/DSDdemod_plugin.xcf index 7be41e72d..9b176e599 100644 Binary files a/doc/img/DSDdemod_plugin.xcf and b/doc/img/DSDdemod_plugin.xcf differ diff --git a/doc/img/DSDdemod_plugin_2fsk.png b/doc/img/DSDdemod_plugin_2fsk.png new file mode 100644 index 000000000..7f352aaff Binary files /dev/null and b/doc/img/DSDdemod_plugin_2fsk.png differ diff --git a/doc/img/DSDdemod_plugin_2fsk.xcf b/doc/img/DSDdemod_plugin_2fsk.xcf new file mode 100644 index 000000000..8f528c8e4 Binary files /dev/null and b/doc/img/DSDdemod_plugin_2fsk.xcf differ diff --git a/doc/img/DSDdemod_plugin_2fsk_sym.png b/doc/img/DSDdemod_plugin_2fsk_sym.png new file mode 100644 index 000000000..f38ee59b8 Binary files /dev/null and b/doc/img/DSDdemod_plugin_2fsk_sym.png differ diff --git a/doc/img/DSDdemod_plugin_2fsk_sym.xcf b/doc/img/DSDdemod_plugin_2fsk_sym.xcf new file mode 100644 index 000000000..0d9488995 Binary files /dev/null and b/doc/img/DSDdemod_plugin_2fsk_sym.xcf differ diff --git a/doc/img/DSDdemod_plugin_4fsk.png b/doc/img/DSDdemod_plugin_4fsk.png new file mode 100644 index 000000000..57e800306 Binary files /dev/null and b/doc/img/DSDdemod_plugin_4fsk.png differ diff --git a/doc/img/DSDdemod_plugin_4fsk.xcf b/doc/img/DSDdemod_plugin_4fsk.xcf new file mode 100644 index 000000000..4e890e0f6 Binary files /dev/null and b/doc/img/DSDdemod_plugin_4fsk.xcf differ diff --git a/doc/img/DSDdemod_plugin_4fsk_sym.png b/doc/img/DSDdemod_plugin_4fsk_sym.png new file mode 100644 index 000000000..5f09dbb77 Binary files /dev/null and b/doc/img/DSDdemod_plugin_4fsk_sym.png differ diff --git a/doc/img/DSDdemod_plugin_4fsk_sym.xcf b/doc/img/DSDdemod_plugin_4fsk_sym.xcf new file mode 100644 index 000000000..cd378c5d1 Binary files /dev/null and b/doc/img/DSDdemod_plugin_4fsk_sym.xcf differ diff --git a/doc/img/DSDdemod_plugin_dmr_polar.png b/doc/img/DSDdemod_plugin_dmr_polar.png deleted file mode 100644 index 57b548684..000000000 Binary files a/doc/img/DSDdemod_plugin_dmr_polar.png and /dev/null differ diff --git a/doc/img/DSDdemod_plugin_dstar_polar.png b/doc/img/DSDdemod_plugin_dstar_polar.png deleted file mode 100644 index 342b9b3e2..000000000 Binary files a/doc/img/DSDdemod_plugin_dstar_polar.png and /dev/null differ diff --git a/doc/img/DSDdemod_plugin_nxdn_rcch_status.png b/doc/img/DSDdemod_plugin_nxdn_rcch_status.png new file mode 100644 index 000000000..de95cb2a7 Binary files /dev/null and b/doc/img/DSDdemod_plugin_nxdn_rcch_status.png differ diff --git a/doc/img/DSDdemod_plugin_nxdn_rcch_status.xcf b/doc/img/DSDdemod_plugin_nxdn_rcch_status.xcf new file mode 100644 index 000000000..7a4fa4aea Binary files /dev/null and b/doc/img/DSDdemod_plugin_nxdn_rcch_status.xcf differ diff --git a/doc/img/DSDdemod_plugin_nxdn_rtdch_status.png b/doc/img/DSDdemod_plugin_nxdn_rtdch_status.png new file mode 100644 index 000000000..329106e0d Binary files /dev/null and b/doc/img/DSDdemod_plugin_nxdn_rtdch_status.png differ diff --git a/doc/img/DSDdemod_plugin_nxdn_rtdch_status.xcf b/doc/img/DSDdemod_plugin_nxdn_rtdch_status.xcf new file mode 100644 index 000000000..7e1a1611a Binary files /dev/null and b/doc/img/DSDdemod_plugin_nxdn_rtdch_status.xcf differ diff --git a/doc/img/DSDdemod_plugin_scope.png b/doc/img/DSDdemod_plugin_scope.png deleted file mode 100644 index be19ad4f7..000000000 Binary files a/doc/img/DSDdemod_plugin_scope.png and /dev/null differ diff --git a/doc/img/DSDdemod_plugin_scope.xcf b/doc/img/DSDdemod_plugin_scope.xcf deleted file mode 100644 index 18302b922..000000000 Binary files a/doc/img/DSDdemod_plugin_scope.xcf and /dev/null differ diff --git a/doc/img/DSDdemod_plugin_scope2.png b/doc/img/DSDdemod_plugin_scope2.png deleted file mode 100644 index f71f7a3d4..000000000 Binary files a/doc/img/DSDdemod_plugin_scope2.png and /dev/null differ diff --git a/doc/img/DSDdemod_plugin_scope2.xcf b/doc/img/DSDdemod_plugin_scope2.xcf deleted file mode 100644 index 75687fd36..000000000 Binary files a/doc/img/DSDdemod_plugin_scope2.xcf and /dev/null differ diff --git a/doc/img/DSDdemod_plugin_status_text_log.png b/doc/img/DSDdemod_plugin_status_text_log.png new file mode 100644 index 000000000..890348a08 Binary files /dev/null and b/doc/img/DSDdemod_plugin_status_text_log.png differ diff --git a/doc/img/DSDdemod_plugin_status_text_log.xcf b/doc/img/DSDdemod_plugin_status_text_log.xcf new file mode 100644 index 000000000..7c1c19393 Binary files /dev/null and b/doc/img/DSDdemod_plugin_status_text_log.xcf differ diff --git a/doc/img/DaemonSink.png b/doc/img/DaemonSink.png new file mode 100644 index 000000000..1a6f1afcd Binary files /dev/null and b/doc/img/DaemonSink.png differ diff --git a/doc/img/DaemonSink.xcf b/doc/img/DaemonSink.xcf new file mode 100644 index 000000000..ba60ba5e9 Binary files /dev/null and b/doc/img/DaemonSink.xcf differ diff --git a/doc/img/DaemonSource.png b/doc/img/DaemonSource.png new file mode 100644 index 000000000..1adf4ac07 Binary files /dev/null and b/doc/img/DaemonSource.png differ diff --git a/doc/img/DaemonSource.xcf b/doc/img/DaemonSource.xcf new file mode 100644 index 000000000..d40f8a58c Binary files /dev/null and b/doc/img/DaemonSource.xcf differ diff --git a/doc/img/DaemonSource_5.png b/doc/img/DaemonSource_5.png new file mode 100644 index 000000000..723242bdd Binary files /dev/null and b/doc/img/DaemonSource_5.png differ diff --git a/doc/img/DaemonSource_5.xcf b/doc/img/DaemonSource_5.xcf new file mode 100644 index 000000000..fee564fec Binary files /dev/null and b/doc/img/DaemonSource_5.xcf differ diff --git a/doc/img/FileSource_plugin.png b/doc/img/FileSource_plugin.png new file mode 100644 index 000000000..a01a2244d Binary files /dev/null and b/doc/img/FileSource_plugin.png differ diff --git a/doc/img/FileSource_plugin.xcf b/doc/img/FileSource_plugin.xcf new file mode 100644 index 000000000..acf561e39 Binary files /dev/null and b/doc/img/FileSource_plugin.xcf differ diff --git a/doc/img/LimeSDRInput_plugin.png b/doc/img/LimeSDRInput_plugin.png index 32023ff73..87ae70250 100644 Binary files a/doc/img/LimeSDRInput_plugin.png and b/doc/img/LimeSDRInput_plugin.png differ diff --git a/doc/img/LimeSDRInput_plugin.xcf b/doc/img/LimeSDRInput_plugin.xcf index 7aca4d4cd..ae259f017 100644 Binary files a/doc/img/LimeSDRInput_plugin.xcf and b/doc/img/LimeSDRInput_plugin.xcf differ diff --git a/doc/img/LimeSDRInput_plugin_2.png b/doc/img/LimeSDRInput_plugin_2.png index 522d491d7..39b3927a0 100644 Binary files a/doc/img/LimeSDRInput_plugin_2.png and b/doc/img/LimeSDRInput_plugin_2.png differ diff --git a/doc/img/LimeSDRInput_plugin_2.xcf b/doc/img/LimeSDRInput_plugin_2.xcf index 40a1428ba..6399d989d 100644 Binary files a/doc/img/LimeSDRInput_plugin_2.xcf and b/doc/img/LimeSDRInput_plugin_2.xcf differ diff --git a/doc/img/LimeSDROutput_plugin.png b/doc/img/LimeSDROutput_plugin.png index fb07270f8..b51b19244 100644 Binary files a/doc/img/LimeSDROutput_plugin.png and b/doc/img/LimeSDROutput_plugin.png differ diff --git a/doc/img/LimeSDROutput_plugin.xcf b/doc/img/LimeSDROutput_plugin.xcf index 766fba225..0d5adc76d 100644 Binary files a/doc/img/LimeSDROutput_plugin.xcf and b/doc/img/LimeSDROutput_plugin.xcf differ diff --git a/doc/img/MainWindow_PreferencesAudioInput.png b/doc/img/MainWindow_PreferencesAudioInput.png deleted file mode 100644 index f29421e91..000000000 Binary files a/doc/img/MainWindow_PreferencesAudioInput.png and /dev/null differ diff --git a/doc/img/MainWindow_PreferencesAudioOutput.png b/doc/img/MainWindow_PreferencesAudioOutput.png deleted file mode 100644 index 927b468e0..000000000 Binary files a/doc/img/MainWindow_PreferencesAudioOutput.png and /dev/null differ diff --git a/doc/img/MainWindow_spectrum_gui.png b/doc/img/MainWindow_spectrum_gui.png new file mode 100644 index 000000000..ec92eaf15 Binary files /dev/null and b/doc/img/MainWindow_spectrum_gui.png differ diff --git a/doc/img/MainWindow_spectrum_gui.xcf b/doc/img/MainWindow_spectrum_gui.xcf new file mode 100644 index 000000000..0745ac0c2 Binary files /dev/null and b/doc/img/MainWindow_spectrum_gui.xcf differ diff --git a/doc/img/NFMdemod_plugin.png b/doc/img/NFMdemod_plugin.png index 4ff267df3..fbacd4b8b 100644 Binary files a/doc/img/NFMdemod_plugin.png and b/doc/img/NFMdemod_plugin.png differ diff --git a/doc/img/NFMdemod_plugin.xcf b/doc/img/NFMdemod_plugin.xcf index c121a648c..17fa30289 100644 Binary files a/doc/img/NFMdemod_plugin.xcf and b/doc/img/NFMdemod_plugin.xcf differ diff --git a/doc/img/SDRdaemonSink_plugin.png b/doc/img/SDRdaemonSink_plugin.png index e6ffbdf9c..f2d1d4fb7 100644 Binary files a/doc/img/SDRdaemonSink_plugin.png and b/doc/img/SDRdaemonSink_plugin.png differ diff --git a/doc/img/SDRdaemonSink_plugin.xcf b/doc/img/SDRdaemonSink_plugin.xcf index a2a04f3a2..1b195f62e 100644 Binary files a/doc/img/SDRdaemonSink_plugin.xcf and b/doc/img/SDRdaemonSink_plugin.xcf differ diff --git a/doc/img/SDRdaemonSink_plugin_04.png b/doc/img/SDRdaemonSink_plugin_04.png deleted file mode 100644 index 356d141a4..000000000 Binary files a/doc/img/SDRdaemonSink_plugin_04.png and /dev/null differ diff --git a/doc/img/SDRdaemonSink_plugin_04.xcf b/doc/img/SDRdaemonSink_plugin_04.xcf deleted file mode 100644 index d7e9e9360..000000000 Binary files a/doc/img/SDRdaemonSink_plugin_04.xcf and /dev/null differ diff --git a/doc/img/SDRdaemonSink_plugin_05.png b/doc/img/SDRdaemonSink_plugin_05.png new file mode 100644 index 000000000..51b0180dc Binary files /dev/null and b/doc/img/SDRdaemonSink_plugin_05.png differ diff --git a/doc/img/SDRdaemonSink_plugin_05.xcf b/doc/img/SDRdaemonSink_plugin_05.xcf new file mode 100644 index 000000000..08dcc57d7 Binary files /dev/null and b/doc/img/SDRdaemonSink_plugin_05.xcf differ diff --git a/doc/img/SDRdaemonSink_plugin_06.png b/doc/img/SDRdaemonSink_plugin_06.png index c69e557f0..792aa82e7 100644 Binary files a/doc/img/SDRdaemonSink_plugin_06.png and b/doc/img/SDRdaemonSink_plugin_06.png differ diff --git a/doc/img/SDRdaemonSink_plugin_06.xcf b/doc/img/SDRdaemonSink_plugin_06.xcf index 732c630d2..4d161ca7e 100644 Binary files a/doc/img/SDRdaemonSink_plugin_06.xcf and b/doc/img/SDRdaemonSink_plugin_06.xcf differ diff --git a/doc/img/SDRdaemonSink_plugin_07.png b/doc/img/SDRdaemonSink_plugin_07.png deleted file mode 100644 index 949d697b0..000000000 Binary files a/doc/img/SDRdaemonSink_plugin_07.png and /dev/null differ diff --git a/doc/img/SDRdaemonSink_plugin_07.xcf b/doc/img/SDRdaemonSink_plugin_07.xcf deleted file mode 100644 index bc4d995da..000000000 Binary files a/doc/img/SDRdaemonSink_plugin_07.xcf and /dev/null differ diff --git a/doc/img/SDRdaemonSource_plugin.png b/doc/img/SDRdaemonSource_plugin.png index f435841f6..60ad56837 100644 Binary files a/doc/img/SDRdaemonSource_plugin.png and b/doc/img/SDRdaemonSource_plugin.png differ diff --git a/doc/img/SDRdaemonSource_plugin_02.png b/doc/img/SDRdaemonSource_plugin_02.png index a9b63f076..5e350cecd 100644 Binary files a/doc/img/SDRdaemonSource_plugin_02.png and b/doc/img/SDRdaemonSource_plugin_02.png differ diff --git a/doc/img/SDRdaemonSource_plugin_04.png b/doc/img/SDRdaemonSource_plugin_04.png index 806842819..2c2fd4bf6 100644 Binary files a/doc/img/SDRdaemonSource_plugin_04.png and b/doc/img/SDRdaemonSource_plugin_04.png differ diff --git a/doc/img/SDRdaemonSource_plugin_05.png b/doc/img/SDRdaemonSource_plugin_05.png index 5a07c3a82..a5ec1f341 100644 Binary files a/doc/img/SDRdaemonSource_plugin_05.png and b/doc/img/SDRdaemonSource_plugin_05.png differ diff --git a/doc/img/SDRdaemonSource_plugin_06.png b/doc/img/SDRdaemonSource_plugin_06.png new file mode 100644 index 000000000..368cb751a Binary files /dev/null and b/doc/img/SDRdaemonSource_plugin_06.png differ diff --git a/doc/img/SDRdaemonSource_plugin_06.xcf b/doc/img/SDRdaemonSource_plugin_06.xcf new file mode 100644 index 000000000..4bd7114c9 Binary files /dev/null and b/doc/img/SDRdaemonSource_plugin_06.xcf differ diff --git a/doc/img/SSBDemod_plugin.png b/doc/img/SSBDemod_plugin.png index e8437037a..01c670ad4 100644 Binary files a/doc/img/SSBDemod_plugin.png and b/doc/img/SSBDemod_plugin.png differ diff --git a/doc/img/SSBDemod_plugin.xcf b/doc/img/SSBDemod_plugin.xcf index 9e3857bb4..4e8d04834 100644 Binary files a/doc/img/SSBDemod_plugin.xcf and b/doc/img/SSBDemod_plugin.xcf differ diff --git a/doc/img/UDPsink_plugin.png b/doc/img/UDPsink_plugin.png index 8dfbc5312..cc4a913cf 100644 Binary files a/doc/img/UDPsink_plugin.png and b/doc/img/UDPsink_plugin.png differ diff --git a/doc/img/UDPsink_plugin.xcf b/doc/img/UDPsink_plugin.xcf index 39e9a6e0f..5c6042f25 100644 Binary files a/doc/img/UDPsink_plugin.xcf and b/doc/img/UDPsink_plugin.xcf differ diff --git a/doc/img/UDPsrc_plugin_agc.png b/doc/img/UDPsink_plugin_agc.png similarity index 100% rename from doc/img/UDPsrc_plugin_agc.png rename to doc/img/UDPsink_plugin_agc.png diff --git a/doc/img/UDPsrc_plugin_agc.xcf b/doc/img/UDPsink_plugin_agc.xcf similarity index 100% rename from doc/img/UDPsrc_plugin_agc.xcf rename to doc/img/UDPsink_plugin_agc.xcf diff --git a/doc/img/UDPsrc_plugin_sq.png b/doc/img/UDPsink_plugin_sq.png similarity index 100% rename from doc/img/UDPsrc_plugin_sq.png rename to doc/img/UDPsink_plugin_sq.png diff --git a/doc/img/UDPsrc_plugin_sq.xcf b/doc/img/UDPsink_plugin_sq.xcf similarity index 100% rename from doc/img/UDPsrc_plugin_sq.xcf rename to doc/img/UDPsink_plugin_sq.xcf diff --git a/doc/img/UDPsource_plugin.png b/doc/img/UDPsource_plugin.png new file mode 100644 index 000000000..8dfbc5312 Binary files /dev/null and b/doc/img/UDPsource_plugin.png differ diff --git a/doc/img/UDPsource_plugin.xcf b/doc/img/UDPsource_plugin.xcf new file mode 100644 index 000000000..39e9a6e0f Binary files /dev/null and b/doc/img/UDPsource_plugin.xcf differ diff --git a/doc/img/UDPsrc_plugin.png b/doc/img/UDPsrc_plugin.png deleted file mode 100644 index cc4a913cf..000000000 Binary files a/doc/img/UDPsrc_plugin.png and /dev/null differ diff --git a/doc/img/UDPsrc_plugin.xcf b/doc/img/UDPsrc_plugin.xcf deleted file mode 100644 index 5c6042f25..000000000 Binary files a/doc/img/UDPsrc_plugin.xcf and /dev/null differ diff --git a/doc/img/sdrangel.xcf b/doc/img/sdrangel.xcf index d2aa83d3e..687bc1976 100644 Binary files a/doc/img/sdrangel.xcf and b/doc/img/sdrangel.xcf differ diff --git a/dsdcc/CMakeLists.txt b/dsdcc/CMakeLists.txt index b981ab010..5f5611cae 100644 --- a/dsdcc/CMakeLists.txt +++ b/dsdcc/CMakeLists.txt @@ -12,8 +12,11 @@ set(dsdcc_SOURCES ${LIBDSDCCSRC}/dsd_symbol.cpp ${LIBDSDCCSRC}/dstar.cpp ${LIBDSDCCSRC}/ysf.cpp - ${LIBDSDCCSRC}/nxdn.cpp ${LIBDSDCCSRC}/dpmr.cpp + ${LIBDSDCCSRC}/nxdn.cpp + ${LIBDSDCCSRC}/nxdnconvolution.cpp + ${LIBDSDCCSRC}/nxdncrc.cpp + ${LIBDSDCCSRC}/nxdnmessage.cpp ${LIBDSDCCSRC}/p25p1_heuristics.cpp ${LIBDSDCCSRC}/fec.cpp ${LIBDSDCCSRC}/crc.cpp @@ -39,8 +42,11 @@ set(dsdcc_HEADERS ${LIBDSDCCSRC}/dsd_symbol.h ${LIBDSDCCSRC}/dstar.h ${LIBDSDCCSRC}/ysf.h - ${LIBDSDCCSRC}/nxdn.h ${LIBDSDCCSRC}/dpmr.h + ${LIBDSDCCSRC}/nxdn.h + ${LIBDSDCCSRC}/nxdnconvolution.h + ${LIBDSDCCSRC}/nxdncrc.h + ${LIBDSDCCSRC}/nxdnmessage.h ${LIBDSDCCSRC}/p25p1_heuristics.h ${LIBDSDCCSRC}/runningmaxmin.h ${LIBDSDCCSRC}/doublebuffer.h diff --git a/dsdcc/dsdcc.pro b/dsdcc/dsdcc.pro index e4ced1a58..7cb2add66 100644 --- a/dsdcc/dsdcc.pro +++ b/dsdcc/dsdcc.pro @@ -9,12 +9,12 @@ QT += core TEMPLATE = lib TARGET = dsdcc -CONFIG(MINGW32):LIBDSDCCSRC = "D:\softs\dsdcc" -CONFIG(MINGW64):LIBDSDCCSRC = "D:\softs\dsdcc" +CONFIG(MINGW32):LIBDSDCCSRC = "C:\softs\dsdcc" +CONFIG(MINGW64):LIBDSDCCSRC = "C:\softs\dsdcc" CONFIG(macx):LIBDSDCCSRC = "../../deps/dsdcc" -CONFIG(MINGW32):LIBMBELIBSRC = "D:\softs\mbelib" -CONFIG(MINGW64):LIBMBELIBSRC = "D:\softs\mbelib" +CONFIG(MINGW32):LIBMBELIBSRC = "C:\softs\mbelib" +CONFIG(MINGW64):LIBMBELIBSRC = "C:\softs\mbelib" CONFIG(macx):LIBMBELIBSRC = "../../deps/mbelib" INCLUDEPATH += $$LIBDSDCCSRC @@ -37,6 +37,9 @@ $$LIBDSDCCSRC/dsd_symbol.cpp\ $$LIBDSDCCSRC/dstar.cpp\ $$LIBDSDCCSRC/ysf.cpp\ $$LIBDSDCCSRC/nxdn.cpp\ +$$LIBDSDCCSRC/nxdnconvolution.cpp\ +$$LIBDSDCCSRC/nxdncrc.cpp\ +$$LIBDSDCCSRC/nxdnmessage.cpp\ $$LIBDSDCCSRC/dpmr.cpp\ $$LIBDSDCCSRC/p25p1_heuristics.cpp\ $$LIBDSDCCSRC/fec.cpp\ @@ -62,6 +65,9 @@ $$LIBDSDCCSRC/dsd_symbol.h\ $$LIBDSDCCSRC/dstar.h\ $$LIBDSDCCSRC/ysf.h\ $$LIBDSDCCSRC/nxdn.h\ +$$LIBDSDCCSRC/nxdnconvolution.h\ +$$LIBDSDCCSRC/nxdncrc.h\ +$$LIBDSDCCSRC/nxdnmessage.h\ $$LIBDSDCCSRC/dpmr.h\ $$LIBDSDCCSRC/p25p1_heuristics.h\ $$LIBDSDCCSRC/runningmaxmin.h\ diff --git a/exports/export.h b/exports/export.h new file mode 100644 index 000000000..10b79fea5 --- /dev/null +++ b/exports/export.h @@ -0,0 +1,117 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 Edouard Griffiths, F4EXB. // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef __SDRANGEL_EXPORT_H +#define __SDRANGEL_EXPORT_H + +#if defined (__GNUC__) && (__GNUC__ >= 4) +# define __SDR_EXPORT __attribute__((visibility("default"))) +# define __SDR_IMPORT __attribute__((visibility("default"))) + +#elif defined (_MSC_VER) +# define __SDR_EXPORT __declspec(dllexport) +# define __SDR_IMPORT __declspec(dllimport) + +#else +# define __SDR_EXPORT +# define __SDR_IMPORT +#endif + +/* The 'SDRBASE_API' controls the import/export of 'sdrbase' symbols and classes. + */ +#if !defined(sdrangel_STATIC) +# if defined sdrbase_EXPORTS +# define SDRBASE_API __SDR_EXPORT +# else +# define SDRBASE_API __SDR_IMPORT +# endif +#else +# define SDRBASE_API +#endif + +/* the 'SDRGUI_API' controls the import/export of 'sdrgui' symbols + */ +#if !defined(sdrangel_STATIC) +# ifdef sdrgui_EXPORTS +# define SDRGUI_API __SDR_EXPORT +# else +# define SDRGUI_API __SDR_IMPORT +# endif +#else +# define SDRGUI_API +#endif + +/* the 'DEVICES_API' controls the import/export of 'devices' symbols + */ +#if !defined(sdrangel_STATIC) +# ifdef devices_EXPORTS +# define DEVICES_API __SDR_EXPORT +# else +# define DEVICES_API __SDR_IMPORT +# endif +#else +# define DEVICES_API +#endif + +/* the 'HTTPSERVER_API' controls the import/export of 'httpserver' symbols + */ +#if !defined(sdrangel_STATIC) +# ifdef httpserver_EXPORTS +# define HTTPSERVER_API __SDR_EXPORT +# else +# define HTTPSERVER_API __SDR_IMPORT +# endif +#else +# define HTTPSERVER_API +#endif + +/* the 'LOGGING_API' controls the import/export of 'logging' symbols + */ +#if !defined(sdrangel_STATIC) +# ifdef logging_EXPORTS +# define LOGGING_API __SDR_EXPORT +# else +# define LOGGING_API __SDR_IMPORT +# endif +#else +# define LOGGING_API +#endif + +/* the 'QRTPLIB_API' controls the import/export of 'qrtplib' symbols + */ +#if !defined(sdrangel_STATIC) +# ifdef qrtplib_EXPORTS +# define QRTPLIB_API __SDR_EXPORT +# else +# define QRTPLIB_API __SDR_IMPORT +# endif +#else +# define QRTPLIB_API +#endif + +/* the 'SWG_API' controls the import/export of 'swagger' symbols + */ +#if !defined(sdrangel_STATIC) +# ifdef swagger_EXPORTS +# define SWG_API __SDR_EXPORT +# else +# define SWG_API __SDR_IMPORT +# endif +#else +# define SWG_API +#endif + +#endif /* __SDRANGEL_EXPORT_H */ diff --git a/fcdhid/fcdhid.pro b/fcdhid/fcdhid.pro index 3b37e19bc..5f2ef61ce 100644 --- a/fcdhid/fcdhid.pro +++ b/fcdhid/fcdhid.pro @@ -9,10 +9,10 @@ QT += core TEMPLATE = lib TARGET = fcdhid -CONFIG(MINGW32):INCLUDEPATH += "D:\softs\libusb-1.0.20\include" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.22\include" CONFIG(MINGW32):DEFINES += MINGW32=1 -CONFIG(MINGW64):INCLUDEPATH += "D:\softs\libusb-1.0.20\include" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.22\include" CONFIG(MINGW64):DEFINES += MINGW32=1 CONFIG(macx):INCLUDEPATH += "/opt/local/include" @@ -26,6 +26,6 @@ HEADERS = $$PWD/fcdhid.h\ $$PWD/hid-libusb.h\ $$PWD/hidapi.h -CONFIG(MINGW32):LIBS += -LD:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -liconv -CONFIG(MINGW64):LIBS += -LD:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 -liconv +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.22\MinGW32\dll -llibusb-1.0 -liconv +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.22\MinGW64\dll -llibusb-1.0 -liconv CONFIG(macx):LIBS += -L/opt/local/lib -lusb-1.0 -liconv diff --git a/fcdlib/fcdlib.pro b/fcdlib/fcdlib.pro index f6bf6c8b7..e69ae264a 100644 --- a/fcdlib/fcdlib.pro +++ b/fcdlib/fcdlib.pro @@ -9,8 +9,9 @@ QT += core TEMPLATE = lib TARGET = fcdlib -CONFIG(MINGW32):INCLUDEPATH += "D:\softs\libusb-1.0.20\include" -CONFIG(MINGW64):INCLUDEPATH += "D:\softs\libusb-1.0.20\include" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.22\include" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.22\include" +CONFIG(MSVC):INCLUDEPATH += "C:\softs\libusb-1.0.22\include" SOURCES = $$PWD/fcdtraits.cpp\ $$PWD/fcdproplusconst.cpp\ @@ -20,5 +21,6 @@ HEADERS = $$PWD/fcdtraits.h\ $$PWD/fcdproplusconst.h\ $$PWD/fcdproconst.h -CONFIG(MINGW32):LIBS += -LD:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -CONFIG(MINGW64):LIBS += -LD:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.22\MinGW32\dll -llibusb-1.0 +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.22\MinGW64\dll -llibusb-1.0 +CONFIG(MSVC):LIBS += -LC:\softs\libusb-1.0.22\MS64\dll -llibusb-1.0 diff --git a/fcdlib/fcdtraits.cpp b/fcdlib/fcdtraits.cpp index 2ace765d2..41f8b66a6 100644 --- a/fcdlib/fcdtraits.cpp +++ b/fcdlib/fcdtraits.cpp @@ -22,8 +22,8 @@ const char *fcd_traits::displayedName = "FunCube Dongle Pro+"; const char *fcd_traits::pluginDisplayedName = "FunCube Pro Input"; const char *fcd_traits::pluginDisplayedName = "FunCube Pro+ Input"; -const char *fcd_traits::pluginVersion = "3.9.0"; -const char *fcd_traits::pluginVersion = "3.9.0"; +const char *fcd_traits::pluginVersion = "4.0.0"; +const char *fcd_traits::pluginVersion = "4.0.0"; const int64_t fcd_traits::loLowLimitFreq = 64000000L; const int64_t fcd_traits::loLowLimitFreq = 150000L; diff --git a/httpserver/CMakeLists.txt b/httpserver/CMakeLists.txt index 31d74f277..1b8341028 100644 --- a/httpserver/CMakeLists.txt +++ b/httpserver/CMakeLists.txt @@ -33,6 +33,7 @@ set(httpserver_HEADERS include_directories( . + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_CURRENT_BINARY_DIR} ) @@ -49,6 +50,6 @@ target_link_libraries(httpserver ${QT_LIBRARIES} ) -qt5_use_modules(httpserver Core Network) +target_link_libraries(httpserver Qt5::Core Qt5::Network) install(TARGETS httpserver DESTINATION lib) diff --git a/httpserver/httpconnectionhandler.cpp b/httpserver/httpconnectionhandler.cpp index fc92311a2..4b3fc9a31 100644 --- a/httpserver/httpconnectionhandler.cpp +++ b/httpserver/httpconnectionhandler.cpp @@ -97,7 +97,9 @@ void HttpConnectionHandler::createSocket() void HttpConnectionHandler::run() { +#ifdef SUPERVERBOSE qDebug("HttpConnectionHandler (%p): thread started", this); +#endif try { exec(); @@ -109,13 +111,17 @@ void HttpConnectionHandler::run() socket->close(); delete socket; readTimer.stop(); +#ifdef SUPERVERBOSE qDebug("HttpConnectionHandler (%p): thread stopped", this); +#endif } void HttpConnectionHandler::handleConnection(tSocketDescriptor socketDescriptor) { +#ifdef SUPERVERBOSE qDebug("HttpConnectionHandler (%p): handle new connection", this); +#endif busy = true; Q_ASSERT(socket->isOpen()==false); // if not, then the handler is already busy @@ -228,7 +234,11 @@ void HttpConnectionHandler::read() if (currentRequest->getStatus()==HttpRequest::complete) { readTimer.stop(); - qDebug("HttpConnectionHandler (%p): received request",this); + qDebug("HttpConnectionHandler (%p): received request from %s (%s) %s", + this, + qPrintable(currentRequest->getPeerAddress().toString()), + currentRequest->getMethod().toStdString().c_str(), + currentRequest->getPath().toStdString().c_str()); // Copy the Connection:close header to the response HttpResponse response(socket); @@ -266,7 +276,9 @@ void HttpConnectionHandler::read() response.write(QByteArray(),true); } +#ifdef SUPERVERBOSE qDebug("HttpConnectionHandler (%p): finished request",this); +#endif // Find out whether the connection must be closed if (!closeConnection) diff --git a/httpserver/httpconnectionhandler.h b/httpserver/httpconnectionhandler.h index b93c73432..855956c8d 100644 --- a/httpserver/httpconnectionhandler.h +++ b/httpserver/httpconnectionhandler.h @@ -18,6 +18,8 @@ #include "httprequesthandler.h" #include "httplistenersettings.h" +#include "export.h" + namespace qtwebapp { /** Alias type definition, for compatibility to different Qt versions */ @@ -47,7 +49,7 @@ namespace qtwebapp { The readTimeout value defines the maximum time to wait for a complete HTTP request. @see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize. */ -class DECLSPEC HttpConnectionHandler : public QThread { +class HTTPSERVER_API HttpConnectionHandler : public QThread { Q_OBJECT Q_DISABLE_COPY(HttpConnectionHandler) diff --git a/httpserver/httpconnectionhandlerpool.h b/httpserver/httpconnectionhandlerpool.h index 3122a6d20..92d43f3b5 100644 --- a/httpserver/httpconnectionhandlerpool.h +++ b/httpserver/httpconnectionhandlerpool.h @@ -9,6 +9,8 @@ #include "httpconnectionhandler.h" #include "httplistenersettings.h" +#include "export.h" + namespace qtwebapp { /** @@ -46,7 +48,7 @@ namespace qtwebapp { @see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize */ -class DECLSPEC HttpConnectionHandlerPool : public QObject { +class HTTPSERVER_API HttpConnectionHandlerPool : public QObject { Q_OBJECT Q_DISABLE_COPY(HttpConnectionHandlerPool) public: diff --git a/httpserver/httpcookie.h b/httpserver/httpcookie.h index 2075554d5..6d22d9ec1 100644 --- a/httpserver/httpcookie.h +++ b/httpserver/httpcookie.h @@ -10,6 +10,8 @@ #include #include "httpglobal.h" +#include "export.h" + namespace qtwebapp { /** @@ -18,7 +20,7 @@ namespace qtwebapp { 2109. */ -class DECLSPEC HttpCookie +class HTTPSERVER_API HttpCookie { public: diff --git a/httpserver/httpdocrootsettings.h b/httpserver/httpdocrootsettings.h index ebcc73bff..1bb3a7d15 100644 --- a/httpserver/httpdocrootsettings.h +++ b/httpserver/httpdocrootsettings.h @@ -12,8 +12,9 @@ namespace qtwebapp { -struct HttpDocrootSettings +class HttpDocrootSettings { +public: QString path; QString encoding; int maxAge; diff --git a/httpserver/httplistener.h b/httpserver/httplistener.h index aa3168de8..c10951ef6 100644 --- a/httpserver/httplistener.h +++ b/httpserver/httplistener.h @@ -15,6 +15,8 @@ #include "httprequesthandler.h" #include "httplistenersettings.h" +#include "export.h" + namespace qtwebapp { /** @@ -42,7 +44,7 @@ namespace qtwebapp { @see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize */ -class DECLSPEC HttpListener : public QTcpServer { +class HTTPSERVER_API HttpListener : public QTcpServer { Q_OBJECT Q_DISABLE_COPY(HttpListener) public: diff --git a/httpserver/httplistenersettings.h b/httpserver/httplistenersettings.h index 378da1c66..018ee8303 100644 --- a/httpserver/httplistenersettings.h +++ b/httpserver/httplistenersettings.h @@ -10,8 +10,9 @@ namespace qtwebapp { -struct HttpListenerSettings +class HttpListenerSettings { +public: QString host; int port; int minThreads; diff --git a/httpserver/httprequest.h b/httpserver/httprequest.h index 79cd2bbee..e35f6a65e 100644 --- a/httpserver/httprequest.h +++ b/httpserver/httprequest.h @@ -17,6 +17,8 @@ #include "httpglobal.h" #include "httplistenersettings.h" +#include "export.h" + namespace qtwebapp { /** @@ -36,7 +38,7 @@ namespace qtwebapp { The body is always a little larger than the file itself. */ -class DECLSPEC HttpRequest { +class HTTPSERVER_API HttpRequest { Q_DISABLE_COPY(HttpRequest) friend class HttpSessionStore; diff --git a/httpserver/httprequesthandler.h b/httpserver/httprequesthandler.h index 862baf89c..86e22c7a9 100644 --- a/httpserver/httprequesthandler.h +++ b/httpserver/httprequesthandler.h @@ -10,6 +10,8 @@ #include "httprequest.h" #include "httpresponse.h" +#include "export.h" + namespace qtwebapp { /** @@ -24,7 +26,7 @@ namespace qtwebapp { @see StaticFileController which delivers static local files. */ -class DECLSPEC HttpRequestHandler : public QObject { +class HTTPSERVER_API HttpRequestHandler : public QObject { Q_OBJECT Q_DISABLE_COPY(HttpRequestHandler) public: diff --git a/httpserver/httpresponse.h b/httpserver/httpresponse.h index 044484b79..61060886c 100644 --- a/httpserver/httpresponse.h +++ b/httpserver/httpresponse.h @@ -12,6 +12,8 @@ #include "httpglobal.h" #include "httpcookie.h" +#include "export.h" + namespace qtwebapp { /** @@ -33,7 +35,7 @@ namespace qtwebapp { before calling write(). Web Browsers use that information to display a progress bar. */ -class DECLSPEC HttpResponse { +class HTTPSERVER_API HttpResponse { Q_DISABLE_COPY(HttpResponse) public: diff --git a/httpserver/httpserver.pro b/httpserver/httpserver.pro index 33e3b89a4..9dd999c07 100644 --- a/httpserver/httpserver.pro +++ b/httpserver/httpserver.pro @@ -10,6 +10,8 @@ TEMPLATE = lib TARGET = httpserver INCLUDEPATH += $$PWD +INCLUDEPATH += ../exports + QMAKE_CXXFLAGS += -std=c++11 CONFIG(Release):build_subdir = release diff --git a/httpserver/httpsession.h b/httpserver/httpsession.h index dabe982c2..0625a1171 100644 --- a/httpserver/httpsession.h +++ b/httpserver/httpsession.h @@ -11,6 +11,8 @@ #include #include "httpglobal.h" +#include "export.h" + namespace qtwebapp { /** @@ -20,7 +22,7 @@ namespace qtwebapp { @see HttpSessionStore should be used to create and get instances of this class. */ -class DECLSPEC HttpSession { +class HTTPSERVER_API HttpSession { public: @@ -53,7 +55,7 @@ public: QByteArray getId() const; /** - Null sessions cannot store data. All calls to set() and remove() + Null sessions cannot store data. All calls to set() and remove() do not have any effect.This method is thread safe. */ bool isNull() const; diff --git a/httpserver/httpsessionstore.h b/httpserver/httpsessionstore.h index 395ff8530..2e789d7c4 100644 --- a/httpserver/httpsessionstore.h +++ b/httpserver/httpsessionstore.h @@ -16,6 +16,8 @@ #include "httprequest.h" #include "httpsessionssettings.h" +#include "export.h" + namespace qtwebapp { /** @@ -33,7 +35,7 @@ namespace qtwebapp { */ -class DECLSPEC HttpSessionStore : public QObject { +class HTTPSERVER_API HttpSessionStore : public QObject { Q_OBJECT Q_DISABLE_COPY(HttpSessionStore) public: diff --git a/httpserver/staticfilecontroller.h b/httpserver/staticfilecontroller.h index f369181e3..30f009d25 100644 --- a/httpserver/staticfilecontroller.h +++ b/httpserver/staticfilecontroller.h @@ -13,6 +13,8 @@ #include "httpresponse.h" #include "httprequesthandler.h" +#include "export.h" + namespace qtwebapp { /** @@ -44,7 +46,7 @@ namespace qtwebapp { class HttpDocrootSettings; -class DECLSPEC StaticFileController : public HttpRequestHandler { +class HTTPSERVER_API StaticFileController : public HttpRequestHandler { Q_OBJECT Q_DISABLE_COPY(StaticFileController) public: diff --git a/libairspy/libairspy.pro b/libairspy/libairspy.pro index aa5031906..fbefedc88 100644 --- a/libairspy/libairspy.pro +++ b/libairspy/libairspy.pro @@ -9,12 +9,18 @@ QT += core TEMPLATE = lib TARGET = libairspy -CONFIG(MINGW32):LIBAIRSPYSRC = "D:\softs\libairspy\libairspy" -CONFIG(MINGW64):LIBAIRSPYSRC = "D:\softs\libairspy\libairspy" +CONFIG(MINGW32):LIBAIRSPYSRC = "C:\softs\libairspy\libairspy" +CONFIG(MINGW64):LIBAIRSPYSRC = "C:\softs\libairspy\libairspy" +CONFIG(MSVC):LIBAIRSPYSRC = "C:\softs\libairspy\libairspy" + INCLUDEPATH += $$LIBAIRSPYSRC/src -CONFIG(MINGW32):INCLUDEPATH += "D:\softs\libusb-1.0.20\include\libusb-1.0" -CONFIG(MINGW64):INCLUDEPATH += "D:\softs\libusb-1.0.20\include\libusb-1.0" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" +CONFIG(MSVC):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" +CONFIG(MSVC):INCLUDEPATH += "C:\softs\pthreads-w32\include" + +CONFIG(MSVC):DEFINES += _TIMESPEC_DEFINED SOURCES = $$LIBAIRSPYSRC/src/airspy.c\ $$LIBAIRSPYSRC/src/iqconverter_float.c\ @@ -26,8 +32,10 @@ HEADERS = $$LIBAIRSPYSRC/src/airspy.h\ $$LIBAIRSPYSRC/src/iqconverter_int16.h\ $$LIBAIRSPYSRC/src/filters.h -CONFIG(MINGW32):LIBS += -LD:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -CONFIG(MINGW64):LIBS += -LD:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.22\MinGW32\dll -llibusb-1.0 +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.22\MinGW64\dll -llibusb-1.0 +CONFIG(MSVC):LIBS += -LC:\softs\libusb-1.0.22\MS64\dll -llibusb-1.0 +CONFIG(MSVC):LIBS += -LC:\softs\pthreads-w32\lib\x64 -lpthreadVC2 CONFIG(ANDROID):CONFIG += mobility CONFIG(ANDROID):MOBILITY = diff --git a/libairspyhf/libairspyhf.pro b/libairspyhf/libairspyhf.pro index 0c9efeae6..1d4bf12d7 100644 --- a/libairspyhf/libairspyhf.pro +++ b/libairspyhf/libairspyhf.pro @@ -9,12 +9,16 @@ QT += core TEMPLATE = lib TARGET = libairspyhf -CONFIG(MINGW32):LIBAIRSPYHFSRC = "D:\softs\airspyhf\libairspyhf" -CONFIG(MINGW64):LIBAIRSPYHFSRC = "D:\softs\airspyhf\libairspyhf" +CONFIG(MINGW32):LIBAIRSPYHFSRC = "C:\softs\airspyhf\libairspyhf" +CONFIG(MINGW64):LIBAIRSPYHFSRC = "C:\softs\airspyhf\libairspyhf" +CONFIG(MSVC):LIBAIRSPYHFSRC = "C:\softs\airspyhf\libairspyhf" + INCLUDEPATH += $$LIBAIRSPYHFSRC/src -CONFIG(MINGW32):INCLUDEPATH += "D:\softs\libusb-1.0.20\include\libusb-1.0" -CONFIG(MINGW64):INCLUDEPATH += "D:\softs\libusb-1.0.20\include\libusb-1.0" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" +CONFIG(MSVC):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" +CONFIG(MSVC):INCLUDEPATH += "C:\softs\pthreads-w32\include" SOURCES = $$LIBAIRSPYHFSRC/src/airspyhf.c\ $$LIBAIRSPYHFSRC/src/iqbalancer.c @@ -23,8 +27,10 @@ HEADERS = $$LIBAIRSPYHFSRC/src/airspyhf.h\ $$LIBAIRSPYHFSRC/src/airspyhf_commands.h\ $$LIBAIRSPYHFSRC/src/iqbalancer.h -CONFIG(MINGW32):LIBS += -LD:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -CONFIG(MINGW64):LIBS += -LD:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.22\MinGW32\dll -llibusb-1.0 +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.22\MinGW64\dll -llibusb-1.0 +CONFIG(MSVC):LIBS += -LC:\softs\libusb-1.0.22\MS64\dll -llibusb-1.0 +CONFIG(MSVC):LIBS += -LC:\softs\pthreads-w32\lib\x64 -lpthreadVC2 CONFIG(ANDROID):CONFIG += mobility CONFIG(ANDROID):MOBILITY = diff --git a/libbladerf/CMakeLists.txt b/libbladerf/CMakeLists.txt index 75075a2a4..990b09984 100644 --- a/libbladerf/CMakeLists.txt +++ b/libbladerf/CMakeLists.txt @@ -5,79 +5,125 @@ find_package(LibUSB) add_definitions(-DBLADERF_OS_LINUX) set(bladerf_SOURCES - ${LIBBLADERFLIBSRC}/src/async.c - ${LIBBLADERFLIBSRC}/src/bladerf_priv.c - ${LIBBLADERFLIBSRC}/src/config.c - ${LIBBLADERFLIBSRC}/src/device_identifier.c - src/file_ops.c - ${LIBBLADERFLIBSRC}/src/flash_fields.c - ${LIBBLADERFLIBSRC}/src/fx3_fw.c - ${LIBBLADERFLIBSRC}/src/gain.c - ${LIBBLADERFLIBSRC}/src/init_fini.c - ${LIBBLADERFLIBSRC}/src/sync.c - ${LIBBLADERFLIBSRC}/src/smb_clock.c - ${LIBBLADERFLIBSRC}/src/tuning.c - ${LIBBLADERFLIBSRC}/src/xb.c + ${LIBBLADERFCOMMONSRC}/src/sha256.c + ${LIBBLADERFCOMMONSRC}/src/dc_calibration.c + ${LIBBLADERFCOMMONSRC}/src/parse.c + ${LIBBLADERFCOMMONSRC}/src/devcfg.c + ${LIBBLADERFCOMMONSRC}/src/conversions.c + ${LIBBLADERFCOMMONSRC}/src/log.c + ${LIBBLADERFCOMMONSRC}/src/str_queue.c + ${LIBBLADERFSRC}/host/misc/dev/lms_freqsel/freqsel.c + ${LIBBLADERFSRC}/fpga_common/src/lms.c + ${LIBBLADERFSRC}/fpga_common/src/band_select.c + ${LIBBLADERFLIBSRC}/src/helpers/interleave.c + ${LIBBLADERFLIBSRC}/src/helpers/timeout.c + ${LIBBLADERFLIBSRC}/src/helpers/wallclock.c + ${LIBBLADERFLIBSRC}/src/helpers/configfile.c + ${LIBBLADERFLIBSRC}/src/helpers/file.c + ${LIBBLADERFLIBSRC}/src/helpers/version.c + ${LIBBLADERFLIBSRC}/src/driver/fpga_trigger.c + ${LIBBLADERFLIBSRC}/src/driver/si5338.c + ${LIBBLADERFLIBSRC}/src/driver/dac161s055.c + ${LIBBLADERFLIBSRC}/src/driver/fx3_fw.c + ${LIBBLADERFLIBSRC}/src/driver/smb_clock.c + ${LIBBLADERFLIBSRC}/src/driver/thirdparty/adi/dac_core.c + ${LIBBLADERFLIBSRC}/src/driver/thirdparty/adi/ad9361.c + ${LIBBLADERFLIBSRC}/src/driver/thirdparty/adi/util.c + ${LIBBLADERFLIBSRC}/src/driver/thirdparty/adi/platform.c + ${LIBBLADERFLIBSRC}/src/driver/thirdparty/adi/ad9361_api.c + ${LIBBLADERFLIBSRC}/src/driver/thirdparty/adi/adc_core.c + ${LIBBLADERFLIBSRC}/src/driver/thirdparty/adi/ad9361_conv.c + ${LIBBLADERFLIBSRC}/src/driver/spi_flash.c + ${LIBBLADERFLIBSRC}/src/driver/ina219.c + ${LIBBLADERFLIBSRC}/src/board/bladerf2/compatibility.c + ${LIBBLADERFLIBSRC}/src/board/bladerf2/capabilities.c + ${LIBBLADERFLIBSRC}/src/board/bladerf2/params.c + ${LIBBLADERFLIBSRC}/src/board/bladerf2/bladerf2.c + ${LIBBLADERFLIBSRC}/src/board/board.c + ${LIBBLADERFLIBSRC}/src/board/bladerf1/flash.c + ${LIBBLADERFLIBSRC}/src/board/bladerf1/bladerf1.c + ${LIBBLADERFLIBSRC}/src/board/bladerf1/image.c + ${LIBBLADERFLIBSRC}/src/board/bladerf1/compatibility.c + ${LIBBLADERFLIBSRC}/src/board/bladerf1/calibration.c + ${LIBBLADERFLIBSRC}/src/board/bladerf1/capabilities.c + ${LIBBLADERFLIBSRC}/src/expansion/xb100.c + ${LIBBLADERFLIBSRC}/src/expansion/xb200.c + ${LIBBLADERFLIBSRC}/src/expansion/xb300.c + ${LIBBLADERFLIBSRC}/src/streaming/async.c + ${LIBBLADERFLIBSRC}/src/streaming/sync_worker.c + ${LIBBLADERFLIBSRC}/src/streaming/sync.c ${LIBBLADERFLIBSRC}/src/bladerf.c - ${LIBBLADERFLIBSRC}/src/capabilities.c - ${LIBBLADERFLIBSRC}/src/dc_cal_table.c - ${LIBBLADERFLIBSRC}/src/devinfo.c - ${LIBBLADERFLIBSRC}/src/flash.c - ${LIBBLADERFLIBSRC}/src/fpga.c - ${LIBBLADERFLIBSRC}/src/fx3_fw_log.c - ${LIBBLADERFLIBSRC}/src/image.c - ${LIBBLADERFLIBSRC}/src/si5338.c - ${LIBBLADERFLIBSRC}/src/sync_worker.c - ${LIBBLADERFLIBSRC}/src/trigger.c - ${LIBBLADERFLIBSRC}/src/version_compat.c + ${LIBBLADERFLIBSRC}/src/init_fini.c + ${LIBBLADERFLIBSRC}/src/backend/dummy/dummy.c ${LIBBLADERFLIBSRC}/src/backend/backend.c - ${LIBBLADERFLIBSRC}/src/backend/dummy.c - ${LIBBLADERFLIBSRC}/src/backend/usb/libusb.c ${LIBBLADERFLIBSRC}/src/backend/usb/usb.c + ${LIBBLADERFLIBSRC}/src/backend/usb/libusb.c ${LIBBLADERFLIBSRC}/src/backend/usb/nios_access.c ${LIBBLADERFLIBSRC}/src/backend/usb/nios_legacy_access.c - ${LIBBLADERFSRC}/fpga_common/src/band_select.c - ${LIBBLADERFSRC}/fpga_common/src/lms.c - ${LIBBLADERFCOMMONSRC}/src/conversions.c - ${LIBBLADERFCOMMONSRC}/src/devcfg.c - ${LIBBLADERFCOMMONSRC}/src/sha256.c + ${LIBBLADERFLIBSRC}/src/devinfo.c ) set(bladerf_HEADERS - ${LIBBLADERFLIBSRC}/src/async.h - ${LIBBLADERFLIBSRC}/src/capabilities.h - ${LIBBLADERFLIBSRC}/src/dc_cal_table.h - ${LIBBLADERFLIBSRC}/src/devinfo.h - ${LIBBLADERFLIBSRC}/src/flash.h - ${LIBBLADERFLIBSRC}/src/fpga.h - ${LIBBLADERFLIBSRC}/src/fx3_fw_log.h - ${LIBBLADERFLIBSRC}/src/metadata.h - ${LIBBLADERFLIBSRC}/src/sync.h - ${LIBBLADERFLIBSRC}/src/smb_clock.h - ${LIBBLADERFLIBSRC}/src/tuning.h - ${LIBBLADERFLIBSRC}/src/xb.h - ${LIBBLADERFLIBSRC}/src/bladerf_priv.h - ${LIBBLADERFLIBSRC}/src/config.h - ${LIBBLADERFLIBSRC}/src/device_identifier.h - ${LIBBLADERFLIBSRC}/src/file_ops.h - ${LIBBLADERFLIBSRC}/src/flash_fields.h - ${LIBBLADERFLIBSRC}/src/fx3_fw.h - ${LIBBLADERFLIBSRC}/src/gain.h - ${LIBBLADERFLIBSRC}/src/si5338.h - ${LIBBLADERFLIBSRC}/src/sync_worker.h - ${LIBBLADERFLIBSRC}/src/trigger.h - ${LIBBLADERFLIBSRC}/src/version_compat.h - ${LIBBLADERFLIBSRC}/src/backend/backend.h - ${LIBBLADERFLIBSRC}/src/backend/dummy.h - ${LIBBLADERFLIBSRC}/src/backend/usb/usb.h - ${LIBBLADERFLIBSRC}/src/backend/usb/nios_access.h - ${LIBBLADERFLIBSRC}/src/backend/usb/nios_legacy_access.h - ${LIBBLADERFSRC}/fpga_common/include/band_select.h - ${LIBBLADERFSRC}/fpga_common/include/lms.h + ./common/include/host_config.h + ./libraries/libbladeRF/src/version.h + ./libraries/libbladeRF/src/backend/backend_config.h + ${LIBBLADERFCOMMONSRC}/include/thread.h + ${LIBBLADERFCOMMONSRC}/include/parse.h + ${LIBBLADERFCOMMONSRC}/include/minmax.h + ${LIBBLADERFCOMMONSRC}/include/rel_assert.h + ${LIBBLADERFCOMMONSRC}/include/devcfg.h + ${LIBBLADERFCOMMONSRC}/include/str_queue.h + ${LIBBLADERFCOMMONSRC}/include/log.h + ${LIBBLADERFCOMMONSRC}/include/dc_calibration.h ${LIBBLADERFCOMMONSRC}/include/sha256.h - include/host_config.h - include/backend/backend_config.h - include/version.h + ${LIBBLADERFCOMMONSRC}/include/conversions.h + ${LIBBLADERFSRC}/fpga_common/include/lms.h + ${LIBBLADERFSRC}/fpga_common/include/band_select.h + ${LIBBLADERFLIBSRC}/src/helpers/interleave.h + ${LIBBLADERFLIBSRC}/src/helpers/wallclock.h + ${LIBBLADERFLIBSRC}/src/helpers/timeout.h + ${LIBBLADERFLIBSRC}/src/helpers/version.h + ${LIBBLADERFLIBSRC}/src/helpers/configfile.h + ${LIBBLADERFLIBSRC}/src/helpers/file.h + ${LIBBLADERFLIBSRC}/src/driver/dac161s055.h + ${LIBBLADERFLIBSRC}/src/driver/fpga_trigger.h + ${LIBBLADERFLIBSRC}/src/driver/si5338.h + ${LIBBLADERFLIBSRC}/src/driver/ina219.h + ${LIBBLADERFLIBSRC}/src/driver/thirdparty/adi/platform.h + ${LIBBLADERFLIBSRC}/src/driver/thirdparty/adi/util.h + ${LIBBLADERFLIBSRC}/src/driver/thirdparty/adi/dac_core.h + ${LIBBLADERFLIBSRC}/src/driver/thirdparty/adi/config.h + ${LIBBLADERFLIBSRC}/src/driver/thirdparty/adi/adc_core.h + ${LIBBLADERFLIBSRC}/src/driver/thirdparty/adi/common.h + ${LIBBLADERFLIBSRC}/src/driver/thirdparty/adi/ad9361.h + ${LIBBLADERFLIBSRC}/src/driver/thirdparty/adi/ad9361_api.h + ${LIBBLADERFLIBSRC}/src/driver/spi_flash.h + ${LIBBLADERFLIBSRC}/src/driver/fx3_fw.h + ${LIBBLADERFLIBSRC}/src/driver/smb_clock.h + ${LIBBLADERFLIBSRC}/src/board/bladerf2/capabilities.h + ${LIBBLADERFLIBSRC}/src/board/bladerf2/compatibility.h + ${LIBBLADERFLIBSRC}/src/board/board.h + ${LIBBLADERFLIBSRC}/src/board/bladerf1/calibration.h + ${LIBBLADERFLIBSRC}/src/board/bladerf1/capabilities.h + ${LIBBLADERFLIBSRC}/src/board/bladerf1/compatibility.h + ${LIBBLADERFLIBSRC}/src/board/bladerf1/flash.h + ${LIBBLADERFLIBSRC}/src/expansion/xb300.h + ${LIBBLADERFLIBSRC}/src/expansion/xb100.h + ${LIBBLADERFLIBSRC}/src/expansion/xb200.h + ${LIBBLADERFLIBSRC}/src/streaming/sync.h + ${LIBBLADERFLIBSRC}/src/streaming/sync_worker.h + ${LIBBLADERFLIBSRC}/src/streaming/metadata.h + ${LIBBLADERFLIBSRC}/src/streaming/format.h + ${LIBBLADERFLIBSRC}/src/streaming/async.h + ${LIBBLADERFLIBSRC}/src/backend/backend.h + ${LIBBLADERFLIBSRC}/src/backend/dummy/dummy.h + ${LIBBLADERFLIBSRC}/src/backend/usb/nios_legacy_access.h + ${LIBBLADERFLIBSRC}/src/backend/usb/nios_access.h + ${LIBBLADERFLIBSRC}/src/backend/usb/usb.h + ${LIBBLADERFLIBSRC}/src/devinfo.h + ${LIBBLADERFLIBSRC}/include/bladeRF2.h + ${LIBBLADERFLIBSRC}/include/libbladeRF.h + ${LIBBLADERFLIBSRC}/include/bladeRF1.h ) include_directories( @@ -91,6 +137,9 @@ include_directories( ${LIBBLADERFCOMMONSRC}/include ${LIBBLADERFCOMMONSRC}/include/windows ./include + ./common/include + ./libraries/libbladeRF/src + ./libraries/libbladeRF/src/backend ) add_definitions(-DQT_SHARED) @@ -104,3 +153,4 @@ target_link_libraries(bladerf ) install(TARGETS bladerf DESTINATION lib) + diff --git a/libbladerf/common/include/host_config.h b/libbladerf/common/include/host_config.h new file mode 100644 index 000000000..6f8b5f668 --- /dev/null +++ b/libbladerf/common/include/host_config.h @@ -0,0 +1,334 @@ +/** + * @file host_config.h.in + * + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (c) 2013-2018 Nuand LLC. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef HOST_CONFIG_H__ +#define HOST_CONFIG_H__ + +#define BLADERF_OS_LINUX 1 +#define BLADERF_OS_FREEBSD 0 +#define BLADERF_OS_OSX 0 +#define BLADERF_OS_WINDOWS 0 +#define BLADERF_BIG_ENDIAN 0 +#define LIBBLADERF_SEARCH_PREFIX "/opt/install/libbladeRF" + +#if !(BLADERF_OS_LINUX || BLADERF_OS_OSX || BLADERF_OS_WINDOWS || BLADERF_OS_FREEBSD) +# error "Build not configured for any supported operating systems" +#endif + +#if 1 < (BLADERF_OS_LINUX + BLADERF_OS_OSX + BLADERF_OS_WINDOWS + BLADERF_OS_FREEBSD) +#error "Build configured for multiple operating systems" +#endif + +#define HAVE_CLOCK_GETTIME 1 + +/******************************************************************************* + * Endianness conversions + * + * HOST_TO_LE16 16-bit host endianness to little endian conversion + * LE16_TO_HOST 16-bit little endian to host endianness conversion + * HOST_TO_BE16 16-bit host endianness to big endian conversion + * BE16_TO_HOST 16-bit big endian to host endianness conversion + * HOST_TO_LE32 32-bit host endianness to little endian conversion + * LE32_TO_HOST 32-bit little endian to host endianness conversion + * HOST_TO_BE32 32-bit host endianness to big endian conversion + * BE32_TO_HOST 32-bit big endian to host endianness conversion + * HOST_TO_BE64 64-bit host endianness to big endian conversion + * BE64_TO_HOST 64-bit big endian to host endianness conversion + ******************************************************************************/ + +/*----------------------- + * Linux + *---------------------*/ +#if BLADERF_OS_LINUX +#include + +#define HOST_TO_LE16(val) htole16(val) +#define LE16_TO_HOST(val) le16toh(val) +#define HOST_TO_BE16(val) htobe16(val) +#define BE16_TO_HOST(val) be16toh(val) + +#define HOST_TO_LE32(val) htole32(val) +#define LE32_TO_HOST(val) le32toh(val) +#define HOST_TO_BE32(val) htobe32(val) +#define BE32_TO_HOST(val) be32toh(val) + +#define HOST_TO_LE64(val) htole64(val) +#define LE64_TO_HOST(val) le64toh(val) +#define HOST_TO_BE64(val) be64toh(val) +#define BE64_TO_HOST(val) be64toh(val) + +/*----------------------- + * FREEBSD + *---------------------*/ +#elif BLADERF_OS_FREEBSD +#include + +#define HOST_TO_LE16(val) htole16(val) +#define LE16_TO_HOST(val) le16toh(val) +#define HOST_TO_BE16(val) htobe16(val) +#define BE16_TO_HOST(val) be16toh(val) + +#define HOST_TO_LE32(val) htole32(val) +#define LE32_TO_HOST(val) le32toh(val) +#define HOST_TO_BE32(val) htobe32(val) +#define BE32_TO_HOST(val) be32toh(val) + +#define HOST_TO_LE64(val) htole64(val) +#define LE64_TO_HOST(val) le64toh(val) +#define HOST_TO_BE64(val) be64toh(val) +#define BE64_TO_HOST(val) be64toh(val) + +/*----------------------- + * Apple OSX + *---------------------*/ +#elif BLADERF_OS_OSX +#include + +#define HOST_TO_LE16(val) OSSwapHostToLittleInt16(val) +#define LE16_TO_HOST(val) OSSwapLittleToHostInt16(val) +#define HOST_TO_BE16(val) OSSwapHostToBigInt16(val) +#define BE16_TO_HOST(val) OSSwapBigToHostInt16(val) + +#define HOST_TO_LE32(val) OSSwapHostToLittleInt32(val) +#define LE32_TO_HOST(val) OSSwapLittleToHostInt32(val) +#define HOST_TO_BE32(val) OSSwapHostToBigInt32(val) +#define BE32_TO_HOST(val) OSSwapBigToHostInt32(val) + +#define HOST_TO_LE64(val) OSSwapHostToLittleInt64(val) +#define LE64_TO_HOST(val) OSSwapLittleToHostInt64(val) +#define HOST_TO_BE64(val) OSSwapHostToBigInt64(val) +#define BE64_TO_HOST(val) OSSwapBigToHostInt64(val) + +/*----------------------- + * Windows + *---------------------*/ +#elif BLADERF_OS_WINDOWS +#include + +/* We'll assume little endian for Windows platforms: + * http://blogs.msdn.com/b/larryosterman/archive/2005/06/07/426334.aspx + */ +#define HOST_TO_LE16(val) (val) +#define LE16_TO_HOST(val) (val) +#define HOST_TO_BE16(val) _byteswap_ushort(val) +#define BE16_TO_HOST(val) _byteswap_ushort(val) + +#define HOST_TO_LE32(val) (val) +#define LE32_TO_HOST(val) (val) +#define HOST_TO_BE32(val) _byteswap_ulong(val) +#define BE32_TO_HOST(val) _byteswap_ulong(val) + + +#define HOST_TO_LE64(val) (val) +#define LE64_TO_HOST(val) (val) +#define HOST_TO_BE64(val) _byteswap_uint64(val) +#define BE64_TO_HOST(val) _byteswap_uint64(val) + +/*----------------------- + * Unsupported or bug + *---------------------*/ +#else +#error "Encountered an OS that we do not have endian wrappers for?" +#endif + +/******************************************************************************* + * Endianness conversions for constants + * + * We shouldn't be using the above items for assigning constants because the + * implementations may be functions [1]. + * + * [1] https://sourceware.org/bugzilla/show_bug.cgi?id=17679#c7 + ******************************************************************************/ + +#define BLADERF_BYTESWAP16(x) ((((x) & 0xff00) >> 8) | (((x) & 0x00ff) << 8)) + +#define BLADERF_BYTESWAP32(x) ((((x) & 0xff000000) >> 24) | \ + (((x) & 0x00ff0000) >> 8) | \ + (((x) & 0x0000ff00) << 8) | \ + (((x) & 0x000000ff) << 24) ) + +#define BLADERF_BYTESWAP64(x) ((((x) & 0xff00000000000000llu) >> 56) | \ + (((x) & 0x00ff000000000000llu) >> 40) | \ + (((x) & 0x0000ff0000000000llu) >> 24) | \ + (((x) & 0x000000ff00000000llu) >> 8) | \ + (((x) & 0x00000000ff000000llu) << 8) | \ + (((x) & 0x0000000000ff0000llu) << 24) | \ + (((x) & 0x000000000000ff00llu) << 40) | \ + (((x) & 0x00000000000000ffllu) << 56) | \ + +#if BLADERF_BIG_ENDIAN +# define HOST_TO_LE16_CONST(x) (BLADERF_BYTESWAP16(x)) +# define LE16_TO_HOST_CONST(x) (BLADERF_BYTESWAP16(x)) +# define HOST_TO_BE16_CONST(x) (x) +# define BE16_TO_HOST_CONST(x) (x) + +# define HOST_TO_LE32_CONST(x) (BLADERF_BYTESWAP32(x)) +# define LE32_TO_HOST_CONST(x) (BLADERF_BYTESWAP32(x)) +# define HOST_TO_BE32_CONST(x) (x) +# define BE32_TO_HOST_CONST(x) (x) + +# define HOST_TO_LE64_CONST(x) (BLADERF_BYTESWAP64(x)) +# define LE64_TO_HOST_CONST(x) (BLADERF_BYTESWAP64(x)) +# define HOST_TO_BE64_CONST(x) (x) +# define BE64_TO_HOST_CONST(x) (x) +#else +# define HOST_TO_LE16_CONST(x) (x) +# define LE16_TO_HOST_CONST(x) (x) +# define HOST_TO_BE16_CONST(x) (BLADERF_BYTESWAP16(x)) +# define BE16_TO_HOST_CONST(x) (BLADERF_BYTESWAP16(x)) + +# define HOST_TO_LE32_CONST(x) (x) +# define LE32_TO_HOST_CONST(x) (x) +# define HOST_TO_BE32_CONST(x) (BLADERF_BYTESWAP32(x)) +# define BE32_TO_HOST_CONST(x) (BLADERF_BYTESWAP32(x)) + +# define HOST_TO_LE64_CONST(x) (x) +# define LE64_TO_HOST_CONST(x) (x) +# define HOST_TO_BE64_CONST(x) (BLADERF_BYTESWAP64(x)) +# define BE64_TO_HOST_CONST(x) (BLADERF_BYTESWAP64(x)) +#endif + +/******************************************************************************* + * Miscellaneous Linux fixups + ******************************************************************************/ +#if BLADERF_OS_LINUX + +/* Added here just to keep this out of the other source files. We'll have + * a few Windows replacements for unistd.h items. */ +#include +#include + +/******************************************************************************* + * Miscellaneous FREEBSD fixups + ******************************************************************************/ +#elif BLADERF_OS_FREEBSD + +#include +#include +#include + +/******************************************************************************* + * Miscellaneous OSX fixups + ******************************************************************************/ +#elif BLADERF_OS_OSX + +#include +#include + +/******************************************************************************* + * Miscellaneous Windows fixups + ******************************************************************************/ +#elif BLADERF_OS_WINDOWS + +/* Rename a few functions and types */ +#include +#include +#include +#define usleep(x) Sleep((x+999)/1000) + +/* Changes specific to Microsoft Visual Studio and its C89-compliant ways */ +#if _MSC_VER +# define strtok_r strtok_s +# define strtoull _strtoui64 +#if _MSC_VER < 1900 +# define snprintf _snprintf +# define vsnprintf _vsnprintf +#else +#define STDC99 +#endif +# define strcasecmp _stricmp +# define strncasecmp _strnicmp +# define fileno _fileno +# define strdup _strdup +# define access _access +# define chdir _chdir +# define getcwd _getcwd +# define rmdir _rmdir +# define unlink _unlink +# define mkdir(n, m) _mkdir(n) + +/* ssize_t lives elsewhere */ +# include +# define ssize_t SSIZE_T + +/* With msvc, inline is only available for C++. Otherwise we need to use __inline: + * http://msdn.microsoft.com/en-us/library/z8y1yy88.aspx */ +# if !defined(__cplusplus) +# define inline __inline +# endif + +/* Visual studio 2012 compiler (v17.00.XX) doesn't have round functions */ +# if _MSC_VER <= 1700 +# include + static inline float roundf(float x) + { + return x >= 0.0f ? floorf(x + 0.5f) : ceilf(x - 0.5f); + } + static inline double round(double x) + { + return x >= 0.0 ? floor(x + 0.5) : ceil(x - 0.5); + } +# endif + +#endif // _MSC_VER + +#endif + +/* Windows (msvc) does not support C99 designated initializers. + * + * Therefore, the following macro should be used. However, note that you'll + * need to be sure to keep your elements in order to avoid breaking Windows + * portability! + * + * http://stackoverflow.com/questions/5440611/how-to-rewrite-c-struct-designated-initializers-to-c89-resp-msvc-c-compiler + * + * Here's a sample regexep you could use in vim to clean these up in your code: + * @\(\s\+\)\(\.[a-zA-Z0-9_]\+\)\s*=\s*\([a-zA-Z0-9_]\+\)\(,\)\?@\1FIELD_INIT(\2,\3)\4@gc + */ +#if _MSC_VER +# define FIELD_INIT(field, ...) __VA_ARGS__ +#else +# define FIELD_INIT(field, ...) field = __VA_ARGS__ +#endif // _MSC_VER + +/******************************************************************************* + * Miscellaneous utility macros + ******************************************************************************/ +#define ARRAY_SIZE(n) (sizeof(n) / sizeof(n[0])) + +/** + * GCC 7+ warns on implicit fallthroughs in switch statements + * (-Wimplicit-fallthrough), which we use in a couple places for simplicity. + * The statement attribute syntax used to suppress this warning is not + * supported by anything else. + */ +#if __GNUC__ >= 7 +#define EXPLICIT_FALLTHROUGH __attribute__((fallthrough)) +#else +#define EXPLICIT_FALLTHROUGH +#endif // __GNUC__ >= 7 + +#endif diff --git a/libbladerf/include/version.h b/libbladerf/include/version.h deleted file mode 100644 index acacd8034..000000000 --- a/libbladerf/include/version.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef VERSION_H__ -#define VERSION_H__ - -#define LIBBLADERF_VERSION "1.7.2-git-unknown" - -#define LIBBLADERF_VERSION_MAJOR 1 -#define LIBBLADERF_VERSION_MINOR 7 -#define LIBBLADERF_VERSION_PATCH 2 - -#endif diff --git a/libbladerf/libbladerf.pro b/libbladerf/libbladerf.pro index f25f027d0..423d497a2 100644 --- a/libbladerf/libbladerf.pro +++ b/libbladerf/libbladerf.pro @@ -9,99 +9,153 @@ QT += core TEMPLATE = lib TARGET = libbladerf -DEFINES += BLADERF_OS_WINDOWS=1 +#DEFINES += BLADERF_OS_WINDOWS=1 -CONFIG(MINGW32):LIBBLADERFSRC = "D:\softs\bladeRF" -CONFIG(MINGW32):LIBBLADERFCOMMONSRC = "D:\softs\bladeRF\host\common" -CONFIG(MINGW32):LIBBLADERFLIBSRC = "D:\softs\bladeRF\host\libraries\libbladeRF" -CONFIG(MINGW64):LIBBLADERFSRC = "D:\softs\bladeRF" -CONFIG(MINGW64):LIBBLADERFCOMMONSRC = "D:\softs\bladeRF\host\common" -CONFIG(MINGW64):LIBBLADERFLIBSRC = "D:\softs\bladeRF\host\libraries\libbladeRF" +CONFIG(MINGW32):LIBBLADERFSRC = "C:\softs\bladeRF" +CONFIG(MINGW32):LIBBLADERFCOMMONSRC = "C:\softs\bladeRF\host\common" +CONFIG(MINGW32):LIBBLADERFLIBSRC = "C:\softs\bladeRF\host\libraries\libbladeRF" +CONFIG(MINGW64):LIBBLADERFSRC = "C:\softs\bladeRF" +CONFIG(MINGW64):LIBBLADERFCOMMONSRC = "C:\softs\bladeRF\host\common" +CONFIG(MINGW64):LIBBLADERFLIBSRC = "C:\softs\bladeRF\host\libraries\libbladeRF" +INCLUDEPATH += $$PWD/mingw/include +INCLUDEPATH += $$PWD/mingw/common/include +INCLUDEPATH += $$PWD/mingw/libraries/libbladeRF/src +INCLUDEPATH += $$PWD/mingw/libraries/libbladeRF/src/backend INCLUDEPATH += $$LIBBLADERFLIBSRC/include INCLUDEPATH += $$LIBBLADERFLIBSRC/src INCLUDEPATH += $$LIBBLADERFSRC/firmware_common INCLUDEPATH += $$LIBBLADERFSRC/fpga_common/include INCLUDEPATH += $$LIBBLADERFCOMMONSRC/include INCLUDEPATH += $$LIBBLADERFCOMMONSRC/include/windows -INCLUDEPATH += $$PWD/include -CONFIG(MINGW32):INCLUDEPATH += "D:\softs\libusb-1.0.20\include\libusb-1.0" -CONFIG(MINGW64):INCLUDEPATH += "D:\softs\libusb-1.0.20\include\libusb-1.0" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.19\include\libusb-1.0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.19\include\libusb-1.0" -SOURCES = $$LIBBLADERFLIBSRC/src/async.c\ - $$LIBBLADERFLIBSRC/src/bladerf_priv.c\ - $$LIBBLADERFLIBSRC/src/config.c\ - $$LIBBLADERFLIBSRC/src/device_identifier.c\ - $$PWD/src/file_ops.c\ - $$LIBBLADERFLIBSRC/src/flash_fields.c\ - $$LIBBLADERFLIBSRC/src/fx3_fw.c\ - $$LIBBLADERFLIBSRC/src/gain.c\ - $$LIBBLADERFLIBSRC/src/init_fini.c\ - $$LIBBLADERFLIBSRC/src/sync.c\ - $$LIBBLADERFLIBSRC/src/smb_clock.c\ - $$LIBBLADERFLIBSRC/src/tuning.c\ - $$LIBBLADERFLIBSRC/src/xb.c\ +SOURCES = $$LIBBLADERFCOMMONSRC/src/sha256.c\ + $$LIBBLADERFCOMMONSRC/src/dc_calibration.c\ + $$LIBBLADERFCOMMONSRC/src/parse.c\ + $$LIBBLADERFCOMMONSRC/src/devcfg.c\ + $$LIBBLADERFCOMMONSRC/src/conversions.c\ + $$LIBBLADERFCOMMONSRC/src/log.c\ + $$LIBBLADERFCOMMONSRC/src/str_queue.c\ +# $$LIBBLADERFCOMMONSRC/src/windows/clock_gettime.c\ +# $$LIBBLADERFCOMMONSRC/src/windows/getopt_long.c\ +# $$LIBBLADERFCOMMONSRC/src/windows/mkdtemp.c\ +# $$LIBBLADERFCOMMONSRC/src/windows/nanosleep.c\ +# $$LIBBLADERFCOMMONSRC/src/windows/setenv.c\ + $$LIBBLADERFSRC/host/misc/dev/lms_freqsel/freqsel.c\ + $$LIBBLADERFSRC/fpga_common/src/lms.c\ + $$LIBBLADERFSRC/fpga_common/src/band_select.c\ + $$LIBBLADERFLIBSRC/src/helpers/interleave.c\ + $$LIBBLADERFLIBSRC/src/helpers/timeout.c\ + $$LIBBLADERFLIBSRC/src/helpers/wallclock.c\ + $$LIBBLADERFLIBSRC/src/helpers/configfile.c\ + $$LIBBLADERFLIBSRC/src/helpers/file.c\ + $$LIBBLADERFLIBSRC/src/helpers/version.c\ + $$LIBBLADERFLIBSRC/src/driver/fpga_trigger.c\ + $$LIBBLADERFLIBSRC/src/driver/si5338.c\ + $$LIBBLADERFLIBSRC/src/driver/dac161s055.c\ + $$LIBBLADERFLIBSRC/src/driver/fx3_fw.c\ + $$LIBBLADERFLIBSRC/src/driver/smb_clock.c\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/dac_core.c\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/ad9361.c\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/util.c\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/platform.c\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/ad9361_api.c\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/adc_core.c\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/ad9361_conv.c\ + $$LIBBLADERFLIBSRC/src/driver/spi_flash.c\ + $$LIBBLADERFLIBSRC/src/driver/ina219.c\ + $$LIBBLADERFLIBSRC/src/board/bladerf2/compatibility.c\ + $$LIBBLADERFLIBSRC/src/board/bladerf2/capabilities.c\ + $$LIBBLADERFLIBSRC/src/board/bladerf2/params.c\ + $$LIBBLADERFLIBSRC/src/board/bladerf2/bladerf2.c\ + $$LIBBLADERFLIBSRC/src/board/board.c\ + $$LIBBLADERFLIBSRC/src/board/bladerf1/flash.c\ + $$LIBBLADERFLIBSRC/src/board/bladerf1/bladerf1.c\ + $$LIBBLADERFLIBSRC/src/board/bladerf1/image.c\ + $$LIBBLADERFLIBSRC/src/board/bladerf1/compatibility.c\ + $$LIBBLADERFLIBSRC/src/board/bladerf1/calibration.c\ + $$LIBBLADERFLIBSRC/src/board/bladerf1/capabilities.c\ + $$LIBBLADERFLIBSRC/src/expansion/xb100.c\ + $$LIBBLADERFLIBSRC/src/expansion/xb200.c\ + $$LIBBLADERFLIBSRC/src/expansion/xb300.c\ + $$LIBBLADERFLIBSRC/src/streaming/async.c\ + $$LIBBLADERFLIBSRC/src/streaming/sync_worker.c\ + $$LIBBLADERFLIBSRC/src/streaming/sync.c\ $$LIBBLADERFLIBSRC/src/bladerf.c\ - $$LIBBLADERFLIBSRC/src/capabilities.c\ - $$LIBBLADERFLIBSRC/src/dc_cal_table.c\ - $$LIBBLADERFLIBSRC/src/devinfo.c\ - $$LIBBLADERFLIBSRC/src/flash.c\ - $$LIBBLADERFLIBSRC/src/fpga.c\ - $$LIBBLADERFLIBSRC/src/fx3_fw_log.c\ - $$LIBBLADERFLIBSRC/src/image.c\ - $$LIBBLADERFLIBSRC/src/si5338.c\ - $$LIBBLADERFLIBSRC/src/sync_worker.c\ - $$LIBBLADERFLIBSRC/src/trigger.c\ - $$LIBBLADERFLIBSRC/src/version_compat.c\ + $$LIBBLADERFLIBSRC/src/init_fini.c\ + $$LIBBLADERFLIBSRC/src/backend/dummy/dummy.c\ $$LIBBLADERFLIBSRC/src/backend/backend.c\ - $$LIBBLADERFLIBSRC/src/backend/dummy.c\ - $$LIBBLADERFLIBSRC/src/backend/usb/libusb.c\ $$LIBBLADERFLIBSRC/src/backend/usb/usb.c\ + $$LIBBLADERFLIBSRC/src/backend/usb/libusb.c\ $$LIBBLADERFLIBSRC/src/backend/usb/nios_access.c\ $$LIBBLADERFLIBSRC/src/backend/usb/nios_legacy_access.c\ - $$LIBBLADERFSRC/fpga_common/src/band_select.c\ - $$LIBBLADERFSRC/fpga_common/src/lms.c\ - $$LIBBLADERFCOMMONSRC/src/conversions.c\ - $$LIBBLADERFCOMMONSRC/src/devcfg.c\ - $$LIBBLADERFCOMMONSRC/src/sha256.c + $$LIBBLADERFLIBSRC/src/devinfo.c -HEADERS = $$LIBBLADERFLIBSRC/src/async.h\ - $$LIBBLADERFLIBSRC/src/capabilities.h\ - $$LIBBLADERFLIBSRC/src/dc_cal_table.h\ - $$LIBBLADERFLIBSRC/src/devinfo.h\ - $$LIBBLADERFLIBSRC/src/flash.h\ - $$LIBBLADERFLIBSRC/src/fpga.h\ - $$LIBBLADERFLIBSRC/src/fx3_fw_log.h\ - $$LIBBLADERFLIBSRC/src/metadata.h\ - $$LIBBLADERFLIBSRC/src/sync.h\ - $$LIBBLADERFLIBSRC/src/smb_clock.h\ - $$LIBBLADERFLIBSRC/src/tuning.h\ - $$LIBBLADERFLIBSRC/src/xb.h\ - $$LIBBLADERFLIBSRC/src/bladerf_priv.h\ - $$LIBBLADERFLIBSRC/src/config.h\ - $$LIBBLADERFLIBSRC/src/device_identifier.h\ - $$LIBBLADERFLIBSRC/src/file_ops.h\ - $$LIBBLADERFLIBSRC/src/flash_fields.h\ - $$LIBBLADERFLIBSRC/src/fx3_fw.h\ - $$LIBBLADERFLIBSRC/src/gain.h\ - $$LIBBLADERFLIBSRC/src/si5338.h\ - $$LIBBLADERFLIBSRC/src/sync_worker.h\ - $$LIBBLADERFLIBSRC/src/trigger.h\ - $$LIBBLADERFLIBSRC/src/version_compat.h\ - $$LIBBLADERFLIBSRC/src/backend/backend.h\ - $$LIBBLADERFLIBSRC/src/backend/dummy.h\ - $$LIBBLADERFLIBSRC/src/backend/usb/usb.h\ - $$LIBBLADERFLIBSRC/src/backend/usb/nios_access.h\ - $$LIBBLADERFLIBSRC/src/backend/usb/nios_legacy_access.h\ - $$LIBBLADERFSRC/fpga_common/include/band_select.h\ - $$LIBBLADERFSRC/fpga_common/include/lms.h\ +HEADERS = $$PWD/mingw/common/include/host_config.h\ + $$PWD/mingw/libraries/libbladeRF/src/version.h\ + $$PWD/mingw/libraries/libbladeRF/src/backend/backend_config.h\ + $$LIBBLADERFCOMMONSRC/include/thread.h\ + $$LIBBLADERFCOMMONSRC/include/parse.h\ + $$LIBBLADERFCOMMONSRC/include/minmax.h\ + $$LIBBLADERFCOMMONSRC/include/rel_assert.h\ + $$LIBBLADERFCOMMONSRC/include/devcfg.h\ + $$LIBBLADERFCOMMONSRC/include/str_queue.h\ + $$LIBBLADERFCOMMONSRC/include/log.h\ + $$LIBBLADERFCOMMONSRC/include/dc_calibration.h\ $$LIBBLADERFCOMMONSRC/include/sha256.h\ - $$PWD/include/host_config.h\ - $$PWD/include/backend/backend_config.h\ - $$PWD/include/version.h + $$LIBBLADERFCOMMONSRC/include/conversions.h\ + $$LIBBLADERFSRC/fpga_common/include/lms.h\ + $$LIBBLADERFSRC/fpga_common/include/band_select.h\ + $$LIBBLADERFLIBSRC/src/helpers/interleave.h\ + $$LIBBLADERFLIBSRC/src/helpers/wallclock.h\ + $$LIBBLADERFLIBSRC/src/helpers/timeout.h\ + $$LIBBLADERFLIBSRC/src/helpers/version.h\ + $$LIBBLADERFLIBSRC/src/helpers/configfile.h\ + $$LIBBLADERFLIBSRC/src/helpers/file.h\ + $$LIBBLADERFLIBSRC/src/driver/dac161s055.h\ + $$LIBBLADERFLIBSRC/src/driver/fpga_trigger.h\ + $$LIBBLADERFLIBSRC/src/driver/si5338.h\ + $$LIBBLADERFLIBSRC/src/driver/ina219.h\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/platform.h\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/util.h\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/dac_core.h\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/config.h\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/adc_core.h\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/common.h\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/ad9361.h\ + $$LIBBLADERFLIBSRC/src/driver/thirdparty/adi/ad9361_api.h\ + $$LIBBLADERFLIBSRC/src/driver/spi_flash.h\ + $$LIBBLADERFLIBSRC/src/driver/fx3_fw.h\ + $$LIBBLADERFLIBSRC/src/driver/smb_clock.h\ + $$LIBBLADERFLIBSRC/src/board/bladerf2/capabilities.h\ + $$LIBBLADERFLIBSRC/src/board/bladerf2/compatibility.h\ + $$LIBBLADERFLIBSRC/src/board/board.h\ + $$LIBBLADERFLIBSRC/src/board/bladerf1/calibration.h\ + $$LIBBLADERFLIBSRC/src/board/bladerf1/capabilities.h\ + $$LIBBLADERFLIBSRC/src/board/bladerf1/compatibility.h\ + $$LIBBLADERFLIBSRC/src/board/bladerf1/flash.h\ + $$LIBBLADERFLIBSRC/src/expansion/xb300.h\ + $$LIBBLADERFLIBSRC/src/expansion/xb100.h\ + $$LIBBLADERFLIBSRC/src/expansion/xb200.h\ + $$LIBBLADERFLIBSRC/src/streaming/sync.h\ + $$LIBBLADERFLIBSRC/src/streaming/sync_worker.h\ + $$LIBBLADERFLIBSRC/src/streaming/metadata.h\ + $$LIBBLADERFLIBSRC/src/streaming/format.h\ + $$LIBBLADERFLIBSRC/src/streaming/async.h\ + $$LIBBLADERFLIBSRC/src/backend/backend.h\ + $$LIBBLADERFLIBSRC/src/backend/dummy/dummy.h\ + $$LIBBLADERFLIBSRC/src/backend/usb/nios_legacy_access.h\ + $$LIBBLADERFLIBSRC/src/backend/usb/nios_access.h\ + $$LIBBLADERFLIBSRC/src/backend/usb/usb.h\ + $$LIBBLADERFLIBSRC/src/devinfo.h\ + $$LIBBLADERFLIBSRC/include/bladeRF2.h\ + $$LIBBLADERFLIBSRC/include/libbladeRF.h\ + $$LIBBLADERFLIBSRC/include/bladeRF1.h -CONFIG(MINGW32):LIBS += -LD:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -CONFIG(MINGW64):LIBS += -LD:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.22\MinGW32\dll -llibusb-1.0 +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.22\MinGW64\dll -llibusb-1.0 CONFIG(ANDROID):CONFIG += mobility CONFIG(ANDROID):MOBILITY = diff --git a/libbladerf/include/backend/backend_config.h b/libbladerf/libraries/libbladeRF/src/backend/backend_config.h similarity index 53% rename from libbladerf/include/backend/backend_config.h rename to libbladerf/libraries/libbladeRF/src/backend/backend_config.h index 5cf57a0b1..351d5d12b 100644 --- a/libbladerf/include/backend/backend_config.h +++ b/libbladerf/libraries/libbladeRF/src/backend/backend_config.h @@ -22,8 +22,9 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef BLADERF_BACKEND_CONFIG_H__ -#define BLADERF_BACKEND_CONFIG_H__ + +#ifndef BACKEND_BACKEND_CONFIG_H_ +#define BACKEND_BACKEND_CONFIG_H_ #define ENABLE_BACKEND_USB #define ENABLE_BACKEND_LIBUSB @@ -35,52 +36,53 @@ #include "backend/usb/usb.h" #ifdef ENABLE_BACKEND_DUMMY - extern const struct backend_fns backend_fns_dummy; -# define BACKEND_DUMMY &backend_fns_dummy, +extern const struct backend_fns backend_fns_dummy; +#define BACKEND_DUMMY &backend_fns_dummy, #else -# define BACKEND_DUMMY +#define BACKEND_DUMMY #endif #ifdef ENABLE_BACKEND_USB - extern const struct backend_fns backend_fns_usb; -# define BACKEND_USB &backend_fns_usb, +extern const struct backend_fns backend_fns_usb; +#define BACKEND_USB &backend_fns_usb, -# ifdef ENABLE_BACKEND_LIBUSB - extern const struct usb_driver usb_driver_libusb; -# define BACKEND_USB_LIBUSB &usb_driver_libusb, -# else -# define BACKEND_USB_LIBUSB -# endif - -# ifdef ENABLE_BACKEND_CYAPI - extern const struct usb_driver usb_driver_cypress; -# define BACKEND_USB_CYAPI &usb_driver_cypress, -# else -# define BACKEND_USB_CYAPI -# endif - - -# define BLADERF_USB_BACKEND_LIST { \ - BACKEND_USB_LIBUSB \ - BACKEND_USB_CYAPI \ - } - -# if !defined(ENABLE_BACKEND_LIBUSB) && !defined(ENABLE_BACKEND_CYAPI) -# error "No USB backends are enabled. One or more must be enabled." -# endif +#ifdef ENABLE_BACKEND_LIBUSB +extern const struct usb_driver usb_driver_libusb; +#define BACKEND_USB_LIBUSB &usb_driver_libusb, #else -# define BACKEND_USB +#define BACKEND_USB_LIBUSB #endif -#if !defined(ENABLE_BACKEND_USB) && \ - !defined(ENABLE_BACKEND_DUMMY) - #error "No backends are enabled. One more more must be enabled." +#ifdef ENABLE_BACKEND_CYAPI +extern const struct usb_driver usb_driver_cypress; +#define BACKEND_USB_CYAPI &usb_driver_cypress, +#else +#define BACKEND_USB_CYAPI +#endif + +#define BLADERF_USB_BACKEND_LIST \ + { \ + BACKEND_USB_LIBUSB \ + BACKEND_USB_CYAPI \ + } + +#if !defined(ENABLE_BACKEND_LIBUSB) && !defined(ENABLE_BACKEND_CYAPI) +#error "No USB backends are enabled. One or more must be enabled." +#endif + +#else +#define BACKEND_USB +#endif + +#if !defined(ENABLE_BACKEND_USB) && !defined(ENABLE_BACKEND_DUMMY) +#error "No backends are enabled. One more more must be enabled." #endif /* This list should be ordered by preference (highest first) */ -#define BLADERF_BACKEND_LIST { \ - BACKEND_USB \ - BACKEND_DUMMY \ -} +#define BLADERF_BACKEND_LIST \ + { \ + BACKEND_USB \ + BACKEND_DUMMY \ + } #endif diff --git a/libbladerf/libraries/libbladeRF/src/version.h b/libbladerf/libraries/libbladeRF/src/version.h new file mode 100644 index 000000000..b7e66b15c --- /dev/null +++ b/libbladerf/libraries/libbladeRF/src/version.h @@ -0,0 +1,12 @@ +#ifndef VERSION_H_ +#define VERSION_H_ + +#define LIBBLADERF_VERSION "2.0.2-git-32058c4" + +// clang-format off +#define LIBBLADERF_VERSION_MAJOR 2 +#define LIBBLADERF_VERSION_MINOR 0 +#define LIBBLADERF_VERSION_PATCH 2 +// clang-format on + +#endif diff --git a/libbladerf/include/host_config.h b/libbladerf/mingw/common/include/host_config.h similarity index 68% rename from libbladerf/include/host_config.h rename to libbladerf/mingw/common/include/host_config.h index d4284a83a..96871ea0e 100644 --- a/libbladerf/include/host_config.h +++ b/libbladerf/mingw/common/include/host_config.h @@ -4,7 +4,7 @@ * This file is part of the bladeRF project: * http://www.github.com/nuand/bladeRF * - * Copyright (c) 2013 Nuand LLC. + * Copyright (c) 2013-2018 Nuand LLC. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -27,11 +27,11 @@ #ifndef HOST_CONFIG_H__ #define HOST_CONFIG_H__ -//#define BLADERF_OS_LINUX 0 -//#define BLADERF_OS_FREEBSD 0 -//#define BLADERF_OS_OSX 0 -//#define BLADERF_OS_WINDOWS 1 -//#define BLADERF_BIG_ENDIAN 0 +#define BLADERF_OS_LINUX 0 +#define BLADERF_OS_FREEBSD 0 +#define BLADERF_OS_OSX 0 +#define BLADERF_OS_WINDOWS 1 +#define BLADERF_BIG_ENDIAN 0 #if !(BLADERF_OS_LINUX || BLADERF_OS_OSX || BLADERF_OS_WINDOWS || BLADERF_OS_FREEBSD) # error "Build not configured for any supported operating systems" @@ -41,6 +41,7 @@ #error "Build configured for multiple operating systems" #endif +#define HAVE_CLOCK_GETTIME 0 /******************************************************************************* * Endianness conversions @@ -152,6 +153,63 @@ #error "Encountered an OS that we do not have endian wrappers for?" #endif +/******************************************************************************* + * Endianness conversions for constants + * + * We shouldn't be using the above items for assigning constants because the + * implementations may be functions [1]. + * + * [1] https://sourceware.org/bugzilla/show_bug.cgi?id=17679#c7 + ******************************************************************************/ + +#define BLADERF_BYTESWAP16(x) ((((x) & 0xff00) >> 8) | (((x) & 0x00ff) << 8)) + +#define BLADERF_BYTESWAP32(x) ((((x) & 0xff000000) >> 24) | \ + (((x) & 0x00ff0000) >> 8) | \ + (((x) & 0x0000ff00) << 8) | \ + (((x) & 0x000000ff) << 24) ) + +#define BLADERF_BYTESWAP64(x) ((((x) & 0xff00000000000000llu) >> 56) | \ + (((x) & 0x00ff000000000000llu) >> 40) | \ + (((x) & 0x0000ff0000000000llu) >> 24) | \ + (((x) & 0x000000ff00000000llu) >> 8) | \ + (((x) & 0x00000000ff000000llu) << 8) | \ + (((x) & 0x0000000000ff0000llu) << 24) | \ + (((x) & 0x000000000000ff00llu) << 40) | \ + (((x) & 0x00000000000000ffllu) << 56) | \ + +#if BLADERF_BIG_ENDIAN +# define HOST_TO_LE16_CONST(x) (BLADERF_BYTESWAP16(x)) +# define LE16_TO_HOST_CONST(x) (BLADERF_BYTESWAP16(x)) +# define HOST_TO_BE16_CONST(x) (x) +# define BE16_TO_HOST_CONST(x) (x) + +# define HOST_TO_LE32_CONST(x) (BLADERF_BYTESWAP32(x)) +# define LE32_TO_HOST_CONST(x) (BLADERF_BYTESWAP32(x)) +# define HOST_TO_BE32_CONST(x) (x) +# define BE32_TO_HOST_CONST(x) (x) + +# define HOST_TO_LE64_CONST(x) (BLADERF_BYTESWAP64(x)) +# define LE64_TO_HOST_CONST(x) (BLADERF_BYTESWAP64(x)) +# define HOST_TO_BE64_CONST(x) (x) +# define BE64_TO_HOST_CONST(x) (x) +#else +# define HOST_TO_LE16_CONST(x) (x) +# define LE16_TO_HOST_CONST(x) (x) +# define HOST_TO_BE16_CONST(x) (BLADERF_BYTESWAP16(x)) +# define BE16_TO_HOST_CONST(x) (BLADERF_BYTESWAP16(x)) + +# define HOST_TO_LE32_CONST(x) (x) +# define LE32_TO_HOST_CONST(x) (x) +# define HOST_TO_BE32_CONST(x) (BLADERF_BYTESWAP32(x)) +# define BE32_TO_HOST_CONST(x) (BLADERF_BYTESWAP32(x)) + +# define HOST_TO_LE64_CONST(x) (x) +# define LE64_TO_HOST_CONST(x) (x) +# define HOST_TO_BE64_CONST(x) (BLADERF_BYTESWAP64(x)) +# define BE64_TO_HOST_CONST(x) (BLADERF_BYTESWAP64(x)) +#endif + /******************************************************************************* * Miscellaneous Linux fixups ******************************************************************************/ @@ -187,19 +245,29 @@ /* Rename a few functions and types */ #include #include +#include #define usleep(x) Sleep((x+999)/1000) /* Changes specific to Microsoft Visual Studio and its C89-compliant ways */ #if _MSC_VER # define strtok_r strtok_s # define strtoull _strtoui64 +#if _MSC_VER < 1900 # define snprintf _snprintf # define vsnprintf _vsnprintf +#else +#define STDC99 +#endif # define strcasecmp _stricmp # define strncasecmp _strnicmp # define fileno _fileno # define strdup _strdup # define access _access +# define chdir _chdir +# define getcwd _getcwd +# define rmdir _rmdir +# define unlink _unlink +# define mkdir(n, m) _mkdir(n) /* ssize_t lives elsewhere */ # include @@ -211,6 +279,19 @@ # define inline __inline # endif +/* Visual studio 2012 compiler (v17.00.XX) doesn't have round functions */ +# if _MSC_VER <= 1700 +# include + static inline float roundf(float x) + { + return x >= 0.0f ? floorf(x + 0.5f) : ceilf(x - 0.5f); + } + static inline double round(double x) + { + return x >= 0.0 ? floor(x + 0.5) : ceil(x - 0.5); + } +# endif + #endif // _MSC_VER #endif @@ -237,4 +318,16 @@ ******************************************************************************/ #define ARRAY_SIZE(n) (sizeof(n) / sizeof(n[0])) +/** + * GCC 7+ warns on implicit fallthroughs in switch statements + * (-Wimplicit-fallthrough), which we use in a couple places for simplicity. + * The statement attribute syntax used to suppress this warning is not + * supported by anything else. + */ +#if __GNUC__ >= 7 +#define EXPLICIT_FALLTHROUGH __attribute__((fallthrough)) +#else +#define EXPLICIT_FALLTHROUGH +#endif // __GNUC__ >= 7 + #endif diff --git a/libbladerf/mingw/libraries/libbladeRF/src/backend/backend_config.h b/libbladerf/mingw/libraries/libbladeRF/src/backend/backend_config.h new file mode 100644 index 000000000..351d5d12b --- /dev/null +++ b/libbladerf/mingw/libraries/libbladeRF/src/backend/backend_config.h @@ -0,0 +1,88 @@ +/** + * @file backend_config.h + * + * @brief Compile-time backend selection + * + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2013 Nuand LLC + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef BACKEND_BACKEND_CONFIG_H_ +#define BACKEND_BACKEND_CONFIG_H_ + +#define ENABLE_BACKEND_USB +#define ENABLE_BACKEND_LIBUSB +/* #undef ENABLE_BACKEND_CYAPI */ +/* #undef ENABLE_BACKEND_DUMMY */ +/* #undef ENABLE_BACKEND_LINUX_DRIVER */ + +#include "backend/backend.h" +#include "backend/usb/usb.h" + +#ifdef ENABLE_BACKEND_DUMMY +extern const struct backend_fns backend_fns_dummy; +#define BACKEND_DUMMY &backend_fns_dummy, +#else +#define BACKEND_DUMMY +#endif + +#ifdef ENABLE_BACKEND_USB +extern const struct backend_fns backend_fns_usb; +#define BACKEND_USB &backend_fns_usb, + +#ifdef ENABLE_BACKEND_LIBUSB +extern const struct usb_driver usb_driver_libusb; +#define BACKEND_USB_LIBUSB &usb_driver_libusb, +#else +#define BACKEND_USB_LIBUSB +#endif + +#ifdef ENABLE_BACKEND_CYAPI +extern const struct usb_driver usb_driver_cypress; +#define BACKEND_USB_CYAPI &usb_driver_cypress, +#else +#define BACKEND_USB_CYAPI +#endif + +#define BLADERF_USB_BACKEND_LIST \ + { \ + BACKEND_USB_LIBUSB \ + BACKEND_USB_CYAPI \ + } + +#if !defined(ENABLE_BACKEND_LIBUSB) && !defined(ENABLE_BACKEND_CYAPI) +#error "No USB backends are enabled. One or more must be enabled." +#endif + +#else +#define BACKEND_USB +#endif + +#if !defined(ENABLE_BACKEND_USB) && !defined(ENABLE_BACKEND_DUMMY) +#error "No backends are enabled. One more more must be enabled." +#endif + +/* This list should be ordered by preference (highest first) */ +#define BLADERF_BACKEND_LIST \ + { \ + BACKEND_USB \ + BACKEND_DUMMY \ + } + +#endif diff --git a/libbladerf/mingw/libraries/libbladeRF/src/version.h b/libbladerf/mingw/libraries/libbladeRF/src/version.h new file mode 100644 index 000000000..480695256 --- /dev/null +++ b/libbladerf/mingw/libraries/libbladeRF/src/version.h @@ -0,0 +1,12 @@ +#ifndef VERSION_H_ +#define VERSION_H_ + +#define LIBBLADERF_VERSION "2.0.2-git-32058c47-dirty" + +// clang-format off +#define LIBBLADERF_VERSION_MAJOR 2 +#define LIBBLADERF_VERSION_MINOR 0 +#define LIBBLADERF_VERSION_PATCH 2 +// clang-format on + +#endif diff --git a/libhackrf/libhackrf.pro b/libhackrf/libhackrf.pro index 5f92e0285..b270160de 100644 --- a/libhackrf/libhackrf.pro +++ b/libhackrf/libhackrf.pro @@ -9,19 +9,25 @@ QT += core TEMPLATE = lib TARGET = libhackrf -CONFIG(MINGW32):LIBHACKRFSRC = "D:\softs\hackrf\host\libhackrf" -CONFIG(MINGW64):LIBHACKRFSRC = "D:\softs\hackrf\host\libhackrf" +CONFIG(MINGW32):LIBHACKRFSRC = "C:\softs\hackrf\host\libhackrf" +CONFIG(MINGW64):LIBHACKRFSRC = "C:\softs\hackrf\host\libhackrf" +CONFIG(MSVC):LIBHACKRFSRC = "C:\softs\hackrf\host\libhackrf" + INCLUDEPATH += $$LIBHACKRFSRC/src -CONFIG(MINGW32):INCLUDEPATH += "D:\softs\libusb-1.0.20\include\libusb-1.0" -CONFIG(MINGW64):INCLUDEPATH += "D:\softs\libusb-1.0.20\include\libusb-1.0" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" +CONFIG(MSVC):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" +CONFIG(MSVC):INCLUDEPATH += "C:\softs\pthreads-w32\include" SOURCES = $$LIBHACKRFSRC/src/hackrf.c HEADERS = $$LIBHACKRFSRC/src/hackrf.h -CONFIG(MINGW32):LIBS += -LD:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -CONFIG(MINGW64):LIBS += -LD:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.22\MinGW32\dll -llibusb-1.0 +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.22\MinGW64\dll -llibusb-1.0 +CONFIG(MSVC):LIBS += -LC:\softs\libusb-1.0.22\MS64\dll -llibusb-1.0 +CONFIG(MSVC):LIBS += -LC:\softs\pthreads-w32\lib\x64 -lpthreadVC2 CONFIG(ANDROID):CONFIG += mobility CONFIG(ANDROID):MOBILITY = diff --git a/libiio/libiio.pro b/libiio/libiio.pro index d02e40b64..bbde28890 100644 --- a/libiio/libiio.pro +++ b/libiio/libiio.pro @@ -9,23 +9,23 @@ QT += core TEMPLATE = lib TARGET = libiio -CONFIG(MINGW32):LIBIIOSRC = "D:\softs\libiio" -CONFIG(MINGW64):LIBIIOSRC = "D:\softs\libiio" +CONFIG(MINGW32):LIBIIOSRC = "C:\softs\libiio" +CONFIG(MINGW64):LIBIIOSRC = "C:\softs\libiio" DEFINES += LIBIIO_EXPORTS=1 INCLUDEPATH += $$PWD/includemw INCLUDEPATH += $$LIBIIOSRC -CONFIG(MINGW32):INCLUDEPATH += "D:\softs\libusb-1.0.20\include" -CONFIG(MINGW64):INCLUDEPATH += "D:\softs\libusb-1.0.20\include" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.22\include" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.22\include" # LibXml2 Windows distribution from: # http://xmlsoft.org/sources/win32/ # http://xmlsoft.org/sources/win32/64bit/ -CONFIG(MINGW32):INCLUDEPATH += "D:\softs\libxml2-2.7.8.win32\include" -CONFIG(MINGW64):INCLUDEPATH += "D:\softs\libxml2-2.9.3-win32-x86_64\include\libxml2" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libxml2-2.7.8.win32\include" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libxml2-2.9.3-win32-x86_64\include\libxml2" SOURCES = $$LIBIIOSRC/backend.c\ $$LIBIIOSRC/buffer.c\ @@ -49,11 +49,11 @@ HEADERS = $$LIBIIOSRC/debug.h\ $$LIBIIOSRC/iio-private.h\ $$PWD/includemw/iio-config.h -CONFIG(MINGW32):LIBS += -LD:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -CONFIG(MINGW64):LIBS += -LD:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.22\MinGW32\dll -llibusb-1.0 +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.22\MinGW64\dll -llibusb-1.0 -CONFIG(MINGW32):LIBS += -LD:\softs\libxml2-2.7.8.win32\bin -llibxml2 -CONFIG(MINGW64):LIBS += -LD:\softs\libxml2-2.9.3-win32-x86_64\bin -llibxml2-2 +CONFIG(MINGW32):LIBS += -LC:\softs\libxml2-2.7.8.win32\bin -llibxml2 +CONFIG(MINGW64):LIBS += -LC:\softs\libxml2-2.9.3-win32-x86_64\bin -llibxml2-2 CONFIG(ANDROID):CONFIG += mobility CONFIG(ANDROID):MOBILITY = diff --git a/liblimesuite/CMakeLists.txt b/liblimesuite/CMakeLists.txt index c09750124..36f6662f9 100644 --- a/liblimesuite/CMakeLists.txt +++ b/liblimesuite/CMakeLists.txt @@ -1,96 +1,63 @@ project(limesuite) find_package(LibUSB) -find_package(SQLite3) set(limesuite_SOURCES - ${LIBLIMESUITESRC}/src/ADF4002/ADF4002.cpp - ${LIBLIMESUITESRC}/src/API/lms7_api.cpp - ${LIBLIMESUITESRC}/src/API/lms7_device.cpp - ${LIBLIMESUITESRC}/src/API/LimeSDR_mini.cpp - ${LIBLIMESUITESRC}/src/API/qLimeSDR.cpp - src/BuiltinConnections.cpp + ${LIBLIMESUITESRC}/src/Logger.cpp + ${LIBLIMESUITESRC}/src/ADF4002/ADF4002.cpp + ${LIBLIMESUITESRC}/src/lms7002m_mcu/MCU_BD.cpp + ${LIBLIMESUITESRC}/src/ConnectionRegistry/IConnection.cpp ${LIBLIMESUITESRC}/src/ConnectionRegistry/ConnectionHandle.cpp ${LIBLIMESUITESRC}/src/ConnectionRegistry/ConnectionRegistry.cpp - ${LIBLIMESUITESRC}/src/ConnectionRegistry/IConnection.cpp - ${LIBLIMESUITESRC}/src/ConnectionSTREAM/ConnectionSTREAM.cpp - ${LIBLIMESUITESRC}/src/ConnectionSTREAM/ConnectionSTREAMEntry.cpp - ${LIBLIMESUITESRC}/src/ConnectionSTREAM/ConnectionSTREAMImages.cpp - ${LIBLIMESUITESRC}/src/ConnectionSTREAM/ConnectionSTREAMing.cpp - ${LIBLIMESUITESRC}/src/Connection_uLimeSDR/Connection_uLimeSDR.cpp - ${LIBLIMESUITESRC}/src/Connection_uLimeSDR/Connection_uLimeSDREntry.cpp - ${LIBLIMESUITESRC}/src/Connection_uLimeSDR/Connection_uLimeSDRing.cpp -# ${LIBLIMESUITESRC}/src/Connection_uLimeSDR/DRV_DriverInterface.cpp - ${LIBLIMESUITESRC}/src/ConnectionXillybus/ConnectionXillybus.cpp - ${LIBLIMESUITESRC}/src/ConnectionXillybus/ConnectionXillybusEntry.cpp - ${LIBLIMESUITESRC}/src/ConnectionXillybus/ConnectionXillybusing.cpp + ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_RegistersMap.cpp + ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_parameters.cpp + ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M.cpp + ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_RxTxCalibrations.cpp + ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_BaseCalibrations.cpp + ${LIBLIMESUITESRC}/src/lms7002m/goert.cpp + ${LIBLIMESUITESRC}/src/lms7002m/mcu_dc_iq_calibration.cpp + ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_filtersCalibration.cpp + ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_gainCalibrations.cpp + ${LIBLIMESUITESRC}/src/protocols/LMS64CProtocol.cpp + ${LIBLIMESUITESRC}/src/protocols/Streamer.cpp + ${LIBLIMESUITESRC}/src/protocols/ConnectionImages.cpp + ${LIBLIMESUITESRC}/src/Si5351C/Si5351C.cpp + ${LIBLIMESUITESRC}/src/API/lms7_api.cpp + ${LIBLIMESUITESRC}/src/API/lms7_device.cpp + ${LIBLIMESUITESRC}/src/API/LmsGeneric.cpp + ${LIBLIMESUITESRC}/src/API/qLimeSDR.cpp + ${LIBLIMESUITESRC}/src/API/LimeSDR_mini.cpp + ${LIBLIMESUITESRC}/src/API/LimeSDR.cpp ${LIBLIMESUITESRC}/src/FPGA_common/FPGA_common.cpp + ${LIBLIMESUITESRC}/src/FPGA_common/FPGA_Mini.cpp + ${LIBLIMESUITESRC}/src/FPGA_common/FPGA_Q.cpp ${LIBLIMESUITESRC}/src/GFIR/corrections.c ${LIBLIMESUITESRC}/src/GFIR/gfir_lms.c ${LIBLIMESUITESRC}/src/GFIR/lms.c ${LIBLIMESUITESRC}/src/GFIR/recipes.c ${LIBLIMESUITESRC}/src/GFIR/rounding.c - ${LIBLIMESUITESRC}/src/kissFFT/kiss_fft.c - ${LIBLIMESUITESRC}/src/lms7002m/CalibrationCache.cpp - ${LIBLIMESUITESRC}/src/lms7002m/goert.cpp - ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_BaseCalibrations.cpp - ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M.cpp - ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_filtersCalibration.cpp - ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_gainCalibrations.cpp - ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_parameters.cpp - ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_RegistersMap.cpp - ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_RxTxCalibrations.cpp - ${LIBLIMESUITESRC}/src/lms7002m/mcu_dc_iq_calibration.cpp - ${LIBLIMESUITESRC}/src/lms7002m_mcu/MCU_BD.cpp - ${LIBLIMESUITESRC}/src/protocols/ILimeSDRStreaming.cpp - ${LIBLIMESUITESRC}/src/protocols/LMS64CProtocol.cpp - ${LIBLIMESUITESRC}/src/Si5351C/Si5351C.cpp - ${LIBLIMESUITESRC}/src/ErrorReporting.cpp - ${LIBLIMESUITESRC}/src/Logger.cpp + ${LIBLIMESUITESRC}/src/windowFunction.cpp + ${LIBLIMESUITESRC}/src/ConnectionFTDI/ConnectionFT601.cpp + ${LIBLIMESUITESRC}/src/ConnectionFTDI//ConnectionFT601Entry.cpp + ${LIBLIMESUITESRC}/src/ConnectionFX3/ConnectionFX3Entry.cpp + ${LIBLIMESUITESRC}/src/ConnectionFX3/ConnectionFX3.cpp + src/BuiltinConnections.cpp src/SystemResources.cpp src/VersionInfo.cpp ) set(limesuite_HEADERS - ${LIBLIMESUITESRC}/src/ADF4002/ADF4002.h - ${LIBLIMESUITESRC}/src/API/lms7_device.h - ${LIBLIMESUITESRC}/src/API/LimeSDR_mini.h - ${LIBLIMESUITESRC}/src/API/qLimeSDR.h - ${LIBLIMESUITESRC}/src/ConnectionRegistry/ConnectionHandle.h - ${LIBLIMESUITESRC}/src/ConnectionRegistry/ConnectionRegistry.h - ${LIBLIMESUITESRC}/src/ConnectionRegistry/IConnection.h - ${LIBLIMESUITESRC}/src/ConnectionSTREAM/ConnectionSTREAM.h - ${LIBLIMESUITESRC}/src/Connection_uLimeSDR/Connection_uLimeSDR.h -# ${LIBLIMESUITESRC}/src/Connection_uLimeSDR/DRV_DriverInterface.h - ${LIBLIMESUITESRC}/src/Connection_uLimeSDR/FTD3XXLibrary/FTD3XX.h - ${LIBLIMESUITESRC}/src/ConnectionXillybus/ConnectionXillybus.h - ${LIBLIMESUITESRC}/src/FPGA_common/FPGA_common.h - ${LIBLIMESUITESRC}/src/GFIR/dfilter.h - ${LIBLIMESUITESRC}/src/GFIR/lms_gfir.h - ${LIBLIMESUITESRC}/src/GFIR/lms.h - ${LIBLIMESUITESRC}/src/kissFFT/_kiss_fft_guts.h - ${LIBLIMESUITESRC}/src/kissFFT/kiss_fft.h - ${LIBLIMESUITESRC}/src/lms7002m/CalibrationCache.h - ${LIBLIMESUITESRC}/src/lms7002m/goertzel.h - ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M.h - ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_parameters.h - ${LIBLIMESUITESRC}/src/lms7002m/LMS7002M_RegistersMap.h - ${LIBLIMESUITESRC}/src/lms7002m/mcu_programs.h - ${LIBLIMESUITESRC}/src/lms7002m_mcu/MCU_BD.h - ${LIBLIMESUITESRC}/src/lms7002m_mcu/MCU_File.h - ${LIBLIMESUITESRC}/src/protocols/ADCUnits.h - ${LIBLIMESUITESRC}/src/protocols/dataTypes.h - ${LIBLIMESUITESRC}/src/protocols/fifo.h - ${LIBLIMESUITESRC}/src/protocols/ILimeSDRStreaming.h - ${LIBLIMESUITESRC}/src/protocols/LMS64CCommands.h - ${LIBLIMESUITESRC}/src/protocols/LMS64CProtocol.h - ${LIBLIMESUITESRC}/src/protocols/LMSBoards.h - ${LIBLIMESUITESRC}/src/Si5351C/Si5351C.h - ${LIBLIMESUITESRC}/src/ErrorReporting.h - ${LIBLIMESUITESRC}/src/Logger.h - ${LIBLIMESUITESRC}/src/SystemResources.h - ${LIBLIMESUITESRC}/src/VersionInfo.h - ${LIBLIMESUITESRC}/src/lime/LimeSuite.h + ${LIBLIMESUITESRC}/src/lime/*.h + ${LIBLIMESUITESRC}/src/API/*.h + ${LIBLIMESUITESRC}/src/GFIR/*.h + ${LIBLIMESUITESRC}/src/protocols/*.h + ${LIBLIMESUITESRC}/src/ConnectionRegistry/*.h + ${LIBLIMESUITESRC}/src/lms7002m_mcu/*.h + ${LIBLIMESUITESRC}/src/ADF4002/*.h + ${LIBLIMESUITESRC}/src/Si5351C/*.h + ${LIBLIMESUITESRC}/src/lms7002m/*.h + ${LIBLIMESUITESRC}/src/FPGA_common/*.h + ${LIBLIMESUITESRC}/src/HPM7/*.h ) include_directories( @@ -98,14 +65,18 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${LIBUSB_INCLUDE_DIR} ${LIBLIMESUITESRC}/src - ${LIBLIMESUITESRC}/src/ADF4002 - ${LIBLIMESUITESRC}/src/ConnectionRegistry - ${LIBLIMESUITESRC}/src/FPGA_common + ${LIBLIMESUITESRC}/src/lime + ${LIBLIMESUITESRC}/src/API ${LIBLIMESUITESRC}/src/GFIR - ${LIBLIMESUITESRC}/src/lms7002m - ${LIBLIMESUITESRC}/src/lms7002m_mcu - ${LIBLIMESUITESRC}/src/Si5351C ${LIBLIMESUITESRC}/src/protocols + ${LIBLIMESUITESRC}/src/ConnectionRegistry + ${LIBLIMESUITESRC}/src/lms7002m_mcu + ${LIBLIMESUITESRC}/src/ADF4002 + ${LIBLIMESUITESRC}/src/Si5351C + ${LIBLIMESUITESRC}/src/lms7002m + ${LIBLIMESUITESRC}/src/FPGA_common + ${LIBLIMESUITESRC}/src/HPM7 + ${LIBLIMESUITESRC}/src/kissFFT ${LIBLIMESUITESRC}/external/cpp-feather-ini-parser ./include ) @@ -119,7 +90,6 @@ add_library(limesuite SHARED target_link_libraries(limesuite ${LIBUSB_LIBRARIES} - ${SQLITE3_LIBRARIES} ) install(TARGETS limesuite DESTINATION lib) diff --git a/liblimesuite/liblimesuite.pro b/liblimesuite/liblimesuite.pro index 32cdee284..da6c4e0d8 100644 --- a/liblimesuite/liblimesuite.pro +++ b/liblimesuite/liblimesuite.pro @@ -10,20 +10,22 @@ TEMPLATE = lib TARGET = liblimesuite DEFINES += ENOLINK=21 +DEFINES += "__unix__" QMAKE_CXXFLAGS += -fpermissive QMAKE_CXXFLAGS += -std=c++11 -CONFIG(MINGW32):LIBLIMESUITESRC = "D:\softs\LimeSuite" -CONFIG(MINGW64):LIBLIMESUITESRC = "D:\softs\LimeSuite" +CONFIG(MINGW32):LIBLIMESUITESRC = "C:\softs\LimeSuite" +CONFIG(MINGW64):LIBLIMESUITESRC = "C:\softs\LimeSuite" -CONFIG(MINGW32):INCLUDEPATH += "D:\softs\libusb-1.0.20\include\libusb-1.0" -CONFIG(MINGW64):INCLUDEPATH += "D:\softs\libusb-1.0.20\include\libusb-1.0" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.22\include" -CONFIG(MINGW32):INCLUDEPATH += "..\libsqlite3\src" -CONFIG(MINGW64):INCLUDEPATH += "..\libsqlite3\src" +#CONFIG(MINGW32):INCLUDEPATH += "..\libsqlite3\src" +#CONFIG(MINGW64):INCLUDEPATH += "..\libsqlite3\src" INCLUDEPATH += $$LIBLIMESUITESRC/src +INCLUDEPATH += $$LIBLIMESUITESRC/src/lime INCLUDEPATH += $$LIBLIMESUITESRC/src/ADF4002 INCLUDEPATH += $$LIBLIMESUITESRC/src/ConnectionRegistry INCLUDEPATH += $$LIBLIMESUITESRC/src/FPGA_common @@ -34,95 +36,64 @@ INCLUDEPATH += $$LIBLIMESUITESRC/src/Si5351C INCLUDEPATH += $$LIBLIMESUITESRC/src/protocols INCLUDEPATH += $$LIBLIMESUITESRC/external/cpp-feather-ini-parser -SOURCES = $$LIBLIMESUITESRC/src/ADF4002/ADF4002.cpp\ - $$LIBLIMESUITESRC/src/API/lms7_api.cpp\ - $$LIBLIMESUITESRC/src/API/lms7_device.cpp\ - $$LIBLIMESUITESRC/src/API/LimeSDR_mini.cpp\ - $$LIBLIMESUITESRC/src/API/qLimeSDR.cpp\ - src/BuiltinConnections.cpp\ +SOURCES = $$LIBLIMESUITESRC/src/Logger.cpp\ + $$LIBLIMESUITESRC/src/ADF4002/ADF4002.cpp\ + $$LIBLIMESUITESRC/src/lms7002m_mcu/MCU_BD.cpp\ + $$LIBLIMESUITESRC/src/ConnectionRegistry/IConnection.cpp\ $$LIBLIMESUITESRC/src/ConnectionRegistry/ConnectionHandle.cpp\ $$LIBLIMESUITESRC/src/ConnectionRegistry/ConnectionRegistry.cpp\ - $$LIBLIMESUITESRC/src/ConnectionRegistry/IConnection.cpp\ - $$LIBLIMESUITESRC/src/ConnectionSTREAM/ConnectionSTREAM.cpp\ - $$LIBLIMESUITESRC/src/ConnectionSTREAM/ConnectionSTREAMImages.cpp\ - $$LIBLIMESUITESRC/src/ConnectionSTREAM/ConnectionSTREAMing.cpp\ - $$LIBLIMESUITESRC/src/ConnectionSTREAM/ConnectionSTREAMEntry.cpp\ - $$LIBLIMESUITESRC/src/Connection_uLimeSDR/Connection_uLimeSDR.cpp\ - $$LIBLIMESUITESRC/src/Connection_uLimeSDR/Connection_uLimeSDRing.cpp\ - $$LIBLIMESUITESRC/src/Connection_uLimeSDR/Connection_uLimeSDREntry.cpp\ - $$LIBLIMESUITESRC/src/ConnectionXillybus/ConnectionXillybus.cpp\ - $$LIBLIMESUITESRC/src/ConnectionXillybus/ConnectionXillybusEntry.cpp\ - $$LIBLIMESUITESRC/src/ConnectionXillybus/ConnectionXillybusing.cpp\ + $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_RegistersMap.cpp\ + $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_parameters.cpp\ + $$LIBLIMESUITESRC/src/lms7002m/LMS7002M.cpp\ + $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_RxTxCalibrations.cpp\ + $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_BaseCalibrations.cpp\ + $$LIBLIMESUITESRC/src/lms7002m/goert.cpp\ + $$LIBLIMESUITESRC/src/lms7002m/mcu_dc_iq_calibration.cpp\ + $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_filtersCalibration.cpp\ + $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_gainCalibrations.cpp\ + $$LIBLIMESUITESRC/src/protocols/LMS64CProtocol.cpp\ + $$LIBLIMESUITESRC/src/protocols/Streamer.cpp\ + $$LIBLIMESUITESRC/src/protocols/ConnectionImages.cpp\ + $$LIBLIMESUITESRC/src/Si5351C/Si5351C.cpp\ + $$LIBLIMESUITESRC/src/API/lms7_api.cpp\ + $$LIBLIMESUITESRC/src/API/lms7_device.cpp\ + $$LIBLIMESUITESRC/src/API/LmsGeneric.cpp\ + $$LIBLIMESUITESRC/src/API/qLimeSDR.cpp\ + $$LIBLIMESUITESRC/src/API/LimeSDR_mini.cpp\ + $$LIBLIMESUITESRC/src/API/LimeSDR.cpp\ $$LIBLIMESUITESRC/src/FPGA_common/FPGA_common.cpp\ + $$LIBLIMESUITESRC/src/FPGA_common/FPGA_Mini.cpp\ + $$LIBLIMESUITESRC/src/FPGA_common/FPGA_Q.cpp\ $$LIBLIMESUITESRC/src/GFIR/corrections.c\ $$LIBLIMESUITESRC/src/GFIR/gfir_lms.c\ $$LIBLIMESUITESRC/src/GFIR/lms.c\ $$LIBLIMESUITESRC/src/GFIR/recipes.c\ $$LIBLIMESUITESRC/src/GFIR/rounding.c\ - $$LIBLIMESUITESRC/src/kissFFT/kiss_fft.c\ - $$LIBLIMESUITESRC/src/lms7002m/CalibrationCache.cpp\ - $$LIBLIMESUITESRC/src/lms7002m/goert.cpp\ - $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_BaseCalibrations.cpp\ - $$LIBLIMESUITESRC/src/lms7002m/LMS7002M.cpp\ - $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_filtersCalibration.cpp\ - $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_gainCalibrations.cpp\ - $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_parameters.cpp\ - $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_RegistersMap.cpp\ - $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_RxTxCalibrations.cpp\ - $$LIBLIMESUITESRC/src/lms7002m/mcu_dc_iq_calibration.cpp\ - $$LIBLIMESUITESRC/src/lms7002m_mcu/MCU_BD.cpp\ - $$LIBLIMESUITESRC/src/protocols/ILimeSDRStreaming.cpp\ - $$LIBLIMESUITESRC/src/protocols/LMS64CProtocol.cpp\ - $$LIBLIMESUITESRC/src/Si5351C/Si5351C.cpp\ - $$LIBLIMESUITESRC/src/ErrorReporting.cpp\ - $$LIBLIMESUITESRC/src/Logger.cpp\ + $$LIBLIMESUITESRC/src/windowFunction.cpp\ + $$LIBLIMESUITESRC/src/ConnectionFTDI/ConnectionFT601.cpp\ + $$LIBLIMESUITESRC/src/ConnectionFTDI//ConnectionFT601Entry.cpp\ + $$LIBLIMESUITESRC/src/ConnectionFX3/ConnectionFX3Entry.cpp\ + $$LIBLIMESUITESRC/src/ConnectionFX3/ConnectionFX3.cpp\ + src/BuiltinConnections.cpp\ src/SystemResources.cpp\ src/VersionInfo.cpp -HEADERS = $$LIBLIMESUITESRC/src/ADF4002/ADF4002.h\ - $$LIBLIMESUITESRC/src/API/lms7_device.h\ - $$LIBLIMESUITESRC/src/API/LimeSDR_mini.h\ - $$LIBLIMESUITESRC/src/API/qLimeSDR.h\ - $$LIBLIMESUITESRC/src/ConnectionRegistry/ConnectionHandle.h\ - $$LIBLIMESUITESRC/src/ConnectionRegistry/ConnectionRegistry.h\ - $$LIBLIMESUITESRC/src/ConnectionRegistry/IConnection.h\ - $$LIBLIMESUITESRC/src/ConnectionSTREAM/ConnectionSTREAM.h\ - $$LIBLIMESUITESRC/src/Connection_uLimeSDR/Connection_uLimeSDR.h\ - $$LIBLIMESUITESRC/src/Connection_uLimeSDR/FTD3XXLibrary/FTD3XX.h\ - $$LIBLIMESUITESRC/src/ConnectionXillybus/ConnectionXillybus.h\ - $$LIBLIMESUITESRC/src/FPGA_common/FPGA_common.h\ - $$LIBLIMESUITESRC/src/GFIR/dfilter.h\ - $$LIBLIMESUITESRC/src/GFIR/lms_gfir.h\ - $$LIBLIMESUITESRC/src/GFIR/lms.h\ - $$LIBLIMESUITESRC/src/kissFFT/_kiss_fft_guts.h\ - $$LIBLIMESUITESRC/src/kissFFT/kiss_fft.h\ - $$LIBLIMESUITESRC/src/lms7002m/CalibrationCache.h\ - $$LIBLIMESUITESRC/src/lms7002m/goertzel.h\ - $$LIBLIMESUITESRC/src/lms7002m/LMS7002M.h\ - $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_parameters.h\ - $$LIBLIMESUITESRC/src/lms7002m/LMS7002M_RegistersMap.h\ - $$LIBLIMESUITESRC/src/lms7002m/mcu_programs.h\ - $$LIBLIMESUITESRC/src/lms7002m_mcu/MCU_BD.h\ - $$LIBLIMESUITESRC/src/lms7002m_mcu/MCU_File.h\ - $$LIBLIMESUITESRC/src/protocols/ADCUnits.h\ - $$LIBLIMESUITESRC/src/protocols/dataTypes.h\ - $$LIBLIMESUITESRC/src/protocols/fifo.h\ - $$LIBLIMESUITESRC/src/protocols/ILimeSDRStreaming.h\ - $$LIBLIMESUITESRC/src/protocols/LMS64CCommands.h\ - $$LIBLIMESUITESRC/src/protocols/LMS64CProtocol.h\ - $$LIBLIMESUITESRC/src/protocols/LMSBoards.h\ - $$LIBLIMESUITESRC/src/Si5351C/Si5351C.h\ - $$LIBLIMESUITESRC/src/ErrorReporting.h\ - $$LIBLIMESUITESRC/src/Logger.h\ - $$LIBLIMESUITESRC/src/SystemResources.h\ - $$LIBLIMESUITESRC/src/VersionInfo.h\ - $$LIBLIMESUITESRC/src/lime/LimeSuite.h +HEADERS = $$LIBLIMESUITESRC/src/API/*.h\ + $$LIBLIMESUITESRC/src/GFIR/*.h\ + $$LIBLIMESUITESRC/src/protocols/*.h\ + $$LIBLIMESUITESRC/src/ConnectionRegistry/*.h\ + $$LIBLIMESUITESRC/src/lms7002m_mcu/*.h\ + $$LIBLIMESUITESRC/src/ADF4002/*.h\ + $$LIBLIMESUITESRC/src/Si5351C/*.h\ + $$LIBLIMESUITESRC/src/lms7002m/*.h\ + $$LIBLIMESUITESRC/src/FPGA_common/*.h\ + $$LIBLIMESUITESRC/src/HPM7/*.h -CONFIG(MINGW32):LIBS += -LD:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -CONFIG(MINGW64):LIBS += -LD:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.22\MinGW32\dll -llibusb-1.0 +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.22\MinGW64\dll -llibusb-1.0 -CONFIG(MINGW32):LIBS += -L../libsqlite3/release -llibsqlite3 -CONFIG(MINGW64):LIBS += -L../libsqlite3/release -llibsqlite3 +#CONFIG(MINGW32):LIBS += -L../libsqlite3/release -llibsqlite3 +#CONFIG(MINGW64):LIBS += -L../libsqlite3/release -llibsqlite3 CONFIG(ANDROID):CONFIG += mobility CONFIG(ANDROID):MOBILITY = diff --git a/liblimesuite/src/BuiltinConnections.cpp b/liblimesuite/src/BuiltinConnections.cpp index 592650b3e..c7b3313f7 100644 --- a/liblimesuite/src/BuiltinConnections.cpp +++ b/liblimesuite/src/BuiltinConnections.cpp @@ -4,17 +4,17 @@ **********************************************************************/ /* #undef ENABLE_EVB7COM */ -#define ENABLE_STREAM +#define ENABLE_FX3 /* #undef ENABLE_STREAM_UNITE */ /* #undef ENABLE_NOVENARF7 */ -#define ENABLE_uLimeSDR -#define ENABLE_PCIE_XILLYBUS +#define ENABLE_FTDI +/* #undef ENABLE_PCIE_XILLYBUS */ void __loadConnectionEVB7COMEntry(void); -void __loadConnectionSTREAMEntry(void); +void __loadConnectionFX3Entry(void); void __loadConnectionSTREAM_UNITEEntry(void); void __loadConnectionNovenaRF7Entry(void); -void __loadConnection_uLimeSDREntry(void); +void __loadConnectionFT601Entry(void); void __loadConnectionXillybusEntry(void); void __loadAllConnections(void) @@ -23,16 +23,16 @@ void __loadAllConnections(void) __loadConnectionEVB7COMEntry(); #endif - #ifdef ENABLE_STREAM - __loadConnectionSTREAMEntry(); + #ifdef ENABLE_FX3 + __loadConnectionFX3Entry(); #endif #ifdef ENABLE_STREAM_UNITE __loadConnectionSTREAM_UNITEEntry(); #endif - #ifdef ENABLE_uLimeSDR - __loadConnection_uLimeSDREntry(); + #ifdef ENABLE_FTDI + __loadConnectionFT601Entry(); #endif #ifdef ENABLE_NOVENARF7 diff --git a/liblimesuite/src/SystemResources.cpp b/liblimesuite/src/SystemResources.cpp index 70d39e968..75bbf7f1f 100644 --- a/liblimesuite/src/SystemResources.cpp +++ b/liblimesuite/src/SystemResources.cpp @@ -5,7 +5,7 @@ */ #include "SystemResources.h" -#include "ErrorReporting.h" +#include "Logger.h" #include //getenv, system #include @@ -23,9 +23,11 @@ #define W_OK 4 #endif -#ifdef __unix__ -#include +#ifdef __MINGW32__ #include +#elif __unix__ +#include +#include #endif #include @@ -65,6 +67,7 @@ std::string lime::getLimeSuiteRoot(void) std::string lime::getHomeDirectory(void) { +#ifndef __MINGW32__ //first check the HOME environment variable const char *userHome = std::getenv("HOME"); if (userHome != nullptr) return userHome; @@ -74,7 +77,7 @@ std::string lime::getHomeDirectory(void) const char *pwDir = getpwuid(getuid())->pw_dir; if (pwDir != nullptr) return pwDir; #endif - +#endif return ""; } @@ -155,7 +158,7 @@ std::string lime::locateImageResource(const std::string &name) { for (const auto &searchPath : lime::listImageSearchPaths()) { - const std::string fullPath(searchPath + "/17.03/" + name); + const std::string fullPath(searchPath + "/18.02/" + name); if (access(fullPath.c_str(), R_OK) == 0) return fullPath; } return ""; @@ -163,9 +166,9 @@ std::string lime::locateImageResource(const std::string &name) int lime::downloadImageResource(const std::string &name) { - const std::string destDir(lime::getAppDataDirectory() + "/images/17.03"); + const std::string destDir(lime::getAppDataDirectory() + "/images/18.02"); const std::string destFile(destDir + "/" + name); - const std::string sourceUrl("http://downloads.myriadrf.org/project/limesuite/17.03/" + name); + const std::string sourceUrl("http://downloads.myriadrf.org/project/limesuite/18.02/" + name); //check if the directory already exists struct stat s; diff --git a/libperseus/libperseus.pro b/libperseus/libperseus.pro index e3cf9c2b3..64d4eddbf 100644 --- a/libperseus/libperseus.pro +++ b/libperseus/libperseus.pro @@ -11,12 +11,12 @@ TARGET = libperseus DEFINES += HAVE_CONFIG_H=1 -CONFIG(MINGW32):LIBPERSEUSSRC = "D:\softs\libperseus-sdr" -CONFIG(MINGW64):LIBPERSEUSSRC = "D:\softs\libperseus-sdr" +CONFIG(MINGW32):LIBPERSEUSSRC = "C:\softs\libperseus-sdr" +CONFIG(MINGW64):LIBPERSEUSSRC = "C:\softs\libperseus-sdr" INCLUDEPATH += $$LIBPERSEUSSRC/src -CONFIG(MINGW32):INCLUDEPATH += "D:\softs\libusb-1.0.20\include" -CONFIG(MINGW64):INCLUDEPATH += "D:\softs\libusb-1.0.20\include" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.22\include" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.22\include" SOURCES = fpga_data.c\ $$LIBPERSEUSSRC/fifo.c\ @@ -34,8 +34,8 @@ HEADERS = fpga_data.h\ $$LIBPERSEUSSRC/perseus-in.h\ $$LIBPERSEUSSRC/perseus-sdr.h -CONFIG(MINGW32):LIBS += -LD:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -CONFIG(MINGW64):LIBS += -LD:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.22\MinGW32\dll -llibusb-1.0 +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.22\MinGW64\dll -llibusb-1.0 CONFIG(ANDROID):CONFIG += mobility CONFIG(ANDROID):MOBILITY = diff --git a/librtlsdr/librtlsdr.pro b/librtlsdr/librtlsdr.pro index 8568dd11e..3af498a62 100644 --- a/librtlsdr/librtlsdr.pro +++ b/librtlsdr/librtlsdr.pro @@ -9,12 +9,15 @@ QT += core TEMPLATE = lib TARGET = librtlsdr -CONFIG(MINGW32):LIBRTLSDRSRC = "D:\softs\librtlsdr" -CONFIG(MINGW64):LIBRTLSDRSRC = "D:\softs\librtlsdr" +CONFIG(MINGW32):LIBRTLSDRSRC = "C:\softs\librtlsdr" +CONFIG(MINGW64):LIBRTLSDRSRC = "C:\softs\librtlsdr" +CONFIG(MSVC):LIBRTLSDRSRC = "C:\softs\librtlsdr" + INCLUDEPATH += $$LIBRTLSDRSRC/include -CONFIG(MINGW32):INCLUDEPATH += "D:\softs\libusb-1.0.20\include\libusb-1.0" -CONFIG(MINGW64):INCLUDEPATH += "D:\softs\libusb-1.0.20\include\libusb-1.0" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" +CONFIG(MSVC):INCLUDEPATH += "C:\softs\libusb-1.0.22\include\libusb-1.0" SOURCES = $$LIBRTLSDRSRC/src/librtlsdr.c\ $$LIBRTLSDRSRC/src/tuner_e4k.c\ @@ -37,8 +40,9 @@ HEADERS = $$LIBRTLSDRSRC/include/reg_field.h\ $$LIBRTLSDRSRC/src/getopt/getopt.h\ $$LIBRTLSDRSRC/src/convenience/convenience.h -CONFIG(MINGW32):LIBS += -LD:\softs\libusb-1.0.20\MinGW32\dll -llibusb-1.0 -CONFIG(MINGW64):LIBS += -LD:\softs\libusb-1.0.20\MinGW64\dll -llibusb-1.0 +CONFIG(MINGW32):LIBS += -LC:\softs\libusb-1.0.22\MinGW32\dll -llibusb-1.0 +CONFIG(MINGW64):LIBS += -LC:\softs\libusb-1.0.22\MinGW64\dll -llibusb-1.0 +CONFIG(MSVC):LIBS += -LC:\softs\libusb-1.0.22\MS64\dll -llibusb-1.0 CONFIG(ANDROID):CONFIG += mobility CONFIG(ANDROID):MOBILITY = diff --git a/logging/CMakeLists.txt b/logging/CMakeLists.txt index 4c615fae8..5208d4326 100644 --- a/logging/CMakeLists.txt +++ b/logging/CMakeLists.txt @@ -19,6 +19,7 @@ set(httpserver_HEADERS include_directories( . + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_CURRENT_BINARY_DIR} ) @@ -35,6 +36,6 @@ target_link_libraries(logging ${QT_LIBRARIES} ) -qt5_use_modules(logging Core Network) +target_link_libraries(logging Qt5::Core Qt5::Network) install(TARGETS logging DESTINATION lib) diff --git a/logging/dualfilelogger.h b/logging/dualfilelogger.h index 6fb16c568..f8c0dfadf 100644 --- a/logging/dualfilelogger.h +++ b/logging/dualfilelogger.h @@ -13,6 +13,8 @@ #include "logger.h" #include "filelogger.h" +#include "export.h" + namespace qtwebapp { /** @@ -21,7 +23,7 @@ namespace qtwebapp { @see FileLogger for a description of the two underlying loggers. */ -class DECLSPEC DualFileLogger : public Logger { +class LOGGING_API DualFileLogger : public Logger { Q_OBJECT Q_DISABLE_COPY(DualFileLogger) public: diff --git a/logging/filelogger.cpp b/logging/filelogger.cpp index d73dada71..63e4113a3 100644 --- a/logging/filelogger.cpp +++ b/logging/filelogger.cpp @@ -52,7 +52,7 @@ void FileLogger::refreshQtSettings() maxSize = settings->value("maxSize", 0).toLongLong(); maxBackups = settings->value("maxBackups", 0).toInt(); msgFormat = settings->value("msgFormat", "{timestamp} {type} {msg}").toString(); - timestampFormat = settings->value("timestampFormat", "yyyy-MM-dd hh:mm:ss.zzz").toString(); + timestampFormat = settings->value("timestampFormat", "yyyy-MM-dd HH:mm:ss.zzz").toString(); minLevel = static_cast(settings->value("minLevel", 0).toInt()); bufferSize = settings->value("bufferSize", 0).toInt(); diff --git a/logging/filelogger.h b/logging/filelogger.h index 7e23a303d..1304ac896 100644 --- a/logging/filelogger.h +++ b/logging/filelogger.h @@ -15,6 +15,8 @@ #include "logger.h" #include "fileloggersettings.h" +#include "export.h" + namespace qtwebapp { /** @@ -28,7 +30,7 @@ namespace qtwebapp { maxBackups=2 minLevel=0 msgformat={timestamp} {typeNr} {type} thread={thread}: {msg} - timestampFormat=dd.MM.yyyy hh:mm:ss.zzz + timestampFormat=yyyy-MM-dd HH:mm:ss.zzz bufferSize=0 @@ -41,7 +43,7 @@ namespace qtwebapp { - maxBackups defines the number of backup files to keep. Default is 0=unlimited. - minLevel defines the minimum type of messages that are written (together with buffered messages) into the file. Defaults is 0=debug. - msgFormat defines the decoration of log messages, see LogMessage class. Default is "{timestamp} {type} {msg}". - - timestampFormat defines the format of timestamps, see QDateTime::toString(). Default is "yyyy-MM-dd hh:mm:ss.zzz". + - timestampFormat defines the format of timestamps, see QDateTime::toString(). Default is "yyyy-MM-dd HH:mm:ss.zzz". - bufferSize defines the size of the buffer. Default is 0=disabled. @see set() describes how to set logger variables @@ -49,7 +51,7 @@ namespace qtwebapp { @see Logger for a descrition of the buffer. */ -class DECLSPEC FileLogger : public Logger { +class LOGGING_API FileLogger : public Logger { Q_OBJECT Q_DISABLE_COPY(FileLogger) public: diff --git a/logging/fileloggersettings.h b/logging/fileloggersettings.h index 8daee5ca1..0d5b127e4 100644 --- a/logging/fileloggersettings.h +++ b/logging/fileloggersettings.h @@ -31,7 +31,7 @@ struct FileLoggerSettings maxSize = 1000000; maxBackups = 2; msgFormat = "{timestamp} {type} {msg}"; - timestampFormat = "dd.MM.yyyy hh:mm:ss.zzz"; + timestampFormat = "yyyy-MM-dd HH:mm:ss.zzz"; minLevel = QtDebugMsg; bufferSize = 100; } diff --git a/logging/logger.cpp b/logging/logger.cpp index 8e440a3e8..fca4a307f 100644 --- a/logging/logger.cpp +++ b/logging/logger.cpp @@ -25,7 +25,7 @@ QMutex Logger::mutex; Logger::Logger(QObject* parent) : QObject(parent), msgFormat("{timestamp} {type} {msg}"), - timestampFormat("dd.MM.yyyy hh:mm:ss.zzz"), + timestampFormat("yyyy-MM-dd HH:mm:ss.zzz"), minLevel(QtDebugMsg), bufferSize(0) {} diff --git a/logging/logger.h b/logging/logger.h index 17353333a..9169691f8 100644 --- a/logging/logger.h +++ b/logging/logger.h @@ -15,6 +15,8 @@ #include "logglobal.h" #include "logmessage.h" +#include "export.h" + namespace qtwebapp { /** @@ -47,7 +49,7 @@ namespace qtwebapp { because logging to the console is less useful. */ -class DECLSPEC Logger : public QObject { +class LOGGING_API Logger : public QObject { Q_OBJECT Q_DISABLE_COPY(Logger) public: @@ -63,13 +65,13 @@ public: /** Constructor. @param msgFormat Format of the decoration, e.g. "{timestamp} {type} thread={thread}: {msg}" - @param timestampFormat Format of timestamp, e.g. "dd.MM.yyyy hh:mm:ss.zzz" + @param timestampFormat Format of timestamp, e.g. "yyyy-MM-dd HH:mm:ss.zzz" @param minLevel Minimum severity that genertes an output (0=debug, 1=warning, 2=critical, 3=fatal). @param bufferSize Size of the backtrace buffer, number of messages per thread. 0=disabled. @param parent Parent object @see LogMessage for a description of the message decoration. */ - Logger(const QString msgFormat="{timestamp} {type} {msg}", const QString timestampFormat="dd.MM.yyyy hh:mm:ss.zzz", const QtMsgType minLevel=QtDebugMsg, const int bufferSize=0, QObject* parent = 0); + Logger(const QString msgFormat="{timestamp} {type} {msg}", const QString timestampFormat="yyyy-MM-dd HH:mm:ss.zzz", const QtMsgType minLevel=QtDebugMsg, const int bufferSize=0, QObject* parent = 0); /** Destructor */ virtual ~Logger(); diff --git a/logging/loggerwithfile.h b/logging/loggerwithfile.h index 526e28df7..1de8f1b34 100644 --- a/logging/loggerwithfile.h +++ b/logging/loggerwithfile.h @@ -12,6 +12,8 @@ #include "logger.h" #include "filelogger.h" +#include "export.h" + namespace qtwebapp { /** @@ -20,7 +22,7 @@ namespace qtwebapp { @see Logger for a description of the console loger. */ -class DECLSPEC LoggerWithFile : public Logger { +class LOGGING_API LoggerWithFile : public Logger { Q_OBJECT Q_DISABLE_COPY(LoggerWithFile) diff --git a/logging/logging.pro b/logging/logging.pro index 92b2b9d90..f3016a033 100644 --- a/logging/logging.pro +++ b/logging/logging.pro @@ -10,8 +10,12 @@ TEMPLATE = lib TARGET = logging INCLUDEPATH += $$PWD +INCLUDEPATH += ../exports + QMAKE_CXXFLAGS += -std=c++11 +CONFIG(MSVC):DEFINES += logging_EXPORTS + CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug diff --git a/logging/logmessage.h b/logging/logmessage.h index c84c83b4d..5fb4920ee 100644 --- a/logging/logmessage.h +++ b/logging/logmessage.h @@ -11,6 +11,8 @@ #include #include "logglobal.h" +#include "export.h" + namespace qtwebapp { /** @@ -33,7 +35,7 @@ namespace qtwebapp { - {line} Line number where the message was generated */ -class DECLSPEC LogMessage +class LOGGING_API LogMessage { Q_DISABLE_COPY(LogMessage) public: @@ -54,7 +56,7 @@ public: Returns the log message as decorated string. @param msgFormat Format of the decoration. May contain variables and static text, e.g. "{timestamp} {type} thread={thread}: {msg}". - @param timestampFormat Format of timestamp, e.g. "dd.MM.yyyy hh:mm:ss.zzz", see QDateTime::toString(). + @param timestampFormat Format of timestamp, e.g. "yyyy-MM-dd HH:mm:ss.zzz", see QDateTime::toString(). @see QDatetime for a description of the timestamp format pattern */ QString toString(const QString& msgFormat, const QString& timestampFormat) const; diff --git a/mbelib/mbelib.pro b/mbelib/mbelib.pro index d4063254b..9fa45a02b 100644 --- a/mbelib/mbelib.pro +++ b/mbelib/mbelib.pro @@ -9,8 +9,9 @@ QT += core TEMPLATE = lib TARGET = mbelib -CONFIG(MINGW32):LIBMBELIBSRC = "D:\softs\mbelib" -CONFIG(MINGW64):LIBMBELIBSRC = "D:\softs\mbelib" +CONFIG(MINGW32):LIBMBELIBSRC = "C:\softs\mbelib" +CONFIG(MINGW64):LIBMBELIBSRC = "C:\softs\mbelib" +CONFIG(MSVC):LIBMBELIBSRC = "C:\softs\mbelib" CONFIG(macx):LIBMBELIBSRC = "../../deps/mbelib" INCLUDEPATH += $$LIBMBELIBSRC diff --git a/nanomsg/nanomsg.pro b/nanomsg/nanomsg.pro deleted file mode 100644 index c291592fd..000000000 --- a/nanomsg/nanomsg.pro +++ /dev/null @@ -1,275 +0,0 @@ -#-------------------------------------------------------- -# -# Pro file for Android and Windows builds with Qt Creator -# -#-------------------------------------------------------- - -QT += core - -TEMPLATE = lib -TARGET = nanomsg - -CONFIG(MINGW32):LIBNANOMSGSRC = "D:\softs\nanomsg-0.8-beta" -CONFIG(MINGW64):LIBNANOMSGSRC = "D:\softs\nanomsg-0.8-beta" - -CONFIG(MINGW32):DEFINES += NN_HAVE_WINDOWS=1 -CONFIG(MINGW32):DEFINES += _CRT_SECURE_NO_WARNINGS=1 -CONFIG(MINGW32):DEFINES += NN_HAVE_MINGW=1 -CONFIG(MINGW32):DEFINES += NN_HAVE_STDINT=1 -CONFIG(MINGW32):DEFINES += _WIN32_WINNT=0x0600 -CONFIG(MINGW32):DEFINES += NN_EXPORTS=1 - -CONFIG(MINGW64):DEFINES += NN_HAVE_WINDOWS=1 -CONFIG(MINGW64):DEFINES += _CRT_SECURE_NO_WARNINGS=1 -CONFIG(MINGW64):DEFINES += NN_HAVE_MINGW=1 -CONFIG(MINGW64):DEFINES += NN_HAVE_STDINT=1 -CONFIG(MINGW64):DEFINES += _WIN32_WINNT=0x0600 -CONFIG(MINGW64):DEFINES += NN_EXPORTS=1 -CONFIG(MINGW64):DEFINES += _POSIX_C_SOURCE=1 - -INCLUDEPATH += $$LIBNANOMSGSRC/src - -SOURCES = $$LIBNANOMSGSRC/src/core/ep.c\ -$$LIBNANOMSGSRC/src/core/epbase.c\ -$$LIBNANOMSGSRC/src/core/global.c\ -$$LIBNANOMSGSRC/src/core/pipe.c\ -$$LIBNANOMSGSRC/src/core/poll.c\ -$$LIBNANOMSGSRC/src/core/sock.c\ -$$LIBNANOMSGSRC/src/core/sockbase.c\ -$$LIBNANOMSGSRC/src/core/symbol.c\ -$$LIBNANOMSGSRC/src/aio/ctx.c\ -$$LIBNANOMSGSRC/src/aio/fsm.c\ -$$LIBNANOMSGSRC/src/aio/poller.c\ -$$LIBNANOMSGSRC/src/aio/pool.c\ -$$LIBNANOMSGSRC/src/aio/timer.c\ -$$LIBNANOMSGSRC/src/aio/timerset.c\ -$$LIBNANOMSGSRC/src/aio/usock.c\ -$$LIBNANOMSGSRC/src/aio/worker.c\ -$$LIBNANOMSGSRC/src/utils/alloc.c\ -$$LIBNANOMSGSRC/src/utils/atomic.c\ -$$LIBNANOMSGSRC/src/utils/chunk.c\ -$$LIBNANOMSGSRC/src/utils/chunkref.c\ -$$LIBNANOMSGSRC/src/utils/clock.c\ -$$LIBNANOMSGSRC/src/utils/closefd.c\ -$$LIBNANOMSGSRC/src/utils/efd.c\ -$$LIBNANOMSGSRC/src/utils/err.c\ -$$LIBNANOMSGSRC/src/utils/glock.c\ -$$LIBNANOMSGSRC/src/utils/hash.c\ -$$LIBNANOMSGSRC/src/utils/list.c\ -$$LIBNANOMSGSRC/src/utils/msg.c\ -$$LIBNANOMSGSRC/src/utils/mutex.c\ -$$LIBNANOMSGSRC/src/utils/queue.c\ -$$LIBNANOMSGSRC/src/utils/random.c\ -$$LIBNANOMSGSRC/src/utils/sem.c\ -$$LIBNANOMSGSRC/src/utils/sleep.c\ -$$LIBNANOMSGSRC/src/utils/thread.c\ -$$LIBNANOMSGSRC/src/utils/wire.c\ -$$LIBNANOMSGSRC/src/devices/device.c\ -$$LIBNANOMSGSRC/src/devices/tcpmuxd.c\ -$$LIBNANOMSGSRC/src/protocols/utils/dist.c\ -$$LIBNANOMSGSRC/src/protocols/utils/excl.c\ -$$LIBNANOMSGSRC/src/protocols/utils/fq.c\ -$$LIBNANOMSGSRC/src/protocols/utils/lb.c\ -$$LIBNANOMSGSRC/src/protocols/utils/priolist.c\ -$$LIBNANOMSGSRC/src/protocols/bus/bus.c\ -$$LIBNANOMSGSRC/src/protocols/bus/xbus.c\ -$$LIBNANOMSGSRC/src/protocols/pipeline/push.c\ -$$LIBNANOMSGSRC/src/protocols/pipeline/pull.c\ -$$LIBNANOMSGSRC/src/protocols/pipeline/xpull.c\ -$$LIBNANOMSGSRC/src/protocols/pipeline/xpush.c\ -$$LIBNANOMSGSRC/src/protocols/pair/pair.c\ -$$LIBNANOMSGSRC/src/protocols/pair/xpair.c\ -$$LIBNANOMSGSRC/src/protocols/pubsub/pub.c\ -$$LIBNANOMSGSRC/src/protocols/pubsub/sub.c\ -$$LIBNANOMSGSRC/src/protocols/pubsub/trie.c\ -$$LIBNANOMSGSRC/src/protocols/pubsub/xpub.c\ -$$LIBNANOMSGSRC/src/protocols/pubsub/xsub.c\ -$$LIBNANOMSGSRC/src/protocols/reqrep/req.c\ -$$LIBNANOMSGSRC/src/protocols/reqrep/rep.c\ -$$LIBNANOMSGSRC/src/protocols/reqrep/task.c\ -$$LIBNANOMSGSRC/src/protocols/reqrep/xrep.c\ -$$LIBNANOMSGSRC/src/protocols/reqrep/xreq.c\ -$$LIBNANOMSGSRC/src/protocols/survey/respondent.c\ -$$LIBNANOMSGSRC/src/protocols/survey/surveyor.c\ -$$LIBNANOMSGSRC/src/protocols/survey/xrespondent.c\ -$$LIBNANOMSGSRC/src/protocols/survey/xsurveyor.c\ -$$LIBNANOMSGSRC/src/transports/utils/backoff.c\ -$$LIBNANOMSGSRC/src/transports/utils/dns.c\ -$$LIBNANOMSGSRC/src/transports/utils/iface.c\ -$$LIBNANOMSGSRC/src/transports/utils/literal.c\ -$$LIBNANOMSGSRC/src/transports/utils/port.c\ -$$LIBNANOMSGSRC/src/transports/utils/streamhdr.c\ -$$LIBNANOMSGSRC/src/transports/utils/base64.c\ -$$LIBNANOMSGSRC/src/transports/inproc/binproc.c\ -$$LIBNANOMSGSRC/src/transports/inproc/cinproc.c\ -$$LIBNANOMSGSRC/src/transports/inproc/inproc.c\ -$$LIBNANOMSGSRC/src/transports/inproc/ins.c\ -$$LIBNANOMSGSRC/src/transports/inproc/msgqueue.c\ -$$LIBNANOMSGSRC/src/transports/inproc/sinproc.c\ -$$LIBNANOMSGSRC/src/transports/ipc/aipc.c\ -$$LIBNANOMSGSRC/src/transports/ipc/bipc.c\ -$$LIBNANOMSGSRC/src/transports/ipc/cipc.c\ -$$LIBNANOMSGSRC/src/transports/ipc/ipc.c\ -$$LIBNANOMSGSRC/src/transports/ipc/sipc.c\ -$$LIBNANOMSGSRC/src/transports/tcp/atcp.c\ -$$LIBNANOMSGSRC/src/transports/tcp/btcp.c\ -$$LIBNANOMSGSRC/src/transports/tcp/ctcp.c\ -$$LIBNANOMSGSRC/src/transports/tcp/stcp.c\ -$$LIBNANOMSGSRC/src/transports/tcp/tcp.c\ -$$LIBNANOMSGSRC/src/transports/tcpmux/atcpmux.c\ -$$LIBNANOMSGSRC/src/transports/tcpmux/btcpmux.c\ -$$LIBNANOMSGSRC/src/transports/tcpmux/ctcpmux.c\ -$$LIBNANOMSGSRC/src/transports/tcpmux/stcpmux.c\ -$$LIBNANOMSGSRC/src/transports/tcpmux/tcpmux.c\ -$$LIBNANOMSGSRC/src/transports/ws/aws.c\ -$$LIBNANOMSGSRC/src/transports/ws/bws.c\ -$$LIBNANOMSGSRC/src/transports/ws/cws.c\ -$$LIBNANOMSGSRC/src/transports/ws/sws.c\ -$$LIBNANOMSGSRC/src/transports/ws/ws.c\ -$$LIBNANOMSGSRC/src/transports/ws/ws_handshake.c\ -$$LIBNANOMSGSRC/src/transports/ws/sha1.c - -HEADERS = $$LIBNANOMSGSRC/src/nn.h\ -$$LIBNANOMSGSRC/src/inproc.h\ -$$LIBNANOMSGSRC/src/ipc.h\ -$$LIBNANOMSGSRC/src/tcp.h\ -$$LIBNANOMSGSRC/src/ws.h\ -$$LIBNANOMSGSRC/src/pair.h\ -$$LIBNANOMSGSRC/src/pubsub.h\ -$$LIBNANOMSGSRC/src/reqrep.h\ -$$LIBNANOMSGSRC/src/pipeline.h\ -$$LIBNANOMSGSRC/src/survey.h\ -$$LIBNANOMSGSRC/src/bus.h\ -$$LIBNANOMSGSRC/src/core/ep.h\ -$$LIBNANOMSGSRC/src/core/global.h\ -$$LIBNANOMSGSRC/src/core/sock.h\ -$$LIBNANOMSGSRC/src/aio/ctx.h\ -$$LIBNANOMSGSRC/src/aio/fsm.h\ -$$LIBNANOMSGSRC/src/aio/poller.h\ -$$LIBNANOMSGSRC/src/aio/poller_epoll.h\ -$$LIBNANOMSGSRC/src/aio/poller_kqueue.h\ -$$LIBNANOMSGSRC/src/aio/poller_poll.h\ -$$LIBNANOMSGSRC/src/aio/pool.h\ -$$LIBNANOMSGSRC/src/aio/timer.h\ -$$LIBNANOMSGSRC/src/aio/timerset.h\ -$$LIBNANOMSGSRC/src/aio/usock.h\ -$$LIBNANOMSGSRC/src/aio/usock_posix.h\ -$$LIBNANOMSGSRC/src/aio/usock_win.h\ -$$LIBNANOMSGSRC/src/aio/worker.h\ -$$LIBNANOMSGSRC/src/aio/worker_posix.h\ -$$LIBNANOMSGSRC/src/aio/worker_win.h\ -$$LIBNANOMSGSRC/src/utils/alloc.h\ -$$LIBNANOMSGSRC/src/utils/atomic.h\ -$$LIBNANOMSGSRC/src/utils/attr.h\ -$$LIBNANOMSGSRC/src/utils/chunk.h\ -$$LIBNANOMSGSRC/src/utils/chunkref.h\ -$$LIBNANOMSGSRC/src/utils/clock.h\ -$$LIBNANOMSGSRC/src/utils/closefd.h\ -$$LIBNANOMSGSRC/src/utils/cont.h\ -$$LIBNANOMSGSRC/src/utils/efd.h\ -$$LIBNANOMSGSRC/src/utils/efd_eventfd.h\ -$$LIBNANOMSGSRC/src/utils/efd_pipe.h\ -$$LIBNANOMSGSRC/src/utils/efd_socketpair.h\ -$$LIBNANOMSGSRC/src/utils/efd_win.h\ -$$LIBNANOMSGSRC/src/utils/err.h\ -$$LIBNANOMSGSRC/src/utils/fast.h\ -$$LIBNANOMSGSRC/src/utils/fd.h\ -$$LIBNANOMSGSRC/src/utils/glock.h\ -$$LIBNANOMSGSRC/src/utils/hash.h\ -$$LIBNANOMSGSRC/src/utils/int.h\ -$$LIBNANOMSGSRC/src/utils/list.h\ -$$LIBNANOMSGSRC/src/utils/msg.h\ -$$LIBNANOMSGSRC/src/utils/mutex.h\ -$$LIBNANOMSGSRC/src/utils/queue.h\ -$$LIBNANOMSGSRC/src/utils/random.h\ -$$LIBNANOMSGSRC/src/utils/sem.h\ -$$LIBNANOMSGSRC/src/utils/sleep.h\ -$$LIBNANOMSGSRC/src/utils/thread.h\ -$$LIBNANOMSGSRC/src/utils/thread_posix.h\ -$$LIBNANOMSGSRC/src/utils/thread_win.h\ -$$LIBNANOMSGSRC/src/utils/wire.h\ -$$LIBNANOMSGSRC/src/devices/device.h\ -$$LIBNANOMSGSRC/src/protocols/utils/dist.h\ -$$LIBNANOMSGSRC/src/protocols/utils/excl.h\ -$$LIBNANOMSGSRC/src/protocols/utils/fq.h\ -$$LIBNANOMSGSRC/src/protocols/utils/lb.h\ -$$LIBNANOMSGSRC/src/protocols/utils/priolist.h\ -$$LIBNANOMSGSRC/src/protocols/bus/bus.h\ -$$LIBNANOMSGSRC/src/protocols/bus/xbus.h\ -$$LIBNANOMSGSRC/src/protocols/pipeline/push.h\ -$$LIBNANOMSGSRC/src/protocols/pipeline/pull.h\ -$$LIBNANOMSGSRC/src/protocols/pipeline/xpull.h\ -$$LIBNANOMSGSRC/src/protocols/pipeline/xpush.h\ -$$LIBNANOMSGSRC/src/protocols/pair/pair.h\ -$$LIBNANOMSGSRC/src/protocols/pair/xpair.h\ -$$LIBNANOMSGSRC/src/protocols/pubsub/pub.h\ -$$LIBNANOMSGSRC/src/protocols/pubsub/sub.h\ -$$LIBNANOMSGSRC/src/protocols/pubsub/trie.h\ -$$LIBNANOMSGSRC/src/protocols/pubsub/xpub.h\ -$$LIBNANOMSGSRC/src/protocols/pubsub/xsub.h\ -$$LIBNANOMSGSRC/src/protocols/reqrep/req.h\ -$$LIBNANOMSGSRC/src/protocols/reqrep/rep.h\ -$$LIBNANOMSGSRC/src/protocols/reqrep/task.h\ -$$LIBNANOMSGSRC/src/protocols/reqrep/xrep.h\ -$$LIBNANOMSGSRC/src/protocols/reqrep/xreq.h\ -$$LIBNANOMSGSRC/src/protocols/survey/respondent.h\ -$$LIBNANOMSGSRC/src/protocols/survey/surveyor.h\ -$$LIBNANOMSGSRC/src/protocols/survey/xrespondent.h\ -$$LIBNANOMSGSRC/src/protocols/survey/xsurveyor.h\ -$$LIBNANOMSGSRC/src/transports/utils/backoff.h\ -$$LIBNANOMSGSRC/src/transports/utils/dns.h\ -$$LIBNANOMSGSRC/src/transports/utils/dns_getaddrinfo.h\ -$$LIBNANOMSGSRC/src/transports/utils/dns_getaddrinfo_a.h\ -$$LIBNANOMSGSRC/src/transports/utils/iface.h\ -$$LIBNANOMSGSRC/src/transports/utils/literal.h\ -$$LIBNANOMSGSRC/src/transports/utils/port.h\ -$$LIBNANOMSGSRC/src/transports/utils/streamhdr.h\ -$$LIBNANOMSGSRC/src/transports/utils/base64.h\ -$$LIBNANOMSGSRC/src/transports/inproc/binproc.h\ -$$LIBNANOMSGSRC/src/transports/inproc/cinproc.h\ -$$LIBNANOMSGSRC/src/transports/inproc/inproc.h\ -$$LIBNANOMSGSRC/src/transports/inproc/ins.h\ -$$LIBNANOMSGSRC/src/transports/inproc/msgqueue.h\ -$$LIBNANOMSGSRC/src/transports/inproc/sinproc.h\ -$$LIBNANOMSGSRC/src/transports/ipc/aipc.h\ -$$LIBNANOMSGSRC/src/transports/ipc/bipc.h\ -$$LIBNANOMSGSRC/src/transports/ipc/cipc.h\ -$$LIBNANOMSGSRC/src/transports/ipc/ipc.h\ -$$LIBNANOMSGSRC/src/transports/ipc/sipc.h\ -$$LIBNANOMSGSRC/src/transports/tcp/atcp.h\ -$$LIBNANOMSGSRC/src/transports/tcp/btcp.h\ -$$LIBNANOMSGSRC/src/transports/tcp/ctcp.h\ -$$LIBNANOMSGSRC/src/transports/tcp/stcp.h\ -$$LIBNANOMSGSRC/src/transports/tcp/tcp.h\ -$$LIBNANOMSGSRC/src/transports/tcpmux/atcpmux.h\ -$$LIBNANOMSGSRC/src/transports/tcpmux/btcpmux.h\ -$$LIBNANOMSGSRC/src/transports/tcpmux/ctcpmux.h\ -$$LIBNANOMSGSRC/src/transports/tcpmux/stcpmux.h\ -$$LIBNANOMSGSRC/src/transports/tcpmux/tcpmux.h\ -$$LIBNANOMSGSRC/src/transports/ws/aws.h\ -$$LIBNANOMSGSRC/src/transports/ws/bws.h\ -$$LIBNANOMSGSRC/src/transports/ws/cws.h\ -$$LIBNANOMSGSRC/src/transports/ws/sws.h\ -$$LIBNANOMSGSRC/src/transports/ws/ws.h\ -$$LIBNANOMSGSRC/src/transports/ws/ws_handshake.h\ -$$LIBNANOMSGSRC/src/transports/ws/sha1.h\ -$$LIBNANOMSGSRC/src/utils/win.h\ -$$LIBNANOMSGSRC/src/aio/poller_epoll.inc\ -$$LIBNANOMSGSRC/src/aio/poller_kqueue.inc\ -$$LIBNANOMSGSRC/src/aio/poller_poll.inc\ -$$LIBNANOMSGSRC/src/aio/usock_posix.inc\ -$$LIBNANOMSGSRC/src/aio/usock_win.inc\ -$$LIBNANOMSGSRC/src/aio/worker_posix.inc\ -$$LIBNANOMSGSRC/src/aio/worker_win.inc\ -$$LIBNANOMSGSRC/src/utils/efd_eventfd.inc\ -$$LIBNANOMSGSRC/src/utils/efd_pipe.inc\ -$$LIBNANOMSGSRC/src/utils/efd_socketpair.inc\ -$$LIBNANOMSGSRC/src/utils/efd_win.inc\ -$$LIBNANOMSGSRC/src/utils/thread_posix.inc\ -$$LIBNANOMSGSRC/src/utils/thread_win.inc\ -$$LIBNANOMSGSRC/src/transports/utils/dns_getaddrinfo.inc\ -$$LIBNANOMSGSRC/src/transports/utils/dns_getaddrinfo_a.inc - -#CONFIG(MINGW32):LIBS += -lws2_32 -lmswsock -ladvapi32 -CONFIG(MINGW32):LIBS += -lws2_32 -lmswsock -CONFIG(MINGW64):LIBS += -lws2_32 -lmswsock diff --git a/plugins/channelrx/CMakeLists.txt b/plugins/channelrx/CMakeLists.txt index 6f1dd1ec6..e16372740 100644 --- a/plugins/channelrx/CMakeLists.txt +++ b/plugins/channelrx/CMakeLists.txt @@ -5,21 +5,32 @@ add_subdirectory(demodam) add_subdirectory(demodbfm) add_subdirectory(demodnfm) add_subdirectory(demodssb) -add_subdirectory(tcpsrc) -add_subdirectory(udpsrc) +add_subdirectory(udpsink) add_subdirectory(demodwfm) add_subdirectory(chanalyzer) -add_subdirectory(chanalyzerng) add_subdirectory(demodatv) if(LIBDSDCC_FOUND AND LIBMBE_FOUND) add_subdirectory(demoddsd) endif(LIBDSDCC_FOUND AND LIBMBE_FOUND) -if (NOT RX_SAMPLE_24BIT) - add_subdirectory(demoddatv) +find_package(FFmpeg) +if (FFMPEG_FOUND) + # You can only get FFmpeg version from the command line + EXECUTE_PROCESS(COMMAND ffmpeg -version COMMAND grep ffmpeg COMMAND cut -d\ -f3 COMMAND tr -d '\n' OUTPUT_VARIABLE FFMPEG_VERSION) + message(STATUS "FFmpeg version ${FFMPEG_VERSION} found") + if(FFMPEG_VERSION VERSION_GREATER "3.1") + message(STATUS "Include demoddatv") + add_subdirectory(demoddatv) + endif() endif() +find_package(CM256cc) +if(CM256CC_FOUND) + add_subdirectory(daemonsink) +endif(CM256CC_FOUND) + if (BUILD_DEBIAN) add_subdirectory(demoddsd) + add_subdirectory(daemonsink) endif (BUILD_DEBIAN) diff --git a/plugins/channelrx/chanalyzer/CMakeLists.txt b/plugins/channelrx/chanalyzer/CMakeLists.txt index 1d099f8fe..a33c29a51 100644 --- a/plugins/channelrx/chanalyzer/CMakeLists.txt +++ b/plugins/channelrx/chanalyzer/CMakeLists.txt @@ -4,12 +4,14 @@ set(chanalyzer_SOURCES chanalyzer.cpp chanalyzergui.cpp chanalyzerplugin.cpp + chanalyzersettings.cpp ) set(chanalyzer_HEADERS chanalyzer.h chanalyzergui.h chanalyzerplugin.h + chanalyzersettings.h ) set(chanalyzer_FORMS @@ -41,6 +43,6 @@ target_link_libraries(chanalyzer sdrgui ) -qt5_use_modules(chanalyzer Core Widgets ) +target_link_libraries(chanalyzer Qt5::Core Qt5::Widgets) install(TARGETS chanalyzer DESTINATION lib/plugins/channelrx) diff --git a/plugins/channelrx/chanalyzer/chanalyzer.cpp b/plugins/channelrx/chanalyzer/chanalyzer.cpp index c8f80498f..3299e5ab5 100644 --- a/plugins/channelrx/chanalyzer/chanalyzer.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzer.cpp @@ -1,242 +1,392 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2015 Edouard Griffiths, F4EXB // -// // -// This program is free software; you can redistribute it and/or modify // -// it under the terms of the GNU General Public License as published by // -// the Free Software Foundation as version 3 of the License, or // -// // -// This program is distributed in the hope that it will be useful, // -// but WITHOUT ANY WARRANTY; without even the implied warranty of // -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // -// GNU General Public License V3 for more details. // -// // -// You should have received a copy of the GNU General Public License // -// along with this program. If not, see . // -/////////////////////////////////////////////////////////////////////////////////// - -#include "chanalyzer.h" - -#include -#include -#include - -#include -#include "dsp/threadedbasebandsamplesink.h" -#include "device/devicesourceapi.h" -#include "audio/audiooutput.h" - -MESSAGE_CLASS_DEFINITION(ChannelAnalyzer::MsgConfigureChannelAnalyzer, Message) -MESSAGE_CLASS_DEFINITION(ChannelAnalyzer::MsgConfigureChannelizer, Message) -MESSAGE_CLASS_DEFINITION(ChannelAnalyzer::MsgReportChannelSampleRateChanged, Message) - -const QString ChannelAnalyzer::m_channelIdURI = "org.f4exb.sdrangelove.channel.chanalyzer"; -const QString ChannelAnalyzer::m_channelId = "ChannelAnalyzer"; - -ChannelAnalyzer::ChannelAnalyzer(DeviceSourceAPI *deviceAPI) : - ChannelSinkAPI(m_channelIdURI), - m_deviceAPI(deviceAPI), - m_sampleSink(0), - m_settingsMutex(QMutex::Recursive) -{ - setObjectName(m_channelId); - - m_Bandwidth = 5000; - m_LowCutoff = 300; - m_spanLog2 = 3; - m_sampleRate = 96000; - m_frequency = 0; - m_nco.setFreq(m_frequency, m_sampleRate); - m_undersampleCount = 0; - m_sum = 0; - m_usb = true; - m_ssb = true; - m_magsq = 0; - SSBFilter = new fftfilt(m_LowCutoff / m_sampleRate, m_Bandwidth / m_sampleRate, ssbFftLen); - DSBFilter = new fftfilt(m_Bandwidth / m_sampleRate, 2*ssbFftLen); - - m_channelizer = new DownChannelizer(this); - m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); - connect(m_channelizer, SIGNAL(inputSampleRateChanged()), this, SLOT(channelSampleRateChanged())); - m_deviceAPI->addThreadedSink(m_threadedChannelizer); - m_deviceAPI->addChannelAPI(this); -} - -ChannelAnalyzer::~ChannelAnalyzer() -{ - if (SSBFilter) delete SSBFilter; - if (DSBFilter) delete DSBFilter; - m_deviceAPI->removeChannelAPI(this); - m_deviceAPI->removeThreadedSink(m_threadedChannelizer); - delete m_threadedChannelizer; - delete m_channelizer; -} - -void ChannelAnalyzer::configure(MessageQueue* messageQueue, - Real Bandwidth, - Real LowCutoff, - int spanLog2, - bool ssb) -{ - Message* cmd = MsgConfigureChannelAnalyzer::create(Bandwidth, LowCutoff, spanLog2, ssb); - messageQueue->push(cmd); -} - -void ChannelAnalyzer::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly __attribute__((unused))) -{ - fftfilt::cmplx *sideband; - int n_out; - int decim = 1<real(), it->imag()); - c *= m_nco.nextIQ(); - - if (m_ssb) - { - n_out = SSBFilter->runSSB(c, &sideband, m_usb); - } - else - { - n_out = DSBFilter->runDSB(c, &sideband); - } - - for (int i = 0; i < n_out; i++) - { - // Downsample by 2^(m_scaleLog2 - 1) for SSB band spectrum display - // smart decimation with bit gain using float arithmetic (23 bits significand) - - m_sum += sideband[i]; - - if (!(m_undersampleCount++ & decim_mask)) - { - m_sum /= decim; - Real re = m_sum.real() / SDR_RX_SCALED; - Real im = m_sum.imag() / SDR_RX_SCALED; - m_magsq = re*re + im*im; - - if (m_ssb & !m_usb) - { // invert spectrum for LSB - m_sampleBuffer.push_back(Sample(m_sum.imag(), m_sum.real())); - } - else - { - m_sampleBuffer.push_back(Sample(m_sum.real(), m_sum.imag())); - } - - m_sum = 0; - } - } - } - - if(m_sampleSink != NULL) - { - m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), m_ssb); // m_ssb = positive only - } - - m_sampleBuffer.clear(); - - m_settingsMutex.unlock(); -} - -void ChannelAnalyzer::start() -{ -} - -void ChannelAnalyzer::stop() -{ -} - -void ChannelAnalyzer::channelSampleRateChanged() -{ - MsgReportChannelSampleRateChanged *msg = MsgReportChannelSampleRateChanged::create(); - getMessageQueueToGUI()->push(msg); -} - -bool ChannelAnalyzer::handleMessage(const Message& cmd) -{ - float band, lowCutoff; - - qDebug() << "ChannelAnalyzer::handleMessage"; - - if (DownChannelizer::MsgChannelizerNotification::match(cmd)) - { - DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd; - - m_sampleRate = notif.getSampleRate(); - m_nco.setFreq(-notif.getFrequencyOffset(), m_sampleRate); - - qDebug() << "ChannelAnalyzer::handleMessage: MsgChannelizerNotification: m_sampleRate: " << m_sampleRate - << " frequencyOffset: " << notif.getFrequencyOffset(); - - return true; - } - else if (MsgConfigureChannelizer::match(cmd)) - { - MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; - - m_channelizer->configure(m_channelizer->getInputMessageQueue(), - m_channelizer->getInputSampleRate(), - cfg.getCenterFrequency()); - - return true; - } - else if (MsgConfigureChannelAnalyzer::match(cmd)) - { - MsgConfigureChannelAnalyzer& cfg = (MsgConfigureChannelAnalyzer&) cmd; - - band = cfg.getBandwidth(); - lowCutoff = cfg.getLoCutoff(); - - if (band < 0) - { - band = -band; - lowCutoff = -lowCutoff; - m_usb = false; - } - else - { - m_usb = true; - } - - if (band < 100.0f) - { - band = 100.0f; - lowCutoff = 0; - } - - m_settingsMutex.lock(); - - m_Bandwidth = band; - m_LowCutoff = lowCutoff; - - SSBFilter->create_filter(m_LowCutoff / m_sampleRate, m_Bandwidth / m_sampleRate); - DSBFilter->create_dsb_filter(m_Bandwidth / m_sampleRate); - - m_spanLog2 = cfg.getSpanLog2(); - m_ssb = cfg.getSSB(); - - m_settingsMutex.unlock(); - - qDebug() << "ChannelAnalyzer::handleMessage: MsgConfigureChannelAnalyzer: m_Bandwidth: " << m_Bandwidth - << " m_LowCutoff: " << m_LowCutoff - << " m_spanLog2: " << m_spanLog2 - << " m_ssb: " << m_ssb; - - return true; - } - else - { - if (m_sampleSink != 0) - { - return m_sampleSink->handleMessage(cmd); - } - else - { - return false; - } - } -} +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#include "device/devicesourceapi.h" +#include "audio/audiooutput.h" +#include "dsp/threadedbasebandsamplesink.h" +#include "dsp/downchannelizer.h" +#include "chanalyzer.h" + +MESSAGE_CLASS_DEFINITION(ChannelAnalyzer::MsgConfigureChannelAnalyzer, Message) +MESSAGE_CLASS_DEFINITION(ChannelAnalyzer::MsgConfigureChannelAnalyzerOld, Message) +MESSAGE_CLASS_DEFINITION(ChannelAnalyzer::MsgConfigureChannelizer, Message) +MESSAGE_CLASS_DEFINITION(ChannelAnalyzer::MsgReportChannelSampleRateChanged, Message) + +const QString ChannelAnalyzer::m_channelIdURI = "sdrangel.channel.chanalyzer"; +const QString ChannelAnalyzer::m_channelId = "ChannelAnalyzer"; + +ChannelAnalyzer::ChannelAnalyzer(DeviceSourceAPI *deviceAPI) : + ChannelSinkAPI(m_channelIdURI), + m_deviceAPI(deviceAPI), + m_sampleSink(0), + m_settingsMutex(QMutex::Recursive) +{ + setObjectName(m_channelId); + + m_undersampleCount = 0; + m_sum = 0; + m_usb = true; + m_magsq = 0; + m_useInterpolator = false; + m_interpolatorDistance = 1.0f; + m_interpolatorDistanceRemain = 0.0f; + m_inputSampleRate = 48000; + m_inputFrequencyOffset = 0; + SSBFilter = new fftfilt(m_settings.m_lowCutoff / m_inputSampleRate, m_settings.m_bandwidth / m_inputSampleRate, ssbFftLen); + DSBFilter = new fftfilt(m_settings.m_bandwidth / m_inputSampleRate, 2*ssbFftLen); + RRCFilter = new fftfilt(m_settings.m_bandwidth / m_inputSampleRate, 2*ssbFftLen); + m_corr = new fftcorr(8*ssbFftLen); // 8k for 4k effective samples + m_pll.computeCoefficients(0.002f, 0.5f, 10.0f); // bandwidth, damping factor, loop gain + + applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); + + m_channelizer = new DownChannelizer(this); + m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); + m_deviceAPI->addThreadedSink(m_threadedChannelizer); + m_deviceAPI->addChannelAPI(this); +} + +ChannelAnalyzer::~ChannelAnalyzer() +{ + m_deviceAPI->removeChannelAPI(this); + m_deviceAPI->removeThreadedSink(m_threadedChannelizer); + delete m_threadedChannelizer; + delete m_channelizer; + delete SSBFilter; + delete DSBFilter; + delete RRCFilter; +} + +void ChannelAnalyzer::configure(MessageQueue* messageQueue, + int channelSampleRate, + Real Bandwidth, + Real LowCutoff, + int spanLog2, + bool ssb, + bool pll, + bool fll, + unsigned int pllPskOrder) +{ + Message* cmd = MsgConfigureChannelAnalyzerOld::create(channelSampleRate, Bandwidth, LowCutoff, spanLog2, ssb, pll, fll, pllPskOrder); + messageQueue->push(cmd); +} + +void ChannelAnalyzer::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly __attribute__((unused))) +{ + fftfilt::cmplx *sideband = 0; + Complex ci; + + m_settingsMutex.lock(); + + for(SampleVector::const_iterator it = begin; it < end; ++it) + { + Complex c(it->real(), it->imag()); + c *= m_nco.nextIQ(); + + if (m_useInterpolator) + { + if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci)) + { + processOneSample(ci, sideband); + m_interpolatorDistanceRemain += m_interpolatorDistance; + } + } + else + { + processOneSample(c, sideband); + } + } + + if(m_sampleSink != 0) + { + m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), m_settings.m_ssb); // m_ssb = positive only + } + + m_sampleBuffer.clear(); + + m_settingsMutex.unlock(); +} + +void ChannelAnalyzer::processOneSample(Complex& c, fftfilt::cmplx *sideband) +{ + int n_out; + int decim = 1<runSSB(c, &sideband, m_usb); + } + else + { + if (m_settings.m_rrc) { + n_out = RRCFilter->runFilt(c, &sideband); + } else { + n_out = DSBFilter->runDSB(c, &sideband); + } + } + + for (int i = 0; i < n_out; i++) + { + // Downsample by 2^(m_scaleLog2 - 1) for SSB band spectrum display + // smart decimation with bit gain using float arithmetic (23 bits significand) + + m_sum += sideband[i]; + + if (!(m_undersampleCount++ & (decim - 1))) // counter LSB bit mask for decimation by 2^(m_scaleLog2 - 1) + { + m_sum /= decim; + Real re = m_sum.real() / SDR_RX_SCALEF; + Real im = m_sum.imag() / SDR_RX_SCALEF; + m_magsq = re*re + im*im; + m_channelPowerAvg(m_magsq); + std::complex mix; + + if (m_settings.m_pll) + { + if (m_settings.m_fll) + { + m_fll.feed(re, im); + // Use -fPLL to mix (exchange PLL real and image in the complex multiplication) + mix = m_sum * std::conj(m_fll.getComplex()); + } + else + { + m_pll.feed(re, im); + // Use -fPLL to mix (exchange PLL real and image in the complex multiplication) + mix = m_sum * std::conj(m_pll.getComplex()); + } + } + + feedOneSample(m_settings.m_pll ? mix : m_sum, m_settings.m_fll ? m_fll.getComplex() : m_pll.getComplex()); + m_sum = 0; + } + } +} + +void ChannelAnalyzer::start() +{ + applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); +} + +void ChannelAnalyzer::stop() +{ +} + +bool ChannelAnalyzer::handleMessage(const Message& cmd) +{ + if (DownChannelizer::MsgChannelizerNotification::match(cmd)) + { + DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd; + qDebug() << "ChannelAnalyzer::handleMessage: DownChannelizer::MsgChannelizerNotification:" + << " sampleRate: " << notif.getSampleRate() + << " frequencyOffset: " << notif.getFrequencyOffset(); + + applyChannelSettings(notif.getSampleRate(), notif.getFrequencyOffset()); + + if (getMessageQueueToGUI()) + { + MsgReportChannelSampleRateChanged *msg = MsgReportChannelSampleRateChanged::create(); + getMessageQueueToGUI()->push(msg); + } + + return true; + } + else if (MsgConfigureChannelizer::match(cmd)) + { + MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; + qDebug() << "ChannelAnalyzer::handleMessage: MsgConfigureChannelizer:" + << " sampleRate: " << cfg.getSampleRate() + << " centerFrequency: " << cfg.getCenterFrequency(); + + m_channelizer->configure(m_channelizer->getInputMessageQueue(), + cfg.getSampleRate(), + cfg.getCenterFrequency()); + + return true; + } + else if (MsgConfigureChannelAnalyzer::match(cmd)) + { + qDebug("ChannelAnalyzer::handleMessage: MsgConfigureChannelAnalyzer"); + MsgConfigureChannelAnalyzer& cfg = (MsgConfigureChannelAnalyzer&) cmd; + + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } + else + { + if (m_sampleSink != 0) + { + return m_sampleSink->handleMessage(cmd); + } + else + { + return false; + } + } +} + +void ChannelAnalyzer::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force) +{ + qDebug() << "ChannelAnalyzer::applyChannelSettings:" + << " inputSampleRate: " << inputSampleRate + << " inputFrequencyOffset: " << inputFrequencyOffset; + + if ((m_inputFrequencyOffset != inputFrequencyOffset) || + (m_inputSampleRate != inputSampleRate) || force) + { + m_nco.setFreq(-inputFrequencyOffset, inputSampleRate); + } + + if ((m_inputSampleRate != inputSampleRate) || force) + { + m_settingsMutex.lock(); + + m_interpolator.create(16, inputSampleRate, inputSampleRate / 2.2f); + m_interpolatorDistanceRemain = 0; + m_interpolatorDistance = (Real) inputSampleRate / (Real) m_settings.m_downSampleRate; + + if (!m_settings.m_downSample) + { + setFilters(inputSampleRate, m_settings.m_bandwidth, m_settings.m_lowCutoff); + m_pll.setSampleRate(inputSampleRate / (1<create_filter(lowCutoff / sampleRate, bandwidth / sampleRate); + DSBFilter->create_dsb_filter(bandwidth / sampleRate); + RRCFilter->create_rrc_filter(bandwidth / sampleRate, m_settings.m_rrcRolloff / 100.0); +} + +void ChannelAnalyzer::applySettings(const ChannelAnalyzerSettings& settings, bool force) +{ + qDebug() << "ChannelAnalyzer::applySettings:" + << " m_downSample: " << settings.m_downSample + << " m_downSampleRate: " << settings.m_downSampleRate + << " m_rcc: " << settings.m_rrc + << " m_rrcRolloff: " << settings.m_rrcRolloff / 100.0 + << " m_bandwidth: " << settings.m_bandwidth + << " m_lowCutoff: " << settings.m_lowCutoff + << " m_spanLog2: " << settings.m_spanLog2 + << " m_ssb: " << settings.m_ssb + << " m_pll: " << settings.m_pll + << " m_fll: " << settings.m_fll + << " m_pllPskOrder: " << settings.m_pllPskOrder + << " m_inputType: " << (int) settings.m_inputType; + + if ((settings.m_downSampleRate != m_settings.m_downSampleRate) || force) + { + m_settingsMutex.lock(); + m_interpolator.create(16, m_inputSampleRate, m_inputSampleRate / 2.2); + m_interpolatorDistanceRemain = 0.0f; + m_interpolatorDistance = (Real) m_inputSampleRate / (Real) settings.m_downSampleRate; + m_settingsMutex.unlock(); + } + + if ((settings.m_downSample != m_settings.m_downSample) || force) + { + int sampleRate = settings.m_downSample ? settings.m_downSampleRate : m_inputSampleRate; + + m_settingsMutex.lock(); + m_useInterpolator = settings.m_downSample; + setFilters(sampleRate, settings.m_bandwidth, settings.m_lowCutoff); + m_pll.setSampleRate(sampleRate / (1<create_rrc_filter(settings.m_bandwidth / sampleRate, settings.m_rrcRolloff / 100.0); + m_settingsMutex.unlock(); + } + + if ((settings.m_spanLog2 != m_settings.m_spanLog2) || force) + { + int sampleRate = (settings.m_downSample ? settings.m_downSampleRate : m_inputSampleRate) / (1<. // -/////////////////////////////////////////////////////////////////////////////////// - -#ifndef INCLUDE_CHANALYZER_H -#define INCLUDE_CHANALYZER_H - -#include -#include - -#include "dsp/basebandsamplesink.h" -#include "channel/channelsinkapi.h" -#include "dsp/ncof.h" -#include "dsp/fftfilt.h" -#include "audio/audiofifo.h" -#include "util/message.h" - -#define ssbFftLen 1024 - -class DeviceSourceAPI; -class ThreadedBasebandSampleSink; -class DownChannelizer; - -class ChannelAnalyzer : public BasebandSampleSink, public ChannelSinkAPI { -public: - class MsgConfigureChannelAnalyzer : public Message { - MESSAGE_CLASS_DECLARATION - - public: - Real getBandwidth() const { return m_Bandwidth; } - Real getLoCutoff() const { return m_LowCutoff; } - int getSpanLog2() const { return m_spanLog2; } - bool getSSB() const { return m_ssb; } - - static MsgConfigureChannelAnalyzer* create(Real Bandwidth, - Real LowCutoff, - int spanLog2, - bool ssb) - { - return new MsgConfigureChannelAnalyzer(Bandwidth, LowCutoff, spanLog2, ssb); - } - - private: - Real m_Bandwidth; - Real m_LowCutoff; - int m_spanLog2; - bool m_ssb; - - MsgConfigureChannelAnalyzer(Real Bandwidth, - Real LowCutoff, - int spanLog2, - bool ssb) : - Message(), - m_Bandwidth(Bandwidth), - m_LowCutoff(LowCutoff), - m_spanLog2(spanLog2), - m_ssb(ssb) - { } - }; - - class MsgConfigureChannelizer : public Message { - MESSAGE_CLASS_DECLARATION - - public: - int getCenterFrequency() const { return m_centerFrequency; } - - static MsgConfigureChannelizer* create(int centerFrequency) - { - return new MsgConfigureChannelizer(centerFrequency); - } - - private: - int m_centerFrequency; - - MsgConfigureChannelizer(int centerFrequency) : - Message(), - m_centerFrequency(centerFrequency) - { } - }; - - class MsgReportChannelSampleRateChanged : public Message { - MESSAGE_CLASS_DECLARATION - - public: - - static MsgReportChannelSampleRateChanged* create() - { - return new MsgReportChannelSampleRateChanged(); - } - - private: - - MsgReportChannelSampleRateChanged() : - Message() - { } - }; - - ChannelAnalyzer(DeviceSourceAPI *deviceAPI); - virtual ~ChannelAnalyzer(); - virtual void destroy() { delete this; } - void setSampleSink(BasebandSampleSink* sampleSink) { m_sampleSink = sampleSink; } - - void configure(MessageQueue* messageQueue, - Real Bandwidth, - Real LowCutoff, - int spanLog2, - bool ssb); - - int getSampleRate() const { return m_sampleRate; } - Real getMagSq() const { return m_magsq; } - - virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); - virtual void start(); - virtual void stop(); - virtual bool handleMessage(const Message& cmd); - - virtual void getIdentifier(QString& id) { id = objectName(); } - virtual void getTitle(QString& title) { title = objectName(); } - virtual qint64 getCenterFrequency() const { return m_frequency; } - - virtual QByteArray serialize() const { return QByteArray(); } - virtual bool deserialize(const QByteArray& data __attribute__((unused))) { return false; } - - static const QString m_channelIdURI; - static const QString m_channelId; - -private slots: - void channelSampleRateChanged(); - -private: - DeviceSourceAPI *m_deviceAPI; - ThreadedBasebandSampleSink* m_threadedChannelizer; - DownChannelizer* m_channelizer; - - Real m_Bandwidth; - Real m_LowCutoff; - int m_spanLog2; - int m_undersampleCount; - fftfilt::cmplx m_sum; - int m_sampleRate; - int m_frequency; - bool m_usb; - bool m_ssb; - Real m_magsq; - - NCOF m_nco; - fftfilt* SSBFilter; - fftfilt* DSBFilter; - - BasebandSampleSink* m_sampleSink; - SampleVector m_sampleBuffer; - QMutex m_settingsMutex; -}; - -#endif // INCLUDE_CHANALYZER_H +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_CHANALYZERNG_H +#define INCLUDE_CHANALYZERNG_H + +#include +#include + +#include "dsp/basebandsamplesink.h" +#include "channel/channelsinkapi.h" +#include "dsp/interpolator.h" +#include "dsp/ncof.h" +#include "dsp/fftcorr.h" +#include "dsp/fftfilt.h" +#include "dsp/phaselockcomplex.h" +#include "dsp/freqlockcomplex.h" +#include "audio/audiofifo.h" +#include "util/message.h" +#include "util/movingaverage.h" + +#include "chanalyzersettings.h" + +#define ssbFftLen 1024 + +class DeviceSourceAPI; +class ThreadedBasebandSampleSink; +class DownChannelizer; + +class ChannelAnalyzer : public BasebandSampleSink, public ChannelSinkAPI { +public: + class MsgConfigureChannelAnalyzer : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const ChannelAnalyzerSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureChannelAnalyzer* create(const ChannelAnalyzerSettings& settings, bool force) + { + return new MsgConfigureChannelAnalyzer(settings, force); + } + + private: + ChannelAnalyzerSettings m_settings; + bool m_force; + + MsgConfigureChannelAnalyzer(const ChannelAnalyzerSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgConfigureChannelAnalyzerOld : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getChannelSampleRate() const { return m_channelSampleRate; } + Real getBandwidth() const { return m_Bandwidth; } + Real getLoCutoff() const { return m_LowCutoff; } + int getSpanLog2() const { return m_spanLog2; } + bool getSSB() const { return m_ssb; } + bool getPLL() const { return m_pll; } + bool getFLL() const { return m_fll; } + unsigned int getPLLPSKOrder() const { return m_pllPskOrder; } + + static MsgConfigureChannelAnalyzerOld* create( + int channelSampleRate, + Real Bandwidth, + Real LowCutoff, + int spanLog2, + bool ssb, + bool pll, + bool fll, + unsigned int pllPskOrder) + { + return new MsgConfigureChannelAnalyzerOld( + channelSampleRate, + Bandwidth, + LowCutoff, + spanLog2, + ssb, + pll, + fll, + pllPskOrder); + } + + private: + int m_channelSampleRate; + Real m_Bandwidth; + Real m_LowCutoff; + int m_spanLog2; + bool m_ssb; + bool m_pll; + bool m_fll; + unsigned int m_pllPskOrder; + + MsgConfigureChannelAnalyzerOld( + int channelSampleRate, + Real Bandwidth, + Real LowCutoff, + int spanLog2, + bool ssb, + bool pll, + bool fll, + unsigned int pllPskOrder) : + Message(), + m_channelSampleRate(channelSampleRate), + m_Bandwidth(Bandwidth), + m_LowCutoff(LowCutoff), + m_spanLog2(spanLog2), + m_ssb(ssb), + m_pll(pll), + m_fll(fll), + m_pllPskOrder(pllPskOrder) + { } + }; + + class MsgConfigureChannelizer : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getSampleRate() const { return m_sampleRate; } + int getCenterFrequency() const { return m_centerFrequency; } + + static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency) + { + return new MsgConfigureChannelizer(sampleRate, centerFrequency); + } + + private: + int m_sampleRate; + int m_centerFrequency; + + MsgConfigureChannelizer(int sampleRate, int centerFrequency) : + Message(), + m_sampleRate(sampleRate), + m_centerFrequency(centerFrequency) + { } + }; + + class MsgReportChannelSampleRateChanged : public Message { + MESSAGE_CLASS_DECLARATION + + public: + + static MsgReportChannelSampleRateChanged* create() + { + return new MsgReportChannelSampleRateChanged(); + } + + private: + + MsgReportChannelSampleRateChanged() : + Message() + { } + }; + + ChannelAnalyzer(DeviceSourceAPI *deviceAPI); + virtual ~ChannelAnalyzer(); + virtual void destroy() { delete this; } + void setSampleSink(BasebandSampleSink* sampleSink) { m_sampleSink = sampleSink; } + + void configure(MessageQueue* messageQueue, + int channelSampleRate, + Real Bandwidth, + Real LowCutoff, + int spanLog2, + bool ssb, + bool pll, + bool fll, + unsigned int pllPskOrder); + + DownChannelizer *getChannelizer() { return m_channelizer; } + int getInputSampleRate() const { return m_inputSampleRate; } + int getChannelSampleRate() const { return m_settings.m_downSample ? m_settings.m_downSampleRate : m_inputSampleRate; } + double getMagSq() const { return m_magsq; } + double getMagSqAvg() const { return (double) m_channelPowerAvg; } + bool isPllLocked() const { return m_settings.m_pll && m_pll.locked(); } + Real getPllFrequency() const { return m_pll.getFreq(); } + Real getPllDeltaPhase() const { return m_pll.getDeltaPhi(); } + Real getPllPhase() const { return m_pll.getPhiHat(); } + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); + virtual void start(); + virtual void stop(); + virtual bool handleMessage(const Message& cmd); + + virtual void getIdentifier(QString& id) { id = objectName(); } + virtual void getTitle(QString& title) { title = objectName(); } + virtual qint64 getCenterFrequency() const { return m_settings.m_frequency; } + + virtual QByteArray serialize() const { return QByteArray(); } + virtual bool deserialize(const QByteArray& data __attribute__((unused))) { return false; } + + static const QString m_channelIdURI; + static const QString m_channelId; + +private: + DeviceSourceAPI *m_deviceAPI; + ThreadedBasebandSampleSink* m_threadedChannelizer; + DownChannelizer* m_channelizer; + ChannelAnalyzerSettings m_settings; + + int m_inputSampleRate; + int m_inputFrequencyOffset; + int m_undersampleCount; + fftfilt::cmplx m_sum; + bool m_usb; + double m_magsq; + bool m_useInterpolator; + + NCOF m_nco; + PhaseLockComplex m_pll; + FreqLockComplex m_fll; + Interpolator m_interpolator; + Real m_interpolatorDistance; + Real m_interpolatorDistanceRemain; + + fftfilt* SSBFilter; + fftfilt* DSBFilter; + fftfilt* RRCFilter; + fftcorr* m_corr; + + BasebandSampleSink* m_sampleSink; + SampleVector m_sampleBuffer; + MovingAverageUtil m_channelPowerAvg; + QMutex m_settingsMutex; + +// void apply(bool force = false); + void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); + void applySettings(const ChannelAnalyzerSettings& settings, bool force = false); + void setFilters(int sampleRate, float bandwidth, float lowCutoff); + void processOneSample(Complex& c, fftfilt::cmplx *sideband); + + inline void feedOneSample(const fftfilt::cmplx& s, const fftfilt::cmplx& pll) + { + switch (m_settings.m_inputType) + { + case ChannelAnalyzerSettings::InputPLL: + { + if (m_settings.m_ssb & !m_usb) { // invert spectrum for LSB + m_sampleBuffer.push_back(Sample(pll.imag()*SDR_RX_SCALEF, pll.real()*SDR_RX_SCALEF)); + } else { + m_sampleBuffer.push_back(Sample(pll.real()*SDR_RX_SCALEF, pll.imag()*SDR_RX_SCALEF)); + } + } + break; + case ChannelAnalyzerSettings::InputAutoCorr: + { + std::complex a = m_corr->run(s/(SDR_RX_SCALEF/768.0f), 0); + + if (m_settings.m_ssb & !m_usb) { // invert spectrum for LSB + m_sampleBuffer.push_back(Sample(a.imag(), a.real())); + } else { + m_sampleBuffer.push_back(Sample(a.real(), a.imag())); + } + } + break; + case ChannelAnalyzerSettings::InputSignal: + default: + { + if (m_settings.m_ssb & !m_usb) { // invert spectrum for LSB + m_sampleBuffer.push_back(Sample(s.imag(), s.real())); + } else { + m_sampleBuffer.push_back(Sample(s.real(), s.imag())); + } + } + break; + } + } +}; + +#endif // INCLUDE_CHANALYZERNG_H diff --git a/plugins/channelrx/chanalyzer/chanalyzer.pro b/plugins/channelrx/chanalyzer/chanalyzer.pro index 67a4d5588..e2209802a 100644 --- a/plugins/channelrx/chanalyzer/chanalyzer.pro +++ b/plugins/channelrx/chanalyzer/chanalyzer.pro @@ -18,24 +18,28 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui CONFIG(ANDROID):INCLUDEPATH += /opt/softs/boost_1_60_0 -CONFIG(MINGW32):INCLUDEPATH += "D:\boost_1_58_0" -CONFIG(MINGW64):INCLUDEPATH += "D:\boost_1_58_0" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\boost_1_66_0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\boost_1_66_0" CONFIG(macx):INCLUDEPATH += "../../../../../boost_1_64_0" CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug SOURCES += chanalyzer.cpp\ - chanalyzergui.cpp\ - chanalyzerplugin.cpp + chanalyzergui.cpp\ + chanalyzerplugin.cpp\ + chanalyzersettings.cpp\ HEADERS += chanalyzer.h\ -chanalyzergui.h\ -chanalyzerplugin.h + chanalyzergui.h\ + chanalyzerplugin.h\ + chanalyzerplugin.h + FORMS += chanalyzergui.ui diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.cpp b/plugins/channelrx/chanalyzer/chanalyzergui.cpp index 8a966b09c..4a21d237b 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzergui.cpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2015 Edouard Griffiths, F4EXB // +// Copyright (C) 2017 Edouard Griffiths, F4EXB // // // // This program is free software; you can redistribute it and/or modify // // it under the terms of the GNU General Public License as published by // @@ -14,8 +14,6 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "chanalyzergui.h" - #include #include "device/deviceuiset.h" #include @@ -26,9 +24,9 @@ #include "ui_chanalyzergui.h" #include "dsp/spectrumscopecombovis.h" #include "dsp/spectrumvis.h" -#include "dsp/scopevis.h" #include "gui/glspectrum.h" #include "gui/glscope.h" +#include "gui/basicchannelsettingsdialog.h" #include "plugin/pluginapi.h" #include "util/simpleserializer.h" #include "util/db.h" @@ -36,10 +34,11 @@ #include "mainwindow.h" #include "chanalyzer.h" +#include "chanalyzergui.h" ChannelAnalyzerGUI* ChannelAnalyzerGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) { - ChannelAnalyzerGUI* gui = new ChannelAnalyzerGUI(pluginAPI, deviceUISet, rxChannel); + ChannelAnalyzerGUI* gui = new ChannelAnalyzerGUI(pluginAPI, deviceUISet, rxChannel); return gui; } @@ -66,103 +65,136 @@ qint64 ChannelAnalyzerGUI::getCenterFrequency() const void ChannelAnalyzerGUI::setCenterFrequency(qint64 centerFrequency) { m_channelMarker.setCenterFrequency(centerFrequency); + m_settings.m_frequency = m_channelMarker.getCenterFrequency(); applySettings(); } void ChannelAnalyzerGUI::resetToDefaults() { - blockApplySettings(true); + m_settings.resetToDefaults(); +} - ui->BW->setValue(30); - ui->deltaFrequency->setValue(0); - ui->spanLog2->setValue(3); +void ChannelAnalyzerGUI::displaySettings() +{ + m_channelMarker.blockSignals(true); + m_channelMarker.setCenterFrequency(m_settings.m_frequency); + m_channelMarker.setBandwidth(m_settings.m_bandwidth * 2); + m_channelMarker.setTitle(m_settings.m_title); + m_channelMarker.setLowCutoff(m_settings.m_lowCutoff); - blockApplySettings(false); - applySettings(); + if (m_settings.m_ssb) + { + if (m_settings.m_bandwidth < 0) { + m_channelMarker.setSidebands(ChannelMarker::lsb); + } else { + m_channelMarker.setSidebands(ChannelMarker::usb); + } + } + else + { + m_channelMarker.setSidebands(ChannelMarker::dsb); + } + + m_channelMarker.blockSignals(false); + m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only + + setTitleColor(m_settings.m_rgbColor); + setWindowTitle(m_channelMarker.getTitle()); + + blockApplySettings(true); + + ui->useRationalDownsampler->setChecked(m_settings.m_downSample); + ui->channelSampleRate->setValue(m_settings.m_downSampleRate); + setNewFinalRate(); + if (m_settings.m_ssb) { + ui->BWLabel->setText("LP"); + } else { + ui->BWLabel->setText("BP"); + } + ui->ssb->setChecked(m_settings.m_ssb); + ui->BW->setValue(m_settings.m_bandwidth/100); + ui->lowCut->setValue(m_settings.m_lowCutoff/100); + ui->deltaFrequency->setValue(m_settings.m_frequency); + ui->spanLog2->setCurrentIndex(m_settings.m_spanLog2); + displayPLLSettings(); + ui->signalSelect->setCurrentIndex((int) m_settings.m_inputType); + ui->rrcFilter->setChecked(m_settings.m_rrc); + QString rolloffStr = QString::number(m_settings.m_rrcRolloff/100.0, 'f', 2); + ui->rrcRolloffText->setText(rolloffStr); + + blockApplySettings(false); +} + +void ChannelAnalyzerGUI::displayPLLSettings() +{ + if (m_settings.m_fll) + { + ui->pllPskOrder->setCurrentIndex(5); + } + else + { + int i = 0; + for(; ((m_settings.m_pllPskOrder>>i) & 1) == 0; i++); + ui->pllPskOrder->setCurrentIndex(i); + } + + ui->pll->setChecked(m_settings.m_pll); +} + +void ChannelAnalyzerGUI::setSpectrumDisplay() +{ + qDebug("ChannelAnalyzerGUI::setSpectrumDisplay: m_rate: %d", m_rate); + if (m_settings.m_ssb) + { + ui->glSpectrum->setCenterFrequency(m_rate/4); + ui->glSpectrum->setSampleRate(m_rate/2); + ui->glSpectrum->setSsbSpectrum(true); + ui->glSpectrum->setLsbDisplay(ui->BW->value() < 0); + } + else + { + ui->glSpectrum->setCenterFrequency(0); + ui->glSpectrum->setSampleRate(m_rate); + ui->glSpectrum->setSsbSpectrum(false); + ui->glSpectrum->setLsbDisplay(false); + } } QByteArray ChannelAnalyzerGUI::serialize() const { - SimpleSerializer s(1); - s.writeS32(1, m_channelMarker.getCenterFrequency()); - s.writeS32(2, ui->BW->value()); - s.writeBlob(3, ui->spectrumGUI->serialize()); - s.writeU32(4, m_channelMarker.getColor().rgb()); - s.writeS32(5, ui->lowCut->value()); - s.writeS32(6, ui->spanLog2->value()); - s.writeBool(7, ui->ssb->isChecked()); - s.writeBlob(8, ui->scopeGUI->serialize()); - - return s.final(); + return m_settings.serialize(); } bool ChannelAnalyzerGUI::deserialize(const QByteArray& data) { - SimpleDeserializer d(data); - - if(!d.isValid()) + if(m_settings.deserialize(data)) { - resetToDefaults(); - return false; - } - - if(d.getVersion() == 1) - { - QByteArray bytetmp; - quint32 u32tmp; - qint32 tmp, bw, lowCut; - bool tmpBool; - - blockApplySettings(true); - m_channelMarker.blockSignals(true); - - d.readS32(1, &tmp, 0); - m_channelMarker.setCenterFrequency(tmp); - d.readS32(2, &bw, 30); - ui->BW->setValue(bw); - d.readBlob(3, &bytetmp); - ui->spectrumGUI->deserialize(bytetmp); - - if(d.readU32(4, &u32tmp)) - { - m_channelMarker.setColor(u32tmp); - } - - d.readS32(5, &lowCut, 3); - ui->lowCut->setValue(lowCut); - d.readS32(6, &tmp, 20); - ui->spanLog2->setValue(tmp); - setNewRate(tmp); - d.readBool(7, &tmpBool, false); - ui->ssb->setChecked(tmpBool); - d.readBlob(8, &bytetmp); - ui->scopeGUI->deserialize(bytetmp); - - blockApplySettings(false); - m_channelMarker.blockSignals(false); - m_channelMarker.emitChangedByAPI(); - - ui->BW->setValue(bw); - ui->lowCut->setValue(lowCut); // does applySettings(); - - return true; - } + displaySettings(); + applySettings(true); // will have true + return true; + } else { - resetToDefaults(); - return false; - } + m_settings.resetToDefaults(); + displaySettings(); + applySettings(true); // will have true + return false; + } } bool ChannelAnalyzerGUI::handleMessage(const Message& message) { if (ChannelAnalyzer::MsgReportChannelSampleRateChanged::match(message)) { - setNewRate(m_spanLog2); + qDebug() << "ChannelAnalyzerGUI::handleMessage: MsgReportChannelSampleRateChanged"; + ui->channelSampleRate->setValueRange(7, 2000U, m_channelAnalyzer->getInputSampleRate()); + ui->channelSampleRate->setValue(m_settings.m_downSampleRate); + setNewFinalRate(); + return true; } - return false; + return false; } void ChannelAnalyzerGUI::handleInputMessages() @@ -182,10 +214,8 @@ void ChannelAnalyzerGUI::handleInputMessages() void ChannelAnalyzerGUI::channelMarkerChangedByCursor() { - ui->deltaFrequency->setValue(abs(m_channelMarker.getCenterFrequency())); - ui->deltaMinus->setChecked(m_channelMarker.getCenterFrequency() < 0); - - applySettings(); + ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); + applySettings(); } void ChannelAnalyzerGUI::channelMarkerHighlightedByCursor() @@ -195,132 +225,150 @@ void ChannelAnalyzerGUI::channelMarkerHighlightedByCursor() void ChannelAnalyzerGUI::tick() { - Real powDb = CalcDb::dbPower(m_channelAnalyzer->getMagSq()); - m_channelPowerDbAvg(powDb); - ui->channelPower->setText(QString::number((Real) m_channelPowerDbAvg, 'f', 1)); -} + m_channelPowerAvg(m_channelAnalyzer->getMagSqAvg()); + double powDb = CalcDb::dbPower((double) m_channelPowerAvg); + ui->channelPower->setText(tr("%1 dB").arg(powDb, 0, 'f', 1)); -void ChannelAnalyzerGUI::on_deltaMinus_toggled(bool minus) -{ - int deltaFrequency = m_channelMarker.getCenterFrequency(); - bool minusDelta = (deltaFrequency < 0); - - if (minus ^ minusDelta) // sign change - { - m_channelMarker.setCenterFrequency(-deltaFrequency); - } -} - -void ChannelAnalyzerGUI::on_deltaFrequency_changed(quint64 value) -{ - if (ui->deltaMinus->isChecked()) { - m_channelMarker.setCenterFrequency(-value); + if (m_channelAnalyzer->isPllLocked()) { + ui->pll->setStyleSheet("QToolButton { background-color : green; }"); } else { - m_channelMarker.setCenterFrequency(value); + ui->pll->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); } + if (ui->pll->isChecked()) + { + int freq = (m_channelAnalyzer->getPllFrequency() * m_channelAnalyzer->getChannelSampleRate()) / (2.0*M_PI); + ui->pll->setToolTip(tr("PLL lock. Freq = %1 Hz").arg(freq)); + } +} + +void ChannelAnalyzerGUI::on_channelSampleRate_changed(quint64 value) +{ + m_settings.m_downSampleRate = value; + setNewFinalRate(); + applySettings(); +} + +void ChannelAnalyzerGUI::on_pll_toggled(bool checked) +{ + if (!checked) { + ui->pll->setToolTip(tr("PLL lock")); + } + + m_settings.m_pll = checked; + applySettings(); +} + +void ChannelAnalyzerGUI::on_pllPskOrder_currentIndexChanged(int index) +{ + if (index < 5) { + m_settings.m_pllPskOrder = (1<useRationalDownsampler->isChecked()) { + return ui->channelSampleRate->getValueNew(); + } else { + return m_channelAnalyzer->getChannelizer()->getInputSampleRate(); + } +} + +void ChannelAnalyzerGUI::on_signalSelect_currentIndexChanged(int index) +{ + m_settings.m_inputType = (ChannelAnalyzerSettings::InputType) index; + applySettings(); +} + +void ChannelAnalyzerGUI::on_deltaFrequency_changed(qint64 value) +{ + m_channelMarker.setCenterFrequency(value); + m_settings.m_frequency = m_channelMarker.getCenterFrequency(); + applySettings(); +} + +void ChannelAnalyzerGUI::on_rrcFilter_toggled(bool checked) +{ + m_settings.m_rrc = checked; + applySettings(); +} + +void ChannelAnalyzerGUI::on_rrcRolloff_valueChanged(int value) +{ + m_settings.m_rrcRolloff = value; + QString rolloffStr = QString::number(value/100.0, 'f', 2); + ui->rrcRolloffText->setText(rolloffStr); + applySettings(); +} + +void ChannelAnalyzerGUI::on_BW_valueChanged(int value __attribute__((unused))) +{ + setFiltersUIBoundaries(); + m_settings.m_bandwidth = ui->BW->value() * 100; + m_settings.m_lowCutoff = ui->lowCut->value() * 100; applySettings(); } -void ChannelAnalyzerGUI::on_BW_valueChanged(int value) +void ChannelAnalyzerGUI::on_lowCut_valueChanged(int value __attribute__((unused))) { - QString s = QString::number(value/10.0, 'f', 1); - ui->BWText->setText(tr("%1k").arg(s)); - m_channelMarker.setBandwidth(value * 100 * 2); - - if (ui->ssb->isChecked()) - { - if (value < 0) { - m_channelMarker.setSidebands(ChannelMarker::lsb); - } else { - m_channelMarker.setSidebands(ChannelMarker::usb); - } - } - else - { - m_channelMarker.setSidebands(ChannelMarker::dsb); - } - - on_lowCut_valueChanged(m_channelMarker.getLowCutoff()/100); -} - -int ChannelAnalyzerGUI::getEffectiveLowCutoff(int lowCutoff) -{ - int ssbBW = m_channelMarker.getBandwidth() / 2; - int effectiveLowCutoff = lowCutoff; - const int guard = 100; - - if (ssbBW < 0) { - if (effectiveLowCutoff < ssbBW + guard) { - effectiveLowCutoff = ssbBW + guard; - } - if (effectiveLowCutoff > 0) { - effectiveLowCutoff = 0; - } - } else { - if (effectiveLowCutoff > ssbBW - guard) { - effectiveLowCutoff = ssbBW - guard; - } - if (effectiveLowCutoff < 0) { - effectiveLowCutoff = 0; - } - } - - return effectiveLowCutoff; -} - -void ChannelAnalyzerGUI::on_lowCut_valueChanged(int value) -{ - int lowCutoff = getEffectiveLowCutoff(value * 100); - m_channelMarker.setLowCutoff(lowCutoff); - QString s = QString::number(lowCutoff/1000.0, 'f', 1); - ui->lowCutText->setText(tr("%1k").arg(s)); - ui->lowCut->setValue(lowCutoff/100); + setFiltersUIBoundaries(); + m_settings.m_bandwidth = ui->BW->value() * 100; + m_settings.m_lowCutoff = ui->lowCut->value() * 100; applySettings(); } -void ChannelAnalyzerGUI::on_spanLog2_valueChanged(int value) +void ChannelAnalyzerGUI::on_spanLog2_currentIndexChanged(int index) { - if (setNewRate(value)) { - applySettings(); - } + if ((index < 0) || (index > 6)) { + return; + } + m_settings.m_spanLog2 = index; + setNewFinalRate(); + applySettings(); } void ChannelAnalyzerGUI::on_ssb_toggled(bool checked) { - if (checked) - { - if (ui->BW->value() < 0) { - m_channelMarker.setSidebands(ChannelMarker::lsb); - } else { - m_channelMarker.setSidebands(ChannelMarker::usb); - } - - ui->glSpectrum->setCenterFrequency(m_rate/4); - ui->glSpectrum->setSampleRate(m_rate/2); - ui->glSpectrum->setSsbSpectrum(true); - - on_lowCut_valueChanged(m_channelMarker.getLowCutoff()/100); - } - else - { - m_channelMarker.setSidebands(ChannelMarker::dsb); - - ui->glSpectrum->setCenterFrequency(0); - ui->glSpectrum->setSampleRate(m_rate); - ui->glSpectrum->setSsbSpectrum(false); - - applySettings(); + m_settings.m_ssb = checked; + if (checked) { + ui->BWLabel->setText("LP"); + } else { + ui->BWLabel->setText("BP"); } + setFiltersUIBoundaries(); + applySettings(); } void ChannelAnalyzerGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rollDown __attribute__((unused))) { - /* - if((widget == ui->spectrumContainer) && (m_ssbDemod != NULL)) - m_ssbDemod->setSpectrum(m_threadedSampleSink->getMessageQueue(), rollDown); - */ +} + +void ChannelAnalyzerGUI::onMenuDialogCalled(const QPoint& p) +{ + BasicChannelSettingsDialog dialog(&m_channelMarker, this); + dialog.move(p); + dialog.exec(); + + m_settings.m_frequency = m_channelMarker.getCenterFrequency(); + m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); + m_settings.m_title = m_channelMarker.getTitle(); + + setWindowTitle(m_settings.m_title); + setTitleColor(m_settings.m_rgbColor); + + applySettings(); } ChannelAnalyzerGUI::ChannelAnalyzerGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) : @@ -330,55 +378,65 @@ ChannelAnalyzerGUI::ChannelAnalyzerGUI(PluginAPI* pluginAPI, DeviceUISet *device m_deviceUISet(deviceUISet), m_channelMarker(this), m_doApplySettings(true), - m_rate(6000), - m_spanLog2(3) + m_rate(48000) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose, true); connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); m_spectrumVis = new SpectrumVis(SDR_RX_SCALEF, ui->glSpectrum); - m_scopeVis = new ScopeVis(SDR_RX_SCALEF, ui->glScope); + m_scopeVis = new ScopeVis(ui->glScope); m_spectrumScopeComboVis = new SpectrumScopeComboVis(m_spectrumVis, m_scopeVis); m_channelAnalyzer = (ChannelAnalyzer*) rxChannel; //new ChannelAnalyzer(m_deviceUISet->m_deviceSourceAPI); m_channelAnalyzer->setSampleSink(m_spectrumScopeComboVis); m_channelAnalyzer->setMessageQueueToGUI(getInputMessageQueue()); - ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::ReverseGold)); - ui->deltaFrequency->setValueRange(7, 0U, 9999999U); + ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); + ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); + ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999); + + ui->channelSampleRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow)); + ui->channelSampleRate->setValueRange(7, 2000U, 9999999U); ui->glSpectrum->setCenterFrequency(m_rate/2); ui->glSpectrum->setSampleRate(m_rate); ui->glSpectrum->setDisplayWaterfall(true); ui->glSpectrum->setDisplayMaxHold(true); - ui->glSpectrum->setSsbSpectrum(true); + ui->glSpectrum->setSsbSpectrum(false); + ui->glSpectrum->setLsbDisplay(false); - ui->glSpectrum->connectTimer(MainWindow::getInstance()->getMasterTimer()); - ui->glScope->connectTimer(MainWindow::getInstance()->getMasterTimer()); - connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); + ui->glSpectrum->connectTimer(MainWindow::getInstance()->getMasterTimer()); + ui->glScope->connectTimer(MainWindow::getInstance()->getMasterTimer()); + connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); - m_channelMarker.blockSignals(true); + m_channelMarker.blockSignals(true); m_channelMarker.setColor(Qt::gray); m_channelMarker.setBandwidth(m_rate); m_channelMarker.setSidebands(ChannelMarker::usb); m_channelMarker.setCenterFrequency(0); + m_channelMarker.setTitle("Channel Analyzer"); m_channelMarker.blockSignals(false); m_channelMarker.setVisible(true); // activate signal on the last setting only - setTitleColor(m_channelMarker.getColor()); + setTitleColor(m_channelMarker.getColor()); - m_deviceUISet->registerRxChannelInstance(ChannelAnalyzer::m_channelIdURI, this); + m_deviceUISet->registerRxChannelInstance(ChannelAnalyzer::m_channelIdURI, this); m_deviceUISet->addChannelMarker(&m_channelMarker); m_deviceUISet->addRollupWidget(this); ui->spectrumGUI->setBuddies(m_spectrumVis->getInputMessageQueue(), m_spectrumVis, ui->glSpectrum); ui->scopeGUI->setBuddies(m_scopeVis->getInputMessageQueue(), m_scopeVis, ui->glScope); + m_settings.setChannelMarker(&m_channelMarker); + m_settings.setSpectrumGUI(ui->spectrumGUI); + m_settings.setScopeGUI(ui->scopeGUI); + connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor())); connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor())); connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); - applySettings(); - setNewRate(m_spanLog2); + displaySettings(); + applySettings(true); } ChannelAnalyzerGUI::~ChannelAnalyzerGUI() @@ -391,66 +449,78 @@ ChannelAnalyzerGUI::~ChannelAnalyzerGUI() delete ui; } -bool ChannelAnalyzerGUI::setNewRate(int spanLog2) +void ChannelAnalyzerGUI::setNewFinalRate() { - qDebug("ChannelAnalyzerGUI::setNewRate"); + m_rate = getRequestedChannelSampleRate() / (1< 6)) { - return false; - } - - m_spanLog2 = spanLog2; - m_rate = m_channelAnalyzer->getSampleRate() / (1<BW->value() < -m_rate/200) { - ui->BW->setValue(-m_rate/200); - m_channelMarker.setBandwidth(-m_rate*2); - } else if (ui->BW->value() > m_rate/200) { - ui->BW->setValue(m_rate/200); - m_channelMarker.setBandwidth(m_rate*2); - } - - if (ui->lowCut->value() < -m_rate/200) { - ui->lowCut->setValue(-m_rate/200); - m_channelMarker.setLowCutoff(-m_rate); - } else if (ui->lowCut->value() > m_rate/200) { - ui->lowCut->setValue(m_rate/200); - m_channelMarker.setLowCutoff(m_rate); - } - - ui->BW->setMinimum(-m_rate/200); - ui->lowCut->setMinimum(-m_rate/200); - ui->BW->setMaximum(m_rate/200); - ui->lowCut->setMaximum(m_rate/200); + setFiltersUIBoundaries(); QString s = QString::number(m_rate/1000.0, 'f', 1); - ui->spanText->setText(tr("%1k").arg(s)); + ui->spanText->setText(tr("%1 kS/s").arg(s)); - if (ui->ssb->isChecked()) - { - if (ui->BW->value() < 0) { - m_channelMarker.setSidebands(ChannelMarker::lsb); - } else { - m_channelMarker.setSidebands(ChannelMarker::usb); - } + m_scopeVis->setLiveRate(getRequestedChannelSampleRate()); +} - ui->glSpectrum->setCenterFrequency(m_rate/4); - ui->glSpectrum->setSampleRate(m_rate/2); - ui->glSpectrum->setSsbSpectrum(true); - } - else - { - m_channelMarker.setSidebands(ChannelMarker::dsb); +void ChannelAnalyzerGUI::setFiltersUIBoundaries() +{ + bool dsb = !ui->ssb->isChecked(); + int bw = ui->BW->value(); + int lw = ui->lowCut->value(); + int bwMax = m_rate / 200; - ui->glSpectrum->setCenterFrequency(0); - ui->glSpectrum->setSampleRate(m_rate); - ui->glSpectrum->setSsbSpectrum(false); - } + bw = bw < -bwMax ? -bwMax : bw > bwMax ? bwMax : bw; - ui->glScope->setSampleRate(m_rate); - m_scopeVis->setSampleRate(m_rate); + if (bw < 0) { + lw = lw < bw+1 ? bw+1 : lw < 0 ? lw : 0; + } else if (bw > 0) { + lw = lw > bw-1 ? bw-1 : lw < 0 ? 0 : lw; + } else { + lw = 0; + } - return true; + if (dsb) + { + bw = bw < 0 ? -bw : bw; + lw = 0; + } + + QString bwStr = QString::number(bw/10.0, 'f', 1); + QString lwStr = QString::number(lw/10.0, 'f', 1); + + if (dsb) { + ui->BWText->setText(tr("%1%2k").arg(QChar(0xB1, 0x00)).arg(bwStr)); + } else { + ui->BWText->setText(tr("%1k").arg(bwStr)); + } + + ui->lowCutText->setText(tr("%1k").arg(lwStr)); + + ui->BW->blockSignals(true); + ui->lowCut->blockSignals(true); + + ui->BW->setMaximum(bwMax); + ui->BW->setMinimum(dsb ? 0 : -bwMax); + ui->BW->setValue(bw); + + ui->lowCut->setMaximum(dsb ? 0 : bw); + ui->lowCut->setMinimum(dsb ? 0 : -bw); + ui->lowCut->setValue(lw); + + ui->lowCut->blockSignals(false); + ui->BW->blockSignals(false); + + setSpectrumDisplay(); + + m_channelMarker.setBandwidth(bw * 200); + m_channelMarker.setSidebands(dsb ? ChannelMarker::dsb : bw < 0 ? ChannelMarker::lsb : ChannelMarker::usb); + + if (!dsb) { + m_channelMarker.setLowCutoff(lw * 100); + } } void ChannelAnalyzerGUI::blockApplySettings(bool block) @@ -460,18 +530,25 @@ void ChannelAnalyzerGUI::blockApplySettings(bool block) m_doApplySettings = !block; } -void ChannelAnalyzerGUI::applySettings() +void ChannelAnalyzerGUI::applySettings(bool force) { if (m_doApplySettings) { - ChannelAnalyzer::MsgConfigureChannelizer *msg = ChannelAnalyzer::MsgConfigureChannelizer::create(m_channelMarker.getCenterFrequency()); - m_channelAnalyzer->getInputMessageQueue()->push(msg); + int sampleRate = getRequestedChannelSampleRate(); - m_channelAnalyzer->configure(m_channelAnalyzer->getInputMessageQueue(), - ui->BW->value() * 100.0, - ui->lowCut->value() * 100.0, - m_spanLog2, - ui->ssb->isChecked()); + ChannelAnalyzer::MsgConfigureChannelizer *msgChannelizer = + ChannelAnalyzer::MsgConfigureChannelizer::create(sampleRate, m_channelMarker.getCenterFrequency()); + m_channelAnalyzer->getInputMessageQueue()->push(msgChannelizer); + + ChannelAnalyzer::MsgConfigureChannelizer *msg = + ChannelAnalyzer::MsgConfigureChannelizer::create(sampleRate, m_channelMarker.getCenterFrequency()); + m_channelAnalyzer->getInputMessageQueue()->push(msg); + + ChannelAnalyzer::MsgConfigureChannelAnalyzer* message = + ChannelAnalyzer::MsgConfigureChannelAnalyzer::create( m_settings, force); + m_channelAnalyzer->getInputMessageQueue()->push(message); + + m_scopeVis->setLiveRateLog2Decim(m_settings.m_spanLog2); } } diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.h b/plugins/channelrx/chanalyzer/chanalyzergui.h index d81a8dc9c..04a278ad5 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.h +++ b/plugins/channelrx/chanalyzer/chanalyzergui.h @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2015 Edouard Griffiths, F4EXB // +// Copyright (C) 2017 Edouard Griffiths, F4EXB // // // // This program is free software; you can redistribute it and/or modify // // it under the terms of the GNU General Public License as published by // @@ -14,16 +14,18 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef INCLUDE_CHANNELANALYZERGUI_H -#define INCLUDE_CHANNELANALYZERGUI_H +#ifndef INCLUDE_CHANNELANALYZERNGGUI_H +#define INCLUDE_CHANNELANALYZERNGGUI_H -#include +#include "plugin/plugininstancegui.h" #include "gui/rollupwidget.h" #include "dsp/channelmarker.h" #include "dsp/dsptypes.h" #include "util/movingaverage.h" #include "util/messagequeue.h" +#include "chanalyzersettings.h" + class PluginAPI; class DeviceUISet; class BasebandSampleSink; @@ -40,7 +42,7 @@ class ChannelAnalyzerGUI : public RollupWidget, public PluginInstanceGUI { Q_OBJECT public: - static ChannelAnalyzerGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUIset, BasebandSampleSink *rxChannel); + static ChannelAnalyzerGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel); virtual void destroy(); void setName(const QString& name); @@ -63,10 +65,10 @@ private: PluginAPI* m_pluginAPI; DeviceUISet* m_deviceUISet; ChannelMarker m_channelMarker; + ChannelAnalyzerSettings m_settings; bool m_doApplySettings; - int m_rate; - int m_spanLog2; - MovingAverageUtil m_channelPowerDbAvg; + int m_rate; //!< sample rate after final in-channel decimation (spanlog2) + MovingAverageUtil m_channelPowerAvg; ChannelAnalyzer* m_channelAnalyzer; SpectrumScopeComboVis* m_spectrumScopeComboVis; @@ -77,25 +79,36 @@ private: explicit ChannelAnalyzerGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); virtual ~ChannelAnalyzerGUI(); - int getEffectiveLowCutoff(int lowCutoff); - bool setNewRate(int spanLog2); + int getRequestedChannelSampleRate(); + void setNewFinalRate(); //!< set sample rate after final in-channel decimation + void setFiltersUIBoundaries(); void blockApplySettings(bool block); - void applySettings(); + void applySettings(bool force = false); + void displaySettings(); + void displayPLLSettings(); + void setSpectrumDisplay(); void leaveEvent(QEvent*); void enterEvent(QEvent*); private slots: - void on_deltaFrequency_changed(quint64 value); - void on_deltaMinus_toggled(bool minus); + void on_deltaFrequency_changed(qint64 value); + void on_channelSampleRate_changed(quint64 value); + void on_pll_toggled(bool checked); + void on_pllPskOrder_currentIndexChanged(int index); + void on_useRationalDownsampler_toggled(bool checked); + void on_signalSelect_currentIndexChanged(int index); + void on_rrcFilter_toggled(bool checked); + void on_rrcRolloff_valueChanged(int value); void on_BW_valueChanged(int value); void on_lowCut_valueChanged(int value); - void on_spanLog2_valueChanged(int value); + void on_spanLog2_currentIndexChanged(int index); void on_ssb_toggled(bool checked); void onWidgetRolled(QWidget* widget, bool rollDown); + void onMenuDialogCalled(const QPoint& p); void handleInputMessages(); void tick(); }; -#endif // INCLUDE_CHANNELANALYZERGUI_H +#endif // INCLUDE_CHANNELANALYZERNGGUI_H diff --git a/plugins/channelrx/chanalyzer/chanalyzergui.ui b/plugins/channelrx/chanalyzer/chanalyzergui.ui index bec5d53ad..30b482199 100644 --- a/plugins/channelrx/chanalyzer/chanalyzergui.ui +++ b/plugins/channelrx/chanalyzer/chanalyzergui.ui @@ -6,19 +6,19 @@ 0 0 - 640 - 814 + 739 + 778 - 640 + 720 0 - Sans Serif + Liberation Sans 9 @@ -30,8 +30,8 @@ 0 10 - 301 - 131 + 631 + 81 @@ -41,37 +41,40 @@ 3 - + + 2 + + + 2 + + + 2 + + 2 - + + + 2 + - - - Frequency shift direction + + + + 16 + 0 + - ... - - - - :/plus.png - :/minus.png - - - - true - - - false + Df - + 0 @@ -86,12 +89,12 @@ - Monospace + Liberation Mono 12 - SizeVerCursor + PointingHandCursor Qt::StrongFocus @@ -174,6 +177,209 @@ + + + + + + PLL lock + + + + + + + :/unlocked.png + :/locked.png:/unlocked.png + + + true + + + + + + + + 40 + 16777215 + + + + PLL PSK order (1 for CW) + + + + 1 + + + + + 2 + + + + + 4 + + + + + 8 + + + + + 16 + + + + + F + + + + + + + + Use rational downsampler + + + + + + + :/arrow_down.png:/arrow_down.png + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 12 + + + + PointingHandCursor + + + Rational downsampler output rate + + + + + + + S/s + + + + + + + + 50 + 16777215 + + + + Channel decimation + + + + 1 + + + + + 2 + + + + + 4 + + + + + 8 + + + + + 16 + + + + + 32 + + + + + 64 + + + + + + + + + 80 + 0 + + + + Channel final sample rate + + + 00000.0 kS/s + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Select input signal + + + + Sig + + + + + Lock + + + + + ACorr + + + + + + @@ -188,99 +394,93 @@ - - - - - Channel power - - - Qt::LeftToRight - - - 0.0 - - - - - - - dB - - - - - - - - - - - - - Rate + + + + 52 + 0 + - - - - - Channel sample rate + Channel power - - 0 + + Qt::LeftToRight - - 6 - - - 1 - - - 3 - - - 3 - - - Qt::Horizontal - - - true - - - true - - - - - - 6.0k + -100.0 dB Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - SSB/DSB togggle - - - SSB - - - - + + + Toggle RRC filter + - BW + + + + + :/dsb.png:/dsb.png + + + true + + + + + + + + 24 + 24 + + + + Tune RRC filter rolloff factor + + + 10 + + + 70 + + + 1 + + + 35 + + + + + + + RRC filter rolloff factor value + + + 0.00 + + + + + + + + 15 + 0 + + + + BP @@ -322,14 +522,33 @@ - - - - + + + + Qt::Vertical + + + + + + + SSB/DSB toggle + + + SSB + + + + + + 15 + 0 + + - Low cut. + HP @@ -378,15 +597,15 @@ - 10 - 180 - 636 + 0 + 98 + 720 284 - 636 + 716 0 @@ -397,7 +616,16 @@ 2 - + + 2 + + + 2 + + + 2 + + 2 @@ -410,7 +638,7 @@ - Monospace + Liberation Mono 8 @@ -425,14 +653,14 @@ 0 - 470 - 636 - 284 + 390 + 720 + 334 - 636 + 716 0 @@ -443,7 +671,16 @@ 2 - + + 3 + + + 3 + + + 3 + + 3 @@ -451,12 +688,12 @@ 200 - 250 + 300 - Monospace + Liberation Mono 8 @@ -475,12 +712,6 @@
gui/rollupwidget.h
1 - - ValueDial - QWidget -
gui/valuedial.h
- 1 -
GLSpectrum QWidget @@ -493,6 +724,23 @@
gui/glspectrumgui.h
1
+ + ValueDialZ + QWidget +
gui/valuedialz.h
+ 1 +
+ + ValueDial + QWidget +
gui/valuedial.h
+ 1 +
+ + ButtonSwitch + QToolButton +
gui/buttonswitch.h
+
GLScope QWidget diff --git a/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp b/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp index c728805a4..8dcb43823 100644 --- a/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp +++ b/plugins/channelrx/chanalyzer/chanalyzerplugin.cpp @@ -1,14 +1,29 @@ -#include "chanalyzerplugin.h" +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// #include -#include "plugin/pluginapi.h" -#include "chanalyzergui.h" +#include "plugin/pluginapi.h" #include "chanalyzer.h" +#include "chanalyzerplugin.h" +#include "chanalyzergui.h" const PluginDescriptor ChannelAnalyzerPlugin::m_pluginDescriptor = { QString("Channel Analyzer"), - QString("3.9.0"), + QString("4.2.4"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, @@ -48,3 +63,4 @@ ChannelSinkAPI* ChannelAnalyzerPlugin::createRxChannelCS(DeviceSourceAPI *device { return new ChannelAnalyzer(deviceAPI); } + diff --git a/plugins/channelrx/chanalyzer/chanalyzerplugin.h b/plugins/channelrx/chanalyzer/chanalyzerplugin.h index e8a48d3b6..f8254385b 100644 --- a/plugins/channelrx/chanalyzer/chanalyzerplugin.h +++ b/plugins/channelrx/chanalyzer/chanalyzerplugin.h @@ -1,17 +1,33 @@ -#ifndef INCLUDE_CHANALYZERPLUGIN_H -#define INCLUDE_CHANALYZERPLUGIN_H +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_CHANALYZERNGPLUGIN_H +#define INCLUDE_CHANALYZERNGPLUGIN_H #include + #include "plugin/plugininterface.h" class DeviceUISet; class BasebandSampleSink; -class ChannelSinkAPI; class ChannelAnalyzerPlugin : public QObject, PluginInterface { Q_OBJECT Q_INTERFACES(PluginInterface) - Q_PLUGIN_METADATA(IID "org.f4exb.sdrangelove.channel.chanalyzer") + Q_PLUGIN_METADATA(IID "sdrangel.channel.chanalyzerng") public: explicit ChannelAnalyzerPlugin(QObject* parent = NULL); @@ -20,8 +36,8 @@ public: void initPlugin(PluginAPI* pluginAPI); virtual PluginInstanceGUI* createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel); - virtual BasebandSampleSink* createRxChannelBS(DeviceSourceAPI *deviceAPI); - virtual ChannelSinkAPI* createRxChannelCS(DeviceSourceAPI *deviceAPI); + virtual BasebandSampleSink* createRxChannelBS(DeviceSourceAPI *deviceAPI); + virtual ChannelSinkAPI* createRxChannelCS(DeviceSourceAPI *deviceAPI); private: static const PluginDescriptor m_pluginDescriptor; @@ -29,4 +45,4 @@ private: PluginAPI* m_pluginAPI; }; -#endif // INCLUDE_CHANALYZERPLUGIN_H +#endif // INCLUDE_CHANALYZERNGPLUGIN_H diff --git a/plugins/channelrx/chanalyzer/chanalyzersettings.cpp b/plugins/channelrx/chanalyzer/chanalyzersettings.cpp new file mode 100644 index 000000000..02fe476e6 --- /dev/null +++ b/plugins/channelrx/chanalyzer/chanalyzersettings.cpp @@ -0,0 +1,128 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "dsp/dspengine.h" +#include "util/simpleserializer.h" +#include "settings/serializable.h" + +#include "chanalyzersettings.h" + +ChannelAnalyzerSettings::ChannelAnalyzerSettings() : + m_channelMarker(0), + m_spectrumGUI(0), + m_scopeGUI(0) +{ + resetToDefaults(); +} + +void ChannelAnalyzerSettings::resetToDefaults() +{ + m_frequency = 0; + m_downSample = false; + m_downSampleRate = 0; + m_bandwidth = 5000; + m_lowCutoff = 300; + m_spanLog2 = 0; + m_ssb = false; + m_pll = false; + m_fll = false; + m_rrc = false; + m_rrcRolloff = 35; // 0.35 + m_pllPskOrder = 1; + m_inputType = InputSignal; + m_rgbColor = QColor(128, 128, 128).rgb(); + m_title = "Channel Analyzer"; +} + +QByteArray ChannelAnalyzerSettings::serialize() const +{ + SimpleSerializer s(1); + + s.writeS32(1, m_frequency); + s.writeS32(2, m_bandwidth); + s.writeBlob(3, m_spectrumGUI->serialize()); + s.writeU32(4, m_rgbColor); + s.writeS32(5, m_lowCutoff); + s.writeS32(6, m_spanLog2); + s.writeBool(7, m_ssb); + s.writeBlob(8, m_scopeGUI->serialize()); + s.writeBool(9, m_downSample); + s.writeU32(10, m_downSampleRate); + s.writeBool(11, m_pll); + s.writeBool(12, m_fll); + s.writeU32(13, m_pllPskOrder); + s.writeS32(14, (int) m_inputType); + s.writeString(15, m_title); + s.writeBool(16, m_rrc); + s.writeU32(17, m_rrcRolloff); + + return s.final(); +} + +bool ChannelAnalyzerSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if(!d.isValid()) + { + resetToDefaults(); + return false; + } + + if(d.getVersion() == 1) + { + QByteArray bytetmp; + int tmp; + + d.readS32(1, &m_frequency, 0); + d.readS32(2, &m_bandwidth, 5000); + + if (m_spectrumGUI) { + d.readBlob(3, &bytetmp); + m_spectrumGUI->deserialize(bytetmp); + } + + d.readU32(4, &m_rgbColor); + d.readS32(5, &m_lowCutoff, 3); + d.readS32(6, &m_spanLog2, 0); + d.readBool(7, &m_ssb, false); + + if (m_scopeGUI) { + d.readBlob(8, &bytetmp); + m_scopeGUI->deserialize(bytetmp); + } + + d.readBool(9, &m_downSample, false); + d.readU32(10, &m_downSampleRate, 2000U); + d.readBool(11, &m_pll, false); + d.readBool(12, &m_fll, false); + d.readU32(13, &m_pllPskOrder, 1); + d.readS32(14, &tmp, 0); + m_inputType = (InputType) tmp; + d.readString(15, &m_title, "Channel Analyzer"); + d.readBool(16, &m_rrc, false); + d.readU32(17, &m_rrcRolloff, 35); + + return true; + } + else + { + resetToDefaults(); + return false; + } +} diff --git a/plugins/channelrx/tcpsrc/tcpsrcsettings.h b/plugins/channelrx/chanalyzer/chanalyzersettings.h similarity index 63% rename from plugins/channelrx/tcpsrc/tcpsrcsettings.h rename to plugins/channelrx/chanalyzer/chanalyzersettings.h index 372a032f0..065c42049 100644 --- a/plugins/channelrx/tcpsrc/tcpsrcsettings.h +++ b/plugins/channelrx/chanalyzer/chanalyzersettings.h @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2017 Edouard Griffiths, F4EXB // +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // // // // This program is free software; you can redistribute it and/or modify // // it under the terms of the GNU General Public License as published by // @@ -14,59 +14,50 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef PLUGINS_CHANNELRX_TCPSRC_TCPSRCSETTINGS_H_ -#define PLUGINS_CHANNELRX_TCPSRC_TCPSRCSETTINGS_H_ +#ifndef PLUGINS_CHANNELRX_CHANALYZERNG_CHANALYZERSETTINGS_H_ +#define PLUGINS_CHANNELRX_CHANALYZERNG_CHANALYZERSETTINGS_H_ #include -#include -#include -struct Serializable; +class Serializable; -struct TCPSrcSettings +struct ChannelAnalyzerSettings { - enum SampleFormat { - FormatS16LE, - FormatNFM, - FormatSSB, - FormatNone + enum InputType + { + InputSignal, + InputPLL, + InputAutoCorr }; - float m_outputSampleRate; - SampleFormat m_sampleFormat; - float m_inputSampleRate; - int64_t m_inputFrequencyOffset; - float m_rfBandwidth; - uint16_t m_tcpPort; - int m_fmDeviation; - bool m_channelMute; - float m_gain; - int m_squelchdB; //!< power dB - int m_squelchGate; //!< 100ths seconds - bool m_squelchEnabled; - bool m_agc; - bool m_audioActive; - bool m_audioStereo; - int m_volume; + int m_frequency; + bool m_downSample; + quint32 m_downSampleRate; + int m_bandwidth; + int m_lowCutoff; + int m_spanLog2; + bool m_ssb; + bool m_pll; + bool m_fll; + bool m_rrc; + quint32 m_rrcRolloff; //!< in 100ths + unsigned int m_pllPskOrder; + InputType m_inputType; quint32 m_rgbColor; - - QString m_udpAddress; - uint16_t m_udpPort; - uint16_t m_audioPort; - QString m_title; - Serializable *m_channelMarker; Serializable *m_spectrumGUI; + Serializable *m_scopeGUI; - TCPSrcSettings(); + ChannelAnalyzerSettings(); void resetToDefaults(); void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; } void setSpectrumGUI(Serializable *spectrumGUI) { m_spectrumGUI = spectrumGUI; } + void setScopeGUI(Serializable *scopeGUI) { m_scopeGUI = scopeGUI; } QByteArray serialize() const; bool deserialize(const QByteArray& data); }; -#endif /* PLUGINS_CHANNELRX_TCPSRC_TCPSRCSETTINGS_H_ */ +#endif /* PLUGINS_CHANNELRX_CHANALYZERNG_CHANALYZERSETTINGS_H_ */ diff --git a/plugins/channelrx/chanalyzerng/readme.md b/plugins/channelrx/chanalyzer/readme.md similarity index 60% rename from plugins/channelrx/chanalyzerng/readme.md rename to plugins/channelrx/chanalyzer/readme.md index ce2ac81cc..19ee2fda7 100644 --- a/plugins/channelrx/chanalyzerng/readme.md +++ b/plugins/channelrx/chanalyzer/readme.md @@ -9,9 +9,14 @@ This plugin can be used to analyze the complex signal received in its passband. - Real part - Imaginary part - Magnitude linear + - Power i.e. squared magnitude linear - Power i.e. squared magnitude log (dB) - Phase - Phase derivative (instant frequency) + - BPSK symbol mapping + - QPSK symbol mapping + - 8-PSK symbol mapping + - 16-PSK symbol mapping The same waveforms can be used to trigger the scope trace @@ -27,7 +32,9 @@ The interface is essentially divided in the following sections 4. Scope trace control 5. Scope trigger control -Note: the spectrum view (Channel spectrum) is not presented here. +Note 1: the scope trace is updated continuously for sweep times of 1 second or more else the display is refreshed only when the trace finishes. + +Note 2: the spectrum view (Channel spectrum) is not presented here.

C. Channel controls

@@ -35,53 +42,88 @@ Note: the spectrum view (Channel spectrum) is not presented here.

1: Frequency shift from center frequency of reception

-Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. -

3: Toggle the rational downsampler

+

2: Locked loop

-The input channel sample rate is given by the source device sample rate possibly downsampled by a power of two in the source device plugin. This input sample rate can be optionnaly downsampled to any value using a rational downsampler. This allows a precise control of the timings independently of the source plugin sample rate. Some devices are flexible on their sample rate some like the Airspy are not. +Locks a PLL or FLL (depends on control 3) on the signal and mixes its NCO with the input signal. This is mostly useful for carrier recovery on PSK modulations (PLL is used). This effectively de-rotates the signal and symbol points (constellation) can be seen in XY mode with real part as X and imagiary part as Y. -

4: Rational downsampler output rate

+When the PLL is locked the icon lights up in green. The frequency shift from carrier appears in the tooltip. Locking indicator is pretty sharp with about +/- 100 Hz range. The FLL has no indicator. + +

3: Locked loop mode

-Use the wheels to adjust the sample rate that will be used in the rest of the signal processing in the channel. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. The minimum value is 2000 S/s and the maximum value is the source plugin output sample rate. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use this combo to control the locked loop type: -

5: Downsampler by a power of two

+ - 1: PLL with no phase modulation. Locks to CW carrier. + - 2: PLL for BPSK modulation (bi-phase). Locks to a BPSK transmission + - 4: PLL for QPSK modulation (quad-phase). Locks to a QPSK transmission + - 8: PLL for 8-PSK modulation (octo-phase). Locks to a 8-PSK transmission + - 16: PLL for 16-PSK modulation (16-phase). Locks to a 16-PSK transmission + - F: FLL. Actually a frequency follower. This effectively implements an AFC for FM modulations. + +

4: Toggle the rational downsampler

+ +The input channel sample rate is given by the source device sample rate possibly downsampled by a power of two in the source device plugin. This input sample rate can be optionally downsampled to any value using a rational downsampler. This allows a precise control of the timings independently of the source plugin sample rate. Some devices are flexible on their sample rate some like the Airspy are not. + +

5: Rational downsampler output rate

+ +Use the wheels to adjust the sample rate that will be used in the rest of the signal processing in the channel. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. The minimum value is 2000 S/s and the maximum value is the source plugin output sample rate. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. + +

6: Downsampler by a power of two

This combo can select a further downsampling by a power of two. This downsampling applies on the signal coming either directly from the source plugin when the rational downsampler is disabled or from the output of the rational downsampler if it is engaged. -

6: Processing sample rate

+

7: Processing sample rate

This is the resulting sample rate that will be used by the spectrum and scope visualizations -

7. Channel power

+

8: signal selection

+ +Use this combo to select which (complex) signal to use as the display source: + + - Sig: the main signal possibly mixed with PLL/FLL output (see 2 and 3) + - Lock: the output signal (NCO) from PLL or FLL + - ACorr: Auto-correlation of the main signal. It is a fixed 4096 point auto-correlation using FFT technique thus spanning the length of 4096 samples. The trace may show more samples in which case you will see the successive auto-correlation results. + +☞ Auto-correlation hint: because there is always a peak of magnitude at t=0 triggering on the magnitude will make sure the trace starts at t=0 + +

9. Channel power

Average total power in dB relative to a +/- 1.0 amplitude signal received in the pass band. -

8. Select lowpass filter cut-off frequency

+

10. Toggle root raised cosine filter

+ +Use this toggle button to activate or de-activate the root raised cosine (RRC) filter. When active the bnadpass boxcar filter is replaced by a RRC filter. This takes effect only in normal (DSB) mode (see control 14). + +

11. Tune RRC filter rolloff factor

+ +This button tunes the rolloff factor (a.k.a alpha) of the RRC filter in 0.01 steps between 0.1 and 0.7. Default is 0.35. + +

12. Select lowpass filter cut-off frequency

In SSB mode this filter is a complex filter that can lowpass on either side of the center frequency. It is therefore labeled as "LP". For negative frequencies (LSB) the cut-off frequency is therefore negative. In fact setting a negative frequency in SSB mode automatically turns on the LSB mode processing and the spectrum is reversed. -In normal (DSB) mode this filter is a real filter that lowpass on both sides of the zero (center) frequency symmetrically. Therefore it acts as a badpass filter centered on the zero frequency and therefore it is labeled as "BP". The value displayed in (9) is the full bandwidth of the filter. +In normal (DSB) mode this filter is a real filter that lowpass on both sides of the zero (center) frequency symmetrically. Therefore it acts as a bandpass filter centered on the zero frequency and therefore it is labeled as "BP". The value displayed in (9) is the full bandwidth of the filter. -

9. Lowpass filter cut-off frequency

+

13. Lowpass filter cut-off frequency

In SSB mode this is the complex cut-off frequency and is negative for LSB. In normal (DSB) mode this is the full bandwidth of the real lowpass filter centered around zero frequency. -

10. SSB filtering

+

14. SSB filtering

When this toggle is engaged the signal is filtered either above (USB) or below (LSB) the channel center frequency. The sideband is selected according to the sign of the lowpass filter cut-off frequency (8): if positive the USB is selected else the LSB. In LSB mode the spectrum is reversed. When SSB is off the lowpass filter is actually a bandpass filter around the channel center frequency. -

11. Select highpass filter cut-off frequency

+

15. Select highpass filter cut-off frequency

-In SSB mode this controls the cut-off frequency of the complex highpass filter which is the filter closest to the zero frequency. This cut-off frquency is always at least 0.1 kHz in absolute value below the lowpass filter cut-off frequency (8). +In SSB mode this controls the cut-off frequency of the complex highpass filter which is the filter closest to the zero frequency. This cut-off frequency is always at least 0.1 kHz in absolute value below the lowpass filter cut-off frequency (8). In normal (DSB) mode this filter is not active. -

12. Hghpass filter cut-off frequency

+

16. Highpass filter cut-off frequency

This is the cut-off frequency of the highpass filter in kHz. It is zero or negative in LSB mode. @@ -121,27 +163,31 @@ This button selects the display of "X" and "Yn" traces on top of each other with This button selects the display of all traces on the left side of the screen and the polar display of the "Yn" traces using the "X" trace as the x coordinates. -

6. Select trace intensity

+

6. Line or points only display on the 2D XY display (right)

+ +Use this button to toggle points display (on) or line display (off) for the 2D XY display on the right. The points display may yield a more visible graph when the distinct artifact is an accumulation of points. + +

7. Select trace intensity

This button lets you adjust the traces intensity. The value in percent of the maximum intensity appears as a tooltip -

7. Select grid intensity

+

8. Select grid intensity

This button lets you adjust the grid intensity. The value in percent of the maximum intensity appears as a tooltip -

8. Displayed trace length

+

9. Displayed trace length

This slider lets you adjust the length of the traces on display. Each step further divides the length of the full trace controlled by (10). The duration of the full length shown on display appears on the left of the slider and the corresponding number of samples appears as a tooltip. -

9. Trace offset

+

10. Trace offset

This slider lets you move the start of traces on display. Each step moves the trace by an amount of time corresponding to 1/100 of the length of the full trace controlled by (10). The time offset from the start of the traces appears on the left of the slider and the corresponding number of samples appears as a tooltip. -

10. Trace length

+

11. Trace length

This slider lets you control the full length of the trace. Each step increases the corresponding amount of samples by 4800 samples with a minimum of 4800 samples and a maximum of 20*4800 = 96000 samples. The duration of a full trace appears on the left of the slider and he corresponding number of samples appears as a tooltip. -

11. Trace sample rate

+

12. Trace sample rate

This is the sample rate used by the scope and corresponds to the final sample rate after the whole decimation chain. It should be the same amount as the one displayed on the plugin control (C.6) @@ -172,14 +218,63 @@ To construct a trace which represents real values the incoming complex signal mu - Real: take the real part - Imag: take the imaginary part - Mag: calculate magnitude in linear representation. This is just the module of the complex sample + - MagSq: calculate power in linear representation. This is the squared module of the complex sample - MagDB: calculate power in log representation as 10*log10(x) or decibel (dB) representation. This is the squared module of the complex sample expressed in decibels - - Phi: instantaneous phase. This is the argument of the comlpex sample. + - Phi: instantaneous phase. This is the argument of the complex sample. - dPhi: instantaneous derivative of the phase. This is the difference of arguments between successive samples thus represents the instantaneous frequency. + - BPSK: maps -π to π phase into two π wide sectors centered on 0 and π on the -1 to +1 range (sector is 1.0 wide): + - 0 → 0.5, + - π → -0.5 + - QPSK: maps -π to π phase into four π/2 wide sectors centered on 0, π/2, π, -π/2 on the -1 to +1 range (sector is 0.5 wide): + - 0 → 0.25 + - π/2 → 0.75 + - π → -0.75 + - -π/2 → -0.25 + - 8PSK: maps -π to π phase into eight π/4 wide sectors centered on 0, π/4, π/2, 3π/4, π, -3π/4, -π/2, -π/4 on the -1 to +1 range (sector is 0.25 wide): + - 0 → 0.125 + - π/4 → 0.375 + - π/2 → 0.625 + - 3π/4 → 0.875 + - π → -0.875 + - -3π/4 → -0.625 + - -π/2 → -0.375 + - -π/4 → -0.125 + - 16PSK: maps -π to π phase into sixteen π/8 wide sectors centered on 0, π/8, π/4, 3π/8, π/2, 5π/8, 3π/4, 7π/8, π, -7π/8, -3π/4, -5π/8, -π/2, -3π/8, -π/4, -π/8 on the -1 to +1 range (sector is 0.125 wide): + - 0 → 0.0625 + - π/8 → 0.1875 + - π/4 → 0.3125 + - 3π/8 → 0.4375 + - π/2 → 0.5625 + - 5π/8 → 0.6875 + - 3π/4 → 0.8125 + - 7π/8 → 0.9375 + - π → -0.9375 + - -7π/8 → -0.8125 + - -3π/4 → -0.6875 + - -5π/8 → -0.5625 + - -π/2 → -0.4375 + - -3π/8 → -0.3125 + - -π/4 → -0.1875 + - -π/8 → -0.0625 + +**Note1**: example of QPSK projection on a synchronized Tetra signal: -in the MagDB mode when the trace is selected (1) the display overlay on the top right of the trace shows 3 figures. From left to right: peak power in dB, average power in dB and peak to average difference in dB. +![Channel Analyzer NG plugin tetra example](../../../doc/img/ChAnalyzerNG_plugin_tetra.png) + +The signal is synchronized with the PLL in 4 phase mode (locker icon is green). + - A Tetra signal is QPSK modulated at 18 kSym/s therefore the sample rate is set at 90 kS/s thus we have an integer number of samples per symbol (5 samples per symbol). See green square. + - We have set two traces (X and Y1) with QPSK projection. The Y1 trace is delayed by two symbols (10 samples) which makes a 111.11 μs delay. See blue square + - In XY mode on the XY display (right) we can see an accumulation of points around the 16 possible symbol transitions. In two places the same symbol is repeated several times which results in a stronger accumulation. One is with the symbol at 0 (see red circle and square) and the other is with the symbol at π (see yellow circle and square). + - On the left panel of the XY mode display we can see that the 4 possible symbols mark 4 vertical stronger areas centered on 0.25, 0.75, -0.25 and -0.75. + +**Note2**: in the MagDB mode when the trace is selected (1) the display overlay on the top right of the trace shows 3 figures. From left to right: peak power in dB, average power in dB and peak to average difference in dB. ![Channel Analyzer NG plugin scope1 controls](../../../doc/img/ChAnalyzerNG_plugin_overlay_dB.png) +**Note3**: in the MagSq mode when the trace is selected (1) the display overlay on the top right of the trace shows 2 figures in scientific notation. From left to right: peak power and average power. + +![Channel Analyzer NG plugin scope2 controls](../../../doc/img/ChAnalyzerNG_plugin_overlay_lin.png) +

5. Source select

This is for future use when more than one incoming complex signals can be applied. The signal index appears on the right of the button @@ -188,7 +283,7 @@ This is for future use when more than one incoming complex signals can be applie This slider lets you adjust the amplitude scale. The full scale value appears on the left of the slider. The unit depends on the projection. -

7. Offset adjustement

+

7. Offset adjustment

This pair of sliders let you offset the trace vertically. The offset value from reference appears on the left of the slider. The reference is either: @@ -196,10 +291,11 @@ This pair of sliders let you offset the trace vertically. The offset value from - bottom zero value for MagLin projection - bottom -200 dB value for MagDB projection -The top slider is a coarse adjustement. Each step moves the trace by an amount that depends on the projection type: +The top slider is a coarse adjustment. Each step moves the trace by an amount that depends on the projection type: - Real, Imag: 0.01 - Mag: 0.005 + - MagSq: 0.005 - MagDB: 1 dB - Phi, dPhi: 0.01 @@ -207,6 +303,7 @@ The bottom slider is a fine adjustment. Each step moves the trace by an amount t - Real, Imag: 50.0E-6 - Mag: 25.0sE-6 + - MagSq: 25.0sE-6 - MagDB: 0.01 dB - Phi, dPhi: 50.0E-6 @@ -214,33 +311,43 @@ The bottom slider is a fine adjustment. Each step moves the trace by an amount t This pair of sliders let you control the time offset of the trace from the global starting point. The time offset value appears on the left of the slider and the corresponding number of samples appears as a tooltip. -The top slider is a coarse adjustement. Each step moves trace by 100 samples +The top slider is a coarse adjustment. Each step moves trace by 100 samples The bottom slider is a fine adjustment. Each step moves trace by 1 sample

9. Trace display enable

-By default the trace display is enabled and this checkbox is checked. You can optionnally "mute" the trace by unchecking this checkbox. +By default the trace display is enabled and this checkbox is checked. You can optionally "mute" the trace by unchecking this checkbox.

10. Trace color

This area shows the current trace color. When clicking on it a color chooser dialog appears that lets you change the color of the current trace -

11. Memory select

+

11. Save traces in memory

-The last 15 traces are stored in memory and this button lets you browse through traces in memory. The memory index appears on the left of the button. Traces in memory are sorted from latest (1) to oldest (15). The memory index 0 is the current live trace. When indexes > 0 are selected the live trace is suspended. +While in memory mode (see E.13 next) use this button to save the bank of traces in memory (50 last traces) to file. A file dialog will open to let you choose the file name and locaion. By default the file extension is `.trcm`. + +

12. Load traces into memory

+ +While in memory mode (see E.13 next) use this button to load traces previously saved to file using the (E.11) button into the traces memory bank (50 traces). A file dialog will open to let you select the file. It will look for files with `.trcm` extension by default. + +

13. Memory select

+ +The last 50 traces are stored in memory and this button lets you browse through traces in memory. The memory index appears on the left of the button. Traces in memory are sorted from latest (1) to oldest (50). The memory index 0 is the current live trace. When indexes > 0 are selected the live trace is suspended. It is the complex signal that is memorized actually so when a trace in memory is selected you can still use the global and trace controls to change the display. In particular the projection mode and the number of traces can be changed. Only the full trace length cannot be modified. When in memory mode the triggers are disabled since they only apply to a live trace. +While in memory trace the save (E.11) and load (E.12) traces to file buttons can be used. +

F. Trigger control line

![Channel Analyzer NG plugin scope1 controls](../../../doc/img/ChAnalyzerNG_plugin_scope3.png)

1. Select trigger

-This button lets you select which triger condition is affected by the controls. The trigger index appears on the left of the button. +This button lets you select which trigger condition is affected by the controls. The trigger index appears on the left of the button. -Up to 10 triggers (index 0..9) can be chained to give the final trigger top. The first trigger condition (index 0) is tested then if the trigger is raised the next trigger condition (index 1) is activated then when trigger is raised it passes control to the next trigger etc... until the last trigger is raised then the trace process starts. This established to point in time of the trigger. Optionnally the trace can start some time before this point (this is pre-trigger - see 11) +Up to 10 triggers (index 0..9) can be chained to give the final trigger top. The first trigger condition (index 0) is tested then if the trigger is raised the next trigger condition (index 1) is activated then when trigger is raised it passes control to the next trigger etc... until the last trigger is raised then the trace process starts. This established to point in time of the trigger. Optionally the trace can start some time before this point (this is pre-trigger - see 11)

2. Add/delete trigger

@@ -260,7 +367,7 @@ Real: take the real part Imag: take the imaginary part Mag: calculate magnitude in linear representation. This is just the module of the complex sample MagDB: calculate power in log representation as 10*log10(x) or decibel (dB) representation. This is the squared module of the complex sample expressed in decibels -Phi: instantaneous phase. This is the argument of the comlpex sample. +Phi: instantaneous phase. This is the argument of the complex sample. dPhi: instantaneous derivative of the phase. This is the difference of arguments between successive samples thus represents the instantaneous frequency.

5. Trigger repetition

@@ -279,44 +386,56 @@ This button selects the negative edge triggering. Trigger is raised only of the This button selects both signal edges triggering. Trigger is raised if the signal crosses threshold in any direction. This actually combines the positive and negative edge testing with an or condition. -

9. Trigger level adjustment

+

9. Trigger holdoff

+ +Use this button to control the trigger holdoff in a number of samples from 1 to 12. + +The above level condition is counted for a number of samples and if it is has been true for the holdoff number of samples the condition is declared true. A similar count is used for the false condition (below level). + +When the condition is declared true the counter of false conditions is reset and accordingly when the condition is declared false the counter of true conditions is reset. + +A value above 1 helps eliminating false triggers when small spikes appear on the leading or falling edge of a larger pulse. A value of 1 (minimum) means that the holdoff is not active. + +

10. Trigger level adjustment

This pair of sliders let you adjust the trigger level, The level appears on the left of the sliders. -The top slider is a coarse adjustement. Each step moves the trigger level by an amount that depends on the projection type: +The top slider is a coarse adjustment. Each step moves the trigger level by an amount that depends on the projection type: - Real, Imag: 0.01 - - Mag: 0.005 + - Mag: 0.01 + - MagSq: 0.01 - MagDB: 1 dB - Phi, dPhi: 0.01 The bottom slider is a fine adjustment. Each step moves the trigger level by an amount that depends on the projection type: - - Real, Imag: 50.0E-6 - - Mag: 25.0sE-6 + - Real, Imag: 20.0E-6 + - Mag: 20.0sE-6 + - MagSq: 20.0sE-6 - MagDB: 0.01 dB - - Phi, dPhi: 50.0E-6 + - Phi, dPhi: 20.0E-6 -

10: Trigger delay

+

11: Trigger delay

The actual trigger top can be moved forward by a number of samples. This pair of slider lets you adjust this delay. The delay in time units appears at the left of the sliders and the amount of samples as a tooltip -The top slider is a coarse adjustement. Each step moves the delay by a trace length. The bottom slider is a fine adjustment. Each step moves the delay by 20 samples +The top slider is a coarse adjustment. Each step moves the delay by a trace length. The bottom slider is a fine adjustment. Each step moves the delay by 20 samples -

11. Pre-trigger delay

+

12. Pre-trigger delay

The trace can start an amount of time before the trigger top. This pair of sliders let you adjust this amount of time which is displayed at the left of the sliders. The corresponding number of samples appear as a tooltip. -The top slider is a coarse adjustement. Each step moves the delay by a hundreth of the trace length. The bottom slider is a fine adjustment. Each step moves the delay by 20 samples. +The top slider is a coarse adjustment. Each step moves the delay by a hundreth of the trace length. The bottom slider is a fine adjustment. Each step moves the delay by 20 samples. -

12. Trigger line color

+

13. Trigger line color

-This area shows the current trigger line color. When clicking on it a color chooser dialog appears that lets you change the color of the current trigger line color. This line appears when the selected trace projection matches the trigger projecion. +This area shows the current trigger line color. When clicking on it a color chooser dialog appears that lets you change the color of the current trigger line color. This line appears when the selected trace projection matches the trigger projection. -

13. One-shot trigger

+

14. One-shot trigger

This button toggles a one shot trigger. When the (final) trigger is raised only one trace is processed until the button is released. -

14. Freerun

+

15. Freerun

-When active the triggers are disabled and traces are processed continuously. This is the default at plugin start time. \ No newline at end of file +When active the triggers are disabled and traces are processed continuously. This is the default at plugin start time. diff --git a/plugins/channelrx/chanalyzerng/CMakeLists.txt b/plugins/channelrx/chanalyzerng/CMakeLists.txt deleted file mode 100644 index 773912188..000000000 --- a/plugins/channelrx/chanalyzerng/CMakeLists.txt +++ /dev/null @@ -1,46 +0,0 @@ -project(chanalyzerng) - -set(chanalyzerng_SOURCES - chanalyzerng.cpp - chanalyzernggui.cpp - chanalyzerngplugin.cpp -) - -set(chanalyzerng_HEADERS - chanalyzerng.h - chanalyzernggui.h - chanalyzerngplugin.h -) - -set(chanalyzerng_FORMS - chanalyzernggui.ui -) - -include_directories( - . - ${CMAKE_CURRENT_BINARY_DIR} -) - -#include(${QT_USE_FILE}) -add_definitions(${QT_DEFINITIONS}) -add_definitions(-DQT_PLUGIN) -add_definitions(-DQT_SHARED) - -#qt5_wrap_cpp(chanalyzer_HEADERS_MOC ${chanalyzer_HEADERS}) -qt5_wrap_ui(chanalyzerng_FORMS_HEADERS ${chanalyzerng_FORMS}) - -add_library(chanalyzerng SHARED - ${chanalyzerng_SOURCES} - ${chanalyzerng_HEADERS_MOC} - ${chanalyzerng_FORMS_HEADERS} -) - -target_link_libraries(chanalyzerng - ${QT_LIBRARIES} - sdrbase - sdrgui -) - -qt5_use_modules(chanalyzerng Core Widgets ) - -install(TARGETS chanalyzerng DESTINATION lib/plugins/channelrx) diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp b/plugins/channelrx/chanalyzerng/chanalyzerng.cpp deleted file mode 100644 index 85a009219..000000000 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.cpp +++ /dev/null @@ -1,258 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2017 Edouard Griffiths, F4EXB // -// // -// This program is free software; you can redistribute it and/or modify // -// it under the terms of the GNU General Public License as published by // -// the Free Software Foundation as version 3 of the License, or // -// // -// This program is distributed in the hope that it will be useful, // -// but WITHOUT ANY WARRANTY; without even the implied warranty of // -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // -// GNU General Public License V3 for more details. // -// // -// You should have received a copy of the GNU General Public License // -// along with this program. If not, see . // -/////////////////////////////////////////////////////////////////////////////////// - -#include "chanalyzerng.h" - -#include -#include -#include - -#include "device/devicesourceapi.h" -#include "audio/audiooutput.h" -#include "dsp/threadedbasebandsamplesink.h" -#include "dsp/downchannelizer.h" - -MESSAGE_CLASS_DEFINITION(ChannelAnalyzerNG::MsgConfigureChannelAnalyzer, Message) -MESSAGE_CLASS_DEFINITION(ChannelAnalyzerNG::MsgConfigureChannelizer, Message) -MESSAGE_CLASS_DEFINITION(ChannelAnalyzerNG::MsgReportChannelSampleRateChanged, Message) - -const QString ChannelAnalyzerNG::m_channelIdURI = "sdrangel.channel.chanalyzerng"; -const QString ChannelAnalyzerNG::m_channelId = "ChannelAnalyzerNG"; - -ChannelAnalyzerNG::ChannelAnalyzerNG(DeviceSourceAPI *deviceAPI) : - ChannelSinkAPI(m_channelIdURI), - m_deviceAPI(deviceAPI), - m_sampleSink(0), - m_settingsMutex(QMutex::Recursive) -{ - setObjectName(m_channelId); - - m_undersampleCount = 0; - m_sum = 0; - m_usb = true; - m_magsq = 0; - m_useInterpolator = false; - m_interpolatorDistance = 1.0f; - m_interpolatorDistanceRemain = 0.0f; - SSBFilter = new fftfilt(m_config.m_LowCutoff / m_config.m_inputSampleRate, m_config.m_Bandwidth / m_config.m_inputSampleRate, ssbFftLen); - DSBFilter = new fftfilt(m_config.m_Bandwidth / m_config.m_inputSampleRate, 2*ssbFftLen); - - m_channelizer = new DownChannelizer(this); - m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); - m_deviceAPI->addThreadedSink(m_threadedChannelizer); - m_deviceAPI->addChannelAPI(this); - - apply(true); -} - -ChannelAnalyzerNG::~ChannelAnalyzerNG() -{ - delete SSBFilter; - delete DSBFilter; - m_deviceAPI->removeChannelAPI(this); - m_deviceAPI->removeThreadedSink(m_threadedChannelizer); - delete m_threadedChannelizer; - delete m_channelizer; -} - -void ChannelAnalyzerNG::configure(MessageQueue* messageQueue, - int channelSampleRate, - Real Bandwidth, - Real LowCutoff, - int spanLog2, - bool ssb) -{ - Message* cmd = MsgConfigureChannelAnalyzer::create(channelSampleRate, Bandwidth, LowCutoff, spanLog2, ssb); - messageQueue->push(cmd); -} - -void ChannelAnalyzerNG::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly __attribute__((unused))) -{ - fftfilt::cmplx *sideband = 0; - Complex ci; - - m_settingsMutex.lock(); - - for(SampleVector::const_iterator it = begin; it < end; ++it) - { - Complex c(it->real(), it->imag()); - c *= m_nco.nextIQ(); - - if (m_useInterpolator) - { - if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci)) - { - processOneSample(ci, sideband); - m_interpolatorDistanceRemain += m_interpolatorDistance; - } - } - else - { - processOneSample(c, sideband); - } - } - - if(m_sampleSink != 0) - { - m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), m_running.m_ssb); // m_ssb = positive only - } - - m_sampleBuffer.clear(); - - m_settingsMutex.unlock(); -} - -void ChannelAnalyzerNG::start() -{ -} - -void ChannelAnalyzerNG::stop() -{ -} - -bool ChannelAnalyzerNG::handleMessage(const Message& cmd) -{ - qDebug() << "ChannelAnalyzerNG::handleMessage: " << cmd.getIdentifier(); - - if (DownChannelizer::MsgChannelizerNotification::match(cmd)) - { - DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd; - - m_config.m_inputSampleRate = notif.getSampleRate(); - m_config.m_frequency = notif.getFrequencyOffset(); - - qDebug() << "ChannelAnalyzerNG::handleMessage: MsgChannelizerNotification:" - << " m_sampleRate: " << m_config.m_inputSampleRate - << " frequencyOffset: " << m_config.m_frequency; - - apply(); - - if (getMessageQueueToGUI()) - { - MsgReportChannelSampleRateChanged *msg = MsgReportChannelSampleRateChanged::create(); - getMessageQueueToGUI()->push(msg); - } - - return true; - } - else if (MsgConfigureChannelizer::match(cmd)) - { - MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; - m_channelizer->configure(m_channelizer->getInputMessageQueue(), - cfg.getSampleRate(), - cfg.getCenterFrequency()); - return true; - } - else if (MsgConfigureChannelAnalyzer::match(cmd)) - { - MsgConfigureChannelAnalyzer& cfg = (MsgConfigureChannelAnalyzer&) cmd; - - m_config.m_channelSampleRate = cfg.getChannelSampleRate(); - m_config.m_Bandwidth = cfg.getBandwidth(); - m_config.m_LowCutoff = cfg.getLoCutoff(); - m_config.m_spanLog2 = cfg.getSpanLog2(); - m_config.m_ssb = cfg.getSSB(); - - qDebug() << "ChannelAnalyzerNG::handleMessage: MsgConfigureChannelAnalyzer:" - << " m_channelSampleRate: " << m_config.m_channelSampleRate - << " m_Bandwidth: " << m_config.m_Bandwidth - << " m_LowCutoff: " << m_config.m_LowCutoff - << " m_spanLog2: " << m_config.m_spanLog2 - << " m_ssb: " << m_config.m_ssb; - - apply(); - return true; - } - else - { - if (m_sampleSink != 0) - { - return m_sampleSink->handleMessage(cmd); - } - else - { - return false; - } - } -} - - - -void ChannelAnalyzerNG::apply(bool force) -{ - if ((m_running.m_frequency != m_config.m_frequency) || - (m_running.m_inputSampleRate != m_config.m_inputSampleRate) || - force) - { - m_nco.setFreq(-m_config.m_frequency, m_config.m_inputSampleRate); - } - - if ((m_running.m_inputSampleRate != m_config.m_inputSampleRate) || - (m_running.m_channelSampleRate != m_config.m_channelSampleRate) || - force) - { - m_settingsMutex.lock(); - m_interpolator.create(16, m_config.m_inputSampleRate, m_config.m_inputSampleRate / 2.2); - m_interpolatorDistanceRemain = 0.0f; - m_interpolatorDistance = (Real) m_config.m_inputSampleRate / (Real) m_config.m_channelSampleRate; - m_useInterpolator = (m_config.m_inputSampleRate != m_config.m_channelSampleRate); // optim - m_settingsMutex.unlock(); - } - - if ((m_running.m_channelSampleRate != m_config.m_channelSampleRate) || - (m_running.m_Bandwidth != m_config.m_Bandwidth) || - (m_running.m_LowCutoff != m_config.m_LowCutoff) || - force) - { - float bandwidth = m_config.m_Bandwidth; - float lowCutoff = m_config.m_LowCutoff; - - if (bandwidth < 0) - { - bandwidth = -bandwidth; - lowCutoff = -lowCutoff; - m_usb = false; - } - else - { - m_usb = true; - } - - if (bandwidth < 100.0f) - { - bandwidth = 100.0f; - lowCutoff = 0; - } - - m_settingsMutex.lock(); - - SSBFilter->create_filter(lowCutoff / m_config.m_channelSampleRate, bandwidth / m_config.m_channelSampleRate); - DSBFilter->create_dsb_filter(bandwidth / m_config.m_channelSampleRate); - - m_settingsMutex.unlock(); - } - - m_running.m_frequency = m_config.m_frequency; - m_running.m_channelSampleRate = m_config.m_channelSampleRate; - m_running.m_inputSampleRate = m_config.m_inputSampleRate; - m_running.m_Bandwidth = m_config.m_Bandwidth; - m_running.m_LowCutoff = m_config.m_LowCutoff; - - //m_settingsMutex.lock(); - m_running.m_spanLog2 = m_config.m_spanLog2; - m_running.m_ssb = m_config.m_ssb; - //m_settingsMutex.unlock(); -} diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.h b/plugins/channelrx/chanalyzerng/chanalyzerng.h deleted file mode 100644 index aea652558..000000000 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.h +++ /dev/null @@ -1,251 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2017 Edouard Griffiths, F4EXB // -// // -// This program is free software; you can redistribute it and/or modify // -// it under the terms of the GNU General Public License as published by // -// the Free Software Foundation as version 3 of the License, or // -// // -// This program is distributed in the hope that it will be useful, // -// but WITHOUT ANY WARRANTY; without even the implied warranty of // -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // -// GNU General Public License V3 for more details. // -// // -// You should have received a copy of the GNU General Public License // -// along with this program. If not, see . // -/////////////////////////////////////////////////////////////////////////////////// - -#ifndef INCLUDE_CHANALYZERNG_H -#define INCLUDE_CHANALYZERNG_H - -#include -#include - -#include "dsp/basebandsamplesink.h" -#include "channel/channelsinkapi.h" -#include "dsp/interpolator.h" -#include "dsp/ncof.h" -#include "dsp/fftfilt.h" -#include "audio/audiofifo.h" -#include "util/message.h" - -#define ssbFftLen 1024 - -class DeviceSourceAPI; -class ThreadedBasebandSampleSink; -class DownChannelizer; - -class ChannelAnalyzerNG : public BasebandSampleSink, public ChannelSinkAPI { -public: - class MsgConfigureChannelAnalyzer : public Message { - MESSAGE_CLASS_DECLARATION - - public: - int getChannelSampleRate() const { return m_channelSampleRate; } - Real getBandwidth() const { return m_Bandwidth; } - Real getLoCutoff() const { return m_LowCutoff; } - int getSpanLog2() const { return m_spanLog2; } - bool getSSB() const { return m_ssb; } - - static MsgConfigureChannelAnalyzer* create( - int channelSampleRate, - Real Bandwidth, - Real LowCutoff, - int spanLog2, - bool ssb) - { - return new MsgConfigureChannelAnalyzer( - channelSampleRate, - Bandwidth, - LowCutoff, - spanLog2, - ssb); - } - - private: - int m_channelSampleRate; - Real m_Bandwidth; - Real m_LowCutoff; - int m_spanLog2; - bool m_ssb; - - MsgConfigureChannelAnalyzer( - int channelSampleRate, - Real Bandwidth, - Real LowCutoff, - int spanLog2, - bool ssb) : - Message(), - m_channelSampleRate(channelSampleRate), - m_Bandwidth(Bandwidth), - m_LowCutoff(LowCutoff), - m_spanLog2(spanLog2), - m_ssb(ssb) - { } - }; - - class MsgConfigureChannelizer : public Message { - MESSAGE_CLASS_DECLARATION - - public: - int getSampleRate() const { return m_sampleRate; } - int getCenterFrequency() const { return m_centerFrequency; } - - static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency) - { - return new MsgConfigureChannelizer(sampleRate, centerFrequency); - } - - private: - int m_sampleRate; - int m_centerFrequency; - - MsgConfigureChannelizer(int sampleRate, int centerFrequency) : - Message(), - m_sampleRate(sampleRate), - m_centerFrequency(centerFrequency) - { } - }; - - class MsgReportChannelSampleRateChanged : public Message { - MESSAGE_CLASS_DECLARATION - - public: - - static MsgReportChannelSampleRateChanged* create() - { - return new MsgReportChannelSampleRateChanged(); - } - - private: - - MsgReportChannelSampleRateChanged() : - Message() - { } - }; - - ChannelAnalyzerNG(DeviceSourceAPI *deviceAPI); - virtual ~ChannelAnalyzerNG(); - virtual void destroy() { delete this; } - void setSampleSink(BasebandSampleSink* sampleSink) { m_sampleSink = sampleSink; } - - void configure(MessageQueue* messageQueue, - int channelSampleRate, - Real Bandwidth, - Real LowCutoff, - int spanLog2, - bool ssb); - - DownChannelizer *getChannelizer() { return m_channelizer; } - int getInputSampleRate() const { return m_running.m_inputSampleRate; } - int getChannelSampleRate() const { return m_running.m_channelSampleRate; } - double getMagSq() const { return m_magsq; } - - virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); - virtual void start(); - virtual void stop(); - virtual bool handleMessage(const Message& cmd); - - virtual void getIdentifier(QString& id) { id = objectName(); } - virtual void getTitle(QString& title) { title = objectName(); } - virtual qint64 getCenterFrequency() const { return m_running.m_frequency; } - - virtual QByteArray serialize() const { return QByteArray(); } - virtual bool deserialize(const QByteArray& data __attribute__((unused))) { return false; } - - static const QString m_channelIdURI; - static const QString m_channelId; - -private: - - struct Config - { - int m_frequency; - int m_inputSampleRate; - int m_channelSampleRate; - Real m_Bandwidth; - Real m_LowCutoff; - int m_spanLog2; - bool m_ssb; - - Config() : - m_frequency(0), - m_inputSampleRate(96000), - m_channelSampleRate(96000), - m_Bandwidth(5000), - m_LowCutoff(300), - m_spanLog2(3), - m_ssb(false) - {} - }; - - Config m_config; - Config m_running; - - DeviceSourceAPI *m_deviceAPI; - ThreadedBasebandSampleSink* m_threadedChannelizer; - DownChannelizer* m_channelizer; - - int m_undersampleCount; - fftfilt::cmplx m_sum; - bool m_usb; - double m_magsq; - bool m_useInterpolator; - - NCOF m_nco; - Interpolator m_interpolator; - Real m_interpolatorDistance; - Real m_interpolatorDistanceRemain; - - fftfilt* SSBFilter; - fftfilt* DSBFilter; - - BasebandSampleSink* m_sampleSink; - SampleVector m_sampleBuffer; - QMutex m_settingsMutex; - - void apply(bool force = false); - - void processOneSample(Complex& c, fftfilt::cmplx *sideband) - { - int n_out; - int decim = 1<runSSB(c, &sideband, m_usb); - } - else - { - n_out = DSBFilter->runDSB(c, &sideband); - } - - for (int i = 0; i < n_out; i++) - { - // Downsample by 2^(m_scaleLog2 - 1) for SSB band spectrum display - // smart decimation with bit gain using float arithmetic (23 bits significand) - - m_sum += sideband[i]; - - if (!(m_undersampleCount++ & (decim - 1))) // counter LSB bit mask for decimation by 2^(m_scaleLog2 - 1) - { - m_sum /= decim; - Real re = m_sum.real() / SDR_RX_SCALED; - Real im = m_sum.imag() / SDR_RX_SCALED; - m_magsq = re*re + im*im; - - if (m_running.m_ssb & !m_usb) - { // invert spectrum for LSB - m_sampleBuffer.push_back(Sample(m_sum.imag(), m_sum.real())); - } - else - { - m_sampleBuffer.push_back(Sample(m_sum.real(), m_sum.imag())); - } - - m_sum = 0; - } - } - } -}; - -#endif // INCLUDE_CHANALYZERNG_H diff --git a/plugins/channelrx/chanalyzerng/chanalyzerng.pro b/plugins/channelrx/chanalyzerng/chanalyzerng.pro deleted file mode 100644 index 19a0d8987..000000000 --- a/plugins/channelrx/chanalyzerng/chanalyzerng.pro +++ /dev/null @@ -1,45 +0,0 @@ -#-------------------------------------------------------- -# -# Pro file for Android and Windows builds with Qt Creator -# -#-------------------------------------------------------- - -TEMPLATE = lib -CONFIG += plugin - -QT += core gui widgets multimedia opengl - -TARGET = chanalyzerng - -DEFINES += USE_SSE2=1 -QMAKE_CXXFLAGS += -msse2 -DEFINES += USE_SSE4_1=1 -QMAKE_CXXFLAGS += -msse4.1 -QMAKE_CXXFLAGS += -std=c++11 - -INCLUDEPATH += $$PWD -INCLUDEPATH += ../../../sdrbase -INCLUDEPATH += ../../../sdrgui - -CONFIG(ANDROID):INCLUDEPATH += /opt/softs/boost_1_60_0 -CONFIG(MINGW32):INCLUDEPATH += "D:\boost_1_58_0" -CONFIG(MINGW64):INCLUDEPATH += "D:\boost_1_58_0" -CONFIG(macx):INCLUDEPATH += "../../../../../boost_1_64_0" - -CONFIG(Release):build_subdir = release -CONFIG(Debug):build_subdir = debug - -SOURCES += chanalyzerng.cpp\ - chanalyzernggui.cpp\ - chanalyzerngplugin.cpp - -HEADERS += chanalyzerng.h\ -chanalyzernggui.h\ -chanalyzerngplugin.h - -FORMS += chanalyzernggui.ui - -LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase -LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui - -RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp b/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp deleted file mode 100644 index 72be0e3bf..000000000 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.cpp +++ /dev/null @@ -1,594 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2017 Edouard Griffiths, F4EXB // -// // -// This program is free software; you can redistribute it and/or modify // -// it under the terms of the GNU General Public License as published by // -// the Free Software Foundation as version 3 of the License, or // -// // -// This program is distributed in the hope that it will be useful, // -// but WITHOUT ANY WARRANTY; without even the implied warranty of // -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // -// GNU General Public License V3 for more details. // -// // -// You should have received a copy of the GNU General Public License // -// along with this program. If not, see . // -/////////////////////////////////////////////////////////////////////////////////// - -#include "chanalyzernggui.h" - -#include -#include "device/deviceuiset.h" -#include -#include -#include - -#include "dsp/threadedbasebandsamplesink.h" -#include "ui_chanalyzernggui.h" -#include "dsp/spectrumscopengcombovis.h" -#include "dsp/spectrumvis.h" -#include "dsp/scopevis.h" -#include "gui/glspectrum.h" -#include "gui/glscopeng.h" -#include "plugin/pluginapi.h" -#include "util/simpleserializer.h" -#include "util/db.h" -#include "dsp/dspengine.h" -#include "mainwindow.h" - -#include "chanalyzerng.h" - -ChannelAnalyzerNGGUI* ChannelAnalyzerNGGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) -{ - ChannelAnalyzerNGGUI* gui = new ChannelAnalyzerNGGUI(pluginAPI, deviceUISet, rxChannel); - return gui; -} - -void ChannelAnalyzerNGGUI::destroy() -{ - delete this; -} - -void ChannelAnalyzerNGGUI::setName(const QString& name) -{ - setObjectName(name); -} - -QString ChannelAnalyzerNGGUI::getName() const -{ - return objectName(); -} - -qint64 ChannelAnalyzerNGGUI::getCenterFrequency() const -{ - return m_channelMarker.getCenterFrequency(); -} - -void ChannelAnalyzerNGGUI::setCenterFrequency(qint64 centerFrequency) -{ - m_channelMarker.setCenterFrequency(centerFrequency); - applySettings(); -} - -void ChannelAnalyzerNGGUI::resetToDefaults() -{ - blockApplySettings(true); - - ui->useRationalDownsampler->setChecked(false); - ui->BW->setValue(30); - ui->deltaFrequency->setValue(0); - ui->spanLog2->setCurrentIndex(3); - - blockApplySettings(false); - applySettings(); -} - -QByteArray ChannelAnalyzerNGGUI::serialize() const -{ - SimpleSerializer s(1); - s.writeS32(1, m_channelMarker.getCenterFrequency()); - s.writeS32(2, ui->BW->value()); - s.writeBlob(3, ui->spectrumGUI->serialize()); - s.writeU32(4, m_channelMarker.getColor().rgb()); - s.writeS32(5, ui->lowCut->value()); - s.writeS32(6, ui->spanLog2->currentIndex()); - s.writeBool(7, ui->ssb->isChecked()); - s.writeBlob(8, ui->scopeGUI->serialize()); - s.writeU64(9, ui->channelSampleRate->getValueNew()); - return s.final(); -} - -bool ChannelAnalyzerNGGUI::deserialize(const QByteArray& data) -{ - SimpleDeserializer d(data); - - if(!d.isValid()) - { - resetToDefaults(); - return false; - } - - if(d.getVersion() == 1) - { - QByteArray bytetmp; - quint32 u32tmp; - quint64 u64tmp; - qint32 tmp, spanLog2, bw, lowCut; - bool tmpBool; - - blockApplySettings(true); - m_channelMarker.blockSignals(true); - - d.readS32(1, &tmp, 0); - m_channelMarker.setCenterFrequency(tmp); - d.readS32(2, &bw, 30); - d.readBlob(3, &bytetmp); - ui->spectrumGUI->deserialize(bytetmp); - - if(d.readU32(4, &u32tmp)) - { - m_channelMarker.setColor(u32tmp); - } - - d.readS32(5, &lowCut, 3); - d.readS32(6, &spanLog2, 3); - d.readBool(7, &tmpBool, false); - ui->ssb->setChecked(tmpBool); - d.readBlob(8, &bytetmp); - ui->scopeGUI->deserialize(bytetmp); - d.readU64(9, &u64tmp, 2000U); - ui->channelSampleRate->setValue(u64tmp); - - blockApplySettings(false); - m_channelMarker.blockSignals(false); - m_channelMarker.emitChangedByAPI(); - - ui->spanLog2->setCurrentIndex(spanLog2); - setNewFinalRate(spanLog2); - ui->BW->setValue(bw); - ui->lowCut->setValue(lowCut); // does applySettings(); - - return true; - } - else - { - resetToDefaults(); - return false; - } -} - -bool ChannelAnalyzerNGGUI::handleMessage(const Message& message) -{ - if (ChannelAnalyzerNG::MsgReportChannelSampleRateChanged::match(message)) - { - int newRate = getRequestedChannelSampleRate() / (1<BW->value() * 100L * m_rate) / newRate; - uint64_t newLC = (ui->lowCut->value() * 100L * m_rate) / newRate; - - qDebug() << "ChannelAnalyzerNGGUI::handleMessage: MsgReportChannelSampleRateChanged:" - << " newRate: " << newRate - << " newBW: " << newBW - << " newLC: " << newLC; - - m_rate = newRate; - - ui->BW->setValue(newBW/100); - ui->lowCut->setValue(newLC/100); - - blockApplySettings(true); - setFiltersUIBoundaries(); - blockApplySettings(false); - - if (ui->ssb->isChecked()) - { - QString s = QString::number(ui->BW->value()/10.0, 'f', 1); - ui->BWText->setText(tr("%1k").arg(s)); - } - else - { - QString s = QString::number(ui->BW->value()/5.0, 'f', 1); // BW = value * 2 - ui->BWText->setText(tr("%1k").arg(s)); - } - - QString s = QString::number(ui->lowCut->value()/10.0, 'f', 1); - ui->lowCutText->setText(tr("%1k").arg(s)); - - s = QString::number(m_rate/1000.0, 'f', 1); - ui->spanText->setText(tr("%1 kS/s").arg(s)); - - ui->glScope->setSampleRate(m_rate); - - displayBandwidth(); // sets ui->glSpectrum sample rate - - return true; - } - - return false; -} - -void ChannelAnalyzerNGGUI::handleInputMessages() -{ - Message* message; - - while ((message = getInputMessageQueue()->pop()) != 0) - { - qDebug("ChannelAnalyzerGUI::handleInputMessages: message: %s", message->getIdentifier()); - - if (handleMessage(*message)) - { - delete message; - } - } -} - -void ChannelAnalyzerNGGUI::channelMarkerChangedByCursor() -{ - ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); - applySettings(); -} - -void ChannelAnalyzerNGGUI::channelMarkerHighlightedByCursor() -{ - setHighlighted(m_channelMarker.getHighlighted()); -} - -void ChannelAnalyzerNGGUI::tick() -{ - double powDb = CalcDb::dbPower(m_channelAnalyzer->getMagSq()); - m_channelPowerDbAvg(powDb); - ui->channelPower->setText(tr("%1 dB").arg((Real) m_channelPowerDbAvg, 0, 'f', 1)); -} - -void ChannelAnalyzerNGGUI::on_channelSampleRate_changed(quint64 value) -{ - ui->channelSampleRate->setValueRange(7, 2000U, m_channelAnalyzer->getInputSampleRate()); - - if (ui->useRationalDownsampler->isChecked()) - { - qDebug("ChannelAnalyzerNGGUI::on_channelSampleRate_changed: %llu", value); - setNewFinalRate(m_spanLog2); - applySettings(); - } -} - -void ChannelAnalyzerNGGUI::on_useRationalDownsampler_toggled(bool checked __attribute__((unused))) -{ - setNewFinalRate(m_spanLog2); - applySettings(); -} - -int ChannelAnalyzerNGGUI::getRequestedChannelSampleRate() -{ - if (ui->useRationalDownsampler->isChecked()) { - return ui->channelSampleRate->getValueNew(); - } else { - return m_channelAnalyzer->getChannelizer()->getInputSampleRate(); - } -} - -void ChannelAnalyzerNGGUI::on_deltaFrequency_changed(qint64 value) -{ - m_channelMarker.setCenterFrequency(value); - applySettings(); -} - -void ChannelAnalyzerNGGUI::on_BW_valueChanged(int value) -{ -// m_channelMarker.setBandwidth(value * 100 * 2); - - if (ui->ssb->isChecked()) - { - QString s = QString::number(value/10.0, 'f', 1); - ui->BWText->setText(tr("%1k").arg(s)); - } - else - { - QString s = QString::number(value/5.0, 'f', 1); // BW = value * 2 - ui->BWText->setText(tr("%1k").arg(s)); - } - - displayBandwidth(); - on_lowCut_valueChanged(m_channelMarker.getLowCutoff()/100); // does apply settings -} - -int ChannelAnalyzerNGGUI::getEffectiveLowCutoff(int lowCutoff) -{ - int ssbBW = m_channelMarker.getBandwidth() / 2; - int effectiveLowCutoff = lowCutoff; - const int guard = 100; - - if (ssbBW < 0) { - if (effectiveLowCutoff < ssbBW + guard) { - effectiveLowCutoff = ssbBW + guard; - } - if (effectiveLowCutoff > 0) { - effectiveLowCutoff = 0; - } - } else { - if (effectiveLowCutoff > ssbBW - guard) { - effectiveLowCutoff = ssbBW - guard; - } - if (effectiveLowCutoff < 0) { - effectiveLowCutoff = 0; - } - } - - return effectiveLowCutoff; -} - -void ChannelAnalyzerNGGUI::on_lowCut_valueChanged(int value) -{ - blockApplySettings(true); - int lowCutoff = getEffectiveLowCutoff(value * 100); - m_channelMarker.setLowCutoff(lowCutoff); - QString s = QString::number(lowCutoff/1000.0, 'f', 1); - ui->lowCutText->setText(tr("%1k").arg(s)); - ui->lowCut->setValue(lowCutoff/100); - blockApplySettings(false); - applySettings(); -} - -void ChannelAnalyzerNGGUI::on_spanLog2_currentIndexChanged(int index) -{ - if (setNewFinalRate(index)) { - applySettings(); - } - -} - -void ChannelAnalyzerNGGUI::on_ssb_toggled(bool checked) -{ - //int bw = m_channelMarker.getBandwidth(); - - if (checked) - { - setFiltersUIBoundaries(); - - ui->BWLabel->setText("LP"); - QString s = QString::number(ui->BW->value()/10.0, 'f', 1); // bw/2 - ui->BWText->setText(tr("%1k").arg(s)); - - on_lowCut_valueChanged(m_channelMarker.getLowCutoff()/100); - } - else - { - if (ui->BW->value() < 0) { - ui->BW->setValue(-ui->BW->value()); - } - - setFiltersUIBoundaries(); - //m_channelMarker.setBandwidth(ui->BW->value() * 200.0); - - ui->BWLabel->setText("BP"); - QString s = QString::number(ui->BW->value()/5.0, 'f', 1); // bw - ui->BWText->setText(tr("%1k").arg(s)); - - ui->lowCut->setEnabled(false); - ui->lowCut->setValue(0); - ui->lowCutText->setText("0.0k"); - } - - applySettings(); - displayBandwidth(); -} - -void ChannelAnalyzerNGGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rollDown __attribute__((unused))) -{ - /* - if((widget == ui->spectrumContainer) && (m_ssbDemod != NULL)) - m_ssbDemod->setSpectrum(m_threadedSampleSink->getMessageQueue(), rollDown); - */ -} - -ChannelAnalyzerNGGUI::ChannelAnalyzerNGGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) : - RollupWidget(parent), - ui(new Ui::ChannelAnalyzerNGGUI), - m_pluginAPI(pluginAPI), - m_deviceUISet(deviceUISet), - m_channelMarker(this), - m_doApplySettings(true), - m_rate(6000), - m_spanLog2(0) -{ - ui->setupUi(this); - setAttribute(Qt::WA_DeleteOnClose, true); - connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); - - m_spectrumVis = new SpectrumVis(SDR_RX_SCALEF, ui->glSpectrum); - m_scopeVis = new ScopeVisNG(ui->glScope); - m_spectrumScopeComboVis = new SpectrumScopeNGComboVis(m_spectrumVis, m_scopeVis); - m_channelAnalyzer = (ChannelAnalyzerNG*) rxChannel; //new ChannelAnalyzerNG(m_deviceUISet->m_deviceSourceAPI); - m_channelAnalyzer->setSampleSink(m_spectrumScopeComboVis); - m_channelAnalyzer->setMessageQueueToGUI(getInputMessageQueue()); - - ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); - ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); - ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999); - - ui->channelSampleRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow)); - ui->channelSampleRate->setValueRange(7, 2000U, 9999999U); - - ui->glSpectrum->setCenterFrequency(m_rate/2); - ui->glSpectrum->setSampleRate(m_rate); - ui->glSpectrum->setDisplayWaterfall(true); - ui->glSpectrum->setDisplayMaxHold(true); - ui->glSpectrum->setSsbSpectrum(false); - ui->glSpectrum->setLsbDisplay(false); - ui->BWLabel->setText("BP"); - - ui->glSpectrum->connectTimer(MainWindow::getInstance()->getMasterTimer()); - ui->glScope->connectTimer(MainWindow::getInstance()->getMasterTimer()); - connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); - - m_channelMarker.blockSignals(true); - m_channelMarker.setColor(Qt::gray); - m_channelMarker.setBandwidth(m_rate); - m_channelMarker.setSidebands(ChannelMarker::usb); - m_channelMarker.setCenterFrequency(0); - m_channelMarker.blockSignals(false); - m_channelMarker.setVisible(true); // activate signal on the last setting only - setTitleColor(m_channelMarker.getColor()); - - m_deviceUISet->registerRxChannelInstance(ChannelAnalyzerNG::m_channelIdURI, this); - m_deviceUISet->addChannelMarker(&m_channelMarker); - m_deviceUISet->addRollupWidget(this); - - ui->spectrumGUI->setBuddies(m_spectrumVis->getInputMessageQueue(), m_spectrumVis, ui->glSpectrum); - ui->scopeGUI->setBuddies(m_scopeVis->getInputMessageQueue(), m_scopeVis, ui->glScope); - - connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor())); - connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor())); - connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); - - applySettings(); - setNewFinalRate(m_spanLog2); -} - -ChannelAnalyzerNGGUI::~ChannelAnalyzerNGGUI() -{ - m_deviceUISet->removeRxChannelInstance(this); - delete m_channelAnalyzer; // TODO: check this: when the GUI closes it has to delete the demodulator - delete m_spectrumVis; - delete m_scopeVis; - delete m_spectrumScopeComboVis; - delete ui; -} - -bool ChannelAnalyzerNGGUI::setNewFinalRate(int spanLog2) -{ - qDebug("ChannelAnalyzerNGGUI::setNewRate"); - - if ((spanLog2 < 0) || (spanLog2 > 6)) { - return false; - } - - m_spanLog2 = spanLog2; - m_rate = getRequestedChannelSampleRate() / (1<spanText->setText(tr("%1 kS/s").arg(s)); - - displayBandwidth(); - - ui->glScope->setSampleRate(m_rate); - ui->glSpectrum->setSampleRate(m_rate); - m_scopeVis->setSampleRate(m_rate); - - return true; -} - -void ChannelAnalyzerNGGUI::displayBandwidth() -{ - blockApplySettings(true); - - m_channelMarker.setBandwidth(ui->BW->value() * 100 * 2); - - if (ui->ssb->isChecked()) - { - if (ui->BW->value() < 0) - { - m_channelMarker.setSidebands(ChannelMarker::lsb); - ui->glSpectrum->setLsbDisplay(true); - } - else - { - m_channelMarker.setSidebands(ChannelMarker::usb); - ui->glSpectrum->setLsbDisplay(false); - } - - m_channelMarker.setLowCutoff(ui->lowCut->value()*100); - ui->glSpectrum->setSampleRate(m_rate/2); - ui->glSpectrum->setCenterFrequency(m_rate/4); - ui->glSpectrum->setSsbSpectrum(true); - } - else - { - m_channelMarker.setSidebands(ChannelMarker::dsb); - - ui->glSpectrum->setCenterFrequency(0); - ui->glSpectrum->setSampleRate(m_rate); - ui->glSpectrum->setLsbDisplay(false); - ui->glSpectrum->setSsbSpectrum(false); - } - - blockApplySettings(false); -} - -void ChannelAnalyzerNGGUI::setFiltersUIBoundaries() -{ - if (ui->BW->value() < -m_rate/200) { - ui->BW->setValue(-m_rate/200); - m_channelMarker.setBandwidth(-m_rate*2); - } else if (ui->BW->value() > m_rate/200) { - ui->BW->setValue(m_rate/200); - m_channelMarker.setBandwidth(m_rate*2); - } - - if (ui->lowCut->value() < -m_rate/200) { - ui->lowCut->setValue(-m_rate/200); - m_channelMarker.setLowCutoff(-m_rate); - } else if (ui->lowCut->value() > m_rate/200) { - ui->lowCut->setValue(m_rate/200); - m_channelMarker.setLowCutoff(m_rate); - } - - if (ui->ssb->isChecked()) { - ui->BW->setMinimum(-m_rate/200); - ui->lowCut->setMinimum(-m_rate/200); - } else { - ui->BW->setMinimum(0); - ui->lowCut->setMinimum(-m_rate/200); - ui->lowCut->setValue(0); - } - - ui->BW->setMaximum(m_rate/200); - ui->lowCut->setMaximum(m_rate/200); -} - -void ChannelAnalyzerNGGUI::blockApplySettings(bool block) -{ - ui->glScope->blockSignals(block); - ui->glSpectrum->blockSignals(block); - m_doApplySettings = !block; -} - -void ChannelAnalyzerNGGUI::applySettings() -{ - if (m_doApplySettings) - { - int sampleRate = getRequestedChannelSampleRate(); - - ChannelAnalyzerNG::MsgConfigureChannelizer *msgChannelizer = ChannelAnalyzerNG::MsgConfigureChannelizer::create(sampleRate, m_channelMarker.getCenterFrequency()); - m_channelAnalyzer->getInputMessageQueue()->push(msgChannelizer); - - ChannelAnalyzerNG::MsgConfigureChannelizer *msg = - ChannelAnalyzerNG::MsgConfigureChannelizer::create( - sampleRate, - m_channelMarker.getCenterFrequency()); - m_channelAnalyzer->getInputMessageQueue()->push(msg); - - m_channelAnalyzer->configure(m_channelAnalyzer->getInputMessageQueue(), - sampleRate, - ui->BW->value() * 100.0, - ui->lowCut->value() * 100.0, - m_spanLog2, - ui->ssb->isChecked()); - } -} - -void ChannelAnalyzerNGGUI::leaveEvent(QEvent*) -{ - m_channelMarker.setHighlighted(false); -} - -void ChannelAnalyzerNGGUI::enterEvent(QEvent*) -{ - m_channelMarker.setHighlighted(true); -} - diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.h b/plugins/channelrx/chanalyzerng/chanalyzernggui.h deleted file mode 100644 index e620b00fe..000000000 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.h +++ /dev/null @@ -1,105 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2017 Edouard Griffiths, F4EXB // -// // -// This program is free software; you can redistribute it and/or modify // -// it under the terms of the GNU General Public License as published by // -// the Free Software Foundation as version 3 of the License, or // -// // -// This program is distributed in the hope that it will be useful, // -// but WITHOUT ANY WARRANTY; without even the implied warranty of // -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // -// GNU General Public License V3 for more details. // -// // -// You should have received a copy of the GNU General Public License // -// along with this program. If not, see . // -/////////////////////////////////////////////////////////////////////////////////// - -#ifndef INCLUDE_CHANNELANALYZERNGGUI_H -#define INCLUDE_CHANNELANALYZERNGGUI_H - -#include -#include "gui/rollupwidget.h" -#include "dsp/channelmarker.h" -#include "dsp/dsptypes.h" -#include "util/movingaverage.h" -#include "util/messagequeue.h" - -class PluginAPI; -class DeviceUISet; -class BasebandSampleSink; -class ChannelAnalyzerNG; -class SpectrumScopeNGComboVis; -class SpectrumVis; -class ScopeVisNG; - -namespace Ui { - class ChannelAnalyzerNGGUI; -} - -class ChannelAnalyzerNGGUI : public RollupWidget, public PluginInstanceGUI { - Q_OBJECT - -public: - static ChannelAnalyzerNGGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel); - virtual void destroy(); - - void setName(const QString& name); - QString getName() const; - virtual qint64 getCenterFrequency() const; - virtual void setCenterFrequency(qint64 centerFrequency); - - void resetToDefaults(); - QByteArray serialize() const; - bool deserialize(const QByteArray& data); - virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } - virtual bool handleMessage(const Message& message); - -public slots: - void channelMarkerChangedByCursor(); - void channelMarkerHighlightedByCursor(); - -private: - Ui::ChannelAnalyzerNGGUI* ui; - PluginAPI* m_pluginAPI; - DeviceUISet* m_deviceUISet; - ChannelMarker m_channelMarker; - bool m_doApplySettings; - int m_rate; //!< sample rate after final in-channel decimation (spanlog2) - int m_spanLog2; - MovingAverageUtil m_channelPowerDbAvg; - - ChannelAnalyzerNG* m_channelAnalyzer; - SpectrumScopeNGComboVis* m_spectrumScopeComboVis; - SpectrumVis* m_spectrumVis; - ScopeVisNG* m_scopeVis; - MessageQueue m_inputMessageQueue; - - explicit ChannelAnalyzerNGGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); - virtual ~ChannelAnalyzerNGGUI(); - - int getRequestedChannelSampleRate(); - int getEffectiveLowCutoff(int lowCutoff); - bool setNewFinalRate(int spanLog2); //!< set sample rate after final in-channel decimation - void setFiltersUIBoundaries(); - - void blockApplySettings(bool block); - void applySettings(); - void displayBandwidth(); - - void leaveEvent(QEvent*); - void enterEvent(QEvent*); - -private slots: - void on_deltaFrequency_changed(qint64 value); - void on_channelSampleRate_changed(quint64 value); - void on_useRationalDownsampler_toggled(bool checked); - void on_BW_valueChanged(int value); - void on_lowCut_valueChanged(int value); - void on_spanLog2_currentIndexChanged(int index); - void on_ssb_toggled(bool checked); - void onWidgetRolled(QWidget* widget, bool rollDown); - void handleInputMessages(); - void tick(); -}; - -#endif // INCLUDE_CHANNELANALYZERNGGUI_H diff --git a/plugins/channelrx/chanalyzerng/chanalyzernggui.ui b/plugins/channelrx/chanalyzerng/chanalyzernggui.ui deleted file mode 100644 index 448ad44b1..000000000 --- a/plugins/channelrx/chanalyzerng/chanalyzernggui.ui +++ /dev/null @@ -1,626 +0,0 @@ - - - ChannelAnalyzerNGGUI - - - - 0 - 0 - 739 - 778 - - - - - 720 - 0 - - - - - Sans Serif - 9 - - - - Channel Analyzer NG - - - - - 0 - 10 - 631 - 81 - - - - Settings - - - - 3 - - - 2 - - - 2 - - - 2 - - - 2 - - - - - 2 - - - - - - - - 16 - 0 - - - - Df - - - - - - - - 0 - 0 - - - - - 32 - 16 - - - - - DejaVu Sans Mono - 12 - - - - PointingHandCursor - - - Qt::StrongFocus - - - Demod shift frequency from center in Hz - - - - - - - - - - - - 26 - 26 - 26 - - - - - - - 255 - 255 - 255 - - - - - - - - - 26 - 26 - 26 - - - - - - - 255 - 255 - 255 - - - - - - - - - 118 - 118 - 117 - - - - - - - 255 - 255 - 255 - - - - - - - - Hz - - - - - - - - - - - Use rational downsampler - - - - - - - :/arrow_down.png:/arrow_down.png - - - - - - - - 0 - 0 - - - - - 32 - 16 - - - - - DejaVu Sans Mono - 12 - - - - PointingHandCursor - - - Rational downsampler output rate - - - - - - - S/s - - - - - - - - 50 - 16777215 - - - - Channel decimation - - - - 1 - - - - - 2 - - - - - 4 - - - - - 8 - - - - - 16 - - - - - 32 - - - - - 64 - - - - - - - - - 80 - 0 - - - - Channel final sample rate - - - 00000.0 kS/s - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 52 - 0 - - - - Channel power - - - Qt::LeftToRight - - - -100.0 dB - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - 15 - 0 - - - - BP - - - - - - - Lowpass filter cutoff frequency - - - -60 - - - 60 - - - 1 - - - 30 - - - Qt::Horizontal - - - - - - - - 50 - 0 - - - - 3.0k - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Qt::Vertical - - - - - - - SSB/DSB togggle - - - SSB - - - - - - - - 15 - 0 - - - - HP - - - - - - - Highpass filter cutoff frequency (SSB) - - - -60 - - - 60 - - - 1 - - - 3 - - - Qt::Horizontal - - - - - - - - 50 - 0 - - - - 0.3k - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - 0 - 98 - 720 - 284 - - - - - 716 - 0 - - - - Channel Spectrum - - - - 2 - - - 2 - - - 2 - - - 2 - - - 2 - - - - - - 200 - 250 - - - - - Monospace - 8 - - - - - - - - - - - - - 0 - 390 - 720 - 334 - - - - - 716 - 0 - - - - Channel Scope - - - - 2 - - - 3 - - - 3 - - - 3 - - - 3 - - - - - - 200 - 300 - - - - - Monospace - 8 - - - - - - - - - - - - - RollupWidget - QWidget -
gui/rollupwidget.h
- 1 -
- - GLSpectrum - QWidget -
gui/glspectrum.h
- 1 -
- - GLSpectrumGUI - QWidget -
gui/glspectrumgui.h
- 1 -
- - ValueDialZ - QWidget -
gui/valuedialz.h
- 1 -
- - ValueDial - QWidget -
gui/valuedial.h
- 1 -
- - ButtonSwitch - QToolButton -
gui/buttonswitch.h
-
- - GLScopeNG - QWidget -
gui/glscopeng.h
- 1 -
- - GLScopeNGGUI - QWidget -
gui/glscopenggui.h
- 1 -
-
- - - - -
diff --git a/plugins/channelrx/chanalyzerng/chanalyzerngplugin.cpp b/plugins/channelrx/chanalyzerng/chanalyzerngplugin.cpp deleted file mode 100644 index 4161511b4..000000000 --- a/plugins/channelrx/chanalyzerng/chanalyzerngplugin.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2017 Edouard Griffiths, F4EXB // -// // -// This program is free software; you can redistribute it and/or modify // -// it under the terms of the GNU General Public License as published by // -// the Free Software Foundation as version 3 of the License, or // -// // -// This program is distributed in the hope that it will be useful, // -// but WITHOUT ANY WARRANTY; without even the implied warranty of // -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // -// GNU General Public License V3 for more details. // -// // -// You should have received a copy of the GNU General Public License // -// along with this program. If not, see . // -/////////////////////////////////////////////////////////////////////////////////// - -#include - -#include "plugin/pluginapi.h" -#include "chanalyzerngplugin.h" -#include "chanalyzernggui.h" -#include "chanalyzerng.h" - -const PluginDescriptor ChannelAnalyzerNGPlugin::m_pluginDescriptor = { - QString("Channel Analyzer NG"), - QString("3.9.0"), - QString("(c) Edouard Griffiths, F4EXB"), - QString("https://github.com/f4exb/sdrangel"), - true, - QString("https://github.com/f4exb/sdrangel") -}; - -ChannelAnalyzerNGPlugin::ChannelAnalyzerNGPlugin(QObject* parent) : - QObject(parent), - m_pluginAPI(0) -{ -} - -const PluginDescriptor& ChannelAnalyzerNGPlugin::getPluginDescriptor() const -{ - return m_pluginDescriptor; -} - -void ChannelAnalyzerNGPlugin::initPlugin(PluginAPI* pluginAPI) -{ - m_pluginAPI = pluginAPI; - - // register demodulator - m_pluginAPI->registerRxChannel(ChannelAnalyzerNG::m_channelIdURI, ChannelAnalyzerNG::m_channelId, this); -} - -PluginInstanceGUI* ChannelAnalyzerNGPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) -{ - return ChannelAnalyzerNGGUI::create(m_pluginAPI, deviceUISet, rxChannel); -} - -BasebandSampleSink* ChannelAnalyzerNGPlugin::createRxChannelBS(DeviceSourceAPI *deviceAPI) -{ - return new ChannelAnalyzerNG(deviceAPI); -} - -ChannelSinkAPI* ChannelAnalyzerNGPlugin::createRxChannelCS(DeviceSourceAPI *deviceAPI) -{ - return new ChannelAnalyzerNG(deviceAPI); -} - diff --git a/plugins/channelrx/daemonsink/CMakeLists.txt b/plugins/channelrx/daemonsink/CMakeLists.txt new file mode 100644 index 000000000..8507daf6c --- /dev/null +++ b/plugins/channelrx/daemonsink/CMakeLists.txt @@ -0,0 +1,83 @@ +project(daemonsink) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +if (HAS_SSSE3) + message(STATUS "DaemonSink: use SSSE3 SIMD" ) +elseif (HAS_NEON) + message(STATUS "DaemonSink: use Neon SIMD" ) +else() + message(STATUS "DaemonSink: Unsupported architecture") + return() +endif() + +set(daemonsink_SOURCES + daemonsink.cpp + daemonsinkgui.cpp + daemonsinksettings.cpp + daemonsinkthread.cpp + daemonsinkplugin.cpp +) + +set(daemonsink_HEADERS + daemonsink.h + daemonsinkgui.h + daemonsinksettings.h + daemonsinkthread.h + daemonsinkplugin.h +) + +set(daemonsink_FORMS + daemonsinkgui.ui +) + +#include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +qt5_wrap_ui(daemonsink_FORMS_HEADERS ${daemonsink_FORMS}) + +add_library(daemonsink SHARED + ${daemonsink_SOURCES} + ${daemonsink_HEADERS_MOC} + ${daemonsink_FORMS_HEADERS} +) + +if (BUILD_DEBIAN) +target_include_directories(daemonsink PUBLIC + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${LIBCM256CCSRC} +) +else (BUILD_DEBIAN) +target_include_directories(daemonsink PUBLIC + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CM256CC_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +if (BUILD_DEBIAN) +target_link_libraries(daemonsink + ${QT_LIBRARIES} + cm256cc + sdrbase + sdrgui + swagger +) +else (BUILD_DEBIAN) +target_link_libraries(daemonsink + ${QT_LIBRARIES} + ${CM256CC_LIBRARIES} + sdrbase + sdrgui + swagger +) +endif (BUILD_DEBIAN) + +target_link_libraries(daemonsink Qt5::Core Qt5::Widgets) + +install(TARGETS daemonsink DESTINATION lib/plugins/channelrx) diff --git a/plugins/channelrx/daemonsink/daemonsink.cpp b/plugins/channelrx/daemonsink/daemonsink.cpp new file mode 100644 index 000000000..ac5dfd02e --- /dev/null +++ b/plugins/channelrx/daemonsink/daemonsink.cpp @@ -0,0 +1,440 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon sink channel (Rx) // +// // +// SDRdaemon is a detached SDR front end that handles the interface with a // +// physical device and sends or receives the I/Q samples stream to or from a // +// SDRangel instance via UDP. It is controlled via a Web REST API. // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#include "SWGChannelSettings.h" + +#include "util/simpleserializer.h" +#include "dsp/threadedbasebandsamplesink.h" +#include "dsp/downchannelizer.h" +#include "dsp/dspcommands.h" +#include "device/devicesourceapi.h" +#include "daemonsinkthread.h" +#include "daemonsink.h" + +MESSAGE_CLASS_DEFINITION(DaemonSink::MsgConfigureDaemonSink, Message) +MESSAGE_CLASS_DEFINITION(DaemonSink::MsgSampleRateNotification, Message) + +const QString DaemonSink::m_channelIdURI = "sdrangel.channel.daemonsink"; +const QString DaemonSink::m_channelId = "DaemonSink"; + +DaemonSink::DaemonSink(DeviceSourceAPI *deviceAPI) : + ChannelSinkAPI(m_channelIdURI), + m_deviceAPI(deviceAPI), + m_running(false), + m_sinkThread(0), + m_txBlockIndex(0), + m_frameCount(0), + m_sampleIndex(0), + m_dataBlock(0), + m_centerFrequency(0), + m_sampleRate(48000), + m_nbBlocksFEC(0), + m_txDelay(35), + m_dataAddress("127.0.0.1"), + m_dataPort(9090) +{ + setObjectName(m_channelId); + + m_channelizer = new DownChannelizer(this); + m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); + m_deviceAPI->addThreadedSink(m_threadedChannelizer); + m_deviceAPI->addChannelAPI(this); +} + +DaemonSink::~DaemonSink() +{ + m_dataBlockMutex.lock(); + if (m_dataBlock && !m_dataBlock->m_txControlBlock.m_complete) { + delete m_dataBlock; + } + m_dataBlockMutex.unlock(); + m_deviceAPI->removeChannelAPI(this); + m_deviceAPI->removeThreadedSink(m_threadedChannelizer); + delete m_threadedChannelizer; + delete m_channelizer; +} + +void DaemonSink::setTxDelay(int txDelay, int nbBlocksFEC) +{ + double txDelayRatio = txDelay / 100.0; + int samplesPerBlock = SDRDaemonNbBytesPerBlock / sizeof(Sample); + double delay = m_sampleRate == 0 ? 1.0 : (127*samplesPerBlock*txDelayRatio) / m_sampleRate; + delay /= 128 + nbBlocksFEC; + m_txDelay = roundf(delay*1e6); // microseconds + qDebug() << "DaemonSink::setTxDelay:" + << " " << txDelay + << "% m_txDelay: " << m_txDelay << "us" + << " m_sampleRate: " << m_sampleRate << "S/s"; +} + +void DaemonSink::setNbBlocksFEC(int nbBlocksFEC) +{ + qDebug() << "DaemonSink::setNbBlocksFEC: nbBlocksFEC: " << nbBlocksFEC; + m_nbBlocksFEC = nbBlocksFEC; +} + +void DaemonSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst __attribute__((unused))) +{ + SampleVector::const_iterator it = begin; + + while (it != end) + { + int inSamplesIndex = it - begin; + int inRemainingSamples = end - it; + + if (m_txBlockIndex == 0) + { + struct timeval tv; + SDRDaemonMetaDataFEC metaData; + gettimeofday(&tv, 0); + + metaData.m_centerFrequency = m_centerFrequency; + metaData.m_sampleRate = m_sampleRate; + metaData.m_sampleBytes = (SDR_RX_SAMP_SZ <= 16 ? 2 : 4); + metaData.m_sampleBits = SDR_RX_SAMP_SZ; + metaData.m_nbOriginalBlocks = SDRDaemonNbOrginalBlocks; + metaData.m_nbFECBlocks = m_nbBlocksFEC; + metaData.m_tv_sec = tv.tv_sec; + metaData.m_tv_usec = tv.tv_usec; + + if (!m_dataBlock) { // on the very first cycle there is no data block allocated + m_dataBlock = new SDRDaemonDataBlock(); + } + + boost::crc_32_type crc32; + crc32.process_bytes(&metaData, 20); + metaData.m_crc32 = crc32.checksum(); + SDRDaemonSuperBlock& superBlock = m_dataBlock->m_superBlocks[0]; // first block + superBlock.init(); + superBlock.m_header.m_frameIndex = m_frameCount; + superBlock.m_header.m_blockIndex = m_txBlockIndex; + superBlock.m_header.m_sampleBytes = (SDR_RX_SAMP_SZ <= 16 ? 2 : 4); + superBlock.m_header.m_sampleBits = SDR_RX_SAMP_SZ; + memcpy((void *) &superBlock.m_protectedBlock, (const void *) &metaData, sizeof(SDRDaemonMetaDataFEC)); + + if (!(metaData == m_currentMetaFEC)) + { + qDebug() << "SDRDaemonChannelSink::feed: meta: " + << "|" << metaData.m_centerFrequency + << ":" << metaData.m_sampleRate + << ":" << (int) (metaData.m_sampleBytes & 0xF) + << ":" << (int) metaData.m_sampleBits + << "|" << (int) metaData.m_nbOriginalBlocks + << ":" << (int) metaData.m_nbFECBlocks + << "|" << metaData.m_tv_sec + << ":" << metaData.m_tv_usec; + + m_currentMetaFEC = metaData; + } + + m_txBlockIndex = 1; // next Tx block with data + } // block zero + + // handle different sample sizes... + int samplesPerBlock = SDRDaemonNbBytesPerBlock / (SDR_RX_SAMP_SZ <= 16 ? 4 : 8); // two I or Q samples + if (m_sampleIndex + inRemainingSamples < samplesPerBlock) // there is still room in the current super block + { + memcpy((void *) &m_superBlock.m_protectedBlock.buf[m_sampleIndex*sizeof(Sample)], + (const void *) &(*(begin+inSamplesIndex)), + inRemainingSamples * sizeof(Sample)); + m_sampleIndex += inRemainingSamples; + it = end; // all input samples are consumed + } + else // complete super block and initiate the next if not end of frame + { + memcpy((void *) &m_superBlock.m_protectedBlock.buf[m_sampleIndex*sizeof(Sample)], + (const void *) &(*(begin+inSamplesIndex)), + (samplesPerBlock - m_sampleIndex) * sizeof(Sample)); + it += samplesPerBlock - m_sampleIndex; + m_sampleIndex = 0; + + m_superBlock.m_header.m_frameIndex = m_frameCount; + m_superBlock.m_header.m_blockIndex = m_txBlockIndex; + m_superBlock.m_header.m_sampleBytes = (SDR_RX_SAMP_SZ <= 16 ? 2 : 4); + m_superBlock.m_header.m_sampleBits = SDR_RX_SAMP_SZ; + m_dataBlock->m_superBlocks[m_txBlockIndex] = m_superBlock; + + if (m_txBlockIndex == SDRDaemonNbOrginalBlocks - 1) // frame complete + { + m_dataBlockMutex.lock(); + m_dataBlock->m_txControlBlock.m_frameIndex = m_frameCount; + m_dataBlock->m_txControlBlock.m_processed = false; + m_dataBlock->m_txControlBlock.m_complete = true; + m_dataBlock->m_txControlBlock.m_nbBlocksFEC = m_nbBlocksFEC; + m_dataBlock->m_txControlBlock.m_txDelay = m_txDelay; + m_dataBlock->m_txControlBlock.m_dataAddress = m_dataAddress; + m_dataBlock->m_txControlBlock.m_dataPort = m_dataPort; + + emit dataBlockAvailable(m_dataBlock); + m_dataBlock = new SDRDaemonDataBlock(); // create a new one immediately + m_dataBlockMutex.unlock(); + + m_txBlockIndex = 0; + m_frameCount++; + } + else + { + m_txBlockIndex++; + } + } + } +} + +void DaemonSink::start() +{ + qDebug("DaemonSink::start"); + + memset((void *) &m_currentMetaFEC, 0, sizeof(SDRDaemonMetaDataFEC)); + + if (m_running) { + stop(); + } + + m_sinkThread = new DaemonSinkThread(); + connect(this, + SIGNAL(dataBlockAvailable(SDRDaemonDataBlock *)), + m_sinkThread, + SLOT(processDataBlock(SDRDaemonDataBlock *)), + Qt::QueuedConnection); + m_sinkThread->startStop(true); + m_running = true; +} + +void DaemonSink::stop() +{ + qDebug("DaemonSink::stop"); + + if (m_sinkThread != 0) + { + m_sinkThread->startStop(false); + m_sinkThread->deleteLater(); + m_sinkThread = 0; + } + + m_running = false; +} + +bool DaemonSink::handleMessage(const Message& cmd __attribute__((unused))) +{ + if (DownChannelizer::MsgChannelizerNotification::match(cmd)) + { + DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd; + + qDebug() << "DaemonSink::handleMessage: MsgChannelizerNotification:" + << " channelSampleRate: " << notif.getSampleRate() + << " offsetFrequency: " << notif.getFrequencyOffset(); + + if (notif.getSampleRate() > 0) { + setSampleRate(notif.getSampleRate()); + } + + setTxDelay(m_settings.m_txDelay, m_settings.m_nbFECBlocks); + + if (m_guiMessageQueue) + { + MsgSampleRateNotification *msg = MsgSampleRateNotification::create(notif.getSampleRate()); + m_guiMessageQueue->push(msg); + } + + return true; + } + else if (DSPSignalNotification::match(cmd)) + { + DSPSignalNotification& notif = (DSPSignalNotification&) cmd; + + qDebug() << "DaemonSink::handleMessage: DSPSignalNotification:" + << " inputSampleRate: " << notif.getSampleRate() + << " centerFrequency: " << notif.getCenterFrequency(); + + setCenterFrequency(notif.getCenterFrequency()); + + return true; + } + else if (MsgConfigureDaemonSink::match(cmd)) + { + MsgConfigureDaemonSink& cfg = (MsgConfigureDaemonSink&) cmd; + qDebug() << "DaemonSink::handleMessage: MsgConfigureDaemonSink"; + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } + else + { + return false; + } +} + +QByteArray DaemonSink::serialize() const +{ + return m_settings.serialize(); +} + +bool DaemonSink::deserialize(const QByteArray& data __attribute__((unused))) +{ + if (m_settings.deserialize(data)) + { + MsgConfigureDaemonSink *msg = MsgConfigureDaemonSink::create(m_settings, true); + m_inputMessageQueue.push(msg); + return true; + } + else + { + m_settings.resetToDefaults(); + MsgConfigureDaemonSink *msg = MsgConfigureDaemonSink::create(m_settings, true); + m_inputMessageQueue.push(msg); + return false; + } +} + +void DaemonSink::applySettings(const DaemonSinkSettings& settings, bool force) +{ + qDebug() << "DaemonSink::applySettings:" + << " m_nbFECBlocks: " << settings.m_nbFECBlocks + << " m_txDelay: " << settings.m_txDelay + << " m_dataAddress: " << settings.m_dataAddress + << " m_dataPort: " << settings.m_dataPort + << " force: " << force; + + if ((m_settings.m_nbFECBlocks != settings.m_nbFECBlocks) || force) { + setNbBlocksFEC(settings.m_nbFECBlocks); + setTxDelay(settings.m_txDelay, settings.m_nbFECBlocks); + } + + if ((m_settings.m_txDelay != settings.m_txDelay) || force) { + setTxDelay(settings.m_txDelay, settings.m_nbFECBlocks); + } + + if ((m_settings.m_dataAddress != settings.m_dataAddress) || force) { + m_dataAddress = settings.m_dataAddress; + } + + if ((m_settings.m_dataPort != settings.m_dataPort) || force) { + m_dataPort = settings.m_dataPort; + } + + m_settings = settings; +} + +int DaemonSink::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setDaemonSinkSettings(new SWGSDRangel::SWGDaemonSinkSettings()); + response.getDaemonSinkSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int DaemonSink::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + DaemonSinkSettings settings = m_settings; + + if (channelSettingsKeys.contains("nbFECBlocks")) + { + int nbFECBlocks = response.getDaemonSinkSettings()->getNbFecBlocks(); + + if ((nbFECBlocks < 0) || (nbFECBlocks > 127)) { + settings.m_nbFECBlocks = 8; + } else { + settings.m_nbFECBlocks = response.getDaemonSinkSettings()->getNbFecBlocks(); + } + } + + if (channelSettingsKeys.contains("txDelay")) + { + int txDelay = response.getDaemonSinkSettings()->getTxDelay(); + + if (txDelay < 0) { + settings.m_txDelay = 35; + } else { + settings.m_txDelay = txDelay; + } + } + + if (channelSettingsKeys.contains("dataAddress")) { + settings.m_dataAddress = *response.getDaemonSinkSettings()->getDataAddress(); + } + + if (channelSettingsKeys.contains("dataPort")) + { + int dataPort = response.getDaemonSinkSettings()->getDataPort(); + + if ((dataPort < 1024) || (dataPort > 65535)) { + settings.m_dataPort = 9090; + } else { + settings.m_dataPort = dataPort; + } + } + + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getDaemonSinkSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getDaemonSinkSettings()->getTitle(); + } + + + MsgConfigureDaemonSink *msg = MsgConfigureDaemonSink::create(settings, force); + m_inputMessageQueue.push(msg); + + qDebug("DaemonSink::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureDaemonSink *msgToGUI = MsgConfigureDaemonSink::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +void DaemonSink::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const DaemonSinkSettings& settings) +{ + response.getDaemonSinkSettings()->setNbFecBlocks(settings.m_nbFECBlocks); + response.getDaemonSinkSettings()->setTxDelay(settings.m_txDelay); + + if (response.getDaemonSinkSettings()->getDataAddress()) { + *response.getDaemonSinkSettings()->getDataAddress() = settings.m_dataAddress; + } else { + response.getDaemonSinkSettings()->setDataAddress(new QString(settings.m_dataAddress)); + } + + response.getDaemonSinkSettings()->setDataPort(settings.m_dataPort); + response.getDaemonSinkSettings()->setRgbColor(settings.m_rgbColor); + + if (response.getDaemonSinkSettings()->getTitle()) { + *response.getDaemonSinkSettings()->getTitle() = settings.m_title; + } else { + response.getDaemonSinkSettings()->setTitle(new QString(settings.m_title)); + } + +} diff --git a/plugins/channelrx/daemonsink/daemonsink.h b/plugins/channelrx/daemonsink/daemonsink.h new file mode 100644 index 000000000..b50986c48 --- /dev/null +++ b/plugins/channelrx/daemonsink/daemonsink.h @@ -0,0 +1,156 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon sink channel (Rx) // +// // +// SDRdaemon is a detached SDR front end that handles the interface with a // +// physical device and sends or receives the I/Q samples stream to or from a // +// SDRangel instance via UDP. It is controlled via a Web REST API. // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_DAEMONSINK_H_ +#define INCLUDE_DAEMONSINK_H_ + +#include +#include + +#include "dsp/basebandsamplesink.h" +#include "channel/channelsinkapi.h" +#include "channel/sdrdaemondatablock.h" +#include "daemonsinksettings.h" + +class DeviceSourceAPI; +class ThreadedBasebandSampleSink; +class DownChannelizer; +class DaemonSinkThread; + +class DaemonSink : public BasebandSampleSink, public ChannelSinkAPI { + Q_OBJECT +public: + class MsgConfigureDaemonSink : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const DaemonSinkSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureDaemonSink* create(const DaemonSinkSettings& settings, bool force) + { + return new MsgConfigureDaemonSink(settings, force); + } + + private: + DaemonSinkSettings m_settings; + bool m_force; + + MsgConfigureDaemonSink(const DaemonSinkSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgSampleRateNotification : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgSampleRateNotification* create(int sampleRate) { + return new MsgSampleRateNotification(sampleRate); + } + + int getSampleRate() const { return m_sampleRate; } + + private: + + MsgSampleRateNotification(int sampleRate) : + Message(), + m_sampleRate(sampleRate) + { } + + int m_sampleRate; + }; + + DaemonSink(DeviceSourceAPI *deviceAPI); + virtual ~DaemonSink(); + virtual void destroy() { delete this; } + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po); + virtual void start(); + virtual void stop(); + virtual bool handleMessage(const Message& cmd); + + virtual void getIdentifier(QString& id) { id = objectName(); } + virtual void getTitle(QString& title) { title = "SDRDaemon Sink"; } + virtual qint64 getCenterFrequency() const { return 0; } + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + /** Set center frequency given in Hz */ + void setCenterFrequency(uint64_t centerFrequency) { m_centerFrequency = centerFrequency / 1000; } + + /** Set sample rate given in Hz */ + void setSampleRate(uint32_t sampleRate) { m_sampleRate = sampleRate; } + + void setNbBlocksFEC(int nbBlocksFEC); + void setTxDelay(int txDelay, int nbBlocksFEC); + void setDataAddress(const QString& address) { m_dataAddress = address; } + void setDataPort(uint16_t port) { m_dataPort = port; } + + static const QString m_channelIdURI; + static const QString m_channelId; + +signals: + void dataBlockAvailable(SDRDaemonDataBlock *dataBlock); + +private: + DeviceSourceAPI *m_deviceAPI; + ThreadedBasebandSampleSink* m_threadedChannelizer; + DownChannelizer* m_channelizer; + bool m_running; + + DaemonSinkSettings m_settings; + DaemonSinkThread *m_sinkThread; + + int m_txBlockIndex; //!< Current index in blocks to transmit in the Tx row + uint16_t m_frameCount; //!< transmission frame count + int m_sampleIndex; //!< Current sample index in protected block data + SDRDaemonSuperBlock m_superBlock; + SDRDaemonMetaDataFEC m_currentMetaFEC; + SDRDaemonDataBlock *m_dataBlock; + QMutex m_dataBlockMutex; + + uint64_t m_centerFrequency; + uint32_t m_sampleRate; + int m_nbBlocksFEC; + int m_txDelay; + QString m_dataAddress; + uint16_t m_dataPort; + + void applySettings(const DaemonSinkSettings& settings, bool force = false); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const DaemonSinkSettings& settings); +}; + +#endif /* INCLUDE_DAEMONSINK_H_ */ diff --git a/plugins/channelrx/daemonsink/daemonsinkgui.cpp b/plugins/channelrx/daemonsink/daemonsinkgui.cpp new file mode 100644 index 000000000..83338b822 --- /dev/null +++ b/plugins/channelrx/daemonsink/daemonsinkgui.cpp @@ -0,0 +1,301 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "device/devicesourceapi.h" +#include "device/deviceuiset.h" +#include "gui/basicchannelsettingsdialog.h" +#include "mainwindow.h" + +#include "daemonsink.h" +#include "ui_daemonsinkgui.h" +#include "daemonsinkgui.h" + +DaemonSinkGUI* DaemonSinkGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *channelRx) +{ + DaemonSinkGUI* gui = new DaemonSinkGUI(pluginAPI, deviceUISet, channelRx); + return gui; +} + +void DaemonSinkGUI::destroy() +{ + delete this; +} + +void DaemonSinkGUI::setName(const QString& name) +{ + setObjectName(name); +} + +QString DaemonSinkGUI::getName() const +{ + return objectName(); +} + +qint64 DaemonSinkGUI::getCenterFrequency() const { + return 0; +} + +void DaemonSinkGUI::setCenterFrequency(qint64 centerFrequency __attribute__((unused))) +{ +} + +void DaemonSinkGUI::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + applySettings(true); +} + +QByteArray DaemonSinkGUI::serialize() const +{ + return m_settings.serialize(); +} + +bool DaemonSinkGUI::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) { + displaySettings(); + applySettings(true); + return true; + } else { + resetToDefaults(); + return false; + } +} + +bool DaemonSinkGUI::handleMessage(const Message& message) +{ + if (DaemonSink::MsgSampleRateNotification::match(message)) + { + DaemonSink::MsgSampleRateNotification& notif = (DaemonSink::MsgSampleRateNotification&) message; + m_channelMarker.setBandwidth(notif.getSampleRate()); + m_sampleRate = notif.getSampleRate(); + updateTxDelayTime(); + return true; + } + else if (DaemonSink::MsgConfigureDaemonSink::match(message)) + { + const DaemonSink::MsgConfigureDaemonSink& cfg = (DaemonSink::MsgConfigureDaemonSink&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else + { + return false; + } +} + +DaemonSinkGUI::DaemonSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *channelrx, QWidget* parent) : + RollupWidget(parent), + ui(new Ui::DaemonSinkGUI), + m_pluginAPI(pluginAPI), + m_deviceUISet(deviceUISet), + m_sampleRate(0), + m_tickCount(0) +{ + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose, true); + connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + + m_daemonSink = (DaemonSink*) channelrx; + m_daemonSink->setMessageQueueToGUI(getInputMessageQueue()); + + m_channelMarker.blockSignals(true); + m_channelMarker.setColor(m_settings.m_rgbColor); + m_channelMarker.setCenterFrequency(0); + m_channelMarker.setTitle("Daemon source"); + m_channelMarker.blockSignals(false); + m_channelMarker.setVisible(true); // activate signal on the last setting only + + m_settings.setChannelMarker(&m_channelMarker); + + m_deviceUISet->registerRxChannelInstance(DaemonSink::m_channelIdURI, this); + m_deviceUISet->addChannelMarker(&m_channelMarker); + m_deviceUISet->addRollupWidget(this); + + connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages())); + //connect(&(m_deviceUISet->m_deviceSourceAPI->getMasterTimer()), SIGNAL(timeout()), this, SLOT(tick())); + + m_time.start(); + + displaySettings(); + applySettings(true); +} + +DaemonSinkGUI::~DaemonSinkGUI() +{ + m_deviceUISet->removeRxChannelInstance(this); + delete ui; +} + +void DaemonSinkGUI::blockApplySettings(bool block) +{ + m_doApplySettings = !block; +} + +void DaemonSinkGUI::applySettings(bool force) +{ + if (m_doApplySettings) + { + setTitleColor(m_channelMarker.getColor()); + + DaemonSink::MsgConfigureDaemonSink* message = DaemonSink::MsgConfigureDaemonSink::create(m_settings, force); + m_daemonSink->getInputMessageQueue()->push(message); + } +} + +void DaemonSinkGUI::displaySettings() +{ + m_channelMarker.blockSignals(true); + m_channelMarker.setCenterFrequency(0); + m_channelMarker.setTitle(m_settings.m_title); + m_channelMarker.setBandwidth(m_sampleRate); // TODO + m_channelMarker.blockSignals(false); + m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only + + setTitleColor(m_settings.m_rgbColor); + setWindowTitle(m_channelMarker.getTitle()); + + blockApplySettings(true); + ui->dataAddress->setText(m_settings.m_dataAddress); + ui->dataPort->setText(tr("%1").arg(m_settings.m_dataPort)); + QString s = QString::number(128 + m_settings.m_nbFECBlocks, 'f', 0); + QString s1 = QString::number(m_settings.m_nbFECBlocks, 'f', 0); + ui->nominalNbBlocksText->setText(tr("%1/%2").arg(s).arg(s1)); + ui->txDelayText->setText(tr("%1%").arg(m_settings.m_txDelay)); + ui->txDelay->setValue(m_settings.m_txDelay); + updateTxDelayTime(); + blockApplySettings(false); +} + +void DaemonSinkGUI::leaveEvent(QEvent*) +{ + m_channelMarker.setHighlighted(false); +} + +void DaemonSinkGUI::enterEvent(QEvent*) +{ + m_channelMarker.setHighlighted(true); +} + +void DaemonSinkGUI::handleSourceMessages() +{ + Message* message; + + while ((message = getInputMessageQueue()->pop()) != 0) + { + if (handleMessage(*message)) + { + delete message; + } + } +} + +void DaemonSinkGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rollDown __attribute__((unused))) +{ +} + +void DaemonSinkGUI::onMenuDialogCalled(const QPoint &p) +{ + BasicChannelSettingsDialog dialog(&m_channelMarker, this); + dialog.move(p); + dialog.exec(); + + m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); + m_settings.m_title = m_channelMarker.getTitle(); + + setWindowTitle(m_settings.m_title); + setTitleColor(m_settings.m_rgbColor); + + applySettings(); +} + +void DaemonSinkGUI::on_dataAddress_returnPressed() +{ + m_settings.m_dataAddress = ui->dataAddress->text(); + applySettings(); +} + +void DaemonSinkGUI::on_dataPort_returnPressed() +{ + bool dataOk; + int dataPort = ui->dataPort->text().toInt(&dataOk); + + if((!dataOk) || (dataPort < 1024) || (dataPort > 65535)) + { + return; + } + else + { + m_settings.m_dataPort = dataPort; + } + + applySettings(); +} + +void DaemonSinkGUI::on_dataApplyButton_clicked(bool checked __attribute__((unused))) +{ + m_settings.m_dataAddress = ui->dataAddress->text(); + + bool dataOk; + int udpDataPort = ui->dataPort->text().toInt(&dataOk); + + if((dataOk) && (udpDataPort >= 1024) && (udpDataPort < 65535)) + { + m_settings.m_dataPort = udpDataPort; + } + + applySettings(); +} + +void DaemonSinkGUI::on_txDelay_valueChanged(int value) +{ + m_settings.m_txDelay = value; // percentage + ui->txDelayText->setText(tr("%1%").arg(value)); + updateTxDelayTime(); + applySettings(); +} + +void DaemonSinkGUI::on_nbFECBlocks_valueChanged(int value) +{ + m_settings.m_nbFECBlocks = value; + int nbOriginalBlocks = 128; + int nbFECBlocks = value; + QString s = QString::number(nbOriginalBlocks + nbFECBlocks, 'f', 0); + QString s1 = QString::number(nbFECBlocks, 'f', 0); + ui->nominalNbBlocksText->setText(tr("%1/%2").arg(s).arg(s1)); + updateTxDelayTime(); + applySettings(); +} + +void DaemonSinkGUI::updateTxDelayTime() +{ + double txDelayRatio = m_settings.m_txDelay / 100.0; + int samplesPerBlock = SDRDaemonNbBytesPerBlock / sizeof(Sample); + double delay = m_sampleRate == 0 ? 0.0 : (127*samplesPerBlock*txDelayRatio) / m_sampleRate; + delay /= 128 + m_settings.m_nbFECBlocks; + ui->txDelayTime->setText(tr("%1µs").arg(QString::number(delay*1e6, 'f', 0))); +} + +void DaemonSinkGUI::tick() +{ + if (++m_tickCount == 20) { // once per second + m_tickCount = 0; + } +} diff --git a/plugins/channelrx/daemonsink/daemonsinkgui.h b/plugins/channelrx/daemonsink/daemonsinkgui.h new file mode 100644 index 000000000..139f3281c --- /dev/null +++ b/plugins/channelrx/daemonsink/daemonsinkgui.h @@ -0,0 +1,97 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_CHANNELRX_DAEMONSINK_DAEMONSINKGUI_H_ +#define PLUGINS_CHANNELRX_DAEMONSINK_DAEMONSINKGUI_H_ + +#include +#include + +#include "plugin/plugininstancegui.h" +#include "dsp/channelmarker.h" +#include "gui/rollupwidget.h" +#include "util/messagequeue.h" + +#include "daemonsinksettings.h" + +class PluginAPI; +class DeviceUISet; +class DaemonSink; +class BasebandSampleSink; + +namespace Ui { + class DaemonSinkGUI; +} + +class DaemonSinkGUI : public RollupWidget, public PluginInstanceGUI { + Q_OBJECT +public: + static DaemonSinkGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel); + virtual void destroy(); + + void setName(const QString& name); + QString getName() const; + virtual qint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + virtual bool handleMessage(const Message& message); + +private: + Ui::DaemonSinkGUI* ui; + PluginAPI* m_pluginAPI; + DeviceUISet* m_deviceUISet; + ChannelMarker m_channelMarker; + DaemonSinkSettings m_settings; + int m_sampleRate; + quint64 m_deviceCenterFrequency; //!< Center frequency in device + bool m_doApplySettings; + + DaemonSink* m_daemonSink; + MessageQueue m_inputMessageQueue; + + QTime m_time; + uint32_t m_tickCount; + + explicit DaemonSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); + virtual ~DaemonSinkGUI(); + + void blockApplySettings(bool block); + void applySettings(bool force = false); + void displaySettings(); + void updateTxDelayTime(); + + void leaveEvent(QEvent*); + void enterEvent(QEvent*); + +private slots: + void handleSourceMessages(); + void on_dataAddress_returnPressed(); + void on_dataPort_returnPressed(); + void on_dataApplyButton_clicked(bool checked); + void on_nbFECBlocks_valueChanged(int value); + void on_txDelay_valueChanged(int value); + void onWidgetRolled(QWidget* widget, bool rollDown); + void onMenuDialogCalled(const QPoint& p); + void tick(); +}; + + + +#endif /* PLUGINS_CHANNELRX_DAEMONSINK_DAEMONSINKGUI_H_ */ diff --git a/plugins/channelrx/daemonsink/daemonsinkgui.ui b/plugins/channelrx/daemonsink/daemonsinkgui.ui new file mode 100644 index 000000000..3bf070450 --- /dev/null +++ b/plugins/channelrx/daemonsink/daemonsinkgui.ui @@ -0,0 +1,323 @@ + + + DaemonSinkGUI + + + + 0 + 0 + 320 + 100 + + + + + 0 + 0 + + + + + 320 + 100 + + + + + 320 + 16777215 + + + + + Liberation Sans + 9 + + + + Daemon sink + + + + + 10 + 10 + 301 + 81 + + + + Settings + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + + + 30 + 0 + + + + Data + + + + + + + + 120 + 0 + + + + Local data listener address + + + 000.000.000.000 + + + 0... + + + + + + + : + + + + + + + + 50 + 16777215 + + + + Local data listener port + + + 00000 + + + 0 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 30 + 16777215 + + + + Set local data listener address and port + + + Set + + + + + + + + + + + FEC + + + + + + + + 24 + 24 + + + + Number of FEC blocks per frame + + + 32 + + + 1 + + + 0 + + + + + + + + 50 + 0 + + + + Nb total blocks / Nb FEC blocks + + + 000/00 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + + + + + Udly + + + + + + + + 24 + 24 + + + + Delay between consecutive UDP packets in percentage of nominal UDP packet process time + + + 0 + + + 70 + + + 1 + + + 35 + + + + + + + + 30 + 0 + + + + 50% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 55 + 0 + + + + 10000us + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + RollupWidget + QWidget +
gui/rollupwidget.h
+ 1 +
+
+ + + + +
diff --git a/plugins/channelrx/daemonsink/daemonsinkplugin.cpp b/plugins/channelrx/daemonsink/daemonsinkplugin.cpp new file mode 100644 index 000000000..e65dfae0d --- /dev/null +++ b/plugins/channelrx/daemonsink/daemonsinkplugin.cpp @@ -0,0 +1,81 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "daemonsinkplugin.h" + +#include +#include "plugin/pluginapi.h" + +#ifndef SERVER_MODE +#include "daemonsinkgui.h" +#endif +#include "daemonsink.h" + +const PluginDescriptor DaemonSinkPlugin::m_pluginDescriptor = { + QString("Daemon channel Sink"), + QString("4.1.0"), + QString("(c) Edouard Griffiths, F4EXB"), + QString("https://github.com/f4exb/sdrangel"), + true, + QString("https://github.com/f4exb/sdrangel") +}; + +DaemonSinkPlugin::DaemonSinkPlugin(QObject* parent) : + QObject(parent), + m_pluginAPI(0) +{ +} + +const PluginDescriptor& DaemonSinkPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void DaemonSinkPlugin::initPlugin(PluginAPI* pluginAPI) +{ + m_pluginAPI = pluginAPI; + + // register channel Source + m_pluginAPI->registerRxChannel(DaemonSink::m_channelIdURI, DaemonSink::m_channelId, this); +} + +#ifdef SERVER_MODE +PluginInstanceGUI* DaemonSinkPlugin::createRxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSink *rxChannel __attribute__((unused))) +{ + return 0; +} +#else +PluginInstanceGUI* DaemonSinkPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) +{ + return DaemonSinkGUI::create(m_pluginAPI, deviceUISet, rxChannel); +} +#endif + +BasebandSampleSink* DaemonSinkPlugin::createRxChannelBS(DeviceSourceAPI *deviceAPI) +{ + return new DaemonSink(deviceAPI); +} + +ChannelSinkAPI* DaemonSinkPlugin::createRxChannelCS(DeviceSourceAPI *deviceAPI) +{ + return new DaemonSink(deviceAPI); +} + + + + diff --git a/plugins/channelrx/chanalyzerng/chanalyzerngplugin.h b/plugins/channelrx/daemonsink/daemonsinkplugin.h similarity index 59% rename from plugins/channelrx/chanalyzerng/chanalyzerngplugin.h rename to plugins/channelrx/daemonsink/daemonsinkplugin.h index b6c0ce9d7..16e0792db 100644 --- a/plugins/channelrx/chanalyzerng/chanalyzerngplugin.h +++ b/plugins/channelrx/daemonsink/daemonsinkplugin.h @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2017 Edouard Griffiths, F4EXB // +// Copyright (C) 2016 Edouard Griffiths, F4EXB // // // // This program is free software; you can redistribute it and/or modify // // it under the terms of the GNU General Public License as published by // @@ -14,35 +14,35 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef INCLUDE_CHANALYZERNGPLUGIN_H -#define INCLUDE_CHANALYZERNGPLUGIN_H +#ifndef PLUGINS_CHANNELRX_DAEMONSINK_DAEMONSINKPLUGIN_H_ +#define PLUGINS_CHANNELRX_DAEMONSINK_DAEMONSINKPLUGIN_H_ + #include - #include "plugin/plugininterface.h" class DeviceUISet; class BasebandSampleSink; -class ChannelAnalyzerNGPlugin : public QObject, PluginInterface { - Q_OBJECT - Q_INTERFACES(PluginInterface) - Q_PLUGIN_METADATA(IID "sdrangel.channel.chanalyzerng") +class DaemonSinkPlugin : public QObject, PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID "sdrangel.demod.daemonsink") public: - explicit ChannelAnalyzerNGPlugin(QObject* parent = NULL); + explicit DaemonSinkPlugin(QObject* parent = 0); - const PluginDescriptor& getPluginDescriptor() const; - void initPlugin(PluginAPI* pluginAPI); + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); - virtual PluginInstanceGUI* createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel); - virtual BasebandSampleSink* createRxChannelBS(DeviceSourceAPI *deviceAPI); - virtual ChannelSinkAPI* createRxChannelCS(DeviceSourceAPI *deviceAPI); + virtual PluginInstanceGUI* createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel); + virtual BasebandSampleSink* createRxChannelBS(DeviceSourceAPI *deviceAPI); + virtual ChannelSinkAPI* createRxChannelCS(DeviceSourceAPI *deviceAPI); private: - static const PluginDescriptor m_pluginDescriptor; + static const PluginDescriptor m_pluginDescriptor; - PluginAPI* m_pluginAPI; + PluginAPI* m_pluginAPI; }; -#endif // INCLUDE_CHANALYZERNGPLUGIN_H +#endif /* PLUGINS_CHANNELRX_DAEMONSINK_DAEMONSINKPLUGIN_H_ */ diff --git a/plugins/channelrx/daemonsink/daemonsinksettings.cpp b/plugins/channelrx/daemonsink/daemonsinksettings.cpp new file mode 100644 index 000000000..9b81702f0 --- /dev/null +++ b/plugins/channelrx/daemonsink/daemonsinksettings.cpp @@ -0,0 +1,105 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon sink channel (Rx) main settings // +// // +// SDRdaemon is a detached SDR front end that handles the interface with a // +// physical device and sends or receives the I/Q samples stream to or from a // +// SDRangel instance via UDP. It is controlled via a Web REST API. // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "util/simpleserializer.h" +#include "settings/serializable.h" +#include "daemonsinksettings.h" + +DaemonSinkSettings::DaemonSinkSettings() +{ + resetToDefaults(); +} + +void DaemonSinkSettings::resetToDefaults() +{ + m_nbFECBlocks = 0; + m_txDelay = 35; + m_dataAddress = "127.0.0.1"; + m_dataPort = 9090; + m_rgbColor = QColor(140, 4, 4).rgb(); + m_title = "Daemon sink"; +} + +QByteArray DaemonSinkSettings::serialize() const +{ + SimpleSerializer s(1); + s.writeU32(1, m_nbFECBlocks); + s.writeU32(2, m_txDelay); + s.writeString(3, m_dataAddress); + s.writeU32(4, m_dataPort); + s.writeU32(5, m_rgbColor); + s.writeString(6, m_title); + + return s.final(); +} + +bool DaemonSinkSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if(!d.isValid()) + { + resetToDefaults(); + return false; + } + + if(d.getVersion() == 1) + { + uint32_t tmp; + QString strtmp; + + d.readU32(1, &tmp, 0); + + if (tmp < 128) { + m_nbFECBlocks = tmp; + } else { + m_nbFECBlocks = 0; + } + + d.readU32(2, &m_txDelay, 35); + d.readString(3, &m_dataAddress, "127.0.0.1"); + d.readU32(4, &tmp, 0); + + if ((tmp > 1023) && (tmp < 65535)) { + m_dataPort = tmp; + } else { + m_dataPort = 9090; + } + + d.readU32(5, &m_rgbColor, QColor(0, 255, 255).rgb()); + d.readString(6, &m_title, "Daemon sink"); + + return true; + } + else + { + resetToDefaults(); + return false; + } +} + + + + + diff --git a/plugins/channelrx/daemonsink/daemonsinksettings.h b/plugins/channelrx/daemonsink/daemonsinksettings.h new file mode 100644 index 000000000..49584c57c --- /dev/null +++ b/plugins/channelrx/daemonsink/daemonsinksettings.h @@ -0,0 +1,48 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon sink channel (Rx) main settings // +// // +// SDRdaemon is a detached SDR front end that handles the interface with a // +// physical device and sends or receives the I/Q samples stream to or from a // +// SDRangel instance via UDP. It is controlled via a Web REST API. // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_SDRDAEMONCHANNELSINKSETTINGS_H_ +#define INCLUDE_SDRDAEMONCHANNELSINKSETTINGS_H_ + +#include + +class Serializable; + +struct DaemonSinkSettings +{ + uint16_t m_nbFECBlocks; + uint32_t m_txDelay; + QString m_dataAddress; + uint16_t m_dataPort; + quint32 m_rgbColor; + QString m_title; + + Serializable *m_channelMarker; + + DaemonSinkSettings(); + void resetToDefaults(); + void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; } + QByteArray serialize() const; + bool deserialize(const QByteArray& data); +}; + +#endif /* INCLUDE_SDRDAEMONCHANNELSINKSETTINGS_H_ */ diff --git a/plugins/channelrx/daemonsink/daemonsinkthread.cpp b/plugins/channelrx/daemonsink/daemonsinkthread.cpp new file mode 100644 index 000000000..ba0cc1ab8 --- /dev/null +++ b/plugins/channelrx/daemonsink/daemonsinkthread.cpp @@ -0,0 +1,189 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon sink channel (Rx) UDP sender thread // +// // +// SDRdaemon is a detached SDR front end that handles the interface with a // +// physical device and sends or receives the I/Q samples stream to or from a // +// SDRangel instance via UDP. It is controlled via a Web REST API. // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "channel/sdrdaemondatablock.h" +#include "daemonsinkthread.h" + +#include "cm256.h" + +MESSAGE_CLASS_DEFINITION(DaemonSinkThread::MsgStartStop, Message) + +DaemonSinkThread::DaemonSinkThread(QObject* parent) : + QThread(parent), + m_running(false), + m_address(QHostAddress::LocalHost), + m_socket(0) +{ + + m_cm256p = m_cm256.isInitialized() ? &m_cm256 : 0; + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); +} + +DaemonSinkThread::~DaemonSinkThread() +{ + qDebug("DaemonSinkThread::~DaemonSinkThread"); +} + +void DaemonSinkThread::startStop(bool start) +{ + MsgStartStop *msg = MsgStartStop::create(start); + m_inputMessageQueue.push(msg); +} + +void DaemonSinkThread::startWork() +{ + qDebug("DaemonSinkThread::startWork"); + m_startWaitMutex.lock(); + m_socket = new QUdpSocket(this); + start(); + while(!m_running) + m_startWaiter.wait(&m_startWaitMutex, 100); + m_startWaitMutex.unlock(); +} + +void DaemonSinkThread::stopWork() +{ + qDebug("DaemonSinkThread::stopWork"); + delete m_socket; + m_socket = 0; + m_running = false; + wait(); +} + +void DaemonSinkThread::run() +{ + qDebug("DaemonSinkThread::run: begin"); + m_running = true; + m_startWaiter.wakeAll(); + + while (m_running) + { + sleep(1); // Do nothing as everything is in the data handler (dequeuer) + } + + m_running = false; + qDebug("DaemonSinkThread::run: end"); +} + +void DaemonSinkThread::processDataBlock(SDRDaemonDataBlock *dataBlock) +{ + handleDataBlock(*dataBlock); + delete dataBlock; +} + +void DaemonSinkThread::handleDataBlock(SDRDaemonDataBlock& dataBlock) +{ + CM256::cm256_encoder_params cm256Params; //!< Main interface with CM256 encoder + CM256::cm256_block descriptorBlocks[256]; //!< Pointers to data for CM256 encoder + SDRDaemonProtectedBlock fecBlocks[256]; //!< FEC data + + uint16_t frameIndex = dataBlock.m_txControlBlock.m_frameIndex; + int nbBlocksFEC = dataBlock.m_txControlBlock.m_nbBlocksFEC; + int txDelay = dataBlock.m_txControlBlock.m_txDelay; + m_address.setAddress(dataBlock.m_txControlBlock.m_dataAddress); + uint16_t dataPort = dataBlock.m_txControlBlock.m_dataPort; + SDRDaemonSuperBlock *txBlockx = dataBlock.m_superBlocks; + + if ((nbBlocksFEC == 0) || !m_cm256p) // Do not FEC encode + { + if (m_socket) + { + for (int i = 0; i < SDRDaemonNbOrginalBlocks; i++) + { + // send block via UDP + m_socket->writeDatagram((const char*)&txBlockx[i], (qint64 ) SDRDaemonUdpSize, m_address, dataPort); + usleep(txDelay); + } + } + } + else + { + cm256Params.BlockBytes = sizeof(SDRDaemonProtectedBlock); + cm256Params.OriginalCount = SDRDaemonNbOrginalBlocks; + cm256Params.RecoveryCount = nbBlocksFEC; + + // Fill pointers to data + for (int i = 0; i < cm256Params.OriginalCount + cm256Params.RecoveryCount; ++i) + { + if (i >= cm256Params.OriginalCount) { + memset((void *) &txBlockx[i].m_protectedBlock, 0, sizeof(SDRDaemonProtectedBlock)); + } + + txBlockx[i].m_header.m_frameIndex = frameIndex; + txBlockx[i].m_header.m_blockIndex = i; + txBlockx[i].m_header.m_sampleBytes = (SDR_RX_SAMP_SZ <= 16 ? 2 : 4); + txBlockx[i].m_header.m_sampleBits = SDR_RX_SAMP_SZ; + descriptorBlocks[i].Block = (void *) &(txBlockx[i].m_protectedBlock); + descriptorBlocks[i].Index = txBlockx[i].m_header.m_blockIndex; + } + + // Encode FEC blocks + if (m_cm256p->cm256_encode(cm256Params, descriptorBlocks, fecBlocks)) + { + qWarning("SDRDaemonChannelSinkThread::handleDataBlock: CM256 encode failed. No transmission."); + // TODO: send without FEC changing meta data to set indication of no FEC + } + + // Merge FEC with data to transmit + for (int i = 0; i < cm256Params.RecoveryCount; i++) + { + txBlockx[i + cm256Params.OriginalCount].m_protectedBlock = fecBlocks[i]; + } + + // Transmit all blocks + if (m_socket) + { + for (int i = 0; i < cm256Params.OriginalCount + cm256Params.RecoveryCount; i++) + { + // send block via UDP + m_socket->writeDatagram((const char*)&txBlockx[i], (qint64 ) SDRDaemonUdpSize, m_address, dataPort); + usleep(txDelay); + } + } + } + + dataBlock.m_txControlBlock.m_processed = true; +} + +void DaemonSinkThread::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + if (MsgStartStop::match(*message)) + { + MsgStartStop* notif = (MsgStartStop*) message; + qDebug("DaemonSinkThread::handleInputMessages: MsgStartStop: %s", notif->getStartStop() ? "start" : "stop"); + + if (notif->getStartStop()) { + startWork(); + } else { + stopWork(); + } + + delete message; + } + } +} diff --git a/plugins/channelrx/daemonsink/daemonsinkthread.h b/plugins/channelrx/daemonsink/daemonsinkthread.h new file mode 100644 index 000000000..5e468f7a9 --- /dev/null +++ b/plugins/channelrx/daemonsink/daemonsinkthread.h @@ -0,0 +1,89 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon sink channel (Rx) UDP sender thread // +// // +// SDRdaemon is a detached SDR front end that handles the interface with a // +// physical device and sends or receives the I/Q samples stream to or from a // +// SDRangel instance via UDP. It is controlled via a Web REST API. // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#include "cm256.h" + +#include "util/message.h" +#include "util/messagequeue.h" + +class SDRDaemonDataBlock; +class CM256; +class QUdpSocket; + +class DaemonSinkThread : public QThread { + Q_OBJECT + +public: + class MsgStartStop : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getStartStop() const { return m_startStop; } + + static MsgStartStop* create(bool startStop) { + return new MsgStartStop(startStop); + } + + protected: + bool m_startStop; + + MsgStartStop(bool startStop) : + Message(), + m_startStop(startStop) + { } + }; + + DaemonSinkThread(QObject* parent = 0); + ~DaemonSinkThread(); + + void startStop(bool start); + +public slots: + void processDataBlock(SDRDaemonDataBlock *dataBlock); + +private: + QMutex m_startWaitMutex; + QWaitCondition m_startWaiter; + bool m_running; + + CM256 m_cm256; + CM256 *m_cm256p; + + QHostAddress m_address; + QUdpSocket *m_socket; + + MessageQueue m_inputMessageQueue; + + void startWork(); + void stopWork(); + + void run(); + void handleDataBlock(SDRDaemonDataBlock& dataBlock); + +private slots: + void handleInputMessages(); +}; diff --git a/plugins/channelrx/daemonsink/readme.md b/plugins/channelrx/daemonsink/readme.md new file mode 100644 index 000000000..bd5e0fe53 --- /dev/null +++ b/plugins/channelrx/daemonsink/readme.md @@ -0,0 +1,46 @@ +

Daemon sink channel plugin

+ +

Introduction

+ +This plugin sends I/Q samples from the baseband via UDP to a distant network end point. It can use FEC protection to prevent possible data loss inherent to UDP protocol. + +It is present only in Linux binary releases. + +

Build

+ +The plugin will be built only if the [CM256cc library](https://github.com/f4exb/cm256cc) is installed in your system. For CM256cc library you will have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_INCLUDE_DIR=/opt/install/cm256cc/include/cm256cc -DCM256CC_LIBRARIES=/opt/install/cm256cc/lib/libcm256cc.so` to the cmake commands. + +

Interface

+ +![Daemon sink channel plugin GUI](../../../doc/img/DaemonSink.png) + +

1: Distant address

+ +IP address of the distant network interface from where the I/Q samples are sent via UDP + +

2: Data distant port

+ +Distant port to which the I/Q samples are sent via UDP + +

3: Validation button

+ +When the return key is hit within the address (1) or port (2) the changes are effective immediately. You can also use this button to set again these values. + +

4: Desired number of FEC blocks per frame

+ +This sets the number of FEC blocks per frame. A frame consists of 128 data blocks (1 meta data block followed by 127 I/Q data blocks) and a variable number of FEC blocks used to protect the UDP transmission with a Cauchy MDS block erasure correction. The two numbers next are the total number of blocks and the number of FEC blocks separated by a slash (/). + +

5: Delay between UDP blocks transmission

+ +This sets the minimum delay between transmission of an UDP block (send datagram) and the next. This allows throttling of the UDP transmission that is otherwise uncontrolled and causes network congestion. + +The value is a percentage of the nominal time it takes to process a block of samples corresponding to one UDP block (512 bytes). This is calculated as follows: + + - Sample rate on the network: _SR_ + - Delay percentage: _d_ + - Number of FEC blocks: _F_ + - There are 127 blocks of I/Q data per frame (1 meta block for 128 blocks) and each I/Q data block of 512 bytes (128 samples) has a 8 bytes header (2 samples) thus there are 126 samples remaining effectively. This gives the constant 127*126 = 16002 samples per frame in the formula + +Formula: ((127 ✕ 126 ✕ _d_) / _SR_) / (128 + _F_) + +The percentage appears first at the right of the dial button and then the actual delay value in microseconds. \ No newline at end of file diff --git a/plugins/channelrx/demodam/CMakeLists.txt b/plugins/channelrx/demodam/CMakeLists.txt index 7ec8181dc..602e4a140 100644 --- a/plugins/channelrx/demodam/CMakeLists.txt +++ b/plugins/channelrx/demodam/CMakeLists.txt @@ -1,10 +1,13 @@ project(am) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + set(am_SOURCES amdemod.cpp amdemodgui.cpp amdemodsettings.cpp amdemodplugin.cpp + amdemodssbdialog.cpp ) set(am_HEADERS @@ -12,15 +15,18 @@ set(am_HEADERS amdemodgui.h amdemodsettings.h amdemodplugin.h + amdemodssbdialog.h ) set(am_FORMS amdemodgui.ui + amdemodssb.ui ) include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) #include(${QT_USE_FILE}) @@ -43,6 +49,6 @@ target_link_libraries(demodam sdrgui ) -qt5_use_modules(demodam Core Widgets) +target_link_libraries(demodam Qt5::Core Qt5::Widgets) -install(TARGETS demodam DESTINATION lib/plugins/channelrx) \ No newline at end of file +install(TARGETS demodam DESTINATION lib/plugins/channelrx) diff --git a/plugins/channelrx/demodam/amdemod.cpp b/plugins/channelrx/demodam/amdemod.cpp index a73cfcc69..29a01559f 100644 --- a/plugins/channelrx/demodam/amdemod.cpp +++ b/plugins/channelrx/demodam/amdemod.cpp @@ -22,17 +22,25 @@ #include #include +#include "SWGChannelSettings.h" +#include "SWGAMDemodSettings.h" +#include "SWGChannelReport.h" +#include "SWGAMDemodReport.h" + #include "dsp/downchannelizer.h" #include "audio/audiooutput.h" #include "dsp/dspengine.h" #include "dsp/threadedbasebandsamplesink.h" #include "dsp/dspcommands.h" +#include "dsp/fftfilt.h" #include "device/devicesourceapi.h" +#include "util/db.h" +#include "util/stepfunctions.h" MESSAGE_CLASS_DEFINITION(AMDemod::MsgConfigureAMDemod, Message) MESSAGE_CLASS_DEFINITION(AMDemod::MsgConfigureChannelizer, Message) -const QString AMDemod::m_channelIdURI = "de.maintech.sdrangelove.channel.am"; +const QString AMDemod::m_channelIdURI = "sdrangel.channel.amdemod"; const QString AMDemod::m_channelId = "AMDemod"; const int AMDemod::m_udpBlockSize = 512; @@ -43,10 +51,12 @@ AMDemod::AMDemod(DeviceSourceAPI *deviceAPI) : m_inputFrequencyOffset(0), m_running(false), m_squelchOpen(false), + m_squelchDelayLine(9600), m_magsqSum(0.0f), m_magsqPeak(0.0f), m_magsqCount(0), m_volumeAGC(0.003), + m_syncAMAGC(12000, 0.1, 1e-2), m_audioFifo(48000), m_settingsMutex(QMutex::Recursive) { @@ -57,26 +67,35 @@ AMDemod::AMDemod(DeviceSourceAPI *deviceAPI) : m_magsq = 0.0; - DSPEngine::instance()->addAudioSink(&m_audioFifo); - m_udpBufferAudio = new UDPSink(this, m_udpBlockSize, m_settings.m_udpPort); + DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo, getInputMessageQueue()); + m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate(); + DSBFilter = new fftfilt((2.0f * m_settings.m_rfBandwidth) / m_audioSampleRate, 2 * 1024); + SSBFilter = new fftfilt(0.0f, m_settings.m_rfBandwidth / m_audioSampleRate, 1024); + m_syncAMAGC.setThresholdEnable(false); + m_syncAMAGC.resize(12000, 6000, 0.1); + + applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); m_channelizer = new DownChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); m_deviceAPI->addThreadedSink(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); - applySettings(m_settings, true); + m_pllFilt.create(101, m_audioSampleRate, 200.0); + m_pll.computeCoefficients(0.05, 0.707, 1000); + m_syncAMBuffIndex = 0; } AMDemod::~AMDemod() { - DSPEngine::instance()->removeAudioSink(&m_audioFifo); - delete m_udpBufferAudio; + DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo); m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSink(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; + delete DSBFilter; + delete SSBFilter; } void AMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst __attribute__((unused))) @@ -117,7 +136,7 @@ void AMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector if (m_audioBufferFill > 0) { - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 10); + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); if (res != m_audioBufferFill) { @@ -130,6 +149,132 @@ void AMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector m_settingsMutex.unlock(); } +void AMDemod::processOneSample(Complex &ci) +{ + Real re = ci.real() / SDR_RX_SCALEF; + Real im = ci.imag() / SDR_RX_SCALEF; + Real magsq = re*re + im*im; + m_movingAverage(magsq); + m_magsq = m_movingAverage.asDouble(); + m_magsqSum += magsq; + + if (magsq > m_magsqPeak) + { + m_magsqPeak = magsq; + } + + m_magsqCount++; + + m_squelchDelayLine.write(magsq); + + if (m_magsq < m_squelchLevel) + { + if (m_squelchCount > 0) { + m_squelchCount--; + } + } + else + { + if (m_squelchCount < m_audioSampleRate / 10) { + m_squelchCount++; + } + } + + qint16 sample; + + m_squelchOpen = (m_squelchCount >= m_audioSampleRate / 20); + + if (m_squelchOpen && !m_settings.m_audioMute) + { + Real demod; + + if (m_settings.m_pll) + { + std::complex s(re, im); + s = m_pllFilt.filter(s); + m_pll.feed(s.real(), s.imag()); + float yr = re * m_pll.getImag() - im * m_pll.getReal(); + float yi = re * m_pll.getReal() + im * m_pll.getImag(); + + fftfilt::cmplx *sideband; + std::complex cs(yr, yi); + int n_out; + + if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) { + n_out = DSBFilter->runDSB(cs, &sideband, false); + } else { + n_out = SSBFilter->runSSB(cs, &sideband, m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB, false); + } + + for (int i = 0; i < n_out; i++) + { + float agcVal = m_syncAMAGC.feedAndGetValue(sideband[i]); + fftfilt::cmplx z = sideband[i] * agcVal; // * m_syncAMAGC.getStepValue(); + + if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) { + m_syncAMBuff[i] = (z.real() + z.imag()); + } else if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB) { + m_syncAMBuff[i] = (z.real() + z.imag()); + } else { + m_syncAMBuff[i] = (z.real() + z.imag()); + } + +// if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) { +// m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag())/2.0f; +// } else if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB) { +// m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag()); +// } else { +// m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag()); +// } + + m_syncAMBuffIndex = 0; + } + + m_syncAMBuffIndex = m_syncAMBuffIndex < 2*1024 ? m_syncAMBuffIndex : 0; + demod = m_syncAMBuff[m_syncAMBuffIndex++]*4.0f; // mos pifometrico +// demod = m_syncAMBuff[m_syncAMBuffIndex++]*(SDR_RX_SCALEF/602.0f); +// m_volumeAGC.feed(demod); +// demod /= (10.0*m_volumeAGC.getValue()); + } + else + { + demod = sqrt(m_squelchDelayLine.readBack(m_audioSampleRate/20)); + m_volumeAGC.feed(demod); + demod = (demod - m_volumeAGC.getValue()) / m_volumeAGC.getValue(); + } + + if (m_settings.m_bandpassEnable) + { + demod = m_bandpass.filter(demod); + demod /= 301.0f; + } + + Real attack = (m_squelchCount - 0.05f * m_audioSampleRate) / (0.05f * m_audioSampleRate); + sample = demod * StepFunctions::smootherstep(attack) * (m_audioSampleRate/24) * m_settings.m_volume; + } + else + { + sample = 0; + } + + m_audioBuffer[m_audioBufferFill].l = sample; + m_audioBuffer[m_audioBufferFill].r = sample; + ++m_audioBufferFill; + + if (m_audioBufferFill >= m_audioBuffer.size()) + { + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); + + if (res != m_audioBufferFill) + { + qDebug("AMDemod::processOneSample: %u/%u audio samples written", res, m_audioBufferFill); + m_audioFifo.clear(); + } + + m_audioBufferFill = 0; + } +} + void AMDemod::start() { qDebug("AMDemod::start"); @@ -181,16 +326,69 @@ bool AMDemod::handleMessage(const Message& cmd) return true; } + else if (BasebandSampleSink::MsgThreadedSink::match(cmd)) + { + BasebandSampleSink::MsgThreadedSink& cfg = (BasebandSampleSink::MsgThreadedSink&) cmd; + const QThread *thread = cfg.getThread(); + qDebug("AMDemod::handleMessage: BasebandSampleSink::MsgThreadedSink: %p", thread); + return true; + } else if (DSPSignalNotification::match(cmd)) { return true; } + else if (DSPConfigureAudio::match(cmd)) + { + DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd; + uint32_t sampleRate = cfg.getSampleRate(); + + qDebug() << "AMDemod::handleMessage: DSPConfigureAudio:" + << " sampleRate: " << sampleRate; + + if (sampleRate != m_audioSampleRate) { + applyAudioSampleRate(sampleRate); + } + + return true; + } else { return false; } } +void AMDemod::applyAudioSampleRate(int sampleRate) +{ + qDebug("AMDemod::applyAudioSampleRate: %d", sampleRate); + + MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( + sampleRate, m_settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(channelConfigMsg); + + m_settingsMutex.lock(); + + m_interpolator.create(16, m_inputSampleRate, m_settings.m_rfBandwidth / 2.2f); + m_interpolatorDistanceRemain = 0; + m_interpolatorDistance = (Real) m_inputSampleRate / (Real) sampleRate; + m_bandpass.create(301, sampleRate, 300.0, m_settings.m_rfBandwidth / 2.0f); + m_audioFifo.setSize(sampleRate); + m_squelchDelayLine.resize(sampleRate/5); + DSBFilter->create_dsb_filter((2.0f * m_settings.m_rfBandwidth) / (float) sampleRate); + m_pllFilt.create(101, sampleRate, 200.0); + + if (m_settings.m_pll) { + m_volumeAGC.resizeNew(sampleRate, 0.003); + } else { + m_volumeAGC.resizeNew(sampleRate/10, 0.003); + } + + m_syncAMAGC.resize(sampleRate/4, sampleRate/8, 0.1); + m_pll.setSampleRate(sampleRate); + + m_settingsMutex.unlock(); + m_audioSampleRate = sampleRate; +} + void AMDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force) { qDebug() << "AMDemod::applyChannelSettings:" @@ -208,7 +406,7 @@ void AMDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset m_settingsMutex.lock(); m_interpolator.create(16, inputSampleRate, m_settings.m_rfBandwidth / 2.2f); m_interpolatorDistanceRemain = 0; - m_interpolatorDistance = (Real) inputSampleRate / (Real) m_settings.m_audioSampleRate; + m_interpolatorDistance = (Real) inputSampleRate / (Real) m_audioSampleRate; m_settingsMutex.unlock(); } @@ -225,33 +423,56 @@ void AMDemod::applySettings(const AMDemodSettings& settings, bool force) << " m_squelch: " << settings.m_squelch << " m_audioMute: " << settings.m_audioMute << " m_bandpassEnable: " << settings.m_bandpassEnable - << " m_copyAudioToUDP: " << settings.m_copyAudioToUDP - << " m_udpAddress: " << settings.m_udpAddress - << " m_udpPort: " << settings.m_udpPort + << " m_audioDeviceName: " << settings.m_audioDeviceName + << " m_pll: " << settings.m_pll + << " m_syncAMOperation: " << (int) settings.m_syncAMOperation << " force: " << force; if((m_settings.m_rfBandwidth != settings.m_rfBandwidth) || - (m_settings.m_audioSampleRate != settings.m_audioSampleRate) || (m_settings.m_bandpassEnable != settings.m_bandpassEnable) || force) { m_settingsMutex.lock(); m_interpolator.create(16, m_inputSampleRate, settings.m_rfBandwidth / 2.2f); m_interpolatorDistanceRemain = 0; - m_interpolatorDistance = (Real) m_inputSampleRate / (Real) settings.m_audioSampleRate; - m_bandpass.create(301, settings.m_audioSampleRate, 300.0, settings.m_rfBandwidth / 2.0f); + m_interpolatorDistance = (Real) m_inputSampleRate / (Real) m_audioSampleRate; + m_bandpass.create(301, m_audioSampleRate, 300.0, settings.m_rfBandwidth / 2.0f); + DSBFilter->create_dsb_filter((2.0f * settings.m_rfBandwidth) / (float) m_audioSampleRate); m_settingsMutex.unlock(); } if ((m_settings.m_squelch != settings.m_squelch) || force) { - m_squelchLevel = pow(10.0, settings.m_squelch / 10.0); + m_squelchLevel = CalcDb::powerFromdB(settings.m_squelch); } - if ((m_settings.m_udpAddress != settings.m_udpAddress) - || (m_settings.m_udpPort != settings.m_udpPort) || force) + if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) { - m_udpBufferAudio->setAddress(const_cast(settings.m_udpAddress)); - m_udpBufferAudio->setPort(settings.m_udpPort); + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName); + //qDebug("AMDemod::applySettings: audioDeviceName: %s audioDeviceIndex: %d", qPrintable(settings.m_audioDeviceName), audioDeviceIndex); + audioDeviceManager->addAudioSink(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex); + uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex); + + if (m_audioSampleRate != audioSampleRate) { + applyAudioSampleRate(audioSampleRate); + } + } + + if ((m_settings.m_pll != settings.m_pll) || force) + { + if (settings.m_pll) + { + m_volumeAGC.resizeNew(m_audioSampleRate/4, 0.003); + m_syncAMBuffIndex = 0; + } + else + { + m_volumeAGC.resizeNew(m_audioSampleRate/10, 0.003); + } + } + + if ((m_settings.m_syncAMOperation != settings.m_syncAMOperation) || force) { + m_syncAMBuffIndex = 0; } m_settings = settings; @@ -279,3 +500,133 @@ bool AMDemod::deserialize(const QByteArray& data) } } +int AMDemod::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setAmDemodSettings(new SWGSDRangel::SWGAMDemodSettings()); + response.getAmDemodSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int AMDemod::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + AMDemodSettings settings = m_settings; + bool frequencyOffsetChanged = false; + + if (channelSettingsKeys.contains("audioMute")) { + settings.m_audioMute = response.getAmDemodSettings()->getAudioMute() != 0; + } + if (channelSettingsKeys.contains("inputFrequencyOffset")) + { + settings.m_inputFrequencyOffset = response.getAmDemodSettings()->getInputFrequencyOffset(); + frequencyOffsetChanged = true; + } + if (channelSettingsKeys.contains("rfBandwidth")) { + settings.m_rfBandwidth = response.getAmDemodSettings()->getRfBandwidth(); + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getAmDemodSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("squelch")) { + settings.m_squelch = response.getAmDemodSettings()->getSquelch(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getAmDemodSettings()->getTitle(); + } + if (channelSettingsKeys.contains("volume")) { + settings.m_volume = response.getAmDemodSettings()->getVolume(); + } + if (channelSettingsKeys.contains("bandpassEnable")) { + settings.m_bandpassEnable = response.getAmDemodSettings()->getBandpassEnable() != 0; + } + if (channelSettingsKeys.contains("audioDeviceName")) { + settings.m_audioDeviceName = *response.getAmDemodSettings()->getAudioDeviceName(); + } + + if (channelSettingsKeys.contains("pll")) { + settings.m_pll = response.getAmDemodSettings()->getPll(); + } + + if (channelSettingsKeys.contains("syncAMOperation")) { + qint32 syncAMOperationCode = response.getAmDemodSettings()->getSyncAmOperation(); + settings.m_syncAMOperation = syncAMOperationCode < 0 ? + AMDemodSettings::SyncAMDSB : syncAMOperationCode > 2 ? + AMDemodSettings::SyncAMDSB : (AMDemodSettings::SyncAMOperation) syncAMOperationCode; + } + + if (frequencyOffsetChanged) + { + MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( + m_audioSampleRate, settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(channelConfigMsg); + } + + MsgConfigureAMDemod *msg = MsgConfigureAMDemod::create(settings, force); + m_inputMessageQueue.push(msg); + + qDebug("AMDemod::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureAMDemod *msgToGUI = MsgConfigureAMDemod::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int AMDemod::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setAmDemodReport(new SWGSDRangel::SWGAMDemodReport()); + response.getAmDemodReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + +void AMDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const AMDemodSettings& settings) +{ + response.getAmDemodSettings()->setAudioMute(settings.m_audioMute ? 1 : 0); + response.getAmDemodSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getAmDemodSettings()->setRfBandwidth(settings.m_rfBandwidth); + response.getAmDemodSettings()->setRgbColor(settings.m_rgbColor); + response.getAmDemodSettings()->setSquelch(settings.m_squelch); + response.getAmDemodSettings()->setVolume(settings.m_volume); + response.getAmDemodSettings()->setBandpassEnable(settings.m_bandpassEnable ? 1 : 0); + + if (response.getAmDemodSettings()->getTitle()) { + *response.getAmDemodSettings()->getTitle() = settings.m_title; + } else { + response.getAmDemodSettings()->setTitle(new QString(settings.m_title)); + } + + if (response.getAmDemodSettings()->getAudioDeviceName()) { + *response.getAmDemodSettings()->getAudioDeviceName() = settings.m_audioDeviceName; + } else { + response.getAmDemodSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName)); + } + + response.getAmDemodSettings()->setPll(settings.m_pll ? 1 : 0); + response.getAmDemodSettings()->setSyncAmOperation((int) m_settings.m_syncAMOperation); +} + +void AMDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + double magsqAvg, magsqPeak; + int nbMagsqSamples; + getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples); + + response.getAmDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg)); + response.getAmDemodReport()->setSquelch(m_squelchOpen ? 1 : 0); + response.getAmDemodReport()->setAudioSampleRate(m_audioSampleRate); + response.getAmDemodReport()->setChannelSampleRate(m_inputSampleRate); +} + diff --git a/plugins/channelrx/demodam/amdemod.h b/plugins/channelrx/demodam/amdemod.h index 3dccaeec1..f08ed6451 100644 --- a/plugins/channelrx/demodam/amdemod.h +++ b/plugins/channelrx/demodam/amdemod.h @@ -1,241 +1,218 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2015 Edouard Griffiths, F4EXB. // -// // -// This program is free software; you can redistribute it and/or modify // -// it under the terms of the GNU General Public License as published by // -// the Free Software Foundation as version 3 of the License, or // -// // -// This program is distributed in the hope that it will be useful, // -// but WITHOUT ANY WARRANTY; without even the implied warranty of // -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // -// GNU General Public License V3 for more details. // -// // -// You should have received a copy of the GNU General Public License // -// along with this program. If not, see . // -/////////////////////////////////////////////////////////////////////////////////// - -#ifndef INCLUDE_AMDEMOD_H -#define INCLUDE_AMDEMOD_H - -#include -#include - -#include "dsp/basebandsamplesink.h" -#include "channel/channelsinkapi.h" -#include "dsp/nco.h" -#include "dsp/interpolator.h" -#include "util/movingaverage.h" -#include "dsp/agc.h" -#include "dsp/bandpass.h" -#include "audio/audiofifo.h" -#include "util/message.h" -#include "amdemodsettings.h" - -class DeviceSourceAPI; -class DownChannelizer; -class ThreadedBasebandSampleSink; - -class AMDemod : public BasebandSampleSink, public ChannelSinkAPI { - Q_OBJECT -public: - class MsgConfigureAMDemod : public Message { - MESSAGE_CLASS_DECLARATION - - public: - const AMDemodSettings& getSettings() const { return m_settings; } - bool getForce() const { return m_force; } - - static MsgConfigureAMDemod* create(const AMDemodSettings& settings, bool force) - { - return new MsgConfigureAMDemod(settings, force); - } - - private: - AMDemodSettings m_settings; - bool m_force; - - MsgConfigureAMDemod(const AMDemodSettings& settings, bool force) : - Message(), - m_settings(settings), - m_force(force) - { } - }; - - class MsgConfigureChannelizer : public Message { - MESSAGE_CLASS_DECLARATION - - public: - int getSampleRate() const { return m_sampleRate; } - int getCenterFrequency() const { return m_centerFrequency; } - - static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency) - { - return new MsgConfigureChannelizer(sampleRate, centerFrequency); - } - - private: - int m_sampleRate; - int m_centerFrequency; - - MsgConfigureChannelizer(int sampleRate, int centerFrequency) : - Message(), - m_sampleRate(sampleRate), - m_centerFrequency(centerFrequency) - { } - }; - - AMDemod(DeviceSourceAPI *deviceAPI); - ~AMDemod(); - virtual void destroy() { delete this; } - - virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po); - virtual void start(); - virtual void stop(); - virtual bool handleMessage(const Message& cmd); - - virtual void getIdentifier(QString& id) { id = objectName(); } - virtual void getTitle(QString& title) { title = m_settings.m_title; } - virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; } - - virtual QByteArray serialize() const; - virtual bool deserialize(const QByteArray& data); - - double getMagSq() const { return m_magsq; } - bool getSquelchOpen() const { return m_squelchOpen; } - - void getMagSqLevels(double& avg, double& peak, int& nbSamples) - { - avg = m_magsqCount == 0 ? 1e-10 : m_magsqSum / m_magsqCount; - peak = m_magsqPeak == 0.0 ? 1e-10 : m_magsqPeak; - nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; - m_magsqSum = 0.0f; - m_magsqPeak = 0.0f; - m_magsqCount = 0; - } - - static const QString m_channelIdURI; - static const QString m_channelId; - -private: - enum RateState { - RSInitialFill, - RSRunning - }; - - DeviceSourceAPI *m_deviceAPI; - ThreadedBasebandSampleSink* m_threadedChannelizer; - DownChannelizer* m_channelizer; - - int m_inputSampleRate; - int m_inputFrequencyOffset; - AMDemodSettings m_settings; - bool m_running; - - NCO m_nco; - Interpolator m_interpolator; - Real m_interpolatorDistance; - Real m_interpolatorDistanceRemain; - - Real m_squelchLevel; - uint32_t m_squelchCount; - bool m_squelchOpen; - double m_magsq; - double m_magsqSum; - double m_magsqPeak; - int m_magsqCount; - - MovingAverageUtil m_movingAverage; - SimpleAGC<4096> m_volumeAGC; - Bandpass m_bandpass; - - AudioVector m_audioBuffer; - uint32_t m_audioBufferFill; - AudioFifo m_audioFifo; - UDPSink *m_udpBufferAudio; - - static const int m_udpBlockSize; - - QMutex m_settingsMutex; - - void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); - void applySettings(const AMDemodSettings& settings, bool force = false); - - void processOneSample(Complex &ci) - { - Real re = ci.real() / SDR_RX_SCALED; - Real im = ci.imag() / SDR_RX_SCALED; - Real magsq = re*re + im*im; - m_movingAverage(magsq); - m_magsq = m_movingAverage.asDouble(); - m_magsqSum += magsq; - - if (magsq > m_magsqPeak) - { - m_magsqPeak = magsq; - } - - m_magsqCount++; - - if (m_magsq >= m_squelchLevel) - { - if (m_squelchCount <= m_settings.m_audioSampleRate / 10) - { - m_squelchCount++; - } - } - else - { - if (m_squelchCount > 1) - { - m_squelchCount -= 2; - } - } - - qint16 sample; - - if ((m_squelchCount >= m_settings.m_audioSampleRate / 20) && !m_settings.m_audioMute) - { - Real demod = sqrt(magsq); - m_volumeAGC.feed(demod); - demod = (demod - m_volumeAGC.getValue()) / m_volumeAGC.getValue(); - - if (m_settings.m_bandpassEnable) - { - demod = m_bandpass.filter(demod); - demod /= 301.0f; - } - - Real attack = (m_squelchCount - 0.05f * m_settings.m_audioSampleRate) / (0.05f * m_settings.m_audioSampleRate); - sample = demod * attack * 2048 * m_settings.m_volume; - if (m_settings.m_copyAudioToUDP) m_udpBufferAudio->write(demod * attack * SDR_RX_SCALEF); - - m_squelchOpen = true; - } - else - { - sample = 0; - if (m_settings.m_copyAudioToUDP) m_udpBufferAudio->write(0); - m_squelchOpen = false; - } - - m_audioBuffer[m_audioBufferFill].l = sample; - m_audioBuffer[m_audioBufferFill].r = sample; - ++m_audioBufferFill; - - if (m_audioBufferFill >= m_audioBuffer.size()) - { - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 10); - - if (res != m_audioBufferFill) - { - qDebug("AMDemod::processOneSample: %u/%u audio samples written", res, m_audioBufferFill); - m_audioFifo.clear(); - } - - m_audioBufferFill = 0; - } - } - -}; - -#endif // INCLUDE_AMDEMOD_H +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 Edouard Griffiths, F4EXB. // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_AMDEMOD_H +#define INCLUDE_AMDEMOD_H + +#include +#include + +#include "dsp/basebandsamplesink.h" +#include "channel/channelsinkapi.h" +#include "dsp/nco.h" +#include "dsp/interpolator.h" +#include "util/movingaverage.h" +#include "dsp/agc.h" +#include "dsp/bandpass.h" +#include "dsp/lowpass.h" +#include "dsp/phaselockcomplex.h" +#include "audio/audiofifo.h" +#include "util/message.h" +#include "util/doublebufferfifo.h" + +#include "amdemodsettings.h" + +class DeviceSourceAPI; +class DownChannelizer; +class ThreadedBasebandSampleSink; +class fftfilt; + +class AMDemod : public BasebandSampleSink, public ChannelSinkAPI { + Q_OBJECT +public: + class MsgConfigureAMDemod : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const AMDemodSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureAMDemod* create(const AMDemodSettings& settings, bool force) + { + return new MsgConfigureAMDemod(settings, force); + } + + private: + AMDemodSettings m_settings; + bool m_force; + + MsgConfigureAMDemod(const AMDemodSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgConfigureChannelizer : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getSampleRate() const { return m_sampleRate; } + int getCenterFrequency() const { return m_centerFrequency; } + + static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency) + { + return new MsgConfigureChannelizer(sampleRate, centerFrequency); + } + + private: + int m_sampleRate; + int m_centerFrequency; + + MsgConfigureChannelizer(int sampleRate, int centerFrequency) : + Message(), + m_sampleRate(sampleRate), + m_centerFrequency(centerFrequency) + { } + }; + + AMDemod(DeviceSourceAPI *deviceAPI); + ~AMDemod(); + virtual void destroy() { delete this; } + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po); + virtual void start(); + virtual void stop(); + virtual bool handleMessage(const Message& cmd); + + virtual void getIdentifier(QString& id) { id = objectName(); } + virtual void getTitle(QString& title) { title = m_settings.m_title; } + virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; } + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + + uint32_t getAudioSampleRate() const { return m_audioSampleRate; } + double getMagSq() const { return m_magsq; } + bool getSquelchOpen() const { return m_squelchOpen; } + bool getPllLocked() const { return m_settings.m_pll && m_pll.locked(); } + Real getPllFrequency() const { return m_pll.getFreq(); } + + void getMagSqLevels(double& avg, double& peak, int& nbSamples) + { + if (m_magsqCount > 0) + { + m_magsq = m_magsqSum / m_magsqCount; + m_magSqLevelStore.m_magsq = m_magsq; + m_magSqLevelStore.m_magsqPeak = m_magsqPeak; + } + + avg = m_magSqLevelStore.m_magsq; + peak = m_magSqLevelStore.m_magsqPeak; + nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; + + m_magsqSum = 0.0f; + m_magsqPeak = 0.0f; + m_magsqCount = 0; + } + + static const QString m_channelIdURI; + static const QString m_channelId; + +private: + struct MagSqLevelsStore + { + MagSqLevelsStore() : + m_magsq(1e-12), + m_magsqPeak(1e-12) + {} + double m_magsq; + double m_magsqPeak; + }; + + enum RateState { + RSInitialFill, + RSRunning + }; + + DeviceSourceAPI *m_deviceAPI; + ThreadedBasebandSampleSink* m_threadedChannelizer; + DownChannelizer* m_channelizer; + + int m_inputSampleRate; + int m_inputFrequencyOffset; + AMDemodSettings m_settings; + uint32_t m_audioSampleRate; + bool m_running; + + NCO m_nco; + Interpolator m_interpolator; + Real m_interpolatorDistance; + Real m_interpolatorDistanceRemain; + + Real m_squelchLevel; + uint32_t m_squelchCount; + bool m_squelchOpen; + DoubleBufferFIFO m_squelchDelayLine; + double m_magsq; + double m_magsqSum; + double m_magsqPeak; + int m_magsqCount; + MagSqLevelsStore m_magSqLevelStore; + + MovingAverageUtil m_movingAverage; + SimpleAGC<4800> m_volumeAGC; + Bandpass m_bandpass; + Lowpass > m_pllFilt; + PhaseLockComplex m_pll; + fftfilt* DSBFilter; + fftfilt* SSBFilter; + Real m_syncAMBuff[2*1024]; + uint32_t m_syncAMBuffIndex; + MagAGC m_syncAMAGC; + + AudioVector m_audioBuffer; + uint32_t m_audioBufferFill; + AudioFifo m_audioFifo; + + static const int m_udpBlockSize; + + QMutex m_settingsMutex; + + void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); + void applySettings(const AMDemodSettings& settings, bool force = false); + void applyAudioSampleRate(int sampleRate); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const AMDemodSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); + + void processOneSample(Complex &ci); +}; + +#endif // INCLUDE_AMDEMOD_H diff --git a/plugins/channelrx/demodam/amdemodgui.cpp b/plugins/channelrx/demodam/amdemodgui.cpp index 6302d7a26..c1a8184d2 100644 --- a/plugins/channelrx/demodam/amdemodgui.cpp +++ b/plugins/channelrx/demodam/amdemodgui.cpp @@ -18,11 +18,12 @@ #include #include "amdemodgui.h" +#include "amdemodssbdialog.h" #include "device/devicesourceapi.h" #include "device/deviceuiset.h" #include "dsp/downchannelizer.h" - +#include "dsp/dspengine.h" #include "dsp/threadedbasebandsamplesink.h" #include "ui_amdemodgui.h" #include "plugin/pluginapi.h" @@ -31,6 +32,8 @@ #include "gui/basicchannelsettingsdialog.h" #include "dsp/dspengine.h" #include "mainwindow.h" +#include "gui/crightclickenabler.h" +#include "gui/audioselectdialog.h" #include "amdemod.h" @@ -91,9 +94,33 @@ bool AMDemodGUI::deserialize(const QByteArray& data) bool AMDemodGUI::handleMessage(const Message& message __attribute__((unused))) { + if (AMDemod::MsgConfigureAMDemod::match(message)) + { + qDebug("AMDemodGUI::handleMessage: AMDemod::MsgConfigureAMDemod"); + const AMDemod::MsgConfigureAMDemod& cfg = (AMDemod::MsgConfigureAMDemod&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + return false; } +void AMDemodGUI::handleInputMessages() +{ + Message* message; + + while ((message = getInputMessageQueue()->pop()) != 0) + { + if (handleMessage(*message)) + { + delete message; + } + } +} + void AMDemodGUI::channelMarkerChangedByCursor() { ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); @@ -113,6 +140,24 @@ void AMDemodGUI::on_deltaFrequency_changed(qint64 value) applySettings(); } +void AMDemodGUI::on_pll_toggled(bool checked) +{ + if (!checked) + { + ui->pll->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + ui->pll->setToolTip(tr("PLL for synchronous AM")); + } + + m_settings.m_pll = checked; + applySettings(); +} + +void AMDemodGUI::on_ssb_toggled(bool checked) +{ + m_settings.m_syncAMOperation = checked ? m_samUSB ? AMDemodSettings::SyncAMUSB : AMDemodSettings::SyncAMLSB : AMDemodSettings::SyncAMDSB; + applySettings(); +} + void AMDemodGUI::on_bandpassEnable_toggled(bool checked) { m_settings.m_bandpassEnable = checked; @@ -147,12 +192,6 @@ void AMDemodGUI::on_audioMute_toggled(bool checked) applySettings(); } -void AMDemodGUI::on_copyAudioToUDP_toggled(bool checked) -{ - m_settings.m_copyAudioToUDP = checked; - applySettings(); -} - void AMDemodGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rollDown __attribute__((unused))) { /* @@ -168,14 +207,11 @@ void AMDemodGUI::onMenuDialogCalled(const QPoint &p) dialog.exec(); m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); - m_settings.m_udpAddress = m_channelMarker.getUDPAddress(), - m_settings.m_udpPort = m_channelMarker.getUDPSendPort(), m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); m_settings.m_title = m_channelMarker.getTitle(); setWindowTitle(m_settings.m_title); setTitleColor(m_settings.m_rgbColor); - displayUDPAddress(); applySettings(); } @@ -188,6 +224,7 @@ AMDemodGUI::AMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandS m_channelMarker(this), m_doApplySettings(true), m_squelchOpen(false), + m_samUSB(true), m_tickCount(0) { ui->setupUi(this); @@ -195,10 +232,17 @@ AMDemodGUI::AMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandS connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); - m_amDemod = (AMDemod*) rxChannel; //new AMDemod(m_deviceUISet->m_deviceSourceAPI); + m_amDemod = reinterpret_cast(rxChannel); //new AMDemod(m_deviceUISet->m_deviceSourceAPI); + m_amDemod->setMessageQueueToGUI(getInputMessageQueue()); connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); // 50 ms + CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->audioMute); + connect(audioMuteRightClickEnabler, SIGNAL(rightClick()), this, SLOT(audioSelect())); + + CRightClickEnabler *samSidebandRightClickEnabler = new CRightClickEnabler(ui->ssb); + connect(samSidebandRightClickEnabler, SIGNAL(rightClick()), this, SLOT(samSSBSelect())); + ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999); @@ -209,8 +253,6 @@ AMDemodGUI::AMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandS m_channelMarker.setBandwidth(5000); m_channelMarker.setCenterFrequency(0); m_channelMarker.setTitle("AM Demodulator"); - m_channelMarker.setUDPAddress("127.0.0.1"); - m_channelMarker.setUDPSendPort(9999); m_channelMarker.blockSignals(false); m_channelMarker.setVisible(true); // activate signal on the last setting only @@ -223,6 +265,12 @@ AMDemodGUI::AMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandS connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor())); connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor())); + connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + + m_iconDSBUSB.addPixmap(QPixmap("://dsb.png"), QIcon::Normal, QIcon::Off); + m_iconDSBUSB.addPixmap(QPixmap("://usb.png"), QIcon::Normal, QIcon::On); + m_iconDSBLSB.addPixmap(QPixmap("://dsb.png"), QIcon::Normal, QIcon::Off); + m_iconDSBLSB.addPixmap(QPixmap("://lsb.png"), QIcon::Normal, QIcon::On); displaySettings(); applySettings(true); @@ -245,7 +293,7 @@ void AMDemodGUI::applySettings(bool force) if (m_doApplySettings) { AMDemod::MsgConfigureChannelizer* channelConfigMsg = AMDemod::MsgConfigureChannelizer::create( - 48000, m_channelMarker.getCenterFrequency()); + m_amDemod->getAudioSampleRate(), m_channelMarker.getCenterFrequency()); m_amDemod->getInputMessageQueue()->push(channelConfigMsg); @@ -265,7 +313,6 @@ void AMDemodGUI::displaySettings() setTitleColor(m_settings.m_rgbColor); setWindowTitle(m_channelMarker.getTitle()); - displayUDPAddress(); blockApplySettings(true); @@ -283,16 +330,25 @@ void AMDemodGUI::displaySettings() ui->audioMute->setChecked(m_settings.m_audioMute); ui->bandpassEnable->setChecked(m_settings.m_bandpassEnable); - ui->copyAudioToUDP->setChecked(m_settings.m_copyAudioToUDP); + ui->pll->setChecked(m_settings.m_pll); + + if (m_settings.m_pll) { + if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMLSB) { + m_samUSB = false; + ui->ssb->setIcon(m_iconDSBLSB); + } else { + m_samUSB = true; + ui->ssb->setIcon(m_iconDSBUSB); + } + } + else + { + ui->ssb->setIcon(m_iconDSBUSB); + } blockApplySettings(false); } -void AMDemodGUI::displayUDPAddress() -{ - ui->copyAudioToUDP->setToolTip(QString("Copy audio output to UDP %1:%2").arg(m_settings.m_udpAddress).arg(m_settings.m_udpPort)); -} - void AMDemodGUI::leaveEvent(QEvent*) { m_channelMarker.setHighlighted(false); @@ -303,6 +359,39 @@ void AMDemodGUI::enterEvent(QEvent*) m_channelMarker.setHighlighted(true); } +void AMDemodGUI::audioSelect() +{ + qDebug("AMDemodGUI::audioSelect"); + AudioSelectDialog audioSelect(DSPEngine::instance()->getAudioDeviceManager(), m_settings.m_audioDeviceName); + audioSelect.exec(); + + if (audioSelect.m_selected) + { + m_settings.m_audioDeviceName = audioSelect.m_audioDeviceName; + applySettings(); + } +} + +void AMDemodGUI::samSSBSelect() +{ + AMDemodSSBDialog ssbSelect(m_samUSB); + ssbSelect.exec(); + + ui->ssb->setIcon(ssbSelect.isUsb() ? m_iconDSBUSB : m_iconDSBLSB); + + if (ssbSelect.isUsb() != m_samUSB) + { + qDebug("AMDemodGUI::samSSBSelect: %s", ssbSelect.isUsb() ? "usb" : "lsb"); + m_samUSB = ssbSelect.isUsb(); + + if (m_settings.m_syncAMOperation != AMDemodSettings::SyncAMDSB) + { + m_settings.m_syncAMOperation = m_samUSB ? AMDemodSettings::SyncAMUSB : AMDemodSettings::SyncAMLSB; + applySettings(); + } + } +} + void AMDemodGUI::tick() { double magsqAvg, magsqPeak; @@ -333,6 +422,18 @@ void AMDemodGUI::tick() } } + if (m_settings.m_pll) + { + if (m_amDemod->getPllLocked()) { + ui->pll->setStyleSheet("QToolButton { background-color : green; }"); + } else { + ui->pll->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + } + + int freq = (m_amDemod->getPllFrequency() * m_amDemod->getAudioSampleRate()) / (2.0*M_PI); + ui->pll->setToolTip(tr("PLL for synchronous AM. Freq = %1 Hz").arg(freq)); + } + m_tickCount++; } diff --git a/plugins/channelrx/demodam/amdemodgui.h b/plugins/channelrx/demodam/amdemodgui.h index f853cc220..4849f1196 100644 --- a/plugins/channelrx/demodam/amdemodgui.h +++ b/plugins/channelrx/demodam/amdemodgui.h @@ -1,7 +1,9 @@ #ifndef INCLUDE_AMDEMODGUI_H #define INCLUDE_AMDEMODGUI_H -#include +#include + +#include "plugin/plugininstancegui.h" #include "gui/rollupwidget.h" #include "dsp/channelmarker.h" #include "dsp/movingaverage.h" @@ -50,30 +52,37 @@ private: AMDemod* m_amDemod; bool m_squelchOpen; + bool m_samUSB; uint32_t m_tickCount; MessageQueue m_inputMessageQueue; + QIcon m_iconDSBUSB; + QIcon m_iconDSBLSB; + explicit AMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); virtual ~AMDemodGUI(); void blockApplySettings(bool block); void applySettings(bool force = false); void displaySettings(); - void displayUDPAddress(); void leaveEvent(QEvent*); void enterEvent(QEvent*); private slots: void on_deltaFrequency_changed(qint64 value); + void on_pll_toggled(bool checked); + void on_ssb_toggled(bool checked); void on_bandpassEnable_toggled(bool checked); void on_rfBW_valueChanged(int value); void on_volume_valueChanged(int value); void on_squelch_valueChanged(int value); void on_audioMute_toggled(bool checked); - void on_copyAudioToUDP_toggled(bool copy); void onWidgetRolled(QWidget* widget, bool rollDown); void onMenuDialogCalled(const QPoint& p); + void handleInputMessages(); + void audioSelect(); + void samSSBSelect(); void tick(); }; diff --git a/plugins/channelrx/demodam/amdemodgui.ui b/plugins/channelrx/demodam/amdemodgui.ui index 01b80ae3f..c62321c77 100644 --- a/plugins/channelrx/demodam/amdemodgui.ui +++ b/plugins/channelrx/demodam/amdemodgui.ui @@ -6,7 +6,7 @@ 0 0 - 352 + 396 170
@@ -24,7 +24,7 @@
- Sans Serif + Liberation Sans 9 @@ -42,7 +42,7 @@ 0 0 - 350 + 390 131 @@ -107,7 +107,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -129,6 +129,49 @@ + + + + Qt::Vertical + + + + + + + PLL for synchronous AM + + + + + + + :/unlocked.png + :/locked.png:/unlocked.png + + + true + + + + + + + Synchronous AM SSB/DSB toggle right click to select side band + + + + + + + :/dsb.png + :/usb.png:/dsb.png + + + true + + + @@ -171,7 +214,7 @@ - Mute/Unmute audio + Left: Mute/Unmute audio Right: view/select audio device ... @@ -186,19 +229,6 @@ - - - - Copy audio to UDP - - - U - - - true - - - @@ -226,7 +256,7 @@ - Monospace + Liberation Mono 8 @@ -249,7 +279,7 @@ - Toggle boxcar bandpass filter with 300 Hz low cuttof + Toggle boxcar bandpass filter with 300 Hz low cutoff diff --git a/plugins/channelrx/demodam/amdemodplugin.cpp b/plugins/channelrx/demodam/amdemodplugin.cpp index 09ae64f70..28a4b640c 100644 --- a/plugins/channelrx/demodam/amdemodplugin.cpp +++ b/plugins/channelrx/demodam/amdemodplugin.cpp @@ -1,14 +1,15 @@ #include -#include #include "plugin/pluginapi.h" +#ifndef SERVER_MODE #include "amdemodgui.h" +#endif #include "amdemod.h" #include "amdemodplugin.h" const PluginDescriptor AMDemodPlugin::m_pluginDescriptor = { QString("AM Demodulator"), - QString("3.12.0"), + QString("4.1.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, @@ -34,10 +35,19 @@ void AMDemodPlugin::initPlugin(PluginAPI* pluginAPI) m_pluginAPI->registerRxChannel(AMDemod::m_channelIdURI, AMDemod::m_channelId, this); } +#ifdef SERVER_MODE +PluginInstanceGUI* AMDemodPlugin::createRxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSink *rxChannel __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* AMDemodPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) { return AMDemodGUI::create(m_pluginAPI, deviceUISet, rxChannel); } +#endif BasebandSampleSink* AMDemodPlugin::createRxChannelBS(DeviceSourceAPI *deviceAPI) { diff --git a/plugins/channelrx/demodam/amdemodplugin.h b/plugins/channelrx/demodam/amdemodplugin.h index efcaf974b..5a20b596d 100644 --- a/plugins/channelrx/demodam/amdemodplugin.h +++ b/plugins/channelrx/demodam/amdemodplugin.h @@ -26,7 +26,7 @@ class BasebandSampleSink; class AMDemodPlugin : public QObject, PluginInterface { Q_OBJECT Q_INTERFACES(PluginInterface) - Q_PLUGIN_METADATA(IID "de.maintech.sdrangelove.channel.am") + Q_PLUGIN_METADATA(IID "sdrangel.channel.amdemod") public: explicit AMDemodPlugin(QObject* parent = NULL); diff --git a/plugins/channelrx/demodam/amdemodsettings.cpp b/plugins/channelrx/demodam/amdemodsettings.cpp index 2af7ceaab..cf3c36ae5 100644 --- a/plugins/channelrx/demodam/amdemodsettings.cpp +++ b/plugins/channelrx/demodam/amdemodsettings.cpp @@ -33,14 +33,13 @@ void AMDemodSettings::resetToDefaults() m_rfBandwidth = 5000; m_squelch = -40.0; m_volume = 2.0; - m_audioSampleRate = DSPEngine::instance()->getAudioSampleRate(); m_audioMute = false; m_bandpassEnable = false; - m_copyAudioToUDP = false; - m_udpAddress = "127.0.0.1"; - m_udpPort = 9999; m_rgbColor = QColor(255, 255, 0).rgb(); m_title = "AM Demodulator"; + m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; + m_pll = false; + m_syncAMOperation = SyncAMDSB; } QByteArray AMDemodSettings::serialize() const @@ -58,6 +57,10 @@ QByteArray AMDemodSettings::serialize() const s.writeU32(7, m_rgbColor); s.writeBool(8, m_bandpassEnable); s.writeString(9, m_title); + s.writeString(11, m_audioDeviceName); + s.writeBool(12, m_pll); + s.writeS32(13, (int) m_syncAMOperation); + return s.final(); } @@ -93,6 +96,10 @@ bool AMDemodSettings::deserialize(const QByteArray& data) d.readU32(7, &m_rgbColor); d.readBool(8, &m_bandpassEnable, false); d.readString(9, &m_title, "AM Demodulator"); + d.readString(11, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName); + d.readBool(12, &m_pll, false); + d.readS32(13, &tmp, 0); + m_syncAMOperation = tmp < 0 ? SyncAMDSB : tmp > 2 ? SyncAMDSB : (SyncAMOperation) tmp; return true; } diff --git a/plugins/channelrx/demodam/amdemodsettings.h b/plugins/channelrx/demodam/amdemodsettings.h index c469246f6..3aaebd230 100644 --- a/plugins/channelrx/demodam/amdemodsettings.h +++ b/plugins/channelrx/demodam/amdemodsettings.h @@ -23,19 +23,25 @@ class Serializable; struct AMDemodSettings { + enum SyncAMOperation + { + SyncAMDSB, + SyncAMUSB, + SyncAMLSB + }; + qint32 m_inputFrequencyOffset; Real m_rfBandwidth; Real m_squelch; Real m_volume; - quint32 m_audioSampleRate; bool m_audioMute; bool m_bandpassEnable; - bool m_copyAudioToUDP; - QString m_udpAddress; - quint16 m_udpPort; quint32 m_rgbColor; QString m_title; Serializable *m_channelMarker; + QString m_audioDeviceName; + bool m_pll; + SyncAMOperation m_syncAMOperation; AMDemodSettings(); void resetToDefaults(); diff --git a/plugins/channelrx/demodam/amdemodssb.ui b/plugins/channelrx/demodam/amdemodssb.ui new file mode 100644 index 000000000..5e4b20a22 --- /dev/null +++ b/plugins/channelrx/demodam/amdemodssb.ui @@ -0,0 +1,96 @@ + + + AMDemodSSBDialog + + + + 0 + 0 + 199 + 115 + + + + + Liberation Sans + 9 + + + + Dialog + + + + + + SAM sideband selection + + + + + + LSB + + + + + + + USB + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + buttonBox + + + + + buttonBox + accepted() + AMDemodSSBDialog + accept() + + + 257 + 194 + + + 157 + 203 + + + + + buttonBox + rejected() + AMDemodSSBDialog + reject() + + + 314 + 194 + + + 286 + 203 + + + + + diff --git a/plugins/channelrx/demodam/amdemodssbdialog.cpp b/plugins/channelrx/demodam/amdemodssbdialog.cpp new file mode 100644 index 000000000..d7c42ed70 --- /dev/null +++ b/plugins/channelrx/demodam/amdemodssbdialog.cpp @@ -0,0 +1,41 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "amdemodssbdialog.h" +#include "ui_amdemodssb.h" + +AMDemodSSBDialog::AMDemodSSBDialog(bool usb, QWidget* parent) : + QDialog(parent), + ui(new Ui::AMDemodSSBDialog), + m_usb(usb) +{ + ui->setupUi(this); + ui->usb->setChecked(usb); + ui->lsb->setChecked(!usb); +} + +AMDemodSSBDialog::~AMDemodSSBDialog() +{ + delete ui; +} + +void AMDemodSSBDialog::accept() +{ + m_usb = ui->usb->isChecked(); + QDialog::accept(); +} + + diff --git a/plugins/channelrx/demodam/amdemodssbdialog.h b/plugins/channelrx/demodam/amdemodssbdialog.h new file mode 100644 index 000000000..1ce527488 --- /dev/null +++ b/plugins/channelrx/demodam/amdemodssbdialog.h @@ -0,0 +1,43 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_CHANNELRX_DEMODAM_AMDEMODSSBDIALOG_H_ +#define PLUGINS_CHANNELRX_DEMODAM_AMDEMODSSBDIALOG_H_ + +#include + +namespace Ui { + class AMDemodSSBDialog; +} + +class AMDemodSSBDialog : public QDialog +{ + Q_OBJECT +public: + explicit AMDemodSSBDialog(bool usb, QWidget* parent = 0); + ~AMDemodSSBDialog(); + + bool isUsb() const { return m_usb; } + +private: + Ui::AMDemodSSBDialog* ui; + bool m_usb; + +private slots: + void accept(); +}; + +#endif /* PLUGINS_CHANNELRX_DEMODAM_AMDEMODSSBDIALOG_H_ */ diff --git a/plugins/channelrx/demodam/demodam.pro b/plugins/channelrx/demodam/demodam.pro index 1ab4a6d8b..72a77a5fb 100644 --- a/plugins/channelrx/demodam/demodam.pro +++ b/plugins/channelrx/demodam/demodam.pro @@ -18,8 +18,10 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui +INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug @@ -27,16 +29,20 @@ CONFIG(Debug):build_subdir = debug SOURCES += amdemod.cpp\ amdemodgui.cpp\ amdemodplugin.cpp\ - amdemodsettings.cpp + amdemodsettings.cpp\ + amdemodssbdialog.cpp HEADERS += amdemod.h\ amdemodgui.h\ amdemodplugin.h\ - amdemodsettings.h + amdemodsettings.h\ + amdemodssbdialog.h -FORMS += amdemodgui.ui +FORMS += amdemodgui.ui\ + amdemodssb.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui +LIBS += -L../../../swagger/$${build_subdir} -lswagger RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/plugins/channelrx/demodam/readme.md b/plugins/channelrx/demodam/readme.md index 7de066f4c..418dcce9b 100644 --- a/plugins/channelrx/demodam/readme.md +++ b/plugins/channelrx/demodam/readme.md @@ -10,40 +10,46 @@ This plugin can be used to listen to a narrowband amplitude modulated signal. "N

1: Frequency shift from center frequency of reception

-Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. -

2: Channel power

+

2: PLL and synchronous AM

+ +Use this toggle button to turn on or off the PLL locking and synchronous AM detection. When on the input signal is mixed with the NCO of the PLL that locks to the carrier of the AM transmission. Then the signal is processed as a DSB or SSB (see control 3) modulated signal. The main advantage compared to enveloppe detection is a better resilience to carrier selective fading. This does not prevents all selective fading distorsion but addresses the most annoying. + +When the PLL is locked the icon lights up in green. The frequency shift from carrier appears in the tooltip. Locking indicator is pretty sharp with about +/- 100 Hz range. + +

3: DSB/SSB selection

+ +Use the left mouse button to toggle DSB/SSB operation. Soemtimes one of the two sidebands is affected by interference. Selecting SSB may help by using only the sideband without interference. Right click to open a dialog to select which sideband is used (LSB or USB). + +

4: Channel power

Average total power in dB relative to a +/- 1.0 amplitude signal received in the pass band. -

3: Audio mute

+

5: Audio mute and audio output select

-Use this button to toggle audio mute for this channel. The button will light up in green if the squelch is open. This helps identifying which channels are active in a multi-channel configuration. +Left click on this button to toggle audio mute for this channel. The button will light up in green if the squelch is open. This helps identifying which channels are active in a multi-channel configuration. -

4: UDP output

+If you right click on it it will open a dialog to select the audio output device. See [audio management documentation](../../../sdrgui/audio.md) for details. -Copies audio output to UDP. Audio is set at fixed level and is muted by the mute button (13) and squelch (9) is also applied. Output is mono S16LE samples. - -UDP address and send port are specified in the basic channel settings. See: [here](https://github.com/f4exb/sdrangel/blob/master/sdrgui/readme.md#6-channels) - -

5: Level meter in dB

+

6: Level meter in dB

- top bar (green): average value - bottom bar (blue green): instantaneous peak value - tip vertical bar (bright green): peak hold value -

6:Bandpass boxcar filter toggle

+

7:Bandpass boxcar filter toggle

-Use this button to enable or disable the bandpass boxcar (sharp) filter with low cutoff at 300 Hz and high cutoff at half the RF bandwidth. This may help readibility of low signals on air traffic communications but degrades audio on comfortable AM broadcast transmissions. +Use this button to enable or disable the bandpass boxcar (sharp) filter with low cutoff at 300 Hz and high cutoff at half the RF bandwidth. This may help readability of low signals on air traffic communications but degrades audio on comfortable AM broadcast transmissions. -

7: RF bandwidth

+

8: RF bandwidth

This is the bandwidth in kHz of the channel signal before demodulation. It can be set continuously in 1 kHz steps from 1 to 40 kHz. -

8: Volume

+

9: Volume

This is the volume of the audio signal from 0.0 (mute) to 10.0 (maximum). It can be varied continuously in 0.1 steps using the dial button. -

9: Squelch threshold

+

10: Squelch threshold

This is the squelch threshold in dB. The average total power received in the signal bandwidth before demodulation is compared to this value and the squelch input is open above this value. It can be varied continuously in 0.1 dB steps from 0.0 to -100.0 dB using the dial button. diff --git a/plugins/channelrx/demodatv/CMakeLists.txt b/plugins/channelrx/demodatv/CMakeLists.txt index e92bc546e..7665ee799 100644 --- a/plugins/channelrx/demodatv/CMakeLists.txt +++ b/plugins/channelrx/demodatv/CMakeLists.txt @@ -5,8 +5,6 @@ set(atv_SOURCES atvdemodsettings.cpp atvdemodgui.cpp atvdemodplugin.cpp - atvscreen.cpp - glshaderarray.cpp ) set(atv_HEADERS @@ -14,9 +12,6 @@ set(atv_HEADERS atvdemodsettings.h atvdemodgui.h atvdemodplugin.h - atvscreen.h - atvscreeninterface.h - glshaderarray.h ) set(atv_FORMS @@ -48,6 +43,6 @@ target_link_libraries(demodatv sdrgui ) -qt5_use_modules(demodatv Core Widgets) +target_link_libraries(demodatv Qt5::Core Qt5::Widgets) install(TARGETS demodatv DESTINATION lib/plugins/channelrx) diff --git a/plugins/channelrx/demodatv/atvdemod.cpp b/plugins/channelrx/demodatv/atvdemod.cpp index 6855fd210..68e8feed5 100644 --- a/plugins/channelrx/demodatv/atvdemod.cpp +++ b/plugins/channelrx/demodatv/atvdemod.cpp @@ -42,7 +42,7 @@ ATVDemod::ATVDemod(DeviceSourceAPI *deviceAPI) : ChannelSinkAPI(m_channelIdURI), m_deviceAPI(deviceAPI), m_scopeSink(0), - m_registeredATVScreen(0), + m_registeredTVScreen(0), m_intNumberSamplePerTop(0), m_intImageIndex(0), m_intSynchroPoints(0), @@ -85,14 +85,14 @@ ATVDemod::ATVDemod(DeviceSourceAPI *deviceAPI) : m_objPhaseDiscri.setFMScaling(1.0f); + applyStandard(); + m_channelizer = new DownChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); m_deviceAPI->addThreadedSink(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); connect(m_channelizer, SIGNAL(inputSampleRateChanged()), this, SLOT(channelSampleRateChanged())); - - applyStandard(); } ATVDemod::~ATVDemod() @@ -101,11 +101,13 @@ ATVDemod::~ATVDemod() m_deviceAPI->removeThreadedSink(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; + delete m_DSBFilter; + delete m_DSBFilterBuffer; } -void ATVDemod::setATVScreen(ATVScreenInterface *objScreen) +void ATVDemod::setTVScreen(TVScreen *objScreen) { - m_registeredATVScreen = objScreen; + m_registeredTVScreen = objScreen; } void ATVDemod::configure( @@ -431,13 +433,13 @@ void ATVDemod::demod(Complex& c) //********** process video sample ********** - if (m_running.m_enmATVStandard == ATVStdHSkip) + if (m_registeredTVScreen) // can process only if the screen is available (set via the GUI) { - processHSkip(fltVal, intVal); - } - else - { - processClassic(fltVal, intVal); + if (m_running.m_enmATVStandard == ATVStdHSkip) { + processHSkip(fltVal, intVal); + } else { + processClassic(fltVal, intVal); + } } } @@ -607,10 +609,10 @@ void ATVDemod::applySettings() m_configPrivate.m_intNumberSamplePerLine = (int) (m_config.m_fltLineDuration * m_config.m_intSampleRate); m_intNumberSamplePerTop = (int) (m_config.m_fltTopDuration * m_config.m_intSampleRate); - if (m_registeredATVScreen) + if (m_registeredTVScreen) { - m_registeredATVScreen->setRenderImmediate(!(m_config.m_fltFramePerS > 25.0f)); - m_registeredATVScreen->resizeATVScreen( + //m_registeredTVScreen->setRenderImmediate(!(m_config.m_fltFramePerS > 25.0f)); + m_registeredTVScreen->resizeTVScreen( m_configPrivate.m_intNumberSamplePerLine - m_intNumberSamplePerLineSignals, m_intNumberOfLines - m_intNumberOfBlackLines); } diff --git a/plugins/channelrx/demodatv/atvdemod.h b/plugins/channelrx/demodatv/atvdemod.h index 22d348392..dda259740 100644 --- a/plugins/channelrx/demodatv/atvdemod.h +++ b/plugins/channelrx/demodatv/atvdemod.h @@ -37,7 +37,7 @@ #include "dsp/phasediscri.h" #include "audio/audiofifo.h" #include "util/message.h" -#include "atvscreeninterface.h" +#include "gui/tvscreen.h" class DeviceSourceAPI; class ThreadedBasebandSampleSink; @@ -231,7 +231,7 @@ public: virtual QByteArray serialize() const { return QByteArray(); } virtual bool deserialize(const QByteArray& data __attribute__((unused))) { return false; } - void setATVScreen(ATVScreenInterface *objScreen); + void setTVScreen(TVScreen *objScreen); //!< set by the GUI int getSampleRate(); int getEffectiveSampleRate(); double getMagSq() const { return m_objMagSqAverage; } //!< Beware this is scaled to 2^30 @@ -413,7 +413,7 @@ private: SampleVector m_scopeSampleBuffer; //*************** ATV PARAMETERS *************** - ATVScreenInterface * m_registeredATVScreen; + TVScreen *m_registeredTVScreen; //int m_intNumberSamplePerLine; int m_intNumberSamplePerTop; @@ -497,7 +497,7 @@ private: inline void processHSkip(float& fltVal, int& intVal) { - m_registeredATVScreen->setDataColor(m_intColIndex - m_intNumberSaplesPerHSync + m_intNumberSamplePerTop, intVal, intVal, intVal); + m_registeredTVScreen->setDataColor(m_intColIndex - m_intNumberSaplesPerHSync + m_intNumberSamplePerTop, intVal, intVal, intVal); // Horizontal Synchro detection @@ -522,7 +522,7 @@ private: { //qDebug("VSync: %d %d %d", m_intColIndex, m_intSampleIndex, m_intLineIndex); m_intAvgColIndex = m_intColIndex; - m_registeredATVScreen->renderImage(0); + m_registeredTVScreen->renderImage(0); m_intImageIndex++; m_intLineIndex = 0; @@ -570,7 +570,7 @@ private: m_fltEffMax = -2000000.0f; } - m_registeredATVScreen->selectRow(m_intRowIndex); + m_registeredTVScreen->selectRow(m_intRowIndex); m_intLineIndex++; m_intRowIndex++; } @@ -658,7 +658,7 @@ private: if (m_intRowIndex < m_intNumberOfLines) { - m_registeredATVScreen->selectRow(m_intRowIndex - m_intNumberOfSyncLines); + m_registeredTVScreen->selectRow(m_intRowIndex - m_intNumberOfSyncLines); } m_intLineIndex++; @@ -667,7 +667,7 @@ private: // Filling pixels // +4 is to compensate shift due to hsync amortizing factor of 1/4 - m_registeredATVScreen->setDataColor(m_intColIndex - m_intNumberSaplesPerHSync + m_intNumberSamplePerTop + 4, intVal, intVal, intVal); + m_registeredTVScreen->setDataColor(m_intColIndex - m_intNumberSaplesPerHSync + m_intNumberSamplePerTop + 4, intVal, intVal, intVal); m_intColIndex++; // Vertical sync and image rendering @@ -686,7 +686,7 @@ private: if ((m_intLineIndex % 2 == 0) || !m_interleaved) // even => odd image { - m_registeredATVScreen->renderImage(0); + m_registeredTVScreen->renderImage(0); m_intRowIndex = 1; } else @@ -694,7 +694,7 @@ private: m_intRowIndex = 0; } - m_registeredATVScreen->selectRow(m_intRowIndex - m_intNumberOfSyncLines); + m_registeredTVScreen->selectRow(m_intRowIndex - m_intNumberOfSyncLines); m_intLineIndex = 0; m_intImageIndex++; } @@ -711,7 +711,7 @@ private: { if (m_intImageIndex % 2 == 1) // odd image { - m_registeredATVScreen->renderImage(0); + m_registeredTVScreen->renderImage(0); if (m_rfRunning.m_enmModulation == ATV_AM) { @@ -736,7 +736,7 @@ private: m_intRowIndex = 0; } - m_registeredATVScreen->selectRow(m_intRowIndex - m_intNumberOfSyncLines); + m_registeredTVScreen->selectRow(m_intRowIndex - m_intNumberOfSyncLines); m_intLineIndex = 0; m_intImageIndex++; } diff --git a/plugins/channelrx/demodatv/atvdemodgui.cpp b/plugins/channelrx/demodatv/atvdemodgui.cpp index c794eb420..4fadfd9b6 100644 --- a/plugins/channelrx/demodatv/atvdemodgui.cpp +++ b/plugins/channelrx/demodatv/atvdemodgui.cpp @@ -25,7 +25,7 @@ #include "dsp/downchannelizer.h" #include "dsp/threadedbasebandsamplesink.h" -#include "dsp/scopevisng.h" +#include "dsp/scopevis.h" #include "ui_atvdemodgui.h" #include "plugin/pluginapi.h" #include "util/simpleserializer.h" @@ -211,7 +211,7 @@ bool ATVDemodGUI::handleMessage(const Message& objMessage) int nbPointsPerLine = ((ATVDemod::MsgReportEffectiveSampleRate&)objMessage).getNbPointsPerLine(); ui->channelSampleRateText->setText(tr("%1k").arg(sampleRate/1000.0f, 0, 'f', 2)); ui->nbPointsPerLineText->setText(tr("%1p").arg(nbPointsPerLine)); - m_scopeVis->setSampleRate(sampleRate); + m_scopeVis->setLiveRate(sampleRate); setRFFiltersSlidersRange(sampleRate); lineTimeUpdate(); topTimeUpdate(); @@ -278,14 +278,15 @@ ATVDemodGUI::ATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, Base m_inputSampleRate(48000) { ui->setupUi(this); + ui->screenTV->setColor(false); setAttribute(Qt::WA_DeleteOnClose, true); connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); - m_scopeVis = new ScopeVisNG(ui->glScope); + m_scopeVis = new ScopeVis(ui->glScope); m_atvDemod = (ATVDemod*) rxChannel; //new ATVDemod(m_deviceUISet->m_deviceSourceAPI); m_atvDemod->setMessageQueueToGUI(getInputMessageQueue()); m_atvDemod->setScopeSink(m_scopeVis); - m_atvDemod->setATVScreen(ui->screenTV); + m_atvDemod->setTVScreen(ui->screenTV); ui->glScope->connectTimer(MainWindow::getInstance()->getMasterTimer()); connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); // 50 ms @@ -312,14 +313,14 @@ ATVDemodGUI::ATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, Base resetToDefaults(); // does applySettings() ui->scopeGUI->setPreTrigger(1); - ScopeVisNG::TraceData traceData; + ScopeVis::TraceData traceData; traceData.m_amp = 2.0; // amplification factor traceData.m_ampIndex = 1; // this is second step traceData.m_ofs = 0.5; // direct offset traceData.m_ofsCoarse = 50; // this is 50 coarse steps ui->scopeGUI->changeTrace(0, traceData); ui->scopeGUI->focusOnTrace(0); // re-focus to take changes into account in the GUI - ScopeVisNG::TriggerData triggerData; + ScopeVis::TriggerData triggerData; triggerData.m_triggerLevel = 0.1; triggerData.m_triggerLevelCoarse = 10; triggerData.m_triggerPositiveEdge = false; diff --git a/plugins/channelrx/demodatv/atvdemodgui.h b/plugins/channelrx/demodatv/atvdemodgui.h index 07defe5cc..5c0d1e457 100644 --- a/plugins/channelrx/demodatv/atvdemodgui.h +++ b/plugins/channelrx/demodatv/atvdemodgui.h @@ -28,7 +28,7 @@ class PluginAPI; class DeviceUISet; class BasebandSampleSink; class ATVDemod; -class ScopeVisNG; +class ScopeVis; namespace Ui { @@ -70,7 +70,7 @@ private: MovingAverageUtil m_objMagSqAverage; int m_intTickCount; - ScopeVisNG* m_scopeVis; + ScopeVis* m_scopeVis; float m_fltLineTimeMultiplier; float m_fltTopTimeMultiplier; diff --git a/plugins/channelrx/demodatv/atvdemodgui.ui b/plugins/channelrx/demodatv/atvdemodgui.ui index d48aa0577..4107c6a50 100644 --- a/plugins/channelrx/demodatv/atvdemodgui.ui +++ b/plugins/channelrx/demodatv/atvdemodgui.ui @@ -24,7 +24,7 @@
- Sans Serif + Liberation Sans 9 @@ -86,7 +86,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -265,7 +265,7 @@ - Channel power + Channel power (dB) -100.0 dB @@ -377,7 +377,7 @@ - Engage asymmertical bandpass FFT filter + Engage asymmetrical bandpass FFT filter @@ -1037,7 +1037,7 @@ QLayout::SetMinimumSize - + 0 @@ -1088,17 +1088,23 @@ 0 - + 0 312 + + + Liberation Mono + 8 + + - + @@ -1117,21 +1123,15 @@
gui/buttonswitch.h
- ATVScreen + GLScope QWidget -
atvscreen.h
+
gui/glscope.h
1
- GLScopeNG + GLScopeGUI QWidget -
gui/glscopeng.h
- 1 -
- - GLScopeNGGUI - QWidget -
gui/glscopenggui.h
+
gui/glscopegui.h
1
@@ -1140,6 +1140,12 @@
gui/valuedialz.h
1
+ + TVScreen + QWidget +
gui/tvscreen.h
+ 1 +
diff --git a/plugins/channelrx/demodatv/atvdemodplugin.cpp b/plugins/channelrx/demodatv/atvdemodplugin.cpp index aba8cc1f6..f728462f5 100644 --- a/plugins/channelrx/demodatv/atvdemodplugin.cpp +++ b/plugins/channelrx/demodatv/atvdemodplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor ATVDemodPlugin::m_ptrPluginDescriptor = { QString("ATV Demodulator"), - QString("3.12.0"), + QString("4.2.4"), QString("(c) F4HKW for F4EXB / SDRAngel"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demodatv/atvdemodsettings.h b/plugins/channelrx/demodatv/atvdemodsettings.h index f72f8e9d7..fb4c39fc1 100644 --- a/plugins/channelrx/demodatv/atvdemodsettings.h +++ b/plugins/channelrx/demodatv/atvdemodsettings.h @@ -21,7 +21,7 @@ #include #include -struct Serializable; +class Serializable; struct ATVDemodSettings { diff --git a/plugins/channelrx/demodatv/atvscreen.cpp b/plugins/channelrx/demodatv/atvscreen.cpp deleted file mode 100644 index bba4aaf3d..000000000 --- a/plugins/channelrx/demodatv/atvscreen.cpp +++ /dev/null @@ -1,201 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2017 F4HKW // -// for F4EXB / SDRAngel // -// // -// OpenGL interface modernization. // -// // -// This program is free software; you can redistribute it and/or modify // -// it under the terms of the GNU General Public License as published by // -// the Free Software Foundation as version 3 of the License, or // -// // -// This program is distributed in the hope that it will be useful, // -// but WITHOUT ANY WARRANTY; without even the implied warranty of // -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // -// GNU General Public License V3 for more details. // -// // -// You should have received a copy of the GNU General Public License // -// along with this program. If not, see . // -/////////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include -#include -#include "atvscreen.h" - -#include -#include - -ATVScreen::ATVScreen(QWidget* parent) : - QGLWidget(parent), ATVScreenInterface(), m_objMutex(QMutex::NonRecursive) -{ - setAttribute(Qt::WA_OpaquePaintEvent); - connect(&m_objTimer, SIGNAL(timeout()), this, SLOT(tick())); - m_objTimer.start(40); // capped at 25 FPS - - m_chrLastData = NULL; - m_blnConfigChanged = false; - m_blnDataChanged = false; - m_blnGLContextInitialized = false; - - //Par défaut - m_intAskedCols = ATV_COLS; - m_intAskedRows = ATV_ROWS; -} - -ATVScreen::~ATVScreen() -{ - cleanup(); -} - -QRgb* ATVScreen::getRowBuffer(int intRow) -{ - if (m_blnGLContextInitialized == false) - { - return NULL; - } - - return m_objGLShaderArray.GetRowBuffer(intRow); -} - -void ATVScreen::renderImage(unsigned char * objData) -{ - m_chrLastData = objData; - m_blnDataChanged = true; - if (m_blnRenderImmediate) update(); -} - -void ATVScreen::resetImage() -{ - m_objGLShaderArray.ResetPixels(); -} - -void ATVScreen::resizeATVScreen(int intCols, int intRows) -{ - m_intAskedCols = intCols; - m_intAskedRows = intRows; -} - -void ATVScreen::initializeGL() -{ - m_objMutex.lock(); - - QOpenGLContext *objGlCurrentContext = QOpenGLContext::currentContext(); - - if (objGlCurrentContext) - { - if (QOpenGLContext::currentContext()->isValid()) - { - qDebug() << "ATVScreen::initializeGL: context:" - << " major: " << (QOpenGLContext::currentContext()->format()).majorVersion() - << " minor: " << (QOpenGLContext::currentContext()->format()).minorVersion() - << " ES: " << (QOpenGLContext::currentContext()->isOpenGLES() ? "yes" : "no"); - } - else - { - qDebug() << "ATVScreen::initializeGL: current context is invalid"; - } - } - else - { - qCritical() << "ATVScreen::initializeGL: no current context"; - return; - } - - QSurface *objSurface = objGlCurrentContext->surface(); - - if (objSurface == NULL) - { - qCritical() << "ATVScreen::initializeGL: no surface attached"; - return; - } - else - { - if (objSurface->surfaceType() != QSurface::OpenGLSurface) - { - qCritical() << "ATVScreen::initializeGL: surface is not an OpenGLSurface: " - << objSurface->surfaceType() - << " cannot use an OpenGL context"; - return; - } - else - { - qDebug() << "ATVScreen::initializeGL: OpenGL surface:" - << " class: " << (objSurface->surfaceClass() == QSurface::Window ? "Window" : "Offscreen"); - } - } - - connect(objGlCurrentContext, &QOpenGLContext::aboutToBeDestroyed, this, - &ATVScreen::cleanup); // TODO: when migrating to QOpenGLWidget - - m_blnGLContextInitialized = true; - - m_objMutex.unlock(); -} - -void ATVScreen::resizeGL(int intWidth, int intHeight) -{ - QOpenGLFunctions *ptrF = QOpenGLContext::currentContext()->functions(); - ptrF->glViewport(0, 0, intWidth, intHeight); - m_blnConfigChanged = true; -} - -void ATVScreen::paintGL() -{ - if (!m_objMutex.tryLock(2)) - return; - - m_blnDataChanged = false; - - if ((m_intAskedCols != 0) && (m_intAskedRows != 0)) - { - m_objGLShaderArray.InitializeGL(m_intAskedCols, m_intAskedRows); - m_intAskedCols = 0; - m_intAskedRows = 0; - } - - m_objGLShaderArray.RenderPixels(m_chrLastData); - - m_objMutex.unlock(); -} - -void ATVScreen::mousePressEvent(QMouseEvent* event __attribute__((unused))) -{ -} - -void ATVScreen::tick() -{ - if (m_blnDataChanged) { - update(); - } -} - -void ATVScreen::cleanup() -{ - if (m_blnGLContextInitialized) - { - m_objGLShaderArray.Cleanup(); - } -} - -bool ATVScreen::selectRow(int intLine) -{ - if (m_blnGLContextInitialized) - { - return m_objGLShaderArray.SelectRow(intLine); - } - - return false; -} - -bool ATVScreen::setDataColor(int intCol, int intRed, int intGreen, int intBlue) -{ - if (m_blnGLContextInitialized) - { - return m_objGLShaderArray.SetDataColor(intCol, - qRgb(intRed, intGreen, intBlue)); - } - - return false; -} diff --git a/plugins/channelrx/demodatv/atvscreen.h b/plugins/channelrx/demodatv/atvscreen.h deleted file mode 100644 index 37068aaa0..000000000 --- a/plugins/channelrx/demodatv/atvscreen.h +++ /dev/null @@ -1,94 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2017 F4HKW // -// for F4EXB / SDRAngel // -// // -// OpenGL interface modernization. // -// See: http://doc.qt.io/qt-5/qopenglshaderprogram.html // -// // -// This program is free software; you can redistribute it and/or modify // -// it under the terms of the GNU General Public License as published by // -// the Free Software Foundation as version 3 of the License, or // -// // -// This program is distributed in the hope that it will be useful, // -// but WITHOUT ANY WARRANTY; without even the implied warranty of // -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // -// GNU General Public License V3 for more details. // -// // -// You should have received a copy of the GNU General Public License // -// along with this program. If not, see . // -/////////////////////////////////////////////////////////////////////////////////// - -#ifndef INCLUDE_ATVSCREEN_H -#define INCLUDE_ATVSCREEN_H - -#include -#include -#include -#include -#include -#include -#include "dsp/dsptypes.h" -#include "glshaderarray.h" -#include "gui/glshadertextured.h" -#include "util/export.h" -#include "util/bitfieldindex.h" - -#include "atvscreeninterface.h" - -class QPainter; - - - -class SDRANGEL_API ATVScreen: public QGLWidget, public ATVScreenInterface -{ - Q_OBJECT - -public: - - ATVScreen(QWidget* parent = NULL); - virtual ~ATVScreen(); - - virtual void resizeATVScreen(int intCols, int intRows); - virtual void renderImage(unsigned char * objData); - virtual bool selectRow(int intLine); - virtual bool setDataColor(int intCol,int intRed, int intGreen, int intBlue); - -signals: - void traceSizeChanged(int); - void sampleRateChanged(int); - -private: - bool m_blnGLContextInitialized; - int m_intAskedCols; - int m_intAskedRows; - - - // state - QTimer m_objTimer; - QMutex m_objMutex; - bool m_blnDataChanged; - bool m_blnConfigChanged; - - GLShaderArray m_objGLShaderArray; - - unsigned char *m_chrLastData; - - //Valeurs par défaut - static const int ATV_COLS=192; - static const int ATV_ROWS=625; - - void initializeGL(); - void resizeGL(int width, int height); - void paintGL(); - - void mousePressEvent(QMouseEvent*); - - QRgb* getRowBuffer(int intRow); - void resetImage(); - -protected slots: - void cleanup(); - void tick(); -}; - -#endif // INCLUDE_ATVSCREEN_H diff --git a/plugins/channelrx/demodatv/demodatv.pro b/plugins/channelrx/demodatv/demodatv.pro index 5fc8131bf..decef5410 100644 --- a/plugins/channelrx/demodatv/demodatv.pro +++ b/plugins/channelrx/demodatv/demodatv.pro @@ -18,28 +18,24 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug -CONFIG(MINGW32):INCLUDEPATH += "D:\boost_1_58_0" -CONFIG(MINGW64):INCLUDEPATH += "D:\boost_1_58_0" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\boost_1_66_0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\boost_1_66_0" CONFIG(macx):INCLUDEPATH += "../../../../../boost_1_64_0" SOURCES += atvdemod.cpp\ atvdemodgui.cpp\ - atvdemodplugin.cpp\ - atvscreen.cpp\ - glshaderarray.cpp + atvdemodplugin.cpp HEADERS += atvdemod.h\ atvdemodgui.h\ - atvdemodplugin.h\ - atvscreen.h\ - atvscreeninterface.h\ - glshaderarray.h + atvdemodplugin.h FORMS += atvdemodgui.ui diff --git a/plugins/channelrx/demodatv/glshaderarray.h b/plugins/channelrx/demodatv/glshaderarray.h deleted file mode 100644 index 4c34969f3..000000000 --- a/plugins/channelrx/demodatv/glshaderarray.h +++ /dev/null @@ -1,76 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2017 F4HKW // -// for F4EXB / SDRAngel // -// // -// This program is free software; you can redistribute it and/or modify // -// it under the terms of the GNU General Public License as published by // -// the Free Software Foundation as version 3 of the License, or // -// // -// This program is distributed in the hope that it will be useful, // -// but WITHOUT ANY WARRANTY; without even the implied warranty of // -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // -// GNU General Public License V3 for more details. // -// // -// You should have received a copy of the GNU General Public License // -// along with this program. If not, see . // -/////////////////////////////////////////////////////////////////////////////////// - -#ifndef INCLUDE_GUI_GLSHADERARRAY_H_ -#define INCLUDE_GUI_GLSHADERARRAY_H_ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -class QOpenGLShaderProgram; -class QMatrix4x4; -class QVector4D; - -class GLShaderArray -{ -public: - GLShaderArray(); - ~GLShaderArray(); - - void InitializeGL(int intCols, int intRows); - void ResizeContainer(int intCols, int intRows); - void Cleanup(); - QRgb *GetRowBuffer(int intRow); - void RenderPixels(unsigned char *chrData); - void ResetPixels(); - - bool SelectRow(int intLine); - bool SetDataColor(int intCol,QRgb objColor); - - -protected: - - QOpenGLShaderProgram *m_objProgram; - int m_objMatrixLoc; - int m_objTextureLoc; - int m_objColorLoc; - static const QString m_strVertexShaderSourceArray; - static const QString m_strFragmentShaderSourceColored; - - QImage *m_objImage=NULL; - QOpenGLTexture *m_objTexture=NULL; - - int m_intCols; - int m_intRows; - - QRgb * m_objCurrentRow; - - bool m_blnInitialized; -}; - -#endif /* INCLUDE_GUI_GLSHADERARRAY_H_ */ diff --git a/plugins/channelrx/demodatv/readme.md b/plugins/channelrx/demodatv/readme.md index 3f1370775..b858ba8b9 100644 --- a/plugins/channelrx/demodatv/readme.md +++ b/plugins/channelrx/demodatv/readme.md @@ -26,7 +26,7 @@ Each part is detailed next

1: Frequency shift from center frequency of reception

-Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews.Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows.Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

3: Rational downsampler toggle

@@ -42,7 +42,7 @@ N = S / (l × F) ☞ The number of points should be a bit larger than the number of lines up to 240 lines then it can be a bit smaller to give an acceptable image quality. -When the rational dowsampler is engaged (3) the resulting sample rate is calculated as the closest 10 S/s multiple to the source sample rate to fit an integer number of line points. +When the rational downsampler is engaged (3) the resulting sample rate is calculated as the closest 10 S/s multiple to the source sample rate to fit an integer number of line points. The example taken in the screenshot is from a 405 lines × 20 FPS video signal: @@ -66,9 +66,9 @@ When single sideband demodulation is selected (USB, LSB) the BFO is phased locke ⚠ this is experimental. -This allows adjstment of BFO frequency in 1 Hz steps from -2 to +2 kHz. You will have to look for the right value to lock to the carrier. See (6) for the lock indicator. This will work only at relatively low sample rates say below 1 MS/s. +This allows adjustment of BFO frequency in 1 Hz steps from -2 to +2 kHz. You will have to look for the right value to lock to the carrier. See (6) for the lock indicator. This will work only at relatively low sample rates say below 1 MS/s. -The BFO base frequency in Hz appears on the right. Actual frequency may change acoording to PLL locking to the carrier. +The BFO base frequency in Hz appears on the right. Actual frequency may change according to PLL locking to the carrier.

8: Channel power

@@ -138,13 +138,13 @@ This combo lets you set the TV standard type. This sets the number of lines per - PAL405: this is not the British standard. It just follows the same scheme as the two above but with only 7 black lines per half frame - ShI: this is an experimental mode that uses the least possible vertical sync lines as possible. That is one line for a long synchronization pulse and one line at a higher level (0.7) to reset the vertical sync condition. Thus only 2 lines are consumed for vertical sync and the rest is left to the image. In this mode the frames are interleaved - ShNI: this is the same as above but with non interleaved frames. - - HSkip: this is the horizontal sync skip technique for vertical synchronization. This has been in use in the first TV experiments with a small number of lines. This method just skips one horizontal synchronization pluse to mark the last or the first line (here it is the last). This method does not use any full line for vertical sync and all lines can be used for the image thus it suits the modes with a small number of lines. With more lines however the risk of missing pulses gets higher in adverse conditions because the pulses get shorter and may get swallowed by a stray pulse or a stray pulse can be taken for a valid one. In this case two images might get out of sync instead of just two lines. In practice this is suitable up to 90~120 lines. + - HSkip: this is the horizontal sync skip technique for vertical synchronization. This has been in use in the first TV experiments with a small number of lines. This method just skips one horizontal synchronization pulse to mark the last or the first line (here it is the last). This method does not use any full line for vertical sync and all lines can be used for the image thus it suits the modes with a small number of lines. With more lines however the risk of missing pulses gets higher in adverse conditions because the pulses get shorter and may get swallowed by a stray pulse or a stray pulse can be taken for a valid one. In this case two images might get out of sync instead of just two lines. In practice this is suitable up to 90~120 lines. When the standard chosen matches the standard of transmission the image should appear in full size and proper aspect ratio. ☞ Interleaved mode requires an odd number of lines because the system recognizes the even and odd frames depending on a odd or even number of lines respectively for the half images -☞ For non interlaved mode all standards are supposed to work for any number of lines. You may experiment with any and see if it fits your purpose. However it will be easier to obtain good or optimal results in general with the following recommendations: +☞ For non interleaved mode all standards are supposed to work for any number of lines. You may experiment with any and see if it fits your purpose. However it will be easier to obtain good or optimal results in general with the following recommendations: diff --git a/plugins/channelrx/demodbfm/CMakeLists.txt b/plugins/channelrx/demodbfm/CMakeLists.txt index 8eae42d0b..71721706c 100644 --- a/plugins/channelrx/demodbfm/CMakeLists.txt +++ b/plugins/channelrx/demodbfm/CMakeLists.txt @@ -1,5 +1,7 @@ project(bfm) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + set(bfm_SOURCES bfmdemod.cpp bfmdemodgui.cpp @@ -22,7 +24,9 @@ set(bfm_HEADERS rdstmc.h ) -set_source_files_properties(rdstmc.cpp PROPERTIES COMPILE_FLAGS -fno-var-tracking-assignments) +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set_source_files_properties(rdstmc.cpp PROPERTIES COMPILE_FLAGS -fno-var-tracking-assignments) +endif() set(bfm_FORMS bfmdemodgui.ui @@ -31,6 +35,7 @@ set(bfm_FORMS include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) #include(${QT_USE_FILE}) @@ -52,6 +57,6 @@ target_link_libraries(demodbfm sdrgui ) -qt5_use_modules(demodbfm Core Widgets) +target_link_libraries(demodbfm Qt5::Core Qt5::Widgets) install(TARGETS demodbfm DESTINATION lib/plugins/channelrx) diff --git a/plugins/channelrx/demodbfm/bfmdemod.cpp b/plugins/channelrx/demodbfm/bfmdemod.cpp index e5a22fe3f..1d056771d 100644 --- a/plugins/channelrx/demodbfm/bfmdemod.cpp +++ b/plugins/channelrx/demodbfm/bfmdemod.cpp @@ -17,15 +17,23 @@ #include #include +#include "boost/format.hpp" #include #include +#include "SWGChannelSettings.h" +#include "SWGBFMDemodSettings.h" +#include "SWGChannelReport.h" +#include "SWGBFMDemodReport.h" +#include "SWGRDSReport.h" + #include "audio/audiooutput.h" #include "dsp/dspengine.h" #include "dsp/downchannelizer.h" #include "dsp/threadedbasebandsamplesink.h" #include "dsp/dspcommands.h" #include "device/devicesourceapi.h" +#include "util/db.h" #include "rdsparser.h" #include "bfmdemod.h" @@ -53,6 +61,9 @@ BFMDemod::BFMDemod(DeviceSourceAPI *deviceAPI) : { setObjectName(m_channelId); + DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo, getInputMessageQueue()); + m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate(); + m_magsq = 0.0f; m_magsqSum = 0.0f; m_magsqPeak = 0.0f; @@ -75,40 +86,31 @@ BFMDemod::BFMDemod(DeviceSourceAPI *deviceAPI) : m_rfFilter = new fftfilt(-50000.0 / 384000.0, 50000.0 / 384000.0, filtFftLen); - - m_deemphasisFilterX.configure(default_deemphasis * m_settings.m_audioSampleRate * 1.0e-6); - m_deemphasisFilterY.configure(default_deemphasis * m_settings.m_audioSampleRate * 1.0e-6); + m_deemphasisFilterX.configure(default_deemphasis * m_audioSampleRate * 1.0e-6); + m_deemphasisFilterY.configure(default_deemphasis * m_audioSampleRate * 1.0e-6); m_phaseDiscri.setFMScaling(384000/m_fmExcursion); m_audioBuffer.resize(16384); m_audioBufferFill = 0; - DSPEngine::instance()->addAudioSink(&m_audioFifo); - m_udpBufferAudio = new UDPSink(this, m_udpBlockSize, m_settings.m_udpPort); + applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); m_channelizer = new DownChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); m_deviceAPI->addThreadedSink(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - - applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); - applySettings(m_settings, true); } BFMDemod::~BFMDemod() { - if (m_rfFilter) - { - delete m_rfFilter; - } - - DSPEngine::instance()->removeAudioSink(&m_audioFifo); - delete m_udpBufferAudio; + DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo); m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSink(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; + delete m_rfFilter; } void BFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst __attribute__((unused))) @@ -133,36 +135,34 @@ void BFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto for (int i =0 ; i m_magsqPeak) - { + if (msq > m_magsqPeak) { m_magsqPeak = msq; } m_magsqCount++; -// m_movingAverage.feed(msq); - - if(m_magsq >= m_squelchLevel) { - m_squelchState = m_settings.m_rfBandwidth / 20; // decay rate - } - - if(m_squelchState > 0) + if (msq >= m_squelchLevel) { - m_squelchState--; - - //demod = phaseDiscriminator2(rf[i], msq); - demod = m_phaseDiscri.phaseDiscriminator(rf[i]); + if (m_squelchState < m_settings.m_rfBandwidth / 10) { // twice attack and decay rate + m_squelchState++; + } } else { + if (m_squelchState > 0) { + m_squelchState--; + } + } + + if (m_squelchState > m_settings.m_rfBandwidth / 20) { // squelch open + demod = m_phaseDiscri.phaseDiscriminator(rf[i]); + } else { demod = 0; } - if (!m_settings.m_showPilot) - { + if (!m_settings.m_showPilot) { m_sampleBuffer.push_back(Sample(demod * SDR_RX_SCALEF, 0.0)); } @@ -177,8 +177,7 @@ void BFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto if (m_rdsDemod.process(cr.real(), bit)) { - if (m_rdsDecoder.frameSync(bit)) - { + if (m_rdsDecoder.frameSync(bit)) { m_rdsParser.parseGroup(m_rdsDecoder.getGroup()); } } @@ -195,8 +194,7 @@ void BFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto { m_pilotPLL.process(demod, m_pilotPLLSamples); - if (m_settings.m_showPilot) - { + if (m_settings.m_showPilot) { m_sampleBuffer.push_back(Sample(m_pilotPLLSamples[1] * SDR_RX_SCALEF, 0.0)); // debug 38 kHz pilot } @@ -232,18 +230,8 @@ void BFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto Real deemph_l, deemph_r; // Pre-emphasis is applied on each channel before multiplexing m_deemphasisFilterX.process(ci.real() + sampleStereo, deemph_l); m_deemphasisFilterY.process(ci.real() - sampleStereo, deemph_r); - if (m_settings.m_lsbStereo) - { - m_audioBuffer[m_audioBufferFill].l = (qint16)(deemph_l * (1<<12) * m_settings.m_volume); - m_audioBuffer[m_audioBufferFill].r = (qint16)(deemph_r * (1<<12) * m_settings.m_volume); - if (m_settings.m_copyAudioToUDP) m_udpBufferAudio->write(m_audioBuffer[m_audioBufferFill]); - } - else - { - m_audioBuffer[m_audioBufferFill].l = (qint16)(deemph_l * (1<<12) * m_settings.m_volume); - m_audioBuffer[m_audioBufferFill].r = (qint16)(deemph_r * (1<<12) * m_settings.m_volume); - if (m_settings.m_copyAudioToUDP) m_udpBufferAudio->write(m_audioBuffer[m_audioBufferFill]); - } + m_audioBuffer[m_audioBufferFill].l = (qint16)(deemph_l * (1<<12) * m_settings.m_volume); + m_audioBuffer[m_audioBufferFill].r = (qint16)(deemph_r * (1<<12) * m_settings.m_volume); } else { @@ -252,17 +240,15 @@ void BFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto quint16 sample = (qint16)(deemph * (1<<12) * m_settings.m_volume); m_audioBuffer[m_audioBufferFill].l = sample; m_audioBuffer[m_audioBufferFill].r = sample; - if (m_settings.m_copyAudioToUDP) m_udpBufferAudio->write(m_audioBuffer[m_audioBufferFill]); } ++m_audioBufferFill; - if(m_audioBufferFill >= m_audioBuffer.size()) + if (m_audioBufferFill >= m_audioBuffer.size()) { - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 1); + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); - if(res != m_audioBufferFill) - { + if(res != m_audioBufferFill) { qDebug("BFMDemod::feed: %u/%u audio samples written", res, m_audioBufferFill); } @@ -274,20 +260,18 @@ void BFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto } } - if(m_audioBufferFill > 0) + if (m_audioBufferFill > 0) { - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 1); + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); - if(res != m_audioBufferFill) - { + if (res != m_audioBufferFill) { qDebug("BFMDemod::feed: %u/%u tail samples written", res, m_audioBufferFill); } m_audioBufferFill = 0; } - if(m_sampleSink != 0) - { + if (m_sampleSink != 0) { m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), true); } @@ -350,6 +334,24 @@ bool BFMDemod::handleMessage(const Message& cmd) return true; } + else if (DSPConfigureAudio::match(cmd)) + { + DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd; + uint32_t sampleRate = cfg.getSampleRate(); + + qDebug() << "BFMDemod::handleMessage: DSPConfigureAudio:" + << " sampleRate: " << sampleRate; + + if (sampleRate != m_audioSampleRate) { + applyAudioSampleRate(sampleRate); + } + + return true; + } + else if (BasebandSampleSink::MsgThreadedSink::match(cmd)) + { + return true; + } else if (DSPSignalNotification::match(cmd)) { return true; @@ -369,6 +371,28 @@ bool BFMDemod::handleMessage(const Message& cmd) } } +void BFMDemod::applyAudioSampleRate(int sampleRate) +{ + qDebug("BFMDemod::applyAudioSampleRate: %d", sampleRate); + + m_settingsMutex.lock(); + + m_interpolator.create(16, m_inputSampleRate, m_settings.m_afBandwidth); + m_interpolatorDistanceRemain = (Real) m_inputSampleRate / sampleRate; + m_interpolatorDistance = (Real) m_inputSampleRate / (Real) sampleRate; + + m_interpolatorStereo.create(16, m_inputSampleRate, m_settings.m_afBandwidth); + m_interpolatorStereoDistanceRemain = (Real) m_inputSampleRate / sampleRate; + m_interpolatorStereoDistance = (Real) m_inputSampleRate / (Real) sampleRate; + + m_deemphasisFilterX.configure(default_deemphasis * sampleRate * 1.0e-6); + m_deemphasisFilterY.configure(default_deemphasis * sampleRate * 1.0e-6); + + m_settingsMutex.unlock(); + + m_audioSampleRate = sampleRate; +} + void BFMDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force) { qDebug() << "BFMDemod::applyChannelSettings:" @@ -388,12 +412,12 @@ void BFMDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffse m_settingsMutex.lock(); m_interpolator.create(16, inputSampleRate, m_settings.m_afBandwidth); - m_interpolatorDistanceRemain = (Real) inputSampleRate / m_settings.m_audioSampleRate; - m_interpolatorDistance = (Real) inputSampleRate / (Real) m_settings.m_audioSampleRate; + m_interpolatorDistanceRemain = (Real) inputSampleRate / m_audioSampleRate; + m_interpolatorDistance = (Real) inputSampleRate / (Real) m_audioSampleRate; m_interpolatorStereo.create(16, inputSampleRate, m_settings.m_afBandwidth); - m_interpolatorStereoDistanceRemain = (Real) inputSampleRate / m_settings.m_audioSampleRate; - m_interpolatorStereoDistance = (Real) inputSampleRate / (Real) m_settings.m_audioSampleRate; + m_interpolatorStereoDistanceRemain = (Real) inputSampleRate / m_audioSampleRate; + m_interpolatorStereoDistance = (Real) inputSampleRate / (Real) m_audioSampleRate; m_interpolatorRDS.create(4, inputSampleRate, 600.0); m_interpolatorRDSDistanceRemain = (Real) inputSampleRate / 250000.0; @@ -416,15 +440,14 @@ void BFMDemod::applySettings(const BFMDemodSettings& settings, bool force) qDebug() << "BFMDemod::applySettings: MsgConfigureBFMDemod:" << " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset << " m_rfBandwidth: " << settings.m_rfBandwidth + << " m_afBandwidth: " << settings.m_afBandwidth << " m_volume: " << settings.m_volume << " m_squelch: " << settings.m_squelch << " m_audioStereo: " << settings.m_audioStereo << " m_lsbStereo: " << settings.m_lsbStereo << " m_showPilot: " << settings.m_showPilot << " m_rdsActive: " << settings.m_rdsActive - << " m_copyAudioToUDP: " << settings.m_copyAudioToUDP - << " m_udpAddress: " << settings.m_udpAddress - << " m_udpPort: " << settings.m_udpPort + << " m_audioDeviceName: " << settings.m_audioDeviceName << " force: " << force; if ((settings.m_audioStereo && (settings.m_audioStereo != m_settings.m_audioStereo)) || force) @@ -437,12 +460,12 @@ void BFMDemod::applySettings(const BFMDemodSettings& settings, bool force) m_settingsMutex.lock(); m_interpolator.create(16, m_inputSampleRate, settings.m_afBandwidth); - m_interpolatorDistanceRemain = (Real) m_inputSampleRate / settings.m_audioSampleRate; - m_interpolatorDistance = (Real) m_inputSampleRate / (Real) settings.m_audioSampleRate; + m_interpolatorDistanceRemain = (Real) m_inputSampleRate / m_audioSampleRate; + m_interpolatorDistance = (Real) m_inputSampleRate / (Real) m_audioSampleRate; m_interpolatorStereo.create(16, m_inputSampleRate, settings.m_afBandwidth); - m_interpolatorStereoDistanceRemain = (Real) m_inputSampleRate / settings.m_audioSampleRate; - m_interpolatorStereoDistance = (Real) m_inputSampleRate / (Real) settings.m_audioSampleRate; + m_interpolatorStereoDistanceRemain = (Real) m_inputSampleRate / m_audioSampleRate; + m_interpolatorStereoDistance = (Real) m_inputSampleRate / (Real) m_audioSampleRate; m_interpolatorRDS.create(4, m_inputSampleRate, 600.0); m_interpolatorRDSDistanceRemain = (Real) m_inputSampleRate / 250000.0; @@ -451,8 +474,7 @@ void BFMDemod::applySettings(const BFMDemodSettings& settings, bool force) m_settingsMutex.unlock(); } - if((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || - (settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) + if((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) { m_settingsMutex.lock(); Real lowCut = -(settings.m_rfBandwidth / 2.0) / m_inputSampleRate; @@ -462,33 +484,31 @@ void BFMDemod::applySettings(const BFMDemodSettings& settings, bool force) m_settingsMutex.unlock(); } - if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || - (settings.m_audioSampleRate != m_settings.m_audioSampleRate) || force) + if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || force) { m_settingsMutex.lock(); qDebug() << "BFMDemod::handleMessage: m_lowpass.create"; - m_lowpass.create(21, settings.m_audioSampleRate, settings.m_afBandwidth); + m_lowpass.create(21, m_audioSampleRate, settings.m_afBandwidth); m_settingsMutex.unlock(); } if ((settings.m_squelch != m_settings.m_squelch) || force) { qDebug() << "BFMDemod::handleMessage: set m_squelchLevel"; - m_squelchLevel = std::pow(10.0, settings.m_squelch / 20.0); - m_squelchLevel *= m_squelchLevel; + m_squelchLevel = std::pow(10.0, settings.m_squelch / 10.0); } - if ((settings.m_audioSampleRate != m_settings.m_audioSampleRate) || force) + if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) { - m_deemphasisFilterX.configure(default_deemphasis * settings.m_audioSampleRate * 1.0e-6); - m_deemphasisFilterY.configure(default_deemphasis * settings.m_audioSampleRate * 1.0e-6); - } + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName); + //qDebug("AMDemod::applySettings: audioDeviceName: %s audioDeviceIndex: %d", qPrintable(settings.m_audioDeviceName), audioDeviceIndex); + audioDeviceManager->addAudioSink(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex); + uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex); - if ((settings.m_udpAddress != m_settings.m_udpAddress) - || (settings.m_udpPort != m_settings.m_udpPort) || force) - { - m_udpBufferAudio->setAddress(const_cast(settings.m_udpAddress)); - m_udpBufferAudio->setPort(settings.m_udpPort); + if (m_audioSampleRate != audioSampleRate) { + applyAudioSampleRate(audioSampleRate); + } } m_settings = settings; @@ -516,3 +536,170 @@ bool BFMDemod::deserialize(const QByteArray& data) } } +int BFMDemod::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setBfmDemodSettings(new SWGSDRangel::SWGBFMDemodSettings()); + response.getBfmDemodSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int BFMDemod::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + BFMDemodSettings settings = m_settings; + bool frequencyOffsetChanged = false; + + if (channelSettingsKeys.contains("inputFrequencyOffset")) + { + settings.m_inputFrequencyOffset = response.getBfmDemodSettings()->getInputFrequencyOffset(); + frequencyOffsetChanged = true; + } + if (channelSettingsKeys.contains("rfBandwidth")) { + settings.m_rfBandwidth = response.getBfmDemodSettings()->getRfBandwidth(); + } + if (channelSettingsKeys.contains("afBandwidth")) { + settings.m_afBandwidth = response.getBfmDemodSettings()->getAfBandwidth(); + } + if (channelSettingsKeys.contains("volume")) { + settings.m_volume = response.getBfmDemodSettings()->getVolume(); + } + if (channelSettingsKeys.contains("squelch")) { + settings.m_squelch = response.getBfmDemodSettings()->getSquelch(); + } + if (channelSettingsKeys.contains("audioStereo")) { + settings.m_audioStereo = response.getBfmDemodSettings()->getAudioStereo() != 0; + } + if (channelSettingsKeys.contains("lsbStereo")) { + settings.m_lsbStereo = response.getBfmDemodSettings()->getLsbStereo() != 0; + } + if (channelSettingsKeys.contains("showPilot")) { + settings.m_showPilot = response.getBfmDemodSettings()->getShowPilot() != 0; + } + if (channelSettingsKeys.contains("rdsActive")) { + settings.m_rdsActive = response.getBfmDemodSettings()->getRdsActive() != 0; + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getBfmDemodSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getBfmDemodSettings()->getTitle(); + } + if (channelSettingsKeys.contains("audioDeviceName")) { + settings.m_audioDeviceName = *response.getBfmDemodSettings()->getAudioDeviceName(); + } + + if (frequencyOffsetChanged) + { + MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( + requiredBW(settings.m_rfBandwidth), settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(channelConfigMsg); + } + + MsgConfigureBFMDemod *msg = MsgConfigureBFMDemod::create(settings, force); + m_inputMessageQueue.push(msg); + + qDebug("BFMDemod::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureBFMDemod *msgToGUI = MsgConfigureBFMDemod::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int BFMDemod::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setBfmDemodReport(new SWGSDRangel::SWGBFMDemodReport()); + response.getBfmDemodReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + +void BFMDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const BFMDemodSettings& settings) +{ + response.getBfmDemodSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getBfmDemodSettings()->setRfBandwidth(settings.m_rfBandwidth); + response.getBfmDemodSettings()->setAfBandwidth(settings.m_afBandwidth); + response.getBfmDemodSettings()->setVolume(settings.m_volume); + response.getBfmDemodSettings()->setSquelch(settings.m_squelch); + response.getBfmDemodSettings()->setAudioStereo(settings.m_audioStereo ? 1 : 0); + response.getBfmDemodSettings()->setLsbStereo(settings.m_lsbStereo ? 1 : 0); + response.getBfmDemodSettings()->setShowPilot(settings.m_showPilot ? 1 : 0); + response.getBfmDemodSettings()->setRdsActive(settings.m_rdsActive ? 1 : 0); + response.getBfmDemodSettings()->setRgbColor(settings.m_rgbColor); + + if (response.getBfmDemodSettings()->getTitle()) { + *response.getBfmDemodSettings()->getTitle() = settings.m_title; + } else { + response.getBfmDemodSettings()->setTitle(new QString(settings.m_title)); + } + + if (response.getBfmDemodSettings()->getAudioDeviceName()) { + *response.getBfmDemodSettings()->getAudioDeviceName() = settings.m_audioDeviceName; + } else { + response.getBfmDemodSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName)); + } +} + +void BFMDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + double magsqAvg, magsqPeak; + int nbMagsqSamples; + getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples); + + response.getBfmDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg)); + response.getBfmDemodReport()->setSquelch(m_squelchState > 0 ? 1 : 0); + response.getBfmDemodReport()->setAudioSampleRate(m_audioSampleRate); + response.getBfmDemodReport()->setChannelSampleRate(m_inputSampleRate); + response.getBfmDemodReport()->setPilotLocked(getPilotLock() ? 1 : 0); + response.getBfmDemodReport()->setPilotPowerDb(CalcDb::dbPower(getPilotLevel())); + + if (m_settings.m_rdsActive) + { + response.getBfmDemodReport()->setRdsReport(new SWGSDRangel::SWGRDSReport()); + webapiFormatRDSReport(response.getBfmDemodReport()->getRdsReport()); + } + else + { + response.getBfmDemodReport()->setRdsReport(0); + } +} + +void BFMDemod::webapiFormatRDSReport(SWGSDRangel::SWGRDSReport *report) +{ + report->setDemodStatus(round(getDemodQua())); + report->setDecodStatus(round(getDecoderQua())); + report->setRdsDemodAccumDb(CalcDb::dbPower(std::fabs(getDemodAcc()))); + report->setRdsDemodFrequency(getDemodFclk()); + report->setPid(new QString(str(boost::format("%04X") % getRDSParser().m_pi_program_identification).c_str())); + report->setPiType(new QString(getRDSParser().pty_table[getRDSParser().m_pi_program_type].c_str())); + report->setPiCoverage(new QString(getRDSParser().coverage_area_codes[getRDSParser().m_pi_area_coverage_index].c_str())); + report->setProgServiceName(new QString(getRDSParser().m_g0_program_service_name)); + report->setMusicSpeech(new QString((getRDSParser().m_g0_music_speech ? "Music" : "Speech"))); + report->setMonoStereo(new QString((getRDSParser().m_g0_mono_stereo ? "Mono" : "Stereo"))); + report->setRadioText(new QString(getRDSParser().m_g2_radiotext)); + std::string time = str(boost::format("%4i-%02i-%02i %02i:%02i (%+.1fh)")\ + % (1900 + getRDSParser().m_g4_year) % getRDSParser().m_g4_month % getRDSParser().m_g4_day % getRDSParser().m_g4_hours % getRDSParser().m_g4_minutes % getRDSParser().m_g4_local_time_offset); + report->setTime(new QString(time.c_str())); + report->setAltFrequencies(new QList); + + for (std::set::iterator it = getRDSParser().m_g0_alt_freq.begin(); it != getRDSParser().m_g0_alt_freq.end(); ++it) + { + if (*it > 76.0) + { + report->getAltFrequencies()->append(new SWGSDRangel::SWGRDSReport_altFrequencies); + report->getAltFrequencies()->back()->setFrequency(*it); + } + } +} diff --git a/plugins/channelrx/demodbfm/bfmdemod.h b/plugins/channelrx/demodbfm/bfmdemod.h index cf0a7ee33..700deb6cf 100644 --- a/plugins/channelrx/demodbfm/bfmdemod.h +++ b/plugins/channelrx/demodbfm/bfmdemod.h @@ -33,7 +33,6 @@ #include "dsp/phasediscri.h" #include "audio/audiofifo.h" #include "util/message.h" -#include "util/udpsink.h" #include "rdsparser.h" #include "rdsdecoder.h" @@ -44,6 +43,10 @@ class DeviceSourceAPI; class ThreadedBasebandSampleSink; class DownChannelizer; +namespace SWGSDRangel { + class SWGRDSReport; +} + class BFMDemod : public BasebandSampleSink, public ChannelSinkAPI { public: class MsgConfigureBFMDemod : public Message { @@ -143,10 +146,17 @@ public: void getMagSqLevels(double& avg, double& peak, int& nbSamples) { - avg = m_magsqCount == 0 ? 1e-10 : m_magsqSum / m_magsqCount; - m_magsq = avg; - peak = m_magsqPeak == 0.0 ? 1e-10 : m_magsqPeak; + if (m_magsqCount > 0) + { + m_magsq = m_magsqSum / m_magsqCount; + m_magSqLevelStore.m_magsq = m_magsq; + m_magSqLevelStore.m_magsqPeak = m_magsqPeak; + } + + avg = m_magSqLevelStore.m_magsq; + peak = m_magSqLevelStore.m_magsqPeak; nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; + m_magsqSum = 0.0f; m_magsqPeak = 0.0f; m_magsqCount = 0; @@ -154,10 +164,43 @@ public: RDSParser& getRDSParser() { return m_rdsParser; } + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + + static int requiredBW(int rfBW) + { + if (rfBW <= 48000) { + return 48000; + } else { + return (3*rfBW)/2; + } + } + static const QString m_channelIdURI; static const QString m_channelId; private: + struct MagSqLevelsStore + { + MagSqLevelsStore() : + m_magsq(1e-12), + m_magsqPeak(1e-12) + {} + double m_magsq; + double m_magsqPeak; + }; + enum RateState { RSInitialFill, RSRunning @@ -170,6 +213,7 @@ private: int m_inputSampleRate; int m_inputFrequencyOffset; BFMDemodSettings m_settings; + quint32 m_audioSampleRate; NCO m_nco; Interpolator m_interpolator; //!< Interpolator between fixed demod bandwidth and audio bandwidth (rational) @@ -197,6 +241,7 @@ private: double m_magsqSum; double m_magsqPeak; int m_magsqCount; + MagSqLevelsStore m_magSqLevelStore; AudioVector m_audioBuffer; uint m_audioBufferFill; @@ -221,12 +266,16 @@ private: static const int default_excursion = 750000; // +/- 75 kHz PhaseDiscriminators m_phaseDiscri; - UDPSink *m_udpBufferAudio; static const int m_udpBlockSize; + void applyAudioSampleRate(int sampleRate); void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); void applySettings(const BFMDemodSettings& settings, bool force = false); + + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const BFMDemodSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); + void webapiFormatRDSReport(SWGSDRangel::SWGRDSReport *report); }; #endif // INCLUDE_BFMDEMOD_H diff --git a/plugins/channelrx/demodbfm/bfmdemodgui.cpp b/plugins/channelrx/demodbfm/bfmdemodgui.cpp index ff1ebcd0b..2e0f4c42a 100644 --- a/plugins/channelrx/demodbfm/bfmdemodgui.cpp +++ b/plugins/channelrx/demodbfm/bfmdemodgui.cpp @@ -36,6 +36,8 @@ #include "util/simpleserializer.h" #include "util/db.h" #include "gui/basicchannelsettingsdialog.h" +#include "gui/crightclickenabler.h" +#include "gui/audioselectdialog.h" #include "mainwindow.h" #include "bfmdemodsettings.h" @@ -117,6 +119,16 @@ bool BFMDemodGUI::handleMessage(const Message& message) ui->glSpectrum->setSampleRate(m_rate / 2); return true; } + else if (BFMDemod::MsgConfigureBFMDemod::match(message)) + { + qDebug("BFMDemodGUI::handleMessage: BFMDemod::MsgConfigureBFMDemod"); + const BFMDemod::MsgConfigureBFMDemod& cfg = (BFMDemod::MsgConfigureBFMDemod&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } else { return false; @@ -201,12 +213,6 @@ void BFMDemodGUI::on_lsbStereo_toggled(bool lsb) applySettings(); } -void BFMDemodGUI::on_copyAudioToUDP_toggled(bool copy) -{ - m_settings.m_copyAudioToUDP = copy; - applySettings(); -} - void BFMDemodGUI::on_showPilot_clicked() { m_settings.m_showPilot = ui->showPilot->isChecked(); @@ -311,14 +317,11 @@ void BFMDemodGUI::onMenuDialogCalled(const QPoint &p) dialog.exec(); m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); - m_settings.m_udpAddress = m_channelMarker.getUDPAddress(), - m_settings.m_udpPort = m_channelMarker.getUDPSendPort(), m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); m_settings.m_title = m_channelMarker.getTitle(); setWindowTitle(m_settings.m_title); setTitleColor(m_settings.m_rgbColor); - displayUDPAddress(); applySettings(); } @@ -338,6 +341,9 @@ BFMDemodGUI::BFMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999); ui->channelPowerMeter->setColorTheme(LevelMeterSignalDB::ColorGreenAndBlue); + CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->audioStereo); + connect(audioMuteRightClickEnabler, SIGNAL(rightClick()), this, SLOT(audioSelect())); + setAttribute(Qt::WA_DeleteOnClose, true); connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); @@ -353,7 +359,14 @@ BFMDemodGUI::BFMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban ui->glSpectrum->setDisplayWaterfall(false); ui->glSpectrum->setDisplayMaxHold(false); ui->glSpectrum->setSsbSpectrum(true); - m_spectrumVis->configure(m_spectrumVis->getInputMessageQueue(), 64, 10, FFTWindow::BlackmanHarris); + m_spectrumVis->configure( + m_spectrumVis->getInputMessageQueue(), + 64, // FFT size + 10, // overlapping % + 0, // number of averaging samples + 0, // no averaging + FFTWindow::BlackmanHarris, + false); // logarithmic scale connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); m_channelMarker.blockSignals(true); @@ -361,8 +374,6 @@ BFMDemodGUI::BFMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban m_channelMarker.setBandwidth(12500); m_channelMarker.setCenterFrequency(0); m_channelMarker.setTitle("Broadcast FM Demod"); - m_channelMarker.setUDPAddress("127.0.0.1"); - m_channelMarker.setUDPSendPort(9999); m_channelMarker.blockSignals(false); m_channelMarker.setVisible(true); // activate signal on the last setting only @@ -394,14 +405,10 @@ BFMDemodGUI::~BFMDemodGUI() { m_deviceUISet->removeRxChannelInstance(this); delete m_bfmDemod; // TODO: check this: when the GUI closes it has to delete the demodulator + delete m_spectrumVis; delete ui; } -void BFMDemodGUI::displayUDPAddress() -{ - ui->copyAudioToUDP->setToolTip(QString("Copy audio output to UDP %1:%2").arg(m_settings.m_udpAddress).arg(m_settings.m_udpPort)); -} - void BFMDemodGUI::blockApplySettings(bool block) { m_doApplySettings = !block; @@ -412,7 +419,7 @@ void BFMDemodGUI::applySettings(bool force) if (m_doApplySettings) { BFMDemod::MsgConfigureChannelizer *msgChan = BFMDemod::MsgConfigureChannelizer::create( - requiredBW(m_settings.m_rfBandwidth), + BFMDemod::requiredBW(m_settings.m_rfBandwidth), m_settings.m_inputFrequencyOffset); m_bfmDemod->getInputMessageQueue()->push(msgChan); @@ -432,7 +439,6 @@ void BFMDemodGUI::displaySettings() setTitleColor(m_settings.m_rgbColor); setWindowTitle(m_channelMarker.getTitle()); - displayUDPAddress(); blockApplySettings(true); @@ -454,7 +460,6 @@ void BFMDemodGUI::displaySettings() ui->lsbStereo->setChecked(m_settings.m_lsbStereo); ui->showPilot->setChecked(m_settings.m_showPilot); ui->rds->setChecked(m_settings.m_rdsActive); - ui->copyAudioToUDP->setChecked(m_settings.m_copyAudioToUDP); blockApplySettings(false); } @@ -469,6 +474,19 @@ void BFMDemodGUI::enterEvent(QEvent*) m_channelMarker.setHighlighted(true); } +void BFMDemodGUI::audioSelect() +{ + qDebug("BFMDemodGUI::audioSelect"); + AudioSelectDialog audioSelect(DSPEngine::instance()->getAudioDeviceManager(), m_settings.m_audioDeviceName); + audioSelect.exec(); + + if (audioSelect.m_selected) + { + m_settings.m_audioDeviceName = audioSelect.m_audioDeviceName; + applySettings(); + } +} + void BFMDemodGUI::tick() { double magsqAvg, magsqPeak; @@ -670,8 +688,8 @@ void BFMDemodGUI::rdsUpdate(bool force) { ui->g04Label->setStyleSheet("QLabel { background-color : green; }"); ui->g04CountText->setNum((int) m_bfmDemod->getRDSParser().m_g4_count); - std::string time = str(boost::format("%02i.%02i.%4i, %02i:%02i (%+.1fh)")\ - % m_bfmDemod->getRDSParser().m_g4_day % m_bfmDemod->getRDSParser().m_g4_month % (1900 + m_bfmDemod->getRDSParser().m_g4_year) % m_bfmDemod->getRDSParser().m_g4_hours % m_bfmDemod->getRDSParser().m_g4_minutes % m_bfmDemod->getRDSParser().m_g4_local_time_offset); + std::string time = str(boost::format("%4i-%02i-%02i %02i:%02i (%+.1fh)")\ + % (1900 + m_bfmDemod->getRDSParser().m_g4_year) % m_bfmDemod->getRDSParser().m_g4_month % m_bfmDemod->getRDSParser().m_g4_day % m_bfmDemod->getRDSParser().m_g4_hours % m_bfmDemod->getRDSParser().m_g4_minutes % m_bfmDemod->getRDSParser().m_g4_local_time_offset); ui->g04Time->setText(QString(time.c_str())); } else diff --git a/plugins/channelrx/demodbfm/bfmdemodgui.h b/plugins/channelrx/demodbfm/bfmdemodgui.h index a008bb22b..06f0f4842 100644 --- a/plugins/channelrx/demodbfm/bfmdemodgui.h +++ b/plugins/channelrx/demodbfm/bfmdemodgui.h @@ -80,7 +80,6 @@ private: void blockApplySettings(bool block); void applySettings(bool force = false); void displaySettings(); - void displayUDPAddress(); void rdsUpdate(bool force); void rdsUpdateFixedFields(); @@ -89,15 +88,6 @@ private: void changeFrequency(qint64 f); - static int requiredBW(int rfBW) - { - if (rfBW <= 48000) { - return 48000; - } else { - return (3*rfBW)/2; - } - } - private slots: void on_deltaFrequency_changed(qint64 value); void on_rfBW_valueChanged(int value); @@ -108,7 +98,6 @@ private slots: void on_lsbStereo_toggled(bool lsb); void on_showPilot_clicked(); void on_rds_clicked(); - void on_copyAudioToUDP_toggled(bool copy); void on_g14ProgServiceNames_currentIndexChanged(int index); void on_clearData_clicked(bool checked); void on_g00AltFrequenciesBox_activated(int index); @@ -117,6 +106,7 @@ private slots: void onWidgetRolled(QWidget* widget, bool rollDown); void onMenuDialogCalled(const QPoint& p); void handleInputMessages(); + void audioSelect(); void tick(); }; diff --git a/plugins/channelrx/demodbfm/bfmdemodgui.ui b/plugins/channelrx/demodbfm/bfmdemodgui.ui index 7894e4185..3592997c0 100644 --- a/plugins/channelrx/demodbfm/bfmdemodgui.ui +++ b/plugins/channelrx/demodbfm/bfmdemodgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -110,7 +110,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -172,7 +172,7 @@ - Mono/Stereo toggle and pilot lock indicator + Light: Pilot lock indicator Left: Mono/Stereo toggle Right: select audio device true @@ -299,7 +299,7 @@ - Monospace + Liberation Mono 8 @@ -308,19 +308,6 @@ - - - - Copy audio to UDP - - - U - - - true - - - @@ -547,8 +534,8 @@ - Sans Serif - 9 + Liberation Mono + 8 @@ -648,7 +635,7 @@ - Lits if synchronized + Lights if synchronized Dec @@ -1122,7 +1109,7 @@ - Lits if PI is updated + Lights if PI is updated PI @@ -1254,7 +1241,7 @@ - Lits if group 1 is updated + Lights if group 1 is updated G01 @@ -1353,7 +1340,7 @@ - Lits if group 0 is updated + Lights if group 0 is updated G00 @@ -1477,7 +1464,7 @@ - Alternalte frequencies (MHz) + Alternate frequencies (MHz) @@ -1510,7 +1497,7 @@ - Lits if group 3 updated is updated + Lights if group 3 updated is updated G03 @@ -1556,7 +1543,7 @@ - Lits if group 9 is updated + Lights if group 9 is updated G09 @@ -1599,7 +1586,7 @@ - Lits if group 2 is updated + Lights if group 2 is updated G02 @@ -1633,7 +1620,7 @@ - Lits if group 4 is updated + Lights if group 4 is updated G04 @@ -1701,7 +1688,7 @@ - Lits if group 14 is updated + Lights if group 14 is updated G14 @@ -1750,7 +1737,7 @@ - Station altermate freqiuencies + Station alternate frequencies @@ -1767,7 +1754,7 @@ - Lits if group 8 is updated + Lights if group 8 is updated G08 diff --git a/plugins/channelrx/demodbfm/bfmdemodsettings.cpp b/plugins/channelrx/demodbfm/bfmdemodsettings.cpp index 1c6d1cd13..9bb54d736 100644 --- a/plugins/channelrx/demodbfm/bfmdemodsettings.cpp +++ b/plugins/channelrx/demodbfm/bfmdemodsettings.cpp @@ -41,16 +41,13 @@ void BFMDemodSettings::resetToDefaults() m_afBandwidth = 15000; m_volume = 2.0; m_squelch = -60.0; - m_audioSampleRate = DSPEngine::instance()->getAudioSampleRate(); m_audioStereo = false; m_lsbStereo = false; m_showPilot = false; m_rdsActive = false; - m_copyAudioToUDP = false; - m_udpAddress = "127.0.0.1"; - m_udpPort = 9999; m_rgbColor = QColor(80, 120, 228).rgb(); m_title = "Broadcast FM Demod"; + m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; } QByteArray BFMDemodSettings::serialize() const @@ -75,6 +72,7 @@ QByteArray BFMDemodSettings::serialize() const } s.writeString(12, m_title); + s.writeString(13, m_audioDeviceName); return s.final(); } @@ -123,6 +121,7 @@ bool BFMDemodSettings::deserialize(const QByteArray& data) } d.readString(12, &m_title, "Broadcast FM Demod"); + d.readString(13, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName); return true; } diff --git a/plugins/channelrx/demodbfm/bfmdemodsettings.h b/plugins/channelrx/demodbfm/bfmdemodsettings.h index b79f6e114..5400baff3 100644 --- a/plugins/channelrx/demodbfm/bfmdemodsettings.h +++ b/plugins/channelrx/demodbfm/bfmdemodsettings.h @@ -28,16 +28,13 @@ struct BFMDemodSettings Real m_afBandwidth; Real m_volume; Real m_squelch; - quint32 m_audioSampleRate; bool m_audioStereo; bool m_lsbStereo; bool m_showPilot; bool m_rdsActive; - bool m_copyAudioToUDP; - QString m_udpAddress; - quint16 m_udpPort; quint32 m_rgbColor; QString m_title; + QString m_audioDeviceName; Serializable *m_channelMarker; Serializable *m_spectrumGUI; diff --git a/plugins/channelrx/demodbfm/bfmplugin.cpp b/plugins/channelrx/demodbfm/bfmplugin.cpp index b32e815e9..b6af176e5 100644 --- a/plugins/channelrx/demodbfm/bfmplugin.cpp +++ b/plugins/channelrx/demodbfm/bfmplugin.cpp @@ -20,12 +20,14 @@ #include #include "plugin/pluginapi.h" +#ifndef SERVER_MODE #include "bfmdemodgui.h" +#endif #include "bfmdemod.h" const PluginDescriptor BFMPlugin::m_pluginDescriptor = { QString("Broadcast FM Demodulator"), - QString("3.12.0"), + QString("4.1.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, @@ -51,11 +53,19 @@ void BFMPlugin::initPlugin(PluginAPI* pluginAPI) m_pluginAPI->registerRxChannel(BFMDemod::m_channelIdURI, BFMDemod::m_channelId, this); } +#ifdef SERVER_MODE +PluginInstanceGUI* BFMPlugin::createRxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSink *rxChannel __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* BFMPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) { return BFMDemodGUI::create(m_pluginAPI, deviceUISet, rxChannel); } - +#endif BasebandSampleSink* BFMPlugin::createRxChannelBS(DeviceSourceAPI *deviceAPI) { diff --git a/plugins/channelrx/demodbfm/demodbfm.pro b/plugins/channelrx/demodbfm/demodbfm.pro index 4a8cd67df..8f7d6bea1 100644 --- a/plugins/channelrx/demodbfm/demodbfm.pro +++ b/plugins/channelrx/demodbfm/demodbfm.pro @@ -18,12 +18,14 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui +INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client CONFIG(ANDROID):INCLUDEPATH += /opt/softs/boost_1_60_0 -CONFIG(MINGW32):INCLUDEPATH += "D:\boost_1_58_0" -CONFIG(MINGW64):INCLUDEPATH += "D:\boost_1_58_0" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\boost_1_66_0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\boost_1_66_0" CONFIG(macx):INCLUDEPATH += "../../../../../boost_1_64_0" CONFIG(Release):build_subdir = release @@ -51,5 +53,6 @@ FORMS += bfmdemodgui.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui +LIBS += -L../../../swagger/$${build_subdir} -lswagger RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/plugins/channelrx/demodbfm/rdsdemod.cpp b/plugins/channelrx/demodbfm/rdsdemod.cpp index fd5094f75..c38cccdf8 100644 --- a/plugins/channelrx/demodbfm/rdsdemod.cpp +++ b/plugins/channelrx/demodbfm/rdsdemod.cpp @@ -36,6 +36,7 @@ RDSDemod::RDSDemod() m_srate = 250000; m_parms.subcarr_phi = 0; + memset(m_parms.subcarr_bb, 0, sizeof(m_parms.subcarr_bb)); m_parms.clock_offset = 0; m_parms.clock_phi = 0; m_parms.prev_clock_phi = 0; @@ -48,6 +49,7 @@ RDSDemod::RDSDemod() m_parms.prev_acc = 0; m_parms.counter = 0; m_parms.reading_frame = 0; + memset(m_parms.tot_errs, 0, sizeof(m_parms.tot_errs)); m_parms.dbit = 0; m_prev = 0.0f; memset(m_xv, 0, 6*sizeof(Real)); diff --git a/plugins/channelrx/demodbfm/rdsdemod.h b/plugins/channelrx/demodbfm/rdsdemod.h index c10c7527a..5a7ea24f6 100644 --- a/plugins/channelrx/demodbfm/rdsdemod.h +++ b/plugins/channelrx/demodbfm/rdsdemod.h @@ -75,8 +75,6 @@ private: int m_srate; - //UDPSink m_udpDebug; // UDP debug - static const Real m_pllBeta; static const Real m_fsc; }; diff --git a/plugins/channelrx/demodbfm/rdsparser.cpp b/plugins/channelrx/demodbfm/rdsparser.cpp index 6909e7e7e..eaa95c98e 100644 --- a/plugins/channelrx/demodbfm/rdsparser.cpp +++ b/plugins/channelrx/demodbfm/rdsparser.cpp @@ -931,21 +931,24 @@ void RDSParser::decode_optional_content(int no_groups, unsigned long int *free_f int content_length = 0; int ff_pointer = 0; - for (int i = no_groups; i == 0; i--) + if (no_groups == 0) { + int i = 0; +// for (int i = no_groups; i == 0; i--) i is always 0 if no_groups is 0 and exit else skip +// { ff_pointer = 12 + 16; - while(ff_pointer > 0) + while(ff_pointer >= 7) // ff_pointer must be >= 0 and is decreased by 7 in the loop { ff_pointer -= 4; - m_g8_label_index = (free_format[i] & (0xf << ff_pointer)); - content_length = optional_content_lengths[m_g8_label_index]; + m_g8_label_index = (free_format[i] && ((0xf << ff_pointer) != 0)) ? 1 : 0; + content_length = 3; // optional_content_lengths[m_g8_label_index]; // always 3 ff_pointer -= content_length; - m_g8_content = (free_format[i] & (int(std::pow(2, content_length) - 1) << ff_pointer)); + m_g8_content = (free_format[i] && ((int(std::pow(2, content_length) - 1) << ff_pointer) != 0)) ? 1 : 0; - /* - qDebug() << "RDSParser::decode_optional_content: TMC optional content (" << label_descriptions[m_g8_label_index].c_str() - << "):" << m_g8_content;*/ + qDebug() << "RDSParser::decode_optional_content:" + << " TMC optional content (" << label_descriptions[m_g8_label_index].c_str() << ")" + << ": " << m_g8_content; } } } diff --git a/plugins/channelrx/demoddatv/CMakeLists.txt b/plugins/channelrx/demoddatv/CMakeLists.txt index 3135612ef..a4bfb1b0e 100644 --- a/plugins/channelrx/demoddatv/CMakeLists.txt +++ b/plugins/channelrx/demoddatv/CMakeLists.txt @@ -4,8 +4,6 @@ set(datv_SOURCES datvdemod.cpp datvdemodgui.cpp datvdemodplugin.cpp - datvscreen.cpp - glshaderarray.cpp datvideostream.cpp datvideorender.cpp ) @@ -14,8 +12,6 @@ set(datv_HEADERS datvdemod.h datvdemodgui.h datvdemodplugin.h - datvscreen.h - glshaderarray.h datvideostream.h datvideorender.h ) @@ -24,11 +20,13 @@ set(datv_FORMS datvdemodgui.ui ) -set (CMAKE_CXX_FLAGS "-Wno-unused-variable -Wno-deprecated-declarations") - include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${AVCODEC_INCLUDE_DIRS} + ${AVFORMAT_INCLUDE_DIRS} + ${AVUTIL_INCLUDE_DIRS} + ${SWSCALE_INCLUDE_DIRS} ) #include(${QT_USE_FILE}) @@ -49,11 +47,13 @@ target_link_libraries(demoddatv ${QT_LIBRARIES} sdrbase sdrgui - avcodec - avformat - swscale + ${AVCODEC_LIBRARIES} + ${AVFORMAT_LIBRARIES} + ${AVUTIL_LIBRARIES} + ${SWSCALE_LIBRARIES} ) -qt5_use_modules(demoddatv Core Widgets Multimedia MultimediaWidgets) +find_package(Qt5MultimediaWidgets 5.0 REQUIRED) +target_link_libraries(demoddatv Qt5::Core Qt5::Widgets Qt5::Multimedia Qt5::MultimediaWidgets) install(TARGETS demoddatv DESTINATION lib/plugins/channelrx) diff --git a/plugins/channelrx/demoddatv/datvconstellation.h b/plugins/channelrx/demoddatv/datvconstellation.h index 9f95a7a15..f3d21b32c 100644 --- a/plugins/channelrx/demoddatv/datvconstellation.h +++ b/plugins/channelrx/demoddatv/datvconstellation.h @@ -20,108 +20,118 @@ #define DATVCONSTELLATION_H #include +#include #include "leansdr/framework.h" -#include "datvscreen.h" +#include "gui/tvscreen.h" namespace leansdr { - static const int DEFAULT_GUI_DECIMATION = 64; +static const int DEFAULT_GUI_DECIMATION = 64; - template struct datvconstellation : runnable +template struct datvconstellation: runnable +{ + T xymin, xymax; + unsigned long decimation; + unsigned long pixels_per_frame; + cstln_lut<256> **cstln; // Optional ptr to optional constellation + TVScreen *m_objDATVScreen; + pipereader > in; + unsigned long phase; + std::vector cstln_rows; + std::vector cstln_cols; + + datvconstellation(scheduler *sch, pipebuf > &_in, T _xymin, T _xymax, const char *_name = 0, TVScreen *objDATVScreen = 0) : + runnable(sch, _name ? _name : _in.name), + xymin(_xymin), + xymax(_xymax), + decimation(DEFAULT_GUI_DECIMATION), + pixels_per_frame(1024), + cstln(0), + m_objDATVScreen(objDATVScreen), + in(_in), + phase(0) { - T xymin, xymax; - unsigned long decimation; - unsigned long pixels_per_frame; - cstln_lut<256> **cstln; // Optional ptr to optional constellation - DATVScreen *m_objDATVScreen; + } - - datvconstellation(scheduler *sch, pipebuf< complex > &_in, T _xymin, T _xymax, const char *_name=NULL, DATVScreen * objDATVScreen=NULL) : - runnable(sch, _name?_name:_in.name), - xymin(_xymin), - xymax(_xymax), - decimation(DEFAULT_GUI_DECIMATION), - pixels_per_frame(1024), - cstln(NULL), - in(_in), - phase(0), - m_objDATVScreen(objDATVScreen) - { - - } - - void run() - { + void run() + { //Symbols - while ( in.readable() >= pixels_per_frame ) + while (in.readable() >= pixels_per_frame) { - if ( ! phase ) + if ((!phase) && m_objDATVScreen) { m_objDATVScreen->resetImage(); - complex *p = in.rd(), *pend = p+pixels_per_frame; + complex *p = in.rd(), *pend = p + pixels_per_frame; - for ( ; pselectRow(256*(p->re-xymin)/(xymax-xymin)); - m_objDATVScreen->setDataColor(256- 256*((p->im-xymin)/(xymax-xymin)),255,0,255); + m_objDATVScreen->selectRow(256 * (p->re - xymin) / (xymax - xymin)); + m_objDATVScreen->setDataColor(256 - 256 * ((p->im - xymin) / (xymax - xymin)), 255, 0, 255); } } - if ( cstln && (*cstln) ) + if (cstln && (*cstln)) { - // Plot constellation points + // Plot constellation points - for ( int i=0; i<(*cstln)->nsymbols; ++i ) - { - complex *p = &(*cstln)->symbols[i]; - int x = 256*(p->re-xymin)/(xymax-xymin); - int y = 256 - 256*(p->im-xymin)/(xymax-xymin); + std::vector::const_iterator row_it = cstln_rows.begin(); + std::vector::const_iterator col_it = cstln_cols.begin(); - for ( int d=-4; d<=4; ++d ) + for (; (row_it != cstln_rows.end()) && (col_it != cstln_cols.end()); ++row_it, ++col_it) { - m_objDATVScreen->selectRow(x+d); - m_objDATVScreen->setDataColor(y,5,250,250); - m_objDATVScreen->selectRow(x); - m_objDATVScreen->setDataColor(y+d,5,250,250); + m_objDATVScreen->selectRow(*row_it); + m_objDATVScreen->setDataColor(*col_it, 250, 250, 5); } - } } - m_objDATVScreen->renderImage(NULL); - + m_objDATVScreen->renderImage(0); } in.read(pixels_per_frame); - if ( ++phase >= decimation ) + if (++phase >= decimation) { phase = 0; } - } - } + } + } - //private: - pipereader< complex > in; - unsigned long phase; - //gfx g; + void draw_begin() + { + } - void draw_begin() - { - //g.clear(); - //g.setfg(0, 255, 0); - //g.line(g.w/2,0, g.w/2, g.h); - //g.line(0,g.h/2, g.w,g.h/2); - } + void calculate_cstln_points() + { + if (!(*cstln)) { + return; + } - }; + cstln_rows.clear(); + cstln_cols.clear(); + + for (int i = 0; i < (*cstln)->nsymbols; ++i) + { + complex *p = &(*cstln)->symbols[i]; + int x = 256 * (p->re - xymin) / (xymax - xymin); + int y = 256 - 256 * (p->im - xymin) / (xymax - xymin); + + for (int d = -4; d <= 4; ++d) + { + cstln_rows.push_back(x + d); + cstln_cols.push_back(y); + cstln_rows.push_back(x); + cstln_cols.push_back(y + d); + } + } + } +}; } diff --git a/plugins/channelrx/demoddatv/datvdemod.cpp b/plugins/channelrx/demoddatv/datvdemod.cpp index df1022e53..2023e5c3f 100644 --- a/plugins/channelrx/demoddatv/datvdemod.cpp +++ b/plugins/channelrx/demoddatv/datvdemod.cpp @@ -29,7 +29,6 @@ #include "dsp/threadedbasebandsamplesink.h" #include "device/devicesourceapi.h" - const QString DATVDemod::m_channelIdURI = "sdrangel.channel.demoddatv"; const QString DATVDemod::m_channelId = "DATVDemod"; @@ -38,15 +37,16 @@ MESSAGE_CLASS_DEFINITION(DATVDemod::MsgConfigureChannelizer, Message) DATVDemod::DATVDemod(DeviceSourceAPI *deviceAPI) : ChannelSinkAPI(m_channelIdURI), - m_deviceAPI(deviceAPI), - m_objSettingsMutex(QMutex::NonRecursive), - m_objRegisteredDATVScreen(NULL), - m_objVideoStream(NULL), - m_objRegisteredVideoRender(NULL), - m_objRenderThread(NULL), - m_enmModulation(BPSK /*DATV_FM1*/), m_blnNeedConfigUpdate(false), - m_blnRenderingVideo(false) + m_deviceAPI(deviceAPI), + m_objRegisteredTVScreen(0), + m_objRegisteredVideoRender(0), + m_objVideoStream(NULL), + m_objRenderThread(NULL), + m_blnRenderingVideo(false), + m_blnStartStopVideo(false), + m_enmModulation(BPSK /*DATV_FM1*/), + m_objSettingsMutex(QMutex::NonRecursive) { setObjectName("DATVDemod"); @@ -62,46 +62,42 @@ DATVDemod::DATVDemod(DeviceSourceAPI *deviceAPI) : m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); m_deviceAPI->addThreadedSink(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - - connect(m_channelizer, SIGNAL(inputSampleRateChanged()), this, SLOT(channelSampleRateChanged())); - } DATVDemod::~DATVDemod() { m_blnInitialized=false; + if(m_objVideoStream!=NULL) + { + //Immediately exit from DATVideoStream if waiting for data before killing thread + m_objVideoStream->ThreadTimeOut=0; + } if(m_objRenderThread!=NULL) { if(m_objRenderThread->isRunning()) { m_objRenderThread->stopRendering(); + m_objRenderThread->quit(); } + + m_objRenderThread->wait(2000); } - //CleanUpDATVFramework(true); - - if(m_objRFFilter!=NULL) - { - //delete m_objRFFilter; - } - - if(m_objVideoStream!=NULL) - { - //m_objVideoStream->close(); - //delete m_objVideoStream; - } + CleanUpDATVFramework(true); m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSink(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; + delete m_objRFFilter; } -bool DATVDemod::SetDATVScreen(DATVScreen *objScreen) +bool DATVDemod::SetTVScreen(TVScreen *objScreen) { - m_objRegisteredDATVScreen = objScreen; + m_objRegisteredTVScreen = objScreen; + return true; } DATVideostream * DATVDemod::SetVideoRender(DATVideoRender *objScreen) @@ -132,6 +128,16 @@ bool DATVDemod::PlayVideo(bool blnStartStop) return false; } + if (m_blnStartStopVideo && !blnStartStop) + { + return true; + } + + if(blnStartStop==true) + { + m_blnStartStopVideo=true; + } + if(m_objRenderThread->isRunning()) { if(blnStartStop==true) @@ -142,12 +148,13 @@ bool DATVDemod::PlayVideo(bool blnStartStop) return true; } - m_objRenderThread->setStreamAndRenderer(m_objRegisteredVideoRender,m_objVideoStream); - m_objVideoStream->MultiThreaded=true; - m_objRenderThread->start(); - - //m_objVideoStream->MultiThreaded=false; - //m_objRenderThread->run(); + if(m_objVideoStream->bytesAvailable()>0) + { + m_objRenderThread->setStreamAndRenderer(m_objRegisteredVideoRender,m_objVideoStream); + m_objVideoStream->MultiThreaded=true; + m_objVideoStream->ThreadTimeOut=5000; //5000 ms + m_objRenderThread->start(); + } return true; } @@ -157,17 +164,18 @@ void DATVDemod::configure(MessageQueue* objMessageQueue, int intCenterFrequency, dvb_version enmStandard, DATVModulation enmModulation, - code_rate enmFEC, + leansdr::code_rate enmFEC, int intSymbolRate, int intNotchFilters, bool blnAllowDrift, bool blnFastLock, - bool blnHDLC, + dvb_sampler enmFilter, bool blnHardMetric, - bool blnResample, - bool blnViterbi) + float fltRollOff, + bool blnViterbi, + int intExcursion) { - Message* msgCmd = MsgConfigureDATVDemod::create(intRFBandwidth,intCenterFrequency,enmStandard, enmModulation, enmFEC, intSymbolRate, intNotchFilters, blnAllowDrift,blnFastLock,blnHDLC,blnHardMetric,blnResample, blnViterbi); + Message* msgCmd = MsgConfigureDATVDemod::create(intRFBandwidth,intCenterFrequency,enmStandard, enmModulation, enmFEC, intSymbolRate, intNotchFilters, blnAllowDrift,blnFastLock,enmFilter,blnHardMetric,fltRollOff, blnViterbi,intExcursion); objMessageQueue->push(msgCmd); } @@ -176,32 +184,33 @@ void DATVDemod::InitDATVParameters(int intMsps, int intCenterFrequency, dvb_version enmStandard, DATVModulation enmModulation, - code_rate enmFEC, + leansdr::code_rate enmFEC, int intSampleRate, int intSymbolRate, int intNotchFilters, bool blnAllowDrift, bool blnFastLock, - bool blnHDLC, + dvb_sampler enmFilter, bool blnHardMetric, - bool blnResample, - bool blnViterbi) + float fltRollOff, + bool blnViterbi, + int intExcursion) { Real fltLowCut; Real fltHiCut; - m_blnInitialized=false; - m_objSettingsMutex.lock(); - //Recalibrage du filtre passe bande + m_blnInitialized=false; + + //Bandpass filter shaping fltLowCut = -((float)intRFBandwidth / 2.0) / (float)intMsps; fltHiCut = ((float)intRFBandwidth / 2.0) / (float)intMsps; m_objRFFilter->create_filter(fltLowCut, fltHiCut); m_objNCO.setFreq(-(float)intCenterFrequency,(float)intMsps); - //Mise à jour de la config + //Config update m_objRunning.intMsps = intMsps; m_objRunning.intCenterFrequency = intCenterFrequency; @@ -214,36 +223,22 @@ void DATVDemod::InitDATVParameters(int intMsps, m_objRunning.intNotchFilters = intNotchFilters; m_objRunning.blnAllowDrift = blnAllowDrift; m_objRunning.blnFastLock = blnFastLock; - m_objRunning.blnHDLC = blnHDLC; + m_objRunning.enmFilter = enmFilter; m_objRunning.blnHardMetric = blnHardMetric; - m_objRunning.blnResample = blnResample; + m_objRunning.fltRollOff = fltRollOff; m_objRunning.blnViterbi = blnViterbi; + m_objRunning.intExcursion = intExcursion; - qDebug() << "DATVDemod::InitDATVParameters:" - << " - Msps: " << intMsps - << " - Sample Rate: " << intSampleRate - << " - Symbol Rate: " << intSymbolRate - << " - Modulation: " << enmModulation - << " - Notch Filters: " << intNotchFilters - << " - Allow Drift: " << blnAllowDrift - << " - Fast Lock: " << blnFastLock - << " - HDLC: " << blnHDLC - << " - HARD METRIC: " << blnHardMetric - << " - Resample: " << blnResample - << " - Viterbi: " << blnViterbi; - + m_blnInitialized=true; m_objSettingsMutex.unlock(); m_blnNeedConfigUpdate=true; - - m_blnInitialized=true; } void DATVDemod::CleanUpDATVFramework(bool blnRelease) { - //if(blnRelease==true) - if(false) + if(blnRelease==true) { if(m_objScheduler!=NULL) { @@ -251,12 +246,8 @@ void DATVDemod::CleanUpDATVFramework(bool blnRelease) delete m_objScheduler; } - // INPUT - if(p_rawiq!=NULL) delete p_rawiq; - if(p_rawiq_writer!=NULL) delete p_rawiq_writer; - if(p_preprocessed!=NULL) delete p_preprocessed; - // NOTCH FILTER + if(r_auto_notch!=NULL) delete r_auto_notch; if(p_autonotched!=NULL) delete p_autonotched; @@ -287,6 +278,7 @@ void DATVDemod::CleanUpDATVFramework(bool blnRelease) if(p_decim!=NULL) delete p_decim; if(r_ppout!=NULL) delete r_ppout; + //GENERIC CONSTELLATION RECEIVER if(m_objDemodulator!=NULL) delete m_objDemodulator; @@ -328,6 +320,12 @@ void DATVDemod::CleanUpDATVFramework(bool blnRelease) //CONSTELLATION if(r_scope_symbols!=NULL) delete r_scope_symbols; + // INPUT + //if(p_rawiq!=NULL) delete p_rawiq; + //if(p_rawiq_writer!=NULL) delete p_rawiq_writer; + //if(p_preprocessed!=NULL) delete p_preprocessed; + + } m_objScheduler=NULL; @@ -425,6 +423,21 @@ void DATVDemod::InitDATVFramework() { m_blnDVBInitialized=false; m_lngReadIQ=0; + CleanUpDATVFramework(false); + + qDebug() << "DATVDemod::InitDATVParameters:" + << " Msps: " << m_objRunning.intMsps + << " Sample Rate: " << m_objRunning.intSampleRate + << " Symbol Rate: " << m_objRunning.intSymbolRate + << " Modulation: " << m_objRunning.enmModulation + << " Notch Filters: " << m_objRunning.intNotchFilters + << " Allow Drift: " << m_objRunning.blnAllowDrift + << " Fast Lock: " << m_objRunning.blnFastLock + << " Filter: " << m_objRunning.enmFilter + << " HARD METRIC: " << m_objRunning.blnHardMetric + << " RollOff: " << m_objRunning.fltRollOff + << " Viterbi: " << m_objRunning.blnViterbi + << " Excursion: " << m_objRunning.intExcursion; m_objCfg.standard = m_objRunning.enmStandard; @@ -433,54 +446,58 @@ void DATVDemod::InitDATVFramework() m_objCfg.Fm = (float) m_objRunning.intSymbolRate; m_objCfg.fastlock = m_objRunning.blnFastLock; + m_objCfg.sampler = m_objRunning.enmFilter; + m_objCfg.rolloff=m_objRunning.fltRollOff; //0...1 + m_objCfg.rrc_rej=(float) m_objRunning.intExcursion; //dB + m_objCfg.rrc_steps=0; //auto + switch(m_objRunning.enmModulation) { case BPSK: - m_objCfg.constellation = cstln_lut<256>::BPSK; + m_objCfg.constellation = leansdr::cstln_lut<256>::BPSK; break; case QPSK: - m_objCfg.constellation = cstln_lut<256>::QPSK; + m_objCfg.constellation = leansdr::cstln_lut<256>::QPSK; break; case PSK8: - m_objCfg.constellation = cstln_lut<256>::PSK8; + m_objCfg.constellation = leansdr::cstln_lut<256>::PSK8; break; case APSK16: - m_objCfg.constellation = cstln_lut<256>::APSK16; + m_objCfg.constellation = leansdr::cstln_lut<256>::APSK16; break; case APSK32: - m_objCfg.constellation = cstln_lut<256>::APSK32; + m_objCfg.constellation = leansdr::cstln_lut<256>::APSK32; break; case APSK64E: - m_objCfg.constellation = cstln_lut<256>::APSK64E; + m_objCfg.constellation = leansdr::cstln_lut<256>::APSK64E; break; case QAM16: - m_objCfg.constellation = cstln_lut<256>::QAM16; + m_objCfg.constellation = leansdr::cstln_lut<256>::QAM16; break; case QAM64: - m_objCfg.constellation = cstln_lut<256>::QAM64; + m_objCfg.constellation = leansdr::cstln_lut<256>::QAM64; break; case QAM256: - m_objCfg.constellation = cstln_lut<256>::QAM256; + m_objCfg.constellation = leansdr::cstln_lut<256>::QAM256; break; default: - m_objCfg.constellation = cstln_lut<256>::BPSK; + m_objCfg.constellation = leansdr::cstln_lut<256>::BPSK; break; } m_objCfg.allow_drift = m_objRunning.blnAllowDrift; m_objCfg.anf = m_objRunning.intNotchFilters; m_objCfg.hard_metric = m_objRunning.blnHardMetric; - m_objCfg.hdlc = m_objRunning.blnHDLC; - m_objCfg.resample = m_objRunning.blnResample; + m_objCfg.sampler = m_objRunning.enmFilter; m_objCfg.viterbi = m_objRunning.blnViterbi; @@ -515,42 +532,34 @@ void DATVDemod::InitDATVFramework() m_lngExpectedReadIQ = BUF_BASEBAND; - - CleanUpDATVFramework(true); - - m_objScheduler = new scheduler(); + m_objScheduler = new leansdr::scheduler(); //*************** - p_rawiq = new pipebuf(m_objScheduler, "rawiq", BUF_BASEBAND); - p_rawiq_writer = new pipewriter(*p_rawiq); + p_rawiq = new leansdr::pipebuf(m_objScheduler, "rawiq", BUF_BASEBAND); + p_rawiq_writer = new leansdr::pipewriter(*p_rawiq); p_preprocessed = p_rawiq; // NOTCH FILTER - if ( m_objCfg.anf ) + if ( m_objCfg.anf>0 ) { - p_autonotched = new pipebuf(m_objScheduler, "autonotched", BUF_BASEBAND); - r_auto_notch = new auto_notch(m_objScheduler, *p_preprocessed, *p_autonotched, m_objCfg.anf, 0); + p_autonotched = new leansdr::pipebuf(m_objScheduler, "autonotched", BUF_BASEBAND); + r_auto_notch = new leansdr::auto_notch(m_objScheduler, *p_preprocessed, *p_autonotched, m_objCfg.anf, 0); p_preprocessed = p_autonotched; } // FREQUENCY CORRECTION - if ( m_objCfg.Fderot ) - { - p_derot = new pipebuf(m_objScheduler, "derotated", BUF_BASEBAND); - r_derot = new rotator(m_objScheduler, *p_preprocessed, *p_derot, -m_objCfg.Fderot/m_objCfg.Fs); - p_preprocessed = p_derot; - } + //******** -> if ( m_objCfg.Fderot>0 ) // CNR ESTIMATION - p_cnr = new pipebuf(m_objScheduler, "cnr", BUF_SLOW); + p_cnr = new leansdr::pipebuf(m_objScheduler, "cnr", BUF_SLOW); - if ( m_objCfg.cnr ) + if ( m_objCfg.cnr==true ) { - r_cnr = new cnr_fft(m_objScheduler, *p_preprocessed, *p_cnr, m_objCfg.Fm/m_objCfg.Fs); + r_cnr = new leansdr::cnr_fft(m_objScheduler, *p_preprocessed, *p_cnr, m_objCfg.Fm/m_objCfg.Fs); r_cnr->decimation = decimation(m_objCfg.Fs, 1); // 1 Hz } @@ -558,78 +567,33 @@ void DATVDemod::InitDATVFramework() int decim = 1; - if ( m_objCfg.resample ) - { - // Lowpass-filter and decimate. - if ( m_objCfg.decim ) - { - decim = m_objCfg.decim; - } - else - { - // Decimate to just above 4 samples per symbol - float target_Fs = m_objCfg.Fm * 4; - decim = m_objCfg.Fs / target_Fs; - if ( decim < 1 ) - { - decim = 1; - } - } + //******** -> if ( m_objCfg.resample ) - float transition = (m_objCfg.Fm/2) * m_objCfg.rolloff; - int order = m_objCfg.resample_rej * m_objCfg.Fs / (22*transition); - order = ((order+1)/2) * 2; // Make even - - p_resampled = new pipebuf(m_objScheduler, "resampled", BUF_BASEBAND); - - - #if 1 // Cut in middle of roll-off region - float Fcut = (m_objCfg.Fm/2) * (1+m_objCfg.rolloff/2) / m_objCfg.Fs; - #else // Cut at beginning of roll-off region - float Fcut = (m_objCfg.Fm/2) / cfg.Fs; - #endif - - ncoeffs = filtergen::lowpass(order, Fcut, &coeffs); - - filtergen::normalize_dcgain(ncoeffs, coeffs, 1); - - r_resample = new fir_filter(m_objScheduler, ncoeffs, coeffs, *p_preprocessed, *p_resampled, decim); - p_preprocessed = p_resampled; - m_objCfg.Fs /= decim; - } // DECIMATION // (Unless already done in resampler) - if ( !m_objCfg.resample && m_objCfg.decim>1 ) - { - decim = m_objCfg.decim; - - p_decimated = new pipebuf(m_objScheduler, "decimated", BUF_BASEBAND); - p_decim = new decimator(m_objScheduler, decim, *p_preprocessed, *p_decimated); - p_preprocessed = p_decimated; - m_objCfg.Fs /= decim; - } + //******** -> if ( !m_objCfg.resample && m_objCfg.decim>1 ) //Resampling FS // Generic constellation receiver - p_symbols = new pipebuf(m_objScheduler, "PSK soft-symbols", BUF_SYMBOLS); - p_freq = new pipebuf (m_objScheduler, "freq", BUF_SLOW); - p_ss = new pipebuf (m_objScheduler, "SS", BUF_SLOW); - p_mer = new pipebuf (m_objScheduler, "MER", BUF_SLOW); - p_sampled = new pipebuf (m_objScheduler, "PSK symbols", BUF_BASEBAND); + p_symbols = new leansdr::pipebuf(m_objScheduler, "PSK soft-symbols", BUF_SYMBOLS); + p_freq = new leansdr::pipebuf (m_objScheduler, "freq", BUF_SLOW); + p_ss = new leansdr::pipebuf (m_objScheduler, "SS", BUF_SLOW); + p_mer = new leansdr::pipebuf (m_objScheduler, "MER", BUF_SLOW); + p_sampled = new leansdr::pipebuf (m_objScheduler, "PSK symbols", BUF_BASEBAND); switch ( m_objCfg.sampler ) { case SAMP_NEAREST: - sampler = new nearest_sampler(); + sampler = new leansdr::nearest_sampler(); break; case SAMP_LINEAR: - sampler = new linear_sampler(); + sampler = new leansdr::linear_sampler(); break; case SAMP_RRC: @@ -639,27 +603,28 @@ void DATVDemod::InitDATVFramework() if ( m_objCfg.rrc_steps == 0 ) { // At least 64 discrete sampling points between symbols - m_objCfg.rrc_steps = max(1, (int)(64*m_objCfg.Fm / m_objCfg.Fs)); + m_objCfg.rrc_steps = std::max(1, (int)(64*m_objCfg.Fm / m_objCfg.Fs)); } float Frrc = m_objCfg.Fs * m_objCfg.rrc_steps; // Sample freq of the RRC filter float transition = (m_objCfg.Fm/2) * m_objCfg.rolloff; int order = m_objCfg.rrc_rej * Frrc / (22*transition); - ncoeffs_sampler = filtergen::root_raised_cosine(order, m_objCfg.Fm/Frrc, m_objCfg.rolloff, &coeffs_sampler); + ncoeffs_sampler = leansdr::filtergen::root_raised_cosine(order, m_objCfg.Fm/Frrc, m_objCfg.rolloff, &coeffs_sampler); - sampler = new fir_sampler(ncoeffs_sampler, coeffs_sampler, m_objCfg.rrc_steps); + sampler = new leansdr::fir_sampler(ncoeffs_sampler, coeffs_sampler, m_objCfg.rrc_steps); break; } default: - fatal("Interpolator not implemented"); + qCritical("DATVDemod::InitDATVFramework: Interpolator not implemented"); + return; } - m_objDemodulator = new cstln_receiver(m_objScheduler, sampler, *p_preprocessed, *p_symbols, p_freq, p_ss, p_mer, p_sampled); + m_objDemodulator = new leansdr::cstln_receiver(m_objScheduler, sampler, *p_preprocessed, *p_symbols, p_freq, p_ss, p_mer, p_sampled); if ( m_objCfg.standard == DVB_S ) { - if ( m_objCfg.constellation != cstln_lut<256>::QPSK && m_objCfg.constellation != cstln_lut<256>::BPSK ) + if ( m_objCfg.constellation != leansdr::cstln_lut<256>::QPSK && m_objCfg.constellation != leansdr::cstln_lut<256>::BPSK ) { fprintf(stderr, "Warning: non-standard constellation for DVB-S\n"); } @@ -681,33 +646,27 @@ void DATVDemod::InitDATVFramework() m_objDemodulator->set_omega(m_objCfg.Fs/m_objCfg.Fm); - if ( m_objCfg.Ftune ) - { - - m_objDemodulator->set_freq(m_objCfg.Ftune/m_objCfg.Fs); - } + //******** if ( m_objCfg.Ftune ) + //{ + // m_objDemodulator->set_freq(m_objCfg.Ftune/m_objCfg.Fs); + //} if ( m_objCfg.allow_drift ) { m_objDemodulator->set_allow_drift(true); } + //******** -> if ( m_objCfg.viterbi ) if ( m_objCfg.viterbi ) { m_objDemodulator->pll_adjustment /= 6; } + m_objDemodulator->meas_decimation = decimation(m_objCfg.Fs, m_objCfg.Finfo); // TRACKING FILTERS - if ( r_resample ) - { - r_resample->freq_tap = &m_objDemodulator->freq_tap; - r_resample->tap_multiplier = 1.0 / decim; - r_resample->freq_tol = m_objCfg.Fm/(m_objCfg.Fs*decim) * 0.1; - } - if ( r_cnr ) { @@ -717,27 +676,33 @@ void DATVDemod::InitDATVFramework() //constellation - m_objRegisteredDATVScreen->resizeDATVScreen(256,256); + if (m_objRegisteredTVScreen) + { + m_objRegisteredTVScreen->resizeTVScreen(256,256); - r_scope_symbols = new datvconstellation(m_objScheduler, *p_sampled, -128,128, NULL, m_objRegisteredDATVScreen); - r_scope_symbols->decimation = 1; - r_scope_symbols->cstln = &m_objDemodulator->cstln; + r_scope_symbols = new leansdr::datvconstellation(m_objScheduler, *p_sampled, -128,128, NULL, m_objRegisteredTVScreen); + r_scope_symbols->decimation = 1; + r_scope_symbols->cstln = &m_objDemodulator->cstln; + r_scope_symbols->calculate_cstln_points(); + } // DECONVOLUTION AND SYNCHRONIZATION - p_bytes = new pipebuf(m_objScheduler, "bytes", BUF_BYTES); + p_bytes = new leansdr::pipebuf(m_objScheduler, "bytes", BUF_BYTES); r_deconv = NULL; + //******** -> if ( m_objCfg.viterbi ) + if ( m_objCfg.viterbi ) { - if ( m_objCfg.fec==FEC23 && (m_objDemodulator->cstln->nsymbols==4 || m_objDemodulator->cstln->nsymbols==64) ) + if ( m_objCfg.fec == leansdr::FEC23 && (m_objDemodulator->cstln->nsymbols == 4 || m_objDemodulator->cstln->nsymbols == 64) ) { - m_objCfg.fec = FEC46; + m_objCfg.fec = leansdr::FEC46; } //To uncomment -> Linking Problem : undefined symbol: _ZN7leansdr21viterbi_dec_interfaceIhhiiE6updateEPiS2_ - r = new viterbi_sync(m_objScheduler, (*p_symbols), (*p_bytes), m_objDemodulator->cstln, m_objCfg.fec); + r = new leansdr::viterbi_sync(m_objScheduler, (*p_symbols), (*p_bytes), m_objDemodulator->cstln, m_objCfg.fec); if ( m_objCfg.fastlock ) { @@ -746,57 +711,37 @@ void DATVDemod::InitDATVFramework() } else { - r_deconv = make_deconvol_sync_simple(m_objScheduler, (*p_symbols), (*p_bytes), m_objCfg.fec); - r_deconv->fastlock = m_objCfg.fastlock; + r_deconv = make_deconvol_sync_simple(m_objScheduler, (*p_symbols), (*p_bytes), m_objCfg.fec); + r_deconv->fastlock = m_objCfg.fastlock; } + //******* -> if ( m_objCfg.hdlc ) - if ( m_objCfg.hdlc ) - { - p_descrambled = new pipebuf(m_objScheduler, "descrambled", BUF_MPEGBYTES); - r_etr192_descrambler = new etr192_descrambler(m_objScheduler, (*p_bytes), *p_descrambled); - p_frames = new pipebuf(m_objScheduler, "frames", BUF_MPEGBYTES); - r_sync = new hdlc_sync(m_objScheduler, *p_descrambled, *p_frames, 2, 278); + p_mpegbytes = new leansdr::pipebuf (m_objScheduler, "mpegbytes", BUF_MPEGBYTES); + p_lock = new leansdr::pipebuf (m_objScheduler, "lock", BUF_SLOW); + p_locktime = new leansdr::pipebuf (m_objScheduler, "locktime", BUF_PACKETS); - if ( m_objCfg.fastlock ) - { - r_sync->resync_period = 1; - } - - if ( m_objCfg.packetized ) - { - r_sync->header16 = true; - } - - } - - p_mpegbytes = new pipebuf (m_objScheduler, "mpegbytes", BUF_MPEGBYTES); - p_lock = new pipebuf (m_objScheduler, "lock", BUF_SLOW); - p_locktime = new pipebuf (m_objScheduler, "locktime", BUF_PACKETS); - - if ( ! m_objCfg.hdlc ) - { - r_sync_mpeg = new mpeg_sync(m_objScheduler, *p_bytes, *p_mpegbytes, r_deconv, p_lock, p_locktime); - r_sync_mpeg->fastlock = m_objCfg.fastlock; - } + r_sync_mpeg = new leansdr::mpeg_sync(m_objScheduler, *p_bytes, *p_mpegbytes, r_deconv, p_lock, p_locktime); + r_sync_mpeg->fastlock = m_objCfg.fastlock; // DEINTERLEAVING - p_rspackets = new pipebuf< rspacket >(m_objScheduler, "RS-enc packets", BUF_PACKETS); - r_deinter = new deinterleaver(m_objScheduler, *p_mpegbytes, *p_rspackets); + p_rspackets = new leansdr::pipebuf< leansdr::rspacket >(m_objScheduler, "RS-enc packets", BUF_PACKETS); + r_deinter = new leansdr::deinterleaver(m_objScheduler, *p_mpegbytes, *p_rspackets); // REED-SOLOMON - p_vbitcount = new pipebuf(m_objScheduler, "Bits processed", BUF_PACKETS); - p_verrcount = new pipebuf(m_objScheduler, "Bits corrected", BUF_PACKETS); - p_rtspackets = new pipebuf(m_objScheduler, "rand TS packets", BUF_PACKETS); - r_rsdec = new rs_decoder (m_objScheduler, *p_rspackets, *p_rtspackets, p_vbitcount, p_verrcount); + p_vbitcount = new leansdr::pipebuf(m_objScheduler, "Bits processed", BUF_PACKETS); + p_verrcount = new leansdr::pipebuf(m_objScheduler, "Bits corrected", BUF_PACKETS); + p_rtspackets = new leansdr::pipebuf(m_objScheduler, "rand TS packets", BUF_PACKETS); + r_rsdec = new leansdr::rs_decoder (m_objScheduler, *p_rspackets, *p_rtspackets, p_vbitcount, p_verrcount); // BER ESTIMATION + /* p_vber = new pipebuf (m_objScheduler, "VBER", BUF_SLOW); r_vber = new rate_estimator (m_objScheduler, *p_verrcount, *p_vbitcount, *p_vber); r_vber->sample_size = m_objCfg.Fm/2; // About twice per second, depending on CR @@ -805,33 +750,34 @@ void DATVDemod::InitDATVFramework() { r_vber->sample_size = 50000; } - + */ // DERANDOMIZATION - p_tspackets = new pipebuf(m_objScheduler, "TS packets", BUF_PACKETS); - r_derand = new derandomizer(m_objScheduler, *p_rtspackets, *p_tspackets); + p_tspackets = new leansdr::pipebuf(m_objScheduler, "TS packets", BUF_PACKETS); + r_derand = new leansdr::derandomizer(m_objScheduler, *p_rtspackets, *p_tspackets); // OUTPUT - r_videoplayer = new datvvideoplayer(m_objScheduler, *p_tspackets,m_objVideoStream); + r_videoplayer = new leansdr::datvvideoplayer(m_objScheduler, *p_tspackets,m_objVideoStream); m_blnDVBInitialized=true; } -void DATVDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst) +void DATVDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst __attribute__((unused))) { float fltI; float fltQ; - cf32 objIQ; + leansdr::cf32 objIQ; //Complex objC; fftfilt::cmplx *objRF; int intRFOut; + double magSq; + //********** Bis repetita : Let's rock and roll buddy ! ********** #ifdef EXTENDED_DIRECT_SAMPLE - qint16 * ptrBufferToRelease=NULL; qint16 * ptrBuffer; qint32 intLen; @@ -850,6 +796,7 @@ void DATVDemod::feed(const SampleVector::const_iterator& begin, const SampleVect ptrBuffer ++; fltQ= ((qint32) (*ptrBuffer)) << 4; ptrBuffer ++; + #else for (SampleVector::const_iterator it = begin; it != end; ++it /* ++it **/) @@ -861,73 +808,63 @@ void DATVDemod::feed(const SampleVector::const_iterator& begin, const SampleVect //********** demodulation ********** - if((m_blnDVBInitialized==false) || (m_blnNeedConfigUpdate==true)) + + if (m_blnNeedConfigUpdate) { + + m_objSettingsMutex.lock(); + m_blnNeedConfigUpdate=false; + InitDATVFramework(); + + m_objSettingsMutex.unlock(); + } + //********** iq stream **************** - if(m_lngReadIQ>p_rawiq_writer->writable()) + Complex objC(fltI,fltQ); + + objC *= m_objNCO.nextIQ(); + + intRFOut = m_objRFFilter->runFilt(objC, &objRF); // filter RF before demod + + for (int intI = 0 ; intI < intRFOut; intI++) { - m_objScheduler->step(); + objIQ.re = objRF->real(); + objIQ.im = objRF->imag(); + magSq = objIQ.re*objIQ.re + objIQ.im*objIQ.im; + m_objMagSqAverage(magSq); - m_objRegisteredDATVScreen->renderImage(NULL); + objRF ++; - m_lngReadIQ=0; - p_rawiq_writer = new pipewriter(*p_rawiq); - } - - if(false) - { - objIQ.re = fltI; - objIQ.im = fltQ; - - p_rawiq_writer->write(objIQ); - - m_lngReadIQ++; - } - else - { - - Complex objC(fltI,fltQ); - - objC *= m_objNCO.nextIQ(); - - intRFOut = m_objRFFilter->runFilt(objC, &objRF); // filter RF before demod - - for (int intI = 0 ; intI < intRFOut; intI++) + if (m_blnDVBInitialized + && (p_rawiq_writer!=NULL) + && (m_objScheduler!=NULL)) { - objIQ.re = objRF->real(); - objIQ.im = objRF->imag(); - p_rawiq_writer->write(objIQ); - - objRF ++; m_lngReadIQ++; + + //Leave +1 by safety + if((m_lngReadIQ+1)>=p_rawiq_writer->writable()) + { + m_objScheduler->step(); + + m_lngReadIQ=0; + delete p_rawiq_writer; + p_rawiq_writer = new leansdr::pipewriter(*p_rawiq); + } } + } - - - //********** demodulation ********** - } - -#ifdef EXTENDED_DIRECT_SAMPLE - if(ptrBufferToRelease!=NULL) - { - delete ptrBufferToRelease; - } -#endif - - //m_objSettingsMutex.unlock(); - } void DATVDemod::start() { - //m_objTimer.start(); + } void DATVDemod::stop() @@ -937,39 +874,34 @@ void DATVDemod::stop() bool DATVDemod::handleMessage(const Message& cmd) { - qDebug() << "DATVDemod::handleMessage"; - if (DownChannelizer::MsgChannelizerNotification::match(cmd)) - { + { DownChannelizer::MsgChannelizerNotification& objNotif = (DownChannelizer::MsgChannelizerNotification&) cmd; - if(m_objRunning.intMsps!=objNotif.getSampleRate()) + qDebug() << "DATVDemod::handleMessage: MsgChannelizerNotification:" + << " m_intSampleRate: " << objNotif.getSampleRate() + << " m_intFrequencyOffset: " << objNotif.getFrequencyOffset(); + + if (m_objRunning.intMsps != objNotif.getSampleRate()) { m_objRunning.intMsps = objNotif.getSampleRate(); m_objRunning.intSampleRate = m_objRunning.intMsps; - printf("Sample Rate: %d\r\n",m_objRunning.intSampleRate ); ApplySettings(); } - qDebug() << "DATVDemod::handleMessage: MsgChannelizerNotification:" - << " intMsps: " << m_objRunning.intMsps; - return true; - } + } else if (MsgConfigureChannelizer::match(cmd)) { - MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; m_channelizer->configure(m_channelizer->getInputMessageQueue(), m_channelizer->getInputSampleRate(), - m_objRunning.intCenterFrequency); + cfg.getCenterFrequency()); - - - qDebug() << "ATVDemod::handleMessage: MsgConfigureChannelizer: sampleRate: " << m_channelizer->getInputSampleRate() - << " centerFrequency: " << m_objRunning.intCenterFrequency; + qDebug() << "DATVDemod::handleMessage: MsgConfigureChannelizer: sampleRate: " << m_channelizer->getInputSampleRate() + << " centerFrequency: " << cfg.getCenterFrequency(); return true; } @@ -983,20 +915,21 @@ bool DATVDemod::handleMessage(const Message& cmd) || (objCfg.m_objMsgConfig.intCenterFrequency != m_objRunning.intCenterFrequency) || (objCfg.m_objMsgConfig.blnFastLock != m_objRunning.blnFastLock) || (objCfg.m_objMsgConfig.blnHardMetric != m_objRunning.blnHardMetric) - || (objCfg.m_objMsgConfig.blnHDLC != m_objRunning.blnHDLC) - || (objCfg.m_objMsgConfig.blnResample != m_objRunning.blnResample) + || (objCfg.m_objMsgConfig.enmFilter != m_objRunning.enmFilter) + || (objCfg.m_objMsgConfig.fltRollOff != m_objRunning.fltRollOff) || (objCfg.m_objMsgConfig.blnViterbi != m_objRunning.blnViterbi) || (objCfg.m_objMsgConfig.enmFEC != m_objRunning.enmFEC) || (objCfg.m_objMsgConfig.enmModulation != m_objRunning.enmModulation) || (objCfg.m_objMsgConfig.enmStandard != m_objRunning.enmStandard) || (objCfg.m_objMsgConfig.intNotchFilters != m_objRunning.intNotchFilters) - || (objCfg.m_objMsgConfig.intSymbolRate != m_objRunning.intSymbolRate)) + || (objCfg.m_objMsgConfig.intSymbolRate != m_objRunning.intSymbolRate) + || (objCfg.m_objMsgConfig.intExcursion != m_objRunning.intExcursion)) { m_objRunning.blnAllowDrift = objCfg.m_objMsgConfig.blnAllowDrift; m_objRunning.blnFastLock = objCfg.m_objMsgConfig.blnFastLock; m_objRunning.blnHardMetric = objCfg.m_objMsgConfig.blnHardMetric; - m_objRunning.blnHDLC = objCfg.m_objMsgConfig.blnHDLC; - m_objRunning.blnResample = objCfg.m_objMsgConfig.blnResample; + m_objRunning.enmFilter = objCfg.m_objMsgConfig.enmFilter; + m_objRunning.fltRollOff = objCfg.m_objMsgConfig.fltRollOff; m_objRunning.blnViterbi = objCfg.m_objMsgConfig.blnViterbi; m_objRunning.enmFEC = objCfg.m_objMsgConfig.enmFEC; m_objRunning.enmModulation = objCfg.m_objMsgConfig.enmModulation; @@ -1005,10 +938,27 @@ bool DATVDemod::handleMessage(const Message& cmd) m_objRunning.intSymbolRate = objCfg.m_objMsgConfig.intSymbolRate; m_objRunning.intRFBandwidth = objCfg.m_objMsgConfig.intRFBandwidth; m_objRunning.intCenterFrequency = objCfg.m_objMsgConfig.intCenterFrequency; + m_objRunning.intExcursion = objCfg.m_objMsgConfig.intExcursion; + + qDebug() << "ATVDemod::handleMessage: MsgConfigureDATVDemod:" + << " blnAllowDrift: " << objCfg.m_objMsgConfig.blnAllowDrift + << " intRFBandwidth: " << objCfg.m_objMsgConfig.intRFBandwidth + << " intCenterFrequency: " << objCfg.m_objMsgConfig.intCenterFrequency + << " blnFastLock: " << objCfg.m_objMsgConfig.blnFastLock + << " enmFilter: " << objCfg.m_objMsgConfig.enmFilter + << " fltRollOff: " << objCfg.m_objMsgConfig.fltRollOff + << " blnViterbi: " << objCfg.m_objMsgConfig.blnViterbi + << " enmFEC: " << objCfg.m_objMsgConfig.enmFEC + << " enmModulation: " << objCfg.m_objMsgConfig.enmModulation + << " enmStandard: " << objCfg.m_objMsgConfig.enmStandard + << " intNotchFilters: " << objCfg.m_objMsgConfig.intNotchFilters + << " intSymbolRate: " << objCfg.m_objMsgConfig.intSymbolRate + << " intRFBandwidth: " << objCfg.m_objMsgConfig.intRFBandwidth + << " intCenterFrequency: " << objCfg.m_objMsgConfig.intCenterFrequency + << " intExcursion: " << objCfg.m_objMsgConfig.intExcursion; ApplySettings(); - } - + } return true; } @@ -1020,14 +970,11 @@ bool DATVDemod::handleMessage(const Message& cmd) void DATVDemod::ApplySettings() { - if(m_objRunning.intMsps==0) { return; } - //m_objSettingsMutex.lock(); - InitDATVParameters(m_objRunning.intMsps, m_objRunning.intRFBandwidth, m_objRunning.intCenterFrequency, @@ -1039,11 +986,11 @@ void DATVDemod::ApplySettings() m_objRunning.intNotchFilters, m_objRunning.blnAllowDrift, m_objRunning.blnFastLock, - m_objRunning.blnHDLC, + m_objRunning.enmFilter, m_objRunning.blnHardMetric, - m_objRunning.blnResample, - m_objRunning.blnViterbi); - + m_objRunning.fltRollOff, + m_objRunning.blnViterbi, + m_objRunning.intExcursion); } int DATVDemod::GetSampleRate() diff --git a/plugins/channelrx/demoddatv/datvdemod.h b/plugins/channelrx/demoddatv/datvdemod.h index 399e596e9..a5af83546 100644 --- a/plugins/channelrx/demoddatv/datvdemod.h +++ b/plugins/channelrx/demoddatv/datvdemod.h @@ -43,41 +43,36 @@ class DownChannelizer; #endif - - #include "datvconstellation.h" #include "datvvideoplayer.h" #include "channel/channelsinkapi.h" -#include -#include -#include -#include -#include -#include -#include -#include +#include "dsp/basebandsamplesink.h" +#include "dsp/devicesamplesource.h" +#include "dsp/dspcommands.h" +#include "dsp/downchannelizer.h" +#include "dsp/fftfilt.h" #include "dsp/nco.h" #include "dsp/interpolator.h" #include "dsp/movingaverage.h" #include "dsp/agc.h" #include "audio/audiofifo.h" #include "util/message.h" -#include +#include "util/movingaverage.h" #include "datvideostream.h" #include "datvideorender.h" -using namespace leansdr; +#include enum DATVModulation { BPSK, QPSK, PSK8, APSK16, APSK32, APSK64E, QAM16, QAM64, QAM256 }; enum dvb_version { DVB_S, DVB_S2 }; enum dvb_sampler { SAMP_NEAREST, SAMP_LINEAR, SAMP_RRC }; -inline int decimation(float Fin, float Fout) { int d = Fin / Fout; return max(d, 1); } +inline int decimation(float Fin, float Fout) { int d = Fin / Fout; return std::max(d, 1); } struct config -{ +{ dvb_version standard; dvb_sampler sampler; @@ -88,8 +83,8 @@ struct config bool cnr; // Measure CNR unsigned int decim; // Decimation, 0=auto float Fm; // QPSK symbol rate (Hz) - cstln_lut<256>::predef constellation; - code_rate fec; + leansdr::cstln_lut<256>::predef constellation; + leansdr::code_rate fec; float Ftune; // Bias frequency for the QPSK demodulator (Hz) bool allow_drift; bool fastlock; @@ -104,30 +99,31 @@ struct config bool packetized; // Output frames with 16-bit BE length float Finfo; // Desired refresh rate on fd_info (Hz) - config() : buf_factor(4), - Fs(2.4e6), - Fderot(0), - anf(1), - cnr(false), - decim(0), - Fm(2e6), - standard(DVB_S), - constellation(cstln_lut<256>::QPSK), - fec(FEC12), - Ftune(0), - allow_drift(false), - fastlock(true), - viterbi(false), - hard_metric(false), - resample(false), - resample_rej(10), - sampler(SAMP_LINEAR), - rrc_steps(0), - rrc_rej(10), - rolloff(0.35), - hdlc(false), - packetized(false), - Finfo(5) + config() : + standard(DVB_S), + sampler(SAMP_LINEAR), + buf_factor(4), + Fs(2.4e6), + Fderot(0), + anf(0), + cnr(false), + decim(0), + Fm(2e6), + constellation(leansdr::cstln_lut<256>::QPSK), + fec(leansdr::FEC12), + Ftune(0), + allow_drift(false), + fastlock(true), + viterbi(false), + hard_metric(false), + resample(false), + resample_rej(10), + rrc_steps(0), + rrc_rej(10), + rolloff(0.35), + hdlc(false), + packetized(false), + Finfo(5) { } }; @@ -140,16 +136,17 @@ struct DATVConfig int intCenterFrequency; dvb_version enmStandard; DATVModulation enmModulation; - code_rate enmFEC; + leansdr::code_rate enmFEC; int intSampleRate; int intSymbolRate; int intNotchFilters; bool blnAllowDrift; bool blnFastLock; - bool blnHDLC; + dvb_sampler enmFilter; bool blnHardMetric; - bool blnResample; + float fltRollOff; bool blnViterbi; + int intExcursion; DATVConfig() : intMsps(1024000), @@ -157,16 +154,17 @@ struct DATVConfig intCenterFrequency(0), enmStandard(DVB_S), enmModulation(BPSK), - enmFEC(FEC12), + enmFEC(leansdr::FEC12), intSampleRate(1024000), intSymbolRate(250000), intNotchFilters(1), blnAllowDrift(false), blnFastLock(false), - blnHDLC(false), + enmFilter(SAMP_LINEAR), blnHardMetric(false), - blnResample(false), - blnViterbi(false) + fltRollOff(0.35), + blnViterbi(false), + intExcursion(10) { } }; @@ -189,52 +187,55 @@ public: virtual QByteArray serialize() const { return QByteArray(); } virtual bool deserialize(const QByteArray& data __attribute__((unused))) { return false; } - - - void configure(MessageQueue* objMessageQueue, - int intRFBandwidth, - int intCenterFrequency, - dvb_version enmStandard, - DATVModulation enmModulation, - code_rate enmFEC, - int intSymbolRate, - int intNotchFilters, - bool blnAllowDrift, - bool blnFastLock, - bool blnHDLC, - bool blnHardMetric, - bool blnResample, - bool blnViterbi); + void configure( + MessageQueue* objMessageQueue, + int intRFBandwidth, + int intCenterFrequency, + dvb_version enmStandard, + DATVModulation enmModulation, + leansdr::code_rate enmFEC, + int intSymbolRate, + int intNotchFilters, + bool blnAllowDrift, + bool blnFastLock, + dvb_sampler enmFilter, + bool blnHardMetric, + float fltRollOff, + bool blnViterbi, + int intfltExcursion); virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po); virtual void start(); virtual void stop(); virtual bool handleMessage(const Message& cmd); - bool SetDATVScreen(DATVScreen *objScreen); + bool SetTVScreen(TVScreen *objScreen); DATVideostream * SetVideoRender(DATVideoRender *objScreen); bool PlayVideo(bool blnStartStop); - void InitDATVParameters(int intMsps, - int intRFBandwidth, - int intCenterFrequency, - dvb_version enmStandard, - DATVModulation enmModulation, - code_rate enmFEC, - int intSampleRate, - int intSymbolRate, - int intNotchFilters, - bool blnAllowDrift, - bool blnFastLock, - bool blnHDLC, - bool blnHardMetric, - bool blnResample, - bool blnViterbi); + void InitDATVParameters( + int intMsps, + int intRFBandwidth, + int intCenterFrequency, + dvb_version enmStandard, + DATVModulation enmModulation, + leansdr::code_rate enmFEC, + int intSampleRate, + int intSymbolRate, + int intNotchFilters, + bool blnAllowDrift, + bool blnFastLock, + dvb_sampler enmFilter, + bool blnHardMetric, + float fltRollOff, + bool blnViterbi, + int intEExcursion); void CleanUpDATVFramework(bool blnRelease); int GetSampleRate(); void InitDATVFramework(); + double getMagSq() const { return m_objMagSqAverage; } //!< Beware this is scaled to 2^30 static const QString m_channelIdURI; static const QString m_channelId; @@ -261,9 +262,67 @@ public: { } }; - - private: + class MsgConfigureDATVDemod : public Message + { + MESSAGE_CLASS_DECLARATION + + public: + static MsgConfigureDATVDemod* create( + int intRFBandwidth, + int intCenterFrequency, + dvb_version enmStandard, + DATVModulation enmModulation, + leansdr::code_rate enmFEC, + int intSymbolRate, + int intNotchFilters, + bool blnAllowDrift, + bool blnFastLock, + dvb_sampler enmFilter, + bool blnHardMetric, + float fltRollOff, + bool blnViterbi, + int intExcursion) + { + return new MsgConfigureDATVDemod(intRFBandwidth,intCenterFrequency,enmStandard, enmModulation, enmFEC, intSymbolRate, intNotchFilters, blnAllowDrift,blnFastLock,enmFilter,blnHardMetric,fltRollOff, blnViterbi, intExcursion); + } + + DATVConfig m_objMsgConfig; + + private: + MsgConfigureDATVDemod( + int intRFBandwidth, + int intCenterFrequency, + dvb_version enmStandard, + DATVModulation enmModulation, + leansdr::code_rate enmFEC, + int intSymbolRate, + int intNotchFilters, + bool blnAllowDrift, + bool blnFastLock, + dvb_sampler enmFilter, + bool blnHardMetric, + float fltRollOff, + bool blnViterbi, + int intExcursion) : + Message() + { + m_objMsgConfig.intRFBandwidth = intRFBandwidth; + m_objMsgConfig.intCenterFrequency = intCenterFrequency; + m_objMsgConfig.enmStandard = enmStandard; + m_objMsgConfig.enmModulation = enmModulation; + m_objMsgConfig.enmFEC = enmFEC; + m_objMsgConfig.intSymbolRate = intSymbolRate; + m_objMsgConfig.intNotchFilters = intNotchFilters; + m_objMsgConfig.blnAllowDrift = blnAllowDrift; + m_objMsgConfig.blnFastLock = blnFastLock; + m_objMsgConfig.enmFilter= enmFilter; + m_objMsgConfig.blnHardMetric = blnHardMetric; + m_objMsgConfig.fltRollOff = fltRollOff; + m_objMsgConfig.blnViterbi = blnViterbi; + m_objMsgConfig.intExcursion = intExcursion; + } + }; unsigned long m_lngExpectedReadIQ; unsigned long m_lngReadIQ; @@ -279,7 +338,7 @@ private: //************** LEANDBV Scheduler *************** - scheduler * m_objScheduler; + leansdr::scheduler * m_objScheduler; struct config m_objCfg; bool m_blnDVBInitialized; @@ -288,179 +347,117 @@ private: //LeanSDR Pipe Buffer // INPUT - pipebuf *p_rawiq; - pipewriter *p_rawiq_writer; - pipebuf *p_preprocessed; + leansdr::pipebuf *p_rawiq; + leansdr::pipewriter *p_rawiq_writer; + leansdr::pipebuf *p_preprocessed; // NOTCH FILTER - auto_notch *r_auto_notch; - pipebuf *p_autonotched; + leansdr::auto_notch *r_auto_notch; + leansdr::pipebuf *p_autonotched; // FREQUENCY CORRECTION : DEROTATOR - pipebuf *p_derot; - rotator *r_derot; + leansdr::pipebuf *p_derot; + leansdr::rotator *r_derot; // CNR ESTIMATION - pipebuf *p_cnr; - cnr_fft *r_cnr; + leansdr::pipebuf *p_cnr; + leansdr::cnr_fft *r_cnr; //FILTERING - fir_filter *r_resample; - pipebuf *p_resampled; + leansdr::fir_filter *r_resample; + leansdr::pipebuf *p_resampled; float *coeffs; int ncoeffs; // OUTPUT PREPROCESSED DATA - sampler_interface *sampler; + leansdr::sampler_interface *sampler; float *coeffs_sampler; int ncoeffs_sampler; - pipebuf *p_symbols; - pipebuf *p_freq; - pipebuf *p_ss; - pipebuf *p_mer; - pipebuf *p_sampled; + leansdr::pipebuf *p_symbols; + leansdr::pipebuf *p_freq; + leansdr::pipebuf *p_ss; + leansdr::pipebuf *p_mer; + leansdr::pipebuf *p_sampled; //DECIMATION - pipebuf *p_decimated; - decimator *p_decim; + leansdr::pipebuf *p_decimated; + leansdr::decimator *p_decim; //PROCESSED DATA MONITORING - file_writer *r_ppout; + leansdr::file_writer *r_ppout; //GENERIC CONSTELLATION RECEIVER - cstln_receiver *m_objDemodulator; + leansdr::cstln_receiver *m_objDemodulator; // DECONVOLUTION AND SYNCHRONIZATION - pipebuf *p_bytes; - deconvol_sync_simple *r_deconv; - viterbi_sync *r; - pipebuf *p_descrambled; - pipebuf *p_frames; + leansdr::pipebuf *p_bytes; + leansdr::deconvol_sync_simple *r_deconv; + leansdr::viterbi_sync *r; + leansdr::pipebuf *p_descrambled; + leansdr::pipebuf *p_frames; - etr192_descrambler * r_etr192_descrambler; - hdlc_sync *r_sync; + leansdr::etr192_descrambler * r_etr192_descrambler; + leansdr::hdlc_sync *r_sync; - pipebuf *p_mpegbytes; - pipebuf *p_lock; - pipebuf *p_locktime; - mpeg_sync *r_sync_mpeg; + leansdr::pipebuf *p_mpegbytes; + leansdr::pipebuf *p_lock; + leansdr::pipebuf *p_locktime; + leansdr::mpeg_sync *r_sync_mpeg; // DEINTERLEAVING - pipebuf< rspacket > *p_rspackets; - deinterleaver *r_deinter; + leansdr::pipebuf > *p_rspackets; + leansdr::deinterleaver *r_deinter; // REED-SOLOMON - pipebuf *p_vbitcount; - pipebuf *p_verrcount; - pipebuf *p_rtspackets; - rs_decoder *r_rsdec; + leansdr::pipebuf *p_vbitcount; + leansdr::pipebuf *p_verrcount; + leansdr::pipebuf *p_rtspackets; + leansdr::rs_decoder *r_rsdec; // BER ESTIMATION - pipebuf *p_vber; - rate_estimator *r_vber; + leansdr::pipebuf *p_vber; + leansdr::rate_estimator *r_vber; // DERANDOMIZATION - pipebuf *p_tspackets; - derandomizer *r_derand; + leansdr::pipebuf *p_tspackets; + leansdr::derandomizer *r_derand; //OUTPUT - file_writer *r_stdout; - datvvideoplayer *r_videoplayer; + leansdr::file_writer *r_stdout; + leansdr::datvvideoplayer *r_videoplayer; //CONSTELLATION - datvconstellation *r_scope_symbols; + leansdr::datvconstellation *r_scope_symbols; + DeviceSourceAPI* m_deviceAPI; -private: + ThreadedBasebandSampleSink* m_threadedChannelizer; + DownChannelizer* m_channelizer; - DeviceSourceAPI* m_deviceAPI; + //*************** DATV PARAMETERS *************** + TVScreen * m_objRegisteredTVScreen; + DATVideoRender * m_objRegisteredVideoRender; + DATVideostream * m_objVideoStream; + DATVideoRenderThread * m_objRenderThread; - ThreadedBasebandSampleSink* m_threadedChannelizer; - DownChannelizer* m_channelizer; + fftfilt * m_objRFFilter; + NCO m_objNCO; - //*************** DATV PARAMETERS *************** - DATVScreen * m_objRegisteredDATVScreen; - DATVideoRender * m_objRegisteredVideoRender; - DATVideostream * m_objVideoStream; - DATVideoRenderThread * m_objRenderThread; - - fftfilt * m_objRFFilter; - NCO m_objNCO; - - bool m_blnInitialized; - bool m_blnRenderingVideo; - - DATVModulation m_enmModulation; - - //QElapsedTimer m_objTimer; -private: - - class MsgConfigureDATVDemod : public Message - { - MESSAGE_CLASS_DECLARATION - - public: - static MsgConfigureDATVDemod* create(int intRFBandwidth, - int intCenterFrequency, - dvb_version enmStandard, - DATVModulation enmModulation, - code_rate enmFEC, - int intSymbolRate, - int intNotchFilters, - bool blnAllowDrift, - bool blnFastLock, - bool blnHDLC, - bool blnHardMetric, - bool blnResample, - bool blnViterbi) - { - return new MsgConfigureDATVDemod(intRFBandwidth,intCenterFrequency,enmStandard, enmModulation, enmFEC, intSymbolRate, intNotchFilters, blnAllowDrift,blnFastLock,blnHDLC,blnHardMetric,blnResample, blnViterbi); - } - - DATVConfig m_objMsgConfig; - - private: - MsgConfigureDATVDemod(int intRFBandwidth, - int intCenterFrequency, - dvb_version enmStandard, - DATVModulation enmModulation, - code_rate enmFEC, - int intSymbolRate, - int intNotchFilters, - bool blnAllowDrift, - bool blnFastLock, - bool blnHDLC, - bool blnHardMetric, - bool blnResample, - bool blnViterbi) : - Message() - { - m_objMsgConfig.intRFBandwidth = intRFBandwidth; - m_objMsgConfig.intCenterFrequency = intCenterFrequency; - m_objMsgConfig.enmStandard = enmStandard; - m_objMsgConfig.enmModulation = enmModulation; - m_objMsgConfig.enmFEC = enmFEC; - m_objMsgConfig.intSymbolRate = intSymbolRate; - m_objMsgConfig.intNotchFilters = intNotchFilters; - m_objMsgConfig.blnAllowDrift = blnAllowDrift; - m_objMsgConfig.blnFastLock = blnFastLock; - m_objMsgConfig.blnHDLC = blnHDLC; - m_objMsgConfig.blnHardMetric = blnHardMetric; - m_objMsgConfig.blnResample = blnResample; - m_objMsgConfig.blnViterbi = blnViterbi; - } - }; + bool m_blnInitialized; + bool m_blnRenderingVideo; + bool m_blnStartStopVideo; + DATVModulation m_enmModulation; DATVConfig m_objRunning; + MovingAverageUtil m_objMagSqAverage; QMutex m_objSettingsMutex; void ApplySettings(); - }; #endif // INCLUDE_DATVDEMOD_H diff --git a/plugins/channelrx/demoddatv/datvdemodgui.cpp b/plugins/channelrx/demoddatv/datvdemodgui.cpp index 254d6049f..21c22892e 100644 --- a/plugins/channelrx/demoddatv/datvdemodgui.cpp +++ b/plugins/channelrx/demoddatv/datvdemodgui.cpp @@ -47,7 +47,7 @@ DATVDemodGUI* DATVDemodGUI::create(PluginAPI* objPluginAPI, } void DATVDemodGUI::destroy() -{ +{ delete this; } @@ -78,22 +78,25 @@ void DATVDemodGUI::resetToDefaults() ui->chkAllowDrift->setChecked(false); ui->chkFastlock->setChecked(true); - ui->chkHDLC->setChecked(false); ui->chkHardMetric->setChecked(false); - ui->chkResample->setChecked(false); ui->chkViterbi->setChecked(false); ui->cmbFEC->setCurrentIndex(0); ui->cmbModulation->setCurrentIndex(0); ui->cmbStandard->setCurrentIndex(0); + ui->cmbFilter->setCurrentIndex(0); + displayRRCParameters(false); - ui->spiNotchFilters->setValue(1); + ui->spiNotchFilters->setValue(0); ui->prgSynchro->setValue(0); ui->lblStatus->setText(""); - ui->spiBandwidth->setValue(512000); + ui->rfBandwidth->setValue(512000); ui->spiSymbolRate->setValue(250000); + ui->spiRollOff->setValue(35); + ui->spiExcursion->setValue(10); + blockApplySettings(false); @@ -109,9 +112,9 @@ QByteArray DATVDemodGUI::serialize() const s.writeBool(3, ui->chkAllowDrift->isChecked()); s.writeBool(4, ui->chkFastlock->isChecked()); - s.writeBool(5, ui->chkHDLC->isChecked()); + s.writeS32(5, ui->cmbFilter->currentIndex()); s.writeBool(6, ui->chkHardMetric->isChecked()); - s.writeBool(7, ui->chkResample->isChecked()); + s.writeS32(7, ui->spiRollOff->value()); s.writeBool(8, ui->chkViterbi->isChecked()); s.writeS32(9, ui->cmbFEC->currentIndex()); @@ -119,8 +122,9 @@ QByteArray DATVDemodGUI::serialize() const s.writeS32(11, ui->cmbStandard->currentIndex()); s.writeS32(12, ui->spiNotchFilters->value()); - s.writeS32(13, ui->spiBandwidth->value()); + s.writeS64(13, ui->rfBandwidth->getValue()); s.writeS32(14, ui->spiSymbolRate->value()); + s.writeS32(15, ui->spiExcursion->value()); return s.final(); } @@ -139,6 +143,7 @@ bool DATVDemodGUI::deserialize(const QByteArray& arrData) { QByteArray bytetmp; uint32_t u32tmp; + qint64 i64tmp; int tmp; bool booltmp; @@ -147,6 +152,7 @@ bool DATVDemodGUI::deserialize(const QByteArray& arrData) d.readS32(1, &tmp, 0); m_objChannelMarker.setCenterFrequency(tmp); + ui->deltaFrequency->setValue(tmp); if (d.readU32(2, &u32tmp)) { @@ -163,14 +169,16 @@ bool DATVDemodGUI::deserialize(const QByteArray& arrData) d.readBool(4, &booltmp, false); ui->chkFastlock->setChecked(booltmp); - d.readBool(5, &booltmp, false); - ui->chkHDLC->setChecked(booltmp); + d.readS32(5, &tmp, false); + ui->cmbFilter->setCurrentIndex(tmp); + + displayRRCParameters((tmp==2)); d.readBool(6, &booltmp, false); ui->chkHardMetric->setChecked(booltmp); - d.readBool(7, &booltmp, false); - ui->chkResample->setChecked(booltmp); + d.readS32(7, &tmp, false); + ui->spiRollOff->setValue(tmp); d.readBool(8, &booltmp, false); ui->chkViterbi->setChecked(booltmp); @@ -185,15 +193,18 @@ bool DATVDemodGUI::deserialize(const QByteArray& arrData) d.readS32(11, &tmp, 0); ui->cmbStandard->setCurrentIndex(tmp); - d.readS32(12, &tmp, 1); + d.readS32(12, &tmp, 0); ui->spiNotchFilters->setValue(tmp); - d.readS32(13, &tmp, 1024000); - ui->spiBandwidth->setValue(tmp); + d.readS64(13, &i64tmp, 5120000); + ui->rfBandwidth->setValue(i64tmp); d.readS32(14, &tmp, 250000); ui->spiSymbolRate->setValue(tmp); + d.readS32(15, &tmp, false); + ui->spiExcursion->setValue(tmp); + blockApplySettings(false); m_objChannelMarker.blockSignals(false); @@ -208,13 +219,15 @@ bool DATVDemodGUI::deserialize(const QByteArray& arrData) } } -bool DATVDemodGUI::handleMessage(const Message& objMessage) +bool DATVDemodGUI::handleMessage(const Message& objMessage __attribute__((unused))) { return false; } void DATVDemodGUI::channelMarkerChangedByCursor() { + ui->deltaFrequency->setValue(m_objChannelMarker.getCenterFrequency()); + if(m_intCenterFrequency!=m_objChannelMarker.getCenterFrequency()) { m_intCenterFrequency=m_objChannelMarker.getCenterFrequency(); @@ -227,26 +240,13 @@ void DATVDemodGUI::channelMarkerHighlightedByCursor() setHighlighted(m_objChannelMarker.getHighlighted()); } -void DATVDemodGUI::channelSampleRateChanged() -{ - qDebug("DATVDemodGUI::channelSampleRateChanged"); - applySettings(); -} -void DATVDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown) +void DATVDemodGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rollDown __attribute__((unused))) { } void DATVDemodGUI::onMenuDoubleClicked() { - /* - if (!m_blnBasicSettingsShown) - { - m_blnBasicSettingsShown = true; - BasicChannelSettingsWidget* bcsw = new BasicChannelSettingsWidget(&m_objChannelMarker, this); - bcsw->show(); - } - */ } //DATVDemodGUI::DATVDemodGUI(PluginAPI* objPluginAPI, DeviceSourceAPI *objDeviceAPI, QWidget* objParent) : @@ -258,26 +258,20 @@ DATVDemodGUI::DATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, Ba m_objChannelMarker(this), m_blnBasicSettingsShown(false), m_blnDoApplySettings(true) -{ +{ ui->setupUi(this); + ui->screenTV->setColor(true); setAttribute(Qt::WA_DeleteOnClose, true); connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); - //connect(this, SIGNAL(menuDoubleClickEvent()), this, SLOT(onMenuDoubleClicked())); - //m_objDATVDemod = new DATVDemod(); m_objDATVDemod = (DATVDemod*) rxChannel; m_objDATVDemod->setMessageQueueToGUI(getInputMessageQueue()); - m_objDATVDemod->SetDATVScreen(ui->screenTV); + m_objDATVDemod->SetTVScreen(ui->screenTV); connect(m_objDATVDemod->SetVideoRender(ui->screenTV_2),&DATVideostream::onDataPackets,this,&DATVDemodGUI::on_StreamDataAvailable); - - //connect(m_objChannelizer, SIGNAL(inputSampleRateChanged()), this, SLOT(channelSampleRateChanged())); - - - //m_objPluginAPI->addThreadedSink(m_objThreadedChannelizer); - //connect(&m_objPluginAPI->getMainWindow()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); // 50 ms + connect(ui->screenTV_2,&DATVideoRender::onMetaDataChanged,this,&DATVDemodGUI::on_StreamMetaDataChanged); m_intPreviousDecodedData=0; m_intLastDecodedData=0; @@ -288,6 +282,12 @@ DATVDemodGUI::DATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, Ba connect(&m_objTimer, SIGNAL(timeout()), this, SLOT(tick())); m_objTimer.start(); + ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); + ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); + ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999); + + ui->rfBandwidth->setColorMapper(ColorMapper(ColorMapper::GrayYellow)); + ui->rfBandwidth->setValueRange(true, 7, 0, 9999999); m_objChannelMarker.blockSignals(true); m_objChannelMarker.setColor(Qt::magenta); @@ -295,7 +295,6 @@ DATVDemodGUI::DATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, Ba m_objChannelMarker.setCenterFrequency(0); m_objChannelMarker.blockSignals(false); m_objChannelMarker.setVisible(true); - //connect(&m_objChannelMarker, SIGNAL(changed()), this, SLOT(viewChanged())); connect(&m_objChannelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor())); connect(&m_objChannelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor())); @@ -304,8 +303,6 @@ DATVDemodGUI::DATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, Ba m_deviceUISet->addChannelMarker(&m_objChannelMarker); m_deviceUISet->addRollupWidget(this); - //ui->screenTV->connectTimer(m_objPluginAPI->getMainWindow()->getMasterTimer()); - ui->pushButton_3->setIcon(style()->standardIcon(QStyle::SP_MediaPlay)); resetToDefaults(); // does applySettings() @@ -334,23 +331,20 @@ void DATVDemodGUI::applySettings() DATVModulation enmSelectedModulation; dvb_version enmVersion; - code_rate enmFEC; + leansdr::code_rate enmFEC; + dvb_sampler enmSampler; if (m_blnDoApplySettings) { - + //Bandwidth and center frequency + m_objChannelMarker.setCenterFrequency(ui->deltaFrequency->getValueNew()); + m_objChannelMarker.setBandwidth(ui->rfBandwidth->getValueNew()); DATVDemod::MsgConfigureChannelizer *msgChan = DATVDemod::MsgConfigureChannelizer::create(m_objChannelMarker.getCenterFrequency()); m_objDATVDemod->getInputMessageQueue()->push(msgChan); - //Bandwidth and center frequency - m_objChannelMarker.setBandwidth(ui->spiBandwidth->value()); - //m_objChannelizer->configure(m_objChannelizer->getInputMessageQueue(), m_objChannelizer->getInputSampleRate(), m_objChannelMarker.getCenterFrequency()); - setTitleColor(m_objChannelMarker.getColor()); - //DATV parameters: cmbStandard cmbModulation cmbFEC spiBandwidth spiSymbolRate spiNotchFilters chkAllowDrift chkFastlock chkHDLC chkHardMetric chkResample chkViterbi - strStandard = ui->cmbStandard->currentText(); if(strStandard=="DVB-S") @@ -412,67 +406,86 @@ void DATVDemodGUI::applySettings() enmSelectedModulation=BPSK; } + //Viterbi only for BPSK et QPSK + if((enmSelectedModulation!=BPSK) && (enmSelectedModulation!=QPSK)) + { + ui->chkViterbi->setChecked(false); + } + strFEC = ui->cmbFEC->currentText(); - if(strFEC=="1/2") + if(strFEC == "1/2") { - enmFEC=FEC12; + enmFEC = leansdr::FEC12; } - else if(strFEC=="2/3") + else if(strFEC == "2/3") { - enmFEC=FEC23; + enmFEC = leansdr::FEC23; } - else if(strFEC=="3/4") + else if(strFEC == "3/4") { - enmFEC=FEC34; + enmFEC = leansdr::FEC34; } - else if(strFEC=="5/6") + else if(strFEC == "5/6") { - enmFEC=FEC56; + enmFEC = leansdr::FEC56; } - else if(strFEC=="7/8") + else if(strFEC == "7/8") { - enmFEC=FEC78; + enmFEC = leansdr::FEC78; } - else if(strFEC=="4/5") + else if(strFEC == "4/5") { - enmFEC=FEC45; + enmFEC = leansdr::FEC45; } - else if(strFEC=="8/9") + else if(strFEC == "8/9") { - enmFEC=FEC89; + enmFEC = leansdr::FEC89; } - else if(strFEC=="9/10") + else if(strFEC == "9/10") { - enmFEC=FEC910; + enmFEC = leansdr::FEC910; } else { - enmFEC=FEC12; + enmFEC = leansdr::FEC12; + } + + if (ui->cmbFilter->currentIndex()==0) + { + enmSampler = SAMP_LINEAR; + } + else if(ui->cmbFilter->currentIndex()==1) + { + enmSampler = SAMP_NEAREST; + } + else + { + enmSampler = SAMP_RRC; } - m_objDATVDemod->configure(m_objDATVDemod->getInputMessageQueue(), - m_objChannelMarker.getBandwidth(), - m_objChannelMarker.getCenterFrequency(), - enmVersion, - enmSelectedModulation, - enmFEC, - ui->spiSymbolRate->value(), - ui->spiNotchFilters->value(), - ui->chkAllowDrift->isChecked(), - ui->chkFastlock->isChecked(), - ui->chkHDLC->isChecked(), - ui->chkHardMetric->isChecked(), - ui->chkResample->isChecked(), - ui->chkViterbi->isChecked()); + m_objDATVDemod->configure( + m_objDATVDemod->getInputMessageQueue(), + m_objChannelMarker.getBandwidth(), + m_objChannelMarker.getCenterFrequency(), + enmVersion, + enmSelectedModulation, + enmFEC, + ui->spiSymbolRate->value(), + ui->spiNotchFilters->value(), + ui->chkAllowDrift->isChecked(), + ui->chkFastlock->isChecked(), + enmSampler, + ui->chkHardMetric->isChecked(), + ((float)ui->spiRollOff->value())/100.0f, + ui->chkViterbi->isChecked(), + ui->spiExcursion->value()); qDebug() << "DATVDemodGUI::applySettings:" - << " .inputSampleRate: " << 0 /*m_objChannelizer->getInputSampleRate()*/ - << " m_objDATVDemod.sampleRate: " << m_objDATVDemod->GetSampleRate(); - - + << " m_objDATVDemod->getCenterFrequency: " << m_objDATVDemod->getCenterFrequency() + << " m_objDATVDemod->GetSampleRate: " << m_objDATVDemod->GetSampleRate(); } } @@ -491,9 +504,16 @@ void DATVDemodGUI::enterEvent(QEvent*) } void DATVDemodGUI::tick() -{ +{ + if (m_objDATVDemod) + { + m_objMagSqAverage(m_objDATVDemod->getMagSq()); + double magSqDB = CalcDb::dbPower(m_objMagSqAverage / (SDR_RX_SCALED*SDR_RX_SCALED)); + ui->channePowerText->setText(tr("%1 dB").arg(magSqDB, 0, 'f', 1)); + } + if((m_intLastDecodedData-m_intPreviousDecodedData)>=0) - { + { m_intLastSpeed = 8*(m_intLastDecodedData-m_intPreviousDecodedData); ui->lblRate->setText(QString("Speed: %1b/s").arg(formatBytes(m_intLastSpeed))); } @@ -506,12 +526,12 @@ void DATVDemodGUI::tick() return; } -void DATVDemodGUI::on_cmbStandard_currentIndexChanged(const QString &arg1) -{ +void DATVDemodGUI::on_cmbStandard_currentIndexChanged(const QString &arg1 __attribute__((unused))) +{ applySettings(); } -void DATVDemodGUI::on_cmbModulation_currentIndexChanged(const QString &arg1) +void DATVDemodGUI::on_cmbModulation_currentIndexChanged(const QString &arg1 __attribute__((unused))) { QString strModulation; QString strFEC; @@ -563,8 +583,8 @@ void DATVDemodGUI::on_cmbModulation_currentIndexChanged(const QString &arg1) } -void DATVDemodGUI::on_cmbFEC_currentIndexChanged(const QString &arg1) -{ +void DATVDemodGUI::on_cmbFEC_currentIndexChanged(const QString &arg1 __attribute__((unused))) +{ QString strFEC; strFEC = ui->cmbFEC->currentText(); @@ -622,50 +642,26 @@ void DATVDemodGUI::on_chkHardMetric_clicked() applySettings(); } -/* -void DATVDemodGUI::on_pushButton_clicked() -{ - applySettings(); -} -*/ - void DATVDemodGUI::on_pushButton_2_clicked() { resetToDefaults(); } -/* -void DATVDemodGUI::on_spiSampleRate_valueChanged(int arg1) -{ - applySettings(); -} -*/ - -void DATVDemodGUI::on_spiSymbolRate_valueChanged(int arg1) +void DATVDemodGUI::on_spiSymbolRate_valueChanged(int arg1 __attribute__((unused))) { applySettings(); } -void DATVDemodGUI::on_spiNotchFilters_valueChanged(int arg1) +void DATVDemodGUI::on_spiNotchFilters_valueChanged(int arg1 __attribute__((unused))) { applySettings(); } -void DATVDemodGUI::on_chkHDLC_clicked() -{ - applySettings(); -} - void DATVDemodGUI::on_chkAllowDrift_clicked() { applySettings(); } -void DATVDemodGUI::on_chkResample_clicked() -{ - applySettings(); -} - void DATVDemodGUI::on_pushButton_3_clicked() { @@ -677,23 +673,6 @@ void DATVDemodGUI::on_pushButton_3_clicked() } } -/* -void DATVDemodGUI::on_mediaStateChanged(QMediaPlayer::State state) -{ - switch(state) - { - case QMediaPlayer::PlayingState: - ui->pushButton_3->setIcon(style()->standardIcon(QStyle::SP_MediaPause)); - break; - default: - ui->pushButton_3->setIcon(style()->standardIcon(QStyle::SP_MediaPlay)); - break; - } - - ui->lblReadStatus->setText(QString("%1").arg(strLitteralState)); - -} -*/ void DATVDemodGUI::on_pushButton_4_clicked() { @@ -701,8 +680,8 @@ void DATVDemodGUI::on_pushButton_4_clicked() } -void DATVDemodGUI::on_mouseEvent(QMouseEvent* obj) -{ +void DATVDemodGUI::on_mouseEvent(QMouseEvent* obj __attribute__((unused))) +{ } QString DATVDemodGUI::formatBytes(qint64 intBytes) @@ -724,9 +703,9 @@ QString DATVDemodGUI::formatBytes(qint64 intBytes) } -void DATVDemodGUI::on_StreamDataAvailable(int *intPackets, int *intBytes, int *intPercent, qint64 *intTotalReceived) +void DATVDemodGUI::on_StreamDataAvailable(int *intPackets __attribute__((unused)), int *intBytes, int *intPercent, qint64 *intTotalReceived) { - ui->lblStatus->setText(QString("Decod: %1B").arg(formatBytes(*intTotalReceived))); + ui->lblStatus->setText(QString("Data: %1B").arg(formatBytes(*intTotalReceived))); m_intLastDecodedData = *intTotalReceived; if((*intPercent)<100) @@ -742,13 +721,87 @@ void DATVDemodGUI::on_StreamDataAvailable(int *intPackets, int *intBytes, int *i } -void DATVDemodGUI::on_spiBandwidth_valueChanged(int arg1) +void DATVDemodGUI::on_spiBandwidth_valueChanged(int arg1 __attribute__((unused))) { applySettings(); } +void DATVDemodGUI::on_deltaFrequency_changed(qint64 value __attribute__((unused))) +{ + applySettings(); +} + +void DATVDemodGUI::on_rfBandwidth_changed(qint64 value __attribute__((unused))) +{ + applySettings(); +} void DATVDemodGUI::on_chkFastlock_clicked() { applySettings(); } + +void DATVDemodGUI::on_StreamMetaDataChanged(DataTSMetaData2 *objMetaData) +{ + QString strMetaData=""; + + if(objMetaData!=NULL) + { + + if(objMetaData->OK_TransportStream==true) + { + strMetaData.sprintf("PID: %d - Width: %d - Height: %d\r\n%s%s\r\nCodec: %s\r\n", + objMetaData->PID, + objMetaData->Width, + objMetaData->Height, + objMetaData->Program.toStdString().c_str(), + objMetaData->Stream.toStdString().c_str(), + objMetaData->CodecDescription.toStdString().c_str()); + } + ui->textEdit->setText(strMetaData); + + ui->chkData->setChecked(objMetaData->OK_Data); + ui->chkTS->setChecked(objMetaData->OK_TransportStream); + ui->chkVS->setChecked(objMetaData->OK_VideoStream); + ui->chkDecoding->setChecked(objMetaData->OK_Decoding); + + if(objMetaData->OK_Decoding==true) + { + ui->pushButton_3->setIcon(style()->standardIcon(QStyle::SP_MediaPause)); + } + else + { + ui->pushButton_3->setIcon(style()->standardIcon(QStyle::SP_MediaPlay)); + } + + if(objMetaData->Height>0) + { + ui->screenTV_2->setFixedWidth((int)objMetaData->Width*(270.0f/(float)objMetaData->Height)); + } + } +} + +void DATVDemodGUI::displayRRCParameters(bool blnVisible) +{ + ui->spiRollOff->setVisible(blnVisible); + ui->spiExcursion->setVisible(blnVisible); + ui->label_5->setVisible(blnVisible); + ui->label_6->setVisible(blnVisible); +} + +void DATVDemodGUI::on_cmbFilter_currentIndexChanged(int index __attribute__((unused))) +{ + displayRRCParameters((ui->cmbFilter->currentIndex()==2)); + + applySettings(); +} + +void DATVDemodGUI::on_spiRollOff_valueChanged(int arg1 __attribute__((unused))) +{ + applySettings(); +} + +void DATVDemodGUI::on_spiExcursion_valueChanged(int arg1 __attribute__((unused))) +{ + applySettings(); +} diff --git a/plugins/channelrx/demoddatv/datvdemodgui.h b/plugins/channelrx/demoddatv/datvdemodgui.h index 1f1052d09..4af8300ac 100644 --- a/plugins/channelrx/demoddatv/datvdemodgui.h +++ b/plugins/channelrx/demoddatv/datvdemodgui.h @@ -20,14 +20,12 @@ #define INCLUDE_DATVDEMODGUI_H #include "gui/rollupwidget.h" -#include +#include "plugin/plugininstancegui.h" #include "dsp/channelmarker.h" #include "dsp/movingaverage.h" #include "datvdemod.h" -#include -#include #include @@ -71,7 +69,6 @@ private slots: void channelMarkerChangedByCursor(); void channelMarkerHighlightedByCursor(); - void channelSampleRateChanged(); void onWidgetRolled(QWidget* widget, bool rollDown); void onMenuDoubleClicked(); void tick(); @@ -81,40 +78,28 @@ private slots: void on_cmbFEC_currentIndexChanged(const QString &arg1); void on_chkViterbi_clicked(); void on_chkHardMetric_clicked(); - //void on_pushButton_clicked(); - void on_pushButton_2_clicked(); - - //void on_spiSampleRate_valueChanged(int arg1); - void on_spiSymbolRate_valueChanged(int arg1); - void on_spiNotchFilters_valueChanged(int arg1); - - void on_chkHDLC_clicked(); - void on_chkAllowDrift_clicked(); - - void on_chkResample_clicked(); - void on_pushButton_3_clicked(); - void on_pushButton_4_clicked(); - void on_mouseEvent(QMouseEvent* obj); void on_StreamDataAvailable(int *intPackets, int *intBytes, int *intPercent, qint64 *intTotalReceived); - + void on_StreamMetaDataChanged(DataTSMetaData2 *objMetaData); void on_spiBandwidth_valueChanged(int arg1); - - void on_chkFastlock_clicked(); + void on_cmbFilter_currentIndexChanged(int index); + void on_spiRollOff_valueChanged(int arg1); + void on_spiExcursion_valueChanged(int arg1); + void on_deltaFrequency_changed(qint64 value); + void on_rfBandwidth_changed(qint64 value); private: Ui::DATVDemodGUI* ui; PluginAPI* m_objPluginAPI; DeviceUISet* m_deviceUISet; - //DeviceSourceAPI* m_objDeviceAPI; ChannelMarker m_objChannelMarker; ThreadedBasebandSampleSink* m_objThreadedChannelizer; DownChannelizer* m_objChannelizer; @@ -132,7 +117,8 @@ private: bool m_blnDoApplySettings; bool m_blnButtonPlayClicked; - //explicit DATVDemodGUI(PluginAPI* objPluginAPI, DeviceSourceAPI *objDeviceAPI, QWidget* objParent = NULL); + MovingAverageUtil m_objMagSqAverage; + explicit DATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* objParent = 0); virtual ~DATVDemodGUI(); @@ -140,6 +126,8 @@ private: void applySettings(); QString formatBytes(qint64 intBytes); + void displayRRCParameters(bool blnVisible); + void leaveEvent(QEvent*); void enterEvent(QEvent*); }; diff --git a/plugins/channelrx/demoddatv/datvdemodgui.ui b/plugins/channelrx/demoddatv/datvdemodgui.ui index 4b374e721..1135f36f0 100644 --- a/plugins/channelrx/demoddatv/datvdemodgui.ui +++ b/plugins/channelrx/demoddatv/datvdemodgui.ui @@ -6,31 +6,25 @@ 0 0 - 512 - 520 + 530 + 442 - + 0 0 - 512 - 520 - - - - - 512 - 520 + 530 + 442 - Sans Serif + Liberation Sans 9 @@ -40,575 +34,888 @@ DATV Demodulator - + - 10 + 0 0 - 496 - 250 + 521 + 41 - - - 496 - 250 - + + RF Settings - - - 496 - 250 - - - - DATV Settings - - - - - 0 - 20 - 222 - 222 - - - - - QLayout::SetMinimumSize - - - - - - 0 - 0 - - - - - 220 - 220 - - - - - 220 - 220 - - - - - - - - - - - - - 230 - 20 - 261 - 221 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - 10 - 10 - 81 - 21 - - - - - DVB-S - - - - - DVB-S2 - - - - - - - 10 - 40 - 80 - 21 - - - - - BPSK - - - - - QPSK - - - - - 8PSK - - - - - 16APSK - - - - - 32APSK - - - - - 64APSKe - - - - - 16QAM - - - - - 64QAM - - - - - 256QAM - - - - - - - 10 - 70 - 80 - 21 - - - - - 1/2 - - - - - 2/3 - - - - - 3/4 - - - - - 5/6 - - - - - 7/8 - - - - - 4/5 - - - - - 8/9 - - - - - 9/10 - - - - - - - 10 - 100 - 101 - 20 - - - - FAST LOCK - - - - - - 140 - 140 - 81 - 20 - - - - VITERBI - - - - - - 10 - 120 - 111 - 20 - - - - HARD METRIC - - - - - - 100 - 40 - 61 - 21 - - - - Symbols/s - - - - - - 100 - 10 - 71 - 21 - - - - Bandwidth - - - - - - 10 - 140 - 101 - 20 - - - - HDLC - - - - - - 140 - 100 - 111 - 20 - - - - ALLOW DRIFT - - - - - - 170 - 70 - 81 - 23 - - - - 32 - - - - - - 100 - 70 - 71 - 21 - - - - Notch filter - - - - - - 140 - 120 - 85 - 20 - - - - RESAMPLE - - - - - - 70 - 190 - 181 - 20 - - - - 0 - - - - - - 10 - 170 - 111 - 16 - - - - - - - - - - - 230 - 140 - 21 - 22 - - - - R - - - - - - 170 - 40 - 81 - 23 - - - - 1 - - - 1024000000 - - - 1000 - - - - - - 170 - 10 - 81 - 23 - - - - 1000 - - - 1024000000 - - - 1000 - - - - - - 130 - 170 - 121 - 16 - - - - - - - - - - - 10 - 190 - 61 - 15 - - - - Buffer: - - - + + + + + + + 10 + + + 10 + + + + + dF + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 12 + false + + + + PointingHandCursor + + + Channel center frequency shift + + + + + + + Hz + + + + + + + + + 10 + + + 10 + + + + + BW + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 12 + false + + + + RF Bandwidth + + + + + + + Hz + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Channel power (dB) + + + -100.0 dB + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + - + - 10 - 260 - 496 - 240 + 0 + 40 + 526 + 400 - 496 - 240 + 526 + 400 - - - 496 - 240 - + + DATV - - VIDEO Stream + + QTabWidget::West - - - - 0 - 20 - 358 - 211 - - - - - QLayout::SetMinimumSize + + 1 + + + + DATV + + + + + 0 + 0 + 496 + 250 + - - - - - 0 - 0 - + + + 496 + 250 + + + + + 496 + 250 + + + + DATV Settings + + + + + 0 + 20 + 222 + 222 + + + + + QLayout::SetMinimumSize - - - 356 - 200 - + + + + + 0 + 0 + + + + + 220 + 220 + + + + + 220 + 220 + + + + Signal constellation + + + + + + + + + + + + 230 + 20 + 261 + 221 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + 10 + 10 + 81 + 21 + - - - 356 - 200 - + + DVB Standard - - + + + DVB-S + + + + + + + 10 + 40 + 80 + 21 + + + + Modulation scheme + + + + BPSK + + + + + QPSK + + + + + 8PSK + + + + + 16APSK + + + + + 32APSK + + + + + 64APSK + + + + + 16QAM + + + + + 64QAM + + + + + 256QAM + + + + + + + 10 + 70 + 80 + 21 + + + + FEC ratio + + + + 1/2 + + + + + 2/3 + + + + + 3/4 + + + + + 5/6 + + + + + 7/8 + + + + + + + 10 + 100 + 101 + 20 + + + + Fast signal decode + + + FAST LOCK - - + + + + 140 + 120 + 81 + 20 + + + + Viterbi algorithm (CPU intensive) + + + VITERBI + + + + + + 10 + 120 + 111 + 20 + + + + Constellation hardening + + + HARD METRIC + + + + + + 100 + 40 + 61 + 21 + + + + Symbols/s + + + + + + 140 + 100 + 111 + 20 + + + + Small frequency drift compensation + + + ALLOW DRIFT + + + + + + 170 + 70 + 81 + 23 + + + + Number of stray peaks to suppress + + + 32 + + + + + + 100 + 70 + 71 + 21 + + + + Notch filter + + + + + + 70 + 200 + 181 + 20 + + + + Video buffer fill + + + 0 + + + + + + 10 + 180 + 111 + 16 + + + + Total number of bytes decoded + + + Data: 0 + + + + + + 230 + 120 + 21 + 22 + + + + R + + + + + + 170 + 40 + 81 + 23 + + + + Symbol rate + + + 1 + + + 1024000000 + + + 1000 + + + + + + 130 + 180 + 121 + 16 + + + + Stream speed + + + Speed: 0b/s + + + + + + 10 + 200 + 61 + 15 + + + + Buffer: + + + + + + 10 + 150 + 91 + 22 + + + + Filter + + + + FIR LINEAR + + + + + FIR NEAREST + + + + + FIR RRC + + + + + + + 140 + 150 + 41 + 23 + + + + RRC filter roll off factor (%) + + + 1 + + + 99 + + + 35 + + + + + + 106 + 150 + 28 + 23 + + + + R.off + + + + + + 180 + 150 + 28 + 23 + + + + Exc + + + + + + 210 + 150 + 41 + 23 + + + + Filter excursion (dB) + + + 1 + + + 99 + + + 10 + + + + - - - - 360 - 20 - 131 - 211 - - - - QFrame::StyledPanel - - - QFrame::Raised - - + + + Video + + - 10 - 10 - 111 - 27 + 0 + 0 + 496 + 385 - - Video + + + 496 + 385 + - - - - - 10 - 50 - 111 - 27 - + + + 496 + 385 + - - Full Screen - - - - - - 10 - 120 - 111 - 16 - - - - - - - - - - - 10 - 90 - 111 - 16 - - - - - + + VIDEO Stream + + + + 0 + 300 + 281 + 81 + + + + Stream information + + + true + + + false + + + + + + 400 + 350 + 91 + 27 + + + + Full screen video (click in the image to return) + + + Full Screen + + + + + + 400 + 300 + 91 + 27 + + + + Start/Stop video streaming + + + Video + + + + + + 0 + 20 + 488 + 272 + + + + + + + + 0 + 0 + + + + + 480 + 270 + + + + + 355 + 270 + + + + Video + + + + + + + + + + + false + + + + 300 + 320 + 85 + 20 + + + + Transport stream detected + + + Transport + + + true + + + + + false + + + + 300 + 340 + 85 + 20 + + + + Video data detected + + + Video + + + true + + + + + false + + + + 300 + 360 + 85 + 20 + + + + Video being decoded + + + Decoding + + + true + + + + + false + + + + 300 + 300 + 85 + 20 + + + + Data being received + + + Data + + + true + + @@ -620,17 +927,24 @@
gui/rollupwidget.h
1 - - DATVScreen - QWidget -
datvscreen.h
-
DATVideoRender QWidget
datvideorender.h
1
+ + ValueDialZ + QWidget +
gui/valuedialz.h
+ 1 +
+ + TVScreen + QWidget +
gui/tvscreen.h
+ 1 +
diff --git a/plugins/channelrx/demoddatv/datvdemodplugin.cpp b/plugins/channelrx/demoddatv/datvdemodplugin.cpp index 4b52fbc09..346a898ff 100644 --- a/plugins/channelrx/demoddatv/datvdemodplugin.cpp +++ b/plugins/channelrx/demoddatv/datvdemodplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor DATVDemodPlugin::m_ptrPluginDescriptor = { QString("DATV Demodulator"), - QString("3.2.0"), + QString("4.0.1"), QString("(c) F4HKW for SDRAngel using LeanSDR framework (c) F4DAV"), QString("https://github.com/f4exb/sdrangel"), true, @@ -52,8 +52,7 @@ void DATVDemodPlugin::initPlugin(PluginAPI* ptrPluginAPI) m_ptrPluginAPI = ptrPluginAPI; // register DATV demodulator - m_ptrPluginAPI->registerRxChannel(DATVDemod::m_channelIdURI, DATVDemod::m_channelId, this); - + m_ptrPluginAPI->registerRxChannel(DATVDemod::m_channelIdURI, DATVDemod::m_channelId, this); } PluginInstanceGUI* DATVDemodPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) diff --git a/plugins/channelrx/demoddatv/datvideorender.cpp b/plugins/channelrx/demoddatv/datvideorender.cpp index c16cc4a0a..15924f548 100644 --- a/plugins/channelrx/demoddatv/datvideorender.cpp +++ b/plugins/channelrx/demoddatv/datvideorender.cpp @@ -18,7 +18,7 @@ #include "datvideorender.h" DATVideoRender::DATVideoRender(QWidget * parent): - DATVScreen(parent) + TVScreen(true, parent) { installEventFilter(this); m_blnIsFullScreen=false; @@ -108,7 +108,7 @@ static int64_t SeekFunction(void* opaque, int64_t offset, int whence) return objStream->pos(); } -bool DATVideoRender::InitializeFFMPEG() +void DATVideoRender::ResetMetaData() { MetaData.CodecID=-1; MetaData.PID=-1; @@ -120,7 +120,18 @@ bool DATVideoRender::InitializeFFMPEG() MetaData.Channels=-1; MetaData.CodecDescription= ""; - if(m_blnIsFFMPEGInitialized==true) + MetaData.OK_Decoding=false; + MetaData.OK_TransportStream=false; + MetaData.OK_VideoStream=false; + + emit onMetaDataChanged(&MetaData); +} + +bool DATVideoRender::InitializeFFMPEG() +{ + ResetMetaData(); + + if(m_blnIsFFMPEGInitialized) { return false; } @@ -169,12 +180,18 @@ bool DATVideoRender::PreprocessStream() //Prepare Codec and extract meta data + // FIXME: codec is depreecated but replacement fails +// AVCodecParameters *parms = m_objFormatCtx->streams[m_intVideoStreamIndex]->codecpar; +// m_objDecoderCtx = new AVCodecContext(); +// avcodec_parameters_to_context(m_objDecoderCtx, parms); m_objDecoderCtx = m_objFormatCtx->streams[m_intVideoStreamIndex]->codec; //Meta Data MetaData.PID = m_objFormatCtx->streams[m_intVideoStreamIndex]->id; MetaData.CodecID = m_objDecoderCtx->codec_id; + MetaData.OK_TransportStream = true; + MetaData.Program=""; MetaData.Stream=""; @@ -199,6 +216,8 @@ bool DATVideoRender::PreprocessStream() MetaData.Stream = QString("%1").arg(objBuffer); } + emit onMetaDataChanged(&MetaData); + //Decoder objCodec = avcodec_find_decoder(m_objDecoderCtx->codec_id); if(objCodec==NULL) @@ -238,10 +257,14 @@ bool DATVideoRender::PreprocessStream() MetaData.Width=m_objDecoderCtx->width; MetaData.Height=m_objDecoderCtx->height; - MetaData.BitRate=m_objDecoderCtx->bit_rate; + MetaData.BitRate= m_objDecoderCtx->bit_rate; MetaData.Channels=m_objDecoderCtx->channels; MetaData.CodecDescription= QString("%1").arg(objCodec->long_name); + MetaData.OK_VideoStream = true; + + emit onMetaDataChanged(&MetaData); + return true; } @@ -251,13 +274,11 @@ bool DATVideoRender::OpenStream(DATVideostream *objDevice) unsigned char * ptrIOBuffer = NULL; AVIOContext * objIOCtx = NULL; - if(m_blnRunning==true) + if(m_blnRunning) { return false; } - //Only once execution - m_blnRunning=true; if(objDevice==NULL) { @@ -266,19 +287,40 @@ bool DATVideoRender::OpenStream(DATVideostream *objDevice) return false; } - if(m_blnIsOpen==true) + + if(m_blnIsOpen) { qDebug() << "DATVideoProcess::OpenStream already open"; return false; } + if(objDevice->bytesAvailable()<=0) + { + + qDebug() << "DATVideoProcess::OpenStream no data available"; + + MetaData.OK_Data=false; + emit onMetaDataChanged(&MetaData); + + return false; + } + + //Only once execution + m_blnRunning=true; + + MetaData.OK_Data=true; + emit onMetaDataChanged(&MetaData); + + InitializeFFMPEG(); + if(!m_blnIsFFMPEGInitialized) { qDebug() << "DATVideoProcess::OpenStream FFMPEG not initialized"; + m_blnRunning=false; return false; } @@ -286,9 +328,11 @@ bool DATVideoRender::OpenStream(DATVideostream *objDevice) { qDebug() << "DATVideoProcess::OpenStream cannot open QIODevice"; + m_blnRunning=false; return false; } + //Connect QIODevice to FFMPEG Reader m_objFormatCtx = avformat_alloc_context(); @@ -297,10 +341,11 @@ bool DATVideoRender::OpenStream(DATVideostream *objDevice) { qDebug() << "DATVideoProcess::OpenStream cannot alloc format FFMPEG context"; + m_blnRunning=false; return false; } - ptrIOBuffer = (unsigned char *)av_malloc(intIOBufferSize+ FF_INPUT_BUFFER_PADDING_SIZE); + ptrIOBuffer = (unsigned char *)av_malloc(intIOBufferSize+ AV_INPUT_BUFFER_PADDING_SIZE); objIOCtx = avio_alloc_context( ptrIOBuffer, intIOBufferSize, @@ -318,12 +363,13 @@ bool DATVideoRender::OpenStream(DATVideostream *objDevice) { qDebug() << "DATVideoProcess::OpenStream cannot open stream"; + m_blnRunning=false; return false; } - if(!PreprocessStream()) { + m_blnRunning=false; return false; } @@ -341,14 +387,14 @@ bool DATVideoRender::RenderStream() int intGotFrame; bool blnNeedRenderingSetup; - if(m_blnIsOpen=false) + if(!m_blnIsOpen) { qDebug() << "DATVideoProcess::RenderStream Stream not open"; return false; } - if(m_blnRunning==true) + if(m_blnRunning) { return false; } @@ -374,7 +420,7 @@ bool DATVideoRender::RenderStream() intGotFrame=0; - if(avcodec_decode_video2( m_objDecoderCtx, m_objFrame, &intGotFrame, &objPacket)<0) + if(new_decode( m_objDecoderCtx, m_objFrame, &intGotFrame, &objPacket)<0) { qDebug() << "DATVideoProcess::RenderStream decoding packet error"; @@ -413,7 +459,7 @@ bool DATVideoRender::RenderStream() av_opt_set_int(m_objSwsCtx,"dsth",m_objFrame->height,0); av_opt_set_int(m_objSwsCtx,"dst_format",AV_PIX_FMT_RGB24 ,0); - av_opt_set_int(m_objSwsCtx,"sws_flag",SWS_FAST_BILINEAR /* SWS_BICUBIC*/,0); + av_opt_set_int(m_objSwsCtx,"sws_flag", SWS_FAST_BILINEAR /* SWS_BICUBIC*/,0); if(sws_init_context(m_objSwsCtx, NULL, NULL)<0) { @@ -446,7 +492,7 @@ bool DATVideoRender::RenderStream() //Rendering device setup - resizeDATVScreen(m_objFrame->width,m_objFrame->height); + resizeTVScreen(m_objFrame->width,m_objFrame->height); update(); resetImage(); @@ -455,6 +501,8 @@ bool DATVideoRender::RenderStream() MetaData.Width = m_objFrame->width; MetaData.Height = m_objFrame->height; + MetaData.OK_Decoding = true; + emit onMetaDataChanged(&MetaData); } //Frame rendering @@ -476,31 +524,23 @@ bool DATVideoRender::RenderStream() } - av_free_packet(&objPacket); + av_packet_unref(&objPacket); //********** Rendering ********** m_blnRunning=false; - //AVDictionaryEntry *objRslt= av_dict_get(fmt_ctx->programs[video_stream_index]->metadata,"service_provider",NULL,0); - //char objErrBuf[1024]; - //memset(objErrBuf,0,1024); - //av_strerror(ret,objErrBuf,1024); - return true; } bool DATVideoRender::CloseStream(QIODevice *objDevice) { - if(m_blnRunning==true) + if(m_blnRunning) { return false; } - //Only once execution - m_blnRunning=true; - if(!objDevice) { qDebug() << "DATVideoProcess::CloseStream QIODevice is NULL"; @@ -508,7 +548,7 @@ bool DATVideoRender::CloseStream(QIODevice *objDevice) return false; } - if(m_blnIsOpen=false) + if(!m_blnIsOpen) { qDebug() << "DATVideoProcess::CloseStream Stream not open"; @@ -522,8 +562,12 @@ bool DATVideoRender::CloseStream(QIODevice *objDevice) return false; } - avformat_close_input(&m_objFormatCtx); - m_objFormatCtx=NULL; + //Only once execution + m_blnRunning=true; + + // maybe done in the avcodec_close +// avformat_close_input(&m_objFormatCtx); +// m_objFormatCtx=NULL; if(m_objDecoderCtx) { @@ -531,7 +575,6 @@ bool DATVideoRender::CloseStream(QIODevice *objDevice) m_objDecoderCtx=NULL; } - if(m_objFrame) { av_frame_unref(m_objFrame); @@ -553,5 +596,35 @@ bool DATVideoRender::CloseStream(QIODevice *objDevice) m_intCurrentRenderWidth=-1; m_intCurrentRenderHeight=-1; + ResetMetaData(); + emit onMetaDataChanged(&MetaData); + return true; } + +/** + * Replacement of deprecated avcodec_decode_video2 with the same signature + * https://blogs.gentoo.org/lu_zero/2016/03/29/new-avcodec-api/ + */ +int DATVideoRender::new_decode(AVCodecContext *avctx, AVFrame *frame, int *got_frame, AVPacket *pkt) +{ + int ret; + + *got_frame = 0; + + if (pkt) { + ret = avcodec_send_packet(avctx, pkt); + // In particular, we don't expect AVERROR(EAGAIN), because we read all + // decoded frames with avcodec_receive_frame() until done. + if (ret < 0) + return ret == AVERROR_EOF ? 0 : ret; + } + + ret = avcodec_receive_frame(avctx, frame); + if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) + return ret; + if (ret >= 0) + *got_frame = 1; + + return 0; +} diff --git a/plugins/channelrx/demoddatv/datvideorender.h b/plugins/channelrx/demoddatv/datvideorender.h index a6fb1860d..02dddfb3d 100644 --- a/plugins/channelrx/demoddatv/datvideorender.h +++ b/plugins/channelrx/demoddatv/datvideorender.h @@ -23,7 +23,7 @@ #include #include -#include "datvscreen.h" +#include "gui/tvscreen.h" #include "datvideostream.h" extern "C" @@ -39,13 +39,18 @@ extern "C" #include #include "libswscale/swscale.h" - } struct DataTSMetaData2 { int PID; int CodecID; + + bool OK_Data; + bool OK_Decoding; + bool OK_TransportStream; + bool OK_VideoStream; + QString Program; QString Stream; @@ -53,12 +58,16 @@ struct DataTSMetaData2 int Height; int BitRate; int Channels; + + QString CodecDescription; DataTSMetaData2() { PID=-1; CodecID=-1; + + Program=""; Stream=""; @@ -67,12 +76,19 @@ struct DataTSMetaData2 BitRate=-1; Channels=-1; CodecDescription=""; + + OK_Data=false; + OK_Decoding=false; + OK_TransportStream=false; + OK_VideoStream=false; + } }; -class DATVideoRender : public DATVScreen +class DATVideoRender : public TVScreen { Q_OBJECT + public: explicit DATVideoRender(QWidget * parent); void SetFullScreen(bool blnFullScreen); @@ -106,18 +122,19 @@ private: bool InitializeFFMPEG(); bool PreprocessStream(); + void ResetMetaData(); - + int new_decode(AVCodecContext *avctx, AVFrame *frame, int *got_frame, AVPacket *pkt); protected: virtual bool eventFilter(QObject *obj, QEvent *event); signals: - -public slots: + void onMetaDataChanged(DataTSMetaData2 *objMetaData); }; +//To run Video Rendering with a dedicated thread class DATVideoRenderThread: public QThread { @@ -141,12 +158,11 @@ class DATVideoRenderThread: public QThread m_objRenderer = objRenderer; m_objStream = objStream; m_blnRenderingVideo=false; - } void run() { - if(m_blnRenderingVideo==true) + if(m_blnRenderingVideo) { return; } @@ -156,18 +172,11 @@ class DATVideoRenderThread: public QThread return ; } - m_blnRenderingVideo=false; + m_blnRenderingVideo = m_objRenderer->OpenStream(m_objStream); - if(m_objRenderer->OpenStream(m_objStream)) + if(!m_blnRenderingVideo) { - printf("PID: %d W: %d H: %d Codec: %s Data: %s %s\r\n",m_objRenderer->MetaData.PID - ,m_objRenderer->MetaData.Width - ,m_objRenderer->MetaData.Height - ,m_objRenderer->MetaData.CodecDescription.toStdString().c_str() - ,m_objRenderer->MetaData.Program.toStdString().c_str() - ,m_objRenderer->MetaData.Stream.toStdString().c_str()); - - m_blnRenderingVideo=true; + return; } while((m_objRenderer->RenderStream()) && (m_blnRenderingVideo==true)) @@ -176,6 +185,8 @@ class DATVideoRenderThread: public QThread m_objRenderer->CloseStream(m_objStream); + m_blnRenderingVideo=false; + } void stopRendering() @@ -185,7 +196,6 @@ class DATVideoRenderThread: public QThread private: - DATVideoRender *m_objRenderer; DATVideostream *m_objStream; bool m_blnRenderingVideo; diff --git a/plugins/channelrx/demoddatv/datvideostream.cpp b/plugins/channelrx/demoddatv/datvideostream.cpp index 88807f353..53016cb76 100644 --- a/plugins/channelrx/demoddatv/datvideostream.cpp +++ b/plugins/channelrx/demoddatv/datvideostream.cpp @@ -26,6 +26,7 @@ DATVideostream::DATVideostream(): m_intPacketReceived=0; m_intMemoryLimit = DefaultMemoryLimit; MultiThreaded=false; + ThreadTimeOut=-1; m_objeventLoop.connect(this,SIGNAL(onDataAvailable()), &m_objeventLoop, SLOT(quit()),Qt::QueuedConnection); } @@ -115,7 +116,7 @@ int DATVideostream::pushData(const char * chrData, int intSize) } bool DATVideostream::isSequential() const -{ +{ return true; } @@ -139,10 +140,11 @@ bool DATVideostream::open(OpenMode mode) //PROTECTED qint64 DATVideostream::readData(char *data, qint64 len) -{ +{ QByteArray objCurrentArray; int intEffectiveLen=0; int intExpectedLen=0; + int intThreadLoop=0; intExpectedLen = (int) len; @@ -160,15 +162,25 @@ qint64 DATVideostream::readData(char *data, qint64 len) //DATA in FIFO ? -> Waiting for DATA if((m_objFIFO.isEmpty()) || (m_objFIFO.count()=0) + { + if(intThreadLoop*5>ThreadTimeOut) + { + return -1; + } + } } } else @@ -212,12 +224,12 @@ qint64 DATVideostream::readData(char *data, qint64 len) return (qint64)intEffectiveLen; } -qint64 DATVideostream::writeData(const char *data, qint64 len) +qint64 DATVideostream::writeData(const char *data __attribute__((unused)), qint64 len __attribute__((unused))) { return 0; } -qint64 DATVideostream::readLineData(char *data, qint64 maxSize) -{ +qint64 DATVideostream::readLineData(char *data __attribute__((unused)), qint64 maxSize __attribute__((unused))) +{ return 0; } diff --git a/plugins/channelrx/demoddatv/datvideostream.h b/plugins/channelrx/demoddatv/datvideostream.h index 717ec5ea9..e9387ac69 100644 --- a/plugins/channelrx/demoddatv/datvideostream.h +++ b/plugins/channelrx/demoddatv/datvideostream.h @@ -37,6 +37,7 @@ public: ~DATVideostream(); bool MultiThreaded; + int ThreadTimeOut; int pushData(const char * chrData, int intSize); bool setMemoryLimit(int intMemoryLimit); diff --git a/plugins/channelrx/demoddatv/datvvideoplayer.h b/plugins/channelrx/demoddatv/datvvideoplayer.h index 3ea33e368..911e13fea 100644 --- a/plugins/channelrx/demoddatv/datvvideoplayer.h +++ b/plugins/channelrx/demoddatv/datvvideoplayer.h @@ -23,32 +23,42 @@ namespace leansdr { - template struct datvvideoplayer : runnable +template struct datvvideoplayer: runnable +{ + datvvideoplayer(scheduler *sch, pipebuf &_in, DATVideostream * objVideoStream) : + runnable(sch, _in.name), in(_in), m_objVideoStream(objVideoStream) { - datvvideoplayer(scheduler *sch, pipebuf &_in, DATVideostream * objVideoStream) : - runnable(sch, _in.name), - in(_in), - m_objVideoStream(objVideoStream) - { - } + } - void run() - { + void run() + { int size = in.readable() * sizeof(T); - if ( ! size ) return; + if (!size) + return; - int nw = m_objVideoStream->pushData((const char *)in.rd(),size); + int nw = m_objVideoStream->pushData((const char *) in.rd(), size); - if ( ! nw ) fatal("pipe"); - if ( nw < 0 ) fatal("write"); - if ( nw % sizeof(T) ) fatal("partial write"); - in.read(nw/sizeof(T)); - - } - private: - pipereader in; - DATVideostream * m_objVideoStream; - }; + if (!nw) + { + fatal("leansdr::datvvideoplayer::run: pipe"); + return; + } + if (nw < 0) + { + fatal("leansdr::datvvideoplayer::run: write"); + return; + } + if (nw % sizeof(T)) + { + fatal("leansdr::datvvideoplayer::run: partial write"); + return; + } + in.read(nw / sizeof(T)); + } +private: + pipereader in; + DATVideostream * m_objVideoStream; +}; } diff --git a/plugins/channelrx/demoddatv/demoddatv.pro b/plugins/channelrx/demoddatv/demoddatv.pro index a0b3cd352..c02e89ad5 100644 --- a/plugins/channelrx/demoddatv/demoddatv.pro +++ b/plugins/channelrx/demoddatv/demoddatv.pro @@ -31,15 +31,12 @@ CONFIG(macx):INCLUDEPATH += "../../../../../boost_1_64_0" SOURCES += datvdemod.cpp\ datvdemodgui.cpp\ datvdemodplugin.cpp\ - datvscreen.cpp \ - glshaderarray.cpp \ datvideostream.cpp \ datvideorender.cpp HEADERS += datvdemod.h\ datvdemodgui.h\ datvdemodplugin.h\ - datvscreen.h \ leansdr/convolutional.h \ leansdr/dsp.h \ leansdr/dvb.h \ @@ -53,7 +50,6 @@ HEADERS += datvdemod.h\ leansdr/sdr.h \ leansdr/viterbi.h \ datvconstellation.h \ - glshaderarray.h \ datvvideoplayer.h \ datvideostream.h \ datvideorender.h diff --git a/plugins/channelrx/demoddatv/glshaderarray.cpp b/plugins/channelrx/demoddatv/glshaderarray.cpp deleted file mode 100644 index bb4bae585..000000000 --- a/plugins/channelrx/demoddatv/glshaderarray.cpp +++ /dev/null @@ -1,301 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2017 F4HKW // -// for F4EXB / SDRAngel // -// // -// This program is free software; you can redistribute it and/or modify // -// it under the terms of the GNU General Public License as published by // -// the Free Software Foundation as version 3 of the License, or // -// // -// This program is distributed in the hope that it will be useful, // -// but WITHOUT ANY WARRANTY; without even the implied warranty of // -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // -// GNU General Public License V3 for more details. // -// // -// You should have received a copy of the GNU General Public License // -// along with this program. If not, see . // -/////////////////////////////////////////////////////////////////////////////////// - -#include "glshaderarray.h" - -const QString GLShaderArray::m_strVertexShaderSourceArray = QString( - "uniform highp mat4 uMatrix;\n" - "attribute highp vec4 vertex;\n" - "attribute highp vec2 texCoord;\n" - "varying mediump vec2 texCoordVar;\n" - "void main() {\n" - " gl_Position = uMatrix * vertex;\n" - " texCoordVar = texCoord;\n" - "}\n"); - -const QString GLShaderArray::m_strFragmentShaderSourceColored = QString( - "uniform lowp sampler2D uTexture;\n" - "varying mediump vec2 texCoordVar;\n" - "void main() {\n" - " gl_FragColor = texture2D(uTexture, texCoordVar);\n" - "}\n"); - -GLShaderArray::GLShaderArray() -{ - m_objProgram = 0; - m_objImage = 0; - m_objTexture = 0; - m_intCols = 0; - m_intRows = 0; - m_blnInitialized = false; - m_objCurrentRow = 0; - - m_objTextureLoc = 0; - m_objColorLoc = 0; - m_objMatrixLoc = 0; -} - -GLShaderArray::~GLShaderArray() -{ - Cleanup(); -} - -void GLShaderArray::InitializeGL(int intCols, int intRows) -{ - QMatrix4x4 objQMatrix; - - m_blnInitialized = false; - - m_intCols = 0; - m_intRows = 0; - - m_objCurrentRow = 0; - - if (m_objProgram == 0) - { - m_objProgram = new QOpenGLShaderProgram(); - - if (!m_objProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, - m_strVertexShaderSourceArray)) - { - qDebug() << "GLShaderArray::initializeGL: error in vertex shader: " - << m_objProgram->log(); - } - - if (!m_objProgram->addShaderFromSourceCode(QOpenGLShader::Fragment, - m_strFragmentShaderSourceColored)) - { - qDebug() - << "GLShaderArray::initializeGL: error in fragment shader: " - << m_objProgram->log(); - } - - m_objProgram->bindAttributeLocation("vertex", 0); - - if (!m_objProgram->link()) - { - qDebug() << "GLShaderArray::initializeGL: error linking shader: " - << m_objProgram->log(); - } - - m_objProgram->bind(); - m_objProgram->setUniformValue(m_objMatrixLoc, objQMatrix); - m_objProgram->setUniformValue(m_objTextureLoc, 0); - m_objProgram->release(); - } - - m_objMatrixLoc = m_objProgram->uniformLocation("uMatrix"); - m_objTextureLoc = m_objProgram->uniformLocation("uTexture"); - m_objColorLoc = m_objProgram->uniformLocation("uColour"); - - if (m_objTexture != 0) - { - delete m_objTexture; - m_objTexture = 0; - } - - //Image container - m_objImage = new QImage(intCols, intRows, QImage::Format_RGBA8888); - m_objImage->fill(QColor(0, 0, 0)); - - m_objTexture = new QOpenGLTexture(*m_objImage); - m_objTexture->setMinificationFilter(QOpenGLTexture::Linear); - m_objTexture->setMagnificationFilter(QOpenGLTexture::Linear); - m_objTexture->setWrapMode(QOpenGLTexture::ClampToEdge); - - m_intCols = intCols; - m_intRows = intRows; - - m_blnInitialized = true; - -} - -QRgb * GLShaderArray::GetRowBuffer(int intRow) -{ - if (m_blnInitialized == false) - { - return 0; - } - - if (m_objImage == 0) - { - return 0; - } - - if (intRow > m_intRows) - { - return 0; - } - - return (QRgb *) m_objImage->scanLine(intRow); -} - -void GLShaderArray::RenderPixels(unsigned char *chrData) -{ - QOpenGLFunctions *ptrF; - int intI; - int intJ; - int intNbVertices = 6; - - QMatrix4x4 objQMatrix; - - GLfloat arrVertices[] = - // 2 3 - // 1 4 - //1 2 3 3 4 1 - { -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, -1.0f }; - - GLfloat arrTextureCoords[] = - // 1 4 - // 2 3 - //1 2 3 3 4 1 - { 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f }; - - QRgb *ptrLine; - - if (m_blnInitialized == false) - { - return; - } - - if (m_objImage == 0) - { - return; - } - - if (chrData != 0) - { - for (intJ = 0; intJ < m_intRows; intJ++) - { - ptrLine = (QRgb *) m_objImage->scanLine(intJ); - - for (intI = 0; intI < m_intCols; intI++) - { - - *ptrLine = qRgb((int) (*(chrData+2)), (int) (*(chrData+1)), (int) (*chrData)); - ptrLine++; - - chrData+=3; - } - } - } - - //Affichage - ptrF = QOpenGLContext::currentContext()->functions(); - - m_objProgram->bind(); - - m_objProgram->setUniformValue(m_objMatrixLoc, objQMatrix); - m_objProgram->setUniformValue(m_objTextureLoc, 0); - - m_objTexture->bind(); - - ptrF->glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_intCols, m_intRows, GL_RGBA, - GL_UNSIGNED_BYTE, m_objImage->bits()); - - ptrF->glEnableVertexAttribArray(0); // vertex - ptrF->glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, arrVertices); - - ptrF->glEnableVertexAttribArray(1); // texture coordinates - ptrF->glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, arrTextureCoords); - - ptrF->glDrawArrays(GL_TRIANGLES, 0, intNbVertices); - - //cleanup - ptrF->glDisableVertexAttribArray(0); - ptrF->glDisableVertexAttribArray(1); - - //*********************// - - m_objTexture->release(); - m_objProgram->release(); - -} - -void GLShaderArray::ResetPixels() -{ - if (m_objImage != 0) - { - m_objImage->fill(0); - } -} - -void GLShaderArray::Cleanup() -{ - m_blnInitialized = false; - - m_intCols = 0; - m_intRows = 0; - - m_objCurrentRow = 0; - - if (m_objProgram) - { - delete m_objProgram; - m_objProgram = 0; - } - - if (m_objTexture != 0) - { - delete m_objTexture; - m_objTexture = 0; - } - - if (m_objImage != 0) - { - delete m_objImage; - m_objImage = 0; - } -} - -bool GLShaderArray::SelectRow(int intLine) -{ - bool blnRslt = false; - - if (m_blnInitialized) - { - if ((intLine < m_intRows) && (intLine >= 0)) - { - m_objCurrentRow = (QRgb *) m_objImage->scanLine(intLine); - blnRslt = true; - } - else - { - m_objCurrentRow = 0; - } - } - - return blnRslt; -} - -bool GLShaderArray::SetDataColor(int intCol, QRgb objColor) -{ - bool blnRslt = false; - - if (m_blnInitialized) - { - if ((intCol < m_intCols) && (intCol >= 0) && (m_objCurrentRow != 0)) - { - m_objCurrentRow[intCol] = objColor; - blnRslt = true; - } - } - - - return blnRslt; -} - diff --git a/plugins/channelrx/demoddatv/leansdr/convolutional.h b/plugins/channelrx/demoddatv/leansdr/convolutional.h index 2b6e909d9..f103fc420 100644 --- a/plugins/channelrx/demoddatv/leansdr/convolutional.h +++ b/plugins/channelrx/demoddatv/leansdr/convolutional.h @@ -1,103 +1,131 @@ #ifndef LEANSDR_CONVOLUTIONAL_H #define LEANSDR_CONVOLUTIONAL_H -namespace leansdr { +namespace leansdr +{ - // ALGEBRAIC DECONVOLUTION +// ALGEBRAIC DECONVOLUTION - // QPSK 1/2 only. - // This is a straightforward implementation, provided for reference. - // deconvol_poly2 is functionally equivalent and much faster. +// QPSK 1/2 only. +// This is a straightforward implementation, provided for reference. +// deconvol_poly2 is functionally equivalent and much faster. - template - struct deconvol_poly { +template +struct deconvol_poly +{ typedef u8 hardsymbol; // Support soft of float input - inline u8 SYMVAL(const hardsymbol *s) { return *s; } - inline u8 SYMVAL(const softsymbol *s) { return s->symbol; } + inline u8 SYMVAL(const hardsymbol *s) + { + return *s; + } + inline u8 SYMVAL(const softsymbol *s) + { + return s->symbol; + } typedef u8 decoded_byte; - deconvol_poly() : hist(0) { } + deconvol_poly() : + hist(0) + { + } // Remap and deconvolve [nb*8] symbols into [nb] bytes. // Return estimated number of bit errors. - int run(const Tin *pin, const u8 remap[], decoded_byte *pout, int nb) { - int nerrors = 0; - int halfway = nb / 2; - for ( ; nb--; ++pout ) { - decoded_byte byte = 0; - for ( int bit=8; bit--; ++pin) { - hist = (hist<<2) | remap[SYMVAL(pin)]; - byte = (byte<<1) | parity(hist&POLY_DECONVOL); - if ( nb < halfway ) - nerrors += parity(hist&POLY_ERRORS); - } - *pout = byte; - } - return nerrors; + int run(const Tin *pin, const u8 remap[], decoded_byte *pout, int nb) + { + int nerrors = 0; + int halfway = nb / 2; + for (; nb--; ++pout) + { + decoded_byte byte = 0; + for (int bit = 8; bit--; ++pin) + { + hist = (hist << 2) | remap[SYMVAL(pin)]; + byte = (byte << 1) | parity(hist & POLY_DECONVOL); + if (nb < halfway) + nerrors += parity(hist & POLY_ERRORS); + } + *pout = byte; + } + return nerrors; } - private: +private: Thist hist; - }; // deconvol_poly +}; +// deconvol_poly +// ALGEBRAIC DECONVOLUTION, OPTIMIZED - // ALGEBRAIC DECONVOLUTION, OPTIMIZED +// QPSK 1/2 only. +// Functionally equivalent to deconvol_poly, +// but processing 32 bits in parallel. - // QPSK 1/2 only. - // Functionally equivalent to deconvol_poly, - // but processing 32 bits in parallel. - - template - struct deconvol_poly2 { +template +struct deconvol_poly2 +{ typedef u8 hardsymbol; // Support instanciation of template with soft of float input - inline u8 SYMVAL(const hardsymbol *s) { return *s; } - inline u8 SYMVAL(const softsymbol *s) { return s->symbol; } + inline u8 SYMVAL(const hardsymbol *s) + { + return *s; + } + inline u8 SYMVAL(const softsymbol *s) + { + return s->symbol; + } typedef u8 decoded_byte; - deconvol_poly2() : inI(0), inQ(0) { } - - // Remap and deconvolve [nb*8] symbols into [nb] bytes. - // Return estimated number of bit errors. + deconvol_poly2() : + inI(0), inQ(0) + { + } - int run(const Tin *pin, const u8 remap[], decoded_byte *pout, int nb) { - if ( nb & (sizeof(Thist)-1) ) - fail("Must deconvolve sizeof(Thist) bytes at a time"); - nb /= sizeof(Thist); - unsigned long nerrors = 0; - int halfway = nb / 2; - Thist histI=inI, histQ=inQ; - for ( ; nb--; ) { - // This is where we convolve bits in parallel. - Thist wd = 0; // decoded bits - Thist we = 0; // error bits (should be 0) + // Remap and deconvolve [nb*8] symbols into [nb] bytes. + // Return estimated number of bit errors or -1 if error. + + int run(const Tin *pin, const u8 remap[], decoded_byte *pout, int nb) + { + if (nb & (sizeof(Thist) - 1)) { + fail("deconvol_poly2::run", "Must deconvolve sizeof(Thist) bytes at a time"); + return -1; + } + nb /= sizeof(Thist); + unsigned long nerrors = 0; + int halfway = nb / 2; + Thist histI = inI, histQ = inQ; + for (; nb--;) + { + // This is where we convolve bits in parallel. + Thist wd = 0; // decoded bits + Thist we = 0; // error bits (should be 0) #if 0 - // Trust gcc to unroll and evaluate the bit tests at compile-time. - for ( int bit=sizeof(Thist)*8; bit--; ++pin ) { - u8 iq = remap[SYMVAL(pin)]; - histI = (histI<<1) | (iq>>1); - histQ = (histQ<<1) | (iq&1); - if ( POLY_DECONVOL & ((Tpoly)2<<(2*bit)) ) wd ^= histI; - if ( POLY_DECONVOL & ((Tpoly)1<<(2*bit)) ) wd ^= histQ; - if ( POLY_ERRORS & ((Tpoly)2<<(2*bit)) ) we ^= histI; - if ( POLY_ERRORS & ((Tpoly)1<<(2*bit)) ) we ^= histQ; - } + // Trust gcc to unroll and evaluate the bit tests at compile-time. + for ( int bit=sizeof(Thist)*8; bit--; ++pin ) + { + u8 iq = remap[SYMVAL(pin)]; + histI = (histI<<1) | (iq>>1); + histQ = (histQ<<1) | (iq&1); + if ( POLY_DECONVOL & ((Tpoly)2<<(2*bit)) ) wd ^= histI; + if ( POLY_DECONVOL & ((Tpoly)1<<(2*bit)) ) wd ^= histQ; + if ( POLY_ERRORS & ((Tpoly)2<<(2*bit)) ) we ^= histI; + if ( POLY_ERRORS & ((Tpoly)1<<(2*bit)) ) we ^= histQ; + } #else - // Unroll manually. + // Unroll manually. #define LOOP(bit) { \ u8 iq = remap[SYMVAL(pin)]; \ histI = (histI<<1) | (iq>>1); \ @@ -108,150 +136,231 @@ namespace leansdr { if ( POLY_ERRORS & ((Tpoly)1<<(2*bit)) ) we ^= histQ; \ ++pin; \ } - // Don't shift by more than the operand width - switch ( sizeof(Thist)*8 ) { + // Don't shift by more than the operand width + switch (sizeof(Thist) /* 8*/) + { #if 0 // Not needed yet - avoid compiler warnings - case 64: - LOOP(63); LOOP(62); LOOP(61); LOOP(60); - LOOP(59); LOOP(58); LOOP(57); LOOP(56); - LOOP(55); LOOP(54); LOOP(53); LOOP(52); - LOOP(51); LOOP(50); LOOP(49); LOOP(48); - LOOP(47); LOOP(46); LOOP(45); LOOP(44); - LOOP(43); LOOP(42); LOOP(41); LOOP(40); - LOOP(39); LOOP(38); LOOP(37); LOOP(36); - LOOP(35); LOOP(34); LOOP(33); LOOP(32); - // Fall-through + case 8: + LOOP(63); LOOP(62); LOOP(61); LOOP(60); + LOOP(59); LOOP(58); LOOP(57); LOOP(56); + LOOP(55); LOOP(54); LOOP(53); LOOP(52); + LOOP(51); LOOP(50); LOOP(49); LOOP(48); + LOOP(47); LOOP(46); LOOP(45); LOOP(44); + LOOP(43); LOOP(42); LOOP(41); LOOP(40); + LOOP(39); LOOP(38); LOOP(37); LOOP(36); + LOOP(35); LOOP(34); LOOP(33); LOOP(32); + // Fall-through #endif - case 32: - LOOP(31); LOOP(30); LOOP(29); LOOP(28); - LOOP(27); LOOP(26); LOOP(25); LOOP(24); - LOOP(23); LOOP(22); LOOP(21); LOOP(20); - LOOP(19); LOOP(18); LOOP(17); LOOP(16); - // Fall-through - case 16: - LOOP(15); LOOP(14); LOOP(13); LOOP(12); - LOOP(11); LOOP(10); LOOP( 9); LOOP( 8); - // Fall-through - case 8: - LOOP( 7); LOOP( 6); LOOP( 5); LOOP( 4); - LOOP( 3); LOOP( 2); LOOP( 1); LOOP( 0); - break; - default: - fail("Thist not supported"); - } + case 4: + LOOP(31) + ; + LOOP(30) + ; + LOOP(29) + ; + LOOP(28) + ; + LOOP(27) + ; + LOOP(26) + ; + LOOP(25) + ; + LOOP(24) + ; + LOOP(23) + ; + LOOP(22) + ; + LOOP(21) + ; + LOOP(20) + ; + LOOP(19) + ; + LOOP(18) + ; + LOOP(17) + ; + LOOP(16) + ; + // Fall-through + case 2: + LOOP(15) + ; + LOOP(14) + ; + LOOP(13) + ; + LOOP(12) + ; + LOOP(11) + ; + LOOP(10) + ; + LOOP(9) + ; + LOOP(8) + ; + // Fall-through + case 1: + LOOP(7) + ; + LOOP(6) + ; + LOOP(5) + ; + LOOP(4) + ; + LOOP(3) + ; + LOOP(2) + ; + LOOP(1) + ; + LOOP(0) + ; + break; + default: + fail("deconvol_poly2::run", "Thist not supported (1)"); + return -1; + } #undef LOOP #endif - switch ( sizeof(Thist)*8 ) { + switch (sizeof(Thist) /* 8*/) + { #if 0 // Not needed yet - avoid compiler warnings - case 64: - *pout++ = wd >> 56; - *pout++ = wd >> 48; - *pout++ = wd >> 40; - *pout++ = wd >> 32; - // Fall-through + case 8: + *pout++ = wd >> 56; + *pout++ = wd >> 48; + *pout++ = wd >> 40; + *pout++ = wd >> 32; + // Fall-through #endif - case 32: - *pout++ = wd >> 24; - *pout++ = wd >> 16; - // Fall-through - case 16: - *pout++ = wd >> 8; - // Fall-through - case 8: - *pout++ = wd; - break; - default: - fail("Thist not supported"); - } - // Count errors when the shift registers are full - if ( nb < halfway ) nerrors += hamming_weight(we); - } - inI = histI; - inQ = histQ; - return nerrors; + case 4: + *pout++ = wd >> 24; + *pout++ = wd >> 16; + // Fall-through + case 2: + *pout++ = wd >> 8; + // Fall-through + case 1: + *pout++ = wd; + break; + default: + fail("deconvol_poly2::run", "Thist not supported (2)"); + return -1; + } + // Count errors when the shift registers are full + if (nb < halfway) + nerrors += hamming_weight(we); + } + inI = histI; + inQ = histQ; + return nerrors; } - private: +private: Thist inI, inQ; - }; // deconvol_poly2 +}; +// deconvol_poly2 +// CONVOLUTIONAL ENCODER - // CONVOLUTIONAL ENCODER +// QPSK 1/2 only. - // QPSK 1/2 only. - - template - struct convol_poly2 { +template +struct convol_poly2 +{ typedef u8 uncoded_byte; typedef u8 hardsymbol; - convol_poly2() : hist(0) { } - + convol_poly2() : + hist(0) + { + } + // Convolve [count] bytes into [count*8] symbols, and remap. - void run(const uncoded_byte *pin, const u8 remap[], - hardsymbol *pout, int count) { - for ( ; count--; ++pin ) { - uncoded_byte b = *pin; - for ( int bit=8; bit--; ++pout ) { - hist = (hist>>1) | (((b>>bit)&1)<<6); - u8 s = (parity(hist&POLY1)<<1) | parity(hist&POLY2); - *pout = remap[s]; - } - } + void run(const uncoded_byte *pin, const u8 remap[], hardsymbol *pout, int count) + { + for (; count--; ++pin) + { + uncoded_byte b = *pin; + for (int bit = 8; bit--; ++pout) + { + hist = (hist >> 1) | (((b >> bit) & 1) << 6); + u8 s = (parity(hist & POLY1) << 1) | parity(hist & POLY2); + *pout = remap[s]; + } + } } - private: +private: Thist hist; - }; // convol_poly2 +}; +// convol_poly2 - // Generic BPSK..256QAM and puncturing +// Generic BPSK..256QAM and puncturing - template - struct convol_multipoly { +template +struct convol_multipoly +{ typedef u8 uncoded_byte; typedef u8 hardsymbol; int bits_in, bits_out, bps; const Thist *polys; // [bits_out] - convol_multipoly() - : bits_in(0), bits_out(0), bps(0), - hist(0), nhist(0), sersymb(0), nsersymb(0) - { } - - void encode(const uncoded_byte *pin, hardsymbol *pout, int count) { - if ( !bits_in || !bits_out || !bps ) - fatal("convol_multipoly not configured"); - hardsymbol symbmask = (1<>1) | ((Thist)((b>>bit)&1)<<(HISTSIZE-1)); - ++nhist; - if ( nhist == bits_in ) { - for ( int p=0; p= bps ) { - hardsymbol s = (sersymb >> (nsersymb-bps)) & symbmask; - *pout++ = s; - nsersymb -= bps; - } - } - } - } - // Ensure deterministic output size - // TBD We can relax this - if ( nhist || nsersymb ) fatal("partial run"); + convol_multipoly() : + bits_in(0), bits_out(0), bps(0), polys(0), hist(0), nhist(0), sersymb(0), nsersymb(0) + { } - private: + + void encode(const uncoded_byte *pin, hardsymbol *pout, int count) + { + if (!bits_in || !bits_out || !bps) + { + fatal("leansdr::convol_multipoly::encode: convol_multipoly not configured"); + return; + } + hardsymbol symbmask = (1 << bps) - 1; + for (; count--; ++pin) + { + uncoded_byte b = *pin; + for (int bit = 8; bit--;) + { + hist = (hist >> 1) | ((Thist) ((b >> bit) & 1) << (HISTSIZE - 1)); + ++nhist; + if (nhist == bits_in) + { + for (int p = 0; p < bits_out; ++p) + { + int b = parity((Thist) (hist & polys[p])); + sersymb = (sersymb << 1) | b; + } + nhist = 0; + nsersymb += bits_out; + while (nsersymb >= bps) + { + hardsymbol s = (sersymb >> (nsersymb - bps)) & symbmask; + *pout++ = s; + nsersymb -= bps; + } + } + } + } + // Ensure deterministic output size + // TBD We can relax this + if (nhist || nsersymb) { + fatal("leansdr::convol_multipoly::encode: partial run"); + } + } +private: Thist hist; int nhist; Thist sersymb; int nsersymb; - }; // convol_multipoly +}; +// convol_multipoly -} // namespace +}// namespace #endif // LEANSDR_CONVOLUTIONAL_H diff --git a/plugins/channelrx/demoddatv/leansdr/dsp.h b/plugins/channelrx/demoddatv/leansdr/dsp.h index cc66d73c5..6067ee7a0 100644 --- a/plugins/channelrx/demoddatv/leansdr/dsp.h +++ b/plugins/channelrx/demoddatv/leansdr/dsp.h @@ -5,347 +5,440 @@ #include "leansdr/framework.h" #include "leansdr/math.h" -namespace leansdr { +namespace leansdr +{ - ////////////////////////////////////////////////////////////////////// - // DSP blocks - ////////////////////////////////////////////////////////////////////// - - // [cconverter] converts complex streams between numric types, - // with optional ofsetting and rational scaling. - template - struct cconverter : runnable { - cconverter(scheduler *sch, pipebuf< complex > &_in, - pipebuf< complex > &_out) - : runnable(sch, "cconverter"), - in(_in), out(_out) { +////////////////////////////////////////////////////////////////////// +// DSP blocks +////////////////////////////////////////////////////////////////////// + +// [cconverter] converts complex streams between numric types, +// with optional ofsetting and rational scaling. +template +struct cconverter: runnable +{ + cconverter(scheduler *sch, pipebuf > &_in, + pipebuf > &_out) : + runnable(sch, "cconverter"), in(_in), out(_out) + { } - void run() { - unsigned long count = min(in.readable(), out.writable()); - complex *pin=in.rd(), *pend=pin+count; - complex *pout = out.wr(); - for ( ; pinre = Zout + (pin->re-(Tin)Zin)*Gn/Gd; - pout->im = Zout + (pin->im-(Tin)Zin)*Gn/Gd; - } - in.read(count); - out.written(count); + + void run() + { + unsigned long count = min(in.readable(), out.writable()); + complex *pin = in.rd(), *pend = pin + count; + complex *pout = out.wr(); + for (; pin < pend; ++pin, ++pout) + { + pout->re = Zout + (pin->re - (Tin) Zin) * Gn / Gd; + pout->im = Zout + (pin->im - (Tin) Zin) * Gn / Gd; + } + in.read(count); + out.written(count); } - private: - pipereader< complex > in; - pipewriter< complex > out; - }; - - template - struct cfft_engine { - const int n; - cfft_engine(int _n) : n(_n), invsqrtn(1.0/sqrt(n)) { - // Compute log2(n) - logn = 0; - for ( int t=n; t>1; t>>=1 ) ++logn; - // Bit reversal - bitrev = new int[n]; - for ( int i=0; i>b)&1); - } - // Float constants - omega = new complex[n]; - omega_rev = new complex[n]; - for ( int i=0; i > in; + pipewriter > out; +}; + +template +struct cfft_engine +{ + const unsigned int n; + + cfft_engine(unsigned int _n) : + n(_n), invsqrtn(1.0 / sqrt(n)) + { + // Compute log2(n) + logn = 0; + for (int t = n; t > 1; t >>= 1) + ++logn; + // Bit reversal + bitrev = new int[n]; + for (unsigned int i = 0; i < n; ++i) + { + bitrev[i] = 0; + for (int b = 0; b < logn; ++b) + bitrev[i] = (bitrev[i] << 1) | ((i >> b) & 1); + } + // Float constants + omega = new complex [n]; + omega_rev = new complex [n]; + for (unsigned int i = 0; i < n; ++i) + { + float a = 2.0 * M_PI * i / n; + omega_rev[i].re = (omega[i].re = cosf(a)); + omega_rev[i].im = -(omega[i].im = sinf(a)); + } } - void inplace(complex *data, bool reverse=false) { - // Bit-reversal permutation - for ( int i=0; i tmp=data[i]; data[i]=data[r]; data[r]=tmp; } - } - complex *om = reverse ? omega_rev : omega; - // Danielson-Lanczos - for ( int i=0; i &w = om[k*dom]; - complex &dqk = data[q+k]; - complex x(w.re*dqk.re - w.im*dqk.im, - w.re*dqk.im + w.im*dqk.re); - data[q+k].re = data[p+k].re - x.re; - data[q+k].im = data[p+k].im - x.im; - data[p+k].re = data[p+k].re + x.re; - data[p+k].im = data[p+k].im + x.im; - } - } - } - if ( reverse ) { - float invn = 1.0 / n; - for ( int i=0; i *data, bool reverse = false) + { + // Bit-reversal permutation + for (unsigned int i = 0; i < n; ++i) + { + unsigned int r = bitrev[i]; + if (r < i) + { + complex tmp = data[i]; + data[i] = data[r]; + data[r] = tmp; + } + } + complex *om = reverse ? omega_rev : omega; + // Danielson-Lanczos + for (int i = 0; i < logn; ++i) + { + int hbs = 1 << i; + int dom = 1 << (logn - 1 - i); + for (int j = 0; j < dom; ++j) + { + int p = j * hbs * 2, q = p + hbs; + for (int k = 0; k < hbs; ++k) + { + complex &w = om[k * dom]; + complex &dqk = data[q + k]; + complex x(w.re * dqk.re - w.im * dqk.im, + w.re * dqk.im + w.im * dqk.re); + data[q + k].re = data[p + k].re - x.re; + data[q + k].im = data[p + k].im - x.im; + data[p + k].re = data[p + k].re + x.re; + data[p + k].im = data[p + k].im + x.im; + } + } + } + if (reverse) + { + float invn = 1.0 / n; + for (unsigned int i = 0; i < n; ++i) + { + data[i].re *= invn; + data[i].im *= invn; + } + } } - private: + +private: int logn; int *bitrev; complex *omega, *omega_rev; float invsqrtn; - }; - - template - struct adder : runnable { - adder(scheduler *sch, - pipebuf &_in1, pipebuf &_in2, pipebuf &_out) - : runnable(sch, "adder"), - in1(_in1), in2(_in2), out(_out) { +}; + +template +struct adder: runnable +{ + adder(scheduler *sch, pipebuf &_in1, pipebuf &_in2, pipebuf &_out) : + runnable(sch, "adder"), + in1(_in1), + in2(_in2), + out(_out) + { } - void run() { - int n = out.writable(); - if ( in1.readable() < n ) n = in1.readable(); - if ( in2.readable() < n ) n = in2.readable(); - T *pin1=in1.rd(), *pin2=in2.rd(), *pout=out.wr(), *pend=pout+n; - while ( pout < pend ) *pout++ = *pin1++ + *pin2++; - in1.read(n); - in2.read(n); - out.written(n); + + void run() + { + int n = out.writable(); + if (in1.readable() < n) + n = in1.readable(); + if (in2.readable() < n) + n = in2.readable(); + T *pin1 = in1.rd(), *pin2 = in2.rd(), *pout = out.wr(), *pend = pout + + n; + while (pout < pend) + *pout++ = *pin1++ + *pin2++; + in1.read(n); + in2.read(n); + out.written(n); } - private: + +private: pipereader in1, in2; pipewriter out; - }; - - template - struct scaler : runnable { +}; + +template +struct scaler: runnable +{ Tscale scale; - scaler(scheduler *sch, Tscale _scale, - pipebuf &_in, pipebuf &_out) - : runnable(sch, "scaler"), - scale(_scale), - in(_in), out(_out) { + + scaler(scheduler *sch, Tscale _scale, pipebuf &_in, pipebuf &_out) : + runnable(sch, "scaler"), + scale(_scale), + in(_in), + out(_out) + { } - void run() { - unsigned long count = min(in.readable(), out.writable()); - Tin *pin=in.rd(), *pend=pin+count; - Tout *pout = out.wr(); - for ( ; pin in; pipewriter out; - }; - - // [awgb_c] generates complex white gaussian noise. - - template - struct wgn_c : runnable { - wgn_c(scheduler *sch, pipebuf< complex > &_out) - : runnable(sch, "awgn"), stddev(1.0), out(_out) { +}; + +// [awgb_c] generates complex white gaussian noise. + +template +struct wgn_c: runnable +{ + wgn_c(scheduler *sch, pipebuf > &_out) : + runnable(sch, "awgn"), + stddev(1.0), + out(_out) + { } - void run() { - int n = out.writable(); - complex *pout=out.wr(), *pend=pout+n; - while ( pout < pend ) { - // TAOCP - float x, y, r2; - do { - x = 2*drand48() - 1; - y = 2*drand48() - 1; - r2 = x*x + y*y; - } while ( r2==0 || r2>=1 ); - float k = sqrtf(-logf(r2)/r2) * stddev; - pout->re = k*x; - pout->im = k*y; - ++pout; - } - out.written(n); + + void run() + { + int n = out.writable(); + complex *pout = out.wr(), *pend = pout + n; + while (pout < pend) + { + // TAOCP + float x, y, r2; + do + { + x = 2 * drand48() - 1; + y = 2 * drand48() - 1; + r2 = x * x + y * y; + } while (r2 == 0 || r2 >= 1); + float k = sqrtf(-logf(r2) / r2) * stddev; + pout->re = k * x; + pout->im = k * y; + ++pout; + } + out.written(n); } + float stddev; - private: - pipewriter< complex > out; - }; - - template - struct naive_lowpass : runnable { - naive_lowpass(scheduler *sch, pipebuf &_in, pipebuf &_out, int _w) - : runnable(sch, "lowpass"), in(_in), out(_out), w(_w) { + +private: + pipewriter > out; +}; + +template +struct naive_lowpass: runnable +{ + naive_lowpass(scheduler *sch, pipebuf &_in, pipebuf &_out, int _w) : + runnable(sch, "lowpass"), + in(_in), + out(_out), + w(_w) + { } - - void run() { - if ( in.readable() < w ) return; - unsigned long count = min(in.readable()-w, out.writable()); - T *pin=in.rd(), *pend=pin+count; - T *pout = out.wr(); - float k = 1.0 / w; - for ( ; pin in; pipewriter out; int w; - }; +}; - template - struct fir_filter : runnable { - fir_filter(scheduler *sch, int _ncoeffs, Tc *_coeffs, - pipebuf &_in, pipebuf &_out, - unsigned int _decim=1) - : runnable(sch, "fir_filter"), - ncoeffs(_ncoeffs), coeffs(_coeffs), - in(_in), out(_out), - decim(_decim), - freq_tap(NULL), tap_multiplier(1), freq_tol(0.1) { - shifted_coeffs = new T[ncoeffs]; - set_freq(0); +template +struct fir_filter: runnable +{ + fir_filter(scheduler *sch, int _ncoeffs, Tc *_coeffs, pipebuf &_in, pipebuf &_out, unsigned int _decim = 1) : + runnable(sch, "fir_filter"), + freq_tap(NULL), + tap_multiplier(1), + freq_tol(0.1), + ncoeffs(_ncoeffs), + coeffs(_coeffs), + in(_in), + out(_out), + decim(_decim) + { + shifted_coeffs = new T[ncoeffs]; + set_freq(0); } - - void run() { - if ( in.readable() < ncoeffs ) return; - if ( freq_tap ) { - float new_freq = *freq_tap * tap_multiplier; - if ( fabs(current_freq-new_freq) > freq_tol ) { - if ( sch->verbose ) - fprintf(stderr, "Shifting filter %f -> %f\n", - current_freq, new_freq); - set_freq(new_freq); - } - } + void run() + { + if (in.readable() < ncoeffs) + return; - unsigned long count = min((in.readable()-ncoeffs)/decim, - out.writable()); - T *pin=in.rd()+ncoeffs, *pend=pin+count*decim, *pout=out.wr(); - // TBD use coeffs when current_freq=0 (fewer mults if float) - for ( ; pin freq_tol) + { + if (sch->verbose) + fprintf(stderr, "Shifting filter %f -> %f\n", current_freq, + new_freq); + set_freq(new_freq); + } + } + + unsigned long count = min((in.readable() - ncoeffs) / decim, + out.writable()); + T *pin = in.rd() + ncoeffs, *pend = pin + count * decim, *pout = + out.wr(); + // TBD use coeffs when current_freq=0 (fewer mults if float) + for (; pin < pend; pin += decim, ++pout) + { + T *pc = shifted_coeffs; + T *pi = pin; + T x = 0; + for (unsigned int i = ncoeffs; i--; ++pc, --pi) + x = x + (*pc) * (*pi); + *pout = x; + } + in.read(count * decim); + out.written(count); } - - private: + +public: + float *freq_tap; + float tap_multiplier; + float freq_tol; + +private: unsigned int ncoeffs; Tc *coeffs; pipereader in; pipewriter out; unsigned int decim; - - T *shifted_coeffs; + T *shifted_coeffs; float current_freq; - void set_freq(float f) { - for ( int i=0; i +struct fir_resampler: runnable +{ + fir_resampler(scheduler *sch, int _ncoeffs, Tc *_coeffs, pipebuf &_in, pipebuf &_out, int _interp = 1, int _decim = 1) : + runnable(sch, "fir_resampler"), + ncoeffs(_ncoeffs), + coeffs(_coeffs), + interp(_interp), + decim(_decim), + in(_in), + out(_out, interp), + freq_tap(NULL), + tap_multiplier(1), + freq_tol(0.1) + { + if (decim != 1) { + fail("fir_resampler::fir_resampler", "decim not implemented"); // TBD + return; + } + shifted_coeffs = new T[ncoeffs]; + set_freq(0); + } + + void run() + { + if (in.readable() < ncoeffs) + return; + + if (freq_tap) + { + float new_freq = *freq_tap * tap_multiplier; + if (fabs(current_freq - new_freq) > freq_tol) + { + if (sch->verbose) + fprintf(stderr, "Shifting filter %f -> %f\n", current_freq, + new_freq); + set_freq(new_freq); + } + } + + if (in.readable() * interp < ncoeffs) + return; + unsigned long count = min((in.readable() * interp - ncoeffs) / interp, + out.writable() / interp); + int latency = (ncoeffs + interp) / interp; + T *pin = in.rd() + latency, *pend = pin + count, *pout = out.wr(); + // TBD use coeffs when current_freq=0 (fewer mults if float) + for (; pin < pend; ++pin) + { + for (int i = 0; i < interp; ++i, ++pout) + { + T *pi = pin; + T *pc = shifted_coeffs + i, *pcend = shifted_coeffs + ncoeffs; + T x = 0; + for (; pc < pcend; pc += interp, --pi) + x = x + (*pc) * (*pi); + *pout = x; + } + } + in.read(count); + out.written(count * interp); + } + +public: float *freq_tap; float tap_multiplier; float freq_tol; - }; // fir_filter - - // FIR FILTER WITH INTERPOLATION AND DECIMATION - - template - struct fir_resampler : runnable { - fir_resampler(scheduler *sch, int _ncoeffs, Tc *_coeffs, - pipebuf &_in, pipebuf &_out, - int _interp=1, int _decim=1) - : runnable(sch, "fir_resampler"), - ncoeffs(_ncoeffs), coeffs(_coeffs), - interp(_interp), decim(_decim), - in(_in), out(_out,interp), - freq_tap(NULL), tap_multiplier(1), freq_tol(0.1) - { - if ( decim != 1 ) fail("fir_resampler: decim not implemented"); // TBD - shifted_coeffs = new T[ncoeffs]; - set_freq(0); - } - - void run() { - if ( in.readable() < ncoeffs ) return; - - if ( freq_tap ) { - float new_freq = *freq_tap * tap_multiplier; - if ( fabs(current_freq-new_freq) > freq_tol ) { - if ( sch->verbose ) - fprintf(stderr, "Shifting filter %f -> %f\n", - current_freq, new_freq); - set_freq(new_freq); - } - } - - if ( in.readable()*interp < ncoeffs ) return; - unsigned long count = min((in.readable()*interp-ncoeffs)/interp, - out.writable()/interp); - int latency = (ncoeffs+interp) / interp; - T *pin=in.rd()+latency, *pend=pin+count, *pout=out.wr(); - // TBD use coeffs when current_freq=0 (fewer mults if float) - for ( ; pin in; pipewriter out; - - public: - float *freq_tap; - float tap_multiplier; - float freq_tol; - - private: - T *shifted_coeffs; + T *shifted_coeffs; float current_freq; - void set_freq(float f) { - for ( int i=0; i * make_dvbs2_constellation(cstln_lut<256>::predef c, - code_rate r) { - float gamma1=1, gamma2=1, gamma3=1; - switch ( c ) { +inline cstln_lut<256> * make_dvbs2_constellation(cstln_lut<256>::predef c, + code_rate r) +{ + float gamma1 = 1, gamma2 = 1, gamma3 = 1; + switch (c) + { case cstln_lut<256>::APSK16: - // EN 302 307, section 5.4.3, Table 9 - switch ( r ) { - case FEC23: - case FEC46: gamma1 = 3.15; break; - case FEC34: gamma1 = 2.85; break; - case FEC45: gamma1 = 2.75; break; - case FEC56: gamma1 = 2.70; break; - case FEC89: gamma1 = 2.60; break; - case FEC910: gamma1 = 2.57; break; - default: fail("Code rate not supported with APSK16"); - } - break; + // EN 302 307, section 5.4.3, Table 9 + switch (r) + { + case FEC23: + case FEC46: + gamma1 = 3.15; + break; + case FEC34: + gamma1 = 2.85; + break; + case FEC45: + gamma1 = 2.75; + break; + case FEC56: + gamma1 = 2.70; + break; + case FEC89: + gamma1 = 2.60; + break; + case FEC910: + gamma1 = 2.57; + break; + default: + fail("cstln_lut<256>::make_dvbs2_constellation", "Code rate not supported with APSK16"); + return 0; + } + break; case cstln_lut<256>::APSK32: - // EN 302 307, section 5.4.4, Table 10 - switch ( r ) { - case FEC34: gamma1 = 2.84; gamma2 = 5.27; break; - case FEC45: gamma1 = 2.72; gamma2 = 4.87; break; - case FEC56: gamma1 = 2.64; gamma2 = 4.64; break; - case FEC89: gamma1 = 2.54; gamma2 = 4.33; break; - case FEC910: gamma1 = 2.53; gamma2 = 4.30; break; - default: fail("Code rate not supported with APSK32"); - } - break; + // EN 302 307, section 5.4.4, Table 10 + switch (r) + { + case FEC34: + gamma1 = 2.84; + gamma2 = 5.27; + break; + case FEC45: + gamma1 = 2.72; + gamma2 = 4.87; + break; + case FEC56: + gamma1 = 2.64; + gamma2 = 4.64; + break; + case FEC89: + gamma1 = 2.54; + gamma2 = 4.33; + break; + case FEC910: + gamma1 = 2.53; + gamma2 = 4.30; + break; + default: + fail("cstln_lut<256>::make_dvbs2_constellation", "Code rate not supported with APSK32"); + return 0; + } + break; case cstln_lut<256>::APSK64E: - // EN 302 307-2, section 5.4.5, Table 13f - gamma1 = 2.4; gamma2 = 4.3; gamma3 = 7; - break; + // EN 302 307-2, section 5.4.5, Table 13f + gamma1 = 2.4; + gamma2 = 4.3; + gamma3 = 7; + break; default: - break; + break; } return new cstln_lut<256>(c, gamma1, gamma2, gamma3); - } +} - // EN 300 421, section 4.4.3, table 2 Punctured code, G1=0171, G2=0133 - static const int DVBS_G1 = 0171; - static const int DVBS_G2 = 0133; +// EN 300 421, section 4.4.3, table 2 Punctured code, G1=0171, G2=0133 +static const int DVBS_G1 = 121; +static const int DVBS_G2 = 91; -// G1 = 0b1111001 +// G1 = 0b1111001 // G2 = 0b1011011 // // G1 = [ 1 1 1 1 0 0 1 ] @@ -98,86 +140,98 @@ namespace leansdr { // IQ = [ Q1; I1; ... Q10; I10 ] = C * S // // D * C == [ 1 0 0 0 0 0 0 0 0 0 0 0 0 0 ] -// +// // D = [ 0 1 0 1 1 1 0 1 1 1 0 0 0 0] -// D = 0x3ba +// D = 0x3ba - template - struct deconvol_sync : runnable { - deconvol_sync(scheduler *sch, - pipebuf &_in, - pipebuf &_out, - uint32_t gX, uint32_t gY, - uint32_t pX, uint32_t pY) - : runnable(sch, "deconvol_sync"), - fastlock(false), - in(_in), out(_out,SIZE_RSPACKET), - skip(0) { - conv = new uint32_t[2]; - conv[0] = gX; - conv[1] = gY; - nG = 2; - punct = new uint32_t[2]; - punct[0] = pX; - punct[1] = pY; - punctperiod = 0; - punctweight = 0; - for ( int i=0; i<2; ++i ) { - int nbits = log2(punct[i]) + 1; - if ( nbits > punctperiod ) punctperiod = nbits; - punctweight += hamming_weight(punct[i]); - } - if ( sch->verbose ) - fprintf(stderr, "puncturing %d/%d\n", punctperiod, punctweight); - deconv = new iq_t[punctperiod]; - deconv2 = new iq_t[punctperiod]; - inverse_convolution(); - init_syncs(); - locked = &syncs[0]; +template +struct deconvol_sync: runnable +{ + deconvol_sync(scheduler *sch, pipebuf &_in, + pipebuf &_out, uint32_t gX, uint32_t gY, uint32_t pX, + uint32_t pY) : + runnable(sch, "deconvol_sync"), fastlock(false), in(_in), out(_out, + SIZE_RSPACKET), skip(0) + { + conv = new uint32_t[2]; + conv[0] = gX; + conv[1] = gY; + nG = 2; + punct = new uint32_t[2]; + punct[0] = pX; + punct[1] = pY; + punctperiod = 0; + punctweight = 0; + for (int i = 0; i < 2; ++i) + { + int nbits = log2(punct[i]) + 1; + if (nbits > punctperiod) + punctperiod = nbits; + punctweight += hamming_weight(punct[i]); + } + if (sch->verbose) + fprintf(stderr, "puncturing %d/%d\n", punctperiod, punctweight); + deconv = new iq_t[punctperiod]; + deconv2 = new iq_t[punctperiod]; + inverse_convolution(); + init_syncs(); + locked = &syncs[0]; } typedef uint64_t signal_t; typedef uint64_t iq_t; - static int log2(uint64_t x) { - int n = -1; - for ( ; x; ++n,x>>=1 ) ; - return n; + static int log2(uint64_t x) + { + int n = -1; + for (; x; ++n, x >>= 1) + ; + return n; } - iq_t convolve(signal_t s) { - int sbits = log2(s) + 1; - iq_t iq = 0; - unsigned char state = 0; - for ( int b=sbits-1; b>=0; --b ) { // Feed into convolver, MSB first - unsigned char bit = (s>>b) & 1; - state = (state>>1) | (bit<<6); // Shift register - for ( int j=0; j= 0; --b) + { // Feed into convolver, MSB first + unsigned char bit = (s >> b) & 1; + state = (state >> 1) | (bit << 6); // Shift register + for (int j = 0; j < nG; ++j) + { + unsigned char xy = parity(state & conv[j]); // Taps + if (punct[j] & (1 << (b % punctperiod))) + iq = (iq << 1) | xy; + } + } + return iq; } - - void run() { - run_decoding(); + + void run() + { + run_decoding(); } - - void next_sync() { - if ( fastlock ) fail("Bug: next_sync() called with fastlock"); - ++locked; - if ( locked == &syncs[NSYNCS] ) { - locked = &syncs[0]; - // Try next symbol alignment (for FEC other than 1/2) - skip = 1; - } + + void next_sync() + { + if (fastlock) + { + fail("deconvol_sync::next_sync", "Bug: next_sync() called with fastlock"); + return; + } + ++locked; + if (locked == &syncs[NSYNCS]) + { + locked = &syncs[0]; + // Try next symbol alignment (for FEC other than 1/2) + skip = 1; + } } bool fastlock; - private: +private: static const int maxsbits = 64; iq_t response[maxsbits]; @@ -185,161 +239,217 @@ namespace leansdr { //static const int traceback = 48; // For code rate 7/8 static const int traceback = 64; // For code rate 7/8 with fastlock - void solve_rec(iq_t prefix, int nprefix, signal_t exp, iq_t *best) { - if ( prefix > *best ) return; - if ( nprefix > sizeof(prefix)*8 ) return; - int solved = 1; - for ( int b=0; b>b)&1) ) { - // Current candidate does not solve this column. - if ( (response[b]>>nprefix) == 0 ) - // No more bits to trace back. - return; - solved = 0; - } - } - if ( solved ) { *best = prefix; return; } - solve_rec(prefix, nprefix+1, exp, best); - solve_rec(prefix|((iq_t)1< *best) + return; + if (nprefix > sizeof(prefix) * 8) + return; + int solved = 1; + for (int b = 0; b < maxsbits; ++b) + { + if (parity(prefix & response[b]) != ((exp >> b) & 1)) + { + // Current candidate does not solve this column. + if ((response[b] >> nprefix) == 0) + // No more bits to trace back. + return; + solved = 0; + } + } + if (solved) + { + *best = prefix; + return; + } + solve_rec(prefix, nprefix + 1, exp, best); + solve_rec(prefix | ((iq_t) 1 << nprefix), nprefix + 1, exp, best); } static const int LATENCY = 0; - void inverse_convolution() { - for ( int sbit=0; sbitdebug ) { - for ( int b=0; b sizeof(iq_t)*8 ) - fail("Bug: traceback exceeds register size"); - if ( log2(deconv[b])+1 > traceback ) - fail("traceback insufficient for deconvolution"); - if ( log2(deconv2[b])+1 > traceback ) - fail("traceback insufficient for deconvolution (alt)"); - } + if (sch->debug) + { + for (int b = 0; b < punctperiod; ++b) + fprintf(stderr, "deconv[%d]=0x%016llx %d taps / %d bits\n", b, + (unsigned long long) deconv[b], + hamming_weight(deconv[b]), log2(deconv[b]) + 1); + } + + // Sanity check + for (int b = 0; b < punctperiod; ++b) + { + for (int i = 0; i < maxsbits; ++i) + { + iq_t iq = convolve((iq_t) 1 << (LATENCY + i)); + int expect = (b == i) ? 1 : 0; + int d = parity(iq & deconv[b]); + if (d != expect) + { + fail("deconvol_sync::inverse_convolution", "Failed to inverse convolutional coding"); + } + int d2 = parity(iq & deconv2[b]); + if (d2 != expect) + { + fail("deconvol_sync::inverse_convolution", "Failed to inverse convolutional coding (alt)"); + } + } + if (traceback > sizeof(iq_t) * 8) + { + fail("deconvol_sync::inverse_convolution", "Bug: traceback exceeds register size"); + } + if (log2(deconv[b]) + 1 > traceback) + { + fail("deconvol_sync::inverse_convolution", "traceback insufficient for deconvolution"); + } + if (log2(deconv2[b]) + 1 > traceback) + { + fail("deconvol_sync::inverse_convolution", "traceback insufficient for deconvolution (alt)"); + } + } } static const int NSYNCS = 4; - struct sync_t { - u8 lut[2][2]; // lut[(re>0)?1:0][(im>0)?1:0] = 0b000000IQ - iq_t in; - int n_in; - signal_t out; - int n_out; - // Auxiliary shift register for fastlock - iq_t in2; - int n_in2, n_out2; + struct sync_t + { + u8 lut[2][2]; // lut[(re>0)?1:0][(im>0)?1:0] = 0b000000IQ + iq_t in; + int n_in; + signal_t out; + int n_out; + // Auxiliary shift register for fastlock + iq_t in2; + int n_in2, n_out2; } syncs[NSYNCS]; - void init_syncs() { - // EN 300 421, section 4.5, Figure 5 QPSK constellation - // Four rotations * two conjugations. - // 180° rotation is detected as polarity inversion in mpeg_sync. - for ( int sync_id=0; sync_id 5 bytes // 7/8 32 symbols -> 7 bytes - inline Tbyte readbyte(sync_t *s, softsymbol *&p) { - while ( s->n_out < 8 ) { - iq_t iq = s->in; - while ( s->n_in < traceback ) { - u8 iqbits = s->lut[(p->symbol&2)?1:0][p->symbol&1]; - ++p; - iq = (iq<<2) | iqbits; - s->n_in += 2; - } - s->in = iq; - for ( int b=punctperiod-1; b>=0; --b ) { - u8 bit = parity(iq&deconv[b]); - s->out = (s->out<<1) | bit; - } - s->n_out += punctperiod; - s->n_in -= punctweight; - } - Tbyte res = (s->out >> (s->n_out-8)) & 255; - s->n_out -= 8; - return res; + inline Tbyte readbyte(sync_t *s, softsymbol *&p) + { + while (s->n_out < 8) + { + iq_t iq = s->in; + while (s->n_in < traceback) + { + u8 iqbits = s->lut[(p->symbol & 2) ? 1 : 0][p->symbol & 1]; + ++p; + iq = (iq << 2) | iqbits; + s->n_in += 2; + } + s->in = iq; + for (int b = punctperiod - 1; b >= 0; --b) + { + u8 bit = parity(iq & deconv[b]); + s->out = (s->out << 1) | bit; + } + s->n_out += punctperiod; + s->n_in -= punctweight; + } + Tbyte res = (s->out >> (s->n_out - 8)) & 255; + s->n_out -= 8; + return res; } - inline unsigned long readerrors(sync_t *s, softsymbol *&p) { - unsigned long res = 0; - while ( s->n_out2 < 8 ) { - iq_t iq = s->in2; - while ( s->n_in2 < traceback ) { - u8 iqbits = s->lut[(p->symbol&2)?1:0][p->symbol&1]; - ++p; - iq = (iq<<2) | iqbits; - s->n_in2 += 2; - } - s->in2 = iq; - for ( int b=punctperiod-1; b>=0; --b ) { - u8 bit = parity(iq&deconv[b]); - u8 bit2 = parity(iq&deconv2[b]); - if ( bit2 != bit ) ++res; - } - s->n_out2 += punctperiod; - s->n_in2 -= punctweight; - } - s->n_out2 -= 8; - return res; + inline unsigned long readerrors(sync_t *s, softsymbol *&p) + { + unsigned long res = 0; + while (s->n_out2 < 8) + { + iq_t iq = s->in2; + while (s->n_in2 < traceback) + { + u8 iqbits = s->lut[(p->symbol & 2) ? 1 : 0][p->symbol & 1]; + ++p; + iq = (iq << 2) | iqbits; + s->n_in2 += 2; + } + s->in2 = iq; + for (int b = punctperiod - 1; b >= 0; --b) + { + u8 bit = parity(iq & deconv[b]); + u8 bit2 = parity(iq & deconv2[b]); + if (bit2 != bit) + ++res; + } + s->n_out2 += punctperiod; + s->n_in2 -= punctweight; + } + s->n_out2 -= 8; + return res; } - void run_decoding() { - in.read(skip); - skip = 0; + void run_decoding() + { + in.read(skip); + skip = 0; - // 8 byte margin to fill the deconvolver - if ( in.readable() < 64 ) return; - int maxrd = (in.readable()-64) / (punctweight/2) * punctperiod / 8; - int maxwr = out.writable(); - int n = (maxrddebug ) - fprintf(stderr, "{%d->%d}\n", - (int)(locked-syncs), (int)(best-syncs)); - locked = best; - } - // If deconvolution bit error rate > 33%, try next sample alignment - if ( errors_best > n*8/3 ) { - // fprintf(stderr, ">"); - skip = 1; - } - } + // 8 byte margin to fill the deconvolver + if (in.readable() < 64) + return; + int maxrd = (in.readable() - 64) / (punctweight / 2) * punctperiod / 8; + int maxwr = out.writable(); + unsigned int n = (maxrd < maxwr) ? maxrd : maxwr; + if (!n) + return; + // Require enough symbols to discriminate in fastlock mode + // (threshold must be less than size of rspacket) + if (n < 32) + return; - softsymbol *pin=in.rd(), *pin0=pin; - Tbyte *pout=out.wr(), *pout0=pout; - while ( n-- ) - *pout++ = readbyte(locked, pin); - in.read(pin-pin0); - out.written(pout-pout0); - } + if (fastlock) + { + // Try all sync alignments + unsigned long errors_best = 1 << 30; + sync_t *best = &syncs[0]; + for (sync_t *s = syncs; s < syncs + NSYNCS; ++s) + { + softsymbol *pin = in.rd(); + unsigned long errors = 0; + for (int c = n; c--;) + errors += readerrors(s, pin); + if (errors < errors_best) + { + errors_best = errors; + best = s; + } + } + if (best != locked) + { + // Another alignment produces fewer bit errors + if (sch->debug) + fprintf(stderr, "{%d->%d}\n", (int) (locked - syncs), + (int) (best - syncs)); + locked = best; + } + // If deconvolution bit error rate > 33%, try next sample alignment + if (errors_best > n * 8 / 3) + { + // fprintf(stderr, ">"); + skip = 1; + } + } + + softsymbol *pin = in.rd(), *pin0 = pin; + Tbyte *pout = out.wr(), *pout0 = pout; + while (n--) + *pout++ = readbyte(locked, pin); + in.read(pin - pin0); + out.written(pout - pout0); + } pipereader in; pipewriter out; @@ -456,410 +584,485 @@ namespace leansdr { sync_t *locked; int skip; - }; +}; - typedef deconvol_sync deconvol_sync_simple; +typedef deconvol_sync deconvol_sync_simple; - inline deconvol_sync_simple *make_deconvol_sync_simple(scheduler *sch, - pipebuf &_in, - pipebuf &_out, - enum code_rate rate) { +inline deconvol_sync_simple *make_deconvol_sync_simple(scheduler *sch, + pipebuf &_in, pipebuf &_out, enum code_rate rate) +{ // EN 300 421, section 4.4.3 Inner coding uint32_t pX, pY; - switch ( rate ) { + switch (rate) + { case FEC12: - pX = 0x1; // 1 - pY = 0x1; // 1 - break; + pX = 0x1; // 1 + pY = 0x1; // 1 + break; case FEC23: case FEC46: - pX = 0xa; // 1010 (Handle as FEC4/6, no half-symbols) - pY = 0xf; // 1111 - break; + pX = 0xa; // 1010 (Handle as FEC4/6, no half-symbols) + pY = 0xf; // 1111 + break; case FEC34: - pX = 0x5; // 101 - pY = 0x6; // 110 - break; + pX = 0x5; // 101 + pY = 0x6; // 110 + break; case FEC56: - pX = 0x15; // 10101 - pY = 0x1a; // 11010 - break; + pX = 0x15; // 10101 + pY = 0x1a; // 11010 + break; case FEC78: - pX = 0x45; // 1000101 - pY = 0x7a; // 1111010 - break; + pX = 0x45; // 1000101 + pY = 0x7a; // 1111010 + break; default: - //fail("Code rate not implemented"); - // For testing DVB-S2 constellations. - fprintf(stderr, "Code rate not implemented; proceeding anyway\n"); - pX = pY = 1; + //fail("Code rate not implemented"); + // For testing DVB-S2 constellations. + fprintf(stderr, "Code rate not implemented; proceeding anyway\n"); + pX = pY = 1; } return new deconvol_sync_simple(sch, _in, _out, DVBS_G1, DVBS_G2, pX, pY); - } +} +// CONVOLUTIONAL ENCODER - // CONVOLUTIONAL ENCODER +static const uint16_t polys_fec12[] = +{ DVBS_G1, DVBS_G2 // X1Y1 + }; +static const uint16_t polys_fec23[] = +{ DVBS_G1, DVBS_G2, DVBS_G2 << 1 // X1Y1Y2 +}; +// Same code rate as 2/3, usable with QPSK +static const uint16_t polys_fec46[] = +{ DVBS_G1, DVBS_G2, DVBS_G2 << 1, // X1Y1Y2 +DVBS_G1 << 2, DVBS_G2 << 2, DVBS_G2 << 3 // X3Y3Y4 +}; +static const uint16_t polys_fec34[] = +{ DVBS_G1, DVBS_G2, // X1Y1 + DVBS_G2 << 1, DVBS_G1 << 2 // Y2X3 +}; +static const uint16_t polys_fec45[] = +{ // Non standard + DVBS_G1, DVBS_G2, // X1Y1 + DVBS_G2 << 1, DVBS_G1 << 2, // Y2X3 + DVBS_G1 << 3 // X4 + }; +static const uint16_t polys_fec56[] = +{ DVBS_G1, DVBS_G2, // X1Y1 + DVBS_G2 << 1, DVBS_G1 << 2, // Y2X3 + DVBS_G2 << 3, DVBS_G1 << 4 // Y4X5 +}; +static const uint16_t polys_fec78[] = +{ DVBS_G1, DVBS_G2, // X1Y1 + DVBS_G2 << 1, DVBS_G2 << 2, // Y2Y3 + DVBS_G2 << 3, DVBS_G1 << 4, // Y4X5 + DVBS_G2 << 5, DVBS_G1 << 6 // Y6X7 +}; - static const uint16_t polys_fec12[] = { - DVBS_G1, DVBS_G2 // X1Y1 - }; - static const uint16_t polys_fec23[] = { - DVBS_G1, DVBS_G2, DVBS_G2<<1 // X1Y1Y2 - }; - // Same code rate as 2/3, usable with QPSK - static const uint16_t polys_fec46[] = { - DVBS_G1, DVBS_G2, DVBS_G2<<1, // X1Y1Y2 - DVBS_G1<<2, DVBS_G2<<2, DVBS_G2<<3 // X3Y3Y4 - }; - static const uint16_t polys_fec34[] = { - DVBS_G1, DVBS_G2, // X1Y1 - DVBS_G2<<1, DVBS_G1<<2 // Y2X3 - }; - static const uint16_t polys_fec45[] = { // Non standard - DVBS_G1, DVBS_G2, // X1Y1 - DVBS_G2<<1, DVBS_G1<<2, // Y2X3 - DVBS_G1<<3 // X4 - }; - static const uint16_t polys_fec56[] = { - DVBS_G1, DVBS_G2, // X1Y1 - DVBS_G2<<1, DVBS_G1<<2, // Y2X3 - DVBS_G2<<3, DVBS_G1<<4 // Y4X5 - }; - static const uint16_t polys_fec78[] = { - DVBS_G1, DVBS_G2, // X1Y1 - DVBS_G2<<1, DVBS_G2<<2, // Y2Y3 - DVBS_G2<<3, DVBS_G1<<4, // Y4X5 - DVBS_G2<<5, DVBS_G1<<6 // Y6X7 - }; - - // FEC parameters, for convolutional coding only (not S2). - static struct fec_spec { +// FEC parameters, for convolutional coding only (not S2). +static struct fec_spec +{ int bits_in; // Entering the convolutional coder int bits_out; // Exiting the convolutional coder const uint16_t *polys; // [bits_out] - } fec_specs[FEC_MAX] = { - [FEC12] = { 1, 2, polys_fec12 }, - [FEC23] = { 2, 3, polys_fec23 }, - [FEC46] = { 4, 6, polys_fec46 }, - [FEC34] = { 3, 4, polys_fec34 }, - [FEC56] = { 5, 6, polys_fec56 }, - [FEC78] = { 7, 8, polys_fec78 }, - [FEC45] = { 4, 5, polys_fec45 }, // Non-standard - }; +} fec_specs[FEC_MAX] = +{ [FEC12] = +{ 1, 2, polys_fec12 }, [FEC23] = +{ 2, 3, polys_fec23 }, [FEC46] = +{ 4, 6, polys_fec46 }, [FEC34] = +{ 3, 4, polys_fec34 }, [FEC56] = +{ 5, 6, polys_fec56 }, [FEC78] = +{ 7, 8, polys_fec78 }, [FEC45] = +{ 4, 5, polys_fec45 }, // Non-standard + }; - struct dvb_convol : runnable { +struct dvb_convol: runnable +{ typedef u8 uncoded_byte; typedef u8 hardsymbol; - dvb_convol(scheduler *sch, - pipebuf &_in, - pipebuf &_out, - code_rate fec, - int bits_per_symbol) - : runnable(sch, "dvb_convol"), - in(_in), out(_out,64) // BPSK 7/8: 7 bytes in, 64 symbols out + dvb_convol(scheduler *sch, pipebuf &_in, + pipebuf &_out, code_rate fec, int bits_per_symbol) : + runnable(sch, "dvb_convol"), in(_in), out(_out, 64) // BPSK 7/8: 7 bytes in, 64 symbols out { - fec_spec *fs = &fec_specs[fec]; - if ( ! fs->bits_in ) fail("Unexpected FEC"); - convol.bits_in = fs->bits_in; - convol.bits_out = fs->bits_out; - convol.polys = fs->polys; - convol.bps = bits_per_symbol; - // FEC must output a whole number of IQ symbols - if ( convol.bits_out % convol.bps ) - fail("Code rate not suitable for this constellation"); + fec_spec *fs = &fec_specs[fec]; + if (!fs->bits_in) + { + fail("dvb_convol::dvb_convol", "Unexpected FEC"); + return; + } + convol.bits_in = fs->bits_in; + convol.bits_out = fs->bits_out; + convol.polys = fs->polys; + convol.bps = bits_per_symbol; + // FEC must output a whole number of IQ symbols + if (convol.bits_out % convol.bps) + { + fail("dvb_convol::dvb_convol", "Code rate not suitable for this constellation"); + return; + } } - void run() { - int count = min(in.readable(), out.writable()*convol.bps/ - convol.bits_out*convol.bits_in/8); - // Process in multiples of the puncturing period and of 8 bits. - int chunk = convol.bits_in; - count = (count/chunk) * chunk; - convol.encode(in.rd(), out.wr(), count); - in.read(count); - int nout = count*8/convol.bits_in*convol.bits_out/convol.bps; - out.written(nout); + void run() + { + int count = min(in.readable(), + out.writable() * convol.bps / convol.bits_out * convol.bits_in + / 8); + // Process in multiples of the puncturing period and of 8 bits. + int chunk = convol.bits_in; + count = (count / chunk) * chunk; + convol.encode(in.rd(), out.wr(), count); + in.read(count); + int nout = count * 8 / convol.bits_in * convol.bits_out / convol.bps; + out.written(nout); } - private: +private: pipereader in; pipewriter out; convol_multipoly convol; - }; // dvb_convol +}; +// dvb_convol +// NEW ALGEBRAIC DECONVOLUTION - // NEW ALGEBRAIC DECONVOLUTION +// QPSK 1/2 only; +// With DVB-S polynomials hardcoded. - // QPSK 1/2 only; - // With DVB-S polynomials hardcoded. - - template - struct dvb_deconvol_sync : runnable { +template +struct dvb_deconvol_sync: runnable +{ typedef u8 decoded_byte; int resync_period; static const int chunk_size = 64; // At least 2*sizeof(Thist)/8 - dvb_deconvol_sync(scheduler *sch, - pipebuf &_in, - pipebuf &_out) - : runnable(sch, "deconvol_sync_multipoly"), - resync_period(32), - in(_in), out(_out,chunk_size), - resync_phase(0) + dvb_deconvol_sync(scheduler *sch, pipebuf &_in, + pipebuf &_out) : + runnable(sch, "deconvol_sync_multipoly"), resync_period(32), in( + _in), out(_out, chunk_size), resync_phase(0) { - init_syncs(); - locked = &syncs[0]; + init_syncs(); + locked = &syncs[0]; } - void run() { + void run() + { - while ( in.readable() >= chunk_size*8 && - out.writable() >= chunk_size ) { - int errors_best = 1 << 30; - sync_t *best = NULL; - for ( sync_t *s=syncs; sdeconv.run(pin, s->lut, pout, chunk_size); - if ( nerrors < errors_best ) { errors_best=nerrors; best=s; } - } - in.read(chunk_size*8); - out.written(chunk_size); - if ( best != locked ) { - if ( sch->debug ) fprintf(stderr, "%%%d", (int)(best-syncs)); - locked = best; - } - if ( ++resync_phase >= resync_period ) resync_phase = 0; - } // Work to do + while (in.readable() >= chunk_size * 8 && out.writable() >= chunk_size) + { + int errors_best = 1 << 30; + sync_t *best = NULL; + for (sync_t *s = syncs; s < syncs + NSYNCS; ++s) + { + if (resync_phase != 0 && s != locked) + // Decode only the currently-locked alignment + continue; + Tin *pin = in.rd(); + static decoded_byte dummy[chunk_size]; + decoded_byte *pout = (s == locked) ? out.wr() : dummy; + int nerrors = s->deconv.run(pin, s->lut, pout, chunk_size); + if (nerrors < errors_best) + { + errors_best = nerrors; + best = s; + } + } + in.read(chunk_size * 8); + out.written(chunk_size); + if (best != locked) + { + if (sch->debug) + fprintf(stderr, "%%%d", (int) (best - syncs)); + locked = best; + } + if (++resync_phase >= resync_period) + resync_phase = 0; + } // Work to do } // run() - private: +private: pipereader in; pipewriter out; int resync_phase; - + static const int NSYNCS = 4; - struct sync_t { - deconvol_poly2 deconv; - u8 lut[4]; // TBD Swap and flip bits in the polynomials instead. + struct sync_t + { + deconvol_poly2 deconv; + u8 lut[4]; // TBD Swap and flip bits in the polynomials instead. } syncs[NSYNCS]; sync_t *locked; - void init_syncs() { - for ( int s=0; s dvb_deconvol_sync_soft; +typedef dvb_deconvol_sync dvb_deconvol_sync_hard; - typedef dvb_deconvol_sync dvb_deconvol_sync_soft; - typedef dvb_deconvol_sync dvb_deconvol_sync_hard; +// BIT ALIGNMENT AND MPEG SYNC DETECTION - - // BIT ALIGNMENT AND MPEG SYNC DETECTION - - template - struct mpeg_sync : runnable { +template +struct mpeg_sync: runnable +{ int scan_syncs, want_syncs; unsigned long lock_timeout; bool fastlock; int resync_period; - mpeg_sync(scheduler *sch, - pipebuf &_in, - pipebuf &_out, - deconvol_sync *_deconv, - pipebuf *_state_out=NULL, - pipebuf *_locktime_out=NULL) - : runnable(sch, "sync_detect"), - scan_syncs(8), want_syncs(4), - lock_timeout(4), - fastlock(false), - resync_period(1), - in(_in), out(_out, SIZE_RSPACKET*(scan_syncs+1)), - deconv(_deconv), - polarity(0), - resync_phase(0), - bitphase(0), synchronized(false), - next_sync_count(0), - report_state(true) { - state_out = _state_out ? new pipewriter(*_state_out) : NULL; - locktime_out = - _locktime_out ? new pipewriter(*_locktime_out) : NULL; - } - - void run() { - if ( report_state && state_out && state_out->writable()>=1 ) { - // Report unlocked state on first invocation. - state_out->write(0); - report_state = false; - } - if ( synchronized ) - run_decoding(); - else { - if ( fastlock ) run_searching_fast(); else run_searching(); - } + mpeg_sync( + scheduler *sch, + pipebuf &_in, + pipebuf &_out, + deconvol_sync *_deconv, + pipebuf *_state_out = NULL, + pipebuf *_locktime_out = NULL) : + runnable(sch, "sync_detect"), + scan_syncs(8), + want_syncs(4), + lock_timeout(4), + fastlock(false), + resync_period(1), + in(_in), + out(_out, SIZE_RSPACKET * (scan_syncs + 1)), + deconv(_deconv), + polarity(0), + resync_phase(0), + bitphase(0), + synchronized(false), + next_sync_count(0), + phase8(-1), + lock_timeleft(0), + locktime(0), + report_state(true) + { + state_out = _state_out ? new pipewriter(*_state_out) : NULL; + locktime_out = locktime_out ? new pipewriter(*_locktime_out) : NULL; } - void run_searching() { - bool next_sync = false; - int chunk = SIZE_RSPACKET * scan_syncs; - while ( in.readable() >= chunk+1 && // Need 1 ahead for bit shifting - out.writable() >= chunk && // Use as temp buffer - ( !state_out || state_out->writable()>=1 ) ) { - if ( search_sync() ) return; - in.read(chunk); - // Switch to next bit alignment - ++bitphase; - if ( bitphase == 8 ) { - bitphase = 0; - next_sync = true; - } - } - - if ( next_sync ) { - // No lock this time - ++next_sync_count; - if ( next_sync_count >= 3 ) { - // After a few cycles without a lock, resync the deconvolver. - next_sync_count = 0; - if ( deconv ) deconv->next_sync(); - } - } + void run() + { + if (report_state && state_out && state_out->writable() >= 1) + { + // Report unlocked state on first invocation. + state_out->write(0); + report_state = false; + } + if (synchronized) + run_decoding(); + else + { + if (fastlock) + run_searching_fast(); + else + run_searching(); + } } - void run_searching_fast() { - int chunk = SIZE_RSPACKET * scan_syncs; - while ( in.readable() >= chunk+1 && // Need 1 ahead for bit shifting - out.writable() >= chunk && // Use as temp buffer - ( !state_out || state_out->writable()>=1 ) ) { - if ( resync_phase == 0 ) { - // Try all bit alighments - for ( bitphase=0; bitphase<=7; ++bitphase ) { - if ( search_sync() ) return; - } - } - in.read(SIZE_RSPACKET); - if ( ++resync_phase >= resync_period ) resync_phase = 0; - } + void run_searching() + { + bool next_sync = false; + unsigned int chunk = SIZE_RSPACKET * scan_syncs; + + while (in.readable() >= chunk + 1 && // Need 1 ahead for bit shifting + out.writable() >= chunk && // Use as temp buffer + (!state_out || state_out->writable() >= 1)) + { + if (search_sync()) + return; + in.read(chunk); + // Switch to next bit alignment + ++bitphase; + if (bitphase == 8) + { + bitphase = 0; + next_sync = true; + } + } + + if (next_sync) + { + // No lock this time + ++next_sync_count; + if (next_sync_count >= 3) + { + // After a few cycles without a lock, resync the deconvolver. + next_sync_count = 0; + if (deconv) + deconv->next_sync(); + } + } } - bool search_sync() { - int chunk = SIZE_RSPACKET * scan_syncs; - // Bit-shift [scan_sync] packets according to current [bitphase] - Tbyte *pin = in.rd(), *pend = pin+chunk; - Tbyte *pout = out.wr(); - unsigned short w = *pin++; - for ( ; pin<=pend; ++pin,++pout ) { - w = (w<<8) | *pin; - *pout = w >> bitphase; - } - // Search for [want_sync] start codes at all 204 offsets - for ( int i=0; i nsyncs_n) - { polarity=0; nsyncs=nsyncs_p; phase8=phase8_p; } - else - { polarity=-1; nsyncs=nsyncs_n; phase8=phase8_n; } - if ( nsyncs>=want_syncs && phase8>=0 ) { - if ( sch->debug ) fprintf(stderr, "Locked\n"); - if ( ! i ) { // Avoid fixpoint detection in scheduler - i = SIZE_RSPACKET; - phase8 = (phase8+1) & 7; - } - in.read(i); // Skip to first start code - synchronized = true; - lock_timeleft = lock_timeout; - locktime = 0; - if ( state_out ) - state_out->write(1); - return true; - } - } - return false; + void run_searching_fast() + { + unsigned int chunk = SIZE_RSPACKET * scan_syncs; + + while (in.readable() >= chunk + 1 && // Need 1 ahead for bit shifting + out.writable() >= chunk && // Use as temp buffer + (!state_out || state_out->writable() >= 1)) + { + if (resync_phase == 0) + { + // Try all bit alighments + for (bitphase = 0; bitphase <= 7; ++bitphase) + { + if (search_sync()) + return; + } + } + in.read(SIZE_RSPACKET); + if (++resync_phase >= resync_period) + resync_phase = 0; + } } - void run_decoding() { - while ( in.readable() >= SIZE_RSPACKET+1 && // +1 for bit shifting - out.writable() >= SIZE_RSPACKET && - ( !state_out || state_out->writable()>=1 ) && - ( !locktime_out || locktime_out->writable()>=1 ) ) { - Tbyte *pin = in.rd(), *pend = pin+SIZE_RSPACKET; - Tbyte *pout = out.wr(); - unsigned short w = *pin++; - for ( ; pin<=pend; ++pin,++pout ) { - w = (w<<8) | *pin; - *pout = (w >> bitphase) ^ polarity; - } - in.read(SIZE_RSPACKET); - Tbyte syncbyte = *out.wr(); - out.written(SIZE_RSPACKET); - ++locktime; - if ( locktime_out ) - locktime_out->write(locktime); - // Reset timer if sync byte is correct - Tbyte expected = phase8 ? MPEG_SYNC : MPEG_SYNC_INV; - if ( syncbyte == expected ) lock_timeleft = lock_timeout; - phase8 = (phase8+1) & 7; - --lock_timeleft; - if ( ! lock_timeleft ) { - if ( sch->debug ) fprintf(stderr, "Unlocked\n"); - synchronized = false; - next_sync_count = 0; - if ( state_out ) - state_out->write(0); - return; - } - } + bool search_sync() + { + int chunk = SIZE_RSPACKET * scan_syncs; + // Bit-shift [scan_sync] packets according to current [bitphase] + Tbyte *pin = in.rd(), *pend = pin + chunk; + Tbyte *pout = out.wr(); + unsigned short w = *pin++; + for (; pin <= pend; ++pin, ++pout) + { + w = (w << 8) | *pin; + *pout = w >> bitphase; + } + // Search for [want_sync] start codes at all 204 offsets + for (int i = 0; i < SIZE_RSPACKET; ++i) + { + int nsyncs_p = 0, nsyncs_n = 0; // # start codes assuming pos/neg polarity + int phase8_p = -1, phase8_n = -1; // Position in sequence of 8 packets + Tbyte *p = &out.wr()[i]; + for (int j = 0; j < scan_syncs; ++j, p += SIZE_RSPACKET) + { + Tbyte b = *p; + if (b == MPEG_SYNC) + { + ++nsyncs_p; + phase8_n = (8 - j) & 7; + } + if (b == MPEG_SYNC_INV) + { + ++nsyncs_n; + phase8_p = (8 - j) & 7; + } + } + // Detect most likely polarity + int nsyncs; + if (nsyncs_p > nsyncs_n) + { + polarity = 0; + nsyncs = nsyncs_p; + phase8 = phase8_p; + } + else + { + polarity = -1; + nsyncs = nsyncs_n; + phase8 = phase8_n; + } + if (nsyncs >= want_syncs && phase8 >= 0) + { + if (sch->debug) + fprintf(stderr, "Locked\n"); + if (!i) + { // Avoid fixpoint detection in scheduler + i = SIZE_RSPACKET; + phase8 = (phase8 + 1) & 7; + } + in.read(i); // Skip to first start code + synchronized = true; + lock_timeleft = lock_timeout; + locktime = 0; + if (state_out) + state_out->write(1); + return true; + } + } + return false; } - private: + void run_decoding() + { + while (in.readable() >= SIZE_RSPACKET + 1 + && // +1 for bit shifting + out.writable() >= SIZE_RSPACKET + && (!state_out || state_out->writable() >= 1) + && (!locktime_out || locktime_out->writable() >= 1)) + { + Tbyte *pin = in.rd(), *pend = pin + SIZE_RSPACKET; + Tbyte *pout = out.wr(); + unsigned short w = *pin++; + for (; pin <= pend; ++pin, ++pout) + { + w = (w << 8) | *pin; + *pout = (w >> bitphase) ^ polarity; + } + in.read(SIZE_RSPACKET); + Tbyte syncbyte = *out.wr(); + out.written(SIZE_RSPACKET); + ++locktime; + if (locktime_out) + locktime_out->write(locktime); + // Reset timer if sync byte is correct + Tbyte expected = phase8 ? MPEG_SYNC : MPEG_SYNC_INV; + if (syncbyte == expected) + lock_timeleft = lock_timeout; + phase8 = (phase8 + 1) & 7; + --lock_timeleft; + if (!lock_timeleft) + { + if (sch->debug) + fprintf(stderr, "Unlocked\n"); + synchronized = false; + next_sync_count = 0; + if (state_out) + state_out->write(0); + return; + } + } + } + +private: pipereader in; pipewriter out; - deconvol_sync *deconv; + deconvol_sync *deconv; unsigned char polarity; // XOR mask, 0 or 0xff int resync_phase; int bitphase; @@ -871,330 +1074,379 @@ namespace leansdr { pipewriter *state_out; pipewriter *locktime_out; bool report_state; - }; +}; +template +struct rspacket +{ + Tbyte data[SIZE_RSPACKET]; +}; - template - struct rspacket { Tbyte data[SIZE_RSPACKET]; }; +// INTERLEAVER - - // INTERLEAVER - - struct interleaver : runnable { - interleaver(scheduler *sch, pipebuf< rspacket > &_in, - pipebuf< u8 > &_out) - : runnable(sch, "interleaver"), - in(_in), out(_out,SIZE_RSPACKET) { +struct interleaver: runnable +{ + interleaver(scheduler *sch, pipebuf > &_in, pipebuf &_out) : + runnable(sch, "interleaver"), in(_in), out(_out, SIZE_RSPACKET) + { } - void run() { - while ( in.readable() >= 12 && - out.writable() >= SIZE_RSPACKET ) { - rspacket *pin=in.rd(); - u8 *pout = out.wr(); - int delay = 0; - for ( int i=0; i= 12 && out.writable() >= SIZE_RSPACKET) + { + rspacket *pin = in.rd(); + u8 *pout = out.wr(); + int delay = 0; + for (int i = 0; i < SIZE_RSPACKET; + ++i, ++pout, delay = (delay + 1) % 12) + *pout = pin[11 - delay].data[i]; + in.read(1); + out.written(SIZE_RSPACKET); + } } - private: - pipereader< rspacket > in; +private: + pipereader > in; pipewriter out; - }; // interleaver +}; +// interleaver +// DEINTERLEAVER - // DEINTERLEAVER - - template - struct deinterleaver : runnable { +template +struct deinterleaver: runnable +{ deinterleaver(scheduler *sch, pipebuf &_in, - pipebuf< rspacket > &_out) - : runnable(sch, "deinterleaver"), - in(_in), out(_out) { + pipebuf > &_out) : + runnable(sch, "deinterleaver"), in(_in), out(_out) + { } - void run() { - while ( in.readable() >= 17*11*12+SIZE_RSPACKET && - out.writable() >= 1 ) { - Tbyte *pin = in.rd()+17*11*12, *pend=pin+SIZE_RSPACKET; - Tbyte *pout= out.wr()->data; - for ( int delay=17*11; pin= 17 * 11 * 12 + SIZE_RSPACKET + && out.writable() >= 1) + { + Tbyte *pin = in.rd() + 17 * 11 * 12, *pend = pin + SIZE_RSPACKET; + Tbyte *pout = out.wr()->data; + for (int delay = 17 * 11; pin < pend; + ++pin, ++pout, delay = (delay - 17 + 17 * 12) % (17 * 12)) + *pout = pin[-delay * 12]; + in.read(SIZE_RSPACKET); + out.written(1); + } } - private: +private: pipereader in; - pipewriter< rspacket > out; - }; // deinterleaver + pipewriter > out; +}; +// deinterleaver +static const int SIZE_TSPACKET = 188; +struct tspacket +{ + u8 data[SIZE_TSPACKET]; +}; - static const int SIZE_TSPACKET = 188; - struct tspacket { u8 data[SIZE_TSPACKET]; }; +// RS ENCODER - - // RS ENCODER - - struct rs_encoder : runnable { - rs_encoder(scheduler *sch, - pipebuf &_in, pipebuf< rspacket > &_out) - : runnable(sch, "RS encoder"), - in(_in), out(_out) { } - - void run() { - while ( in.readable()>=1 && out.writable()>=1 ) { - u8 *pin = in.rd()->data; - u8 *pout = out.wr()->data; - // The first 188 bytes are the uncoded message P(X) - memcpy(pout, pin, SIZE_TSPACKET); - // Append 16 RS parity bytes R(X) = - (P(X)*X^16 mod G(X)) - // so that G(X) divides the coded message S(X) = P(X)*X^16 - R(X). - rs.encode(pout); - in.read(1); - out.written(1); - } +struct rs_encoder: runnable +{ + rs_encoder(scheduler *sch, pipebuf &_in, + pipebuf > &_out) : + runnable(sch, "RS encoder"), in(_in), out(_out) + { } - private: + + void run() + { + while (in.readable() >= 1 && out.writable() >= 1) + { + u8 *pin = in.rd()->data; + u8 *pout = out.wr()->data; + // The first 188 bytes are the uncoded message P(X) + memcpy(pout, pin, SIZE_TSPACKET); + // Append 16 RS parity bytes R(X) = - (P(X)*X^16 mod G(X)) + // so that G(X) divides the coded message S(X) = P(X)*X^16 - R(X). + rs.encode(pout); + in.read(1); + out.written(1); + } + } +private: rs_engine rs; pipereader in; - pipewriter< rspacket > out; - }; // rs_encoder + pipewriter > out; +}; +// rs_encoder +// RS DECODER - // RS DECODER - - template - struct rs_decoder : runnable { +template +struct rs_decoder: runnable +{ rs_engine rs; - rs_decoder(scheduler *sch, - pipebuf< rspacket > &_in, - pipebuf &_out, - pipebuf *_bitcount=NULL, - pipebuf *_errcount=NULL) - : runnable(sch, "RS decoder"), - in(_in), out(_out) { - bitcount = _bitcount ? new pipewriter(*_bitcount) : NULL; - errcount = _errcount ? new pipewriter(*_errcount) : NULL; + rs_decoder(scheduler *sch, pipebuf > &_in, + pipebuf &_out, pipebuf *_bitcount = NULL, + pipebuf *_errcount = NULL) : + runnable(sch, "RS decoder"), in(_in), out(_out) + { + bitcount = _bitcount ? new pipewriter(*_bitcount) : NULL; + errcount = _errcount ? new pipewriter(*_errcount) : NULL; } - void run() { - if ( bitcount && bitcount->writable()<1 ) return; - if ( errcount && errcount->writable()<1 ) return; + void run() + { + if (bitcount && bitcount->writable() < 1) + return; + if (errcount && errcount->writable() < 1) + return; - int nbits=0, nerrs=0; + int nbits = 0, nerrs = 0; - while ( in.readable()>=1 && out.writable()>=1 ) { - Tbyte *pin = in.rd()->data; - u8 *pout = out.wr()->data; + while (in.readable() >= 1 && out.writable() >= 1) + { + Tbyte *pin = in.rd()->data; + u8 *pout = out.wr()->data; - nbits += SIZE_RSPACKET * 8; + nbits += SIZE_RSPACKET * 8; - // The message is the first 188 bytes. - if ( sizeof(Tbyte) == 1 ) - memcpy(pout, pin, SIZE_TSPACKET); - else - fail("Erasures not implemented"); + // The message is the first 188 bytes. + if (sizeof(Tbyte) == 1) + memcpy(pout, pin, SIZE_TSPACKET); + else + { + fail("rs_decoder::run", "Erasures not implemented"); + return; + } - u8 synd[16]; - bool corrupted = rs.syndromes(pin, synd); + u8 synd[16]; + bool corrupted = rs.syndromes(pin, synd); #if 0 - if ( ! corrupted ) { - // Test BM - fprintf(stderr, "Simulating errors\n"); - pin[203] ^= 42; - pin[202] ^= 99; - corrupted = rs.syndromes(pin, synd); - } + if ( ! corrupted ) + { + // Test BM + fprintf(stderr, "Simulating errors\n"); + pin[203] ^= 42; + pin[202] ^= 99; + corrupted = rs.syndromes(pin, synd); + } #endif - if ( ! corrupted ) { - if ( sch->debug ) - fprintf(stderr, "_"); // Packet received without errors. - } else { - corrupted = rs.correct(synd, pout, pin, &nerrs); - if ( sch->debug ) { - if ( ! corrupted ) - fprintf(stderr, "."); // Errors were corrected. - else - fprintf(stderr, "!"); // Packet still corrupted. - } - } + if (!corrupted) + { + if (sch->debug) + fprintf(stderr, "_"); // Packet received without errors. + } + else + { + corrupted = rs.correct(synd, pout, pin, &nerrs); + if (sch->debug) + { + if (!corrupted) + fprintf(stderr, "."); // Errors were corrected. + else + fprintf(stderr, "!"); // Packet still corrupted. + } + } - in.read(1); - - // Output corrupted packets (with a special mark) - // otherwise the derandomizer will lose synchronization. - if ( corrupted ) pout[0] ^= MPEG_SYNC_CORRUPTED; - out.written(1); + in.read(1); - } - if ( nbits ) { - if ( bitcount ) bitcount->write(nbits); - if ( errcount ) errcount->write(nerrs); - } + // Output corrupted packets (with a special mark) + // otherwise the derandomizer will lose synchronization. + if (corrupted) + pout[0] ^= MPEG_SYNC_CORRUPTED; + out.written(1); + + } + if (nbits) + { + if (bitcount) + bitcount->write(nbits); + if (errcount) + errcount->write(nerrs); + } } - private: - pipereader< rspacket > in; +private: + pipereader > in; pipewriter out; pipewriter *bitcount, *errcount; - }; // rs_decoder +}; +// rs_decoder +// RANDOMIZER - // RANDOMIZER - - struct randomizer : runnable { - randomizer(scheduler *sch, - pipebuf &_in, pipebuf &_out) - : runnable(sch, "derandomizer"), - in(_in), out(_out) { - precompute_pattern(); - pos = pattern; - pattern_end = pattern + sizeof(pattern)/sizeof(pattern[0]); +struct randomizer: runnable +{ + randomizer(scheduler *sch, pipebuf &_in, pipebuf &_out) : + runnable(sch, "derandomizer"), in(_in), out(_out) + { + precompute_pattern(); + pos = pattern; + pattern_end = pattern + sizeof(pattern) / sizeof(pattern[0]); } - void precompute_pattern() { - // EN 300 421, section 4.4.1 Transport multiplex adaptation - pattern[0] = 0xff; // Invert one in eight sync bytes - unsigned short st = 000251; // 0b 000 000 010 101 001 (Fig 2 reversed) - for ( int i=1; i<188*8; ++i ) { - u8 out = 0; - for ( int n=8; n--; ) { - int bit = ((st>>13) ^ (st>>14)) & 1; // Taps - out = (out<<1) | bit; // MSB first - st = (st<<1) | bit; // Feedback - } - pattern[i] = (i%188) ? out : 0; // Inhibit on sync bytes - } + void precompute_pattern() + { + // EN 300 421, section 4.4.1 Transport multiplex adaptation + pattern[0] = 0xff; // Invert one in eight sync bytes + unsigned short st = 169; // 0b 000 000 010 101 001 (Fig 2 reversed) + for (int i = 1; i < 188 * 8; ++i) + { + u8 out = 0; + for (int n = 8; n--;) + { + int bit = ((st >> 13) ^ (st >> 14)) & 1; // Taps + out = (out << 1) | bit; // MSB first + st = (st << 1) | bit; // Feedback + } + pattern[i] = (i % 188) ? out : 0; // Inhibit on sync bytes + } } - void run() { - while ( in.readable()>=1 && out.writable()>=1 ) { - u8 *pin = in.rd()->data, *pend = pin+SIZE_TSPACKET; - u8 *pout= out.wr()->data; - if ( pin[0] != MPEG_SYNC ) - fprintf(stderr, "randomizer: bad MPEG sync %02x\n", pin[0]); - for ( ; pin= 1 && out.writable() >= 1) + { + u8 *pin = in.rd()->data, *pend = pin + SIZE_TSPACKET; + u8 *pout = out.wr()->data; + if (pin[0] != MPEG_SYNC) + fprintf(stderr, "randomizer: bad MPEG sync %02x\n", pin[0]); + for (; pin < pend; ++pin, ++pout, ++pos) + *pout = *pin ^ *pos; + if (pos == pattern_end) + pos = pattern; + in.read(1); + out.written(1); + } } - private: - u8 pattern[188*8], *pattern_end, *pos; +private: + u8 pattern[188 * 8], *pattern_end, *pos; pipereader in; pipewriter out; - }; // randomizer +}; +// randomizer +// DERANDOMIZER - // DERANDOMIZER - - struct derandomizer : runnable { - derandomizer(scheduler *sch, - pipebuf &_in, pipebuf &_out) - : runnable(sch, "derandomizer"), - in(_in), out(_out) { - precompute_pattern(); - pos = pattern; - pattern_end = pattern + sizeof(pattern)/sizeof(pattern[0]); +struct derandomizer: runnable +{ + derandomizer(scheduler *sch, pipebuf &_in, + pipebuf &_out) : + runnable(sch, "derandomizer"), in(_in), out(_out) + { + precompute_pattern(); + pos = pattern; + pattern_end = pattern + sizeof(pattern) / sizeof(pattern[0]); } - void precompute_pattern() { - // EN 300 421, section 4.4.1 Transport multiplex adaptation - pattern[0] = 0xff; // Restore the inverted sync byte - unsigned short st = 000251; // 0b 000 000 010 101 001 (Fig 2 reversed) - for ( int i=1; i<188*8; ++i ) { - u8 out = 0; - for ( int n=8; n--; ) { - int bit = ((st>>13) ^ (st>>14)) & 1; // Taps - out = (out<<1) | bit; // MSB first - st = (st<<1) | bit; // Feedback - } - pattern[i] = (i%188) ? out : 0; // Inhibit on sync bytes - } + void precompute_pattern() + { + // EN 300 421, section 4.4.1 Transport multiplex adaptation + pattern[0] = 0xff; // Restore the inverted sync byte + unsigned short st = 169; // 0b 000 000 010 101 001 (Fig 2 reversed) + for (int i = 1; i < 188 * 8; ++i) + { + u8 out = 0; + for (int n = 8; n--;) + { + int bit = ((st >> 13) ^ (st >> 14)) & 1; // Taps + out = (out << 1) | bit; // MSB first + st = (st << 1) | bit; // Feedback + } + pattern[i] = (i % 188) ? out : 0; // Inhibit on sync bytes + } } - void run() { - while ( in.readable()>=1 && out.writable()>=1 ) { - u8 *pin = in.rd()->data, *pend = pin+SIZE_TSPACKET; - u8 *pout= out.wr()->data; - if ( pin[0] == MPEG_SYNC_INV || - pin[0] == (MPEG_SYNC_INV^MPEG_SYNC_CORRUPTED) ) { - if ( pos != pattern ) { - if ( sch->debug ) - fprintf(stderr, "derandomizer: resynchronizing\n"); - pos = pattern; - } - } - for ( ; pin= 1 && out.writable() >= 1) + { + u8 *pin = in.rd()->data, *pend = pin + SIZE_TSPACKET; + u8 *pout = out.wr()->data; + if (pin[0] == MPEG_SYNC_INV + || pin[0] == (MPEG_SYNC_INV ^ MPEG_SYNC_CORRUPTED)) + { + if (pos != pattern) + { + if (sch->debug) + fprintf(stderr, "derandomizer: resynchronizing\n"); + pos = pattern; + } + } + for (; pin < pend; ++pin, ++pout, ++pos) + *pout = *pin ^ *pos; + if (pos == pattern_end) + pos = pattern; + in.read(1); - u8 sync = out.wr()->data[0]; - if ( sync == MPEG_SYNC ) { - out.written(1); - } else { - if ( sync != (MPEG_SYNC^MPEG_SYNC_CORRUPTED) ) - if ( sch->debug ) fprintf(stderr, "(%02x)", sync); - out.wr()->data[1] |= 0x80; // Set the Transport Error Indicator bit - // We could output corrupted packets here, in case the - // MPEG decoder can use them somehow. - //out.written(1); - } - } + u8 sync = out.wr()->data[0]; + if (sync == MPEG_SYNC) + { + out.written(1); + } + else + { + if (sync != (MPEG_SYNC ^ MPEG_SYNC_CORRUPTED)) + if (sch->debug) + fprintf(stderr, "(%02x)", sync); + out.wr()->data[1] |= 0x80; // Set the Transport Error Indicator bit + // We could output corrupted packets here, in case the + // MPEG decoder can use them somehow. + //out.written(1); + } + } } - private: - u8 pattern[188*8], *pattern_end, *pos; +private: + u8 pattern[188 * 8], *pattern_end, *pos; pipereader in; pipewriter out; - }; // derandomizer +}; +// derandomizer +// VITERBI DECODING +// Supports all code rates and constellations +// Simplified metric to support large constellations. - // VITERBI DECODING - // Supports all code rates and constellations - // Simplified metric to support large constellations. +// This version implements puncturing by expanding the trellis. +// TBD Compare performance vs skipping updates in a 1/2 trellis. - // This version implements puncturing by expanding the trellis. - // TBD Compare performance vs skipping updates in a 1/2 trellis. - - struct viterbi_sync : runnable { +struct viterbi_sync: runnable +{ typedef uint8_t TS, TCS, TUS; typedef int32_t TBM; // Only 16 bits per IQ, but several IQ per Viterbi CS typedef int32_t TPM; - typedef viterbi_dec_interface dvb_dec_interface; + typedef viterbi_dec_interface dvb_dec_interface; // 1/2: 6 bits of state, 1 bit in, 2 bits out - typedef bitpath path_12; - typedef trellis trellis_12; - typedef viterbi_dec dvb_dec_12; + typedef bitpath path_12; + typedef trellis trellis_12; + typedef viterbi_dec dvb_dec_12; // 2/3: 6 bits of state, 2 bits in, 3 bits out - typedef bitpath path_23; - typedef trellis trellis_23; - typedef viterbi_dec dvb_dec_23; + typedef bitpath path_23; + typedef trellis trellis_23; + typedef viterbi_dec dvb_dec_23; // 4/6: 6 bits of state, 4 bits in, 6 bits out - typedef bitpath path_46; - typedef trellis trellis_46; - typedef viterbi_dec dvb_dec_46; + typedef bitpath path_46; + typedef trellis trellis_46; + typedef viterbi_dec dvb_dec_46; // 3/4: 6 bits of state, 3 bits in, 4 bits out - typedef bitpath path_34; - typedef trellis trellis_34; - typedef viterbi_dec dvb_dec_34; + typedef bitpath path_34; + typedef trellis trellis_34; + typedef viterbi_dec dvb_dec_34; // 4/5: 6 bits of state, 4 bits in, 5 bits out (non-standard) - typedef bitpath path_45; - typedef trellis trellis_45; - typedef viterbi_dec dvb_dec_45; + typedef bitpath path_45; + typedef trellis trellis_45; + typedef viterbi_dec dvb_dec_45; // 5/6: 6 bits of state, 5 bits in, 6 bits out - typedef bitpath path_56; - typedef trellis trellis_56; - typedef viterbi_dec dvb_dec_56; + typedef bitpath path_56; + typedef trellis trellis_56; + typedef viterbi_dec dvb_dec_56; // QPSK 7/8: 6 bits of state, 7 bits in, 8 bits out - typedef bitpath path_78; - typedef trellis trellis_78; - typedef viterbi_dec dvb_dec_78; + typedef bitpath path_78; + typedef trellis trellis_78; + typedef viterbi_dec dvb_dec_78; - private: +private: pipereader in; pipewriter out; cstln_lut<256> *cstln; @@ -1202,204 +1454,255 @@ namespace leansdr { int bits_per_symbol; // Bits per IQ symbol (not per coded symbol) int nsyncs; int nshifts; - struct sync { - int shift; - dvb_dec_interface *dec; - TCS *map; // [nsymbols] - } *syncs; // [nsyncs] + struct sync + { + int shift; + dvb_dec_interface *dec; + TCS *map; // [nsymbols] + }*syncs; // [nsyncs] + IncrementalArray m_totaldiscr; int current_sync; static const int chunk_size = 128; int resync_phase; - public: +public: int resync_period; - viterbi_sync(scheduler *sch, - pipebuf &_in, pipebuf &_out, - cstln_lut<256> *_cstln, code_rate cr) - : runnable(sch, "viterbi_sync"), - in(_in), out(_out, chunk_size), - cstln(_cstln), - current_sync(0), - resync_phase(0), - resync_period(32) // 1/32 = 9% synchronization overhead TBD + viterbi_sync(scheduler *sch, pipebuf &_in, + pipebuf &_out, cstln_lut<256> *_cstln, code_rate cr) : + runnable(sch, "viterbi_sync"), in(_in), out(_out, chunk_size), cstln( + _cstln), current_sync(0), resync_phase(0), resync_period(32) // 1/32 = 9% synchronization overhead TBD { - bits_per_symbol = log2i(cstln->nsymbols); - fec = &fec_specs[cr]; - { // Sanity check: FEC block size must be a multiple of label size. - int symbols_per_block = fec->bits_out / bits_per_symbol; - if ( bits_per_symbol*symbols_per_block != fec->bits_out ) - fail("Code rate not suitable for this constellation"); - } - int nconj; - switch ( cstln->nsymbols ) { - case 2: nconj = 1; break; // Conjugation is not relevant for BPSK - default: nconj = 2; break; - } + bits_per_symbol = log2i(cstln->nsymbols); + fec = &fec_specs[cr]; + { // Sanity check: FEC block size must be a multiple of label size. + int symbols_per_block = fec->bits_out / bits_per_symbol; + if (bits_per_symbol * symbols_per_block != fec->bits_out) + { + fail("viterbi_sync::viterbi_sync", "Code rate not suitable for this constellation"); + return; + } + } + int nconj; + switch (cstln->nsymbols) + { + case 2: + nconj = 1; + break; // Conjugation is not relevant for BPSK + default: + nconj = 2; + break; + } - int nrotations; - switch ( cstln->nsymbols ) { - case 2: - case 4: - // For BPSK and QPSK, 180° rotation is handled as - // polarity inversion in mpeg_sync. - nrotations = cstln->nrotations/2; - break; - default: - nrotations = cstln->nrotations; - break; - } - nshifts = fec->bits_out / bits_per_symbol; - nsyncs = nconj * nrotations * nshifts; + int nrotations; + switch (cstln->nsymbols) + { + case 2: + case 4: + // For BPSK and QPSK, 180° rotation is handled as + // polarity inversion in mpeg_sync. + nrotations = cstln->nrotations / 2; + break; + default: + nrotations = cstln->nrotations; + break; + } + nshifts = fec->bits_out / bits_per_symbol; + nsyncs = nconj * nrotations * nshifts; - // TBD Many HOM constellations are labelled in such a way - // that certain rot/conj combinations are equivalent to - // polarity inversion. We could reduce nsyncs. + // TBD Many HOM constellations are labelled in such a way + // that certain rot/conj combinations are equivalent to + // polarity inversion. We could reduce nsyncs. - syncs = new sync[nsyncs]; + syncs = new sync[nsyncs]; + m_totaldiscr.allocate(nsyncs); - for ( int s=0; snrotations); + for (int s = 0; s < nsyncs; ++s) + { + // Bit pattern [shift|conj|rot] + int rot = s % nrotations; + int conj = (s / nrotations) % nconj; + int shift = s / nrotations / nconj; + syncs[s].shift = shift; + if (shift) // Reuse identical map + syncs[s].map = syncs[conj * nrotations + rot].map; + else + syncs[s].map = init_map(conj, + 2 * M_PI * rot / cstln->nrotations); #if 0 - fprintf(stderr, "sync %3d: conj%d offs%d rot%d/%d map:", - s, conj, syncs[s].shift, rot, cstln->nrotations); - for ( int i=0; insymbols; ++i ) - fprintf(stderr, " %2d", syncs[s].map[i]); - fprintf(stderr, "\n"); + fprintf(stderr, "sync %3d: conj%d offs%d rot%d/%d map:", + s, conj, syncs[s].shift, rot, cstln->nrotations); + for ( int i=0; insymbols; ++i ) + fprintf(stderr, " %2d", syncs[s].map[i]); + fprintf(stderr, "\n"); #endif - } + } - if ( cr == FEC12 ) { - trellis_12 *trell = new trellis_12(); - trell->init_convolutional(fec->polys); - for ( int s=0; sinit_convolutional(fec->polys); - for ( int s=0; sinit_convolutional(fec->polys); - for ( int s=0; sinit_convolutional(fec->polys); - for ( int s=0; sinit_convolutional(fec->polys); - for ( int s=0; sinit_convolutional(fec->polys); - for ( int s=0; sinit_convolutional(fec->polys); - for ( int s=0; sinit_convolutional(fec->polys); + for (int s = 0; s < nsyncs; ++s) + syncs[s].dec = new dvb_dec_12(trell); + } + else if (cr == FEC23) + { + trellis_23 *trell = new trellis_23(); + trell->init_convolutional(fec->polys); + for (int s = 0; s < nsyncs; ++s) + syncs[s].dec = new dvb_dec_23(trell); + } + else if (cr == FEC46) + { + trellis_46 *trell = new trellis_46(); + trell->init_convolutional(fec->polys); + for (int s = 0; s < nsyncs; ++s) + syncs[s].dec = new dvb_dec_46(trell); + } + else if (cr == FEC34) + { + trellis_34 *trell = new trellis_34(); + trell->init_convolutional(fec->polys); + for (int s = 0; s < nsyncs; ++s) + syncs[s].dec = new dvb_dec_34(trell); + } + else if (cr == FEC45) + { + trellis_45 *trell = new trellis_45(); + trell->init_convolutional(fec->polys); + for (int s = 0; s < nsyncs; ++s) + syncs[s].dec = new dvb_dec_45(trell); + } + else if (cr == FEC56) + { + trellis_56 *trell = new trellis_56(); + trell->init_convolutional(fec->polys); + for (int s = 0; s < nsyncs; ++s) + syncs[s].dec = new dvb_dec_56(trell); + } + else if (cr == FEC78) + { + trellis_78 *trell = new trellis_78(); + trell->init_convolutional(fec->polys); + for (int s = 0; s < nsyncs; ++s) + syncs[s].dec = new dvb_dec_78(trell); + } + else + { + fail("viterbi_sync::viterbi_sync", "CR not supported"); + return; + } } - TCS *init_map(bool conj, float angle) { - // Each constellation has its own pattern for labels. - // Here we simply tabulate systematically. - TCS *map = new TCS[cstln->nsymbols]; - float ca=cosf(angle), sa=sinf(angle); - for ( int i=0; insymbols; ++i ) { - int8_t I = cstln->symbols[i].re; - int8_t Q = cstln->symbols[i].im; - if ( conj ) Q = -Q; - int8_t RI = I*ca - Q*sa; - int8_t RQ = I*sa + Q*ca; - cstln_lut<256>::result *pr = cstln->lookup(RI, RQ); - map[i] = pr->ss.symbol; - } - return map; + TCS *init_map(bool conj, float angle) + { + // Each constellation has its own pattern for labels. + // Here we simply tabulate systematically. + TCS *map = new TCS[cstln->nsymbols]; + float ca = cosf(angle), sa = sinf(angle); + for (int i = 0; i < cstln->nsymbols; ++i) + { + int8_t I = cstln->symbols[i].re; + int8_t Q = cstln->symbols[i].im; + if (conj) + Q = -Q; + int8_t RI = I * ca - Q * sa; + int8_t RQ = I * sa + Q * ca; + cstln_lut<256>::result *pr = cstln->lookup(RI, RQ); + map[i] = pr->ss.symbol; + } + return map; } - inline TUS update_sync(int s, softsymbol *pin, TPM *discr) { - // Read one FEC ouput block - pin += syncs[s].shift; - TCS cs = 0; - TBM cost = 0; - for ( int i=0; isymbol]; - cost += pin->cost; - } - return syncs[s].dec->update(cs, cost, discr); + inline TUS update_sync(int s, softsymbol *pin, TPM *discr) + { + // Read one FEC ouput block + pin += syncs[s].shift; + TCS cs = 0; + TBM cost = 0; + for (int i = 0; i < nshifts; ++i, ++pin) + { + cs = (cs << bits_per_symbol) | syncs[s].map[pin->symbol]; + cost += pin->cost; + } + return syncs[s].dec->update(cs, cost, discr); } - void run() { - // Number of FEC blocks to fill the bitpath depth. - // Before that we cannot discriminate between synchronizers - int discr_delay = 64 / fec->bits_in; + void run() + { + // Number of FEC blocks to fill the bitpath depth. + // Before that we cannot discriminate between synchronizers + int discr_delay = 64 / fec->bits_in; - // Process [chunk_size] FEC blocks at a time + // Process [chunk_size] FEC blocks at a time - while ( in.readable() >= nshifts*chunk_size + (nshifts-1) && - out.writable()*8 >= fec->bits_in*chunk_size ) { - TPM totaldiscr[nsyncs]; - for ( int s=0; s= nshifts * chunk_size + (nshifts - 1) + && ((long) out.writable() * 8) >= fec->bits_in * chunk_size) + { + //TPM totaldiscr[nsyncs]; + TPM *totaldiscr = m_totaldiscr.m_array; + for (int s = 0; s < nsyncs; ++s) + totaldiscr[s] = 0; - uint64_t outstream = 0; - int nout = 0; - softsymbol *pin = in.rd(); - for ( int blocknum=0; blocknumbits_in) | result; - nout += fec->bits_in; - if ( blocknum >= discr_delay ) totaldiscr[current_sync] += discr; - if ( ! resync_phase ) { - // Every [resync_period] chunks, also run the other decoders. - for ( int s=0; s= discr_delay ) totaldiscr[s] += discr; - } - } - while ( nout >= 8 ) { - out.write(outstream>>(nout-8)); - nout -= 8; - } - } // chunk_size - in.read(chunk_size*nshifts); - if ( nout ) fail("overlapping out"); - if ( ! resync_phase ) { - // Switch to another decoder ? - int best = current_sync; - for ( int s=0; s totaldiscr[best] ) best = s; - if ( best != current_sync ) { - if ( sch->debug ) fprintf(stderr, "{%d->%d}", current_sync, best); - current_sync = best; - } - } - if ( ++resync_phase >= resync_period ) resync_phase = 0; - } + uint64_t outstream = 0; + int nout = 0; + softsymbol *pin = in.rd(); + for (int blocknum = 0; blocknum < chunk_size; ++blocknum, pin += + nshifts) + { + TPM discr; + TUS result = update_sync(current_sync, pin, &discr); + outstream = (outstream << fec->bits_in) | result; + nout += fec->bits_in; + if (blocknum >= discr_delay) + totaldiscr[current_sync] += discr; + if (!resync_phase) + { + // Every [resync_period] chunks, also run the other decoders. + for (int s = 0; s < nsyncs; ++s) + { + if (s == current_sync) + continue; + TPM discr; + (void) update_sync(s, pin, &discr); + if (blocknum >= discr_delay) + totaldiscr[s] += discr; + } + } + while (nout >= 8) + { + out.write(outstream >> (nout - 8)); + nout -= 8; + } + } // chunk_size + in.read(chunk_size * nshifts); + if (nout) + { + fail("viterbi_sync::run", "overlapping out"); + return; + } + if (!resync_phase) + { + // Switch to another decoder ? + int best = current_sync; + for (int s = 0; s < nsyncs; ++s) + if (totaldiscr[s] > totaldiscr[best]) + best = s; + if (best != current_sync) + { + if (sch->debug) + fprintf(stderr, "{%d->%d}", current_sync, best); + current_sync = best; + } + } + if (++resync_phase >= resync_period) + resync_phase = 0; + } } - }; // viterbi_sync +}; +// viterbi_sync - - -} // namespace +}// namespace #endif // LEANSDR_DVB_H diff --git a/plugins/channelrx/demoddatv/leansdr/framework.h b/plugins/channelrx/demoddatv/leansdr/framework.h index 449c4864c..e705b2db6 100644 --- a/plugins/channelrx/demoddatv/leansdr/framework.h +++ b/plugins/channelrx/demoddatv/leansdr/framework.h @@ -1,54 +1,102 @@ #ifndef LEANSDR_FRAMEWORK_H #define LEANSDR_FRAMEWORK_H +#include #include #include #include #include -namespace leansdr { - - inline void fatal(const char *s) { perror(s); exit(1); } - inline void fail(const char *s) { fprintf(stderr, "** %s\n", s); exit(1); } - - ////////////////////////////////////////////////////////////////////// - // DSP framework - ////////////////////////////////////////////////////////////////////// - - // [pipebuf] is a FIFO buffer with multiple readers. - // [pipewriter] is a client-side hook for writing into a [pipebuf]. - // [pipereader] is a client-side hook reading from a [pipebuf]. - // [runnable] is anything that moves data between [pipebufs]. - // [scheduler] is a global context which invokes [runnables] until fixpoint. - - static const int MAX_PIPES = 64; - static const int MAX_RUNNABLES = 64; - static const int MAX_READERS = 8; - - struct pipebuf_common { - virtual int sizeofT() { return 0; } - virtual long long hash() { return 0; } - virtual void dump(size_t *total_bufs) { } - const char *name; - pipebuf_common(const char *_name) : name(_name) { } - }; +namespace leansdr +{ + +inline void fatal(const char *s) +{ + perror(s); +} + +inline void fail(const char *f, const char *s) +{ + fprintf(stderr, "leansdr::%s: %s\n", f, s); +} + +////////////////////////////////////////////////////////////////////// +// DSP framework +////////////////////////////////////////////////////////////////////// + +// [pipebuf] is a FIFO buffer with multiple readers. +// [pipewriter] is a client-side hook for writing into a [pipebuf]. +// [pipereader] is a client-side hook reading from a [pipebuf]. +// [runnable] is anything that moves data between [pipebufs]. +// [scheduler] is a global context which invokes [runnables] until fixpoint. + +static const int MAX_PIPES = 64; +static const int MAX_RUNNABLES = 64; +static const int MAX_READERS = 8; + +struct pipebuf_common +{ + virtual int sizeofT() + { + return 0; + } + + virtual long long hash() + { + return 0; + } + + virtual void dump(std::size_t *total_bufs __attribute__((unused))) + { + } + + pipebuf_common(const char *_name) : + name(_name) + { + } + + virtual ~pipebuf_common() + { + } - struct runnable_common { const char *name; - runnable_common(const char *_name) : name(_name) { } - virtual void run() { } - virtual void shutdown() { } +}; + +struct runnable_common +{ + runnable_common(const char *_name) : + name(_name) + { + } + + virtual ~runnable_common() + { + } + + virtual void run() + { + } + + virtual void shutdown() + { + } + #ifdef DEBUG - ~runnable_common() { fprintf(stderr, "Deallocating %s !\n", name); } + ~runnable_common() + { fprintf(stderr, "Deallocating %s !\n", name);} #endif - }; - - struct window_placement { + + const char *name; +}; + +struct window_placement +{ const char *name; // NULL to terminate int x, y, w, h; - }; +}; - struct scheduler { +struct scheduler +{ pipebuf_common *pipes[MAX_PIPES]; int npipes; runnable_common *runnables[MAX_RUNNABLES]; @@ -56,213 +104,367 @@ namespace leansdr { window_placement *windows; bool verbose, debug; - scheduler() - : npipes(0), nrunnables(0), windows(NULL), - verbose(false), debug(false) { + scheduler() : + npipes(0), nrunnables(0), windows(NULL), verbose(false), debug( + false) + { } - void add_pipe(pipebuf_common *p) { - if ( npipes == MAX_PIPES ) fail("MAX_PIPES"); - pipes[npipes++] = p; + + void add_pipe(pipebuf_common *p) + { + if (npipes == MAX_PIPES) + { + fail("scheduler::add_pipe", "MAX_PIPES"); + return; + } + pipes[npipes++] = p; } - void add_runnable(runnable_common *r) { - if ( nrunnables == MAX_RUNNABLES ) fail("MAX_RUNNABLES"); - runnables[nrunnables++] = r; + + void add_runnable(runnable_common *r) + { + if (nrunnables == MAX_RUNNABLES) + { + fail("scheduler::add_runnable", "MAX_RUNNABLES"); + } + runnables[nrunnables++] = r; } - void step() { - for ( int i=0; irun(); + + void step() + { + for (int i = 0; i < nrunnables; ++i) { + runnables[i]->run(); + } } - void run() { - unsigned long long prev_hash = 0; - while ( 1 ) { - step(); - unsigned long long h = hash(); - if ( h == prev_hash ) break; - prev_hash = h; - } + + void run() + { + unsigned long long prev_hash = 0; + + while (1) + { + step(); + unsigned long long h = hash(); + if (h == prev_hash) { + break; + } + prev_hash = h; + } } - void shutdown() { - for ( int i=0; ishutdown(); + + void shutdown() + { + for (int i = 0; i < nrunnables; ++i) { + runnables[i]->shutdown(); + } } - unsigned long long hash() { - unsigned long long h = 0; - for ( int i=0; ihash(); - return h; + + unsigned long long hash() + { + unsigned long long h = 0; + for (int i = 0; i < npipes; ++i) { + h += (1 + i) * pipes[i]->hash(); + } + return h; } - - void dump() { - fprintf(stderr, "\n"); - size_t total_bufs = 0; - for ( int i=0; idump(&total_bufs); - fprintf(stderr, "Total buffer memory: %ld KiB\n", - (unsigned long)total_bufs/1024); + + void dump() + { + fprintf(stderr, "\n"); + std::size_t total_bufs = 0; + for (int i = 0; i < npipes; ++i) { + pipes[i]->dump(&total_bufs); + } + fprintf(stderr, "leansdr::scheduler::dump Total buffer memory: %ld KiB\n", (unsigned long) total_bufs / 1024); } - }; - - struct runnable : runnable_common { - runnable(scheduler *_sch, const char *name) - : runnable_common(name), sch(_sch) { - sch->add_runnable(this); +}; + +struct runnable: runnable_common +{ + runnable(scheduler *_sch, const char *name) : + runnable_common(name), sch(_sch) + { + sch->add_runnable(this); } - protected: +protected: scheduler *sch; - }; - - template - struct pipebuf : pipebuf_common { +}; + +template +struct pipebuf: pipebuf_common +{ T *buf; T *rds[MAX_READERS]; int nrd; T *wr; T *end; - int sizeofT() { return sizeof(T); } - pipebuf(scheduler *sch, const char *name, unsigned long size) - : pipebuf_common(name), - buf(new T[size]), nrd(0), wr(buf), end(buf+size), - min_write(1), - total_written(0), total_read(0) { - sch->add_pipe(this); + + int sizeofT() + { + return sizeof(T); } - int add_reader() { - if ( nrd == MAX_READERS ) fail("too many readers"); - rds[nrd] = wr; - return nrd++; + + pipebuf(scheduler *sch, const char *name, unsigned long size) : + pipebuf_common(name), buf(new T[size]), nrd(0), wr(buf), end( + buf + size), min_write(1), total_written(0), total_read(0) + { + sch->add_pipe(this); } - void pack() { - T *rd = wr; - for ( int i=0; i - struct pipewriter { +}; + +template +struct pipewriter +{ pipebuf &buf; - pipewriter(pipebuf &_buf, unsigned long min_write=1) - : buf(_buf) { - if ( min_write > buf.min_write ) buf.min_write = min_write; - } - // Return number of items writable at this->wr, 0 if full. - unsigned long writable() { - if ( buf.end-buf.wr < buf.min_write ) buf.pack(); - return buf.end - buf.wr; - } - T *wr() { return buf.wr; } - void written(unsigned long n) { - if ( buf.wr+n > buf.end ) { - fprintf(stderr, "Bug: overflow to %s\n", buf.name); - exit(1); - } - buf.wr += n; - buf.total_written += n; - } - void write(const T &e) { - *wr() = e; - written(1); - } - }; - // Convenience functions for working with optional pipes + pipewriter(pipebuf &_buf, unsigned long min_write = 1) : + buf(_buf) + { + if (min_write > buf.min_write) { + buf.min_write = min_write; + } + } - template - pipewriter *opt_writer(pipebuf *buf) { + /** Return number of items writable at this->wr, 0 if full. */ + unsigned long writable() + { + if (buf.end < buf.wr) + { + fprintf(stderr, "leansdr::pipewriter::writable: overflow in %s buffer\n", buf.name); + return 0; + } + + unsigned long delta = buf.end - buf.wr; + + if (delta < buf.min_write) { + buf.pack(); + } + + return delta; + } + + T *wr() + { + return buf.wr; + } + + void written(unsigned long n) + { + if (buf.wr + n > buf.end) + { + fprintf(stderr, "leansdr::pipewriter::written: overflow in %s buffer\n", buf.name); + return; + } + buf.wr += n; + buf.total_written += n; + } + + void write(const T &e) + { + *wr() = e; + written(1); + } +}; + +// Convenience functions for working with optional pipes + +template +pipewriter *opt_writer(pipebuf *buf) +{ return buf ? new pipewriter(*buf) : NULL; - } +} - template - bool opt_writable(pipewriter *p, int n=1) { - return (p==NULL) || p->writable()>=n; - } +template +bool opt_writable(pipewriter *p, unsigned int n = 1) +{ + return (p == NULL) || p->writable() >= n; +} - template - void opt_write(pipewriter *p, T val) { - if ( p ) p->write(val); - } +template +void opt_write(pipewriter *p, T val) +{ + if (p) { + p->write(val); + } +} - template - struct pipereader { +template +struct pipereader +{ pipebuf &buf; int id; - pipereader(pipebuf &_buf) : buf(_buf), id(_buf.add_reader()) { } - unsigned long readable() { return buf.wr - buf.rds[id]; } - T *rd() { return buf.rds[id]; } - void read(unsigned long n) { - if ( buf.rds[id]+n > buf.wr ) { - fprintf(stderr, "Bug: underflow from %s\n", buf.name); - exit(1); - } - buf.rds[id] += n; - buf.total_read += n; + + pipereader(pipebuf &_buf) : + buf(_buf), id(_buf.add_reader()) + { } - }; - - // Math functions for templates - - template T gen_sqrt(T x); - inline float gen_sqrt(float x) { return sqrtf(x); } - inline unsigned int gen_sqrt(unsigned int x) { return sqrtl(x); } - inline long double gen_sqrt(long double x) { return sqrtl(x); } - template T gen_abs(T x); - inline float gen_abs(float x) { return fabsf(x); } - inline int gen_abs(int x) { return abs(x); } - inline long int gen_abs(long int x) { return labs(x); } + unsigned long readable() + { + return buf.wr - buf.rds[id]; + } - template T gen_hypot(T x, T y); - inline float gen_hypot(float x, float y) { return hypotf(x,y); } - inline long double gen_hypot(long double x, long double y) - { return hypotl(x,y); } + T *rd() + { + return buf.rds[id]; + } - template T gen_atan2(T y, T x); - inline float gen_atan2(float y, float x) { return atan2f(y,x); } - inline long double gen_atan2(long double y, long double x) - { return atan2l(y,x); } + void read(unsigned long n) + { + if (buf.rds[id] + n > buf.wr) + { + fprintf(stderr, "leansdr::pipereader::read: underflow in %s buffer\n", buf.name); + return; + } + buf.rds[id] += n; + buf.total_read += n; + } +}; - template - T min(const T &x, const T &y) { return (x - T max(const T &x, const T &y) { return (x T gen_sqrt(T x); +inline float gen_sqrt(float x) +{ + return sqrtf(x); +} - // Abreviations for integer types +inline unsigned int gen_sqrt(unsigned int x) +{ + return sqrtl(x); +} - typedef unsigned char u8; - typedef unsigned short u16; - typedef unsigned long u32; - typedef signed char s8; - typedef signed short s16; - typedef signed long s32; +inline long double gen_sqrt(long double x) +{ + return sqrtl(x); +} + +template T gen_abs(T x); +inline float gen_abs(float x) +{ + return fabsf(x); +} + +inline int gen_abs(int x) +{ + return abs(x); +} + +inline long int gen_abs(long int x) +{ + return labs(x); +} + +template T gen_hypot(T x, T y); +inline float gen_hypot(float x, float y) +{ + return hypotf(x, y); +} + +inline long double gen_hypot(long double x, long double y) +{ + return hypotl(x, y); +} + +template T gen_atan2(T y, T x); +inline float gen_atan2(float y, float x) +{ + return atan2f(y, x); +} + +inline long double gen_atan2(long double y, long double x) +{ + return atan2l(y, x); +} + +template +T min(const T &x, const T &y) +{ + return (x < y) ? x : y; +} + +template +T max(const T &x, const T &y) +{ + return (x < y) ? y : x; +} + +// Abreviations for integer types + +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned long u32; +typedef signed char s8; +typedef signed short s16; +typedef signed long s32; } // namespace diff --git a/plugins/channelrx/demoddatv/leansdr/generic.h b/plugins/channelrx/demoddatv/leansdr/generic.h index 2a4329f54..47577328e 100644 --- a/plugins/channelrx/demoddatv/leansdr/generic.h +++ b/plugins/channelrx/demoddatv/leansdr/generic.h @@ -6,7 +6,8 @@ #include "leansdr/math.h" -namespace leansdr { +namespace leansdr +{ ////////////////////////////////////////////////////////////////////// // Simple blocks @@ -16,103 +17,144 @@ namespace leansdr { // If the file descriptor is seekable, data can be looped. template -struct file_reader : runnable { - file_reader(scheduler *sch, int _fdin, pipebuf &_out) - : runnable(sch, _out.name), - loop(false), - fdin(_fdin), out(_out) - { - } - void run() { - size_t size = out.writable() * sizeof(T); - if ( ! size ) return; - - again: - ssize_t nr = read(fdin, out.wr(), size); - if ( nr < 0 ) fatal("read"); - if ( ! nr ) { - if ( ! loop ) return; - if ( sch->debug ) fprintf(stderr, "%s looping\n", name); - off_t res = lseek(fdin, 0, SEEK_SET); - if ( res == (off_t)-1 ) fatal("lseek"); - goto again; +struct file_reader: runnable +{ + file_reader(scheduler *sch, int _fdin, pipebuf &_out) : + runnable(sch, _out.name), loop(false), fdin(_fdin), out(_out) + { } + void run() + { + size_t size = out.writable() * sizeof(T); + if (!size) + return; - // Always stop at element boundary (may block) - size_t partial = nr % sizeof(T); - size_t remain = partial ? sizeof(T)-partial : 0; - while ( remain ) { - if ( sch->debug ) fprintf(stderr, "+"); - ssize_t nr2 = read(fdin, (char*)out.wr()+nr, remain); - if ( nr2 <= 0 ) fatal("partial read"); - nr += nr2; - remain -= nr2; + again: ssize_t nr = read(fdin, out.wr(), size); + if (nr < 0) + { + fatal("leansdr::file_reader::run: read"); + return; + } + if (!nr) + { + if (!loop) + return; + if (sch->debug) + fprintf(stderr, "leansdr::file_reader::run: %s looping\n", name); + off_t res = lseek(fdin, 0, SEEK_SET); + if (res == (off_t) -1) + { + fatal("leansdr::file_reader::run: lseek"); + return; + } + goto again; + } + + // Always stop at element boundary (may block) + size_t partial = nr % sizeof(T); + size_t remain = partial ? sizeof(T) - partial : 0; + while (remain) + { + if (sch->debug) + fprintf(stderr, "+"); + ssize_t nr2 = read(fdin, (char*) out.wr() + nr, remain); + if (nr2 <= 0) + { + fatal("leansdr::file_reader::run: partial read"); + return; + } + nr += nr2; + remain -= nr2; + } + + out.written(nr / sizeof(T)); } - - out.written(nr / sizeof(T)); - } - bool loop; + bool loop; private: - int fdin; - pipewriter out; + int fdin; + pipewriter out; }; // [file_writer] writes raw data from a [pipebuf] to a file descriptor. template -struct file_writer : runnable { - file_writer(scheduler *sch, pipebuf &_in, int _fdout) : - runnable(sch, _in.name), - in(_in), fdout(_fdout) { - } - void run() { - int size = in.readable() * sizeof(T); - if ( ! size ) return; - int nw = write(fdout, in.rd(), size); - if ( ! nw ) fatal("pipe"); - if ( nw < 0 ) fatal("write"); - if ( nw % sizeof(T) ) fatal("partial write"); - in.read(nw/sizeof(T)); - } +struct file_writer: runnable +{ + file_writer(scheduler *sch, pipebuf &_in, int _fdout) : + runnable(sch, _in.name), in(_in), fdout(_fdout) + { + } + void run() + { + int size = in.readable() * sizeof(T); + if (!size) + return; + int nw = write(fdout, in.rd(), size); + if (!nw) + { + fatal("leansdr::file_writer::run: pipe"); + return; + } + if (nw < 0) + { + fatal("leansdr::file_writer::run: write"); + return; + } + if (nw % sizeof(T)) + { + fatal("leansdr::file_writer::run:partial write"); + return; + } + in.read(nw / sizeof(T)); + } private: - pipereader in; - int fdout; + pipereader in; + int fdout; }; // [file_printer] writes data from a [pipebuf] to a file descriptor, // with printf-style formatting and optional scaling. template -struct file_printer : runnable { - file_printer(scheduler *sch, const char *_format, - pipebuf &_in, int _fdout, - int _decimation=1) : - runnable(sch, _in.name), - scale(1), decimation(_decimation), - in(_in), format(_format), fdout(_fdout), phase(0) { - } - void run() { - int n = in.readable(); - T *pin=in.rd(), *pend=pin+n; - for ( ; pin= decimation ) { - phase -= decimation; - char buf[256]; - int len = snprintf(buf, sizeof(buf), format, (*pin)*scale); - if ( len < 0 ) fatal("obsolete glibc"); - int nw = write(fdout, buf, len); - if ( nw != len ) fatal("partial write"); - } +struct file_printer: runnable +{ + file_printer(scheduler *sch, const char *_format, pipebuf &_in, int _fdout, int _decimation = 1) : + runnable(sch, _in.name), scale(1), decimation(_decimation), in(_in), format(_format), fdout(_fdout), phase(0) + { } - in.read(n); - } - T scale; - int decimation; + void run() + { + int n = in.readable(); + T *pin = in.rd(), *pend = pin + n; + for (; pin < pend; ++pin) + { + if (++phase >= decimation) + { + phase -= decimation; + char buf[256]; + int len = snprintf(buf, sizeof(buf), format, (*pin) * scale); + if (len < 0) + { + fatal("leansdr::file_printer::run: obsolete glibc"); + return; + } + int nw = write(fdout, buf, len); + if (nw != len) + { + fatal("leansdr::file_printer::run: partial write"); + return; + } + } + } + in.read(n); + } + T scale; + int decimation; private: - pipereader in; - const char *format; - int fdout; - int phase; + pipereader in; + const char *format; + int fdout; + int phase; }; // [file_carrayprinter] writes all data available from a [pipebuf] @@ -120,229 +162,246 @@ private: // Special case for complex. template -struct file_carrayprinter : runnable { - file_carrayprinter(scheduler *sch, - const char *_head, - const char *_format, - const char *_sep, - const char *_tail, - pipebuf< complex > &_in, int _fdout) : - runnable(sch, _in.name), - scale(1), fixed_size(0), in(_in), - head(_head), format(_format), sep(_sep), tail(_tail), - fout(fdopen(_fdout,"w")) { - } - void run() { - int n, nmin = fixed_size ? fixed_size : 1; - while ( (n=in.readable()) >= nmin ) { - if ( fixed_size ) n = fixed_size; - if ( fout ) { - fprintf(fout, head, n); - complex *pin = in.rd(); - for ( int i=0; i > &_in, int _fdout) : + runnable(sch, _in.name), scale(1), fixed_size(0), in(_in), head(_head), format(_format), sep(_sep), tail(_tail), fout(fdopen(_fdout, "w")) + { } - } - T scale; - int fixed_size; // Number of elements per batch, or 0. + void run() + { + int n, nmin = fixed_size ? fixed_size : 1; + while ((n = in.readable()) >= nmin) + { + if (fixed_size) + n = fixed_size; + if (fout) + { + fprintf(fout, head, n); + complex *pin = in.rd(); + for (int i = 0; i < n; ++i) + { + if (i) + fprintf(fout, "%s", sep); + fprintf(fout, format, pin[i].re * scale, pin[i].im * scale); + } + fprintf(fout, "%s", tail); + } + fflush(fout); + in.read(n); + } + } + T scale; + int fixed_size; // Number of elements per batch, or 0. private: - pipereader< complex > in; - const char *head, *format, *sep, *tail; - FILE *fout; + pipereader > in; + const char *head, *format, *sep, *tail; + FILE *fout; }; template -struct file_vectorprinter : runnable { - file_vectorprinter(scheduler *sch, - const char *_head, - const char *_format, - const char *_sep, - const char *_tail, - pipebuf &_in, int _fdout) : - runnable(sch, _in.name), scale(1), in(_in), - head(_head), format(_format), sep(_sep), tail(_tail) { - fout = fdopen(_fdout,"w"); - if ( ! fout ) fatal("fdopen"); - } - void run() { - while ( in.readable() >= 1 ) { - fprintf(fout, head, N); - T (*pin)[N] = in.rd(); - for ( int i=0; i &_in, int _fdout) : + runnable(sch, _in.name), scale(1), in(_in), head(_head), format(_format), sep(_sep), tail(_tail) + { + fout = fdopen(_fdout, "w"); + if (!fout) + { + fatal("leansdr::file_vectorprinter::file_vectorprinter: fdopen"); + } } - fflush(fout); - } - T scale; + void run() + { + while (in.readable() >= 1) + { + fprintf(fout, head, N); + T (*pin)[N] = in.rd(); + for (int i = 0; i < N; ++i) + { + if (i) + fprintf(fout, "%s", sep); + fprintf(fout, format, (*pin)[i] * scale); + } + fprintf(fout, "%s", tail); + in.read(1); + } + fflush(fout); + } + T scale; private: - pipereader in; - const char *head, *format, *sep, *tail; - FILE *fout; + pipereader in; + const char *head, *format, *sep, *tail; + FILE *fout; }; // [itemcounter] writes the number of input items to the output [pipebuf]. // [Tout] must be a numeric type. template -struct itemcounter : runnable { - itemcounter(scheduler *sch, pipebuf &_in, pipebuf &_out) - : runnable(sch, "itemcounter"), - in(_in), out(_out) { - } - void run() { - if ( out.writable() < 1 ) return; - unsigned long count = in.readable(); - if ( ! count ) return; - out.write(count); - in.read(count); - } +struct itemcounter: runnable +{ + itemcounter(scheduler *sch, pipebuf &_in, pipebuf &_out) : + runnable(sch, "itemcounter"), in(_in), out(_out) + { + } + void run() + { + if (out.writable() < 1) + return; + unsigned long count = in.readable(); + if (!count) + return; + out.write(count); + in.read(count); + } private: - pipereader in; - pipewriter out; + pipereader in; + pipewriter out; }; // [decimator] forwards 1 in N sample. template -struct decimator : runnable { - unsigned int d; +struct decimator: runnable +{ + unsigned int d; - decimator(scheduler *sch, int _d, pipebuf &_in, pipebuf &_out) - : runnable(sch, "decimator"), - d(_d), - in(_in), out(_out) { - } - void run() { - unsigned long count = min(in.readable()/d, out.writable()); - T *pin=in.rd(), *pend=pin+count*d, *pout=out.wr(); - for ( ; pin &_in, pipebuf &_out) : + runnable(sch, "decimator"), d(_d), in(_in), out(_out) + { + } + void run() + { + unsigned long count = min(in.readable() / d, out.writable()); + T *pin = in.rd(), *pend = pin + count * d, *pout = out.wr(); + for (; pin < pend; pin += d, ++pout) + *pout = *pin; + in.read(count * d); + out.written(count); + } private: - pipereader in; - pipewriter out; + pipereader in; + pipewriter out; }; - // [rate_estimator] accumulates counts of two quantities - // and periodically outputs their ratio. +// [rate_estimator] accumulates counts of two quantities +// and periodically outputs their ratio. - template - struct rate_estimator : runnable { +template +struct rate_estimator: runnable +{ int sample_size; - rate_estimator(scheduler *sch, - pipebuf &_num, pipebuf &_den, - pipebuf &_rate) - : runnable(sch, "rate_estimator"), - sample_size(10000), - num(_num), den(_den), rate(_rate), - acc_num(0), acc_den(0) { + rate_estimator(scheduler *sch, pipebuf &_num, pipebuf &_den, pipebuf &_rate) : + runnable(sch, "rate_estimator"), sample_size(10000), num(_num), den(_den), rate(_rate), acc_num(0), acc_den(0) + { } - - void run() { - if ( rate.writable() < 1 ) return; - int count = min(num.readable(), den.readable()); - int *pnum=num.rd(), *pden=den.rd(); - for ( int n=count; n--; ++pnum,++pden ) { - acc_num += *pnum; - acc_den += *pden; - } - num.read(count); - den.read(count); - if ( acc_den >= sample_size ) { - rate.write((float)acc_num / acc_den); - acc_num = acc_den = 0; - } + + void run() + { + if (rate.writable() < 1) + return; + int count = min(num.readable(), den.readable()); + int *pnum = num.rd(), *pden = den.rd(); + for (int n = count; n--; ++pnum, ++pden) + { + acc_num += *pnum; + acc_den += *pden; + } + num.read(count); + den.read(count); + if (acc_den >= sample_size) + { + rate.write((float) acc_num / acc_den); + acc_num = acc_den = 0; + } } - - private: + +private: pipereader num, den; pipewriter rate; T acc_num, acc_den; - }; +}; +// SERIALIZER - // SERIALIZER - - template - struct serializer : runnable { - serializer(scheduler *sch, pipebuf &_in, pipebuf &_out) - : nin(max((size_t)1,sizeof(Tin)/sizeof(Tout))), - nout(max((size_t)1,sizeof(Tout)/sizeof(Tin))), - in(_in), out(_out,nout) +template +struct serializer: runnable +{ + serializer(scheduler *sch __attribute__((unused)), pipebuf &_in, pipebuf &_out) : + nin(max((size_t) 1, sizeof(Tin) / sizeof(Tout))), nout(max((size_t) 1, sizeof(Tout) / sizeof(Tin))), in(_in), out(_out, nout) { - if ( nin*sizeof(Tin) != nout*sizeof(Tout) ) - fail("serializer: incompatible sizes"); + if (nin * sizeof(Tin) != nout * sizeof(Tout)) + { + fail("serializer::serializer", "incompatible sizes"); + return; + } } - void run() { - while ( in.readable()>=nin && out.writable()>=nout ) { - memcpy(out.wr(), in.rd(), nout*sizeof(Tout)); - in.read(nin); - out.written(nout); - } + void run() + { + while (in.readable() >= nin && out.writable() >= nout) + { + memcpy(out.wr(), in.rd(), nout * sizeof(Tout)); + in.read(nin); + out.written(nout); + } } - private: +private: int nin, nout; pipereader in; pipewriter out; - }; // serializer +}; +// serializer +// [buffer_reader] reads from a user-supplied buffer. - // [buffer_reader] reads from a user-supplied buffer. - - template - struct buffer_reader : runnable { - buffer_reader(scheduler *sch, T *_data, int _count, pipebuf &_out) - : runnable(sch, "buffer_reader"), - data(_data), count(_count), out(_out), pos(0) { +template +struct buffer_reader: runnable +{ + buffer_reader(scheduler *sch, T *_data, int _count, pipebuf &_out) : + runnable(sch, "buffer_reader"), data(_data), count(_count), out(_out), pos(0) + { } - void run() { - int n = min(out.writable(), (unsigned long)(count-pos)); - memcpy(out.wr(), &data[pos], n*sizeof(T)); - pos += n; - out.written(n); + void run() + { + int n = min(out.writable(), (unsigned long) (count - pos)); + memcpy(out.wr(), &data[pos], n * sizeof(T)); + pos += n; + out.written(n); } - private: +private: T *data; int count; pipewriter out; int pos; - }; // buffer_reader +}; +// buffer_reader +// [buffer_writer] writes to a user-supplied buffer. - // [buffer_writer] writes to a user-supplied buffer. - - template - struct buffer_writer : runnable { - buffer_writer(scheduler *sch, pipebuf &_in, T *_data, int _count) - : runnable(sch, "buffer_reader"), - in(_in), data(_data), count(_count), pos(0) { +template +struct buffer_writer: runnable +{ + buffer_writer(scheduler *sch, pipebuf &_in, T *_data, int _count) : + runnable(sch, "buffer_reader"), in(_in), data(_data), count(_count), pos(0) + { } - void run() { - int n = min(in.readable(), (unsigned long)(count-pos)); - memcpy(&data[pos], in.rd(), n*sizeof(T)); - in.read(n); - pos += n; + void run() + { + int n = min(in.readable(), (unsigned long) (count - pos)); + memcpy(&data[pos], in.rd(), n * sizeof(T)); + in.read(n); + pos += n; } - private: +private: pipereader in; T *data; int count; int pos; - }; // buffer_writer +}; +// buffer_writer -} // namespace +}// namespace #endif // LEANSDR_GENERIC_H diff --git a/plugins/channelrx/demoddatv/leansdr/hdlc.h b/plugins/channelrx/demoddatv/leansdr/hdlc.h index efc24c685..ac936f5d1 100644 --- a/plugins/channelrx/demoddatv/leansdr/hdlc.h +++ b/plugins/channelrx/demoddatv/leansdr/hdlc.h @@ -3,107 +3,151 @@ #include "leansdr/framework.h" -namespace leansdr { +namespace leansdr +{ - // HDLC deframer - - struct hdlc_dec { +// HDLC deframer + +struct hdlc_dec +{ hdlc_dec(int _minframesize, // Including CRC, excluding HDLC flags. - int _maxframesize, - bool _invert) - : minframesize(_minframesize), maxframesize(_maxframesize), - invertmask(_invert?0xff:0), - framebuf(new u8[maxframesize]), - debug(false) + int _maxframesize, bool _invert) : + minframesize(_minframesize), + maxframesize(_maxframesize), + invertmask(_invert ? 0xff : 0), + framebuf(new u8[maxframesize]), + debug(false) { - reset(); - } - - void reset() { shiftreg=0; inframe=false; } + byte_out = 0; + nbits_out = 0; + framesize = 0; + crc16 = 0; + + reset(); + } + + void reset() + { + shiftreg = 0; + inframe = false; + } + + void begin_frame() + { + framesize = 0; + crc16 = crc16_init; + } - void begin_frame() { framesize=0; crc16=crc16_init; } - // Decode (*ppin)[count] as MSB-packed HDLC bitstream. // Return pointer to buffer[*pdatasize], or NULL if no valid frame. // Return number of discarded bytes in *discarded. // Return number of checksum errors in *fcs_errors. // *ppin will have increased by at least 1 (unless count==0). - u8 *decode(u8 **ppin, int count, - int *pdatasize, int *hdlc_errors, int *fcs_errors) { - *hdlc_errors = 0; - *fcs_errors = 0; - *pdatasize = -1; - u8 *pin=*ppin, *pend=pin+count; - for ( ; pin>1) | bit_in; - if ( ! inframe ) { - if ( shiftreg == 0x7e ) { // HDLC flag 01111110 - inframe = true; - nbits_out = 0; - begin_frame(); - } - } else { - if ( (shiftreg&0xfe) == 0x7c ) { // 0111110x HDLC stuffing - // Unstuff this 0 - } else if ( shiftreg == 0x7e ) { // 01111110 HDLC flag - if ( nbits_out != 7 ) { - // Not at byte boundary - if ( debug ) fprintf(stderr, "^"); - ++*hdlc_errors; - } else { - // Checksum - crc16 ^= 0xffff; - if ( framesize<2 || framesize= minframesize ) ++*fcs_errors; - } else { - if ( debug ) fprintf(stderr, "_"); - // This will trigger output, but we finish the byte first. - *pdatasize = framesize-2; - } - } - nbits_out = 0; - begin_frame(); - // Keep processing up to 7 remaining bits from byte_in. - // Special cases 0111111 and 1111111 cannot affect *pdatasize. - } else if ( shiftreg == 0xfe ) { // 11111110 HDLC invalid - if ( framesize ) { - if ( debug ) fprintf(stderr, "^"); - ++*hdlc_errors; - } - inframe = false; - } else { // Data bit - byte_out = (byte_out>>1) | bit_in; // HDLC is LSB first - ++nbits_out; - if ( nbits_out == 8 ) { - if ( framesize < maxframesize ) { - framebuf[framesize++] = byte_out; - crc16_byte(byte_out); - } - nbits_out = 0; - } - } - } // inframe - } // bits - if ( *pdatasize != -1 ) { - // Found a complete frame - *ppin = pin+1; - return framebuf; - } - } - *ppin = pin; - return NULL; + u8 *decode(u8 **ppin, int count, int *pdatasize, int *hdlc_errors, + int *fcs_errors) + { + *hdlc_errors = 0; + *fcs_errors = 0; + *pdatasize = -1; + u8 *pin = *ppin, *pend = pin + count; + for (; pin < pend; ++pin) + { + u8 byte_in = (*pin) ^ invertmask; + for (int bits = 8; bits--; byte_in <<= 1) + { + u8 bit_in = byte_in & 128; + shiftreg = (shiftreg >> 1) | bit_in; + if (!inframe) + { + if (shiftreg == 0x7e) + { // HDLC flag 01111110 + inframe = true; + nbits_out = 0; + begin_frame(); + } + } + else + { + if ((shiftreg & 0xfe) == 0x7c) + { // 0111110x HDLC stuffing + // Unstuff this 0 + } + else if (shiftreg == 0x7e) + { // 01111110 HDLC flag + if (nbits_out != 7) + { + // Not at byte boundary + if (debug) + fprintf(stderr, "^"); + ++*hdlc_errors; + } + else + { + // Checksum + crc16 ^= 0xffff; + if (framesize < 2 || framesize < minframesize + || crc16 != crc16_check) + { + if (debug) + fprintf(stderr, "!"); + ++*hdlc_errors; + // Do not report random noise as FCS errors + if (framesize >= minframesize) + ++*fcs_errors; + } + else + { + if (debug) + fprintf(stderr, "_"); + // This will trigger output, but we finish the byte first. + *pdatasize = framesize - 2; + } + } + nbits_out = 0; + begin_frame(); + // Keep processing up to 7 remaining bits from byte_in. + // Special cases 0111111 and 1111111 cannot affect *pdatasize. + } + else if (shiftreg == 0xfe) + { // 11111110 HDLC invalid + if (framesize) + { + if (debug) + fprintf(stderr, "^"); + ++*hdlc_errors; + } + inframe = false; + } + else + { // Data bit + byte_out = (byte_out >> 1) | bit_in; // HDLC is LSB first + ++nbits_out; + if (nbits_out == 8) + { + if (framesize < maxframesize) + { + framebuf[framesize++] = byte_out; + crc16_byte(byte_out); + } + nbits_out = 0; + } + } + } // inframe + } // bits + if (*pdatasize != -1) + { + // Found a complete frame + *ppin = pin + 1; + return framebuf; + } + } + *ppin = pin; + return NULL; } - private: +private: // Config int minframesize, maxframesize; u8 invertmask; @@ -119,151 +163,170 @@ namespace leansdr { static const u16 crc16_init = 0xffff; static const u16 crc16_poly = 0x8408; // 0x1021 MSB-first static const u16 crc16_check = 0x0f47; - void crc16_byte(u8 data) { - crc16 ^= data; - for ( int bit=8; bit--; ) - crc16 = (crc16&1) ? (crc16>>1)^crc16_poly : (crc16>>1); - } - - public: - bool debug; - }; // hdlc_dec - - - // HDLC synchronizer with polarity detection - - struct hdlc_sync : runnable { - hdlc_sync(scheduler *sch, - pipebuf &_in, // Packed bits - pipebuf &_out, // Bytes - int _minframesize, // Including CRC, excluding HDLC flags. - int _maxframesize, - // Status - pipebuf *_lock_out=NULL, - pipebuf *_framecount_out=NULL, - pipebuf *_fcserrcount_out=NULL, - pipebuf *_hdlcbytecount_out=NULL, - pipebuf *_databytecount_out=NULL) - : runnable(sch, "hdlc_sync"), - minframesize(_minframesize), - maxframesize(_maxframesize), - chunk_size(maxframesize+2), - in(_in), out(_out, _maxframesize+chunk_size), - lock_out(opt_writer(_lock_out)), - framecount_out(opt_writer(_framecount_out)), - fcserrcount_out(opt_writer(_fcserrcount_out)), - hdlcbytecount_out(opt_writer(_hdlcbytecount_out)), - databytecount_out(opt_writer(_databytecount_out)), - cur_sync(0), resync_phase(0), - lock_state(false), - resync_period(32), - header16(false) + void crc16_byte(u8 data) { - for ( int s=0; sdebug = sch->debug; - errslot = 0; + crc16 ^= data; + for (int bit = 8; bit--;) + crc16 = (crc16 & 1) ? (crc16 >> 1) ^ crc16_poly : (crc16 >> 1); } - void run() { - if ( ! opt_writable(lock_out) || - ! opt_writable(framecount_out) || - ! opt_writable(fcserrcount_out) || - ! opt_writable(hdlcbytecount_out) || - ! opt_writable(databytecount_out) ) return; +public: + bool debug; +}; +// hdlc_dec - bool previous_lock_state = lock_state; - int fcserrcount=0, framecount=0; - int hdlcbytecount=0, databytecount=0; +// HDLC synchronizer with polarity detection - // Note: hdlc_dec may already hold one frame ready for output. - while ( in.readable() >= chunk_size && - out.writable() >= maxframesize+chunk_size ) { - if ( ! resync_phase ) { - // Once every resync_phase, try all decoders - for ( int s=0; sreset(); - syncs[s].errhist[errslot] = 0; - for ( u8 *pin=in.rd(), *pend=pin+chunk_size; pindecode(&pin, pend-pin, &datasize, - &hdlc_errors, &fcs_errors); - syncs[s].errhist[errslot] += hdlc_errors; - if ( s == cur_sync ) { - if ( f ) { - lock_state = true; - output_frame(f, datasize); - databytecount += datasize; - ++framecount; - } - fcserrcount += fcs_errors; - framecount += fcs_errors; - } - } - } - errslot = (errslot+1) % NERRHIST; - // Switch to another sync option ? - // Compare total error counts over about NERRHIST frames. - int total_errors[NSYNCS]; - for ( int s=0; sdebug ) fprintf(stderr, "[%d:%d->%d:%d]", - cur_sync, total_errors[cur_sync], - best, total_errors[best]); - // No verbose messages on candidate syncs - syncs[cur_sync].dec->debug = false; - cur_sync = best; - syncs[cur_sync].dec->debug = sch->debug; - } - } else { - // Use only the currently selected decoder - for ( u8 *pin=in.rd(), *pend=pin+chunk_size; pindecode(&pin, pend-pin, &datasize, - &hdlc_errors, &fcs_errors); - if ( f ) { - lock_state = true; - output_frame(f, datasize); - databytecount += datasize; - ++framecount; - } - fcserrcount += fcs_errors; - framecount += fcs_errors; - } - } // resync_phase - in.read(chunk_size); - hdlcbytecount += chunk_size; - if ( ++resync_phase >= resync_period ) resync_phase = 0; - } // Work to do - - if ( lock_state != previous_lock_state ) - opt_write(lock_out, lock_state?1:0); - opt_write(framecount_out, framecount); - opt_write(fcserrcount_out, fcserrcount); - opt_write(hdlcbytecount_out, hdlcbytecount); - opt_write(databytecount_out, databytecount); +struct hdlc_sync: runnable +{ + hdlc_sync(scheduler *sch, + pipebuf &_in, // Packed bits + pipebuf &_out, // Bytes + int _minframesize, // Including CRC, excluding HDLC flags. + int _maxframesize, + // Status + pipebuf *_lock_out = NULL, + pipebuf *_framecount_out = NULL, + pipebuf *_fcserrcount_out = NULL, + pipebuf *_hdlcbytecount_out = NULL, + pipebuf *_databytecount_out = NULL) : + runnable(sch, "hdlc_sync"), minframesize(_minframesize), maxframesize( + _maxframesize), chunk_size(maxframesize + 2), in(_in), out( + _out, _maxframesize + chunk_size), lock_out( + opt_writer(_lock_out)), framecount_out( + opt_writer(_framecount_out)), fcserrcount_out( + opt_writer(_fcserrcount_out)), hdlcbytecount_out( + opt_writer(_hdlcbytecount_out)), databytecount_out( + opt_writer(_databytecount_out)), cur_sync(0), resync_phase( + 0), lock_state(false), resync_period(32), header16(false) + { + for (int s = 0; s < NSYNCS; ++s) + { + syncs[s].dec = new hdlc_dec(minframesize, maxframesize, s != 0); + for (int h = 0; h < NERRHIST; ++h) + syncs[s].errhist[h] = 0; + } + syncs[cur_sync].dec->debug = sch->debug; + errslot = 0; } - private: - void output_frame(u8 *f, int size) { - if ( header16 ) { - // Removed 16-bit CRC, add 16-bit prefix -> Still <= maxframesize. - out.write(size >> 8); - out.write(size & 255); - } - memcpy(out.wr(), f, size); - out.written(size); - opt_write(framecount_out, 1); + void run() + { + if (!opt_writable(lock_out) || !opt_writable(framecount_out) + || !opt_writable(fcserrcount_out) + || !opt_writable(hdlcbytecount_out) + || !opt_writable(databytecount_out)) + return; + + bool previous_lock_state = lock_state; + int fcserrcount = 0, framecount = 0; + int hdlcbytecount = 0, databytecount = 0; + + // Note: hdlc_dec may already hold one frame ready for output. + while ((long) in.readable() >= chunk_size + && (long) out.writable() >= maxframesize + chunk_size) + { + if (!resync_phase) + { + // Once every resync_phase, try all decoders + for (int s = 0; s < NSYNCS; ++s) + { + if (s != cur_sync) + syncs[s].dec->reset(); + syncs[s].errhist[errslot] = 0; + for (u8 *pin = in.rd(), *pend = pin + chunk_size; + pin < pend;) + { + int datasize, hdlc_errors, fcs_errors; + u8 *f = syncs[s].dec->decode(&pin, pend - pin, + &datasize, &hdlc_errors, &fcs_errors); + syncs[s].errhist[errslot] += hdlc_errors; + if (s == cur_sync) + { + if (f) + { + lock_state = true; + output_frame(f, datasize); + databytecount += datasize; + ++framecount; + } + fcserrcount += fcs_errors; + framecount += fcs_errors; + } + } + } + errslot = (errslot + 1) % NERRHIST; + // Switch to another sync option ? + // Compare total error counts over about NERRHIST frames. + int total_errors[NSYNCS]; + for (int s = 0; s < NSYNCS; ++s) + { + total_errors[s] = 0; + for (int h = 0; h < NERRHIST; ++h) + total_errors[s] += syncs[s].errhist[h]; + } + int best = cur_sync; + for (int s = 0; s < NSYNCS; ++s) + if (total_errors[s] < total_errors[best]) + best = s; + if (best != cur_sync) + { + lock_state = false; + if (sch->debug) + fprintf(stderr, "[%d:%d->%d:%d]", cur_sync, + total_errors[cur_sync], best, + total_errors[best]); + // No verbose messages on candidate syncs + syncs[cur_sync].dec->debug = false; + cur_sync = best; + syncs[cur_sync].dec->debug = sch->debug; + } + } + else + { + // Use only the currently selected decoder + for (u8 *pin = in.rd(), *pend = pin + chunk_size; pin < pend;) + { + int datasize, hdlc_errors, fcs_errors; + u8 *f = syncs[cur_sync].dec->decode(&pin, pend - pin, + &datasize, &hdlc_errors, &fcs_errors); + if (f) + { + lock_state = true; + output_frame(f, datasize); + databytecount += datasize; + ++framecount; + } + fcserrcount += fcs_errors; + framecount += fcs_errors; + } + } // resync_phase + in.read(chunk_size); + hdlcbytecount += chunk_size; + if (++resync_phase >= resync_period) + resync_phase = 0; + } // Work to do + + if (lock_state != previous_lock_state) + opt_write(lock_out, lock_state ? 1 : 0); + opt_write(framecount_out, framecount); + opt_write(fcserrcount_out, fcserrcount); + opt_write(hdlcbytecount_out, hdlcbytecount); + opt_write(databytecount_out, databytecount); + } + +private: + void output_frame(u8 *f, int size) + { + if (header16) + { + // Removed 16-bit CRC, add 16-bit prefix -> Still <= maxframesize. + out.write(size >> 8); + out.write(size & 255); + } + memcpy(out.wr(), f, size); + out.written(size); + opt_write(framecount_out, 1); } int minframesize, maxframesize; @@ -275,19 +338,21 @@ namespace leansdr { pipewriter *hdlcbytecount_out, *databytecount_out; static const int NSYNCS = 2; // Two possible polarities static const int NERRHIST = 2; // Compare error counts over two frames - struct { - hdlc_dec *dec; - int errhist[NERRHIST]; + struct + { + hdlc_dec *dec; + int errhist[NERRHIST]; } syncs[NSYNCS]; int errslot; int cur_sync; int resync_phase; bool lock_state; - public: +public: int resync_period; bool header16; // Output length prefix - }; // hdlc_sync +}; +// hdlc_sync -} // namespace +}// namespace #endif // LEANSDR_HDLC_H diff --git a/plugins/channelrx/demoddatv/leansdr/incrementalarray.h b/plugins/channelrx/demoddatv/leansdr/incrementalarray.h new file mode 100644 index 000000000..ac2c33db5 --- /dev/null +++ b/plugins/channelrx/demoddatv/leansdr/incrementalarray.h @@ -0,0 +1,64 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_UTIL_INCREMENTALARRAY_H_ +#define SDRBASE_UTIL_INCREMENTALARRAY_H_ + +#include + +namespace leansdr +{ + +template +class IncrementalArray +{ +public: + T *m_array; + + IncrementalArray(); + ~IncrementalArray(); + + void allocate(uint32_t size); + +private: + uint32_t m_size; +}; + +template +IncrementalArray::IncrementalArray() : + m_array(0), + m_size(0) +{ +} + +template +IncrementalArray::~IncrementalArray() +{ + if (m_array) { delete[] m_array; } +} + +template +void IncrementalArray::allocate(uint32_t size) +{ + if (size <= m_size) { return; } + if (m_array) { delete[] m_array; } + m_array = new T[size]; + m_size = size; +} + +} // namespace + +#endif /* SDRBASE_UTIL_INCREMENTALARRAY_H_ */ diff --git a/plugins/channelrx/demoddatv/leansdr/math.h b/plugins/channelrx/demoddatv/leansdr/math.h index c25d64c5d..26a94b250 100644 --- a/plugins/channelrx/demoddatv/leansdr/math.h +++ b/plugins/channelrx/demoddatv/leansdr/math.h @@ -9,7 +9,7 @@ namespace leansdr { template struct complex { T re, im; - complex() { } + complex() : re(0), im(0) { } complex(T x) : re(x), im(0) { } complex(T x, T y) : re(x), im(y) { } inline void operator +=(const complex &x) { re+=x.re; im+=x.im; } @@ -34,7 +34,7 @@ namespace leansdr { complex operator *(const T &k, const complex &a) { return complex(k*a.re, k*a.im); } - + // TBD Optimize with dedicated instructions inline int hamming_weight(uint8_t x) { static const int lut[16] = { 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4 }; @@ -72,7 +72,7 @@ namespace leansdr { for ( ; x; ++n,x>>=1 ) ; return n; } - + // Pre-computed sin/cos for 16-bit angles struct trig16 { diff --git a/plugins/channelrx/demoddatv/leansdr/rs.h b/plugins/channelrx/demoddatv/leansdr/rs.h index fa68a4d06..0df614409 100644 --- a/plugins/channelrx/demoddatv/leansdr/rs.h +++ b/plugins/channelrx/demoddatv/leansdr/rs.h @@ -5,88 +5,118 @@ #define DEBUG_RS 0 -namespace leansdr { +namespace leansdr +{ - // Finite group GF(2^N). +// Finite group GF(2^N). - // GF(2) is the ring ({0,1},+,*). - // GF(2)[X] is the ring of polynomials with coefficients in GF(2). - // P(X) is an irreducible polynomial of GF(2)[X]. - // N is the degree of P(x). - // P is the bitfield representation of P(X), with degree 0 at LSB. - // GF(2)[X]/(P) is GF(2)[X] modulo P(X). - // (GF(2)[X]/(P), +) is a group with 2^N elements. - // (GF(2)[X]/(P)*, *) is a group with 2^N-1 elements. - // (GF(2)[X]/(P), +, *) is a field with 2^N elements, noted GF(2^N). - // Te is a C++ integer type for representing elements of GF(2^N). - // "0" is 0 - // "1" is 1 - // "2" is X - // "3" is X+1 - // "4" is X^2 - // Tp is a C++ integer type for representing P(X) (1 bit larger than Te). - // ALPHA is a primitive element of GF(2^N). Usually "2"=[X] is chosen. +// GF(2) is the ring ({0,1},+,*). +// GF(2)[X] is the ring of polynomials with coefficients in GF(2). +// P(X) is an irreducible polynomial of GF(2)[X]. +// N is the degree of P(x). +// P is the bitfield representation of P(X), with degree 0 at LSB. +// GF(2)[X]/(P) is GF(2)[X] modulo P(X). +// (GF(2)[X]/(P), +) is a group with 2^N elements. +// (GF(2)[X]/(P)*, *) is a group with 2^N-1 elements. +// (GF(2)[X]/(P), +, *) is a field with 2^N elements, noted GF(2^N). +// Te is a C++ integer type for representing elements of GF(2^N). +// "0" is 0 +// "1" is 1 +// "2" is X +// "3" is X+1 +// "4" is X^2 +// Tp is a C++ integer type for representing P(X) (1 bit larger than Te). +// ALPHA is a primitive element of GF(2^N). Usually "2"=[X] is chosen. - template - struct gf2x_p { - gf2x_p() { - if ( ALPHA != 2 ) fail("alpha!=2 not implemented"); - // Precompute log and exp tables. - Tp alpha_i = 1; - for ( Tp i=0; i<(1< +struct gf2x_p +{ + gf2x_p() + { + if (ALPHA != 2) + { + fail("gf2x_p::gf2x_p", "alpha!=2 not implemented"); + return; + } + // Precompute log and exp tables. + Tp alpha_i = 1; + for (Tp i = 0; i < (1 << N); ++i) + { + lut_exp[i] = alpha_i; + lut_exp[((1 << N) - 1) + i] = alpha_i; + lut_log[alpha_i] = i; + alpha_i <<= 1; // Multiply by alpha=[X] i.e. increase degrees + if (alpha_i & (1 << N)) + alpha_i ^= P; // Modulo P iteratively + } } static const Te alpha = ALPHA; - inline Te add(Te x, Te y) { return x ^ y; } // Addition modulo 2 - inline Te sub(Te x, Te y) { return x ^ y; } // Subtraction modulo 2 - inline Te mul(Te x, Te y) { - if ( !x || !y ) return 0; - return lut_exp[lut_log[x] + lut_log[y]]; + inline Te add(Te x, Te y) + { + return x ^ y; + } // Addition modulo 2 + inline Te sub(Te x, Te y) + { + return x ^ y; + } // Subtraction modulo 2 + inline Te mul(Te x, Te y) + { + if (!x || !y) + return 0; + return lut_exp[lut_log[x] + lut_log[y]]; } - inline Te div(Te x, Te y) { - //if ( ! y ) fail("div"); // TODO - if ( ! x ) return 0; - return lut_exp[lut_log[x] + ((1< gf; u8 G[17]; // { G_16, ..., G_0 } - rs_engine() { - // EN 300 421, section 4.4.2, Code Generator Polynomial - // G(X) = (X-alpha^0)*...*(X-alpha^15) - for ( int i=0; i<=16; ++i ) G[i] = (i==16) ? 1 : 0; // Init G=1 - for ( int d=0; d<16; ++d ) { - // Multiply by (X-alpha^d) - // G := X*G - alpha^d*G - for ( int i=0; i<=16; ++i ) - G[i] = gf.sub((i==16)?0:G[i+1], gf.mul(gf.exp(d),G[i])); - } + rs_engine() + { + // EN 300 421, section 4.4.2, Code Generator Polynomial + // G(X) = (X-alpha^0)*...*(X-alpha^15) + for (int i = 0; i <= 16; ++i) + G[i] = (i == 16) ? 1 : 0; // Init G=1 + for (int d = 0; d < 16; ++d) + { + // Multiply by (X-alpha^d) + // G := X*G - alpha^d*G + for (int i = 0; i <= 16; ++i) + G[i] = gf.sub((i == 16) ? 0 : G[i + 1], gf.mul(gf.exp(d), G[i])); + } #if DEBUG_RS - fprintf(stderr, "RS generator:"); - for ( int i=0; i<=16; ++i ) fprintf(stderr, " %02x", G[i]); - fprintf(stderr, "\n"); + fprintf(stderr, "RS generator:"); + for ( int i=0; i<=16; ++i ) fprintf(stderr, " %02x", G[i]); + fprintf(stderr, "\n"); #endif } @@ -96,163 +126,190 @@ namespace leansdr { // By convention coefficients are listed by decreasing degree here, // so we can evaluate syndromes of the shortened code without // prepending with 51 zeroes. - bool syndromes(const u8 *poly, u8 *synd) { - bool corrupted = false; - for ( int i=0; i<16; ++i ) { - synd[i] = eval_poly_rev(poly, 204, gf.exp(i)); - if ( synd[i] ) corrupted = true; - } - return corrupted; + bool syndromes(const u8 *poly, u8 *synd) + { + bool corrupted = false; + for (int i = 0; i < 16; ++i) + { + synd[i] = eval_poly_rev(poly, 204, gf.exp(i)); + if (synd[i]) + corrupted = true; + } + return corrupted; } - u8 eval_poly_rev(const u8 *poly, int n, u8 x) { - // poly[0]*x^(n-1) + .. + poly[n-1]*x^0 with Hörner method. - u8 acc = 0; - for ( int i=0; i=0; --deg ) acc = gf.add(gf.mul(acc,x), poly[deg]); - return acc; + u8 eval_poly(const u8 *poly, int deg, u8 x) + { + // poly[0]*x^0 + .. + poly[deg]*x^deg with Hörner method. + u8 acc = 0; + for (; deg >= 0; --deg) + acc = gf.add(gf.mul(acc, x), poly[deg]); + return acc; } // Append parity symbols - void encode(u8 msg[204]) { - // TBD Avoid copying - u8 p[204]; - memcpy(p, msg, 188); - memset(p+188, 0, 16); - // p = msg*X^16 + void encode(u8 msg[204]) + { + // TBD Avoid copying + u8 p[204]; + memcpy(p, msg, 188); + memset(p + 188, 0, 16); + // p = msg*X^16 #if DEBUG_RS - fprintf(stderr, "uncoded:"); - for ( int i=0; i<204; ++i ) fprintf(stderr, " %d", p[i]); - fprintf(stderr, "\n"); + fprintf(stderr, "uncoded:"); + for ( int i=0; i<204; ++i ) fprintf(stderr, " %d", p[i]); + fprintf(stderr, "\n"); #endif - // Compute remainder modulo G - for ( int d=0; d<188; ++d ) { - // Clear monomial of degree d - if ( ! p[d] ) continue; - u8 k = gf.div(p[d], G[0]); - // p(X) := p(X) - k*G(X)*X^(188-d) - for ( int i=0; i<=16; ++i ) - p[d+i] = gf.sub(p[d+i], gf.mul(k,G[i])); - } + // Compute remainder modulo G + for (int d = 0; d < 188; ++d) + { + // Clear monomial of degree d + if (!p[d]) + continue; + u8 k = gf.div(p[d], G[0]); + // p(X) := p(X) - k*G(X)*X^(188-d) + for (int i = 0; i <= 16; ++i) + p[d + i] = gf.sub(p[d + i], gf.mul(k, G[i])); + } #if DEBUG_RS - fprintf(stderr, "coded:"); - for ( int i=0; i<204; ++i ) fprintf(stderr, " %d", p[i]); - fprintf(stderr, "\n"); + fprintf(stderr, "coded:"); + for ( int i=0; i<204; ++i ) fprintf(stderr, " %d", p[i]); + fprintf(stderr, "\n"); #endif - memcpy(msg+188, p+188, 16); + memcpy(msg + 188, p + 188, 16); } // Try to fix errors in pout[]. // If pin[] is provided, errors will be fixed in the original // message too and syndromes will be updated. - bool correct(u8 synd[16], u8 pout[188], - u8 pin[204]=NULL, int *bits_corrected=NULL) { - // Berlekamp - Massey - // http://en.wikipedia.org/wiki/Berlekamp%E2%80%93Massey_algorithm#Code_sample - u8 C[16] = { 1,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 }; // Max degree is L - u8 B[16] = { 1,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 }; - int L = 0; - int m = 1; - u8 b = 1; - for ( int n=0; n<16; ++n ) { - u8 d = synd[n]; - for ( int i=1; i<=L; ++i ) d ^= gf.mul(C[i], synd[n-i]); - if ( ! d ) { - ++m; - } else if ( 2*L <= n ) { - u8 T[16]; - memcpy(T, C, sizeof(T)); - for ( int i=0; i<16-m; ++i ) - C[m+i] ^= gf.mul(d, gf.mul(gf.inv(b),B[i])); - L = n + 1 - L; - memcpy(B, T, sizeof(B)); - b = d; - m = 1; - } else { - for ( int i=0; i<16-m; ++i ) - C[m+i] ^= gf.mul(d, gf.mul(gf.inv(b),B[i])); - ++m; - } - } - // L is the number of errors - // C of degree L is now the error locator polynomial (Lambda) + bool correct(u8 synd[16], u8 pout[188], u8 pin[204] = NULL, int *bits_corrected = NULL) + { + // Berlekamp - Massey + // http://en.wikipedia.org/wiki/Berlekamp%E2%80%93Massey_algorithm#Code_sample + u8 C[16] = + { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; // Max degree is L + u8 B[16] = + { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + int L = 0; + int m = 1; + u8 b = 1; + for (int n = 0; n < 16; ++n) + { + u8 d = synd[n]; + for (int i = 1; i <= L; ++i) + d ^= gf.mul(C[i], synd[n - i]); + if (!d) + { + ++m; + } + else if (2 * L <= n) + { + u8 T[16]; + memcpy(T, C, sizeof(T)); + for (int i = 0; i < 16 - m; ++i) + C[m + i] ^= gf.mul(d, gf.mul(gf.inv(b), B[i])); + L = n + 1 - L; + memcpy(B, T, sizeof(B)); + b = d; + m = 1; + } + else + { + for (int i = 0; i < 16 - m; ++i) + C[m + i] ^= gf.mul(d, gf.mul(gf.inv(b), B[i])); + ++m; + } + } + // L is the number of errors + // C of degree L is now the error locator polynomial (Lambda) #if DEBUG_RS - fprintf(stderr, "[L=%d C=",L); - for ( int i=0; i<16; ++i ) fprintf(stderr, " %d", C[i]); - fprintf(stderr, "]\n"); - fprintf(stderr, "[S="); - for ( int i=0; i<16; ++i ) fprintf(stderr, " %d", synd[i]); - fprintf(stderr, "]\n"); + fprintf(stderr, "[L=%d C=",L); + for ( int i=0; i<16; ++i ) fprintf(stderr, " %d", C[i]); + fprintf(stderr, "]\n"); + fprintf(stderr, "[S="); + for ( int i=0; i<16; ++i ) fprintf(stderr, " %d", synd[i]); + fprintf(stderr, "]\n"); #endif - - // Forney - // http://en.wikipedia.org/wiki/Forney_algorithm (2t=16) - - // Compute Omega - u8 omega[16]; - memset(omega, 0, sizeof(omega)); - // TODO loops - for ( int i=0; i<16; ++i ) - for ( int j=0; j<16; ++j ) - if ( i+j < 16 ) omega[i+j] ^= gf.mul(synd[i], C[j]); + + // Forney + // http://en.wikipedia.org/wiki/Forney_algorithm (2t=16) + + // Compute Omega + u8 omega[16]; + memset(omega, 0, sizeof(omega)); + // TODO loops + for (int i = 0; i < 16; ++i) + for (int j = 0; j < 16; ++j) + if (i + j < 16) + omega[i + j] ^= gf.mul(synd[i], C[j]); #if DEBUG_RS - fprintf(stderr, "omega="); - for ( int i=0; i<16; ++i ) fprintf(stderr, " %d", omega[i]); - fprintf(stderr, "\n"); + fprintf(stderr, "omega="); + for ( int i=0; i<16; ++i ) fprintf(stderr, " %d", omega[i]); + fprintf(stderr, "\n"); #endif - - // Compute Lambda' - u8 Cprime[15]; - for ( int i=0; i<15; ++i ) - Cprime[i] = (i&1) ? 0 : C[i+1]; + + // Compute Lambda' + u8 Cprime[15]; + for (int i = 0; i < 15; ++i) + Cprime[i] = (i & 1) ? 0 : C[i + 1]; #if DEBUG_RS - fprintf(stderr, "Cprime="); - for ( int i=0; i<15; ++i ) fprintf(stderr, " %d", Cprime[i]); - fprintf(stderr, "\n"); + fprintf(stderr, "Cprime="); + for ( int i=0; i<15; ++i ) fprintf(stderr, " %d", Cprime[i]); + fprintf(stderr, "\n"); #endif - - // Find zeroes of C by exhaustive search? - // TODO Chien method - int roots_found = 0; - for ( int i=0; i<255; ++i ) { - u8 r = gf.exp(i); // Candidate root alpha^0..alpha^254 - u8 v = eval_poly(C, L, r); - if ( ! v ) { - // r is a root X_k^-1 of the error locator polynomial. - u8 xk = gf.inv(r); - int loc = (255-i) % 255; // == log(xk) + + // Find zeroes of C by exhaustive search? + // TODO Chien method + int roots_found = 0; + for (int i = 0; i < 255; ++i) + { + u8 r = gf.exp(i); // Candidate root alpha^0..alpha^254 + u8 v = eval_poly(C, L, r); + if (!v) + { + // r is a root X_k^-1 of the error locator polynomial. + u8 xk = gf.inv(r); + int loc = (255 - i) % 255; // == log(xk) #if DEBUG_RS - fprintf(stderr, "found root=%d, inv=%d, loc=%d\n", r, xk, loc); + fprintf(stderr, "found root=%d, inv=%d, loc=%d\n", r, xk, loc); #endif - if ( loc < 204 ) { - // Evaluate e_k - u8 num = gf.mul(xk, eval_poly(omega, L, r)); - u8 den = eval_poly(Cprime, 14, r); - u8 e = gf.div(num, den); - // Subtract e from coefficient of degree loc. - // Note: Coeffients listed by decreasing degree in pin[] and pout[]. - if ( bits_corrected ) *bits_corrected += hamming_weight(e); - if ( loc >= 16 ) pout[203-loc] ^= e; - if ( pin ) pin[203-loc] ^= e; - } - if ( ++roots_found == L ) break; - } - } - if ( pin ) - return syndromes(pin, synd); - else - return false; + if (loc < 204) + { + // Evaluate e_k + u8 num = gf.mul(xk, eval_poly(omega, L, r)); + u8 den = eval_poly(Cprime, 14, r); + u8 e = gf.div(num, den); + // Subtract e from coefficient of degree loc. + // Note: Coeffients listed by decreasing degree in pin[] and pout[]. + if (bits_corrected) + *bits_corrected += hamming_weight(e); + if (loc >= 16) + pout[203 - loc] ^= e; + if (pin) + pin[203 - loc] ^= e; + } + if (++roots_found == L) + break; + } + } + if (pin) + return syndromes(pin, synd); + else + return false; } - }; +}; } // namespace diff --git a/plugins/channelrx/demoddatv/leansdr/sdr.h b/plugins/channelrx/demoddatv/leansdr/sdr.h index fbe0161c7..6d87eb90e 100644 --- a/plugins/channelrx/demoddatv/leansdr/sdr.h +++ b/plugins/channelrx/demoddatv/leansdr/sdr.h @@ -3,685 +3,846 @@ #include "leansdr/math.h" #include "leansdr/dsp.h" +#include "leansdr/incrementalarray.h" -namespace leansdr { +namespace leansdr +{ - // Abbreviations for floating-point types +// Abbreviations for floating-point types - typedef float f32; +typedef float f32; - typedef complex cu8; - typedef complex cs8; - typedef complex cu16; - typedef complex cs16; - typedef complex cf32; +typedef complex cu8; +typedef complex cs8; +typedef complex cu16; +typedef complex cs16; +typedef complex cf32; +////////////////////////////////////////////////////////////////////// +// SDR blocks +////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////// - // SDR blocks - ////////////////////////////////////////////////////////////////////// - - // AUTO-NOTCH FILTER +// AUTO-NOTCH FILTER - // Periodically detects the [n__slots] strongest peaks with a FFT, - // removes them with a first-order filter. +// Periodically detects the [n__slots] strongest peaks with a FFT, +// removes them with a first-order filter. - - - template - struct auto_notch : runnable { +template +struct auto_notch: runnable +{ int decimation; float k; - auto_notch(scheduler *sch, pipebuf< complex > &_in, - pipebuf< complex > &_out, int _n__slots, - T _agc_rms_setpoint) - : runnable(sch, "auto_notch"), - decimation(1024*4096), k(0.002), // k(0.01) - fft(4096), - in(_in), out(_out,fft.n), - n__slots(_n__slots), __slots(new slot[n__slots]), - phase(0), gain(1), agc_rms_setpoint(_agc_rms_setpoint) { - for ( int s=0; s[fft.n]; - } + + auto_notch(scheduler *sch, pipebuf > &_in, pipebuf > &_out, int _n__slots, T _agc_rms_setpoint) : + runnable(sch, "auto_notch"), + decimation(1024 * 4096), + k(0.002), // k(0.01) + fft(4096), + in(_in), + out(_out, fft.n), + n__slots(_n__slots), + __slots(new slot[n__slots]), + phase(0), + gain(1), + agc_rms_setpoint(_agc_rms_setpoint) + { + for (int s = 0; s < n__slots; ++s) + { + __slots[s].i = -1; + __slots[s].expj = new complex [fft.n]; + } + m_data.allocate(fft.n); + m_amp.allocate(fft.n); } - void run() { - while ( in.readable()>=fft.n && out.writable()>=fft.n ) { - phase += fft.n; - if ( phase >= decimation ) { - phase -= decimation; - detect(); - } - process(); - in.read(fft.n); - out.written(fft.n); - } + + void run() + { + while (in.readable() >= fft.n && out.writable() >= fft.n) + { + phase += fft.n; + if (phase >= decimation) + { + phase -= decimation; + detect(); + } + process(); + in.read(fft.n); + out.written(fft.n); + } } - void detect() { - complex *pin = in.rd(); - complex data[fft.n]; - float m0=0, m2=0; - for ( int i=0; i m0 ) m0 = gen_abs(pin[i].re); - if ( gen_abs(pin[i].im) > m0 ) m0 = gen_abs(pin[i].im); - } - if ( agc_rms_setpoint && m2 ) { - float rms = gen_sqrt(m2/fft.n); - if ( sch->debug ) fprintf(stderr, "(pow %f max %f)", rms, m0); - float new_gain = agc_rms_setpoint / rms; - gain = gain*0.9 + new_gain*0.1; - } - fft.inplace(data, true); - float amp[fft.n]; - for ( int i=0; i amp[iamax] ) iamax=i; - if ( iamax != s->i ) { - if ( sch->debug ) - fprintf(stderr, "%s: slot %d new peak %d -> %d\n", - name, (int)(s-__slots), s->i, iamax); - s->i = iamax; - s->estim.re = 0; - s->estim.im = 0; - s->estt = 0; - for ( int i=0; ii * i / fft.n; - s->expj[i].re = cosf(a); - s->expj[i].im = sinf(a); - } - } - amp[iamax] = 0; - if ( iamax-1 >= 0 ) amp[iamax-1] = 0; - if ( iamax+1 < fft.n ) amp[iamax+1] = 0; - } + + void detect() + { + complex *pin = in.rd(); + //complex data[fft.n]; + complex *data = m_data.m_array; + float m0 = 0, m2 = 0; + + for (unsigned int i = 0; i < fft.n; ++i) + { + data[i].re = pin[i].re; + data[i].im = pin[i].im; + m2 += (float) pin[i].re * pin[i].re + (float) pin[i].im * pin[i].im; + if (gen_abs(pin[i].re) > m0) + m0 = gen_abs(pin[i].re); + if (gen_abs(pin[i].im) > m0) + m0 = gen_abs(pin[i].im); + } + + if (agc_rms_setpoint && m2) + { + float rms = gen_sqrt(m2 / fft.n); + if (sch->debug) + fprintf(stderr, "(pow %f max %f)", rms, m0); + float new_gain = agc_rms_setpoint / rms; + gain = gain * 0.9 + new_gain * 0.1; + } + + fft.inplace(data, true); + //float amp[fft.n]; + float *amp = m_amp.m_array; + + for (unsigned int i = 0; i < fft.n; ++i) { + amp[i] = hypotf(data[i].re, data[i].im); + } + + for (slot *s = __slots; s < __slots + n__slots; ++s) + { + int iamax = 0; + for (unsigned int i = 0; i < fft.n; ++i) + { + if (amp[i] > amp[iamax]) { + iamax = i; + } + } + if (iamax != s->i) + { + if (sch->debug) + fprintf(stderr, "%s: slot %d new peak %d -> %d\n", name, + (int) (s - __slots), s->i, iamax); + s->i = iamax; + s->estim.re = 0; + s->estim.im = 0; + s->estt = 0; + for (unsigned int i = 0; i < fft.n; ++i) + { + float a = 2 * M_PI * s->i * i / fft.n; + s->expj[i].re = cosf(a); + s->expj[i].im = sinf(a); + } + } + amp[iamax] = 0; + if (iamax - 1 >= 0) { + amp[iamax - 1] = 0; + } + if (iamax + 1 < (int) fft.n) { + amp[iamax + 1] = 0; + } + } } - void process() { - complex *pin=in.rd(), *pend=pin+fft.n, *pout=out.wr(); - for ( slot *s=__slots; s<__slots+n__slots; ++s ) s->ej = s->expj; - for ( ; pin out = *pin; - // TODO Optimize for n__slots==1 ? - for ( slot *s=__slots; s<__slots+n__slots; ++s->ej,++s ) { - complex bb(pin->re*s->ej->re + pin->im*s->ej->im, - -pin->re*s->ej->im + pin->im*s->ej->re); - s->estim.re = bb.re*k + s->estim.re*(1-k); - s->estim.im = bb.im*k + s->estim.im*(1-k); - complex sub(s->estim.re*s->ej->re - s->estim.im*s->ej->im, - s->estim.re*s->ej->im + s->estim.im*s->ej->re); - out.re -= sub.re; - out.im -= sub.im; - } - pout->re = gain * out.re; - pout->im = gain * out.im; - } + + void process() + { + complex *pin = in.rd(), *pend = pin + fft.n, *pout = out.wr(); + for (slot *s = __slots; s < __slots + n__slots; ++s) + s->ej = s->expj; + for (; pin < pend; ++pin, ++pout) + { + complex out = *pin; + // TODO Optimize for n__slots==1 ? + for (slot *s = __slots; s < __slots + n__slots; ++s->ej, ++s) + { + complex bb(pin->re * s->ej->re + pin->im * s->ej->im, + -pin->re * s->ej->im + pin->im * s->ej->re); + s->estim.re = bb.re * k + s->estim.re * (1 - k); + s->estim.im = bb.im * k + s->estim.im * (1 - k); + complex sub( + s->estim.re * s->ej->re - s->estim.im * s->ej->im, + s->estim.re * s->ej->im + s->estim.im * s->ej->re); + out.re -= sub.re; + out.im -= sub.im; + } + pout->re = gain * out.re; + pout->im = gain * out.im; + } } - - private: + +private: cfft_engine fft; - pipereader< complex > in; - pipewriter< complex > out; + pipereader > in; + pipewriter > out; int n__slots; - struct slot { - int i; - complex estim; - complex *expj, *ej; - int estt; - } *__slots; + struct slot + { + int i; + complex estim; + complex *expj, *ej; + int estt; + }*__slots; + int phase; float gain; T agc_rms_setpoint; - }; + IncrementalArray > m_data; + IncrementalArray m_amp; +}; +// SIGNAL STRENGTH ESTIMATOR - // SIGNAL STRENGTH ESTIMATOR +// Outputs RMS values. - // Outputs RMS values. - - template - struct ss_estimator : runnable { +template +struct ss_estimator: runnable +{ unsigned long window_size; // Samples per estimation unsigned long decimation; // Output rate - ss_estimator(scheduler *sch, pipebuf< complex > &_in, pipebuf &_out) - : runnable(sch, "SS estimator"), - window_size(1024), decimation(1024), - in(_in), out(_out), - phase(0) { + + ss_estimator(scheduler *sch, pipebuf > &_in, pipebuf &_out) : + runnable(sch, "SS estimator"), + window_size(1024), + decimation(1024), + in(_in), + out(_out), + phase(0) + { } - void run() { - while ( in.readable()>=window_size && out.writable()>=1 ) { - phase += window_size; - if ( phase >= decimation ) { - phase -= decimation; - complex *p=in.rd(), *pend=p+window_size; - float s = 0; - for ( ; pre*p->re + (float)p->im*p->im; - out.write(sqrtf(s/window_size)); - } - in.read(window_size); - } + + void run() + { + while (in.readable() >= window_size && out.writable() >= 1) + { + phase += window_size; + if (phase >= decimation) + { + phase -= decimation; + complex *p = in.rd(), *pend = p + window_size; + float s = 0; + for (; p < pend; ++p) + s += (float) p->re * p->re + (float) p->im * p->im; + out.write(sqrtf(s / window_size)); + } + in.read(window_size); + } } - private: - pipereader< complex > in; + +private: + pipereader > in; pipewriter out; unsigned long phase; - }; +}; - template - struct ss_amp_estimator : runnable { +template +struct ss_amp_estimator: runnable +{ unsigned long window_size; // Samples per estimation unsigned long decimation; // Output rate - ss_amp_estimator(scheduler *sch, pipebuf< complex > &_in, - pipebuf &_out_ss, - pipebuf &_out_ampmin, pipebuf &_out_ampmax) - : runnable(sch, "SS estimator"), - window_size(1024), decimation(1024), - in(_in), out_ss(_out_ss), - out_ampmin(_out_ampmin), out_ampmax(_out_ampmax), - phase(0) { + + ss_amp_estimator(scheduler *sch, pipebuf > &_in, pipebuf &_out_ss, pipebuf &_out_ampmin, pipebuf &_out_ampmax) : + runnable(sch, "SS estimator"), + window_size(1024), + decimation(1024), + in(_in), + out_ss(_out_ss), + out_ampmin(_out_ampmin), + out_ampmax(_out_ampmax), + phase(0) + { } - void run() { - while ( in.readable() >= window_size && - out_ss.writable() >= 1 && - out_ampmin.writable() >= 1 && - out_ampmax.writable() >= 1 ) { - phase += window_size; - if ( phase >= decimation ) { - phase -= decimation; - complex *p=in.rd(), *pend=p+window_size; - float s2 = 0; - float amin=1e38, amax=0; - for ( ; pre*p->re + (float)p->im*p->im; - s2 += mag2; - float mag = sqrtf(mag2); - if ( mag < amin ) amin = mag; - if ( mag > amax ) amax = mag; - } - out_ss.write(sqrtf(s2/window_size)); - out_ampmin.write(amin); - out_ampmax.write(amax); - } - in.read(window_size); - } + + void run() + { + while (in.readable() >= window_size && out_ss.writable() >= 1 + && out_ampmin.writable() >= 1 && out_ampmax.writable() >= 1) + { + phase += window_size; + if (phase >= decimation) + { + phase -= decimation; + complex *p = in.rd(), *pend = p + window_size; + float s2 = 0; + float amin = 1e38, amax = 0; + for (; p < pend; ++p) + { + float mag2 = (float) p->re * p->re + (float) p->im * p->im; + s2 += mag2; + float mag = sqrtf(mag2); + if (mag < amin) + amin = mag; + if (mag > amax) + amax = mag; + } + out_ss.write(sqrtf(s2 / window_size)); + out_ampmin.write(amin); + out_ampmax.write(amax); + } + in.read(window_size); + } } - private: - pipereader< complex > in; + +private: + pipereader > in; pipewriter out_ss, out_ampmin, out_ampmax; unsigned long phase; - }; - - // AGC +}; - template - struct simple_agc : runnable { +// AGC + +template +struct simple_agc: runnable +{ float out_rms; // Desired RMS output power float bw; // Bandwidth float estimated; // Input power - simple_agc(scheduler *sch, - pipebuf< complex > &_in, - pipebuf< complex > &_out) - : runnable(sch, "AGC"), - out_rms(1), bw(0.001), estimated(0), - in(_in), out(_out) { + + simple_agc(scheduler *sch, pipebuf > &_in, pipebuf > &_out) : + runnable(sch, "AGC"), + out_rms(1), + bw(0.001), + estimated(0), + in(_in), + out(_out) + { } - private: - pipereader< complex > in; - pipewriter< complex > out; + +private: + pipereader > in; + pipewriter > out; static const int chunk_size = 128; - void run() { - while ( in.readable() >= chunk_size && - out.writable() >= chunk_size ) { - complex *pin=in.rd(), *pend=pin+chunk_size; - float amp2 = 0; - for ( ; pinre*pin->re + pin->im*pin->im; - amp2 /= chunk_size; - if ( ! estimated ) estimated = amp2; - estimated = estimated*(1-bw) + amp2*bw; - float gain = estimated ? out_rms / sqrtf(estimated) : 0; - pin = in.rd(); - complex *pout = out.wr(); - float bwcomp = 1 - bw; - for ( ; pinre = pin->re * gain; - pout->im = pin->im * gain; - } - in.read(chunk_size); - out.written(chunk_size); - } + + void run() + { + while (in.readable() >= chunk_size && out.writable() >= chunk_size) + { + complex *pin = in.rd(), *pend = pin + chunk_size; + float amp2 = 0; + for (; pin < pend; ++pin) + amp2 += pin->re * pin->re + pin->im * pin->im; + amp2 /= chunk_size; + if (!estimated) + estimated = amp2; + estimated = estimated * (1 - bw) + amp2 * bw; + float gain = estimated ? out_rms / sqrtf(estimated) : 0; + pin = in.rd(); + complex *pout = out.wr(); + for (; pin < pend; ++pin, ++pout) + { + pout->re = pin->re * gain; + pout->im = pin->im * gain; + } + in.read(chunk_size); + out.written(chunk_size); + } } - }; // simple_agc +}; +// simple_agc +typedef uint16_t u_angle; // [0,2PI[ in 65536 steps +typedef int16_t s_angle; // [-PI,PI[ in 65536 steps - typedef uint16_t u_angle; // [0,2PI[ in 65536 steps - typedef int16_t s_angle; // [-PI,PI[ in 65536 steps +// GENERIC CONSTELLATION DECODING BY LOOK-UP TABLE. +// Metrics and phase errors are pre-computed on a RxR grid. +// R must be a power of 2. +// Up to 256 symbols. - // GENERIC CONSTELLATION DECODING BY LOOK-UP TABLE. - - // Metrics and phase errors are pre-computed on a RxR grid. - // R must be a power of 2. - // Up to 256 symbols. - - struct softsymbol { +struct softsymbol +{ int16_t cost; // For Viterbi with TBM=int16_t uint8_t symbol; - }; +}; - // Target RMS amplitude for AGC - //const float cstln_amp = 73; // Best for 32APSK 9/10 - //const float cstln_amp = 90; // Best for QPSK - //const float cstln_amp = 64; // Best for BPSK - //const float cstln_amp = 75; // Best for BPSK at 45° - const float cstln_amp = 75; // Trade-off +// Target RMS amplitude for AGC +//const float cstln_amp = 73; // Best for 32APSK 9/10 +//const float cstln_amp = 90; // Best for QPSK +//const float cstln_amp = 64; // Best for BPSK +//const float cstln_amp = 75; // Best for BPSK at 45° +const float cstln_amp = 75; // Trade-off - template - struct cstln_lut { +template +struct cstln_lut +{ complex *symbols; int nsymbols; int nrotations; - enum predef { - BPSK, // DVB-S2 (and DVB-S variant) - QPSK, // DVB-S - PSK8, APSK16, APSK32, // DVB-S2 - APSK64E, // DVB-S2X - QAM16, QAM64, QAM256 // For experimentation only + enum predef + { + BPSK, // DVB-S2 (and DVB-S variant) + QPSK, // DVB-S + PSK8, + APSK16, + APSK32, // DVB-S2 + APSK64E, // DVB-S2X + QAM16, + QAM64, + QAM256 // For experimentation only }; - cstln_lut(predef type, float gamma1=1, float gamma2=1, float gamma3=1) { - switch ( type ) { - case BPSK: - nrotations = 2; - nsymbols = 2; - symbols = new complex[nsymbols]; + cstln_lut(predef type, float gamma1 = 1, float gamma2 = 1, float gamma3 = 1) + { + switch (type) + { + case BPSK: + nrotations = 2; + nsymbols = 2; + symbols = new complex [nsymbols]; #if 0 // BPSK at 0° - symbols[0] = polar(1, 2, 0); - symbols[1] = polar(1, 2, 1); + symbols[0] = polar(1, 2, 0); + symbols[1] = polar(1, 2, 1); #else // BPSK at 45° - symbols[0] = polar(1, 8, 1); - symbols[1] = polar(1, 8, 5); + symbols[0] = polar(1, 8, 1); + symbols[1] = polar(1, 8, 5); #endif - make_lut_from_symbols(); - break; - case QPSK: - // EN 300 421, section 4.5 Baseband shaping and modulation - // EN 302 307, section 5.4.1 - nrotations = 4; - nsymbols = 4; - symbols = new complex[nsymbols]; - symbols[0] = polar(1, 4, 0.5); - symbols[1] = polar(1, 4, 3.5); - symbols[2] = polar(1, 4, 1.5); - symbols[3] = polar(1, 4, 2.5); - make_lut_from_symbols(); - break; - case PSK8: - // EN 302 307, section 5.4.2 - nrotations = 8; - nsymbols = 8; - symbols = new complex[nsymbols]; - symbols[0] = polar(1, 8, 1); - symbols[1] = polar(1, 8, 0); - symbols[2] = polar(1, 8, 4); - symbols[3] = polar(1, 8, 5); - symbols[4] = polar(1, 8, 2); - symbols[5] = polar(1, 8, 7); - symbols[6] = polar(1, 8, 3); - symbols[7] = polar(1, 8, 6); - make_lut_from_symbols(); - break; - case APSK16: { - // EN 302 307, section 5.4.3 - float r1 = sqrtf(4 / (1+3*gamma1*gamma1)); - float r2 = gamma1 * r1; - nrotations = 4; - nsymbols = 16; - symbols = new complex[nsymbols]; - symbols[0] = polar(r2, 12, 1.5); - symbols[1] = polar(r2, 12, 10.5); - symbols[2] = polar(r2, 12, 4.5); - symbols[3] = polar(r2, 12, 7.5); - symbols[4] = polar(r2, 12, 0.5); - symbols[5] = polar(r2, 12, 11.5); - symbols[6] = polar(r2, 12, 5.5); - symbols[7] = polar(r2, 12, 6.5); - symbols[8] = polar(r2, 12, 2.5); - symbols[9] = polar(r2, 12, 9.5); - symbols[10] = polar(r2, 12, 3.5); - symbols[11] = polar(r2, 12, 8.5); - symbols[12] = polar(r1, 4, 0.5); - symbols[13] = polar(r1, 4, 3.5); - symbols[14] = polar(r1, 4, 1.5); - symbols[15] = polar(r1, 4, 2.5); - make_lut_from_symbols(); - break; - } - case APSK32: { - // EN 302 307, section 5.4.3 - float r1 = sqrtf(8 / (1+3*gamma1*gamma1+4*gamma2*gamma2)); - float r2 = gamma1 * r1; - float r3 = gamma2 * r1; - nrotations = 4; - nsymbols = 32; - symbols = new complex[nsymbols]; - symbols[0] = polar(r2, 12, 1.5); - symbols[1] = polar(r2, 12, 2.5); - symbols[2] = polar(r2, 12, 10.5); - symbols[3] = polar(r2, 12, 9.5); - symbols[4] = polar(r2, 12, 4.5); - symbols[5] = polar(r2, 12, 3.5); - symbols[6] = polar(r2, 12, 7.5); - symbols[7] = polar(r2, 12, 8.5); - symbols[8] = polar(r3, 16, 1 ); - symbols[9] = polar(r3, 16, 3 ); - symbols[10] = polar(r3, 16, 14 ); - symbols[11] = polar(r3, 16, 12 ); - symbols[12] = polar(r3, 16, 6 ); - symbols[13] = polar(r3, 16, 4 ); - symbols[14] = polar(r3, 16, 9 ); - symbols[15] = polar(r3, 16, 11 ); - symbols[16] = polar(r2, 12, 0.5); - symbols[17] = polar(r1, 4, 0.5); - symbols[18] = polar(r2, 12, 11.5); - symbols[19] = polar(r1, 4, 3.5); - symbols[20] = polar(r2, 12, 5.5); - symbols[21] = polar(r1, 4, 1.5); - symbols[22] = polar(r2, 12, 6.5); - symbols[23] = polar(r1, 4, 2.5); - symbols[24] = polar(r3, 16, 0 ); - symbols[25] = polar(r3, 16, 2 ); - symbols[26] = polar(r3, 16, 15 ); - symbols[27] = polar(r3, 16, 13 ); - symbols[28] = polar(r3, 16, 7 ); - symbols[29] = polar(r3, 16, 5 ); - symbols[30] = polar(r3, 16, 8 ); - symbols[31] = polar(r3, 16, 10 ); - make_lut_from_symbols(); - break; - } - case APSK64E: { - // EN 302 307-2, section 5.4.5, Table 13e - float r1 = - sqrtf(64 / (4+12*gamma1*gamma1+20*gamma2*gamma2+28*gamma3*gamma3)); - float r2 = gamma1 * r1; - float r3 = gamma2 * r1; - float r4 = gamma3 * r1; - nrotations = 4; - nsymbols = 64; - symbols = new complex[nsymbols]; - polar2( 0, r4, 1.0/ 4, 7.0/4, 3.0/ 4, 5.0/ 4); - polar2( 4, r4, 13.0/28, 43.0/28, 15.0/28, 41.0/28); - polar2( 8, r4, 1.0/28, 55.0/28, 27.0/28, 29.0/28); - polar2(12, r1, 1.0/ 4, 7.0/ 4, 3.0/ 4, 5.0/ 4); - polar2(16, r4, 9.0/28, 47.0/28, 19.0/28, 37.0/28); - polar2(20, r4, 11.0/28, 45.0/28, 17.0/28, 39.0/28); - polar2(24, r3, 1.0/20, 39.0/20, 19.0/20, 21.0/20); - polar2(28, r2, 1.0/12, 23.0/12, 11.0/12, 13.0/12); - polar2(32, r4, 5.0/28, 51.0/28, 23.0/28, 33.0/28); - polar2(36, r3, 9.0/20, 31.0/20, 11.0/20, 29.0/20); - polar2(40, r4, 3.0/28, 53.0/28, 25.0/28, 31.0/28); - polar2(44, r2, 5.0/12, 19.0/12, 7.0/12, 17.0/12); - polar2(48, r3, 1.0/ 4, 7.0/ 4, 3.0/ 4, 5.0/ 4); - polar2(52, r3, 7.0/20, 33.0/20, 13.0/20, 27.0/20); - polar2(56, r3, 3.0/20, 37.0/20, 17.0/20, 23.0/20); - polar2(60, r2, 1.0/ 4, 7.0/ 4, 3.0/ 4, 5.0/ 4); - make_lut_from_symbols(); - break; - } - case QAM16: - make_qam(16); - break; - case QAM64: - make_qam(64); - break; - case QAM256: - make_qam(256); - break; - default: - fail("Constellation not implemented"); - } + make_lut_from_symbols(); + break; + case QPSK: + // EN 300 421, section 4.5 Baseband shaping and modulation + // EN 302 307, section 5.4.1 + nrotations = 4; + nsymbols = 4; + symbols = new complex [nsymbols]; + symbols[0] = polar(1, 4, 0.5); + symbols[1] = polar(1, 4, 3.5); + symbols[2] = polar(1, 4, 1.5); + symbols[3] = polar(1, 4, 2.5); + make_lut_from_symbols(); + break; + case PSK8: + // EN 302 307, section 5.4.2 + nrotations = 8; + nsymbols = 8; + symbols = new complex [nsymbols]; + symbols[0] = polar(1, 8, 1); + symbols[1] = polar(1, 8, 0); + symbols[2] = polar(1, 8, 4); + symbols[3] = polar(1, 8, 5); + symbols[4] = polar(1, 8, 2); + symbols[5] = polar(1, 8, 7); + symbols[6] = polar(1, 8, 3); + symbols[7] = polar(1, 8, 6); + make_lut_from_symbols(); + break; + case APSK16: + { + // EN 302 307, section 5.4.3 + float r1 = sqrtf(4 / (1 + 3 * gamma1 * gamma1)); + float r2 = gamma1 * r1; + nrotations = 4; + nsymbols = 16; + symbols = new complex [nsymbols]; + symbols[0] = polar(r2, 12, 1.5); + symbols[1] = polar(r2, 12, 10.5); + symbols[2] = polar(r2, 12, 4.5); + symbols[3] = polar(r2, 12, 7.5); + symbols[4] = polar(r2, 12, 0.5); + symbols[5] = polar(r2, 12, 11.5); + symbols[6] = polar(r2, 12, 5.5); + symbols[7] = polar(r2, 12, 6.5); + symbols[8] = polar(r2, 12, 2.5); + symbols[9] = polar(r2, 12, 9.5); + symbols[10] = polar(r2, 12, 3.5); + symbols[11] = polar(r2, 12, 8.5); + symbols[12] = polar(r1, 4, 0.5); + symbols[13] = polar(r1, 4, 3.5); + symbols[14] = polar(r1, 4, 1.5); + symbols[15] = polar(r1, 4, 2.5); + make_lut_from_symbols(); + break; + } + case APSK32: + { + // EN 302 307, section 5.4.3 + float r1 = sqrtf( + 8 / (1 + 3 * gamma1 * gamma1 + 4 * gamma2 * gamma2)); + float r2 = gamma1 * r1; + float r3 = gamma2 * r1; + nrotations = 4; + nsymbols = 32; + symbols = new complex [nsymbols]; + symbols[0] = polar(r2, 12, 1.5); + symbols[1] = polar(r2, 12, 2.5); + symbols[2] = polar(r2, 12, 10.5); + symbols[3] = polar(r2, 12, 9.5); + symbols[4] = polar(r2, 12, 4.5); + symbols[5] = polar(r2, 12, 3.5); + symbols[6] = polar(r2, 12, 7.5); + symbols[7] = polar(r2, 12, 8.5); + symbols[8] = polar(r3, 16, 1); + symbols[9] = polar(r3, 16, 3); + symbols[10] = polar(r3, 16, 14); + symbols[11] = polar(r3, 16, 12); + symbols[12] = polar(r3, 16, 6); + symbols[13] = polar(r3, 16, 4); + symbols[14] = polar(r3, 16, 9); + symbols[15] = polar(r3, 16, 11); + symbols[16] = polar(r2, 12, 0.5); + symbols[17] = polar(r1, 4, 0.5); + symbols[18] = polar(r2, 12, 11.5); + symbols[19] = polar(r1, 4, 3.5); + symbols[20] = polar(r2, 12, 5.5); + symbols[21] = polar(r1, 4, 1.5); + symbols[22] = polar(r2, 12, 6.5); + symbols[23] = polar(r1, 4, 2.5); + symbols[24] = polar(r3, 16, 0); + symbols[25] = polar(r3, 16, 2); + symbols[26] = polar(r3, 16, 15); + symbols[27] = polar(r3, 16, 13); + symbols[28] = polar(r3, 16, 7); + symbols[29] = polar(r3, 16, 5); + symbols[30] = polar(r3, 16, 8); + symbols[31] = polar(r3, 16, 10); + make_lut_from_symbols(); + break; + } + case APSK64E: + { + // EN 302 307-2, section 5.4.5, Table 13e + float r1 = sqrtf( + 64 + / (4 + 12 * gamma1 * gamma1 + 20 * gamma2 * gamma2 + + 28 * gamma3 * gamma3)); + float r2 = gamma1 * r1; + float r3 = gamma2 * r1; + float r4 = gamma3 * r1; + nrotations = 4; + nsymbols = 64; + symbols = new complex [nsymbols]; + polar2(0, r4, 1.0 / 4, 7.0 / 4, 3.0 / 4, 5.0 / 4); + polar2(4, r4, 13.0 / 28, 43.0 / 28, 15.0 / 28, 41.0 / 28); + polar2(8, r4, 1.0 / 28, 55.0 / 28, 27.0 / 28, 29.0 / 28); + polar2(12, r1, 1.0 / 4, 7.0 / 4, 3.0 / 4, 5.0 / 4); + polar2(16, r4, 9.0 / 28, 47.0 / 28, 19.0 / 28, 37.0 / 28); + polar2(20, r4, 11.0 / 28, 45.0 / 28, 17.0 / 28, 39.0 / 28); + polar2(24, r3, 1.0 / 20, 39.0 / 20, 19.0 / 20, 21.0 / 20); + polar2(28, r2, 1.0 / 12, 23.0 / 12, 11.0 / 12, 13.0 / 12); + polar2(32, r4, 5.0 / 28, 51.0 / 28, 23.0 / 28, 33.0 / 28); + polar2(36, r3, 9.0 / 20, 31.0 / 20, 11.0 / 20, 29.0 / 20); + polar2(40, r4, 3.0 / 28, 53.0 / 28, 25.0 / 28, 31.0 / 28); + polar2(44, r2, 5.0 / 12, 19.0 / 12, 7.0 / 12, 17.0 / 12); + polar2(48, r3, 1.0 / 4, 7.0 / 4, 3.0 / 4, 5.0 / 4); + polar2(52, r3, 7.0 / 20, 33.0 / 20, 13.0 / 20, 27.0 / 20); + polar2(56, r3, 3.0 / 20, 37.0 / 20, 17.0 / 20, 23.0 / 20); + polar2(60, r2, 1.0 / 4, 7.0 / 4, 3.0 / 4, 5.0 / 4); + make_lut_from_symbols(); + break; + } + case QAM16: + make_qam(16); + break; + case QAM64: + make_qam(64); + break; + case QAM256: + make_qam(256); + break; + default: + fail("cstln_lut::cstln_lut", "Constellation not implemented"); + return; + } } - struct result { - struct softsymbol ss; - s_angle phase_error; + struct result + { + struct softsymbol ss; + s_angle phase_error; }; - inline result *lookup(float I, float Q) { - // Handling of overflows beyond the lookup table: - // - For BPSK/QPSK/8PSK we only care about the phase, - // so the following is harmless and improves locking at low SNR. - // - For amplitude modulations this is not appropriate. - // However, if there is enough noise to cause overflow, - // demodulation would probably fail anyway. - // - // Comment-out for better throughput at high SNR. + inline result *lookup(float I, float Q) + { + // Handling of overflows beyond the lookup table: + // - For BPSK/QPSK/8PSK we only care about the phase, + // so the following is harmless and improves locking at low SNR. + // - For amplitude modulations this is not appropriate. + // However, if there is enough noise to cause overflow, + // demodulation would probably fail anyway. + // + // Comment-out for better throughput at high SNR. #if 1 - while ( I<-128 || I>127 || Q<-128 || Q>127 ) { - I *= 0.5; - Q *= 0.5; - } + while (I < -128 || I > 127 || Q < -128 || Q > 127) + { + I *= 0.5; + Q *= 0.5; + } #endif - return &lut[(u8)(s8)I][(u8)(s8)Q]; + return &lut[(u8) (s8) I][(u8) (s8) Q]; } - inline result *lookup(int I, int Q) { - // Ignore wrapping modulo 256 - return &lut[(u8)I][(u8)Q]; + + inline result *lookup(int I, int Q) + { + // Ignore wrapping modulo 256 + return &lut[(u8) I][(u8) Q]; } - private: - complex polar(float r, int n, float i) { - float a = i * 2*M_PI / n; - return complex(r*cosf(a)*cstln_amp, r*sinf(a)*cstln_amp); + +private: + complex polar(float r, int n, float i) + { + float a = i * 2 * M_PI / n; + return complex(r * cosf(a) * cstln_amp, + r * sinf(a) * cstln_amp); } + // Helper function for some constellation tables - void polar2(int i, float r, float a0, float a1, float a2, float a3) { - float a[] = { a0, a1, a2, a3 }; - for ( int j=0; j<4; ++j ) { - float phi = a[j] * M_PI; - symbols[i+j] = complex(r*cosf(phi)*cstln_amp, - r*sinf(phi)*cstln_amp); - } + void polar2(int i, float r, float a0, float a1, float a2, float a3) + { + float a[] = + { a0, a1, a2, a3 }; + for (int j = 0; j < 4; ++j) + { + float phi = a[j] * M_PI; + symbols[i + j] = complex(r * cosf(phi) * cstln_amp, + r * sinf(phi) * cstln_amp); + } } - void make_qam(int n) { - nrotations = 4; - nsymbols = n; - symbols = new complex[nsymbols]; - int m = sqrtl(n); - float scale; - { // Average power in first quadrant with unit grid - int q = m / 2; - float avgpower = 2*(q*0.25+(q-1)*q/2+(q-1)*q*(2*q-1)/6) / q; - scale = 1.0 / sqrtf(avgpower); - } - // Arbitrary mapping - int s = 0; - for ( int x=0; x [nsymbols]; + int m = sqrtl(n); + float scale; + { // Average power in first quadrant with unit grid + int q = m / 2; + float avgpower = 2 + * (q * 0.25 + (q - 1) * (q / 2) + + (q - 1) * q * (2 * q - 1) / 6) / q; + scale = 1.0 / sqrtf(avgpower); + } + // Arbitrary mapping + int s = 0; + for (int x = 0; x < m; ++x) + for (int y = 0; y < m; ++y) + { + float I = x - (float) (m - 1) / 2; + float Q = y - (float) (m - 1) / 2; + symbols[s].re = I * scale * cstln_amp; + symbols[s].im = Q * scale * cstln_amp; + ++s; + } + make_lut_from_symbols(); } + result lut[R][R]; - void make_lut_from_symbols() { - for ( int I=-R/2; I Suitable for Viterbi with partial metrics. - uint8_t nearest = 0; - int32_t cost=R*R*2, cost2=R*R*2; - for ( int s=0; s 32767 ) cost = 32767; - if ( cost2 > 32767 ) cost2 = 32767; - pr->ss.cost = cost - cost2; - pr->ss.symbol = nearest; - float ph_symbol = atan2f(symbols[pr->ss.symbol].im, - symbols[pr->ss.symbol].re); - float ph_err = atan2f(Q,I) - ph_symbol; - pr->phase_error = (s32)(ph_err * 65536 / (2*M_PI)); // Mod 65536 - } + + void make_lut_from_symbols() + { + for (int I = -R / 2; I < R / 2; ++I) + for (int Q = -R / 2; Q < R / 2; ++Q) + { + result *pr = &lut[I & (R - 1)][Q & (R - 1)]; + // Simplified metric: + // Distance to nearest minus distance to second-nearest. + // Null at edge of decision regions + // => Suitable for Viterbi with partial metrics. + uint8_t nearest = 0; + int32_t cost = R * R * 2, cost2 = R * R * 2; + for (int s = 0; s < nsymbols; ++s) + { + int32_t d2 = (I - symbols[s].re) * (I - symbols[s].re) + + (Q - symbols[s].im) * (Q - symbols[s].im); + if (d2 < cost) + { + cost2 = cost; + cost = d2; + nearest = s; + } + else if (d2 < cost2) + { + cost2 = d2; + } + } + if (cost > 32767) + cost = 32767; + if (cost2 > 32767) + cost2 = 32767; + pr->ss.cost = cost - cost2; + pr->ss.symbol = nearest; + float ph_symbol = atan2f(symbols[pr->ss.symbol].im, + symbols[pr->ss.symbol].re); + float ph_err = atan2f(Q, I) - ph_symbol; + pr->phase_error = (s32) (ph_err * 65536 / (2 * M_PI)); // Mod 65536 + } } - public: +public: // Convert soft metric to Hamming distance - void harden() { - for ( int i=0; icost < 0 ) ss->cost = -1; - if ( ss->cost > 0 ) ss->cost = 1; - } // for I,Q - } + void harden() + { + for (int i = 0; i < R; ++i) + for (int q = 0; q < R; ++q) + { + softsymbol *ss = &lut[i][q].ss; + if (ss->cost < 0) + ss->cost = -1; + if (ss->cost > 0) + ss->cost = 1; + } // for I,Q + } - }; // cstln_lut +}; +// cstln_lut - static const char *cstln_names[] = { - [cstln_lut<256>::BPSK] = "BPSK", - [cstln_lut<256>::QPSK] = "QPSK", - [cstln_lut<256>::PSK8] = "8PSK", - [cstln_lut<256>::APSK16] = "16APSK", - [cstln_lut<256>::APSK32] = "32APSK", - [cstln_lut<256>::APSK64E] = "64APSKe", - [cstln_lut<256>::QAM16] = "16QAM", - [cstln_lut<256>::QAM64] = "64QAM", - [cstln_lut<256>::QAM256] = "256QAM" - }; +//static const char *cstln_names[] = +//{ [cstln_lut<256>::BPSK] = "BPSK", +// [cstln_lut<256>::QPSK] = "QPSK", +// [cstln_lut<256>::PSK8] = "8PSK", +// [cstln_lut<256>::APSK16] = "16APSK", +// [cstln_lut<256>::APSK32] = "32APSK", +// [cstln_lut<256>::APSK64E] = "64APSKe", +// [cstln_lut<256>::QAM16] = "16QAM", +// [cstln_lut<256>::QAM64] = "64QAM", +// [cstln_lut<256>::QAM256] = "256QAM" +//}; - // SAMPLER INTERFACE FOR CSTLN_RECEIVER - - template - struct sampler_interface { +// SAMPLER INTERFACE FOR CSTLN_RECEIVER + +template +struct sampler_interface +{ virtual complex interp(const complex *pin, float mu, float phase) = 0; - virtual void update_freq(float freqw) { } // 65536 = 1 Hz - virtual int readahead() { return 0; } - }; + virtual void update_freq(float freqw __attribute__((unused))) + { + } // 65536 = 1 Hz - // NEAREST-SAMPLE SAMPLER FOR CSTLN_RECEIVER - // Suitable for bandpass-filtered, oversampled signals only - - template - struct nearest_sampler : sampler_interface { - int readahead() { return 0; } - complex interp(const complex *pin, float mu, float phase) { - return pin[0]*trig.expi(-phase); + virtual int readahead() + { + return 0; } - private: + + virtual ~sampler_interface() + { + } +}; + +// NEAREST-SAMPLE SAMPLER FOR CSTLN_RECEIVER +// Suitable for bandpass-filtered, oversampled signals only + +template +struct nearest_sampler: sampler_interface +{ + int readahead() + { + return 0; + } + + complex interp(const complex *pin, float mu __attribute__((unused)), float phase) + { + return pin[0] * trig.expi(-phase); + } + +private: trig16 trig; - }; // nearest_sampler +}; +// nearest_sampler +// LINEAR SAMPLER FOR CSTLN_RECEIVER - // LINEAR SAMPLER FOR CSTLN_RECEIVER - - template - struct linear_sampler : sampler_interface { - int readahead() { return 1; } - - complex interp(const complex *pin, float mu, float phase) { - // Derotate pin[0] and pin[1] - complex s0 = pin[0]*trig.expi(-phase); - complex s1 = pin[1]*trig.expi(-(phase+freqw)); - // Interpolate linearly - return s0*(1-mu) + s1*mu; +template +struct linear_sampler: sampler_interface +{ + int readahead() + { + return 1; } - void update_freq(float _freqw) { freqw = _freqw; } + complex interp(const complex *pin, float mu, float phase) + { + // Derotate pin[0] and pin[1] + complex s0 = pin[0] * trig.expi(-phase); + complex s1 = pin[1] * trig.expi(-(phase + freqw)); + // Interpolate linearly + return s0 * (1 - mu) + s1 * mu; + } - private: + void update_freq(float _freqw) + { + freqw = _freqw; + } + +private: trig16 trig; float freqw; - }; // linear_sampler +}; +// linear_sampler +// FIR SAMPLER FOR CSTLN_RECEIVER - // FIR SAMPLER FOR CSTLN_RECEIVER - - template - struct fir_sampler : sampler_interface { - fir_sampler(int _ncoeffs, Tc *_coeffs, int _subsampling=1) - : ncoeffs(_ncoeffs), coeffs(_coeffs), subsampling(_subsampling), - shifted_coeffs(new complex[ncoeffs]), - update_freq_phase(0) +template +struct fir_sampler: sampler_interface +{ + fir_sampler(int _ncoeffs, Tc *_coeffs, int _subsampling = 1) : + ncoeffs(_ncoeffs), coeffs(_coeffs), subsampling(_subsampling), shifted_coeffs( + new complex [ncoeffs]), update_freq_phase(0) { } - int readahead() { return ncoeffs-1; } - - complex interp(const complex *pin, float mu, float phase) { - // Apply FIR filter with subsampling - complex acc(0, 0); - complex *pc = shifted_coeffs + (int)((1-mu)*subsampling); - complex *pcend = shifted_coeffs + ncoeffs; - if ( subsampling == 1 ) { - // Special case for heavily oversampled signals, - // where filtering is expensive. - // gcc-4.9.2 can vectorize this form with NEON on ARM. - while ( pc < pcend ) - acc += (*pc++)*(*pin++); - } else { - // Not vectorized because the coefficients are not - // guaranteed to be contiguous in memory. - for ( ; pc interp(const complex *pin, float mu, float phase) + { + // Apply FIR filter with subsampling + complex acc(0, 0); + complex *pc = shifted_coeffs + (int) ((1 - mu) * subsampling); + complex *pcend = shifted_coeffs + ncoeffs; + if (subsampling == 1) + { + // Special case for heavily oversampled signals, + // where filtering is expensive. + // gcc-4.9.2 can vectorize this form with NEON on ARM. + while (pc < pcend) + acc += (*pc++) * (*pin++); + } + else + { + // Not vectorized because the coefficients are not + // guaranteed to be contiguous in memory. + for (; pc < pcend; pc += subsampling, ++pin) + acc += (*pc) * (*pin); + } + // Derotate + return trig.expi(-phase) * acc; } - private: - void do_update_freq(float freqw) { - float f = freqw / subsampling; - for ( int i=0; i - struct cstln_receiver : runnable { +template +struct cstln_receiver: runnable +{ sampler_interface *sampler; cstln_lut<256> *cstln; unsigned long meas_decimation; // Measurement rate @@ -691,226 +852,270 @@ namespace leansdr { bool allow_drift; // Follow carrier beyond safe limits static const unsigned int chunk_size = 128; float kest; - - cstln_receiver(scheduler *sch, - sampler_interface *_sampler, - pipebuf< complex > &_in, - pipebuf &_out, - pipebuf *_freq_out=NULL, - pipebuf *_ss_out=NULL, - pipebuf *_mer_out=NULL, - pipebuf *_cstln_out=NULL) - : runnable(sch, "Constellation receiver"), - sampler(_sampler), - cstln(NULL), - meas_decimation(1048576), - pll_adjustment(1.0), - allow_drift(false), - kest(0.01), - in(_in), out(_out, chunk_size), - est_insp(cstln_amp*cstln_amp), agc_gain(1), - mu(0), phase(0), - est_sp(0), est_ep(0), - meas_count(0) { - set_omega(1); - set_freq(0); - freq_out = _freq_out ? new pipewriter(*_freq_out) : NULL; - ss_out = _ss_out ? new pipewriter(*_ss_out) : NULL; - mer_out = _mer_out ? new pipewriter(*_mer_out) : NULL; - cstln_out = _cstln_out ? new pipewriter(*_cstln_out) : NULL; - memset(hist, 0, sizeof(hist)); - } - - void set_omega(float _omega, float tol=10e-6) { - omega = _omega; - min_omega = omega * (1-tol); - max_omega = omega * (1+tol); - update_freq_limits(); - } - - void set_freq(float freq) { - freqw = freq * 65536; - update_freq_limits(); - refresh_freq_tap(); + + cstln_receiver( + scheduler *sch, + sampler_interface *_sampler, + pipebuf > &_in, + pipebuf &_out, + pipebuf *_freq_out = NULL, + pipebuf *_ss_out = NULL, + pipebuf *_mer_out = NULL, + pipebuf *_cstln_out = NULL) : + runnable(sch, "Constellation receiver"), + sampler(_sampler), + cstln(NULL), + meas_decimation(1048576), + pll_adjustment(1.0), + allow_drift(false), + kest(0.01), + in(_in), + out(_out, chunk_size), + est_insp(cstln_amp * cstln_amp), + agc_gain(1), + mu(0), + phase(0), + est_sp(0), + est_ep(0), + meas_count(0) + { + set_omega(1); + set_freq(0); + freq_out = _freq_out ? new pipewriter(*_freq_out) : NULL; + ss_out = _ss_out ? new pipewriter(*_ss_out) : NULL; + mer_out = _mer_out ? new pipewriter(*_mer_out) : NULL; + cstln_out = _cstln_out ? new pipewriter(*_cstln_out) : NULL; + memset(hist, 0, sizeof(hist)); } - void set_allow_drift(bool d) { - allow_drift = d; + void set_omega(float _omega, float tol = 10e-6) + { + omega = _omega; + min_omega = omega * (1 - tol); + max_omega = omega * (1 + tol); + update_freq_limits(); } - void update_freq_limits() { - // Prevent PLL from crossing +-SR/n/2 and locking at +-SR/n. - int n = 4; - if ( cstln ) { - switch ( cstln->nsymbols ) { - case 2: n = 2; break; // BPSK - case 4: n = 4; break; // QPSK - case 8: n = 8; break; // 8PSK - case 16: n = 12; break; // 16APSK - case 32: n = 16; break; // 32APSK - default: n = 4; break; - } - } - min_freqw = freqw - 65536/max_omega/n/2; - max_freqw = freqw + 65536/max_omega/n/2; + void set_freq(float freq) + { + freqw = freq * 65536; + update_freq_limits(); + refresh_freq_tap(); } - - void run() { - if ( ! cstln ) fail("constellation not set"); - - // Magic constants that work with the qa recordings. - float freq_alpha = 0.04; - float freq_beta = 0.0012 / omega * pll_adjustment; - float gain_mu = 0.02 / (cstln_amp*cstln_amp) * 2; - int max_meas = chunk_size/meas_decimation + 1; - // Large margin on output_size because mu adjustments - // can lead to more than chunk_size/min_omega symbols. - while ( in.readable() >= chunk_size+sampler->readahead() && - out.writable() >= chunk_size && - ( !freq_out || freq_out ->writable()>=max_meas ) && - ( !ss_out || ss_out ->writable()>=max_meas ) && - ( !mer_out || mer_out ->writable()>=max_meas ) && - ( !cstln_out || cstln_out->writable()>=max_meas ) ) { - - sampler->update_freq(freqw); - - complex *pin=in.rd(), *pin0=pin, *pend=pin+chunk_size; - softsymbol *pout=out.wr(), *pout0=pout; - - // These are scoped outside the loop for SS and MER estimation. - complex sg; // Symbol before AGC; - complex s; // For MER estimation and constellation viewer - complex *cstln_point = NULL; - - while ( pin < pend ) { - // Here mu is the time of the next symbol counted from 0 at pin. - if ( mu < 1 ) { - // Here 0<=mu<1 is the fractional time of the next symbol - // between pin and pin+1. - sg = sampler->interp(pin, mu, phase); - s = sg * agc_gain; - - // Constellation look-up - cstln_lut<256>::result *cr = cstln->lookup(s.re, s.im); - *pout = cr->ss; - ++pout; - - // PLL - phase += cr->phase_error * freq_alpha; - freqw += cr->phase_error * freq_beta; - - // Modified Mueller and Müller - // mu[k]=real((c[k]-c[k-2])*conj(p[k-1])-(p[k]-p[k-2])*conj(c[k-1])) - // =dot(c[k]-c[k-2],p[k-1]) - dot(p[k]-p[k-2],c[k-1]) - // p = received signals - // c = decisions (constellation points) - hist[2] = hist[1]; - hist[1] = hist[0]; - hist[0].p.re = s.re; - hist[0].p.im = s.im; - cstln_point = &cstln->symbols[cr->ss.symbol]; - hist[0].c.re = cstln_point->re; - hist[0].c.im = cstln_point->im; - float muerr = - ( (hist[0].p.re-hist[2].p.re)*hist[1].c.re + - (hist[0].p.im-hist[2].p.im)*hist[1].c.im ) - - ( (hist[0].c.re-hist[2].c.re)*hist[1].p.re + - (hist[0].c.im-hist[2].c.im)*hist[1].p.im ); - float mucorr = muerr * gain_mu; - const float max_mucorr = 0.1; - // TBD Optimize out statically - if ( mucorr < -max_mucorr ) mucorr = -max_mucorr; - if ( mucorr > max_mucorr ) mucorr = max_mucorr; - mu += mucorr; - mu += omega; // Next symbol time; - } // mu<1 - - // Next sample - ++pin; - --mu; - phase += freqw; - } // chunk_size - - in.read(pin-pin0); - out.written(pout-pout0); - - // Normalize phase so that it never exceeds 32 bits. - // Max freqw is 2^31/65536/chunk_size = 256 Hz - // (this may happen with leandvb --drift --decim). - phase = fmodf(phase, 65536); - - if ( cstln_point ) { - - // Output the last interpolated PSK symbol, max once per chunk_size - if ( cstln_out ) - cstln_out->write(s); - - // AGC - // For APSK we must do AGC on the symbols, not the whole signal. - // TODO Use a better estimator at low SNR. - float insp = sg.re*sg.re + sg.im*sg.im; - est_insp = insp*kest + est_insp*(1-kest); - if ( est_insp ) - agc_gain = cstln_amp / gen_sqrt(est_insp); - - // SS and MER - complex ev(s.re-cstln_point->re, s.im-cstln_point->im); - float sig_power, ev_power; - if ( cstln->nsymbols == 2 ) { - // Special case for BPSK: Ignore quadrature component of noise. - // TBD Projection on I axis assumes BPSK at 45° - float sig_real = (cstln_point->re+cstln_point->im) * 0.707; - float ev_real = (ev.re+ev.im) * 0.707; - sig_power = sig_real * sig_real; - ev_power = ev_real * ev_real; - } else { - sig_power = - (int)cstln_point->re*cstln_point->re + - (int)cstln_point->im*cstln_point->im; - ev_power = ev.re*ev.re + ev.im*ev.im; - } - est_sp = sig_power*kest + est_sp*(1-kest); - est_ep = ev_power*kest + est_ep*(1-kest); - - } - - // This is best done periodically ouside the inner loop, - // but will cause non-deterministic output. - - if ( ! allow_drift ) { - if ( freqw < min_freqw || freqw > max_freqw ) - freqw = (max_freqw+min_freqw) / 2; - } - - // Output measurements - - refresh_freq_tap(); - - meas_count += pin-pin0; - while ( meas_count >= meas_decimation ) { - meas_count -= meas_decimation; - if ( freq_out ) - freq_out->write(freq_tap); - if ( ss_out ) - ss_out->write(sqrtf(est_insp)); - if ( mer_out ) - mer_out->write(est_ep ? 10*logf(est_sp/est_ep)/logf(10) : 0); - } - - } // Work to do + void set_allow_drift(bool d) + { + allow_drift = d; } - + + void update_freq_limits() + { + // Prevent PLL from crossing +-SR/n/2 and locking at +-SR/n. + int n = 4; + if (cstln) + { + switch (cstln->nsymbols) + { + case 2: + n = 2; + break; // BPSK + case 4: + n = 4; + break; // QPSK + case 8: + n = 8; + break; // 8PSK + case 16: + n = 12; + break; // 16APSK + case 32: + n = 16; + break; // 32APSK + default: + n = 4; + break; + } + } + min_freqw = freqw - 65536 / max_omega / n / 2; + max_freqw = freqw + 65536 / max_omega / n / 2; + } + + void run() + { + if (!cstln) + { + fail("cstln_lut::run", "constellation not set"); + return; + } + + // Magic constants that work with the qa recordings. + float freq_alpha = 0.04; + float freq_beta = 0.0012 / omega * pll_adjustment; + float gain_mu = 0.02 / (cstln_amp * cstln_amp) * 2; + + unsigned int max_meas = chunk_size / meas_decimation + 1; + // Large margin on output_size because mu adjustments + // can lead to more than chunk_size/min_omega symbols. + while (in.readable() >= chunk_size + sampler->readahead() + && out.writable() >= chunk_size + && (!freq_out || freq_out->writable() >= max_meas) + && (!ss_out || ss_out->writable() >= max_meas) + && (!mer_out || mer_out->writable() >= max_meas) + && (!cstln_out || cstln_out->writable() >= max_meas)) + { + + sampler->update_freq(freqw); + + complex *pin = in.rd(), *pin0 = pin, *pend = pin + chunk_size; + softsymbol *pout = out.wr(), *pout0 = pout; + + // These are scoped outside the loop for SS and MER estimation. + complex sg(0.0, 0.0); // Symbol before AGC; + complex s(0.0, 0.0); // For MER estimation and constellation viewer + complex *cstln_point = NULL; + + while (pin < pend) + { + // Here mu is the time of the next symbol counted from 0 at pin. + if (mu < 1) + { + // Here 0<=mu<1 is the fractional time of the next symbol + // between pin and pin+1. + sg = sampler->interp(pin, mu, phase); + s = sg * agc_gain; + + // Constellation look-up + cstln_lut<256>::result *cr = cstln->lookup(s.re, s.im); + *pout = cr->ss; + ++pout; + + // PLL + phase += cr->phase_error * freq_alpha; + freqw += cr->phase_error * freq_beta; + + // Modified Mueller and Müller + // mu[k]=real((c[k]-c[k-2])*conj(p[k-1])-(p[k]-p[k-2])*conj(c[k-1])) + // =dot(c[k]-c[k-2],p[k-1]) - dot(p[k]-p[k-2],c[k-1]) + // p = received signals + // c = decisions (constellation points) + hist[2] = hist[1]; + hist[1] = hist[0]; + hist[0].p.re = s.re; + hist[0].p.im = s.im; + cstln_point = &cstln->symbols[cr->ss.symbol]; + hist[0].c.re = cstln_point->re; + hist[0].c.im = cstln_point->im; + float muerr = ((hist[0].p.re - hist[2].p.re) * hist[1].c.re + + (hist[0].p.im - hist[2].p.im) * hist[1].c.im) + - ((hist[0].c.re - hist[2].c.re) * hist[1].p.re + + (hist[0].c.im - hist[2].c.im) + * hist[1].p.im); + float mucorr = muerr * gain_mu; + const float max_mucorr = 0.1; + // TBD Optimize out statically + if (mucorr < -max_mucorr) + mucorr = -max_mucorr; + if (mucorr > max_mucorr) + mucorr = max_mucorr; + mu += mucorr; + mu += omega; // Next symbol time; + } // mu<1 + + // Next sample + ++pin; + --mu; + phase += freqw; + } // chunk_size + + in.read(pin - pin0); + out.written(pout - pout0); + + // Normalize phase so that it never exceeds 32 bits. + // Max freqw is 2^31/65536/chunk_size = 256 Hz + // (this may happen with leandvb --drift --decim). + phase = fmodf(phase, 65536); + + if (cstln_point) + { + + // Output the last interpolated PSK symbol, max once per chunk_size + if (cstln_out) + cstln_out->write(s); + + // AGC + // For APSK we must do AGC on the symbols, not the whole signal. + // TODO Use a better estimator at low SNR. + float insp = sg.re * sg.re + sg.im * sg.im; + est_insp = insp * kest + est_insp * (1 - kest); + if (est_insp) + agc_gain = cstln_amp / gen_sqrt(est_insp); + + // SS and MER + complex ev(s.re - cstln_point->re, + s.im - cstln_point->im); + float sig_power, ev_power; + if (cstln->nsymbols == 2) + { + // Special case for BPSK: Ignore quadrature component of noise. + // TBD Projection on I axis assumes BPSK at 45° + float sig_real = (cstln_point->re + cstln_point->im) + * 0.707; + float ev_real = (ev.re + ev.im) * 0.707; + sig_power = sig_real * sig_real; + ev_power = ev_real * ev_real; + } + else + { + sig_power = (int) cstln_point->re * cstln_point->re + + (int) cstln_point->im * cstln_point->im; + ev_power = ev.re * ev.re + ev.im * ev.im; + } + est_sp = sig_power * kest + est_sp * (1 - kest); + est_ep = ev_power * kest + est_ep * (1 - kest); + + } + + // This is best done periodically ouside the inner loop, + // but will cause non-deterministic output. + + if (!allow_drift) + { + if (freqw < min_freqw || freqw > max_freqw) + freqw = (max_freqw + min_freqw) / 2; + } + + // Output measurements + + refresh_freq_tap(); + + meas_count += pin - pin0; + while (meas_count >= meas_decimation) + { + meas_count -= meas_decimation; + if (freq_out) + freq_out->write(freq_tap); + if (ss_out) + ss_out->write(sqrtf(est_insp)); + if (mer_out) + mer_out->write( + est_ep ? 10 * logf(est_sp / est_ep) / logf(10) : 0); + } + + } // Work to do + } + float freq_tap; - void refresh_freq_tap() { - freq_tap = freqw / 65536; + void refresh_freq_tap() + { + freq_tap = freqw / 65536; } - private: - struct { - complex p; // Received symbol - complex c; // Matched constellation point +private: + struct + { + complex p; // Received symbol + complex c; // Matched constellation point } hist[3]; - pipereader< complex > in; + pipereader > in; pipewriter out; float est_insp, agc_gain; float mu; // PSK time expressed in clock ticks @@ -921,16 +1126,16 @@ namespace leansdr { unsigned long meas_count; pipewriter *freq_out, *ss_out, *mer_out; pipewriter *cstln_out; - }; - - - // FAST QPSK RECEIVER +}; - // Optimized for u8 input, no AGC, uses phase information only. - // Outputs hard symbols. +// FAST QPSK RECEIVER - template - struct fast_qpsk_receiver : runnable { +// Optimized for u8 input, no AGC, uses phase information only. +// Outputs hard symbols. + +template +struct fast_qpsk_receiver: runnable +{ typedef u8 hardsymbol; unsigned long meas_decimation; // Measurement rate float omega, min_omega, max_omega; // Samples per symbol @@ -938,231 +1143,275 @@ namespace leansdr { float pll_adjustment; bool allow_drift; // Follow carrier beyond safe limits static const unsigned int chunk_size = 128; - - fast_qpsk_receiver(scheduler *sch, - pipebuf< complex > &_in, - pipebuf &_out, - pipebuf *_freq_out=NULL, - pipebuf< complex > *_cstln_out=NULL) - : runnable(sch, "Fast QPSK receiver"), - meas_decimation(1048576), - pll_adjustment(1.0), - allow_drift(false), - in(_in), out(_out, chunk_size), - mu(0), phase(0), - meas_count(0) + + fast_qpsk_receiver( + scheduler *sch, + pipebuf > &_in, + pipebuf &_out, + pipebuf *_freq_out = NULL, + pipebuf > *_cstln_out = NULL) : + runnable(sch, "Fast QPSK receiver"), + meas_decimation(1048576), + pll_adjustment(1.0), + allow_drift(false), + in(_in), + out(_out, chunk_size), + mu(0), + phase(0), + meas_count(0) { - set_omega(1); - set_freq(0); - freq_out = _freq_out ? new pipewriter(*_freq_out) : NULL; - cstln_out = _cstln_out ? new pipewriter< complex >(*_cstln_out) : NULL; - memset(hist, 0, sizeof(hist)); - init_lookup_tables(); - } - - void set_omega(float _omega, float tol=10e-6) { - omega = _omega; - min_omega = omega * (1-tol); - max_omega = omega * (1+tol); - update_freq_limits(); - } - - void set_freq(float freq) { - freqw = freq * 65536; - update_freq_limits(); + set_omega(1); + set_freq(0); + freq_out = _freq_out ? new pipewriter(*_freq_out) : NULL; + cstln_out = + _cstln_out ? new pipewriter >(*_cstln_out) : NULL; + memset(hist, 0, sizeof(hist)); + init_lookup_tables(); } - void update_freq_limits() { - // Prevent PLL from locking at +-symbolrate/4. - // TODO The +-SR/8 limit is suitable for QPSK only. - min_freqw = freqw - 65536/max_omega/8; - max_freqw = freqw + 65536/max_omega/8; + void set_omega(float _omega, float tol = 10e-6) + { + omega = _omega; + min_omega = omega * (1 - tol); + max_omega = omega * (1 + tol); + update_freq_limits(); + } + + void set_freq(float freq) + { + freqw = freq * 65536; + update_freq_limits(); + } + + void update_freq_limits() + { + // Prevent PLL from locking at +-symbolrate/4. + // TODO The +-SR/8 limit is suitable for QPSK only. + min_freqw = freqw - 65536 / max_omega / 8; + max_freqw = freqw + 65536 / max_omega / 8; } static const int RLUT_BITS = 8; static const int RLUT_ANGLES = 1 << RLUT_BITS; - void run() { - // Magic constants that work with the qa recordings. - signed long freq_alpha = 0.04 * 65536; - signed long freq_beta = 0.0012 * 256 * 65536 / omega * pll_adjustment; - if ( ! freq_beta ) fail("Excessive oversampling"); + void run() + { + // Magic constants that work with the qa recordings. + signed long freq_alpha = 0.04 * 65536; + signed long freq_beta = 0.0012 * 256 * 65536 / omega * pll_adjustment; + if (!freq_beta) + { + fail("fast_qpsk_receiver::run", "Excessive oversampling"); + return; + } - float gain_mu = 0.02 / (cstln_amp*cstln_amp) * 2; + float gain_mu = 0.02 / (cstln_amp * cstln_amp) * 2; - int max_meas = chunk_size/meas_decimation + 1; - // Largin margin on output_size because mu adjustments - // can lead to more than chunk_size/min_omega symbols. - while ( in.readable() >= chunk_size+1 && // +1 for interpolation - out.writable() >= chunk_size && - ( !freq_out || freq_out ->writable()>=max_meas ) && - ( !cstln_out || cstln_out->writable()>=max_meas ) ) { - - complex *pin=in.rd(), *pin0=pin, *pend=pin+chunk_size; - hardsymbol *pout=out.wr(), *pout0=pout; + int max_meas = chunk_size / meas_decimation + 1; + // Largin margin on output_size because mu adjustments + // can lead to more than chunk_size/min_omega symbols. + while (in.readable() >= chunk_size + 1 + && // +1 for interpolation + out.writable() >= chunk_size + && (!freq_out || freq_out->writable() >= max_meas) + && (!cstln_out || cstln_out->writable() >= max_meas)) + { - cu8 s; - u_angle symbol_arg = 0; // Exported for constellation viewer + complex *pin = in.rd(), *pin0 = pin, *pend = pin + chunk_size; + hardsymbol *pout = out.wr(), *pout0 = pout; - while ( pin < pend ) { - // Here mu is the time of the next symbol counted from 0 at pin. - if ( mu < 1 ) { - // Here 0<=mu<1 is the fractional time of the next symbol - // between pin and pin+1. + cu8 s; + u_angle symbol_arg = 0; // Exported for constellation viewer - // Derotate and interpolate + while (pin < pend) + { + // Here mu is the time of the next symbol counted from 0 at pin. + if (mu < 1) + { + // Here 0<=mu<1 is the fractional time of the next symbol + // between pin and pin+1. + + // Derotate and interpolate #if 0 // Phase only (does not work) - // Careful with the float/signed/unsigned casts - u_angle a0 = fast_arg(pin[0]) - phase; - u_angle a1 = fast_arg(pin[1]) - (phase+freqw); - s_angle da = a1 - a0; - symbol_arg = a0 + (s_angle)(da*mu); - s = arg_to_symbol(symbol_arg); + // Careful with the float/signed/unsigned casts + u_angle a0 = fast_arg(pin[0]) - phase; + u_angle a1 = fast_arg(pin[1]) - (phase+freqw); + s_angle da = a1 - a0; + symbol_arg = a0 + (s_angle)(da*mu); + s = arg_to_symbol(symbol_arg); #elif 1 // Linear by lookup-table. 1.2M on bench3bishs - polar *p0 = &lut_polar[pin[0].re][pin[0].im]; - u_angle a0 = (u_angle)(p0->a-phase) >> (16-RLUT_BITS); - cu8 *p0r = &lut_rect[a0][p0->r>>1]; - polar *p1 = &lut_polar[pin[1].re][pin[1].im]; - u_angle a1 = (u_angle)(p1->a-(phase+freqw)) >> (16-RLUT_BITS); - cu8 *p1r = &lut_rect[a1][p1->r>>1]; - s.re = (int)(p0r->re + (p1r->re-p0r->re)*mu); - s.im = (int)(p0r->im + (p1r->im-p0r->im)*mu); - symbol_arg = fast_arg(s); + polar *p0 = &lut_polar[pin[0].re][pin[0].im]; + u_angle a0 = (u_angle) (p0->a - phase) >> (16 - RLUT_BITS); + cu8 *p0r = &lut_rect[a0][p0->r >> 1]; + polar *p1 = &lut_polar[pin[1].re][pin[1].im]; + u_angle a1 = (u_angle) (p1->a - (phase + freqw)) + >> (16 - RLUT_BITS); + cu8 *p1r = &lut_rect[a1][p1->r >> 1]; + s.re = (int) (p0r->re + (p1r->re - p0r->re) * mu); + s.im = (int) (p0r->im + (p1r->im - p0r->im) * mu); + symbol_arg = fast_arg(s); #else // Linear floating-point, for reference - float a0 = -(int)phase*M_PI/32768; - float cosa0=cosf(a0), sina0=sinf(a0); - complex - p0r(((float)pin[0].re-128)*cosa0 - ((float)pin[0].im-128)*sina0, - ((float)pin[0].re-128)*sina0 + ((float)pin[0].im-128)*cosa0); - float a1 = -(int)(phase+freqw)*M_PI/32768; - float cosa1=cosf(a1), sina1=sinf(a1); - complex - p1r(((float)pin[1].re-128)*cosa1 - ((float)pin[1].im-128)*sina1, - ((float)pin[1].re-128)*sina1 + ((float)pin[1].im-128)*cosa1); - s.re = (int)(128 + p0r.re + (p1r.re-p0r.re)*mu); - s.im = (int)(128 + p0r.im + (p1r.im-p0r.im)*mu); - symbol_arg = fast_arg(s); + float a0 = -(int)phase*M_PI/32768; + float cosa0=cosf(a0), sina0=sinf(a0); + complex + p0r(((float)pin[0].re-128)*cosa0 - ((float)pin[0].im-128)*sina0, + ((float)pin[0].re-128)*sina0 + ((float)pin[0].im-128)*cosa0); + float a1 = -(int)(phase+freqw)*M_PI/32768; + float cosa1=cosf(a1), sina1=sinf(a1); + complex + p1r(((float)pin[1].re-128)*cosa1 - ((float)pin[1].im-128)*sina1, + ((float)pin[1].re-128)*sina1 + ((float)pin[1].im-128)*cosa1); + s.re = (int)(128 + p0r.re + (p1r.re-p0r.re)*mu); + s.im = (int)(128 + p0r.im + (p1r.im-p0r.im)*mu); + symbol_arg = fast_arg(s); #endif - int quadrant = symbol_arg >> 14; - static unsigned char quadrant_to_symbol[4] = { 0, 2, 3, 1 }; - *pout = quadrant_to_symbol[quadrant]; - ++pout; + int quadrant = symbol_arg >> 14; + static unsigned char quadrant_to_symbol[4] = + { 0, 2, 3, 1 }; + *pout = quadrant_to_symbol[quadrant]; + ++pout; - // PLL - s_angle phase_error = (s_angle)(symbol_arg&16383) - 8192; - phase += (phase_error * freq_alpha + 32768) >> 16; - freqw += (phase_error * freq_beta + 32768*256) >> 24; - - // Modified Mueller and Müller - // mu[k]=real((c[k]-c[k-2])*conj(p[k-1])-(p[k]-p[k-2])*conj(c[k-1])) - // =dot(c[k]-c[k-2],p[k-1]) - dot(p[k]-p[k-2],c[k-1]) - // p = received signals - // c = decisions (constellation points) - hist[2] = hist[1]; - hist[1] = hist[0]; + // PLL + s_angle phase_error = (s_angle) (symbol_arg & 16383) - 8192; + phase += (phase_error * freq_alpha + 32768) >> 16; + freqw += (phase_error * freq_beta + 32768 * 256) >> 24; + + // Modified Mueller and Müller + // mu[k]=real((c[k]-c[k-2])*conj(p[k-1])-(p[k]-p[k-2])*conj(c[k-1])) + // =dot(c[k]-c[k-2],p[k-1]) - dot(p[k]-p[k-2],c[k-1]) + // p = received signals + // c = decisions (constellation points) + hist[2] = hist[1]; + hist[1] = hist[0]; #define HIST_FLOAT 0 #if HIST_FLOAT - hist[0].p.re = (float)s.re - 128; - hist[0].p.im = (float)s.im - 128; + hist[0].p.re = (float)s.re - 128; + hist[0].p.im = (float)s.im - 128; - cu8 cp = arg_to_symbol((symbol_arg&49152)+8192); - hist[0].c.re = (float)cp.re - 128; - hist[0].c.im = (float)cp.im - 128; + cu8 cp = arg_to_symbol((symbol_arg&49152)+8192); + hist[0].c.re = (float)cp.re - 128; + hist[0].c.im = (float)cp.im - 128; - float muerr = - ( (hist[0].p.re-hist[2].p.re)*hist[1].c.re + - (hist[0].p.im-hist[2].p.im)*hist[1].c.im ) - - ( (hist[0].c.re-hist[2].c.re)*hist[1].p.re + - (hist[0].c.im-hist[2].c.im)*hist[1].p.im ); + float muerr = + ( (hist[0].p.re-hist[2].p.re)*hist[1].c.re + + (hist[0].p.im-hist[2].p.im)*hist[1].c.im ) - + ( (hist[0].c.re-hist[2].c.re)*hist[1].p.re + + (hist[0].c.im-hist[2].c.im)*hist[1].p.im ); #else - hist[0].p = s; - hist[0].c = arg_to_symbol((symbol_arg&49152)+8192); + hist[0].p = s; + hist[0].c = arg_to_symbol((symbol_arg & 49152) + 8192); - int muerr = - ( (signed char)(hist[0].p.re-hist[2].p.re)*((int)hist[1].c.re-128) + - (signed char)(hist[0].p.im-hist[2].p.im)*((int)hist[1].c.im-128) ) - - ( (signed char)(hist[0].c.re-hist[2].c.re)*((int)hist[1].p.re-128) + - (signed char)(hist[0].c.im-hist[2].c.im)*((int)hist[1].p.im-128) ); + int muerr = + ((signed char) (hist[0].p.re - hist[2].p.re) + * ((int) hist[1].c.re - 128) + + (signed char) (hist[0].p.im - hist[2].p.im) + * ((int) hist[1].c.im - 128)) + - ((signed char) (hist[0].c.re + - hist[2].c.re) + * ((int) hist[1].p.re - 128) + + (signed char) (hist[0].c.im + - hist[2].c.im) + * ((int) hist[1].p.im - 128)); #endif - float mucorr = muerr * gain_mu; - const float max_mucorr = 0.1; - // TBD Optimize out statically - if ( mucorr < -max_mucorr ) mucorr = -max_mucorr; - if ( mucorr > max_mucorr ) mucorr = max_mucorr; - mu += mucorr; - mu += omega; // Next symbol time; - } // mu<1 - - // Next sample - ++pin; - --mu; - phase += freqw; - } // chunk_size - - in.read(pin-pin0); - out.written(pout-pout0); + float mucorr = muerr * gain_mu; + const float max_mucorr = 0.1; + // TBD Optimize out statically + if (mucorr < -max_mucorr) + mucorr = -max_mucorr; + if (mucorr > max_mucorr) + mucorr = max_mucorr; + mu += mucorr; + mu += omega; // Next symbol time; + } // mu<1 - if ( symbol_arg && cstln_out ) - // Output the last interpolated PSK symbol, max once per chunk_size - cstln_out->write(s); - - // This is best done periodically ouside the inner loop, - // but will cause non-deterministic output. - - if ( ! allow_drift ) { - if ( freqw < min_freqw || freqw > max_freqw ) - freqw = (max_freqw+min_freqw) / 2; - } - - // Output measurements - - meas_count += pin-pin0; - while ( meas_count >= meas_decimation ) { - meas_count -= meas_decimation; - if ( freq_out ) - freq_out->write((float)freqw / 65536); - } - - } // Work to do - } - - private: + // Next sample + ++pin; + --mu; + phase += freqw; + } // chunk_size - struct polar { u_angle a; unsigned char r; } lut_polar[256][256]; - u_angle fast_arg(const cu8 &c) { - // TBD read cu8 as u16 index, same endianness as in init() - return lut_polar[c.re][c.im].a; + in.read(pin - pin0); + out.written(pout - pout0); + + if (symbol_arg && cstln_out) + // Output the last interpolated PSK symbol, max once per chunk_size + cstln_out->write(s); + + // This is best done periodically ouside the inner loop, + // but will cause non-deterministic output. + + if (!allow_drift) + { + if (freqw < min_freqw || freqw > max_freqw) + freqw = (max_freqw + min_freqw) / 2; + } + + // Output measurements + + meas_count += pin - pin0; + while (meas_count >= meas_decimation) + { + meas_count -= meas_decimation; + if (freq_out) + freq_out->write((float) freqw / 65536); + } + + } // Work to do + } + +private: + + struct polar + { + u_angle a; + unsigned char r; + } lut_polar[256][256]; + u_angle fast_arg(const cu8 &c) + { + // TBD read cu8 as u16 index, same endianness as in init() + return lut_polar[c.re][c.im].a; } cu8 lut_rect[RLUT_ANGLES][256]; cu8 lut_sincos[65536]; - cu8 arg_to_symbol(u_angle a) { return lut_sincos[a]; } - void init_lookup_tables() { - for ( int i=0; i<256; ++i ) - for ( int q=0; q<256; ++q ) { - // Don't cast float to unsigned directly - lut_polar[i][q].a = (s_angle)(atan2f(q-128,i-128)*65536/(2*M_PI)); - lut_polar[i][q].r = (int)hypotf(i-128,q-128); - } - for ( unsigned long a=0; a<65536; ++a ) { - float f = 2*M_PI * a / 65536; - lut_sincos[a].re = 128 + cstln_amp*cosf(f); - lut_sincos[a].im = 128 + cstln_amp*sinf(f); - } - for ( int a=0; a p; // Received symbol - complex c; // Matched constellation point + complex p; // Received symbol + complex c;// Matched constellation point #else - cu8 p; // Received symbol - cu8 c; // Matched constellation point + cu8 p; // Received symbol + cu8 c; // Matched constellation point #endif } hist[3]; pipereader in; @@ -1172,224 +1421,278 @@ namespace leansdr { unsigned long meas_count; pipewriter *freq_out, *mer_out; pipewriter *cstln_out; - }; // fast_qpsk_receiver - +}; +// fast_qpsk_receiver - // CONSTELLATION TRANSMITTER +// CONSTELLATION TRANSMITTER - // Maps symbols to I/Q points. +// Maps symbols to I/Q points. - template - struct cstln_transmitter : runnable { +template +struct cstln_transmitter: runnable +{ cstln_lut<256> *cstln; - cstln_transmitter(scheduler *sch, - pipebuf &_in, pipebuf< complex > &_out) - : runnable(sch, "cstln_transmitter"), - in(_in), out(_out) + + cstln_transmitter(scheduler *sch, pipebuf &_in, pipebuf > &_out) : + runnable(sch, "cstln_transmitter"), + cstln(0), + in(_in), + out(_out) { } - void run() { - if ( ! cstln ) fail("constellation not set"); - int count = min(in.readable(), out.writable()); - u8 *pin=in.rd(), *pend=pin+count; - complex *pout = out.wr(); - for ( ; pin *cp = &cstln->symbols[*pin]; - pout->re = Zout + cp->re; - pout->im = Zout + cp->im; - } - in.read(count); - out.written(count); + + void run() + { + if (!cstln) + { + fail("cstln_transmitter::run", "constellation not set"); + return; + } + int count = min(in.readable(), out.writable()); + u8 *pin = in.rd(), *pend = pin + count; + complex *pout = out.wr(); + for (; pin < pend; ++pin, ++pout) + { + complex *cp = &cstln->symbols[*pin]; + pout->re = Zout + cp->re; + pout->im = Zout + cp->im; + } + in.read(count); + out.written(count); } - private: + +private: pipereader in; - pipewriter< complex > out; - }; // cstln_transmitter + pipewriter > out; +}; +// cstln_transmitter +// FREQUENCY SHIFTER - // FREQUENCY SHIFTER +// Resolution is sample_freq/65536. - // Resolution is sample_freq/65536. - - template - struct rotator : runnable { - rotator(scheduler *sch, pipebuf< complex > &_in, - pipebuf< complex > &_out, float freq) - : runnable(sch, "rotator"), - in(_in), out(_out), index(0) { - int ifreq = freq * 65536; - if ( sch->debug ) - fprintf(stderr, "Rotate: req=%f real=%f\n", freq, ifreq/65536.0); - for ( int i=0; i<65536; ++i ) { - lut_cos[i] = cosf(2*M_PI * i * ifreq / 65536); - lut_sin[i] = sinf(2*M_PI * i * ifreq / 65536); - } +template +struct rotator: runnable +{ + rotator(scheduler *sch, pipebuf > &_in, pipebuf > &_out, float freq) : + runnable(sch, "rotator"), + in(_in), + out(_out), + index(0) + { + int ifreq = freq * 65536; + if (sch->debug) + fprintf(stderr, "Rotate: req=%f real=%f\n", freq, ifreq / 65536.0); + for (int i = 0; i < 65536; ++i) + { + lut_cos[i] = cosf(2 * M_PI * i * ifreq / 65536); + lut_sin[i] = sinf(2 * M_PI * i * ifreq / 65536); + } } - void run() { - unsigned long count = min(in.readable(), out.writable()); - complex *pin = in.rd(), *pend = pin+count; - complex *pout = out.wr(); - for ( ; pinre = pin->re*c - pin->im*s; - pout->im = pin->re*s + pin->im*c; - } - in.read(count); - out.written(count); + + void run() + { + unsigned long count = min(in.readable(), out.writable()); + complex *pin = in.rd(), *pend = pin + count; + complex *pout = out.wr(); + for (; pin < pend; ++pin, ++pout, ++index) + { + float c = lut_cos[index]; + float s = lut_sin[index]; + pout->re = pin->re * c - pin->im * s; + pout->im = pin->re * s + pin->im * c; + } + in.read(count); + out.written(count); } - private: - pipereader< complex > in; - pipewriter< complex > out; + +private: + pipereader > in; + pipewriter > out; float lut_cos[65536]; float lut_sin[65536]; unsigned short index; // Current phase - }; // rotator +}; +// rotator +// SPECTRUM-BASED CNR ESTIMATOR - // SPECTRUM-BASED CNR ESTIMATOR +// Assumes that the spectrum is as follows: +// +// ---|--noise---|-roll-off-|---carrier+noise----|-roll-off-|---noise--|--- +// | (bw/2) | (bw) | (bw/2) | (bw) | (bw/2) | +// +// Maximum roll-off 0.5 - // Assumes that the spectrum is as follows: - // - // ---|--noise---|-roll-off-|---carrier+noise----|-roll-off-|---noise--|--- - // | (bw/2) | (bw) | (bw/2) | (bw) | (bw/2) | - // - // Maximum roll-off 0.5 - - template - struct cnr_fft : runnable { - cnr_fft(scheduler *sch, pipebuf< complex > &_in, pipebuf &_out, - float _bandwidth, int nfft=4096) - : runnable(sch, "cnr_fft"), - bandwidth(_bandwidth), freq_tap(NULL), tap_multiplier(1), - decimation(1048576), kavg(0.1), - in(_in), out(_out), - fft(nfft), avgpower(NULL), phase(0) { - if ( bandwidth > 0.25 ) - fail("CNR estimator requires Fsampling > 4x Fsignal"); +template +struct cnr_fft: runnable +{ + cnr_fft(scheduler *sch, pipebuf > &_in, pipebuf &_out, float _bandwidth, int nfft = 4096) : + runnable(sch, "cnr_fft"), + bandwidth(_bandwidth), + freq_tap(NULL), + tap_multiplier(1), + decimation(1048576), + kavg(0.1), + in(_in), + out(_out), + fft(nfft), + avgpower(NULL), + phase(0) + { + if (bandwidth > 0.25) { + fail("cnr_fft::cnr_fft", "CNR estimator requires Fsampling > 4x Fsignal"); + } + m_data.allocate(fft.n); + m_power.allocate(fft.n); } float bandwidth; - float *freq_tap, tap_multiplier; + float *freq_tap, tap_multiplier; int decimation; float kavg; + IncrementalArray > m_data; + IncrementalArray m_power; - void run() { - while ( in.readable()>=fft.n && out.writable()>=1 ) { - phase += fft.n; - if ( phase >= decimation ) { - phase -= decimation; - do_cnr(); - } - in.read(fft.n); - } - } - - private: - - void do_cnr() { - float center_freq = freq_tap ? *freq_tap * tap_multiplier : 0; - int icf = floor(center_freq*fft.n+0.5); - complex data[fft.n]; - memcpy(data, in.rd(), fft.n*sizeof(data[0])); - fft.inplace(data, true); - T power[fft.n]; - for ( int i=0; i0 && n2>0) ? 10 * logf(c2/n2)/logf(10) : -50; - out.write(cnr); + void run() + { + while (in.readable() >= fft.n && out.writable() >= 1) + { + phase += fft.n; + if (phase >= decimation) + { + phase -= decimation; + do_cnr(); + } + in.read(fft.n); + } } - float avg__slots(int i0, int i1) { // i0 <= i1 - T s = 0; - for ( int i=i0; i<=i1; ++i ) s += avgpower[i&(fft.n-1)]; - return s / (i1-i0+1); +private: + + void do_cnr() + { + float center_freq = freq_tap ? *freq_tap * tap_multiplier : 0; + int icf = floor(center_freq * fft.n + 0.5); + //complex data[fft.n]; + complex *data = m_data.m_array; + memcpy(data, in.rd(), fft.n * sizeof(data[0])); + fft.inplace(data, true); + //T power[fft.n]; + T *power = m_power.m_array; + for (unsigned int i = 0; i < fft.n; ++i) + power[i] = data[i].re * data[i].re + data[i].im * data[i].im; + if (!avgpower) + { + // Initialize with first spectrum + avgpower = new T[fft.n]; + memcpy(avgpower, power, fft.n * sizeof(avgpower[0])); + } + // Accumulate and low-pass filter + for (unsigned int i = 0; i < fft.n; ++i) + avgpower[i] = avgpower[i] * (1 - kavg) + power[i] * kavg; + + int bw__slots = (bandwidth / 4) * fft.n; + if (!bw__slots) + return; + // Measure carrier+noise in center band + float c2plusn2 = avg__slots(icf - bw__slots, icf + bw__slots); + // Measure noise left and right of roll-off zones + float n2 = (avg__slots(icf - bw__slots * 4, icf - bw__slots * 3) + + avg__slots(icf + bw__slots * 3, icf + bw__slots * 4)) / 2; + float c2 = c2plusn2 - n2; + float cnr = (c2 > 0 && n2 > 0) ? 10 * logf(c2 / n2) / logf(10) : -50; + out.write(cnr); } - - pipereader< complex > in; - pipewriter< float > out; + + float avg__slots(int i0, int i1) + { // i0 <= i1 + T s = 0; + for (int i = i0; i <= i1; ++i) + s += avgpower[i & (fft.n - 1)]; + return s / (i1 - i0 + 1); + } + + pipereader > in; + pipewriter out; cfft_engine fft; T *avgpower; int phase; - }; // cnr_fft +}; +// cnr_fft - template - struct spectrum : runnable { +template +struct spectrum: runnable +{ static const int nfft = 1024; - spectrum(scheduler *sch, pipebuf< complex > &_in, - pipebuf &_out) - : runnable(sch, "spectrum"), - decimation(1048576), kavg(0.1), - in(_in), out(_out), - fft(nfft), avgpower(NULL), phase(0) { + spectrum(scheduler *sch, pipebuf > &_in, pipebuf &_out) : + runnable(sch, "spectrum"), + decimation(1048576), + kavg(0.1), + in(_in), + out(_out), + fft(nfft), + avgpower(NULL), + phase(0) + { } int decimation; float kavg; - void run() { - while ( in.readable()>=fft.n && out.writable()>=1 ) { - phase += fft.n; - if ( phase >= decimation ) { - phase -= decimation; - do_spectrum(); - } - in.read(fft.n); - } + void run() + { + while (in.readable() >= fft.n && out.writable() >= 1) + { + phase += fft.n; + if (phase >= decimation) + { + phase -= decimation; + do_spectrum(); + } + in.read(fft.n); + } } - private: +private: - void do_spectrum() { - complex data[fft.n]; - memcpy(data, in.rd(), fft.n*sizeof(data[0])); - fft.inplace(data, true); - float power[nfft]; - for ( int i=0; i data[fft.n]; + memcpy(data, in.rd(), fft.n * sizeof(data[0])); + fft.inplace(data, true); + float power[nfft]; + for (int i = 0; i < fft.n; ++i) + power[i] = (float) data[i].re * data[i].re + + (float) data[i].im * data[i].im; + if (!avgpower) + { + // Initialize with first spectrum + avgpower = new float[fft.n]; + memcpy(avgpower, power, fft.n * sizeof(avgpower[0])); + } + // Accumulate and low-pass filter + for (int i = 0; i < fft.n; ++i) + avgpower[i] = avgpower[i] * (1 - kavg) + power[i] * kavg; - // Reuse power[] - for ( int i=0; i > in; - pipewriter< float[nfft] > out; + pipereader > in; + pipewriter out; cfft_engine fft; T *avgpower; int phase; - }; // spectrum +}; +// spectrum - -} // namespace +}// namespace #endif // LEANSDR_SDR_H diff --git a/plugins/channelrx/demoddatv/leansdr/viterbi.h b/plugins/channelrx/demoddatv/leansdr/viterbi.h index 96bbff967..e4121ce73 100644 --- a/plugins/channelrx/demoddatv/leansdr/viterbi.h +++ b/plugins/channelrx/demoddatv/leansdr/viterbi.h @@ -11,271 +11,322 @@ // TBD This is very inefficient. For a specific trellis all loops // can be be unrolled. -namespace leansdr { +namespace leansdr +{ - // TS is an integer type for a least NSTATES+1 states. - // NSTATES is the number of states (e.g. 2^(K-1)). - // TUS is an integer type for uncoded symbols (branch identifiers). - // NUS is the number of uncoded symbols. - // TCS is an integer type for coded symbols (branch labels). - // NCS is the number of coded symbols. - // TP is a type for representing paths. - // TPM, TBM are unsigned integer types for path/branch metrics. - // TPM is at least as wide as TBM. +// TS is an integer type for a least NSTATES+1 states. +// NSTATES is the number of states (e.g. 2^(K-1)). +// TUS is an integer type for uncoded symbols (branch identifiers). +// NUS is the number of uncoded symbols. +// TCS is an integer type for coded symbols (branch labels). +// NCS is the number of coded symbols. +// TP is a type for representing paths. +// TPM, TBM are unsigned integer types for path/branch metrics. +// TPM is at least as wide as TBM. - template - struct trellis { - static const int NOSTATE = NSTATES+1; +template +struct trellis +{ + static const int NOSTATE = NSTATES + 1; - struct state { - struct branch { - TS pred; // Predecessor state or NOSTATE - TUS us; // Uncoded symbol - } branches[NCS]; // Incoming branches indexed by coded symbol + struct state + { + struct branch + { + TS pred; // Predecessor state or NOSTATE + TUS us; // Uncoded symbol + } branches[NCS]; // Incoming branches indexed by coded symbol } states[NSTATES]; - - trellis() { - for ( TS s=0; spred != NOSTATE ) { - fprintf(stderr, "Invalid convolutional code\n"); - exit(1); - } - b->pred = s; - b->us = us; - } - } + for (TS s = 0; s < NSTATES; ++s) + { + for (TUS us = 0; us < NUS; ++us) + { + // Run the convolutional encoder from state s with input us + uint64_t shiftreg = s; // TBD type + // Reverse bits + TUS us_rev = 0; + for (int b = 1; b < NUS; b *= 2) + if (us & b) + us_rev |= (NUS / 2 / b); + shiftreg |= us_rev * NSTATES; + uint32_t cs = 0; // TBD type + for (int g = 0; g < nG; ++g) + cs = (cs << 1) | parity(shiftreg & G[g]); + shiftreg /= NUS; // Shift bits for 1 uncoded symbol + // [us] at state [s] emits [cs] and leads to state [shiftreg]. + typename state::branch *b = &states[shiftreg].branches[cs]; + if (b->pred != NOSTATE) + { + fprintf(stderr, "leansdr::trellis::init_convolutional: Invalid convolutional code\n"); + return; + } + b->pred = s; + b->us = us; + } + } } - void dump() { - for ( int s=0; spred == NOSTATE ) - fprintf(stderr, " - "); - else - fprintf(stderr, " %02x+%x", b->pred, b->us); - } - fprintf(stderr, "\n"); - } + void dump() + { + for (int s = 0; s < NSTATES; ++s) + { + fprintf(stderr, "State %02x:", s); + for (int cs = 0; cs < NCS; ++cs) + { + typename state::branch *b = &states[s].branches[cs]; + if (b->pred == NOSTATE) + fprintf(stderr, " - "); + else + fprintf(stderr, " %02x+%x", b->pred, b->us); + } + fprintf(stderr, "\n"); + } } - }; +}; - // Interface that hides the templated internals. - template - struct viterbi_dec_interface { - virtual TUS update(TBM costs[], TPM *quality=NULL)=0; - virtual TUS update(TCS s, TBM cost, TPM *quality=NULL)=0; - virtual TUS update(int nm, TCS cs[], TBM costs[], TPM *quality=NULL)=0; - }; +// Interface that hides the templated internals. +template +struct viterbi_dec_interface +{ + virtual TUS update(TBM costs[], TPM *quality = NULL)=0; + virtual TUS update(TCS s, TBM cost, TPM *quality = NULL)=0; + virtual TUS update(int nm, TCS cs[], TBM costs[], TPM *quality = NULL)=0; +}; - template - struct viterbi_dec : viterbi_dec_interface { +template +struct viterbi_dec: viterbi_dec_interface +{ trellis *trell; - struct state { - TPM cost; // Metric of best path leading to this state - TP path; // Best path leading to this state + struct state + { + TPM cost; // Metric of best path leading to this state + TP path; // Best path leading to this state }; typedef state statebank[NSTATES]; state statebanks[2][NSTATES]; statebank *states, *newstates; // Alternate between banks viterbi_dec(trellis *_trellis) : - trell(_trellis) + trell(_trellis) { - states = &statebanks[0]; - newstates = &statebanks[1]; - for ( TS s=0; smax_tpm; max_tpm=max_tpm*2+1 ) ; - } + states = &statebanks[0]; + newstates = &statebanks[1]; + for (TS s = 0; s < NSTATES; ++s) + (*states)[s].cost = 0; + // Determine max value that can fit in TPM + max_tpm = (TPM) 0 - 1; + if (max_tpm < 0) + { + // TPM is signed + for (max_tpm = 0; max_tpm * 2 + 1 > max_tpm; max_tpm = max_tpm * 2 + 1) + ; + } } // Update with full metric - - TUS update(TBM costs[NCS], TPM *quality=NULL) { - TPM best_tpm = max_tpm, best2_tpm = max_tpm; - TS best_state = 0; - // Update all states - for ( int s=0; s::state::branch *best_b = NULL; - // Select best branch - for ( int cs=0; cs::state::branch *b = - &trell->states[s].branches[cs]; - if ( b->pred == trell->NOSTATE ) continue; - TPM m = (*states)[b->pred].cost + costs[cs]; - if ( m <= best_m ) { // <= guarantees one match - best_m = m; - best_b = b; - } - } - (*newstates)[s].path = (*states)[best_b->pred].path; - (*newstates)[s].path.append(best_b->us); - (*newstates)[s].cost = best_m; - // Select best and second-best states - if ( best_m < best_tpm ) { - best_state = s; - best2_tpm = best_tpm; - best_tpm = best_m; - } else if ( best_m < best2_tpm ) - best2_tpm = best_m; - } - // Swap banks - { statebank *tmp=states; states=newstates; newstates=tmp; } - // Prevent overflow of path metrics - for ( TS s=0; s::state::branch *best_b = NULL; + // Select best branch + for (int cs = 0; cs < NCS; ++cs) + { + typename trellis::state::branch *b = &trell->states[s].branches[cs]; + if (b->pred == trell->NOSTATE) + continue; + TPM m = (*states)[b->pred].cost + costs[cs]; + if (m <= best_m) + { // <= guarantees one match + best_m = m; + best_b = b; + } + } + (*newstates)[s].path = (*states)[best_b->pred].path; + (*newstates)[s].path.append(best_b->us); + (*newstates)[s].cost = best_m; + // Select best and second-best states + if (best_m < best_tpm) + { + best_state = s; + best2_tpm = best_tpm; + best_tpm = best_m; + } + else if (best_m < best2_tpm) + best2_tpm = best_m; + } + // Swap banks + { + statebank *tmp = states; + states = newstates; + newstates = tmp; + } + // Prevent overflow of path metrics + for (TS s = 0; s < NSTATES; ++s) + (*states)[s].cost -= best_tpm; #if 0 - // Observe that the min-max range remains bounded - fprintf(stderr,"-%2d = [", best_tpm); - for ( TS s=0; s::state::branch *best_b = NULL; - for ( int im=0; im::state::branch *b = - &trell->states[s].branches[cs[im]]; - if ( b->pred == trell->NOSTATE ) continue; - TPM m = (*states)[b->pred].cost + costs[im]; - if ( m <= best_m ) { // <= guarantees one match - best_m = m; - best_b = b; - } - } - if ( nm != NCS ) { - // Also scan the other branches. - // We actually rescan the branches with metrics. - // This works because costs are negative. - for ( int cs=0; cs::state::branch *b = - &trell->states[s].branches[cs]; - if ( b->pred == trell->NOSTATE ) continue; - TPM m = (*states)[b->pred].cost; - if ( m <= best_m ) { - best_m = m; - best_b = b; - } - } - } - (*newstates)[s].path = (*states)[best_b->pred].path; - (*newstates)[s].path.append(best_b->us); - (*newstates)[s].cost = best_m; - // Select best states - if ( best_m < best_tpm ) { - best_state = s; - best2_tpm = best_tpm; - best_tpm = best_m; - } else if ( best_m < best2_tpm ) - best2_tpm = best_m; - } - // Swap banks - { statebank *tmp=states; states=newstates; newstates=tmp; } - // Prevent overflow of path metrics - for ( TS s=0; s::state::branch *best_b = NULL; + for (int im = 0; im < nm; ++im) + { + typename trellis::state::branch *b = &trell->states[s].branches[cs[im]]; + if (b->pred == trell->NOSTATE) + continue; + TPM m = (*states)[b->pred].cost + costs[im]; + if (m <= best_m) + { // <= guarantees one match + best_m = m; + best_b = b; + } + } + if (nm != NCS) + { + // Also scan the other branches. + // We actually rescan the branches with metrics. + // This works because costs are negative. + for (int cs = 0; cs < NCS; ++cs) + { + typename trellis::state::branch *b = &trell->states[s].branches[cs]; + if (b->pred == trell->NOSTATE) + continue; + TPM m = (*states)[b->pred].cost; + if (m <= best_m) + { + best_m = m; + best_b = b; + } + } + } + (*newstates)[s].path = (*states)[best_b->pred].path; + (*newstates)[s].path.append(best_b->us); + (*newstates)[s].cost = best_m; + // Select best states + if (best_m < best_tpm) + { + best_state = s; + best2_tpm = best_tpm; + best_tpm = best_m; + } + else if (best_m < best2_tpm) + best2_tpm = best_m; + } + // Swap banks + { + statebank *tmp = states; + states = newstates; + newstates = tmp; + } + // Prevent overflow of path metrics + for (TS s = 0; s < NSTATES; ++s) + (*states)[s].cost -= best_tpm; #if 0 - // Observe that the min-max range remains bounded - fprintf(stderr,"-%2d = [", best_tpm); - for ( TS s=0; s - struct bitpath { +template +struct bitpath +{ T val; - bitpath() : val(0) { } - void append(TUS us) { val = (val<>(DEPTH-1)*NBITS) & ((1<> (DEPTH - 1) * NBITS) & ((1 << NBITS) - 1); + } +}; } // namespace diff --git a/plugins/channelrx/demoddatv/readme.md b/plugins/channelrx/demoddatv/readme.md index fff354b9e..0fd2bc7b1 100644 --- a/plugins/channelrx/demoddatv/readme.md +++ b/plugins/channelrx/demoddatv/readme.md @@ -1,76 +1,136 @@

DATV demodulator plugin

-

Dependencies

+

Specific dependencies

- ffmpeg - libavcodec-dev - libavformat-dev +[LeanSDR](https://github.com/pabr/leansdr) framework from F4DAV is intensively used. It has been integrated in the source tree and modified to suit SDRangel specific needs. +

Introduction

-TBD... +This plugin can be used to view digital amateur analog television transmissions a.k.a DATV. The only supported standard for now is DVB-S in various modulations. The standard modulation is QPSK but experimental configurations with other PSK modulations (BPSK, 8PSK, QAMn) can be selected. -This plugin can be used to view amateur analog television transmissions a.k.a ATV. The video signal is in fact a 625 lines standard signal black and white or color (PAL, NTSC) but only the black and white levels (luminance) is retained. There is no provision to demodulate the audio subcarrier either. The modulation can be either AM or FM. - -The whole bandwidth available to the channel is used. That is it runs at the device sample rate possibly downsampled by a power of two in the source plugin. It expects an integer number of MS/s and acceptable results require a sample rate of at least 6 MS/s (Airspy Mini, Airspy, BladerRF, HackRF). +The whole bandwidth available to the channel is used. That is it runs at the device sample rate possibly downsampled by a power of two in the source plugin.

Interface

-![ATV Demodulator plugin GUI](../../../doc/img/ATVDemod_plugin.png) +![DATV Demodulator plugin GUI](../../../doc/img/DATVDemod_plugin.png) -

1: Image

+

A: RF settings

-This is where the TV image appears. +![DATV Demodulator plugin RF GUI](../../../doc/img/DATVDemod_pluginRF.png) -

2: Modulation

+

A.1: Channel frequency shift

- - FM1: this is Frequency Modulation with approximative demodulation algorithm not using atan2 - - FM2: this is Frequency Modulation with less approximative demodulation algorithm still not using atan2 - - AM: this is Amplitude Modulation +This is the shift of channel center frequency from RF passband center frequency + +

A.2: RF bandwidth

+ +Sets the bandwidth of the channel filter + +

A.3: Channel power

+ +Power of signal received in the channel (dB) + +

B: DATV section

+ +![DATV Demodulator plugin DATV GUI](../../../doc/img/DATVDemod_pluginDATV.png) + +

B.1: Symbol constellation

+ +This is the constellation of the PSK or QAM synchronized signal. When the demodulation parameters are set correctly (modulation type, symbol rate and filtering) and signal is strong enough to recover symbol synchronization the purple dots appear close to the white crosses. White crosses represent the ideal symbols positions in the I/Q plane. + +

B.2a: DATV signal settings

+ +![DATV Demodulator plugin DATV2 GUI](../../../doc/img/DATVDemod_pluginDATV2.png) + +
B.2a.1: DATV standard
+ +For now only the DVB-S standard is available + +
B.2a.2: Modulation type
+ + - BPSK: binary phase shift keying. Symbols are in π/4 and -3π/4 positions. + - QPSK: quadrature phase shift keying. Symbols are in π/4, 3π/4, -3π/4 and -π/4 positions. + - 8PSK: 8 phase shift keying a.k.a. π/4 QPSK. Symbols are in 0, π/4, π/2, 3π/4, π, -3π/4, -π/2 and -π/4 positions + - APSK16: amplitude and phase shift keying with 16 symbols + - APSK32: amplitude and phase shift keying with 32 symbols + - APSK64e: amplitude and phase shift keying with 64 symbols + - QAM16: quadrature amplitude modulation with 16 symbols + - QAM64: quadrature amplitude modulation with 64 symbols + - QAM256: quadrature amplitude modulation with 256 symbols -For FM choose the algorithm that best suits your conditions. +
B.2a.3: Symbol rate
-

3: Frames Per Second

+This controls the expected symbol rate -This combo lets you chose between a 25 FPS or 30 FPS standard. +
B.2a.4: FEC rate
-

4: Horizontal sync

+Choice between 1/2 , 2/3 , 3/4, 5/6 and 7/8. -Use this button to toggle horizontal synchronization processing. +
B.2a.5: Notch filter
-

5: Vertical sync

+LeanSDR feature: attempts to fix signal spectrum shape by eliminating peaks. This is the number of peaks to be tracked. It is safer to keep the 0 default (no notch). -Use this button to toggle vertical synchronization processing. +
B.2a.6: Fast lock
-

6: Half image

+Faster signal decode but may yield more errors at startup. -Use this button to disable (on) or enable interlacing of the two half images (off). +
B.2a.7: Allow drift
-

7: Reset defaults

+Small frequency drift compensation. -Use this push button to reset values to a standard setting: +
B.2a.8: Hard metric
- - FM1 modulation - - 25 FPS - - Horizontal and vertical syncs active - - Interlacing - - 100 mV sync level - - 310 mV black level - - 64 microsecond line length - - 3 microsecond sync length +Constellation hardening (LeanSDR feature). + +
B.2a.9: Viterbi
+ +Viterbi decoding. Be aware that this is CPU intensive. Should be limited to FEC 1/2 , 2/3 and 3/4 in practice. + +
B.2a.10: Reset to defaults
+ +Push this button when you are lost... + +
B.2a.11: Filter
+ + - FIR Linear + - FIR Nearest + - RRC (Root Raised Cosine): when selected additional controls for roll-off factor (%) and excursion (dB) are provided. + +
B.2a.12: Amount of data decoded
+ +Automatically adjusts unit (kB, MB, ...) + +
B.2a.13: Stream speed
+ +
B.2a.14: Buffer status
+ +Gauge that shows percentage of buffer queue length + +

B.2b: DATV video stream

+ +![DATV Demodulator plugin video GUI](../../../doc/img/DATVDemod_pluginVideo.png) + +
B.2b.1: Image thumbnail
+ +Use full screen button (5) to switch to full screen video + +
B.2b.2: Stream information
+ +
B.2b.3: Stream decoding status
+ +These non clickable checkboxes report the decoding status (checked when OK): + + - data: reception on going + - transport: transport stream detected + - video: video data detected + - decoding: video being decoded -

8: Synchronization level

+
B.2b.4: Play/pause video playback
+ +
B.2b.4: Full screen mode
-Use this slider to adjust the top level of the synchronization pulse on a 0 to 1V scale. The value in mV appears on the right of the slider. Nominal value: 100 mV. - -

9: Black level

- -Use this slider to adjust the black level of the video signal on a 0 to 1V scale. The value in mV appears on the right of the slider. Nominal value: 310 mV. - -

10: Line length

- -This is the line length in time units. The value in microseconds appears on the right of the slider. Nominal value: 64 microseconds. - -

10: Top length

- -This is the length in time units of a synchronization top. The value in microseconds appears on the right of the slider. Nominal value 3 microseconds. \ No newline at end of file +Click on this button to see video in full screen mode then click anywhere on the screen to exit full screen mode \ No newline at end of file diff --git a/plugins/channelrx/demoddsd/CMakeLists.txt b/plugins/channelrx/demoddsd/CMakeLists.txt index 43faa4559..5932da235 100644 --- a/plugins/channelrx/demoddsd/CMakeLists.txt +++ b/plugins/channelrx/demoddsd/CMakeLists.txt @@ -1,5 +1,7 @@ project(dsddemod) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + set(dsddemod_SOURCES dsddemod.cpp dsddemodgui.cpp @@ -7,6 +9,7 @@ set(dsddemod_SOURCES dsddemodbaudrates.cpp dsddemodsettings.cpp dsddecoder.cpp + dsdstatustextdialog.cpp ) set(dsddemod_HEADERS @@ -16,16 +19,19 @@ set(dsddemod_HEADERS dsddemodbaudrates.h dsddemodsettings.h dsddecoder.h + dsdstatustextdialog.h ) set(dsddemod_FORMS dsddemodgui.ui + dsdstatustextdialog.ui ) if (BUILD_DEBIAN) include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${LIBDSDCCSRC} ${LIBMBELIBSRC} ) @@ -33,6 +39,7 @@ else (BUILD_DEBIAN) include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${LIBDSDCC_INCLUDE_DIR} ${LIBMBE_INCLUDE_DIR} ) @@ -70,6 +77,6 @@ target_link_libraries(demoddsd endif (BUILD_DEBIAN) -qt5_use_modules(demoddsd Core Widgets) +target_link_libraries(demoddsd Qt5::Core Qt5::Widgets) install(TARGETS demoddsd DESTINATION lib/plugins/channelrx) diff --git a/plugins/channelrx/demoddsd/demoddsd.pro b/plugins/channelrx/demoddsd/demoddsd.pro index ff90fb57a..8b8c96bc7 100644 --- a/plugins/channelrx/demoddsd/demoddsd.pro +++ b/plugins/channelrx/demoddsd/demoddsd.pro @@ -17,21 +17,23 @@ DEFINES += USE_SSE4_1=1 QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 -CONFIG(MINGW32):LIBDSDCCSRC = "D:\softs\dsdcc" -CONFIG(MINGW64):LIBDSDCCSRC = "D:\softs\dsdcc" +CONFIG(MINGW32):LIBDSDCCSRC = "C:\softs\dsdcc" +CONFIG(MINGW64):LIBDSDCCSRC = "C:\softs\dsdcc" CONFIG(macx):LIBDSDCCSRC = "../../../../deps/dsdcc" -CONFIG(MINGW32):LIBMBELIBSRC = "D:\softs\mbelib" -CONFIG(MINGW64):LIBMBELIBSRC = "D:\softs\mbelib" +CONFIG(MINGW32):LIBMBELIBSRC = "C:\softs\mbelib" +CONFIG(MINGW64):LIBMBELIBSRC = "C:\softs\mbelib" CONFIG(macx):LIBMBELIBSRC = "../../../../deps/mbelib" -CONFIG(MINGW32):INCLUDEPATH += "D:\boost_1_58_0" -CONFIG(MINGW64):INCLUDEPATH += "D:\boost_1_58_0" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\boost_1_66_0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\boost_1_66_0" CONFIG(macx):INCLUDEPATH += "../../../../../boost_1_64_0" INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui +INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client INCLUDEPATH += $$LIBDSDCCSRC INCLUDEPATH += $$LIBMBELIBSRC @@ -43,19 +45,23 @@ dsddemod.cpp\ dsddemodgui.cpp\ dsddemodplugin.cpp\ dsddemodbaudrates.cpp\ -dsddemodsettings.cpp +dsddemodsettings.cpp\ +dsdstatustextdialog.cpp HEADERS = dsddecoder.h\ dsddemod.h\ dsddemodgui.h\ dsddemodplugin.h\ dsddemodbaudrates.h\ -dsddemodsettings.h +dsddemodsettings.h\ +dsdstatustextdialog.h -FORMS = dsddemodgui.ui +FORMS = dsddemodgui.ui\ +dsdstatustextdialog.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui +LIBS += -L../../../swagger/$${build_subdir} -lswagger LIBS += -L../../../dsdcc/$${build_subdir} -ldsdcc RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/plugins/channelrx/demoddsd/dsddecoder.cpp b/plugins/channelrx/demoddsd/dsddecoder.cpp index c8cb55afa..54c8d75e9 100644 --- a/plugins/channelrx/demoddsd/dsddecoder.cpp +++ b/plugins/channelrx/demoddsd/dsddecoder.cpp @@ -15,7 +15,7 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "../../channelrx/demoddsd/dsddecoder.h" +#include "dsddecoder.h" #include #include "audio/audiofifo.h" @@ -36,6 +36,16 @@ DSDDecoder::~DSDDecoder() { } +void DSDDecoder::set48k(bool to48k) +{ + m_decoder.setUpsampling(to48k ? 6 : 0); +} + +void DSDDecoder::setUpsampling(int upsampling) +{ + m_decoder.setUpsampling(upsampling); +} + void DSDDecoder::setBaudRate(int baudRate) { if (baudRate == 2400) diff --git a/plugins/channelrx/demoddsd/dsddecoder.h b/plugins/channelrx/demoddsd/dsddecoder.h index 525281a8d..9f7343169 100644 --- a/plugins/channelrx/demoddsd/dsddecoder.h +++ b/plugins/channelrx/demoddsd/dsddecoder.h @@ -65,12 +65,15 @@ public: const DSDcc::DSDDstar& getDStarDecoder() const { return m_decoder.getDStarDecoder(); } const DSDcc::DSDdPMR& getDPMRDecoder() const { return m_decoder.getDPMRDecoder(); } const DSDcc::DSDYSF& getYSFDecoder() const { return m_decoder.getYSFDecoder(); } + const DSDcc::DSDNXDN& getNXDNDecoder() const { return m_decoder.getNXDNDecoder(); } void setMyPoint(float lat, float lon) { m_decoder.setMyPoint(lat, lon); } void setAudioGain(float gain) { m_decoder.setAudioGain(gain); } void setBaudRate(int baudRate); void setSymbolPLLLock(bool pllLock) { m_decoder.setSymbolPLLLock(pllLock); } void useHPMbelib(bool useHP) { m_decoder.useHPMbelib(useHP); } + void set48k(bool to48k); + void setUpsampling(int upsampling); private: DSDcc::DSDDecoder m_decoder; diff --git a/plugins/channelrx/demoddsd/dsddemod.cpp b/plugins/channelrx/demoddsd/dsddemod.cpp index d668cc743..04a9169e3 100644 --- a/plugins/channelrx/demoddsd/dsddemod.cpp +++ b/plugins/channelrx/demoddsd/dsddemod.cpp @@ -18,15 +18,23 @@ #include #include +#include #include #include +#include "SWGChannelSettings.h" +#include "SWGDSDDemodSettings.h" +#include "SWGChannelReport.h" +#include "SWGDSDDemodReport.h" +#include "SWGRDSReport.h" + #include "audio/audiooutput.h" #include "dsp/dspengine.h" #include "dsp/threadedbasebandsamplesink.h" #include "dsp/downchannelizer.h" #include "dsp/dspcommands.h" #include "device/devicesourceapi.h" +#include "util/db.h" #include "dsddemod.h" @@ -50,12 +58,13 @@ DSDDemod::DSDDemod(DeviceSourceAPI *deviceAPI) : m_squelchGate(0), m_squelchLevel(1e-4), m_squelchOpen(false), - m_fmExcursion(24), + m_squelchDelayLine(24000), m_audioFifo1(48000), m_audioFifo2(48000), - m_scope(0), + m_scopeXY(0), m_scopeEnabled(true), m_dsdDecoder(), + m_signalFormat(signalFormatNone), m_settingsMutex(QMutex::Recursive) { setObjectName(m_channelId); @@ -72,28 +81,24 @@ DSDDemod::DSDDemod(DeviceSourceAPI *deviceAPI) : m_magsqPeak = 0.0f; m_magsqCount = 0; - DSPEngine::instance()->addAudioSink(&m_audioFifo1); - DSPEngine::instance()->addAudioSink(&m_audioFifo2); + DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo1, getInputMessageQueue()); + DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo2, getInputMessageQueue()); + m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate(); - m_udpBufferAudio = new UDPSink(this, m_udpBlockSize, m_settings.m_udpPort); - m_audioFifo1.setUDPSink(m_udpBufferAudio); - m_audioFifo2.setUDPSink(m_udpBufferAudio); + applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); m_channelizer = new DownChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); m_deviceAPI->addThreadedSink(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - - applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); - applySettings(m_settings, true); } DSDDemod::~DSDDemod() { delete[] m_sampleBuffer; - DSPEngine::instance()->removeAudioSink(&m_audioFifo1); - DSPEngine::instance()->removeAudioSink(&m_audioFifo2); - delete m_udpBufferAudio; + DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo1); + DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo2); m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSink(m_threadedChannelizer); @@ -150,11 +155,13 @@ void DSDDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto { if (m_squelchGate > 0) { - if (m_squelchCount < m_squelchGate) { + + if (m_squelchCount < m_squelchGate*2) { m_squelchCount++; } - m_squelchOpen = m_squelchCount == m_squelchGate; + m_squelchDelayLine.write(demod); + m_squelchOpen = m_squelchCount > m_squelchGate; } else { @@ -163,14 +170,33 @@ void DSDDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto } else { - m_squelchCount = 0; - m_squelchOpen = false; + if (m_squelchGate > 0) + { + if (m_squelchCount > 0) { + m_squelchCount--; + } + + m_squelchDelayLine.write(0); + m_squelchOpen = m_squelchCount > m_squelchGate; + } + else + { + m_squelchOpen = false; + } } if (m_squelchOpen) { - sampleDSD = demod * 32768.0f; // DSD decoder takes int16 samples - sample = demod * SDR_RX_SCALEF; // scale to sample size + if (m_squelchGate > 0) + { + sampleDSD = m_squelchDelayLine.readBack(m_squelchGate) * 32768.0f; // DSD decoder takes int16 samples + sample = m_squelchDelayLine.readBack(m_squelchGate) * SDR_RX_SCALEF; // scale to sample size + } + else + { + sampleDSD = demod * 32768.0f; // DSD decoder takes int16 samples + sample = demod * SDR_RX_SCALEF; // scale to sample size + } } else { @@ -200,7 +226,7 @@ void DSDDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto if (m_settings.m_syncOrConstellation) { - Sample s(sample, m_dsdDecoder.getSymbolSyncSample() * m_scaleFromShort); + Sample s(sample, m_dsdDecoder.getSymbolSyncSample() * m_scaleFromShort * 0.84); m_scopeSampleBuffer.push_back(s); } else @@ -221,6 +247,7 @@ void DSDDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto m_settings.m_volume * 10.0, m_settings.m_tdmaStereo ? 1 : 3, // left or both channels m_settings.m_highPassFilter, + m_audioSampleRate/8000, // upsample from native 8k &m_audioFifo1); } @@ -237,6 +264,7 @@ void DSDDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto m_settings.m_volume * 10.0, m_settings.m_tdmaStereo ? 2 : 3, // right or both channels m_settings.m_highPassFilter, + m_audioSampleRate/8000, // upsample from native 8k &m_audioFifo2); } @@ -268,7 +296,7 @@ void DSDDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto if (nbAudioSamples > 0) { if (!m_settings.m_audioMute) { - m_audioFifo1.write((const quint8*) dsdAudio, nbAudioSamples, 10); + m_audioFifo1.write((const quint8*) dsdAudio, nbAudioSamples); } m_dsdDecoder.resetAudio1(); @@ -283,7 +311,7 @@ void DSDDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto if (nbAudioSamples > 0) { if (!m_settings.m_audioMute) { - m_audioFifo2.write((const quint8*) dsdAudio, nbAudioSamples, 10); + m_audioFifo2.write((const quint8*) dsdAudio, nbAudioSamples); } m_dsdDecoder.resetAudio2(); @@ -303,10 +331,9 @@ void DSDDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto // } } - - if ((m_scope != 0) && (m_scopeEnabled)) + if ((m_scopeXY != 0) && (m_scopeEnabled)) { - m_scope->feed(m_scopeSampleBuffer.begin(), m_scopeSampleBuffer.end(), true); // true = real samples for what it's worth + m_scopeXY->feed(m_scopeSampleBuffer.begin(), m_scopeSampleBuffer.end(), true); // true = real samples for what it's worth } m_settingsMutex.unlock(); @@ -364,6 +391,24 @@ bool DSDDemod::handleMessage(const Message& cmd) m_dsdDecoder.setMyPoint(cfg.getMyLatitude(), cfg.getMyLongitude()); return true; } + else if (DSPConfigureAudio::match(cmd)) + { + DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd; + uint32_t sampleRate = cfg.getSampleRate(); + + qDebug() << "DSDDemod::handleMessage: DSPConfigureAudio:" + << " sampleRate: " << sampleRate; + + if (sampleRate != m_audioSampleRate) { + applyAudioSampleRate(sampleRate); + } + + return true; + } + else if (BasebandSampleSink::MsgThreadedSink::match(cmd)) + { + return true; + } else if (DSPSignalNotification::match(cmd)) { return true; @@ -374,6 +419,20 @@ bool DSDDemod::handleMessage(const Message& cmd) } } +void DSDDemod::applyAudioSampleRate(int sampleRate) +{ + int upsampling = sampleRate / 8000; + + qDebug("DSDDemod::applyAudioSampleRate: audio rate: %d upsample by %d", sampleRate, upsampling); + + if (sampleRate % 8000 != 0) { + qDebug("DSDDemod::applyAudioSampleRate: audio will sound best with sample rates that are integer multiples of 8 kS/s"); + } + + m_dsdDecoder.setUpsampling(upsampling); + m_audioSampleRate = sampleRate; +} + void DSDDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force) { qDebug() << "DSDDemod::applyChannelSettings:" @@ -391,7 +450,7 @@ void DSDDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffse m_settingsMutex.lock(); m_interpolator.create(16, inputSampleRate, (m_settings.m_rfBandwidth) / 2.2); m_interpolatorDistanceRemain = 0; - m_interpolatorDistance = (Real) inputSampleRate / (Real) m_settings.m_audioSampleRate; + m_interpolatorDistance = (Real) inputSampleRate / (Real) 48000; m_settingsMutex.unlock(); } @@ -402,25 +461,23 @@ void DSDDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffse void DSDDemod::applySettings(const DSDDemodSettings& settings, bool force) { qDebug() << "DSDDemod::applySettings: " - << " m_inputFrequencyOffset: " << m_settings.m_inputFrequencyOffset - << " m_rfBandwidth: " << m_settings.m_rfBandwidth - << " m_fmDeviation: " << m_settings.m_fmDeviation - << " m_demodGain: " << m_settings.m_demodGain - << " m_volume: " << m_settings.m_volume - << " m_baudRate: " << m_settings.m_baudRate - << " m_squelchGate" << m_settings.m_squelchGate - << " m_squelch: " << m_settings.m_squelch - << " m_audioMute: " << m_settings.m_audioMute - << " m_enableCosineFiltering: " << m_settings.m_enableCosineFiltering - << " m_syncOrConstellation: " << m_settings.m_syncOrConstellation - << " m_slot1On: " << m_settings.m_slot1On - << " m_slot2On: " << m_settings.m_slot2On - << " m_tdmaStereo: " << m_settings.m_tdmaStereo - << " m_pllLock: " << m_settings.m_pllLock - << " m_udpCopyAudio: " << m_settings.m_copyAudioToUDP - << " m_udpAddress: " << m_settings.m_udpAddress - << " m_udpPort: " << m_settings.m_udpPort - << " m_highPassFilter: "<< m_settings.m_highPassFilter + << " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset + << " m_rfBandwidth: " << settings.m_rfBandwidth + << " m_fmDeviation: " << settings.m_fmDeviation + << " m_demodGain: " << settings.m_demodGain + << " m_volume: " << settings.m_volume + << " m_baudRate: " << settings.m_baudRate + << " m_squelchGate" << settings.m_squelchGate + << " m_squelch: " << settings.m_squelch + << " m_audioMute: " << settings.m_audioMute + << " m_enableCosineFiltering: " << settings.m_enableCosineFiltering + << " m_syncOrConstellation: " << settings.m_syncOrConstellation + << " m_slot1On: " << settings.m_slot1On + << " m_slot2On: " << settings.m_slot2On + << " m_tdmaStereo: " << settings.m_tdmaStereo + << " m_pllLock: " << settings.m_pllLock + << " m_highPassFilter: "<< settings.m_highPassFilter + << " m_audioDeviceName: " << settings.m_audioDeviceName << " force: " << force; if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) @@ -428,14 +485,14 @@ void DSDDemod::applySettings(const DSDDemodSettings& settings, bool force) m_settingsMutex.lock(); m_interpolator.create(16, m_inputSampleRate, (settings.m_rfBandwidth) / 2.2); m_interpolatorDistanceRemain = 0; - m_interpolatorDistance = (Real) m_inputSampleRate / (Real) settings.m_audioSampleRate; - m_phaseDiscri.setFMScaling((float) settings.m_rfBandwidth / (float) settings.m_fmDeviation); + m_interpolatorDistance = (Real) m_inputSampleRate / (Real) 48000; + //m_phaseDiscri.setFMScaling((float) settings.m_rfBandwidth / (float) settings.m_fmDeviation); m_settingsMutex.unlock(); } if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) { - m_phaseDiscri.setFMScaling((float) settings.m_rfBandwidth / (float) settings.m_fmDeviation); + m_phaseDiscri.setFMScaling(48000.0f / (2.0f*settings.m_fmDeviation)); } if ((settings.m_squelchGate != m_settings.m_squelchGate) || force) @@ -475,26 +532,25 @@ void DSDDemod::applySettings(const DSDDemodSettings& settings, bool force) m_dsdDecoder.setSymbolPLLLock(settings.m_pllLock); } - if ((settings.m_udpAddress != m_settings.m_udpAddress) - || (settings.m_udpPort != m_settings.m_udpPort) || force) - { - m_udpBufferAudio->setAddress(const_cast(settings.m_udpAddress)); - m_udpBufferAudio->setPort(settings.m_udpPort); - } - - if ((settings.m_copyAudioToUDP != m_settings.m_copyAudioToUDP) - || (settings.m_slot1On != m_settings.m_slot1On) - || (settings.m_slot2On != m_settings.m_slot2On) || force) - { - m_audioFifo1.setCopyToUDP(settings.m_slot1On && settings.m_copyAudioToUDP); - m_audioFifo2.setCopyToUDP(settings.m_slot2On && !settings.m_slot1On && settings.m_copyAudioToUDP); - } - if ((settings.m_highPassFilter != m_settings.m_highPassFilter) || force) { m_dsdDecoder.useHPMbelib(settings.m_highPassFilter); } + if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) + { + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName); + //qDebug("AMDemod::applySettings: audioDeviceName: %s audioDeviceIndex: %d", qPrintable(settings.m_audioDeviceName), audioDeviceIndex); + audioDeviceManager->addAudioSink(&m_audioFifo1, getInputMessageQueue(), audioDeviceIndex); + audioDeviceManager->addAudioSink(&m_audioFifo2, getInputMessageQueue(), audioDeviceIndex); + uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex); + + if (m_audioSampleRate != audioSampleRate) { + applyAudioSampleRate(audioSampleRate); + } + } + m_settings = settings; } @@ -519,3 +575,370 @@ bool DSDDemod::deserialize(const QByteArray& data) return false; } } + +const char *DSDDemod::updateAndGetStatusText() +{ + formatStatusText(); + return m_formatStatusText; +} + +void DSDDemod::formatStatusText() +{ + switch (getDecoder().getSyncType()) + { + case DSDcc::DSDDecoder::DSDSyncDMRDataMS: + case DSDcc::DSDDecoder::DSDSyncDMRDataP: + case DSDcc::DSDDecoder::DSDSyncDMRVoiceMS: + case DSDcc::DSDDecoder::DSDSyncDMRVoiceP: + if (m_signalFormat != signalFormatDMR) + { + strcpy(m_formatStatusText, "Sta: __ S1: __________________________ S2: __________________________"); + } + + switch (getDecoder().getStationType()) + { + case DSDcc::DSDDecoder::DSDBaseStation: + memcpy(&m_formatStatusText[5], "BS ", 3); + break; + case DSDcc::DSDDecoder::DSDMobileStation: + memcpy(&m_formatStatusText[5], "MS ", 3); + break; + default: + memcpy(&m_formatStatusText[5], "NA ", 3); + break; + } + + memcpy(&m_formatStatusText[12], getDecoder().getDMRDecoder().getSlot0Text(), 26); + memcpy(&m_formatStatusText[43], getDecoder().getDMRDecoder().getSlot1Text(), 26); + m_signalFormat = signalFormatDMR; + break; + case DSDcc::DSDDecoder::DSDSyncDStarHeaderN: + case DSDcc::DSDDecoder::DSDSyncDStarHeaderP: + case DSDcc::DSDDecoder::DSDSyncDStarN: + case DSDcc::DSDDecoder::DSDSyncDStarP: + if (m_signalFormat != signalFormatDStar) + { + // 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 + // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0.. + strcpy(m_formatStatusText, "________/____>________|________>________|____________________|______:___/_____._"); + // MY UR RPT1 RPT2 Info Loc Target + } + + { + const std::string& rpt1 = getDecoder().getDStarDecoder().getRpt1(); + const std::string& rpt2 = getDecoder().getDStarDecoder().getRpt2(); + const std::string& mySign = getDecoder().getDStarDecoder().getMySign(); + const std::string& yrSign = getDecoder().getDStarDecoder().getYourSign(); + + if (rpt1.length() > 0) { // 0 or 8 + memcpy(&m_formatStatusText[23], rpt1.c_str(), 8); + } + if (rpt2.length() > 0) { // 0 or 8 + memcpy(&m_formatStatusText[32], rpt2.c_str(), 8); + } + if (yrSign.length() > 0) { // 0 or 8 + memcpy(&m_formatStatusText[14], yrSign.c_str(), 8); + } + if (mySign.length() > 0) { // 0 or 13 + memcpy(&m_formatStatusText[0], mySign.c_str(), 13); + } + memcpy(&m_formatStatusText[41], getDecoder().getDStarDecoder().getInfoText(), 20); + memcpy(&m_formatStatusText[62], getDecoder().getDStarDecoder().getLocator(), 6); + snprintf(&m_formatStatusText[69], 82-69, "%03d/%07.1f", + getDecoder().getDStarDecoder().getBearing(), + getDecoder().getDStarDecoder().getDistance()); + } + + m_formatStatusText[82] = '\0'; + m_signalFormat = signalFormatDStar; + break; + case DSDcc::DSDDecoder::DSDSyncDPMR: + snprintf(m_formatStatusText, 82, "%s CC: %04d OI: %08d CI: %08d", + DSDcc::DSDdPMR::dpmrFrameTypes[(int) getDecoder().getDPMRDecoder().getFrameType()], + getDecoder().getDPMRDecoder().getColorCode(), + getDecoder().getDPMRDecoder().getOwnId(), + getDecoder().getDPMRDecoder().getCalledId()); + m_signalFormat = signalFormatDPMR; + break; + case DSDcc::DSDDecoder::DSDSyncNXDNP: + case DSDcc::DSDDecoder::DSDSyncNXDNN: + if (getDecoder().getNXDNDecoder().getRFChannel() == DSDcc::DSDNXDN::NXDNRCCH) + { + // 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 + // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0.. + // RC r cc mm llllll ssss + snprintf(m_formatStatusText, 82, "RC %s %02d %02X %06X %02X", + getDecoder().getNXDNDecoder().isFullRate() ? "F" : "H", + getDecoder().getNXDNDecoder().getRAN(), + getDecoder().getNXDNDecoder().getMessageType(), + getDecoder().getNXDNDecoder().getLocationId(), + getDecoder().getNXDNDecoder().getServicesFlag()); + } + else if ((getDecoder().getNXDNDecoder().getRFChannel() == DSDcc::DSDNXDN::NXDNRTCH) + || (getDecoder().getNXDNDecoder().getRFChannel() == DSDcc::DSDNXDN::NXDNRDCH)) + { + if (getDecoder().getNXDNDecoder().isIdle()) { + snprintf(m_formatStatusText, 82, "%s IDLE", getDecoder().getNXDNDecoder().getRFChannelStr()); + } + else + { + // 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 + // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0.. + // Rx r cc mm sssss>gddddd + snprintf(m_formatStatusText, 82, "%s %s %02d %02X %05d>%c%05d", + getDecoder().getNXDNDecoder().getRFChannelStr(), + getDecoder().getNXDNDecoder().isFullRate() ? "F" : "H", + getDecoder().getNXDNDecoder().getRAN(), + getDecoder().getNXDNDecoder().getMessageType(), + getDecoder().getNXDNDecoder().getSourceId(), + getDecoder().getNXDNDecoder().isGroupCall() ? 'G' : 'I', + getDecoder().getNXDNDecoder().getDestinationId()); + } + } + else + { + // 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 + // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0.. + // RU + snprintf(m_formatStatusText, 82, "RU"); + } + m_signalFormat = signalFormatNXDN; + break; + case DSDcc::DSDDecoder::DSDSyncYSF: + // 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 + // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0.. + // C V2 RI 0:7 WL000|ssssssssss>dddddddddd |UUUUUUUUUU>DDDDDDDDDD|44444 + if (getDecoder().getYSFDecoder().getFICHError() == DSDcc::DSDYSF::FICHNoError) + { + snprintf(m_formatStatusText, 82, "%s ", DSDcc::DSDYSF::ysfChannelTypeText[(int) getDecoder().getYSFDecoder().getFICH().getFrameInformation()]); + } + else + { + snprintf(m_formatStatusText, 82, "%d ", (int) getDecoder().getYSFDecoder().getFICHError()); + } + + snprintf(&m_formatStatusText[2], 80, "%s %s %d:%d %c%c", + DSDcc::DSDYSF::ysfDataTypeText[(int) getDecoder().getYSFDecoder().getFICH().getDataType()], + DSDcc::DSDYSF::ysfCallModeText[(int) getDecoder().getYSFDecoder().getFICH().getCallMode()], + getDecoder().getYSFDecoder().getFICH().getBlockTotal(), + getDecoder().getYSFDecoder().getFICH().getFrameTotal(), + (getDecoder().getYSFDecoder().getFICH().isNarrowMode() ? 'N' : 'W'), + (getDecoder().getYSFDecoder().getFICH().isInternetPath() ? 'I' : 'L')); + + if (getDecoder().getYSFDecoder().getFICH().isSquelchCodeEnabled()) + { + snprintf(&m_formatStatusText[14], 82-14, "%03d", getDecoder().getYSFDecoder().getFICH().getSquelchCode()); + } + else + { + strncpy(&m_formatStatusText[14], "---", 82-14); + } + + char dest[13]; + + if (getDecoder().getYSFDecoder().radioIdMode()) + { + snprintf(dest, 12, "%-5s:%-5s", + getDecoder().getYSFDecoder().getDestId(), + getDecoder().getYSFDecoder().getSrcId()); + } + else + { + snprintf(dest, 11, "%-10s", getDecoder().getYSFDecoder().getDest()); + } + + snprintf(&m_formatStatusText[17], 82-17, "|%-10s>%s|%-10s>%-10s|%-5s", + getDecoder().getYSFDecoder().getSrc(), + dest, + getDecoder().getYSFDecoder().getUplink(), + getDecoder().getYSFDecoder().getDownlink(), + getDecoder().getYSFDecoder().getRem4()); + + m_signalFormat = signalFormatYSF; + break; + default: + m_signalFormat = signalFormatNone; + m_formatStatusText[0] = '\0'; + break; + } + + m_formatStatusText[82] = '\0'; // guard +} + +int DSDDemod::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setDsdDemodSettings(new SWGSDRangel::SWGDSDDemodSettings()); + response.getDsdDemodSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int DSDDemod::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + DSDDemodSettings settings = m_settings; + bool frequencyOffsetChanged = false; + + if (channelSettingsKeys.contains("inputFrequencyOffset")) + { + settings.m_inputFrequencyOffset = response.getDsdDemodSettings()->getInputFrequencyOffset(); + frequencyOffsetChanged = true; + } + if (channelSettingsKeys.contains("rfBandwidth")) { + settings.m_rfBandwidth = response.getDsdDemodSettings()->getRfBandwidth(); + } + if (channelSettingsKeys.contains("fmDeviation")) { + settings.m_fmDeviation = response.getDsdDemodSettings()->getFmDeviation(); + } + if (channelSettingsKeys.contains("demodGain")) { + settings.m_demodGain = response.getDsdDemodSettings()->getDemodGain(); + } + if (channelSettingsKeys.contains("volume")) { + settings.m_volume = response.getDsdDemodSettings()->getVolume(); + } + if (channelSettingsKeys.contains("baudRate")) { + settings.m_baudRate = response.getDsdDemodSettings()->getBaudRate(); + } + if (channelSettingsKeys.contains("squelchGate")) { + settings.m_squelchGate = response.getDsdDemodSettings()->getSquelchGate(); + } + if (channelSettingsKeys.contains("squelch")) { + settings.m_squelch = response.getDsdDemodSettings()->getSquelch(); + } + if (channelSettingsKeys.contains("audioMute")) { + settings.m_audioMute = response.getDsdDemodSettings()->getAudioMute() != 0; + } + if (channelSettingsKeys.contains("enableCosineFiltering")) { + settings.m_enableCosineFiltering = response.getDsdDemodSettings()->getEnableCosineFiltering() != 0; + } + if (channelSettingsKeys.contains("syncOrConstellation")) { + settings.m_syncOrConstellation = response.getDsdDemodSettings()->getSyncOrConstellation() != 0; + } + if (channelSettingsKeys.contains("slot1On")) { + settings.m_slot1On = response.getDsdDemodSettings()->getSlot1On() != 0; + } + if (channelSettingsKeys.contains("slot2On")) { + settings.m_slot2On = response.getDsdDemodSettings()->getSlot2On() != 0; + } + if (channelSettingsKeys.contains("tdmaStereo")) { + settings.m_tdmaStereo = response.getDsdDemodSettings()->getTdmaStereo() != 0; + } + if (channelSettingsKeys.contains("pllLock")) { + settings.m_pllLock = response.getDsdDemodSettings()->getPllLock() != 0; + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getDsdDemodSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getDsdDemodSettings()->getTitle(); + } + if (channelSettingsKeys.contains("audioDeviceName")) { + settings.m_audioDeviceName = *response.getDsdDemodSettings()->getAudioDeviceName(); + } + if (channelSettingsKeys.contains("highPassFilter")) { + settings.m_highPassFilter = response.getDsdDemodSettings()->getHighPassFilter() != 0; + } + if (channelSettingsKeys.contains("traceLengthMutliplier")) { + settings.m_traceLengthMutliplier = response.getDsdDemodSettings()->getTraceLengthMutliplier(); + } + if (channelSettingsKeys.contains("traceStroke")) { + settings.m_traceStroke = response.getDsdDemodSettings()->getTraceStroke(); + } + if (channelSettingsKeys.contains("traceDecay")) { + settings.m_traceDecay = response.getDsdDemodSettings()->getTraceDecay(); + } + + if (frequencyOffsetChanged) + { + MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( + m_audioSampleRate, settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(channelConfigMsg); + } + + MsgConfigureDSDDemod *msg = MsgConfigureDSDDemod::create(settings, force); + m_inputMessageQueue.push(msg); + + qDebug("DSDDemod::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureDSDDemod *msgToGUI = MsgConfigureDSDDemod::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int DSDDemod::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setDsdDemodReport(new SWGSDRangel::SWGDSDDemodReport()); + response.getDsdDemodReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + +void DSDDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const DSDDemodSettings& settings) +{ + response.getDsdDemodSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getDsdDemodSettings()->setRfBandwidth(settings.m_rfBandwidth); + response.getDsdDemodSettings()->setFmDeviation(settings.m_fmDeviation); + response.getDsdDemodSettings()->setDemodGain(settings.m_demodGain); + response.getDsdDemodSettings()->setVolume(settings.m_volume); + response.getDsdDemodSettings()->setBaudRate(settings.m_baudRate); + response.getDsdDemodSettings()->setSquelchGate(settings.m_squelchGate); + response.getDsdDemodSettings()->setSquelch(settings.m_squelch); + response.getDsdDemodSettings()->setAudioMute(settings.m_audioMute ? 1 : 0); + response.getDsdDemodSettings()->setEnableCosineFiltering(settings.m_enableCosineFiltering ? 1 : 0); + response.getDsdDemodSettings()->setSyncOrConstellation(settings.m_syncOrConstellation ? 1 : 0); + response.getDsdDemodSettings()->setSlot1On(settings.m_slot1On ? 1 : 0); + response.getDsdDemodSettings()->setSlot2On(settings.m_slot2On ? 1 : 0); + response.getDsdDemodSettings()->setTdmaStereo(settings.m_tdmaStereo ? 1 : 0); + response.getDsdDemodSettings()->setPllLock(settings.m_pllLock ? 1 : 0); + response.getDsdDemodSettings()->setRgbColor(settings.m_rgbColor); + + if (response.getDsdDemodSettings()->getTitle()) { + *response.getDsdDemodSettings()->getTitle() = settings.m_title; + } else { + response.getDsdDemodSettings()->setTitle(new QString(settings.m_title)); + } + + if (response.getDsdDemodSettings()->getAudioDeviceName()) { + *response.getDsdDemodSettings()->getAudioDeviceName() = settings.m_audioDeviceName; + } else { + response.getDsdDemodSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName)); + } + + response.getDsdDemodSettings()->setHighPassFilter(settings.m_highPassFilter ? 1 : 0); + response.getDsdDemodSettings()->setTraceLengthMutliplier(settings.m_traceLengthMutliplier); + response.getDsdDemodSettings()->setTraceStroke(settings.m_traceStroke); + response.getDsdDemodSettings()->setTraceDecay(settings.m_traceDecay); +} + +void DSDDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + double magsqAvg, magsqPeak; + int nbMagsqSamples; + getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples); + + response.getDsdDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg)); + response.getDsdDemodReport()->setAudioSampleRate(m_audioSampleRate); + response.getDsdDemodReport()->setChannelSampleRate(m_inputSampleRate); + response.getDsdDemodReport()->setSquelch(m_squelchOpen ? 1 : 0); + response.getDsdDemodReport()->setPllLocked(getDecoder().getSymbolPLLLocked() ? 1 : 0); + response.getDsdDemodReport()->setSlot1On(getDecoder().getVoice1On() ? 1 : 0); + response.getDsdDemodReport()->setSlot2On(getDecoder().getVoice2On() ? 1 : 0); + response.getDsdDemodReport()->setSyncType(new QString(getDecoder().getFrameTypeText())); + response.getDsdDemodReport()->setInLevel(getDecoder().getInLevel()); + response.getDsdDemodReport()->setCarierPosition(getDecoder().getCarrierPos()); + response.getDsdDemodReport()->setZeroCrossingPosition(getDecoder().getZeroCrossingPos()); + response.getDsdDemodReport()->setSyncRate(getDecoder().getSymbolSyncQuality()); + response.getDsdDemodReport()->setStatusText(new QString(updateAndGetStatusText())); +} diff --git a/plugins/channelrx/demoddsd/dsddemod.h b/plugins/channelrx/demoddsd/dsddemod.h index 2554f4f2a..207a05f60 100644 --- a/plugins/channelrx/demoddsd/dsddemod.h +++ b/plugins/channelrx/demoddsd/dsddemod.h @@ -1,210 +1,260 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2016 F4EXB // -// written by Edouard Griffiths // -// // -// This program is free software; you can redistribute it and/or modify // -// it under the terms of the GNU General Public License as published by // -// the Free Software Foundation as version 3 of the License, or // -// // -// This program is distributed in the hope that it will be useful, // -// but WITHOUT ANY WARRANTY; without even the implied warranty of // -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // -// GNU General Public License V3 for more details. // -// // -// You should have received a copy of the GNU General Public License // -// along with this program. If not, see . // -/////////////////////////////////////////////////////////////////////////////////// - -#ifndef INCLUDE_DSDDEMOD_H -#define INCLUDE_DSDDEMOD_H - -#include -#include - -#include "dsp/basebandsamplesink.h" -#include "channel/channelsinkapi.h" -#include "dsp/phasediscri.h" -#include "dsp/nco.h" -#include "dsp/interpolator.h" -#include "dsp/lowpass.h" -#include "dsp/bandpass.h" -#include "dsp/afsquelch.h" -#include "util/movingaverage.h" -#include "dsp/afsquelch.h" -#include "audio/audiofifo.h" -#include "util/message.h" -#include "util/udpsink.h" - -#include "dsddemodsettings.h" -#include "dsddecoder.h" - -class DeviceSourceAPI; -class ThreadedBasebandSampleSink; -class DownChannelizer; - -class DSDDemod : public BasebandSampleSink, public ChannelSinkAPI { -public: - class MsgConfigureDSDDemod : public Message { - MESSAGE_CLASS_DECLARATION - - public: - const DSDDemodSettings& getSettings() const { return m_settings; } - bool getForce() const { return m_force; } - - static MsgConfigureDSDDemod* create(const DSDDemodSettings& settings, bool force) - { - return new MsgConfigureDSDDemod(settings, force); - } - - private: - DSDDemodSettings m_settings; - bool m_force; - - MsgConfigureDSDDemod(const DSDDemodSettings& settings, bool force) : - Message(), - m_settings(settings), - m_force(force) - { } - }; - - class MsgConfigureChannelizer : public Message { - MESSAGE_CLASS_DECLARATION - - public: - int getSampleRate() const { return m_sampleRate; } - int getCenterFrequency() const { return m_centerFrequency; } - - static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency) - { - return new MsgConfigureChannelizer(sampleRate, centerFrequency); - } - - private: - int m_sampleRate; - int m_centerFrequency; - - MsgConfigureChannelizer(int sampleRate, int centerFrequency) : - Message(), - m_sampleRate(sampleRate), - m_centerFrequency(centerFrequency) - { } - }; - - DSDDemod(DeviceSourceAPI *deviceAPI); - ~DSDDemod(); - virtual void destroy() { delete this; } - void setScopeSink(BasebandSampleSink* sampleSink) { m_scope = sampleSink; } - - void configureMyPosition(MessageQueue* messageQueue, float myLatitude, float myLongitude); - - virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po); - virtual void start(); - virtual void stop(); - virtual bool handleMessage(const Message& cmd); - - virtual void getIdentifier(QString& id) { id = objectName(); } - virtual void getTitle(QString& title) { title = m_settings.m_title; } - virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; } - - virtual QByteArray serialize() const; - virtual bool deserialize(const QByteArray& data); - - double getMagSq() { return m_magsq; } - bool getSquelchOpen() const { return m_squelchOpen; } - - const DSDDecoder& getDecoder() const { return m_dsdDecoder; } - - void getMagSqLevels(double& avg, double& peak, int& nbSamples) - { - avg = m_magsqCount == 0 ? 1e-10 : m_magsqSum / m_magsqCount; - m_magsq = avg; - peak = m_magsqPeak == 0.0 ? 1e-10 : m_magsqPeak; - nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; - m_magsqSum = 0.0f; - m_magsqPeak = 0.0f; - m_magsqCount = 0; - } - - static const QString m_channelIdURI; - static const QString m_channelId; - -private: - class MsgConfigureMyPosition : public Message { - MESSAGE_CLASS_DECLARATION - - public: - float getMyLatitude() const { return m_myLatitude; } - float getMyLongitude() const { return m_myLongitude; } - - static MsgConfigureMyPosition* create(float myLatitude, float myLongitude) - { - return new MsgConfigureMyPosition(myLatitude, myLongitude); - } - - private: - float m_myLatitude; - float m_myLongitude; - - MsgConfigureMyPosition(float myLatitude, float myLongitude) : - m_myLatitude(myLatitude), - m_myLongitude(myLongitude) - {} - }; - - enum RateState { - RSInitialFill, - RSRunning - }; - - DeviceSourceAPI *m_deviceAPI; - ThreadedBasebandSampleSink* m_threadedChannelizer; - DownChannelizer* m_channelizer; - - int m_inputSampleRate; - int m_inputFrequencyOffset; - DSDDemodSettings m_settings; - - NCO m_nco; - Interpolator m_interpolator; - Real m_interpolatorDistance; - Real m_interpolatorDistanceRemain; - int m_sampleCount; - int m_squelchCount; - int m_squelchGate; - - double m_squelchLevel; - bool m_squelchOpen; - - MovingAverageUtil m_movingAverage; - double m_magsq; - double m_magsqSum; - double m_magsqPeak; - int m_magsqCount; - - Real m_fmExcursion; - - SampleVector m_scopeSampleBuffer; - AudioVector m_audioBuffer; - uint m_audioBufferFill; - FixReal *m_sampleBuffer; //!< samples ring buffer - int m_sampleBufferIndex; - int m_scaleFromShort; - - AudioFifo m_audioFifo1; - AudioFifo m_audioFifo2; - BasebandSampleSink* m_scope; - bool m_scopeEnabled; - - DSDDecoder m_dsdDecoder; - QMutex m_settingsMutex; - - PhaseDiscriminators m_phaseDiscri; - UDPSink *m_udpBufferAudio; - - static const int m_udpBlockSize; - - void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); - void applySettings(const DSDDemodSettings& settings, bool force = false); -}; - -#endif // INCLUDE_DSDDEMOD_H +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 F4EXB // +// written by Edouard Griffiths // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_DSDDEMOD_H +#define INCLUDE_DSDDEMOD_H + +#include +#include + +#include "dsp/basebandsamplesink.h" +#include "channel/channelsinkapi.h" +#include "dsp/phasediscri.h" +#include "dsp/nco.h" +#include "dsp/interpolator.h" +#include "dsp/lowpass.h" +#include "dsp/bandpass.h" +#include "dsp/afsquelch.h" +#include "util/movingaverage.h" +#include "dsp/afsquelch.h" +#include "audio/audiofifo.h" +#include "util/message.h" +#include "util/doublebufferfifo.h" + +#include "dsddemodsettings.h" +#include "dsddecoder.h" + +class DeviceSourceAPI; +class ThreadedBasebandSampleSink; +class DownChannelizer; + +class DSDDemod : public BasebandSampleSink, public ChannelSinkAPI { +public: + class MsgConfigureDSDDemod : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const DSDDemodSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureDSDDemod* create(const DSDDemodSettings& settings, bool force) + { + return new MsgConfigureDSDDemod(settings, force); + } + + private: + DSDDemodSettings m_settings; + bool m_force; + + MsgConfigureDSDDemod(const DSDDemodSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgConfigureChannelizer : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getSampleRate() const { return m_sampleRate; } + int getCenterFrequency() const { return m_centerFrequency; } + + static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency) + { + return new MsgConfigureChannelizer(sampleRate, centerFrequency); + } + + private: + int m_sampleRate; + int m_centerFrequency; + + MsgConfigureChannelizer(int sampleRate, int centerFrequency) : + Message(), + m_sampleRate(sampleRate), + m_centerFrequency(centerFrequency) + { } + }; + + DSDDemod(DeviceSourceAPI *deviceAPI); + ~DSDDemod(); + virtual void destroy() { delete this; } + void setScopeXYSink(BasebandSampleSink* sampleSink) { m_scopeXY = sampleSink; } + + void configureMyPosition(MessageQueue* messageQueue, float myLatitude, float myLongitude); + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po); + virtual void start(); + virtual void stop(); + virtual bool handleMessage(const Message& cmd); + + virtual void getIdentifier(QString& id) { id = objectName(); } + virtual void getTitle(QString& title) { title = m_settings.m_title; } + virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; } + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + double getMagSq() { return m_magsq; } + bool getSquelchOpen() const { return m_squelchOpen; } + + const DSDDecoder& getDecoder() const { return m_dsdDecoder; } + + void getMagSqLevels(double& avg, double& peak, int& nbSamples) + { + if (m_magsqCount > 0) + { + m_magsq = m_magsqSum / m_magsqCount; + m_magSqLevelStore.m_magsq = m_magsq; + m_magSqLevelStore.m_magsqPeak = m_magsqPeak; + } + + avg = m_magSqLevelStore.m_magsq; + peak = m_magSqLevelStore.m_magsqPeak; + nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; + + m_magsqSum = 0.0f; + m_magsqPeak = 0.0f; + m_magsqCount = 0; + } + + const char *updateAndGetStatusText(); + + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + + static const QString m_channelIdURI; + static const QString m_channelId; + +private: + struct MagSqLevelsStore + { + MagSqLevelsStore() : + m_magsq(1e-12), + m_magsqPeak(1e-12) + {} + double m_magsq; + double m_magsqPeak; + }; + + typedef enum + { + signalFormatNone, + signalFormatDMR, + signalFormatDStar, + signalFormatDPMR, + signalFormatYSF, + signalFormatNXDN + } SignalFormat; //!< Used for status text formatting + + class MsgConfigureMyPosition : public Message { + MESSAGE_CLASS_DECLARATION + + public: + float getMyLatitude() const { return m_myLatitude; } + float getMyLongitude() const { return m_myLongitude; } + + static MsgConfigureMyPosition* create(float myLatitude, float myLongitude) + { + return new MsgConfigureMyPosition(myLatitude, myLongitude); + } + + private: + float m_myLatitude; + float m_myLongitude; + + MsgConfigureMyPosition(float myLatitude, float myLongitude) : + m_myLatitude(myLatitude), + m_myLongitude(myLongitude) + {} + }; + + enum RateState { + RSInitialFill, + RSRunning + }; + + DeviceSourceAPI *m_deviceAPI; + ThreadedBasebandSampleSink* m_threadedChannelizer; + DownChannelizer* m_channelizer; + + int m_inputSampleRate; + int m_inputFrequencyOffset; + DSDDemodSettings m_settings; + quint32 m_audioSampleRate; + + NCO m_nco; + Interpolator m_interpolator; + Real m_interpolatorDistance; + Real m_interpolatorDistanceRemain; + int m_sampleCount; + int m_squelchCount; + int m_squelchGate; + double m_squelchLevel; + bool m_squelchOpen; + DoubleBufferFIFO m_squelchDelayLine; + + MovingAverageUtil m_movingAverage; + double m_magsq; + double m_magsqSum; + double m_magsqPeak; + int m_magsqCount; + MagSqLevelsStore m_magSqLevelStore; + + SampleVector m_scopeSampleBuffer; + AudioVector m_audioBuffer; + uint m_audioBufferFill; + FixReal *m_sampleBuffer; //!< samples ring buffer + int m_sampleBufferIndex; + int m_scaleFromShort; + + AudioFifo m_audioFifo1; + AudioFifo m_audioFifo2; + BasebandSampleSink* m_scopeXY; + bool m_scopeEnabled; + + DSDDecoder m_dsdDecoder; + + char m_formatStatusText[82+1]; //!< Fixed signal format dependent status text + SignalFormat m_signalFormat; //!< Used to keep formatting during successive calls for the same standard type + PhaseDiscriminators m_phaseDiscri; + + QMutex m_settingsMutex; + + static const int m_udpBlockSize; + + void applyAudioSampleRate(int sampleRate); + void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); + void applySettings(const DSDDemodSettings& settings, bool force = false); + void formatStatusText(); + + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const DSDDemodSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); +}; + +#endif // INCLUDE_DSDDEMOD_H diff --git a/plugins/channelrx/demoddsd/dsddemodgui.cpp b/plugins/channelrx/demoddsd/dsddemodgui.cpp index 7471d988f..514a3e333 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.cpp +++ b/plugins/channelrx/demoddsd/dsddemodgui.cpp @@ -20,24 +20,28 @@ #include #include "device/deviceuiset.h" #include -#include -#include -#include #include "dsp/threadedbasebandsamplesink.h" #include "ui_dsddemodgui.h" -#include "dsp/scopevis.h" -#include "gui/glscope.h" +#include "dsp/scopevisxy.h" #include "plugin/pluginapi.h" #include "util/simpleserializer.h" #include "util/db.h" #include "gui/basicchannelsettingsdialog.h" +#include "gui/crightclickenabler.h" +#include "gui/audioselectdialog.h" #include "dsp/dspengine.h" #include "mainwindow.h" #include "dsddemodbaudrates.h" #include "dsddemod.h" +#include +#include +#include + +#include + DSDDemodGUI* DSDDemodGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) { DSDDemodGUI* gui = new DSDDemodGUI(pluginAPI, deviceUISet, rxChannel); @@ -99,9 +103,35 @@ bool DSDDemodGUI::deserialize(const QByteArray& data) } } -bool DSDDemodGUI::handleMessage(const Message& message __attribute__((unused))) +bool DSDDemodGUI::handleMessage(const Message& message) { - return false; + if (DSDDemod::MsgConfigureDSDDemod::match(message)) + { + qDebug("DSDDemodGUI::handleMessage: DSDDemod::MsgConfigureDSDDemod"); + const DSDDemod::MsgConfigureDSDDemod& cfg = (DSDDemod::MsgConfigureDSDDemod&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else + { + return false; + } +} + +void DSDDemodGUI::handleInputMessages() +{ + Message* message; + + while ((message = getInputMessageQueue()->pop()) != 0) + { + if (handleMessage(*message)) + { + delete message; + } + } } void DSDDemodGUI::on_deltaFrequency_changed(qint64 value) @@ -129,7 +159,7 @@ void DSDDemodGUI::on_demodGain_valueChanged(int value) void DSDDemodGUI::on_fmDeviation_valueChanged(int value) { m_settings.m_fmDeviation = value * 100.0; - ui->fmDeviationText->setText(QString("%1k").arg(value / 10.0, 0, 'f', 1)); + ui->fmDeviationText->setText(QString("%1%2k").arg(QChar(0xB1, 0x00)).arg(value / 10.0, 0, 'f', 1)); applySettings(); } @@ -158,6 +188,27 @@ void DSDDemodGUI::on_syncOrConstellation_toggled(bool checked) applySettings(); } +void DSDDemodGUI::on_traceLength_valueChanged(int value) +{ + m_settings.m_traceLengthMutliplier = value; + ui->traceLengthText->setText(QString("%1").arg(m_settings.m_traceLengthMutliplier*50)); + m_scopeVisXY->setPixelsPerFrame(m_settings.m_traceLengthMutliplier*960); // 48000 / 50. Chunks of 50 ms. +} + +void DSDDemodGUI::on_traceStroke_valueChanged(int value) +{ + m_settings.m_traceStroke = value; + ui->traceStrokeText->setText(QString("%1").arg(m_settings.m_traceStroke)); + m_scopeVisXY->setStroke(m_settings.m_traceStroke); +} + +void DSDDemodGUI::on_traceDecay_valueChanged(int value) +{ + m_settings.m_traceDecay = value; + ui->traceDecayText->setText(QString("%1").arg(m_settings.m_traceDecay)); + m_scopeVisXY->setDecay(m_settings.m_traceDecay); +} + void DSDDemodGUI::on_slot1On_toggled(bool checked) { m_settings.m_slot1On = checked; @@ -185,8 +236,8 @@ void DSDDemodGUI::on_squelchGate_valueChanged(int value) void DSDDemodGUI::on_squelch_valueChanged(int value) { - ui->squelchText->setText(QString("%1").arg(value / 10.0, 0, 'f', 1)); - m_settings.m_squelch = value / 10.0; + ui->squelchText->setText(QString("%1").arg(value / 1.0, 0, 'f', 0)); + m_settings.m_squelch = value; applySettings(); } @@ -213,18 +264,8 @@ void DSDDemodGUI::on_symbolPLLLock_toggled(bool checked) applySettings(); } -void DSDDemodGUI::on_udpOutput_toggled(bool checked) -{ - m_settings.m_copyAudioToUDP = checked; - applySettings(); -} - void DSDDemodGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rollDown __attribute__((unused))) { - /* - if((widget == ui->spectrumContainer) && (DSDDemodGUI != NULL)) - m_dsdDemod->setSpectrum(m_threadedSampleSink->getMessageQueue(), rollDown); - */ } void DSDDemodGUI::onMenuDialogCalled(const QPoint &p) @@ -235,18 +276,21 @@ void DSDDemodGUI::onMenuDialogCalled(const QPoint &p) dialog.exec(); m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); - m_settings.m_udpAddress = m_channelMarker.getUDPAddress(), - m_settings.m_udpPort = m_channelMarker.getUDPSendPort(), m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); m_settings.m_title = m_channelMarker.getTitle(); setWindowTitle(m_settings.m_title); setTitleColor(m_settings.m_rgbColor); - displayUDPAddress(); applySettings(); } +void DSDDemodGUI::on_viewStatusLog_clicked() +{ + qDebug("DSDDemodGUI::on_viewStatusLog_clicked"); + m_dsdStatusTextDialog.exec(); +} + DSDDemodGUI::DSDDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) : RollupWidget(parent), ui(new Ui::DSDDemodGUI), @@ -254,31 +298,47 @@ DSDDemodGUI::DSDDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban m_deviceUISet(deviceUISet), m_channelMarker(this), m_doApplySettings(true), - m_signalFormat(signalFormatNone), m_enableCosineFiltering(false), m_syncOrConstellation(false), m_slot1On(false), m_slot2On(false), m_tdmaStereo(false), m_squelchOpen(false), - m_tickCount(0) + m_tickCount(0), + m_dsdStatusTextDialog(0) { ui->setupUi(this); + ui->screenTV->setColor(true); + ui->screenTV->resizeTVScreen(200,200); setAttribute(Qt::WA_DeleteOnClose, true); connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); + connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + + CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->audioMute); + connect(audioMuteRightClickEnabler, SIGNAL(rightClick()), this, SLOT(audioSelect())); + + m_scopeVisXY = new ScopeVisXY(ui->screenTV); + m_scopeVisXY->setScale(2.0); + m_scopeVisXY->setPixelsPerFrame(4001); + m_scopeVisXY->setPlotRGB(qRgb(0, 220, 250)); + m_scopeVisXY->setGridRGB(qRgb(255, 255, 128)); + + for (float x = -0.84; x < 1.0; x += 0.56) + { + for (float y = -0.84; y < 1.0; y += 0.56) + { + m_scopeVisXY->addGraticulePoint(std::complex(x, y)); + } + } + + m_scopeVisXY->calculateGraticule(200,200); - m_scopeVis = new ScopeVis(SDR_RX_SCALEF, ui->glScope); m_dsdDemod = (DSDDemod*) rxChannel; //new DSDDemod(m_deviceUISet->m_deviceSourceAPI); - m_dsdDemod->setScopeSink(m_scopeVis); + m_dsdDemod->setScopeXYSink(m_scopeVisXY); m_dsdDemod->setMessageQueueToGUI(getInputMessageQueue()); - ui->glScope->setSampleRate(48000); - m_scopeVis->setSampleRate(48000); - - ui->glScope->connectTimer(MainWindow::getInstance()->getMasterTimer()); - connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); ui->audioMute->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); @@ -293,8 +353,6 @@ DSDDemodGUI::DSDDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban m_channelMarker.setBandwidth(10000); m_channelMarker.setCenterFrequency(0); m_channelMarker.setTitle("DSD Demodulator"); - m_channelMarker.setUDPAddress("127.0.0.1"); - m_channelMarker.setUDPSendPort(9999); m_channelMarker.blockSignals(false); m_channelMarker.setVisible(true); // activate signal on the last setting only @@ -305,10 +363,7 @@ DSDDemodGUI::DSDDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor())); connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor())); - ui->scopeGUI->setBuddies(m_scopeVis->getInputMessageQueue(), m_scopeVis, ui->glScope); - m_settings.setChannelMarker(&m_channelMarker); - m_settings.setScopeGUI(ui->scopeGUI); updateMyPosition(); displaySettings(); @@ -319,7 +374,7 @@ DSDDemodGUI::~DSDDemodGUI() { m_deviceUISet->removeRxChannelInstance(this); delete m_dsdDemod; // TODO: check this: when the GUI closes it has to delete the demodulator - delete m_scopeVis; + delete m_scopeVisXY; delete ui; } @@ -336,11 +391,6 @@ void DSDDemodGUI::updateMyPosition() } } -void DSDDemodGUI::displayUDPAddress() -{ - ui->udpOutput->setToolTip(QString("Copy audio output to UDP %1:%2").arg(m_settings.m_udpAddress).arg(m_settings.m_udpPort)); -} - void DSDDemodGUI::displaySettings() { m_channelMarker.blockSignals(true); @@ -352,7 +402,6 @@ void DSDDemodGUI::displaySettings() setTitleColor(m_settings.m_rgbColor); setWindowTitle(m_channelMarker.getTitle()); - displayUDPAddress(); blockApplySettings(true); @@ -362,10 +411,10 @@ void DSDDemodGUI::displaySettings() ui->rfBWText->setText(QString("%1k").arg(ui->rfBW->value() / 10.0, 0, 'f', 1)); ui->fmDeviation->setValue(m_settings.m_fmDeviation / 100.0); - ui->fmDeviationText->setText(QString("%1k").arg(ui->fmDeviation->value() / 10.0, 0, 'f', 1)); + ui->fmDeviationText->setText(QString("%1%2k").arg(QChar(0xB1, 0x00)).arg(ui->fmDeviation->value() / 10.0, 0, 'f', 1)); - ui->squelch->setValue(m_settings.m_squelch * 10.0); - ui->squelchText->setText(QString("%1").arg(ui->squelch->value() / 10.0, 0, 'f', 1)); + ui->squelch->setValue(m_settings.m_squelch); + ui->squelchText->setText(QString("%1").arg(ui->squelch->value() / 1.0, 0, 'f', 0)); ui->squelchGate->setValue(m_settings.m_squelchGate); ui->squelchGateText->setText(QString("%1").arg(ui->squelchGate->value() * 10.0, 0, 'f', 0)); @@ -382,11 +431,23 @@ void DSDDemodGUI::displaySettings() ui->slot2On->setChecked(m_settings.m_slot2On); ui->tdmaStereoSplit->setChecked(m_settings.m_tdmaStereo); ui->audioMute->setChecked(m_settings.m_audioMute); - ui->udpOutput->setChecked(m_settings.m_copyAudioToUDP); ui->symbolPLLLock->setChecked(m_settings.m_pllLock); + ui->highPassFilter->setChecked(m_settings.m_highPassFilter); ui->baudRate->setCurrentIndex(DSDDemodBaudRates::getRateIndex(m_settings.m_baudRate)); + ui->traceLength->setValue(m_settings.m_traceLengthMutliplier); + ui->traceLengthText->setText(QString("%1").arg(m_settings.m_traceLengthMutliplier*50)); + m_scopeVisXY->setPixelsPerFrame(m_settings.m_traceLengthMutliplier*960); // 48000 / 50. Chunks of 50 ms. + + ui->traceStroke->setValue(m_settings.m_traceStroke); + ui->traceStrokeText->setText(QString("%1").arg(m_settings.m_traceStroke)); + m_scopeVisXY->setStroke(m_settings.m_traceStroke); + + ui->traceDecay->setValue(m_settings.m_traceDecay); + ui->traceDecayText->setText(QString("%1").arg(m_settings.m_traceDecay)); + m_scopeVisXY->setDecay(m_settings.m_traceDecay); + blockApplySettings(false); } @@ -420,145 +481,6 @@ void DSDDemodGUI::blockApplySettings(bool block) m_doApplySettings = !block; } -void DSDDemodGUI::formatStatusText() -{ - switch (m_dsdDemod->getDecoder().getSyncType()) - { - case DSDcc::DSDDecoder::DSDSyncDMRDataMS: - case DSDcc::DSDDecoder::DSDSyncDMRDataP: - case DSDcc::DSDDecoder::DSDSyncDMRVoiceMS: - case DSDcc::DSDDecoder::DSDSyncDMRVoiceP: - if (m_signalFormat != signalFormatDMR) - { - strcpy(m_formatStatusText, "Sta: __ S1: __________________________ S2: __________________________"); - } - - switch (m_dsdDemod->getDecoder().getStationType()) - { - case DSDcc::DSDDecoder::DSDBaseStation: - memcpy(&m_formatStatusText[5], "BS ", 3); - break; - case DSDcc::DSDDecoder::DSDMobileStation: - memcpy(&m_formatStatusText[5], "MS ", 3); - break; - default: - memcpy(&m_formatStatusText[5], "NA ", 3); - break; - } - - memcpy(&m_formatStatusText[12], m_dsdDemod->getDecoder().getDMRDecoder().getSlot0Text(), 26); - memcpy(&m_formatStatusText[43], m_dsdDemod->getDecoder().getDMRDecoder().getSlot1Text(), 26); - m_signalFormat = signalFormatDMR; - break; - case DSDcc::DSDDecoder::DSDSyncDStarHeaderN: - case DSDcc::DSDDecoder::DSDSyncDStarHeaderP: - case DSDcc::DSDDecoder::DSDSyncDStarN: - case DSDcc::DSDDecoder::DSDSyncDStarP: - if (m_signalFormat != signalFormatDStar) - { - // 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 - // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0.. - strcpy(m_formatStatusText, "________/____>________|________>________|____________________|______:___/_____._"); - // MY UR RPT1 RPT2 Info Loc Target - } - - { - const std::string& rpt1 = m_dsdDemod->getDecoder().getDStarDecoder().getRpt1(); - const std::string& rpt2 = m_dsdDemod->getDecoder().getDStarDecoder().getRpt2(); - const std::string& mySign = m_dsdDemod->getDecoder().getDStarDecoder().getMySign(); - const std::string& yrSign = m_dsdDemod->getDecoder().getDStarDecoder().getYourSign(); - - if (rpt1.length() > 0) { // 0 or 8 - memcpy(&m_formatStatusText[23], rpt1.c_str(), 8); - } - if (rpt2.length() > 0) { // 0 or 8 - memcpy(&m_formatStatusText[32], rpt2.c_str(), 8); - } - if (yrSign.length() > 0) { // 0 or 8 - memcpy(&m_formatStatusText[14], yrSign.c_str(), 8); - } - if (mySign.length() > 0) { // 0 or 13 - memcpy(&m_formatStatusText[0], mySign.c_str(), 13); - } - memcpy(&m_formatStatusText[41], m_dsdDemod->getDecoder().getDStarDecoder().getInfoText(), 20); - memcpy(&m_formatStatusText[62], m_dsdDemod->getDecoder().getDStarDecoder().getLocator(), 6); - snprintf(&m_formatStatusText[69], 82-69, "%03d/%07.1f", - m_dsdDemod->getDecoder().getDStarDecoder().getBearing(), - m_dsdDemod->getDecoder().getDStarDecoder().getDistance()); - } - - m_formatStatusText[82] = '\0'; - m_signalFormat = signalFormatDStar; - break; - case DSDcc::DSDDecoder::DSDSyncDPMR: - snprintf(m_formatStatusText, 82, "%s CC: %04d OI: %08d CI: %08d", - DSDcc::DSDdPMR::dpmrFrameTypes[(int) m_dsdDemod->getDecoder().getDPMRDecoder().getFrameType()], - m_dsdDemod->getDecoder().getDPMRDecoder().getColorCode(), - m_dsdDemod->getDecoder().getDPMRDecoder().getOwnId(), - m_dsdDemod->getDecoder().getDPMRDecoder().getCalledId()); - m_signalFormat = signalFormatDPMR; - break; - case DSDcc::DSDDecoder::DSDSyncYSF: - // 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 - // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0.. - // C V2 RI 0:7 WL000|ssssssssss>dddddddddd |UUUUUUUUUU>DDDDDDDDDD|44444 - if (m_dsdDemod->getDecoder().getYSFDecoder().getFICHError() == DSDcc::DSDYSF::FICHNoError) - { - snprintf(m_formatStatusText, 82, "%s ", DSDcc::DSDYSF::ysfChannelTypeText[(int) m_dsdDemod->getDecoder().getYSFDecoder().getFICH().getFrameInformation()]); - } - else - { - snprintf(m_formatStatusText, 82, "%d ", (int) m_dsdDemod->getDecoder().getYSFDecoder().getFICHError()); - } - - snprintf(&m_formatStatusText[2], 80, "%s %s %d:%d %c%c", - DSDcc::DSDYSF::ysfDataTypeText[(int) m_dsdDemod->getDecoder().getYSFDecoder().getFICH().getDataType()], - DSDcc::DSDYSF::ysfCallModeText[(int) m_dsdDemod->getDecoder().getYSFDecoder().getFICH().getCallMode()], - m_dsdDemod->getDecoder().getYSFDecoder().getFICH().getBlockTotal(), - m_dsdDemod->getDecoder().getYSFDecoder().getFICH().getFrameTotal(), - (m_dsdDemod->getDecoder().getYSFDecoder().getFICH().isNarrowMode() ? 'N' : 'W'), - (m_dsdDemod->getDecoder().getYSFDecoder().getFICH().isInternetPath() ? 'I' : 'L')); - - if (m_dsdDemod->getDecoder().getYSFDecoder().getFICH().isSquelchCodeEnabled()) - { - snprintf(&m_formatStatusText[14], 82-14, "%03d", m_dsdDemod->getDecoder().getYSFDecoder().getFICH().getSquelchCode()); - } - else - { - strncpy(&m_formatStatusText[14], "---", 82-14); - } - - char dest[13]; - - if ( m_dsdDemod->getDecoder().getYSFDecoder().radioIdMode()) - { - snprintf(dest, 12, "%-5s:%-5s", - m_dsdDemod->getDecoder().getYSFDecoder().getDestId(), - m_dsdDemod->getDecoder().getYSFDecoder().getSrcId()); - } - else - { - snprintf(dest, 11, "%-10s", m_dsdDemod->getDecoder().getYSFDecoder().getDest()); - } - - snprintf(&m_formatStatusText[17], 82-17, "|%-10s>%s|%-10s>%-10s|%-5s", - m_dsdDemod->getDecoder().getYSFDecoder().getSrc(), - dest, - m_dsdDemod->getDecoder().getYSFDecoder().getUplink(), - m_dsdDemod->getDecoder().getYSFDecoder().getDownlink(), - m_dsdDemod->getDecoder().getYSFDecoder().getRem4()); - - m_signalFormat = signalFormatYSF; - break; - default: - m_signalFormat = signalFormatNone; - m_formatStatusText[0] = '\0'; - break; - } - - m_formatStatusText[82] = '\0'; // guard -} - void DSDDemodGUI::channelMarkerChangedByCursor() { ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); @@ -571,6 +493,19 @@ void DSDDemodGUI::channelMarkerHighlightedByCursor() setHighlighted(m_channelMarker.getHighlighted()); } +void DSDDemodGUI::audioSelect() +{ + qDebug("DSDDemodGUI::audioSelect"); + AudioSelectDialog audioSelect(DSPEngine::instance()->getAudioDeviceManager(), m_settings.m_audioDeviceName); + audioSelect.exec(); + + if (audioSelect.m_selected) + { + m_settings.m_audioDeviceName = audioSelect.m_audioDeviceName; + applySettings(); + } +} + void DSDDemodGUI::tick() { double magsqAvg, magsqPeak; @@ -632,10 +567,14 @@ void DSDDemodGUI::tick() ui->syncText->setText(QString(frameTypeText)); - formatStatusText(); - ui->formatStatusText->setText(QString(m_formatStatusText)); + const char *formatStatusText = m_dsdDemod->updateAndGetStatusText(); + ui->formatStatusText->setText(QString(formatStatusText)); - if (m_formatStatusText[0] == '\0') { + if (ui->activateStatusLog->isChecked()) { + m_dsdStatusTextDialog.addLine(QString(formatStatusText)); + } + + if (formatStatusText[0] == '\0') { ui->formatStatusText->setStyleSheet("QLabel { background:rgb(53,53,53); }"); // turn off background } else { ui->formatStatusText->setStyleSheet("QLabel { background:rgb(37,53,39); }"); // turn on background diff --git a/plugins/channelrx/demoddsd/dsddemodgui.h b/plugins/channelrx/demoddsd/dsddemodgui.h index 40fa8e18d..a5e1bdb37 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.h +++ b/plugins/channelrx/demoddsd/dsddemodgui.h @@ -28,11 +28,13 @@ #include "util/messagequeue.h" #include "dsddemodsettings.h" +#include "dsdstatustextdialog.h" class PluginAPI; class DeviceUISet; class BasebandSampleSink; class ScopeVis; +class ScopeVisXY; class DSDDemod; namespace Ui { @@ -62,14 +64,14 @@ public slots: void channelMarkerHighlightedByCursor(); private: - typedef enum - { - signalFormatNone, - signalFormatDMR, - signalFormatDStar, - signalFormatDPMR, - signalFormatYSF - } SignalFormat; +// typedef enum +// { +// signalFormatNone, +// signalFormatDMR, +// signalFormatDStar, +// signalFormatDPMR, +// signalFormatYSF +// } SignalFormat; Ui::DSDDemodGUI* ui; PluginAPI* m_pluginAPI; @@ -77,10 +79,8 @@ private: ChannelMarker m_channelMarker; DSDDemodSettings m_settings; bool m_doApplySettings; - char m_formatStatusText[82+1]; //!< Fixed signal format dependent status text - SignalFormat m_signalFormat; - ScopeVis* m_scopeVis; + ScopeVisXY* m_scopeVisXY; DSDDemod* m_dsdDemod; bool m_enableCosineFiltering; @@ -97,6 +97,8 @@ private: MessageQueue m_inputMessageQueue; + DSDStatusTextDialog m_dsdStatusTextDialog; + explicit DSDDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); virtual ~DSDDemodGUI(); @@ -104,13 +106,11 @@ private: void applySettings(bool force = false); void displaySettings(); void updateMyPosition(); - void displayUDPAddress(); void leaveEvent(QEvent*); void enterEvent(QEvent*); private slots: - void formatStatusText(); void on_deltaFrequency_changed(qint64 value); void on_rfBW_valueChanged(int index); void on_demodGain_valueChanged(int value); @@ -118,6 +118,9 @@ private slots: void on_baudRate_currentIndexChanged(int index); void on_enableCosineFiltering_toggled(bool enable); void on_syncOrConstellation_toggled(bool checked); + void on_traceLength_valueChanged(int value); + void on_traceStroke_valueChanged(int value); + void on_traceDecay_valueChanged(int value); void on_slot1On_toggled(bool checked); void on_slot2On_toggled(bool checked); void on_tdmaStereoSplit_toggled(bool checked); @@ -127,9 +130,11 @@ private slots: void on_highPassFilter_toggled(bool checked); void on_audioMute_toggled(bool checked); void on_symbolPLLLock_toggled(bool checked); - void on_udpOutput_toggled(bool checked); void onWidgetRolled(QWidget* widget, bool rollDown); void onMenuDialogCalled(const QPoint& p); + void on_viewStatusLog_clicked(); + void handleInputMessages(); + void audioSelect(); void tick(); }; diff --git a/plugins/channelrx/demoddsd/dsddemodgui.ui b/plugins/channelrx/demoddsd/dsddemodgui.ui index 81dae5996..03548e69c 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.ui +++ b/plugins/channelrx/demoddsd/dsddemodgui.ui @@ -6,8 +6,8 @@ 0 0 - 686 - 841 + 613 + 392 @@ -18,13 +18,13 @@ - 686 + 610 0 - Sans Serif + Liberation Sans 9 @@ -39,7 +39,7 @@ 0 0 - 684 + 608 172 @@ -51,7 +51,7 @@ - 684 + 608 0 @@ -113,7 +113,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -140,6 +140,48 @@ + + + + RFBW + + + + + + + Bandwidth (kHz) before discriminator + + + 500 + + + 1 + + + 100 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + 00.0k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + @@ -153,210 +195,6 @@ - - - - - 0 - 0 - - - - - 35 - 0 - - - - - 16777215 - 16777215 - - - - Baud rate: 2.4k: NXDN48, dPMR 4.8k: DMR, D-Star, YSF, NXDN96 - - - - 2.4k - - - - - 4.8k - - - - - - - - - 110 - 0 - - - - - 16777215 - 25 - - - - - DejaVu Sans Mono - 9 - - - - Synchronized on this frame type - - - QFrame::Box - - - QFrame::Sunken - - - 2 - - - No Sync______ - - - - - - - true - - - Symbol PLL toggle (green: PLL locked) - - - ... - - - - :/unlocked.png - :/locked.png:/unlocked.png - - - true - - - true - - - - - - - - 25 - 0 - - - - Symbol synchronization rate (%) - - - 000 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 25 - 0 - - - - Zero crossing relative position in number of samples (<0 sampling point lags, >0 it leads) - - - -00 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Enable cosine filtering - - - - - - - :/dsb.png:/dsb.png - - - - - - - Toggle between transition constellation and symbol synchronization displays - - - - - - - :/constellation.png - :/slopep_icon.png:/constellation.png - - - true - - - - - - - - 25 - 0 - - - - Carrier relative position (%) when synchronized - - - -00 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 25 - 0 - - - - Carrier input level (%) when synchronized - - - 000 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - @@ -415,7 +253,7 @@ - Monospace + Liberation Mono 8 @@ -424,56 +262,11 @@ - + - - - RFBW - - - - - + - Bandwidth (kHz) before discriminator - - - 500 - - - 1 - - - 100 - - - Qt::Horizontal - - - - - - - - 40 - 0 - - - - 00.0k - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 10 - 0 - + Activate status text log @@ -481,58 +274,33 @@ - - - Gain - - - - - + - 0 + 24 0 + + + 24 + 16777215 + + - Gain after discriminator - - - 50 - - - 400 - - - 1 - - - 100 - - - Qt::Horizontal - - - - - - - - 35 - 0 - + View status text log - 0.00 + - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + :/listing.png:/listing.png - + Qt::Horizontal @@ -605,123 +373,17 @@
- - - - - - - FMd - - - - - - - Maximum frequency deviation (kHz) - - - 500 - - - 1 - - - 50 - + - Qt::Horizontal - - - - - - - - 40 - 0 - - - - 00.0k - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - TDMA slot1 or FDMA unique slot voice on/off - - - - - - - :/slot1_off.png - :/slot1_on.png:/slot1_off.png - - - true - - - - - - - TDMA slot2 voice on/off - - - - - - - :/slot2_off.png - :/slot2_on.png:/slot2_off.png - - - true - - - - - - - Split TDMA channels on left (slot 1) and right (slot 2) audio stereo channels or merge as mono - - - - - - - :/mono.png - :/stereo.png:/mono.png - - - true + Qt::Vertical - Sq + Sq @@ -737,7 +399,7 @@ Squelch threshold (dB) - -1000 + -100 0 @@ -749,7 +411,7 @@ 1 - -150 + -40
@@ -757,21 +419,15 @@ - 0 + 30 0 - - - 40 - 16777215 - - Squelch threshold (dB) - -15.0 + -100 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -842,7 +498,7 @@ - Mute/Unmute audio (all slots) + Left: Mute/Unmute audio (all slots) Right: select audio output ... @@ -857,16 +513,6 @@ - - - - Copy audio output to UDP - - - U - - - @@ -890,10 +536,13 @@ - DejaVu Sans Mono + Liberation Mono 9 + + Status text + QFrame::Box @@ -925,25 +574,25 @@ - + 10 180 - 636 - 651 + 600 + 210 - 636 - 0 + 600 + 210 - Discriminator Scope + Digital - + 2 @@ -957,23 +606,642 @@ 2 - + + + + 0 + 0 + + - 632 - 250 + 200 + 200 - - - Monospace - 8 - - - + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + 10 + 10 + 59 + 20 + + + + + 0 + 0 + + + + + 35 + 0 + + + + + 16777215 + 16777215 + + + + Baud rate: 2.4k: NXDN48, dPMR 4.8k: DMR, D-Star, YSF, NXDN96 + + + + 2.4k + + + + + 4.8k + + + + + + + 80 + 10 + 110 + 25 + + + + + 110 + 0 + + + + + 16777215 + 25 + + + + + Liberation Mono + 9 + + + + Synchronized on this frame type + + + QFrame::Box + + + QFrame::Sunken + + + 2 + + + No Sync______ + + + + + true + + + + 230 + 10 + 23 + 22 + + + + Symbol PLL toggle (green: PLL locked) + + + ... + + + + :/unlocked.png + :/locked.png:/unlocked.png + + + true + + + true + + + + + + 10 + 40 + 25 + 28 + + + + + 25 + 0 + + + + Symbol synchronization rate (%) + + + 000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 40 + 40 + 25 + 28 + + + + + 25 + 0 + + + + Zero crossing relative position in number of samples (<0 sampling point lags, >0 it leads) + + + -00 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 200 + 10 + 23 + 22 + + + + Enable cosine filtering + + + + + + + :/dsb.png:/dsb.png + + + + + + 80 + 40 + 25 + 28 + + + + + 25 + 0 + + + + Carrier relative position (%) when synchronized + + + -00 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 110 + 40 + 25 + 28 + + + + + 25 + 0 + + + + Carrier input level (%) when synchronized + + + 000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 70 + 23 + 22 + + + + Toggle between transition constellation and symbol synchronization displays + + + + + + + :/constellation.png + :/slopep_icon.png:/constellation.png + + + true + + + + + + 50 + 107 + 141 + 16 + + + + Maximum frequency deviation (kHz) + + + 100 + + + 1 + + + 35 + + + 35 + + + Qt::Horizontal + + + + + + 10 + 100 + 25 + 29 + + + + FMd + + + + + + 200 + 100 + 50 + 29 + + + + + 50 + 0 + + + + +00.0k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 130 + 28 + 30 + + + + Gain + + + + + + 50 + 137 + 141 + 16 + + + + + 0 + 0 + + + + Gain after discriminator + + + 50 + + + 200 + + + 1 + + + 100 + + + Qt::Horizontal + + + + + + 170 + 40 + 23 + 22 + + + + TDMA slot1 or FDMA unique slot voice on/off + + + + + + + :/slot1_off.png + :/slot1_on.png:/slot1_off.png + + + true + + + + + + 200 + 40 + 23 + 22 + + + + TDMA slot2 voice on/off + + + + + + + :/slot2_off.png + :/slot2_on.png:/slot2_off.png + + + true + + + + + + 230 + 40 + 23 + 22 + + + + Split TDMA channels on left (slot 1) and right (slot 2) audio stereo channels or merge as mono + + + + + + + :/mono.png + :/stereo.png:/mono.png + + + true + + + + + + 40 + 68 + 24 + 24 + + + + + 24 + 24 + + + + Display trace length (ms) + + + 6 + + + 30 + + + 1 + + + 6 + + + + + + 70 + 73 + 31 + 16 + + + + Display trace length (ms) + + + 0000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 200 + 130 + 50 + 29 + + + + + 50 + 0 + + + + 0.00 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 110 + 70 + 24 + 24 + + + + + 24 + 24 + + + + Trace stroke [0..255] + + + 0 + + + 255 + + + 1 + + + 100 + + + + + + 130 + 73 + 31 + 16 + + + + Trace stroke value + + + 000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 170 + 70 + 24 + 24 + + + + + 24 + 24 + + + + Trace decay [0..255] + + + 0 + + + 255 + + + 1 + + + 200 + + + + + + 190 + 73 + 31 + 16 + + + + Trace decay value + + + 000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + @@ -996,24 +1264,18 @@
gui/levelmeter.h
1 - - GLScope - QWidget -
gui/glscope.h
- 1 -
- - GLScopeGUI - QWidget -
gui/glscopegui.h
- 1 -
ValueDialZ QWidget
gui/valuedialz.h
1
+ + TVScreen + QWidget +
gui/tvscreen.h
+ 1 +
diff --git a/plugins/channelrx/demoddsd/dsddemodplugin.cpp b/plugins/channelrx/demoddsd/dsddemodplugin.cpp index a4c96b3fe..0d14131da 100644 --- a/plugins/channelrx/demoddsd/dsddemodplugin.cpp +++ b/plugins/channelrx/demoddsd/dsddemodplugin.cpp @@ -20,12 +20,14 @@ #include #include #include "plugin/pluginapi.h" +#ifndef SERVER_MODE #include "dsddemodgui.h" +#endif #include "dsddemod.h" const PluginDescriptor DSDDemodPlugin::m_pluginDescriptor = { QString("DSD Demodulator"), - QString("3.12.0"), + QString("4.2.4"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, @@ -51,10 +53,19 @@ void DSDDemodPlugin::initPlugin(PluginAPI* pluginAPI) m_pluginAPI->registerRxChannel(DSDDemod::m_channelIdURI, DSDDemod::m_channelId, this); } +#ifdef SERVER_MODE +PluginInstanceGUI* DSDDemodPlugin::createRxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSink *rxChannel __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* DSDDemodPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) { return DSDDemodGUI::create(m_pluginAPI, deviceUISet, rxChannel); } +#endif BasebandSampleSink* DSDDemodPlugin::createRxChannelBS(DeviceSourceAPI *deviceAPI) { diff --git a/plugins/channelrx/demoddsd/dsddemodsettings.cpp b/plugins/channelrx/demoddsd/dsddemodsettings.cpp index c7404ee77..d73e029dd 100644 --- a/plugins/channelrx/demoddsd/dsddemodsettings.cpp +++ b/plugins/channelrx/demoddsd/dsddemodsettings.cpp @@ -32,26 +32,26 @@ void DSDDemodSettings::resetToDefaults() { m_inputFrequencyOffset = 0; m_rfBandwidth = 12500.0; - m_fmDeviation = 5000.0; - m_demodGain = 1.25; + m_fmDeviation = 3500.0; + m_demodGain = 1.0; m_volume = 2.0; m_baudRate = 4800; m_squelchGate = 5; // 10s of ms at 48000 Hz sample rate. Corresponds to 2400 for AGC attack m_squelch = -40.0; m_audioMute = false; - m_audioSampleRate = DSPEngine::instance()->getAudioSampleRate(); m_enableCosineFiltering = false; m_syncOrConstellation = false; m_slot1On = true; m_slot2On = false; m_tdmaStereo = false; m_pllLock = true; - m_copyAudioToUDP = false; - m_udpAddress = "127.0.0.1"; - m_udpPort = 9999; m_rgbColor = QColor(0, 255, 255).rgb(); m_title = "DSD Demodulator"; m_highPassFilter = false; + m_traceLengthMutliplier = 6; // 300 ms + m_traceStroke = 100; + m_traceDecay = 200; + m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; } QByteArray DSDDemodSettings::serialize() const @@ -61,7 +61,7 @@ QByteArray DSDDemodSettings::serialize() const s.writeS32(2, m_rfBandwidth/100.0); s.writeS32(3, m_demodGain*100.0); s.writeS32(4, m_fmDeviation/100.0); - s.writeS32(5, m_squelch*10.0); + s.writeS32(5, m_squelch); s.writeU32(7, m_rgbColor); s.writeS32(8, m_squelchGate); s.writeS32(9, m_volume*10.0); @@ -83,6 +83,10 @@ QByteArray DSDDemodSettings::serialize() const s.writeString(18, m_title); s.writeBool(19, m_highPassFilter); + s.writeString(20, m_audioDeviceName); + s.writeS32(21, m_traceLengthMutliplier); + s.writeS32(22, m_traceStroke); + s.writeS32(23, m_traceDecay); return s.final(); } @@ -116,8 +120,8 @@ bool DSDDemodSettings::deserialize(const QByteArray& data) m_demodGain = tmp / 100.0; d.readS32(4, &tmp, 50); m_fmDeviation = tmp * 100.0; - d.readS32(5, &tmp, -400); - m_squelch = tmp / 10.0; + d.readS32(5, &tmp, -40); + m_squelch = tmp < -100 ? tmp / 10.0 : tmp; d.readU32(7, &m_rgbColor); d.readS32(8, &m_squelchGate, 5); d.readS32(9, &tmp, 20); @@ -136,6 +140,13 @@ bool DSDDemodSettings::deserialize(const QByteArray& data) d.readBool(16, &m_tdmaStereo, false); d.readString(18, &m_title, "DSD Demodulator"); d.readBool(19, &m_highPassFilter, false); + d.readString(20, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName); + d.readS32(21, &tmp, 6); + m_traceLengthMutliplier = tmp < 2 ? 2 : tmp > 30 ? 30 : tmp; + d.readS32(22, &tmp, 100); + m_traceStroke = tmp < 0 ? 0 : tmp > 255 ? 255 : tmp; + d.readS32(23, &tmp, 200); + m_traceDecay = tmp < 0 ? 0 : tmp > 255 ? 255 : tmp; return true; } diff --git a/plugins/channelrx/demoddsd/dsddemodsettings.h b/plugins/channelrx/demoddsd/dsddemodsettings.h index f60a4d6c2..e22bfa3f0 100644 --- a/plugins/channelrx/demoddsd/dsddemodsettings.h +++ b/plugins/channelrx/demoddsd/dsddemodsettings.h @@ -32,19 +32,19 @@ struct DSDDemodSettings int m_squelchGate; Real m_squelch; bool m_audioMute; - quint32 m_audioSampleRate; bool m_enableCosineFiltering; bool m_syncOrConstellation; bool m_slot1On; bool m_slot2On; bool m_tdmaStereo; bool m_pllLock; - bool m_copyAudioToUDP; - QString m_udpAddress; - quint16 m_udpPort; quint32 m_rgbColor; QString m_title; bool m_highPassFilter; + int m_traceLengthMutliplier; // x 50ms + int m_traceStroke; // [0..255] + int m_traceDecay; // [0..255] + QString m_audioDeviceName; Serializable *m_channelMarker; Serializable *m_scopeGUI; diff --git a/plugins/channelrx/demoddsd/dsdstatustextdialog.cpp b/plugins/channelrx/demoddsd/dsdstatustextdialog.cpp new file mode 100644 index 000000000..df574c699 --- /dev/null +++ b/plugins/channelrx/demoddsd/dsdstatustextdialog.cpp @@ -0,0 +1,87 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "dsdstatustextdialog.h" +#include "ui_dsdstatustextdialog.h" + +#include +#include +#include +#include +#include + +DSDStatusTextDialog::DSDStatusTextDialog(QWidget* parent) : + QDialog(parent), + ui(new Ui::DSDStatusTextDialog) +{ + ui->setupUi(this); +} + +DSDStatusTextDialog::~DSDStatusTextDialog() +{ + delete ui; +} + +void DSDStatusTextDialog::addLine(const QString& line) +{ + if ((line.size() > 0) && (line != m_lastLine)) + { + QDateTime dt = QDateTime::currentDateTime(); + QString dateStr = dt.toString("HH:mm:ss"); + QTextCursor cursor = ui->logEdit->textCursor(); + cursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor); + cursor.insertText(tr("%1 %2\n").arg(dateStr).arg(line)); + if (ui->pinToLastLine->isChecked()) { + ui->logEdit->verticalScrollBar()->setValue(ui->logEdit->verticalScrollBar()->maximum()); + } + m_lastLine = line; + } +} + +void DSDStatusTextDialog::on_clear_clicked() +{ + ui->logEdit->clear(); +} + +void DSDStatusTextDialog::on_saveLog_clicked() +{ + QString fileName = QFileDialog::getSaveFileName(this, + tr("Open log file"), ".", tr("Log files (*.log)"), 0, QFileDialog::DontUseNativeDialog); + + if (fileName != "") + { + QFileInfo fileInfo(fileName); + + if (fileInfo.suffix() != "log") { + fileName += ".log"; + } + + QFile exportFile(fileName); + + if (exportFile.open(QIODevice::WriteOnly | QIODevice::Text)) + { + QTextStream outstream(&exportFile); + outstream << ui->logEdit->toPlainText(); + exportFile.close(); + } + else + { + QMessageBox::information(this, tr("Message"), tr("Cannot open file for writing")); + } + } + +} diff --git a/plugins/channelrx/demoddsd/dsdstatustextdialog.h b/plugins/channelrx/demoddsd/dsdstatustextdialog.h new file mode 100644 index 000000000..23c18b1ae --- /dev/null +++ b/plugins/channelrx/demoddsd/dsdstatustextdialog.h @@ -0,0 +1,46 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_CHANNELRX_DEMODDSD_DSDSTATUSTEXTDIALOG_H_ +#define PLUGINS_CHANNELRX_DEMODDSD_DSDSTATUSTEXTDIALOG_H_ + +#include + +namespace Ui { + class DSDStatusTextDialog; +} + +class DSDStatusTextDialog : public QDialog { + Q_OBJECT + +public: + explicit DSDStatusTextDialog(QWidget* parent = 0); + ~DSDStatusTextDialog(); + + void addLine(const QString& line); + +private: + Ui::DSDStatusTextDialog* ui; + QString m_lastLine; + +private slots: + void on_clear_clicked(); + void on_saveLog_clicked(); +}; + + +#endif /* PLUGINS_CHANNELRX_DEMODDSD_DSDSTATUSTEXTDIALOG_H_ */ diff --git a/plugins/channelrx/demoddsd/dsdstatustextdialog.ui b/plugins/channelrx/demoddsd/dsdstatustextdialog.ui new file mode 100644 index 000000000..df743fc08 --- /dev/null +++ b/plugins/channelrx/demoddsd/dsdstatustextdialog.ui @@ -0,0 +1,181 @@ + + + DSDStatusTextDialog + + + + 0 + 0 + 740 + 380 + + + + + Sans Serif + 9 + + + + Status text log + + + + :/sdrangel_icon.png:/sdrangel_icon.png + + + + + + + + + 24 + 24 + + + + Clear log + + + + + + + :/sweep.png:/sweep.png + + + false + + + + + + + Pin to last line + + + + + + + :/pin_last.png:/pin_last.png + + + + + + + + 24 + 24 + + + + Save log to file + + + + + + + :/save.png:/save.png + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + true + + + + Liberation Mono + + + + Log + + + true + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + ButtonSwitch + QToolButton +
gui/buttonswitch.h
+
+
+ + buttonBox + + + + + + + buttonBox + accepted() + DSDStatusTextDialog + accept() + + + 257 + 194 + + + 157 + 203 + + + + + buttonBox + rejected() + DSDStatusTextDialog + reject() + + + 314 + 194 + + + 286 + 203 + + + + +
diff --git a/plugins/channelrx/demoddsd/readme.md b/plugins/channelrx/demoddsd/readme.md index 3a68dd177..acf3ca59e 100644 --- a/plugins/channelrx/demoddsd/readme.md +++ b/plugins/channelrx/demoddsd/readme.md @@ -8,9 +8,6 @@ This plugin uses the [DSDcc](https://github.com/f4exb/dsdcc) library that has be - dPMR: Another ETSI standard at slower rate (2400 Baud / 6.25 kHz) and FDMA - D-Star: developed and promoted by Icom for Amateur Radio customers. - Yaesu System Fusion (YSF): developed and promoted by Yaesu for Amateur Radio customers. Voice full rate with DV serial devices is not supported - -It can only detect the following standards (no data, no voice): - - NXDN: A joint Icom (IDAS) and Kenwood (Nexedge) standard with 2400 and 4800 Baud versions. The modulation and standard is automatically detected and switched depending on the Baud rate chosen. @@ -22,19 +19,21 @@ To enable this plugin at compile time you will need to have DSDcc installed in y You can use a serial device connected to your system that implements and exposes the packet interface of the AMBE3000 chip. This can be for example a ThumbDV USB dongle. In order to support DV serial devices in your system you will need two things: - Compile with [SerialDV](https://github.com/f4exb/serialDV) support Please refer to this project Readme.md to compile and install SerialDV. If you install it in a custom location say `/opt/install/serialdv` you will need to add these defines to the cmake command: `-DLIBSERIALDV_INCLUDE_DIR=/opt/install/serialdv/include/serialdv -DLIBSERIALDV_LIBRARY=/opt/install/serialdv/lib/libserialdv.so` - - Enable DV serial devices in your system by checking the option in the Preferences menu. YOu will need to enable the DV serial devices each time you start SDRangel. + - Enable DV serial devices in your system by checking the option in the Preferences menu. You will need to enable the DV serial devices each time you start SDRangel. Although such serial devices work with a serial interface at 400 kb in practice maybe for other reasons they are capable of handling only one conversation at a time. The software will allocate the device dynamically to a conversation with an inactivity timeout of 1 second so that conversations do not get interrupted constantly making the audio output too choppy. In practice you will have to have as many devices connected to your system as the number of conversations you would like to be handled in parallel. Note also that this is not supported in Windows because of trouble with COM port support (contributors welcome!). -Altermatively you can use software decoding with Mbelib. Possible copyright issues apart (see next) the audio quality with the DVSI AMBE chip is much better. +Alternatively you can use software decoding with Mbelib. Possible copyright issues apart (see next) the audio quality with the DVSI AMBE chip is much better. --- -⚠ Since kernel 4.4.52 the default for FTDI devices (that is in the ftdi_sio kernel module) is not to set it as low latency. This results in the ThumbDV dongle not working anymore because its response is too slow to sustain the normal AMBE packets flow. The solution is to force low latency by changing the variable for your device (ex: /dev/ttyUSB0) as follows: +⚠ With kernel 4.4.52 and maybe other 4.4 versions the default for FTDI devices (that is in the ftdi_sio kernel module) is not to set it as low latency. This results in the ThumbDV dongle not working anymore because its response is too slow to sustain the normal AMBE packets flow. The solution is to force low latency by changing the variable for your device (ex: /dev/ttyUSB0) as follows: `echo 1 | sudo tee /sys/bus/usb-serial/devices/ttyUSB0/latency_timer` or `sudo setserial /dev/ttyUSB0 low_latency` +Newer kernels do not seem to have this issue. + ---

Mbelib support

@@ -46,206 +45,133 @@ If you are not comfortable with this just do not install DSDcc and/or mbelib and - For Linux distributions: `plugins/channel/libdemoddsd.so` - For Windows distributions: `dsdcc.dll`, `mbelib.dll`, `plugins\channel\demoddsd.dll` -For software built fron source if you choose to have `mbelib` support you will need to have DSDcc compiled with `mbelib` support. You will also need to have defines for it on the cmake command. If you have mbelib installed in a custom location, say `/opt/install/mbelib` you will need to add these defines to the cmake command: `-DLIBMBE_INCLUDE_DIR=/opt/install/mbelib/include -DLIBMBE_LIBRARY=/opt/install/mbelib/lib/libmbe.so` +For software built from source if you choose to have `mbelib` support you will need to have DSDcc compiled with `mbelib` support. You will also need to have defines for it on the cmake command. If you have mbelib installed in a custom location, say `/opt/install/mbelib` you will need to add these defines to the cmake command: `-DLIBMBE_INCLUDE_DIR=/opt/install/mbelib/include -DLIBMBE_LIBRARY=/opt/install/mbelib/lib/libmbe.so`

Interface

![DSD Demodulator plugin GUI](../../../doc/img/DSDdemod_plugin.png) -

1: Frequency shift from center frequency of reception

+

A section: settings

-Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews.Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +

A.1: Frequency shift from center frequency of reception

-

2: Symbol (Baud) rate

+Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows.Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. -Here you can specify which symbol rate or Baud rate is expected. Choices are: - - - `2.4k`: 2400 S/s used for dPMR and 4800 b/s NXDN - - `4.8k`: 4800 S/s used for 9600 b/s NXDN, DMR, D-Star and YSF. - -

3: Type of frame detected

- -This can be one of the following: - - - `+DMRd`: non-inverted DMR data frame - - `+DMRv`: non-inverted DMR voice frame - - `+D-STAR`: non-inverted D-Star frame - - `-D-STAR`: inverted D-Star frame - - `+D-STAR_HD`: non-inverted D-Star header frame encountered - - `-D-STAR_HD`: inverted D-Star header frame encountered - - `+dPMR`: non-inverted dPMR non-packet frame - - `+NXDN`: non-inverted NXDN frame (detection only) - - `+YSF`: non-inverted Yaesu System Fusion frame (detection only) - -

3a: Symbol PLL lock indicator

- -Since dsdcc version 1.7.1 the synbol synchronization can be done with a PLL fed by a ringing filter (narrow passband) tuned at the symbol rate and itself fed with the squared magnitude of the discriminator signal. For signals strong enough to lock the PLL this works significantly better than with the ringing filter alone that was the only option in versions <= 1.6.0. Version 1.7.0 had the PLL enabled permanently. - -However with marginal signals the ringing filter alone and a few heuristics work better. This is why since DSDcc version 1.7.1 the PLL became optional. - -You can use this button to toggle between the two options: - - - with the locker icon in locked position: PLL is engaged - - with the locker icon in unlocked position: PLL is bypassed - -When in lock position the button lights itself in green when the PLL lock is acquired. Occasional drops may occur without noticeable impact on decoding. - -

4: Symbol synchronization zero crossing hits in %

- -This is the percentage per symbols for which a valid zero crossing has been detected. The more the better the symbol synchronization is tracked however the zero crossing shifts much not deviate too much from 0 (see next). - -With the PLL engaged the figure should be 100% all the time in presence of a locked signal. Occasional small drops may occur without noticeable impact on decoding. - -

5: Zero crossing shift

- -This is the current (at display polling time) zero crosing shift. It should be the closest to 0 as possible. However some jitter is acceptable for good symbol synchronization: - - - `2400 S/s`: +/- 5 inclusive - - `4800 S/s`: +/- 2 inclusive - -

6: Matched filter toggle

- -Normally you would always want to have a matched filter however on some strong D-Star signals more synchronization points could be obtained without. When engaged the background of the button is lit in orange. - -

7: Transition constellation or symbol synchronization signal toggle

- -Using this button you can either: - - - show the transitions constellation - - show a indicative signal about symbol synchronization - - when a zero crossing is detected the signal is set to estimated input discriminator signal maximum value - - when the symbol clock is 0 (start of symbol period) the signal is set to the estimated median point of the input discriminator signal - -

8: Discriminator input signal median level in %

- -This is the estimated median level (center) of the discriminator input signal in percentage of half the total range. When the signal is correctly aligned in the input range it should be 0 - -

9: Discriminator input signal level range in %

- -This is the estimated discriminator input signal level range (max - min) in percentage of half the total range. For optimal decoding it should be maintained close to 100. - -

10: Channel power

- -Total power in dB relative to a +/- 1.0 amplitude signal received in the pass band. - -

11: Channel bandwidth before discriminator

+

A.2: Channel bandwidth before discriminator

This is the bandwidth of the pre-discriminator filter -

12: Gain after discriminator

+

A.3: Channel power

-This is the gain applied to the output of the discriminator before the decoder +Total power in dB relative to a +/- 1.0 amplitude signal received in the pass band. -

13: Audio volume

+

A.4: Channel power bar graph

+ +

A.5: Activate status text log

+ +Check to send the status text lines (A.12) to the log. Uncheck to dismiss sending. + +

A.6 View status text log

+ +Click to open a dialog to view the status text lines log: + +![DSD Demodulator status text log GUI](../../../doc/img/DSDdemod_plugin_status_text_log.png) + +
A.6.1 Clear log
+ +Push this button to clear the log + +
A.6.2 Pin to last line
+ +Use this toggle to pin or unpin the log to the last line + +
A.6.3 Save log to file
+ +Save the present log content to a file + +
A.6.4 Timestamp
+ +Each line in the log starts with the timestamp when the status line was fetched from the decoder + +
A.6.5 Status text
+ +One line per status text + +

A.7: Audio volume

When working with mbelib this is a linear multiplication factor. A value of zero triggers the auto gain feature. With the DV serial device(s) amplification factor in dB is given by `(value - 3.0)*5.0`. In most practical cases the middle value of 5.0 (+10 dB) is a comfortable level. -

14: Maximum expected FM deviation

- -This is the deviation in kHz leading to maximum (100%) deviation. You should aim for 30 to 50% (+/-300 to +/-500m) deviation on the scope display. - -

15: Two slot TDMA handling

- -This is useful for two slot TDMA modes that is only DMR at present. FDMA modes are treated as using slot #1 only. - -![DSD TDMA handling](../../../doc/img/DSDdemod_plugin_tdma.png) - -

15.1: Slot #1 voice select

- -Toggle button to select slot #1 voice output. When on waves appear on the icon. The icon turns green when voice frames are processed for this slot. For FDMA modes you may want to leave only this toggle on. - -

15.2: Slot #2 voice select

- -Toggle button to select slot #2 voice output. When on waves appear on the icon. The icon turns green when voice frames are processed for this slot. For FDMA modes you may want to leave this toggle off. - -

15.3: TDMA stereo mode toggle

- - - When off the icon shows a single loudspeaker. It mixes slot #1 and slot #2 voice as a mono audio signal - - When on the icon shows a pair of loudspeakers. It sends slot #1 vocie to the left stereo audio channel and slot #2 to the right one - -For FDMA standards you may want to leave this as mono mode. - -

16: Squelch level

+

A.8: Squelch level

The level corresponds to the channel power above which the squelch gate opens. -

17: Squelch time gate

+

A.9: Squelch time gate

-Number of milliseconds following squelch gate opening after which the signal is actually fed to the decoder. 0 means no delay i.e. immediate feed. +Number of milliseconds following squelch gate opening after which the signal is declared open. There is a delay line for the samples so samples applied to the decoder actually start at the beginning of the gate period not loosing any samples. 0 means squelch is declared open with no delay. -

18: High-pass filter for audio

+

A.10: High-pass filter for audio

Use this switch to toggle high-pass filter on the audio -

19: Audio mute and squelch indicator

+

A.11: Audio mute, squelch indicator and select audio output device

-Audio mute toggle button. This button lights in green when the squelch opens. +Left click to mute/unmute audio. This button lights in green when the squelch opens. -

20: UDP output

+If you right click on it it will open a dialog to select the audio output device. See [audio management documentation](../../../sdrgui/audio.md) for details. -Copies audio output to UDP. Output is stereo S16LE samples. Depending on which slots are active the output is the following: +

A.12: Format specific status display

- - Slot 1: slot 1 on left channel - - Slot 2: slot 2 on right channel - - Slot 1+2: slot 1 on left channel +When the display is active the background turns from the surrounding gray color to dark green. It shows informational or status messages that are particular to each format. -It cannot mix both channels when slot1+2 are active. - -UDP address and send port are specified in the basic channel settings. See: [here](https://github.com/f4exb/sdrangel/blob/master/sdrgui/readme.md#6-channels) - -

21: Format specific status display

- -When the display is active the background turns from the surrounding gray color to dark green. It shows informatory or status messages that are particular to each format. - -

21.1: D-Star status display

+

A11.1: D-Star status display

![DSD D-Star status](../../../doc/img/DSDdemod_plugin_dstar_status.png) -
21.1.1: Origin (my) and destination (your) callsign
+
A11.1.1: Origin (my) and destination (your) callsign
- at the left of the `>` sign is the origin callsign ` MY` with the 4 character informative suffix nest to the slash `/` - at the right of the `>` sign is the destination callsign `YOUR`. As per Icom standard this is `CQCQCQ` when a call is made to all stations - this information is retrieved from the header or the slow data if it can be decoded -
21.1.2: Repeater callsign
+
A11.1.2: Repeater callsign
- at the left of the `>` sign is the origin repeater or `RPT1` - at the right of the `>` sign is the destination repeater or `RPT2` - this information is retrieved from the header or the slow data if it can be decoded -
21.1.3: Informative text
+
A11.1.3: Informative text
When slow data can be decoded this is the 20 character string that is sent in the text frames -
21.1.4: Geopositional data
+
A11.1.4: Geopositional data
When a `$$CRC` frame that carries geographical position can be successfully decoded from the slow data the geopositional information is displayed: - at the left of the colon `:` is the QTH 6 character locator a.k.a. Maidenhead locator - at the right of the colon `:` is the bearing in degrees and distance in kilometers from the location entered in the main window `Preferences\My Position` dialog. The bearing and distance are separated by a slash `/`. -

21.2: DMR status display

+

A11.2: DMR status display

![DSD DMR status](../../../doc/img/DSDdemod_plugin_dmr_status.png) - Note 1: statuses are polled at ~1s rate and therefore do not reflect values instantaneously. As a consequence some block types that occur during the conversation may not appear. - Note 2: status values remain unchanged until a new value is available for the channel or the transmissions stops then all values of both channels are cleared -
21.2.1: Station role
+
A11.2.1: Station role
- `BS`: base station - `MS`: mobile station - `NA`: not applicable or could not be determined (you should not see this normally) -
21.2.2: TDMA slot #0 status
+
A11.2.2: TDMA slot #0 status
For mobile stations on an inbound channel there is no channel identification (no CACH) so information goes there by default. -
21.2.3: TDMA slot #1 status
+
A11.2.3: TDMA slot #1 status
-
21.2.4: Channel status and color code
+
A11.2.4: Channel status and color code
This applies to base stations and mobile stations in continuous mode that is transmissions including the CACH sequences. @@ -260,7 +186,7 @@ This applies to base stations and mobile stations in continuous mode that is tra - The color code from 0 to 15 (4 bits) - `--`: The color code could not be decoded and information is missing -
21.2.5: Slot type
+
A11.2.5: Slot type
This is either: @@ -268,7 +194,7 @@ This is either: - `IDL`: data idle block - `VLC`: voice Link Control data block - `TLC`: terminator with Link Control information data block - - `CSB`: CSBK (Control Signalling BlocK) data block + - `CSB`: CSBK (Control Signaling BlocK) data block - `MBH`: Multi Block Control block header data block - `MBC`: Multi Block Control block continuation data block - `DAH`: Data header block @@ -278,7 +204,7 @@ This is either: - `RES`: reserved data block - `UNK`: unknown data type or could not be decoded -
21.2.6: Addressing information
+
A11.2.6: Addressing information
String is in the form: `02223297>G00000222` @@ -288,15 +214,15 @@ String is in the form: `02223297>G00000222` - `U`: unit (individual) address - Next on the right is the target address (24 bits) as defined in the DMR ETSI standard -

21.3: dPMR status display

+

A11.3: dPMR status display

![DSD dPMR status](../../../doc/img/DSDdemod_plugin_dpmr_status.png) -
21.3.1: dPMR frame tyoe
+
A11.3.1: dPMR frame tyoe
- `--`: undetermined - `HD`: Header of FS1 type - - `PY`: Payload frame of a sitll undetermined type + - `PY`: Payload frame of a still undetermined type - `VO`: Voice frame - `VD`: Voice and data frame - `D1`: Data without FEC frame @@ -304,23 +230,23 @@ String is in the form: `02223297>G00000222` - `XS`: Extended search: looking for a new payload frame when out of sequence - `EN`: End frame -
21.3.2: Colour code
+
A11.3.2: Colour code
Colour code in decimal (12 bits) -
21.3.3: Own ID
+
A11.3.3: Own ID
Sender's identification code in decimal (24 bits) -
21.3.4: Called ID
+
A11.3.4: Called ID
Called party's identification code in decimal (24 bits) -

21.4: Yaesu System Fusion (YSF) status display

+

A11.4: Yaesu System Fusion (YSF) status display

![DSD YSF status](../../../doc/img/DSDdemod_plugin_ysf_status.png) -
21.4.1: FICH data
+
A11.4.1: FICH data
This displays a summary of FICH (Frame Identification CHannel) block data. From left to right: @@ -350,129 +276,312 @@ This displays a summary of FICH (Frame Identification CHannel) block data. From - `W`: wide band mode (as in the example) - second character is the path type: - `I`: Internet path - - `L`: local path (as inthe example) + - `L`: local path (as in the example) - last three characters are the YSF squelch code (0..127) or dashes `---` if the YSF squelch is not active -
21.4.2: Origin and destination callsigns
+
A11.4.2: Origin and destination callsigns
- at the left of the `>` sign is the origin callsign - at the right of the `>` sign is the destination callsign. It is filled with stars `*` when call is made to all stations (similar to the CQCQCQ in D-Star) -
21.4.3: Origin and destination repeaters callsigns
+
A11.4.3: Origin and destination repeaters callsigns
- at the left of the `>` sign is the origin repeater callsign - at the right of the `>` sign is the destination repeater callsign. -
21.4.4: Originator radio ID
+
A11.4.4: Originator radio ID
This is the unique character string assigned to the device by the manufacturer. -

22: Discriminator output scope display

+

A11.5: NXDN status display

-

22.1 Transitions constellation display

+There are 3 display formats depending on the kind of transmission called RF channel in the NXDN system. -This is selected by the transition constellation or symbol synchronization signal toggle (see 7) +
A11.5.1: RCCH RF channel display
+ +This is the control channel used in trunked systems and is usually sent continuously. + +![DSD NXDN RTDCH status](../../../doc/img/DSDdemod_plugin_nxdn_rcch_status.png) + +
A11.5.1.1: RF channel indicator
+ +This is `RC` for RCCH + +
A11.5.2.2: Half/full rate
+ +Indicator of transmission rate: + + - `H`: half rate (2400 or 4800 S/s). Uses EHR vocoder (AMBE 3600/2450) + - `F`: full rate (4800 S/s only). Uses EFR vocoder (AMBE 7200/4400) + +
A11.5.1.3: RAN number
+ +This is the RAN number (0 to 63) associated to the transmission. RAN stands for "Radio Access Number" and for trunked systems this is the site identifier (Site Id) modulo 64. + +
A11.5.1.4: Last message type code
+ +This is the type code of the last message (6 bits) displayed in hexadecimal. The complete list is found in the NXDN documentation `NXDN TS 1-A Version 1.3` section 6. + +
A11.5.1.5: Location Id
+ +This is the 3 byte location Id associated to the site displayed in hexadecimal + +
A11.5.1.6: Services available flags
+ +This is a 16 bit collection of flags to indicate which services are available displayed in hexadecimal. The breakdown is listed in the NXDN documentation `NXDN TS 1-A Version 1.3` section 6.5.33. From MSB to LSB: + + - first nibble (here `B`): + - `b15`: Multi-site service + - `b14`: Multi-system service + - `b13`: Location Registration service + - `b12`: Group Registration Service + - second nibble (here `3`): + - `b11`: Authentication Service + - `b10`: Composite Control Channel Service + - `b9`: Voice Call Service + - `b8`: Data Call Service + - third nibble (here `C`): + - `b7`: Short Data Call Service + - `b6`: Status Call & Remote Control Service + - `b5`: PSTN Network Connection Service + - `b4`: IP Network Connection Service + - fourth nibble (here `0`) is spare + +
A11.5.2: RTCH or RDCH RF channel display
+ +This is the transmission channel either in a trunked system (RTCH) or conventional system (RDCH). + +![DSD NXDN RTDCH status](../../../doc/img/DSDdemod_plugin_nxdn_rtdch_status.png) + +
A11.5.2.1: RF channel indicator
+ +It can be either `RT` for RTCH or `RD` for a RDCH channel + +
A11.5.2.2: Half/full rate
+ +Indicator of transmission rate: + + - `H`: half rate (2400 or 4800 S/s). Uses EHR vocoder (AMBE 3600/2450) + - `F`: full rate (4800 S/s only). Uses EFR vocoder (AMBE 7200/4400) + +
A11.5.2.3: RAN number
+ +This is the RAN number (0 to 63) associated to the transmission. RAN stands for "Radio Access Number" and has a different usage in conventional or trunked systems: + + - Conventional (RDCH): this is used as a selective squelch. Code `0` means always unmute. + - Trunked (RTCH): this is the site identifier (Site Id) modulo 64. + +
A11.5.2.4: Last message type code
+ +This is the type code of the last message (6 bits) displayed in hexadecimal. The complete list is found in the NXDN documentation `NXDN TS 1-A Version 1.3` section 6. + +
A11.5.2.5: Source Id
+ +This is the source of transmission identification code on two bytes (0 to 65353) displayed in decimal. + +
A11.5.2.6: Destination Id
+ +This is the destination of transmission identification code on two bytes (0 to 65353) displayed in decimal. It is prefixed by a group call indicator: + + - `G`: this is a group call + - `I`: this is an individual call + +
A11.5.3: Unknown or erroneous data display
+ +In this case the display is simply "RU" for "unknown" + +

B section: digital

+ +

B.1: FM signal scope

+ +This display shows the sampled points of the demodulated FM signal in a XY plane with either: + + - X as the signal at time t and Y the signal at time t minus symbol time if "transitions constellation" is selected by button (B.13) + - X as the signal and Y as the synchronization signal if "symbol synchronization" is selected by button (B.13) + +The display shows 16 points as yellow crosses that can be used to tune the center frequency (A.1) and FM deviation (B.17) so that symbol recovery can be done with the best conditions. In the rest of the documentation they will be referenced with numbers from 0 to 15 starting at the top left corner and going from left to right and top to bottom. + +
Transition constellation display
+ +This is selected by the transition constellation or symbol synchronization signal toggle (B.13) The discriminator signal at 48 kS/s is routed to the scope display with the following connections: - - I signal: the discriminator samples - - Q signal: the discriminator samples delayed by the baud rate i.e. one symbol delay: + - X input: the discriminator samples + - Y input: the discriminator samples delayed by the baud rate i.e. one symbol delay: - 2400 baud: 20 samples - 4800 baud: 10 samples - - 9600 baud: 5 samples -This allows the visualization of symbol transitions which depend on the type of modulation. - -![DSD scope](../../../doc/img/DSDdemod_plugin_scope.png) - -
22.1.1: Setting the display
- - - On the combo box you should choose IQ (lin) for the primary display and IQ (pol) for secondary display - - On the display buttons you should choose the side by side display - -On the same line you can choose any trace length. If it is too short the constellation points will not appear clearly and if it is too long the polar figure will be too dense. Usually 100ms give good results. - -
22.1.2: IQ linear display
- -The yellow trace (I) is the direct trace and the blue trace (Q) is the delayed trace. This can show how symbols differentiate between each other in a sort of eye diagram. - -
22.1.3: IQ polar display
- -This shows the constellation of transition points. You should adjust the frequency shift to center the figure and the maximum deviation and/or discriminator gain to contain the figure within the +/-0.4 square. +/- 0.1 to +/- 0.3 usually give the best results. +Depending on the type of modulation the figure will have different characteristic forms:
2-FSK or 2-GFSK
-This concerns the following formats: +![DSD Demodulator plugin GUI 2FSK](../../../doc/img/DSDdemod_plugin_2fsk.png) + +This concerns the following standards: - D-Star - -![DSD D-Star polar](../../../doc/img/DSDdemod_plugin_dstar_polar.png) - + There are 4 possible points corresponding to the 4 possible transitions. x represents the current symbol and y the previous symbol. The 4 points given by their (y,x) coordinates correspond to the following: - - (1, 1): upper right corner. The pointer can stay there or move to (1, -1) - - (1, -1): upper left corner. The pointer can move to (-1, -1) or (-1, 1) - - (-1, 1): lower right corner. The pointer can move to (1, -1) or (1, 1) - - (-1, -1): lower left corner. The pointer can stay there or move to (-1, 1) - + - (1, 1): upper right corner. The pointer can stay there or move to (1, -1). Ideally this should be placed at point 3. + - (1, -1): upper left corner. The pointer can move to (-1, -1) or (-1, 1). Ideally this should be placed at point 0. + - (-1, 1): lower right corner. The pointer can move to (1, -1) or (1, 1). Ideally this should be placed at point 15. + - (-1, -1): lower left corner. The pointer can stay there or move to (-1, 1). Ideally this should be placed at point 12. + As you can see the pointer can make all moves except between (-1, -1) and (1,1) hence all vertices between the 4 points can appear except the one between the lower left corner and the upper right corner.
4-FSK or 4-GFSK
-This concerns the following formats: +![DSD Demodulator plugin GUI 4FSK](../../../doc/img/DSDdemod_plugin_4fsk.png) + +This concerns the following standards: - DMR - YSF - dPMR - NXDN -![DSD DMR polar](../../../doc/img/DSDdemod_plugin_dmr_polar.png) - There are 16 possible points corresponding to the 16 possible transitions between the 4 dibits. The 4 dibits are equally spaced at relative positions of -3, -1, 1, 3 hence the 16 points are also equally spaced between each other on the IQ or (x,y) plane. -Because not all transitions are possible similarly to the 2-FSK case pointer moves from the lower left side of the diagonal to the upper right side are not possible. +Ideally the figure should show a cloud of persistent points at the locations marked by the yellow crosses (0 to 15). -
22.1.4: I gain
+
Symbol synchronization display
-You should set the slider to a unity (1) span (+/- 0.5) with no offset. This corresponds to full range in optimal conditions (100%). You can set the slider fully to the left (2) for a +/- 1.0 spn if you don't exactly match these conditions. +2-FSK -
22.1.5: Q gain
+![DSD Demodulator plugin GUI 2FSK symbols](../../../doc/img/DSDdemod_plugin_2fsk_sym.png) -You should set the slider to a unity (1) span (+/- 0.5) with no offset. This corresponds to full range in optimal conditions (100%). You can set the slider fully to the left (2) for a +/- 1.0 spn if you don't exactly match these conditions. +4-FSK -
22.1.6: Trigger settings
+![DSD Demodulator plugin GUI 4FSK symbols](../../../doc/img/DSDdemod_plugin_4fsk_sym.png) -You can leave the trigger free running or set it to I linear with a 0 threshold. +This is selected by the transition constellation or symbol synchronization signal toggle (B.13) -

22.2: Symbol synchronization display

+The X input is the discriminator signal and the Y input is the symbol synchronization signal that goes to the estimated maximum discriminator signal level when a zero crossing in the symbol synchronization control signal is detected and goes to mid position ((max - min) / 2) of the discriminator signal when a symbol period starts. -This is selected by the transition constellation or symbol synchronization signal toggle (see 7) +The symbol synchronization control signal is obtained by squaring the discriminator signal and passing it through a narrow second order bandpass filter centered on the symbol rate. Its zero crossing should occur close to the first fourth of a symbol period therefore when synchronization is ideal the Y input should go down to mid position in the first fourth of the symbol period. -![DSD scope](../../../doc/img/DSDdemod_plugin_scope2.png) +Ideally the figure should show a cloud of persistent points at the locations marked by points 0 to 3. Each one of these points represent an ideally decoded symbol. -
22.2.1: IQ linear display
+

B.2: Symbol (Baud) rate

-The I trace (yellow) is the discriminator signal and the Q trace (blue) is the symbol synchronization monitor trace that goes to the estimated maximum discriminator signal level when a zero crossing in the symbol synchronization control signal is detected and goes to mid position ((max - min) / 2) of the discriminator signal when a symbol period starts. +Here you can specify which symbol rate or Baud rate is expected. Choices are: -The symbol synchronization control signal is obtained by squaring the discriminator signal and passing it through a narrow second order bandpass filter centered on the symbol rate. Its zero crossing should occur close to the first fourth of a symbol period therefore when synchronization is ideal the Q trace (blue) should go down to mid position in the first fourth of the symbol period. + - `2.4k`: 2400 S/s used for dPMR and 4800 b/s NXDN + - `4.8k`: 4800 S/s used for 9600 b/s NXDN, DMR, D-Star and YSF. -
22.2.2: Setting the display
+

B.3: Type of frame detected

- - On the combo box you should choose IQ (lin) for the primary display and IQ (pol) for secondary display - - On the display buttons you should choose the first display (1) +This can be one of the following: -
22.2.3: Timing settings
+ - `+DMRd`: non-inverted DMR data frame + - `+DMRv`: non-inverted DMR voice frame + - `-DMRd`: inverted DMR data frame + - `-DMRv`: inverted DMR voice frame + - `+D-STAR`: non-inverted D-Star frame + - `-D-STAR`: inverted D-Star frame + - `+D-STAR_HD`: non-inverted D-Star header frame encountered + - `-D-STAR_HD`: inverted D-Star header frame encountered + - `+dPMR`: non-inverted dPMR non-packet frame + - `+NXDN`: non-inverted NXDN frame + - `-NXDN`: inverted NXDN frame (not likely) + - `+YSF`: non-inverted Yaesu System Fusion frame -You can choose any trace length with the third slider from the left however 100 ms will give you the best view. You may stretch further the display by reducing the full length to 20 ms or less using the first slider. You can move this 20 ms window across the 100 ms trace with the middle slider. +

B.4: Matched filter toggle

+ +Normally you would always want to have a matched filter however on some strong D-Star signals more synchronization points could be obtained without. When engaged the background of the button is lit in orange. -
22.2.4: I gain
+

B.5: Symbol PLL lock indicator

-You should set the slider to a unity (1) span (+/- 0.5) with no offset. This corresponds to full range in optimal conditions (100%). You can set the slider fully to the left (2) for a +/- 1.0 spn if you don't exactly match these conditions. +Since dsdcc version 1.7.1 the symbol synchronization can be done with a PLL fed by a ringing filter (narrow passband) tuned at the symbol rate and itself fed with the squared magnitude of the discriminator signal. For signals strong enough to lock the PLL this works significantly better than with the ringing filter alone that was the only option in versions <= 1.6.0. Version 1.7.0 had the PLL enabled permanently. -
22.2.5: Q gain
+However with marginal signals the ringing filter alone and a few heuristics work better. This is why since DSDcc version 1.7.1 the PLL became optional. -You should set the slider to a unity (1) span (+/- 0.5) with no offset. This corresponds to full range in optimal conditions (100%). You can set the slider fully to the left (2) for a +/- 1.0 spn if you don't exactly match these conditions. +You can use this button to toggle between the two options: -
22.2.6: Trigger settings
+ - with the locker icon in locked position: PLL is engaged + - with the locker icon in unlocked position: PLL is bypassed -You can leave the trigger free running or set it to I linear with a 0 threshold. +When in lock position the button lights itself in green when the PLL lock is acquired. Occasional drops may occur without noticeable impact on decoding. + +

B.6: Symbol synchronization zero crossing hits in %

+ +This is the percentage per symbols for which a valid zero crossing has been detected. The more the better the symbol synchronization is tracked however the zero crossing shifts much not deviate too much from 0 (see next). + +With the PLL engaged the figure should be 100% all the time in presence of a locked signal. Occasional small drops may occur without noticeable impact on decoding. + +

B.7: Zero crossing shift

+ +This is the current (at display polling time) zero crossing shift. It should be the closest to 0 as possible. However some jitter is acceptable for good symbol synchronization: + + - `2400 S/s`: +/- 5 inclusive + - `4800 S/s`: +/- 2 inclusive + +

B.8: Discriminator input signal median level in %

+ +This is the estimated median level (center) of the discriminator input signal in percentage of half the total range. When the signal is correctly aligned in the input range it should be 0 + +

B.9: Discriminator input signal level range in %

+ +This is the estimated discriminator input signal level range (max - min) in percentage of half the total range. For optimal decoding it should be maintained close to 100. + +

B.10 to B.12: Two slot TDMA handling

+ +This is useful for two slot TDMA modes that is only DMR at present. FDMA modes are treated as using slot #1 only. + +![DSD TDMA handling](../../../doc/img/DSDdemod_plugin_tdma.png) + +
B.10 (1): Slot #1 voice select
+ +Toggle button to select slot #1 voice output. When on waves appear on the icon. The icon turns green when voice frames are processed for this slot. For FDMA modes you may want to leave only this toggle on. + +
B.11 (2): Slot #2 voice select
+ +Toggle button to select slot #2 voice output. When on waves appear on the icon. The icon turns green when voice frames are processed for this slot. For FDMA modes you may want to leave this toggle off. + +
B.12 (3): TDMA stereo mode toggle
+ + - When off the icon shows a single loudspeaker. It mixes slot #1 and slot #2 voice as a mono audio signal + - When on the icon shows a pair of loudspeakers. It sends slot #1 voice to the left stereo audio channel and slot #2 to the right one + +For FDMA standards you may want to leave this as mono mode. + +

B.13: Transition constellation or symbol synchronization signal toggle

+ +Using this button you can either: + + - show the transitions constellation + - show a indicative signal about symbol synchronization + - when a zero crossing is detected the signal is set to estimated input discriminator signal maximum value + - when the symbol clock is 0 (start of symbol period) the signal is set to the estimated median point of the input discriminator signal + +

B.14: Trace length

+ +This button tunes the length of the trace displayed on B.1. Units are milliseconds. Default value is 300. + +

B.15: Trace stroke

+ +This button tunes the stroke of the points displayer on B.1. The trace has limited persistence based on alpha blending. This is the 8 bit unsigned integer value of the trace alpha blending. Default value is 100. + +

B.16: Trace decay

+ +This button tunes the persistence decay of the points displayer on B.1. The trace has limited persistence based on alpha blending. This controls the alpha value of the black screen printed at the end of each trace and thus the trace points decay time. The value is 255 minus he displayed value using 8 bit unsigned integers. + + - A value of 0 yields no persistence + - A value of 255 yields infinite persistence + - Default value is 200s + +

B.17: Maximum expected FM deviation

+ +This is the one side deviation in kHz (±) leading to maximum (100%) deviation at the discriminator output. The correct value depends on the maximum deviation imposed by the modulation scheme with some guard margin. In practice you should adjust this value to make the figure on the signal scope fill the entire screen as shown in the screenshots above. The typical deviations by mode for a unit gain (1.0 at B.18) are: + + - DMR: ±5.4k + - dPMR: ±2.7k + - D-Star: ±3.5k + - YSF: ±7.0k + - NXDN: ±2.7k + +

B.18: Gain after discriminator

+ +This is the gain applied to the output of the discriminator before the decoder. Normally this would be set at unit gain 1.0 while the FM deviation is adjusted. However this can be used to extend the range of FM adjustment. diff --git a/plugins/channelrx/demodlora/CMakeLists.txt b/plugins/channelrx/demodlora/CMakeLists.txt index d8076dac0..a2ced900c 100644 --- a/plugins/channelrx/demodlora/CMakeLists.txt +++ b/plugins/channelrx/demodlora/CMakeLists.txt @@ -43,6 +43,6 @@ target_link_libraries(demodlora sdrgui ) -qt5_use_modules(demodlora Core Widgets) +target_link_libraries(demodlora Qt5::Core Qt5::Widgets) install(TARGETS demodlora DESTINATION lib/plugins/channelrx) diff --git a/plugins/channelrx/demodlora/demodlora.pro b/plugins/channelrx/demodlora/demodlora.pro index 29438cf8f..9f96323bc 100644 --- a/plugins/channelrx/demodlora/demodlora.pro +++ b/plugins/channelrx/demodlora/demodlora.pro @@ -18,6 +18,7 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui diff --git a/plugins/channelrx/demodlora/lorademod.cpp b/plugins/channelrx/demodlora/lorademod.cpp index b25fe12e3..87d9882b3 100644 --- a/plugins/channelrx/demodlora/lorademod.cpp +++ b/plugins/channelrx/demodlora/lorademod.cpp @@ -32,7 +32,7 @@ MESSAGE_CLASS_DEFINITION(LoRaDemod::MsgConfigureLoRaDemod, Message) MESSAGE_CLASS_DEFINITION(LoRaDemod::MsgConfigureChannelizer, Message) -const QString LoRaDemod::m_channelIdURI = "de.maintech.sdrangelove.channel.lora"; +const QString LoRaDemod::m_channelIdURI = "sdrangel.channel.lorademod"; const QString LoRaDemod::m_channelId = "LoRaDemod"; LoRaDemod::LoRaDemod(DeviceSourceAPI* deviceAPI) : diff --git a/plugins/channelrx/demodlora/lorademodgui.ui b/plugins/channelrx/demodlora/lorademodgui.ui index ef9f95270..c17a2a6c6 100644 --- a/plugins/channelrx/demodlora/lorademodgui.ui +++ b/plugins/channelrx/demodlora/lorademodgui.ui @@ -12,7 +12,7 @@ - Sans Serif + Liberation Sans 9 diff --git a/plugins/channelrx/demodlora/loraplugin.cpp b/plugins/channelrx/demodlora/loraplugin.cpp index e889c995b..0a25300a4 100644 --- a/plugins/channelrx/demodlora/loraplugin.cpp +++ b/plugins/channelrx/demodlora/loraplugin.cpp @@ -7,7 +7,7 @@ const PluginDescriptor LoRaPlugin::m_pluginDescriptor = { QString("LoRa Demodulator"), - QString("3.12.0"), + QString("3.14.5"), QString("(c) 2015 John Greb"), QString("http://www.maintech.de"), true, diff --git a/plugins/channelrx/demodlora/loraplugin.h b/plugins/channelrx/demodlora/loraplugin.h index 46a0a0d39..75a812af0 100644 --- a/plugins/channelrx/demodlora/loraplugin.h +++ b/plugins/channelrx/demodlora/loraplugin.h @@ -10,7 +10,7 @@ class BasebandSampleSink; class LoRaPlugin : public QObject, PluginInterface { Q_OBJECT Q_INTERFACES(PluginInterface) - Q_PLUGIN_METADATA(IID "de.maintech.sdrangelove.channel.lora") + Q_PLUGIN_METADATA(IID "sdrangel.channel.lorademod") public: explicit LoRaPlugin(QObject* parent = NULL); diff --git a/plugins/channelrx/demodnfm/CMakeLists.txt b/plugins/channelrx/demodnfm/CMakeLists.txt index 3efccfd31..3e405ea4f 100644 --- a/plugins/channelrx/demodnfm/CMakeLists.txt +++ b/plugins/channelrx/demodnfm/CMakeLists.txt @@ -47,6 +47,6 @@ target_link_libraries(demodnfm swagger ) -qt5_use_modules(demodnfm Core Widgets) +target_link_libraries(demodnfm Qt5::Core Qt5::Widgets) install(TARGETS demodnfm DESTINATION lib/plugins/channelrx) \ No newline at end of file diff --git a/plugins/channelrx/demodnfm/demodnfm.pro b/plugins/channelrx/demodnfm/demodnfm.pro index f3f274eb8..347e194f8 100644 --- a/plugins/channelrx/demodnfm/demodnfm.pro +++ b/plugins/channelrx/demodnfm/demodnfm.pro @@ -18,6 +18,7 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client diff --git a/plugins/channelrx/demodnfm/nfmdemod.cpp b/plugins/channelrx/demodnfm/nfmdemod.cpp index 0a097bee0..2002d2463 100644 --- a/plugins/channelrx/demodnfm/nfmdemod.cpp +++ b/plugins/channelrx/demodnfm/nfmdemod.cpp @@ -22,27 +22,29 @@ #include "SWGChannelSettings.h" #include "SWGNFMDemodSettings.h" +#include "SWGChannelReport.h" +#include "SWGNFMDemodReport.h" #include "dsp/downchannelizer.h" #include "util/stepfunctions.h" +#include "util/db.h" #include "audio/audiooutput.h" #include "dsp/dspengine.h" #include "dsp/threadedbasebandsamplesink.h" #include "dsp/dspcommands.h" #include "device/devicesourceapi.h" -#include "audio/audionetsink.h" -#include "nfmdemodgui.h" #include "nfmdemod.h" MESSAGE_CLASS_DEFINITION(NFMDemod::MsgConfigureNFMDemod, Message) MESSAGE_CLASS_DEFINITION(NFMDemod::MsgConfigureChannelizer, Message) MESSAGE_CLASS_DEFINITION(NFMDemod::MsgReportCTCSSFreq, Message) -const QString NFMDemod::m_channelIdURI = "de.maintech.sdrangelove.channel.nfm"; +const QString NFMDemod::m_channelIdURI = "sdrangel.channel.nfmdemod"; const QString NFMDemod::m_channelId = "NFMDemod"; static const double afSqTones[2] = {1000.0, 6000.0}; // {1200.0, 8000.0}; +static const double afSqTones_lowrate[2] = {1000.0, 3500.0}; const int NFMDemod::m_udpBlockSize = 512; NFMDemod::NFMDemod(DeviceSourceAPI *devieAPI) : @@ -54,8 +56,7 @@ NFMDemod::NFMDemod(DeviceSourceAPI *devieAPI) : m_ctcssIndex(0), m_sampleCount(0), m_squelchCount(0), - m_squelchGate(2), - m_audioMute(false), + m_squelchGate(4800), m_squelchLevel(-990), m_squelchOpen(false), m_afSquelchOpen(false), @@ -63,11 +64,12 @@ NFMDemod::NFMDemod(DeviceSourceAPI *devieAPI) : m_magsqSum(0.0f), m_magsqPeak(0.0f), m_magsqCount(0), - m_afSquelch(2, afSqTones), - m_fmExcursion(2400), + m_afSquelch(), + m_squelchDelayLine(24000), m_audioFifo(48000), m_settingsMutex(QMutex::Recursive) { + qDebug("NFMDemod::NFMDemod"); setObjectName(m_channelId); m_audioBuffer.resize(1<<14); @@ -75,37 +77,34 @@ NFMDemod::NFMDemod(DeviceSourceAPI *devieAPI) : m_agcLevel = 1.0; - m_ctcssDetector.setCoefficients(3000, 6000.0); // 0.5s / 2 Hz resolution - m_afSquelch.setCoefficients(24, 600, 48000.0, 200, 0); // 0.5ms test period, 300ms average span, 48kS/s SR, 100ms attack, no decay + DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo, getInputMessageQueue()); + m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate(); + m_discriCompensation = (m_audioSampleRate/48000.0f); + m_discriCompensation *= sqrt(m_discriCompensation); - DSPEngine::instance()->addAudioSink(&m_audioFifo); - m_audioNetSink = new AudioNetSink(this); - m_audioNetSink->setDestination(m_settings.m_udpAddress, m_settings.m_udpPort); + m_ctcssDetector.setCoefficients(m_audioSampleRate/16, m_audioSampleRate/8.0f); // 0.5s / 2 Hz resolution + m_afSquelch.setCoefficients(m_audioSampleRate/2000, 600, m_audioSampleRate, 200, 0, afSqTones); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay + + m_lowpass.create(301, m_audioSampleRate, 250.0); + + applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); m_channelizer = new DownChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); m_deviceAPI->addThreadedSink(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - - applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); - applySettings(m_settings, true); } NFMDemod::~NFMDemod() { - DSPEngine::instance()->removeAudioSink(&m_audioFifo); - delete m_audioNetSink; + DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo); m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSink(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; } -bool NFMDemod::isAudioNetSinkRTPCapable() const -{ - return m_audioNetSink && m_audioNetSink->isRTPCapable(); -} - float arctan2(Real y, Real x) { Real coeff_1 = M_PI / 4; @@ -179,21 +178,28 @@ void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto if (m_settings.m_deltaSquelch) { - if (m_afSquelch.analyze(demod)) { - m_afSquelchOpen = m_afSquelch.evaluate() ? m_squelchGate + 480 : 0; + if (m_afSquelch.analyze(demod * m_discriCompensation)) + { + m_afSquelchOpen = m_afSquelch.evaluate(); // ? m_squelchGate + m_squelchDecay : 0; + + if (!m_afSquelchOpen) { + m_squelchDelayLine.zeroBack(m_audioSampleRate/10); // zero out evaluation period + } } if (m_afSquelchOpen) { - if (m_squelchCount < m_squelchGate + 480) - { + m_squelchDelayLine.write(demod * m_discriCompensation); + + if (m_squelchCount < 2*m_squelchGate) { m_squelchCount++; } } else { - if (m_squelchCount > 0) - { + m_squelchDelayLine.write(0); + + if (m_squelchCount > 0) { m_squelchCount--; } } @@ -202,15 +208,17 @@ void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto { if ((Real) m_movingAverage < m_squelchLevel) { - if (m_squelchCount > 0) - { + m_squelchDelayLine.write(0); + + if (m_squelchCount > 0) { m_squelchCount--; } } else { - if (m_squelchCount < m_squelchGate + 480) - { + m_squelchDelayLine.write(demod * m_discriCompensation); + + if (m_squelchCount < 2*m_squelchGate) { m_squelchCount++; } } @@ -218,86 +226,83 @@ void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto m_squelchOpen = (m_squelchCount > m_squelchGate); - if ((m_squelchOpen) && !m_settings.m_audioMute) + if (m_settings.m_audioMute) { - if (m_settings.m_ctcssOn) + sample = 0; + } + else + { + if (m_squelchOpen) { - Real ctcss_sample = m_lowpass.filter(demod); - - if ((m_sampleCount & 7) == 7) // decimate 48k -> 6k + if (m_settings.m_ctcssOn) { - if (m_ctcssDetector.analyze(&ctcss_sample)) - { - int maxToneIndex; + Real ctcss_sample = m_lowpass.filter(demod * m_discriCompensation); - if (m_ctcssDetector.getDetectedTone(maxToneIndex)) + if ((m_sampleCount & 7) == 7) // decimate 48k -> 6k + { + if (m_ctcssDetector.analyze(&ctcss_sample)) { - if (maxToneIndex+1 != m_ctcssIndex) + int maxToneIndex; + + if (m_ctcssDetector.getDetectedTone(maxToneIndex)) { - if (getMessageQueueToGUI()) { - MsgReportCTCSSFreq *msg = MsgReportCTCSSFreq::create(m_ctcssDetector.getToneSet()[maxToneIndex]); - getMessageQueueToGUI()->push(msg); + if (maxToneIndex+1 != m_ctcssIndex) + { + if (getMessageQueueToGUI()) { + MsgReportCTCSSFreq *msg = MsgReportCTCSSFreq::create(m_ctcssDetector.getToneSet()[maxToneIndex]); + getMessageQueueToGUI()->push(msg); + } + m_ctcssIndex = maxToneIndex+1; } - m_ctcssIndex = maxToneIndex+1; } - } - else - { - if (m_ctcssIndex != 0) + else { - if (getMessageQueueToGUI()) { - MsgReportCTCSSFreq *msg = MsgReportCTCSSFreq::create(0); - getMessageQueueToGUI()->push(msg); + if (m_ctcssIndex != 0) + { + if (getMessageQueueToGUI()) { + MsgReportCTCSSFreq *msg = MsgReportCTCSSFreq::create(0); + getMessageQueueToGUI()->push(msg); + } + m_ctcssIndex = 0; } - m_ctcssIndex = 0; } } } } - } - if (m_settings.m_ctcssOn && m_ctcssIndexSelected && (m_ctcssIndexSelected != m_ctcssIndex)) - { - sample = 0; - if (m_settings.m_copyAudioToUDP) { - m_audioNetSink->write(0); + if (m_settings.m_ctcssOn && m_ctcssIndexSelected && (m_ctcssIndexSelected != m_ctcssIndex)) + { + sample = 0; + } + else + { + sample = m_bandpass.filter(m_squelchDelayLine.readBack(m_squelchGate)) * m_settings.m_volume; } } else { - demod = m_bandpass.filter(demod); - Real squelchFactor = StepFunctions::smootherstep((Real) (m_squelchCount - m_squelchGate) / 480.0f); - sample = demod * m_settings.m_volume * squelchFactor; - if (m_settings.m_copyAudioToUDP) { - m_audioNetSink->write(demod * 5.0f * squelchFactor); - } - } - } - else - { - if (m_ctcssIndex != 0) - { - if (getMessageQueueToGUI()) { - MsgReportCTCSSFreq *msg = MsgReportCTCSSFreq::create(0); - getMessageQueueToGUI()->push(msg); + if (m_ctcssIndex != 0) + { + if (getMessageQueueToGUI()) { + MsgReportCTCSSFreq *msg = MsgReportCTCSSFreq::create(0); + getMessageQueueToGUI()->push(msg); + } + + m_ctcssIndex = 0; } - m_ctcssIndex = 0; - } - - sample = 0; - if (m_settings.m_copyAudioToUDP) { - m_audioNetSink->write(0); + sample = 0; } } + m_audioBuffer[m_audioBufferFill].l = sample; m_audioBuffer[m_audioBufferFill].r = sample; ++m_audioBufferFill; if (m_audioBufferFill >= m_audioBuffer.size()) { - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 10); + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); if (res != m_audioBufferFill) { @@ -313,7 +318,7 @@ void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto if (m_audioBufferFill > 0) { - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 10); + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); if (res != m_audioBufferFill) { @@ -376,6 +381,27 @@ bool NFMDemod::handleMessage(const Message& cmd) return true; } + else if (BasebandSampleSink::MsgThreadedSink::match(cmd)) + { + BasebandSampleSink::MsgThreadedSink& cfg = (BasebandSampleSink::MsgThreadedSink&) cmd; + const QThread *thread = cfg.getThread(); + qDebug("NFMDemod::handleMessage: BasebandSampleSink::MsgThreadedSink: %p", thread); + return true; + } + else if (DSPConfigureAudio::match(cmd)) + { + DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd; + uint32_t sampleRate = cfg.getSampleRate(); + + qDebug() << "NFMDemod::handleMessage: DSPConfigureAudio:" + << " sampleRate: " << sampleRate; + + if (sampleRate != m_audioSampleRate) { + applyAudioSampleRate(sampleRate); + } + + return true; + } else if (DSPSignalNotification::match(cmd)) { return true; @@ -386,6 +412,44 @@ bool NFMDemod::handleMessage(const Message& cmd) } } +void NFMDemod::applyAudioSampleRate(int sampleRate) +{ + qDebug("NFMDemod::applyAudioSampleRate: %d", sampleRate); + + MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( + sampleRate, m_settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(channelConfigMsg); + + m_settingsMutex.lock(); + + m_interpolator.create(16, m_inputSampleRate, m_settings.m_rfBandwidth / 2.2f); + m_interpolatorDistanceRemain = 0; + m_interpolatorDistance = (Real) m_inputSampleRate / (Real) sampleRate; + m_lowpass.create(301, sampleRate, 250.0); + m_bandpass.create(301, sampleRate, 300.0, m_settings.m_afBandwidth); + m_squelchGate = (sampleRate / 100) * m_settings.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate + m_squelchCount = 0; // reset squelch open counter + m_ctcssDetector.setCoefficients(sampleRate/16, sampleRate/8.0f); // 0.5s / 2 Hz resolution + + if (sampleRate < 16000) { + m_afSquelch.setCoefficients(sampleRate/2000, 600, sampleRate, 200, 0, afSqTones_lowrate); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay + + } else { + m_afSquelch.setCoefficients(sampleRate/2000, 600, sampleRate, 200, 0, afSqTones); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay + } + + m_discriCompensation = (sampleRate/48000.0f); + m_discriCompensation *= sqrt(m_discriCompensation); + + m_phaseDiscri.setFMScaling(sampleRate / static_cast(m_settings.m_fmDeviation)); + m_audioFifo.setSize(sampleRate); + m_squelchDelayLine.resize(sampleRate/2); + + m_settingsMutex.unlock(); + + m_audioSampleRate = sampleRate; +} + void NFMDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force) { qDebug() << "NFMDemod::applyChannelSettings:" @@ -401,9 +465,9 @@ void NFMDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffse if ((inputSampleRate != m_inputSampleRate) || force) { m_settingsMutex.lock(); - m_interpolator.create(16, inputSampleRate, m_settings.m_rfBandwidth / 2.2); + m_interpolator.create(16, inputSampleRate, m_settings.m_rfBandwidth / 2.2f); m_interpolatorDistanceRemain = 0; - m_interpolatorDistance = (Real) inputSampleRate / (Real) m_settings.m_audioSampleRate; + m_interpolatorDistance = (Real) inputSampleRate / (Real) m_audioSampleRate; m_settingsMutex.unlock(); } @@ -425,40 +489,33 @@ void NFMDemod::applySettings(const NFMDemodSettings& settings, bool force) << " m_ctcssIndex: " << settings.m_ctcssIndex << " m_ctcssOn: " << settings.m_ctcssOn << " m_audioMute: " << settings.m_audioMute - << " m_copyAudioToUDP: " << settings.m_copyAudioToUDP - << " m_copyAudioUseRTP: " << settings.m_copyAudioUseRTP - << " m_udpAddress: " << settings.m_udpAddress - << " m_udpPort: " << settings.m_udpPort + << " m_audioDeviceName: " << settings.m_audioDeviceName << " force: " << force; - if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || - (settings.m_audioSampleRate != m_settings.m_audioSampleRate) || force) + if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) { m_settingsMutex.lock(); m_interpolator.create(16, m_inputSampleRate, settings.m_rfBandwidth / 2.2); m_interpolatorDistanceRemain = 0; - m_interpolatorDistance = (Real) m_inputSampleRate / (Real) settings.m_audioSampleRate; + m_interpolatorDistance = (Real) m_inputSampleRate / (Real) m_audioSampleRate; m_settingsMutex.unlock(); } - if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || - (settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) + if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) { - m_phaseDiscri.setFMScaling((8.0f*settings.m_rfBandwidth) / static_cast(settings.m_fmDeviation)); // integrate 4x factor + m_phaseDiscri.setFMScaling((8.0f*m_audioSampleRate) / static_cast(settings.m_fmDeviation)); // integrate 4x factor } - if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || - (settings.m_audioSampleRate != m_settings.m_audioSampleRate) || force) + if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || force) { m_settingsMutex.lock(); - m_lowpass.create(301, settings.m_audioSampleRate, 250.0); - m_bandpass.create(301, settings.m_audioSampleRate, 300.0, settings.m_afBandwidth); + m_bandpass.create(301, m_audioSampleRate, 300.0, settings.m_afBandwidth); m_settingsMutex.unlock(); } if ((settings.m_squelchGate != m_settings.m_squelchGate) || force) { - m_squelchGate = 480 * settings.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate + m_squelchGate = (m_audioSampleRate / 100) * settings.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate m_squelchCount = 0; // reset squelch open counter } @@ -466,48 +523,35 @@ void NFMDemod::applySettings(const NFMDemodSettings& settings, bool force) (settings.m_deltaSquelch != m_settings.m_deltaSquelch) || force) { if (settings.m_deltaSquelch) - { // input is a value in negative millis - m_squelchLevel = (- settings.m_squelch) / 1000.0; + { // input is a value in negative centis + m_squelchLevel = (- settings.m_squelch) / 100.0; m_afSquelch.setThreshold(m_squelchLevel); m_afSquelch.reset(); } else - { // input is a value in centi-Bels - m_squelchLevel = std::pow(10.0, settings.m_squelch / 100.0); + { // input is a value in deci-Bels + m_squelchLevel = std::pow(10.0, settings.m_squelch / 10.0); m_movingAverage.reset(); } m_squelchCount = 0; // reset squelch open counter } - if ((settings.m_udpAddress != m_settings.m_udpAddress) - || (settings.m_udpPort != m_settings.m_udpPort) || force) - { - m_audioNetSink->setDestination(settings.m_udpAddress, settings.m_udpPort); - } - if ((settings.m_ctcssIndex != m_settings.m_ctcssIndex) || force) { setSelectedCtcssIndex(settings.m_ctcssIndex); } - if ((settings.m_copyAudioUseRTP != m_settings.m_copyAudioUseRTP) || force) + if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) { - if (settings.m_copyAudioUseRTP) - { - if (m_audioNetSink->selectType(AudioNetSink::SinkRTP)) { - qDebug("NFMDemod::applySettings: set audio sink to RTP mode"); - } else { - qWarning("NFMDemod::applySettings: RTP support for audio sink not available. Fall back too UDP"); - } - } - else - { - if (m_audioNetSink->selectType(AudioNetSink::SinkUDP)) { - qDebug("NFMDemod::applySettings: set audio sink to UDP mode"); - } else { - qWarning("NFMDemod::applySettings: failed to set audio sink to UDP mode"); - } + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName); + //qDebug("AMDemod::applySettings: audioDeviceName: %s audioDeviceIndex: %d", qPrintable(settings.m_audioDeviceName), audioDeviceIndex); + audioDeviceManager->addAudioSink(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex); + uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex); + + if (m_audioSampleRate != audioSampleRate) { + applyAudioSampleRate(audioSampleRate); } } @@ -530,7 +574,7 @@ bool NFMDemod::deserialize(const QByteArray& data) } NFMDemod::MsgConfigureChannelizer* channelConfigMsg = NFMDemod::MsgConfigureChannelizer::create( - 48000, m_settings.m_inputFrequencyOffset); + m_audioSampleRate, m_settings.m_inputFrequencyOffset); m_inputMessageQueue.push(channelConfigMsg); MsgConfigureNFMDemod *msg = MsgConfigureNFMDemod::create(m_settings, true); @@ -564,15 +608,6 @@ int NFMDemod::webapiSettingsPutPatch( if (channelSettingsKeys.contains("audioMute")) { settings.m_audioMute = response.getNfmDemodSettings()->getAudioMute() != 0; } - if (channelSettingsKeys.contains("audioSampleRate")) { - settings.m_audioSampleRate = response.getNfmDemodSettings()->getAudioSampleRate(); - } - if (channelSettingsKeys.contains("copyAudioToUDP")) { - settings.m_copyAudioToUDP = response.getNfmDemodSettings()->getCopyAudioToUdp() != 0; - } - if (channelSettingsKeys.contains("copyAudioUseRTP")) { - settings.m_copyAudioUseRTP = response.getNfmDemodSettings()->getCopyAudioUseRtp() != 0; - } if (channelSettingsKeys.contains("ctcssIndex")) { settings.m_ctcssIndex = response.getNfmDemodSettings()->getCtcssIndex(); } @@ -605,20 +640,17 @@ int NFMDemod::webapiSettingsPutPatch( if (channelSettingsKeys.contains("title")) { settings.m_title = *response.getNfmDemodSettings()->getTitle(); } - if (channelSettingsKeys.contains("udpAddress")) { - settings.m_udpAddress = *response.getNfmDemodSettings()->getUdpAddress(); - } - if (channelSettingsKeys.contains("udpPort")) { - settings.m_udpPort = response.getNfmDemodSettings()->getUdpPort(); - } if (channelSettingsKeys.contains("volume")) { settings.m_volume = response.getNfmDemodSettings()->getVolume(); } + if (channelSettingsKeys.contains("audioDeviceName")) { + settings.m_audioDeviceName = *response.getNfmDemodSettings()->getAudioDeviceName(); + } if (frequencyOffsetChanged) { MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( - 48000, settings.m_inputFrequencyOffset); + m_audioSampleRate, settings.m_inputFrequencyOffset); m_inputMessageQueue.push(channelConfigMsg); } @@ -636,13 +668,20 @@ int NFMDemod::webapiSettingsPutPatch( return 200; } +int NFMDemod::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setNfmDemodReport(new SWGSDRangel::SWGNFMDemodReport()); + response.getNfmDemodReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + void NFMDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const NFMDemodSettings& settings) { response.getNfmDemodSettings()->setAfBandwidth(settings.m_afBandwidth); response.getNfmDemodSettings()->setAudioMute(settings.m_audioMute ? 1 : 0); - response.getNfmDemodSettings()->setAudioSampleRate(settings.m_audioSampleRate); - response.getNfmDemodSettings()->setCopyAudioToUdp(settings.m_copyAudioToUDP ? 1 : 0); - response.getNfmDemodSettings()->setCopyAudioUseRtp(settings.m_copyAudioUseRTP ? 1 : 0); response.getNfmDemodSettings()->setCtcssIndex(settings.m_ctcssIndex); response.getNfmDemodSettings()->setCtcssOn(settings.m_ctcssOn ? 1 : 0); response.getNfmDemodSettings()->setDeltaSquelch(settings.m_deltaSquelch ? 1 : 0); @@ -652,7 +691,6 @@ void NFMDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& resp response.getNfmDemodSettings()->setRgbColor(settings.m_rgbColor); response.getNfmDemodSettings()->setSquelch(settings.m_squelch); response.getNfmDemodSettings()->setSquelchGate(settings.m_squelchGate); - response.getNfmDemodSettings()->setUdpPort(settings.m_udpPort); response.getNfmDemodSettings()->setVolume(settings.m_volume); if (response.getNfmDemodSettings()->getTitle()) { @@ -661,10 +699,22 @@ void NFMDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& resp response.getNfmDemodSettings()->setTitle(new QString(settings.m_title)); } - if (response.getNfmDemodSettings()->getUdpAddress()) { - *response.getNfmDemodSettings()->getUdpAddress() = settings.m_udpAddress; + if (response.getNfmDemodSettings()->getAudioDeviceName()) { + *response.getNfmDemodSettings()->getAudioDeviceName() = settings.m_audioDeviceName; } else { - response.getNfmDemodSettings()->setUdpAddress(new QString(settings.m_udpAddress)); + response.getNfmDemodSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName)); } } +void NFMDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + double magsqAvg, magsqPeak; + int nbMagsqSamples; + getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples); + + response.getNfmDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg)); + response.getNfmDemodReport()->setCtcssTone(m_settings.m_ctcssOn ? (m_ctcssIndex ? 0 : m_ctcssDetector.getToneSet()[m_ctcssIndex-1]) : 0); + response.getNfmDemodReport()->setSquelch(m_squelchOpen ? 1 : 0); + response.getNfmDemodReport()->setAudioSampleRate(m_audioSampleRate); + response.getNfmDemodReport()->setChannelSampleRate(m_inputSampleRate); +} diff --git a/plugins/channelrx/demodnfm/nfmdemod.h b/plugins/channelrx/demodnfm/nfmdemod.h index 7fe8d2569..ae1ec8c9b 100644 --- a/plugins/channelrx/demodnfm/nfmdemod.h +++ b/plugins/channelrx/demodnfm/nfmdemod.h @@ -34,13 +34,13 @@ #include "audio/audiofifo.h" #include "util/message.h" #include "util/movingaverage.h" +#include "util/doublebufferfifo.h" #include "nfmdemodsettings.h" class DeviceSourceAPI; class ThreadedBasebandSampleSink; class DownChannelizer; -class AudioNetSink; class NFMDemod : public BasebandSampleSink, public ChannelSinkAPI { public: @@ -136,6 +136,10 @@ public: SWGSDRangel::SWGChannelSettings& response, QString& errorMessage); + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + const Real *getCtcssToneSet(int& nbTones) const { nbTones = m_ctcssDetector.getNTones(); return m_ctcssDetector.getToneSet(); @@ -150,21 +154,36 @@ public: void getMagSqLevels(double& avg, double& peak, int& nbSamples) { - avg = m_magsqCount == 0 ? 1e-10 : m_magsqSum / m_magsqCount; - m_magsq = avg; - peak = m_magsqPeak == 0.0 ? 1e-10 : m_magsqPeak; + if (m_magsqCount > 0) + { + m_magsq = m_magsqSum / m_magsqCount; + m_magSqLevelStore.m_magsq = m_magsq; + m_magSqLevelStore.m_magsqPeak = m_magsqPeak; + } + + avg = m_magSqLevelStore.m_magsq; + peak = m_magSqLevelStore.m_magsqPeak; nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; + m_magsqSum = 0.0f; m_magsqPeak = 0.0f; m_magsqCount = 0; } - bool isAudioNetSinkRTPCapable() const; - static const QString m_channelIdURI; static const QString m_channelId; private: + struct MagSqLevelsStore + { + MagSqLevelsStore() : + m_magsq(1e-12), + m_magsqPeak(1e-12) + {} + double m_magsq; + double m_magsqPeak; + }; + enum RateState { RSInitialFill, RSRunning @@ -177,6 +196,8 @@ private: int m_inputSampleRate; int m_inputFrequencyOffset; NFMDemodSettings m_settings; + uint32_t m_audioSampleRate; + float m_discriCompensation; //!< compensation factor that depends on audio rate (1 for 48 kS/s) bool m_running; NCO m_nco; @@ -191,7 +212,6 @@ private: int m_sampleCount; int m_squelchCount; int m_squelchGate; - bool m_audioMute; Real m_squelchLevel; bool m_squelchOpen; @@ -200,23 +220,16 @@ private: double m_magsqSum; double m_magsqPeak; int m_magsqCount; + MagSqLevelsStore m_magSqLevelStore; - Real m_lastArgument; - //Complex m_m1Sample; - //Complex m_m2Sample; MovingAverageUtil m_movingAverage; AFSquelch m_afSquelch; Real m_agcLevel; // AGC will aim to this level - Real m_agcFloor; // AGC will not go below this level - - Real m_fmExcursion; - //Real m_fmScaling; + DoubleBufferFIFO m_squelchDelayLine; AudioVector m_audioBuffer; uint m_audioBufferFill; - AudioFifo m_audioFifo; - AudioNetSink *m_audioNetSink; QMutex m_settingsMutex; @@ -227,7 +240,9 @@ private: // void apply(bool force = false); void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); void applySettings(const NFMDemodSettings& settings, bool force = false); + void applyAudioSampleRate(int sampleRate); void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const NFMDemodSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); }; #endif // INCLUDE_NFMDEMOD_H diff --git a/plugins/channelrx/demodnfm/nfmdemodgui.cpp b/plugins/channelrx/demodnfm/nfmdemodgui.cpp index a2d27eed7..98c2361d4 100644 --- a/plugins/channelrx/demodnfm/nfmdemodgui.cpp +++ b/plugins/channelrx/demodnfm/nfmdemodgui.cpp @@ -7,11 +7,12 @@ #include #include "ui_nfmdemodgui.h" -#include "dsp/nullsink.h" #include "plugin/pluginapi.h" #include "util/simpleserializer.h" #include "util/db.h" #include "gui/basicchannelsettingsdialog.h" +#include "gui/crightclickenabler.h" +#include "gui/audioselectdialog.h" #include "dsp/dspengine.h" #include "mainwindow.h" #include "nfmdemod.h" @@ -76,7 +77,7 @@ bool NFMDemodGUI::handleMessage(const Message& message) { if (NFMDemod::MsgReportCTCSSFreq::match(message)) { - qDebug("NFMDemodGUI::handleMessage: NFMDemod::MsgReportCTCSSFreq"); + //qDebug("NFMDemodGUI::handleMessage: NFMDemod::MsgReportCTCSSFreq"); NFMDemod::MsgReportCTCSSFreq& report = (NFMDemod::MsgReportCTCSSFreq&) message; setCtcssFreq(report.getFrequency()); //qDebug("NFMDemodGUI::handleMessage: MsgReportCTCSSFreq: %f", report.getFrequency()); @@ -163,15 +164,15 @@ void NFMDemodGUI::on_deltaSquelch_toggled(bool checked) { if (checked) { - ui->squelchText->setText(QString("%1").arg((-ui->squelch->value()) / 10.0, 0, 'f', 1)); + ui->squelchText->setText(QString("%1").arg((-ui->squelch->value()) / 1.0, 0, 'f', 0)); ui->squelchText->setToolTip(tr("Squelch AF balance threshold (%)")); ui->squelch->setToolTip(tr("Squelch AF balance threshold (%)")); } else { - ui->squelchText->setText(QString("%1").arg(ui->squelch->value() / 10.0, 0, 'f', 1)); + ui->squelchText->setText(QString("%1").arg(ui->squelch->value() / 1.0, 0, 'f', 0)); ui->squelchText->setToolTip(tr("Squelch power threshold (dB)")); - ui->squelch->setToolTip(tr("Squelch AF balance threshold (%)")); + ui->squelch->setToolTip(tr("Squelch power threshold (dB)")); } m_settings.m_deltaSquelch = checked; applySettings(); @@ -181,13 +182,15 @@ void NFMDemodGUI::on_squelch_valueChanged(int value) { if (ui->deltaSquelch->isChecked()) { - ui->squelchText->setText(QString("%1").arg(-value / 10.0, 0, 'f', 1)); - ui->squelchText->setToolTip(tr("Squelch deviation threshold (%)")); + ui->squelchText->setText(QString("%1").arg(-value / 1.0, 0, 'f', 0)); + ui->squelchText->setToolTip(tr("Squelch AF balance threshold (%)")); + ui->squelch->setToolTip(tr("Squelch AF balance threshold (%)")); } else { - ui->squelchText->setText(QString("%1").arg(value / 10.0, 0, 'f', 1)); + ui->squelchText->setText(QString("%1").arg(value / 1.0, 0, 'f', 0)); ui->squelchText->setToolTip(tr("Squelch power threshold (dB)")); + ui->squelch->setToolTip(tr("Squelch power threshold (dB)")); } m_settings.m_squelch = value * 1.0; applySettings(); @@ -205,18 +208,6 @@ void NFMDemodGUI::on_audioMute_toggled(bool checked) applySettings(); } -void NFMDemodGUI::on_copyAudioToUDP_toggled(bool checked) -{ - m_settings.m_copyAudioToUDP = checked; - applySettings(); -} - -void NFMDemodGUI::on_useRTP_toggled(bool checked) -{ - m_settings.m_copyAudioUseRTP = checked; - applySettings(); -} - void NFMDemodGUI::on_ctcss_currentIndexChanged(int index) { m_settings.m_ctcssIndex = index; @@ -238,14 +229,11 @@ void NFMDemodGUI::onMenuDialogCalled(const QPoint &p) dialog.exec(); m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); - m_settings.m_udpAddress = m_channelMarker.getUDPAddress(), - m_settings.m_udpPort = m_channelMarker.getUDPSendPort(), m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); m_settings.m_title = m_channelMarker.getTitle(); setWindowTitle(m_settings.m_title); setTitleColor(m_settings.m_rgbColor); - displayUDPAddress(); applySettings(); } @@ -272,6 +260,9 @@ NFMDemodGUI::NFMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); + CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->audioMute); + connect(audioMuteRightClickEnabler, SIGNAL(rightClick()), this, SLOT(audioSelect())); + blockApplySettings(true); ui->rfBW->clear(); @@ -304,8 +295,6 @@ NFMDemodGUI::NFMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban m_channelMarker.setBandwidth(5000); m_channelMarker.setCenterFrequency(0); m_channelMarker.setTitle("NFM Demodulator"); - m_channelMarker.setUDPAddress("127.0.0.1"); - m_channelMarker.setUDPSendPort(9999); m_channelMarker.blockSignals(false); m_channelMarker.setVisible(true); // activate signal on the last setting only @@ -321,10 +310,6 @@ NFMDemodGUI::NFMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban QChar delta = QChar(0x94, 0x03); ui->deltaSquelch->setText(delta); - if (!m_nfmDemod->isAudioNetSinkRTPCapable()) { - ui->useRTP->hide(); - } - connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); displaySettings(); @@ -364,7 +349,6 @@ void NFMDemodGUI::displaySettings() setTitleColor(m_settings.m_rgbColor); setWindowTitle(m_channelMarker.getTitle()); - displayUDPAddress(); blockApplySettings(true); @@ -386,35 +370,25 @@ void NFMDemodGUI::displaySettings() if (m_settings.m_deltaSquelch) { - ui->squelchText->setText(QString("%1").arg((-m_settings.m_squelch) / 10.0, 0, 'f', 1)); + ui->squelchText->setText(QString("%1").arg((-m_settings.m_squelch) / 1.0, 0, 'f', 0)); ui->squelchText->setToolTip(tr("Squelch AF balance threshold (%)")); ui->squelch->setToolTip(tr("Squelch AF balance threshold (%)")); } else { - ui->squelchText->setText(QString("%1").arg(m_settings.m_squelch / 10.0, 0, 'f', 1)); + ui->squelchText->setText(QString("%1").arg(m_settings.m_squelch / 1.0, 0, 'f', 0)); ui->squelchText->setToolTip(tr("Squelch power threshold (dB)")); - ui->squelch->setToolTip(tr("Squelch AF balance threshold (%)")); + ui->squelch->setToolTip(tr("Squelch power threshold (dB)")); } ui->ctcssOn->setChecked(m_settings.m_ctcssOn); ui->audioMute->setChecked(m_settings.m_audioMute); - ui->copyAudioToUDP->setChecked(m_settings.m_copyAudioToUDP); ui->ctcss->setCurrentIndex(m_settings.m_ctcssIndex); - if (m_nfmDemod->isAudioNetSinkRTPCapable()) { - ui->useRTP->setChecked(m_settings.m_copyAudioUseRTP); - } - blockApplySettings(false); } -void NFMDemodGUI::displayUDPAddress() -{ - ui->copyAudioToUDP->setToolTip(QString("Copy audio output to UDP %1:%2").arg(m_settings.m_udpAddress).arg(m_settings.m_udpPort)); -} - void NFMDemodGUI::leaveEvent(QEvent*) { m_channelMarker.setHighlighted(false); @@ -442,6 +416,19 @@ void NFMDemodGUI::blockApplySettings(bool block) m_doApplySettings = !block; } +void NFMDemodGUI::audioSelect() +{ + qDebug("NFMDemodGUI::audioSelect"); + AudioSelectDialog audioSelect(DSPEngine::instance()->getAudioDeviceManager(), m_settings.m_audioDeviceName); + audioSelect.exec(); + + if (audioSelect.m_selected) + { + m_settings.m_audioDeviceName = audioSelect.m_audioDeviceName; + applySettings(); + } +} + void NFMDemodGUI::tick() { double magsqAvg, magsqPeak; diff --git a/plugins/channelrx/demodnfm/nfmdemodgui.h b/plugins/channelrx/demodnfm/nfmdemodgui.h index 3b4b0e68d..5dd19ede4 100644 --- a/plugins/channelrx/demodnfm/nfmdemodgui.h +++ b/plugins/channelrx/demodnfm/nfmdemodgui.h @@ -62,7 +62,6 @@ private: void blockApplySettings(bool block); void applySettings(bool force = false); void displaySettings(); - void displayUDPAddress(); void leaveEvent(QEvent*); void enterEvent(QEvent*); @@ -78,11 +77,10 @@ private slots: void on_ctcss_currentIndexChanged(int index); void on_ctcssOn_toggled(bool checked); void on_audioMute_toggled(bool checked); - void on_copyAudioToUDP_toggled(bool checked); - void on_useRTP_toggled(bool checked); void onWidgetRolled(QWidget* widget, bool rollDown); void onMenuDialogCalled(const QPoint& p); void handleInputMessages(); + void audioSelect(); void tick(); }; diff --git a/plugins/channelrx/demodnfm/nfmdemodgui.ui b/plugins/channelrx/demodnfm/nfmdemodgui.ui index c798abb3f..fc8fe0075 100644 --- a/plugins/channelrx/demodnfm/nfmdemodgui.ui +++ b/plugins/channelrx/demodnfm/nfmdemodgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -101,7 +101,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -190,7 +190,7 @@ - Monospace + Liberation Mono 8 @@ -339,13 +339,13 @@ Sound volume - 100 + 40 1 - 20 + 10
@@ -367,7 +367,7 @@ Sound volume - 2.0 + 1.0 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -428,7 +428,7 @@ Squelch threshold (dB) - -1000 + -100 0 @@ -440,7 +440,7 @@ 1 - -150 + -100 @@ -448,21 +448,15 @@ - 40 + 28 0 - - - 16777215 - 16777215 - - Squelch threshold (dB) - -15.0 + -100 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -583,7 +577,7 @@ - Mute/Unmute audio + Left: Mute/Unmute audio Right: Select audio output device ... @@ -598,32 +592,6 @@ - - - - Copy audio to UDP - - - U - - - true - - - - - - - Use RTP protocol for sending audio via UDP - - - R - - - true - - - diff --git a/plugins/channelrx/demodnfm/nfmdemodsettings.cpp b/plugins/channelrx/demodnfm/nfmdemodsettings.cpp index c1efda589..b23684402 100644 --- a/plugins/channelrx/demodnfm/nfmdemodsettings.cpp +++ b/plugins/channelrx/demodnfm/nfmdemodsettings.cpp @@ -25,8 +25,8 @@ const int NFMDemodSettings::m_rfBW[] = { 5000, 6250, 8330, 10000, 12500, 15000, 20000, 25000, 40000 }; -const int NFMDemodSettings::m_fmDev[] = { // corresponding FM deviations - 1000, 1500, 2000, 2000, 2000, 2500, 3000, 3500, 5000 +const int NFMDemodSettings::m_fmDev[] = { // corresponding single side FM deviations at 0.4 * BW + 2000, 2500, 3330, 4000, 5000, 6000, 8000, 10000, 16000 }; const int NFMDemodSettings::m_nbRfBW = 9; @@ -44,18 +44,14 @@ void NFMDemodSettings::resetToDefaults() m_fmDeviation = 2000; m_squelchGate = 5; // 10s of ms at 48000 Hz sample rate. Corresponds to 2400 for AGC attack m_deltaSquelch = false; - m_squelch = -300.0; + m_squelch = -30.0; m_volume = 1.0; m_ctcssOn = false; m_audioMute = false; m_ctcssIndex = 0; - m_audioSampleRate = DSPEngine::instance()->getAudioSampleRate(); - m_copyAudioToUDP = false; - m_copyAudioUseRTP = false; - m_udpAddress = "127.0.0.1"; - m_udpPort = 9998; m_rgbColor = QColor(255, 0, 0).rgb(); m_title = "NFM Demodulator"; + m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; } QByteArray NFMDemodSettings::serialize() const @@ -78,7 +74,7 @@ QByteArray NFMDemodSettings::serialize() const } s.writeString(14, m_title); - s.writeBool(15, m_copyAudioUseRTP); + s.writeString(15, m_audioDeviceName); return s.final(); } @@ -113,8 +109,8 @@ bool NFMDemodSettings::deserialize(const QByteArray& data) m_afBandwidth = tmp * 1000.0; d.readS32(4, &tmp, 20); m_volume = tmp / 10.0; - d.readS32(5, &tmp, -300); - m_squelch = tmp * 1.0; + d.readS32(5, &tmp, -30); + m_squelch = (tmp < -100 ? tmp/10 : tmp) * 1.0; d.readU32(7, &m_rgbColor, QColor(255, 0, 0).rgb()); d.readS32(8, &m_ctcssIndex, 0); d.readBool(9, &m_ctcssOn, false); @@ -122,7 +118,7 @@ bool NFMDemodSettings::deserialize(const QByteArray& data) d.readS32(11, &m_squelchGate, 5); d.readBool(12, &m_deltaSquelch, false); d.readString(14, &m_title, "NFM Demodulator"); - d.readBool(15, &m_copyAudioUseRTP, false); + d.readString(15, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName); return true; } diff --git a/plugins/channelrx/demodnfm/nfmdemodsettings.h b/plugins/channelrx/demodnfm/nfmdemodsettings.h index 578f7cdd6..285735674 100644 --- a/plugins/channelrx/demodnfm/nfmdemodsettings.h +++ b/plugins/channelrx/demodnfm/nfmdemodsettings.h @@ -27,24 +27,20 @@ struct NFMDemodSettings static const int m_rfBW[]; static const int m_fmDev[]; - int64_t m_inputFrequencyOffset; + int32_t m_inputFrequencyOffset; Real m_rfBandwidth; Real m_afBandwidth; int m_fmDeviation; int m_squelchGate; bool m_deltaSquelch; - Real m_squelch; //!< centi-Bels + Real m_squelch; //!< deci-Bels Real m_volume; bool m_ctcssOn; bool m_audioMute; int m_ctcssIndex; - uint32_t m_audioSampleRate; - bool m_copyAudioToUDP; - bool m_copyAudioUseRTP; - QString m_udpAddress; - uint16_t m_udpPort; quint32 m_rgbColor; QString m_title; + QString m_audioDeviceName; Serializable *m_channelMarker; diff --git a/plugins/channelrx/demodnfm/nfmplugin.cpp b/plugins/channelrx/demodnfm/nfmplugin.cpp index d7dfc0b1b..53b5f8357 100644 --- a/plugins/channelrx/demodnfm/nfmplugin.cpp +++ b/plugins/channelrx/demodnfm/nfmplugin.cpp @@ -2,12 +2,14 @@ #include "plugin/pluginapi.h" #include "nfmplugin.h" +#ifndef SERVER_MODE #include "nfmdemodgui.h" +#endif #include "nfmdemod.h" const PluginDescriptor NFMPlugin::m_pluginDescriptor = { QString("NFM Demodulator"), - QString("3.12.0"), + QString("4.2.1"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demodnfm/nfmplugin.h b/plugins/channelrx/demodnfm/nfmplugin.h index b5f90af70..c5c07976a 100644 --- a/plugins/channelrx/demodnfm/nfmplugin.h +++ b/plugins/channelrx/demodnfm/nfmplugin.h @@ -10,7 +10,7 @@ class BasebandSampleSink; class NFMPlugin : public QObject, PluginInterface { Q_OBJECT Q_INTERFACES(PluginInterface) - Q_PLUGIN_METADATA(IID "de.maintech.sdrangelove.channel.nfm") + Q_PLUGIN_METADATA(IID "sdrangel.channel.nfmdemod") public: explicit NFMPlugin(QObject* parent = NULL); diff --git a/plugins/channelrx/demodnfm/readme.md b/plugins/channelrx/demodnfm/readme.md index f160f5800..aa3fff81b 100644 --- a/plugins/channelrx/demodnfm/readme.md +++ b/plugins/channelrx/demodnfm/readme.md @@ -10,7 +10,7 @@ This plugin can be used to listen to a narrowband FM modulated signal. "Narrowba

1: Frequency shift from center frequency of reception value

-Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. Left click on a digit sets the cursor position at this digit. +Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. Left click on a digit sets the cursor position at this digit.

2: Channel power

@@ -24,15 +24,19 @@ Average total power in dB relative to a +/- 1.0 amplitude signal received in the

4: RF bandwidth

-This is the bandwidth in kHz of the channel signal before demodulation. It can be set in steps as 5, 6.25, 8.33, 10, 12.5, 15, 20, 25 and 40 kHz. +This is the bandwidth in kHz of the channel signal before demodulation. It can be set in steps as 5, 6.25, 8.33, 10, 12.5, 15, 20, 25 and 40 kHz. The expected one side frequency deviation is 0.4 times the bandwidth. + +☞ The demodulation is done at the channel sample rate which is guaranteed not to be lower than the requested audio sample rate but can possibly be equal to it. This means that for correct operation in any case you must ensure that the sample rate of the audio device is not lower than the Nyquist rate required to process this channel bandwidth. + +☞ The channel sample rate is always the baseband signal rate divided by an integer power of two so depending on the baseband sample rate obtained from the sampling device you could also guarantee a minimal channel bandwidth. For example with a 125 kS/s baseband sample rate and a 8 kS/s audio sample rate the channel sample rate cannot be lower than 125/8 = 15.625 kS/s (125/16 = 7.8125 kS/s is too small) which is still OK for 5 or 6.25 kHz channel bandwidths.

5: AF bandwidth

-This is the bandwidth of the audio signal in kHz (i.e. after demodulation). It can be set in continuous kHz steps from 1 to 20 kHz. +This is the bandwidth of the audio signal in kHz (i.e. after demodulation). It can be set in continuous kHz steps from 1 to 20 kHz.

6: Volume

-This is the volume of the audio signal from 0.0 (mute) to 10.0 (maximum). It can be varied continuously in 0.1 steps using the dial button. +This is the volume of the audio signal from 0.0 (mute) to 4.0 (maximum). It can be varied continuously in 0.1 steps using the dial button.

7: Delta/Level squelch

@@ -40,7 +44,21 @@ Use this button to toggle between AF (on) and RF power (off) based squelch.

8: Squelch threshold

-This is the squelch threshold in dB. The average total power received in the signal bandwidth before demodulation is compared to this value and the squelch input is open above this value. It can be varied continuously in 0.1 dB steps from 0.0 to -100.0 dB using the dial button. +

Power threshold mode

+ +Case when the delta/Level squelch control (7) is off (power). This is the squelch threshold in dB. The average total power received in the signal bandwidth before demodulation is compared to this value and the squelch input is open above this value. It can be varied continuously in 1 dB steps from 0 to -100 dB using the dial button. + +

Audio frequency delta mode

+ +Case when the delta/Level squelch control (7) is on (delta). In this mode the squelch compares the power of the demodulated audio signal in a low frequency band and a high frequency band. In the absence of signal the discriminator response is nearly flat and the power in the two bands is more or less balanced. In the presence of a signal the lower band will receive more power than the higher band. The squelch does the ratio of both powers and the squelch is opened if this ratio is lower than the threshold given in percent. + +A ratio of 1 (100%) will always open the squelch and a ratio of 0 will always close it. The value can be varied to detect more distorted and thus weak signals towards the higher values. The button rotation runs from higher to lower as you turn it clockwise thus giving the same feel as in power mode. The best ratio for a standard NFM transmission is ~40%. + +The distinct advantage of this type of squelch is that it guarantees the quality level of the audio signal (optimized for voice) thus remaining closed for too noisy signals received on marginal conditions or bursts of noise independently of the signal power. + +☞ The signal used is the one before AF filtering and the bands are centered around 1000 Hz for the lower band and 6000 Hz for the higher band. This means that it will not work if your audio device runs at 8000 or 11025 Hz. You will need at least a 16000 Hz sample rate. Choose power squelch for lower audio rates. + +☞ The chosen bands around 1000 and 6000 Hz are optimized for standard voice signals in the 300-3000 Hz range.

9: Squelch gate

@@ -58,12 +76,8 @@ This is the tone squelch in Hz. It can be selected using the toolbox among the u This is the value of the tone squelch received when the CTCSS is activated. It displays `--` if the CTCSS system is de-activated. -

13: Audio mute

+

13: Audio mute and audio output select

-Use this button to toggle audio mute for this channel. The button will light up in green if the squelch is open. This helps identifying which channels are active in a multi-channel configuration. +Left click on this button to toggle audio mute for this channel. The button will light up in green if the squelch is open. This helps identifying which channels are active in a multi-channel configuration. -

14: UDP output

- -Copies audio output to UDP. Audio is set at fixed level and is muted by the mute button (13) and squelch is also applied. Output is mono S16LE samples. Note that fixed volume apart this is the exact same audio that is sent to the audio device in particular it is highpass filtered at 300 Hz and thus is not suitable for digital communications. For this purpose you have to use the UDP source plugin instead. - -UDP address and send port are specified in the basic channel settings. See: [here](https://github.com/f4exb/sdrangel/blob/master/sdrgui/readme.md#6-channels) \ No newline at end of file +If you right click on it it will open a dialog to select the audio output device. See [audio management documentation](../../../sdrgui/audio.md) for details. diff --git a/plugins/channelrx/demodssb/CMakeLists.txt b/plugins/channelrx/demodssb/CMakeLists.txt index f70d0a7e5..d1c095eb0 100644 --- a/plugins/channelrx/demodssb/CMakeLists.txt +++ b/plugins/channelrx/demodssb/CMakeLists.txt @@ -1,5 +1,7 @@ project(ssb) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + set(ssb_SOURCES ssbdemod.cpp ssbdemodgui.cpp @@ -21,6 +23,7 @@ set(ssb_FORMS include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) #include(${QT_USE_FILE}) @@ -43,6 +46,6 @@ target_link_libraries(demodssb sdrgui ) -qt5_use_modules(demodssb Core Widgets) +target_link_libraries(demodssb Qt5::Core Qt5::Widgets) install(TARGETS demodssb DESTINATION lib/plugins/channelrx) diff --git a/plugins/channelrx/demodssb/demodssb.pro b/plugins/channelrx/demodssb/demodssb.pro index 11e37b289..9fa689509 100644 --- a/plugins/channelrx/demodssb/demodssb.pro +++ b/plugins/channelrx/demodssb/demodssb.pro @@ -18,8 +18,10 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui +INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug @@ -38,5 +40,6 @@ FORMS += ssbdemodgui.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui +LIBS += -L../../../swagger/$${build_subdir} -lswagger RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/plugins/channelrx/demodssb/readme.md b/plugins/channelrx/demodssb/readme.md index 67cacbf59..c7ffc2e02 100644 --- a/plugins/channelrx/demodssb/readme.md +++ b/plugins/channelrx/demodssb/readme.md @@ -14,7 +14,7 @@ This plugin can be used to listen to a single sideband or double sidebands modul

1: Frequency shift from center frequency of reception

-Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

2: Channel power

@@ -45,20 +45,20 @@ Toggles between SSB (icon with one sideband signal) and DSB (icon with double si

8: Spectrum display frequency span

-The 48 kHz channel sample rate is further decimated by powers of two for the spectrum display and in channel filter limits. This effectively sets the total available bandwidth depending on the decimation: +The audio sample rate SR is further decimated by powers of two for the spectrum display and in channel filter limits. This effectively sets the total available bandwidth depending on the decimation: - - 1 (no decimation): 24 kHz (SSB) or 48 kHz (DSB) - - 2: 12 kHz (SSB) or 24 kHz (DSB) - - 4: 6 kHz (SSB) or 12 kHz (DSB) - - 8: 3 kHz (SSB) or 6 kHz (DSB) - - 16: 1.5 kHz (SSB) or 3 kHz (DSB) + - 1 (no decimation): SR/2 (SSB) or SR (DSB) + - 2: SR/4 (SSB) or SR/2 (DSB) + - 4: SR/8 (SSB) or SR/4 (DSB) + - 8: SR/16 (SSB) or SR/8 (DSB) + - 16: SR/32 (SSB) or SR/16 (DSB) The span value display is set as follows depending on the SSB or DSB mode: - In SSB mode: the span goes from zero to the upper (USB: positive frequencies) or lower (LSB: negative frequencies) limit and the absolute value of the limit is displayed. - In DSB mode: the span goes from the lower to the upper limit of same absolute value and ± the absolute value of the limit is displayed. -This is how the Span (8) and bandpass (9, 10) fitler controls look like in the 3 possible modes: +This is how the Span (8) and bandpass (9, 10) filter controls look like in the 3 possible modes: **DSB**: @@ -97,7 +97,7 @@ Values are expressed in kHz and step is 100 Hz. Values are expressed in kHz and step is 100 Hz. - - In SSB mode this is the lower cutoff (USB: positive frequencies) or higher cutoff (LSB: negative frequencies) of the in channel signe side band bandpass filter. + - In SSB mode this is the lower cutoff (USB: positive frequencies) or higher cutoff (LSB: negative frequencies) of the in channel single side band bandpass filter. - In DSB mode it is inactive and set to zero (double side band filter).

11: Volume and AGC

@@ -112,7 +112,7 @@ This is the volume of the audio signal from 0.0 (mute) to 10.0 (maximum). It can Use this checkbox to toggle AGC on and off. -If you are into digging weak signals out of the noise you probably will not turn the AGC on. AGC is intended for medium and large signals and help accomodate the signal power variations from a station to another or due to QSB. +If you are into digging weak signals out of the noise you probably will not turn the AGC on. AGC is intended for medium and large signals and help accommodate the signal power variations from a station to another or due to QSB. This AGC is based on the calculated magnitude (square root of power of the filtered signal as I² + Q²) and will try to adjust audio volume as if a -20dB power signal was received. @@ -140,18 +140,16 @@ The signal power is calculated as the moving average over the AGC time constant Active only in AGC mode with squelch enabled. -To avoid unwanted squelch opening on short transient bursts only signals wilth power above threshold during this period in milliseconds will open the squelch.It can be varied from 0 to 20 ms in 1 ms steps. +To avoid unwanted squelch opening on short transient bursts only signals with power above threshold during this period in milliseconds will open the squelch.It can be varied from 0 to 20 ms in 1 ms steps. When the power threshold is close to the noise floor a few milliseconds help in preventing noise power wiggle to open the squelch. -

12: Copy audio to UDP

+

13: Audio mute and audio output select

-Copies audio output to UDP. Output is mono S16LE samples regardless of binaural/monaural operation. +Left click on this button to toggle audio mute for this channel. -

13: Audio mute

- -Use this button to toggle audio mute for this channel. +If you right click on it a dialog will open to select the audio output device. See [audio management documentation](../../../sdrgui/audio.md) for details.

14: Spectrum display

-This is the spectrum display of the demodulated signal (SSB) or translated signal (DSB). Controls on the bottom of the panel are identical to the ones of the main spectrum display. \ No newline at end of file +This is the spectrum display of the demodulated signal (SSB) or translated signal (DSB). Controls on the bottom of the panel are identical to the ones of the main spectrum display. diff --git a/plugins/channelrx/demodssb/ssbdemod.cpp b/plugins/channelrx/demodssb/ssbdemod.cpp index 84d92bd8a..0b0aa6b21 100644 --- a/plugins/channelrx/demodssb/ssbdemod.cpp +++ b/plugins/channelrx/demodssb/ssbdemod.cpp @@ -21,6 +21,11 @@ #include #include +#include "SWGChannelSettings.h" +#include "SWGSSBDemodSettings.h" +#include "SWGChannelReport.h" +#include "SWGSSBDemodReport.h" + #include "audio/audiooutput.h" #include "dsp/dspengine.h" #include "dsp/downchannelizer.h" @@ -35,9 +40,8 @@ MESSAGE_CLASS_DEFINITION(SSBDemod::MsgConfigureSSBDemod, Message) MESSAGE_CLASS_DEFINITION(SSBDemod::MsgConfigureSSBDemodPrivate, Message) MESSAGE_CLASS_DEFINITION(SSBDemod::MsgConfigureChannelizer, Message) -const QString SSBDemod::m_channelIdURI = "de.maintech.sdrangelove.channel.ssb"; +const QString SSBDemod::m_channelIdURI = "sdrangel.channel.ssbdemod"; const QString SSBDemod::m_channelId = "SSBDemod"; -const int SSBDemod::m_udpBlockSize = 512; SSBDemod::SSBDemod(DeviceSourceAPI *deviceAPI) : ChannelSinkAPI(m_channelIdURI), @@ -52,6 +56,7 @@ SSBDemod::SSBDemod(DeviceSourceAPI *deviceAPI) : m_agcNbSamples(12000), m_agcPowerThreshold(1e-2), m_agcThresholdGate(0), + m_squelchDelayLine(2*48000), m_audioActive(false), m_sampleSink(0), m_audioFifo(24000), @@ -65,7 +70,9 @@ SSBDemod::SSBDemod(DeviceSourceAPI *deviceAPI) : m_spanLog2 = 3; m_inputSampleRate = 48000; m_inputFrequencyOffset = 0; - m_audioSampleRate = DSPEngine::instance()->getAudioSampleRate(); + + DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo, getInputMessageQueue()); + m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate(); m_audioBuffer.resize(1<<14); m_audioBufferFill = 0; @@ -78,36 +85,31 @@ SSBDemod::SSBDemod(DeviceSourceAPI *deviceAPI) : m_magsqPeak = 0.0f; m_magsqCount = 0; - m_agc.setClampMax(SDR_RX_SCALED*SDR_RX_SCALED); + m_agc.setClampMax(SDR_RX_SCALED/100.0); m_agc.setClamping(m_agcClamping); SSBFilter = new fftfilt(m_LowCutoff / m_audioSampleRate, m_Bandwidth / m_audioSampleRate, ssbFftLen); DSBFilter = new fftfilt((2.0f * m_Bandwidth) / m_audioSampleRate, 2 * ssbFftLen); - DSPEngine::instance()->addAudioSink(&m_audioFifo); - m_udpBufferAudio = new UDPSink(this, m_udpBlockSize, m_settings.m_udpPort); + applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); m_channelizer = new DownChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); m_deviceAPI->addThreadedSink(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - - applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); - applySettings(m_settings, true); } SSBDemod::~SSBDemod() { - if (SSBFilter) delete SSBFilter; - if (DSBFilter) delete DSBFilter; - DSPEngine::instance()->removeAudioSink(&m_audioFifo); + DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo); m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSink(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; - - delete m_udpBufferAudio; + delete SSBFilter; + delete DSBFilter; } void SSBDemod::configure(MessageQueue* messageQueue, @@ -211,41 +213,39 @@ void SSBDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto m_sum.imag(0.0); } - double agcVal = m_agcActive ? m_agc.feedAndGetValue(sideband[i]) : 10.0; // 10.0 for 3276.8, 1.0 for 327.68 - m_audioActive = agcVal != 0.0; + float agcVal = m_agcActive ? m_agc.feedAndGetValue(sideband[i]) : 10.0; // 10.0 for 3276.8, 1.0 for 327.68 + fftfilt::cmplx& delayedSample = m_squelchDelayLine.readBack(m_agc.getStepDownDelay()); + m_audioActive = delayedSample.real() != 0.0; + m_squelchDelayLine.write(sideband[i]*agcVal); if (m_audioMute) { m_audioBuffer[m_audioBufferFill].r = 0; m_audioBuffer[m_audioBufferFill].l = 0; - - if (m_settings.m_copyAudioToUDP) { m_udpBufferAudio->write(0); } } else { + fftfilt::cmplx z = delayedSample * m_agc.getStepValue(); + if (m_audioBinaual) { if (m_audioFlipChannels) { - m_audioBuffer[m_audioBufferFill].r = (qint16)(sideband[i].imag() * m_volume * agcVal); - m_audioBuffer[m_audioBufferFill].l = (qint16)(sideband[i].real() * m_volume * agcVal); + m_audioBuffer[m_audioBufferFill].r = (qint16)(z.imag() * m_volume); + m_audioBuffer[m_audioBufferFill].l = (qint16)(z.real() * m_volume); } else { - m_audioBuffer[m_audioBufferFill].r = (qint16)(sideband[i].real() * m_volume * agcVal); - m_audioBuffer[m_audioBufferFill].l = (qint16)(sideband[i].imag() * m_volume * agcVal); + m_audioBuffer[m_audioBufferFill].r = (qint16)(z.real() * m_volume); + m_audioBuffer[m_audioBufferFill].l = (qint16)(z.imag() * m_volume); } - - if (m_settings.m_copyAudioToUDP) { m_udpBufferAudio->write(m_audioBuffer[m_audioBufferFill].r + m_audioBuffer[m_audioBufferFill].l); } } else { - Real demod = (sideband[i].real() + sideband[i].imag()) * 0.7; - qint16 sample = (qint16)(demod * m_volume * agcVal); + Real demod = (z.real() + z.imag()) * 0.7; + qint16 sample = (qint16)(demod * m_volume); m_audioBuffer[m_audioBufferFill].l = sample; m_audioBuffer[m_audioBufferFill].r = sample; - - if (m_settings.m_copyAudioToUDP) { m_udpBufferAudio->write(sample); } } } @@ -253,7 +253,7 @@ void SSBDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto if (m_audioBufferFill >= m_audioBuffer.size()) { - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 10); + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); if (res != m_audioBufferFill) { @@ -265,7 +265,7 @@ void SSBDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto } } - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 10); + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); if (res != m_audioBufferFill) { @@ -325,6 +325,27 @@ bool SSBDemod::handleMessage(const Message& cmd) return true; } + else if (BasebandSampleSink::MsgThreadedSink::match(cmd)) + { + BasebandSampleSink::MsgThreadedSink& cfg = (BasebandSampleSink::MsgThreadedSink&) cmd; + const QThread *thread = cfg.getThread(); + qDebug("SSBDemod::handleMessage: BasebandSampleSink::MsgThreadedSink: %p", thread); + return true; + } + else if (DSPConfigureAudio::match(cmd)) + { + DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd; + uint32_t sampleRate = cfg.getSampleRate(); + + qDebug() << "SSBDemod::handleMessage: DSPConfigureAudio:" + << " sampleRate: " << sampleRate; + + if (sampleRate != m_audioSampleRate) { + applyAudioSampleRate(sampleRate); + } + + return true; + } else if (DSPSignalNotification::match(cmd)) { return true; @@ -359,7 +380,7 @@ void SSBDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffse m_settingsMutex.lock(); m_interpolator.create(16, inputSampleRate, m_Bandwidth * 1.5f, 2.0f); m_interpolatorDistanceRemain = 0; - m_interpolatorDistance = (Real) inputSampleRate / (Real) m_settings.m_audioSampleRate; + m_interpolatorDistance = (Real) inputSampleRate / (Real) m_audioSampleRate; m_settingsMutex.unlock(); } @@ -367,6 +388,52 @@ void SSBDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffse m_inputFrequencyOffset = inputFrequencyOffset; } +void SSBDemod::applyAudioSampleRate(int sampleRate) +{ + qDebug("SSBDemod::applyAudioSampleRate: %d", sampleRate); + + MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( + sampleRate, m_settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(channelConfigMsg); + + m_settingsMutex.lock(); + + m_interpolator.create(16, m_inputSampleRate, m_Bandwidth * 1.5f, 2.0f); + m_interpolatorDistanceRemain = 0; + m_interpolatorDistance = (Real) m_inputSampleRate / (Real) sampleRate; + + SSBFilter->create_filter(m_LowCutoff / (float) sampleRate, m_Bandwidth / (float) sampleRate); + DSBFilter->create_dsb_filter((2.0f * m_Bandwidth) / (float) sampleRate); + + int agcNbSamples = (sampleRate / 1000) * (1<push(cfg); + } +} + void SSBDemod::applySettings(const SSBDemodSettings& settings, bool force) { qDebug() << "SSBDemod::applySettings:" @@ -379,22 +446,21 @@ void SSBDemod::applySettings(const SSBDemodSettings& settings, bool force) << " m_audioFlipChannels: " << settings.m_audioFlipChannels << " m_dsb: " << settings.m_dsb << " m_audioMute: " << settings.m_audioMute - << " m_copyAudioToUDP: " << settings.m_copyAudioToUDP << " m_agcActive: " << settings.m_agc << " m_agcClamping: " << settings.m_agcClamping << " m_agcTimeLog2: " << settings.m_agcTimeLog2 << " agcPowerThreshold: " << settings.m_agcPowerThreshold - << " agcThresholdGate: " << settings.m_agcThresholdGate; + << " agcThresholdGate: " << settings.m_agcThresholdGate + << " m_audioDeviceName: " << settings.m_audioDeviceName + << " force: " << force; if((m_settings.m_rfBandwidth != settings.m_rfBandwidth) || - (m_settings.m_lowCutoff != settings.m_lowCutoff) || - (m_settings.m_audioSampleRate != settings.m_audioSampleRate) || force) + (m_settings.m_lowCutoff != settings.m_lowCutoff) || force) { float band, lowCutoff; band = settings.m_rfBandwidth; lowCutoff = settings.m_lowCutoff; - m_audioSampleRate = settings.m_audioSampleRate; if (band < 0) { band = -band; @@ -416,7 +482,7 @@ void SSBDemod::applySettings(const SSBDemodSettings& settings, bool force) m_settingsMutex.lock(); m_interpolator.create(16, m_inputSampleRate, m_Bandwidth * 1.5f, 2.0f); m_interpolatorDistanceRemain = 0; - m_interpolatorDistance = (Real) m_inputSampleRate / (Real) m_settings.m_audioSampleRate; + m_interpolatorDistance = (Real) m_inputSampleRate / (Real) m_audioSampleRate; SSBFilter->create_filter(m_LowCutoff / (float) m_audioSampleRate, m_Bandwidth / (float) m_audioSampleRate); DSBFilter->create_dsb_filter((2.0f * m_Bandwidth) / (float) m_audioSampleRate); m_settingsMutex.unlock(); @@ -433,16 +499,16 @@ void SSBDemod::applySettings(const SSBDemodSettings& settings, bool force) (m_settings.m_agcThresholdGate != settings.m_agcThresholdGate) || (m_settings.m_agcClamping != settings.m_agcClamping) || force) { - int agcNbSamples = 48 * (1<setAddress(const_cast(settings.m_udpAddress)); - m_udpBufferAudio->setPort(settings.m_udpPort); + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName); + audioDeviceManager->addAudioSink(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex); + uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex); + + if (m_audioSampleRate != audioSampleRate) { + applyAudioSampleRate(audioSampleRate); + } } m_spanLog2 = settings.m_spanLog2; @@ -511,3 +582,153 @@ bool SSBDemod::deserialize(const QByteArray& data) return false; } } + +int SSBDemod::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setSsbDemodSettings(new SWGSDRangel::SWGSSBDemodSettings()); + response.getSsbDemodSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int SSBDemod::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + SSBDemodSettings settings = m_settings; + bool frequencyOffsetChanged = false; + + if (channelSettingsKeys.contains("inputFrequencyOffset")) + { + settings.m_inputFrequencyOffset = response.getSsbDemodSettings()->getInputFrequencyOffset(); + frequencyOffsetChanged = true; + } + if (channelSettingsKeys.contains("rfBandwidth")) { + settings.m_rfBandwidth = response.getSsbDemodSettings()->getRfBandwidth(); + } + if (channelSettingsKeys.contains("lowCutoff")) { + settings.m_lowCutoff = response.getSsbDemodSettings()->getLowCutoff(); + } + if (channelSettingsKeys.contains("volume")) { + settings.m_volume = response.getSsbDemodSettings()->getVolume(); + } + if (channelSettingsKeys.contains("spanLog2")) { + settings.m_spanLog2 = response.getSsbDemodSettings()->getSpanLog2(); + } + if (channelSettingsKeys.contains("audioBinaural")) { + settings.m_audioBinaural = response.getSsbDemodSettings()->getAudioBinaural() != 0; + } + if (channelSettingsKeys.contains("audioFlipChannels")) { + settings.m_audioFlipChannels = response.getSsbDemodSettings()->getAudioFlipChannels() != 0; + } + if (channelSettingsKeys.contains("dsb")) { + settings.m_dsb = response.getSsbDemodSettings()->getDsb() != 0; + } + if (channelSettingsKeys.contains("audioMute")) { + settings.m_audioMute = response.getSsbDemodSettings()->getAudioMute() != 0; + } + if (channelSettingsKeys.contains("agc")) { + settings.m_agc = response.getSsbDemodSettings()->getAgc() != 0; + } + if (channelSettingsKeys.contains("agcClamping")) { + settings.m_agcClamping = response.getSsbDemodSettings()->getAgcClamping() != 0; + } + if (channelSettingsKeys.contains("agcTimeLog2")) { + settings.m_agcTimeLog2 = response.getSsbDemodSettings()->getAgcTimeLog2(); + } + if (channelSettingsKeys.contains("agcPowerThreshold")) { + settings.m_agcPowerThreshold = response.getSsbDemodSettings()->getAgcPowerThreshold(); + } + if (channelSettingsKeys.contains("agcThresholdGate")) { + settings.m_agcThresholdGate = response.getSsbDemodSettings()->getAgcThresholdGate(); + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getSsbDemodSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getSsbDemodSettings()->getTitle(); + } + if (channelSettingsKeys.contains("audioDeviceName")) { + settings.m_audioDeviceName = *response.getSsbDemodSettings()->getAudioDeviceName(); + } + + if (frequencyOffsetChanged) + { + MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( + m_audioSampleRate, settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(channelConfigMsg); + } + + MsgConfigureSSBDemod *msg = MsgConfigureSSBDemod::create(settings, force); + m_inputMessageQueue.push(msg); + + qDebug("SSBDemod::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureSSBDemod *msgToGUI = MsgConfigureSSBDemod::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int SSBDemod::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setSsbDemodReport(new SWGSDRangel::SWGSSBDemodReport()); + response.getSsbDemodReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + +void SSBDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const SSBDemodSettings& settings) +{ + response.getSsbDemodSettings()->setAudioMute(settings.m_audioMute ? 1 : 0); + response.getSsbDemodSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getSsbDemodSettings()->setRfBandwidth(settings.m_rfBandwidth); + response.getSsbDemodSettings()->setLowCutoff(settings.m_lowCutoff); + response.getSsbDemodSettings()->setVolume(settings.m_volume); + response.getSsbDemodSettings()->setSpanLog2(settings.m_spanLog2); + response.getSsbDemodSettings()->setAudioBinaural(settings.m_audioBinaural ? 1 : 0); + response.getSsbDemodSettings()->setAudioFlipChannels(settings.m_audioFlipChannels ? 1 : 0); + response.getSsbDemodSettings()->setDsb(settings.m_dsb ? 1 : 0); + response.getSsbDemodSettings()->setAudioMute(settings.m_audioMute ? 1 : 0); + response.getSsbDemodSettings()->setAgc(settings.m_agc ? 1 : 0); + response.getSsbDemodSettings()->setAgcClamping(settings.m_agcClamping ? 1 : 0); + response.getSsbDemodSettings()->setAgcTimeLog2(settings.m_agcTimeLog2); + response.getSsbDemodSettings()->setAgcPowerThreshold(settings.m_agcPowerThreshold); + response.getSsbDemodSettings()->setAgcThresholdGate(settings.m_agcThresholdGate); + response.getSsbDemodSettings()->setRgbColor(settings.m_rgbColor); + + if (response.getSsbDemodSettings()->getTitle()) { + *response.getSsbDemodSettings()->getTitle() = settings.m_title; + } else { + response.getSsbDemodSettings()->setTitle(new QString(settings.m_title)); + } + + if (response.getSsbDemodSettings()->getAudioDeviceName()) { + *response.getSsbDemodSettings()->getAudioDeviceName() = settings.m_audioDeviceName; + } else { + response.getSsbDemodSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName)); + } +} + +void SSBDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + double magsqAvg, magsqPeak; + int nbMagsqSamples; + getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples); + + response.getSsbDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg)); + response.getSsbDemodReport()->setSquelch(m_audioActive ? 1 : 0); + response.getSsbDemodReport()->setAudioSampleRate(m_audioSampleRate); + response.getSsbDemodReport()->setChannelSampleRate(m_inputSampleRate); +} + diff --git a/plugins/channelrx/demodssb/ssbdemod.h b/plugins/channelrx/demodssb/ssbdemod.h index a8fa5e0d9..27521aa01 100644 --- a/plugins/channelrx/demodssb/ssbdemod.h +++ b/plugins/channelrx/demodssb/ssbdemod.h @@ -29,6 +29,7 @@ #include "dsp/agc.h" #include "audio/audiofifo.h" #include "util/message.h" +#include "util/doublebufferfifo.h" #include "ssbdemodsettings.h" @@ -119,24 +120,56 @@ public: virtual QByteArray serialize() const; virtual bool deserialize(const QByteArray& data); + uint32_t getAudioSampleRate() const { return m_audioSampleRate; } double getMagSq() const { return m_magsq; } bool getAudioActive() const { return m_audioActive; } void getMagSqLevels(double& avg, double& peak, int& nbSamples) { - avg = m_magsqCount == 0 ? 1e-10 : m_magsqSum / m_magsqCount; - m_magsq = avg; - peak = m_magsqPeak == 0.0 ? 1e-10 : m_magsqPeak; + if (m_magsqCount > 0) + { + m_magsq = m_magsqSum / m_magsqCount; + m_magSqLevelStore.m_magsq = m_magsq; + m_magSqLevelStore.m_magsqPeak = m_magsqPeak; + } + + avg = m_magSqLevelStore.m_magsq; + peak = m_magSqLevelStore.m_magsqPeak; nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; + m_magsqSum = 0.0f; m_magsqPeak = 0.0f; m_magsqCount = 0; } + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + static const QString m_channelIdURI; static const QString m_channelId; private: + struct MagSqLevelsStore + { + MagSqLevelsStore() : + m_magsq(1e-12), + m_magsqPeak(1e-12) + {} + double m_magsq; + double m_magsqPeak; + }; + class MsgConfigureSSBDemodPrivate : public Message { MESSAGE_CLASS_DECLARATION @@ -252,12 +285,14 @@ private: double m_magsqSum; double m_magsqPeak; int m_magsqCount; + MagSqLevelsStore m_magSqLevelStore; MagAGC m_agc; bool m_agcActive; bool m_agcClamping; int m_agcNbSamples; //!< number of audio (48 kHz) samples for AGC averaging double m_agcPowerThreshold; //!< AGC power threshold (linear) int m_agcThresholdGate; //!< Gate length in number of samples befor threshold triggers + DoubleBufferFIFO m_squelchDelayLine; bool m_audioActive; //!< True if an audio signal is produced (no AGC or AGC and above threshold) NCOF m_nco; @@ -274,13 +309,14 @@ private: uint m_audioBufferFill; AudioFifo m_audioFifo; quint32 m_audioSampleRate; - UDPSink *m_udpBufferAudio; - static const int m_udpBlockSize; QMutex m_settingsMutex; void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); void applySettings(const SSBDemodSettings& settings, bool force = false); + void applyAudioSampleRate(int sampleRate); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const SSBDemodSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); }; #endif // INCLUDE_SSBDEMOD_H diff --git a/plugins/channelrx/demodssb/ssbdemodgui.cpp b/plugins/channelrx/demodssb/ssbdemodgui.cpp index 2e07bc7c1..788b1d946 100644 --- a/plugins/channelrx/demodssb/ssbdemodgui.cpp +++ b/plugins/channelrx/demodssb/ssbdemodgui.cpp @@ -9,11 +9,14 @@ #include "ui_ssbdemodgui.h" #include "dsp/spectrumvis.h" #include "dsp/dspengine.h" +#include "dsp/dspcommands.h" #include "gui/glspectrum.h" #include "gui/basicchannelsettingsdialog.h" #include "plugin/pluginapi.h" #include "util/simpleserializer.h" #include "util/db.h" +#include "gui/crightclickenabler.h" +#include "gui/audioselectdialog.h" #include "mainwindow.h" #include "ssbdemod.h" @@ -77,9 +80,41 @@ bool SSBDemodGUI::deserialize(const QByteArray& data) } } -bool SSBDemodGUI::handleMessage(const Message& message __attribute__((unused))) +bool SSBDemodGUI::handleMessage(const Message& message) { - return false; + if (SSBDemod::MsgConfigureSSBDemod::match(message)) + { + qDebug("SSBDemodGUI::handleMessage: SSBDemod::MsgConfigureSSBDemod"); + const SSBDemod::MsgConfigureSSBDemod& cfg = (SSBDemod::MsgConfigureSSBDemod&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else if (DSPConfigureAudio::match(message)) + { + qDebug("SSBDemodGUI::handleMessage: DSPConfigureAudio: %d", m_ssbDemod->getAudioSampleRate()); + applyBandwidths(5 - ui->spanLog2->value()); // will update spectrum details with new sample rate + return true; + } + else + { + return false; + } +} + +void SSBDemodGUI::handleInputMessages() +{ + Message* message; + + while ((message = getInputMessageQueue()->pop()) != 0) + { + if (handleMessage(*message)) + { + delete message; + } + } } void SSBDemodGUI::channelMarkerChangedByCursor() @@ -111,7 +146,7 @@ void SSBDemodGUI::on_audioFlipChannels_toggled(bool flip) void SSBDemodGUI::on_dsb_toggled(bool dsb) { ui->flipSidebands->setEnabled(!dsb); - applyBandwidths(); + applyBandwidths(5 - ui->spanLog2->value()); } void SSBDemodGUI::on_deltaFrequency_changed(qint64 value) @@ -123,12 +158,12 @@ void SSBDemodGUI::on_deltaFrequency_changed(qint64 value) void SSBDemodGUI::on_BW_valueChanged(int value __attribute__((unused))) { - applyBandwidths(); + applyBandwidths(5 - ui->spanLog2->value()); } void SSBDemodGUI::on_lowCut_valueChanged(int value __attribute__((unused))) { - applyBandwidths(); + applyBandwidths(5 - ui->spanLog2->value()); } void SSBDemodGUI::on_volume_valueChanged(int value) @@ -182,11 +217,11 @@ void SSBDemodGUI::on_audioMute_toggled(bool checked) void SSBDemodGUI::on_spanLog2_valueChanged(int value) { - if ((value < 1) || (value > 5)) { + if ((value < 0) || (value > 4)) { return; } - applyBandwidths(); + applyBandwidths(5 - ui->spanLog2->value()); } void SSBDemodGUI::on_flipSidebands_clicked(bool checked __attribute__((unused))) @@ -197,12 +232,6 @@ void SSBDemodGUI::on_flipSidebands_clicked(bool checked __attribute__((unused))) ui->lowCut->setValue(-lcValue); } -void SSBDemodGUI::on_copyAudioToUDP_toggled(bool checked) -{ - m_settings.m_copyAudioToUDP = checked; - applySettings(); -} - void SSBDemodGUI::onMenuDialogCalled(const QPoint &p) { BasicChannelSettingsDialog dialog(&m_channelMarker, this); @@ -210,14 +239,11 @@ void SSBDemodGUI::onMenuDialogCalled(const QPoint &p) dialog.exec(); m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); - m_settings.m_udpAddress = m_channelMarker.getUDPAddress(), - m_settings.m_udpPort = m_channelMarker.getUDPSendPort(), m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); m_settings.m_title = m_channelMarker.getTitle(); setWindowTitle(m_settings.m_title); setTitleColor(m_settings.m_rgbColor); - displayUDPAddress(); applySettings(); } @@ -249,6 +275,9 @@ SSBDemodGUI::SSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban m_ssbDemod->setMessageQueueToGUI(getInputMessageQueue()); m_ssbDemod->setSampleSink(m_spectrumVis); + CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->audioMute); + connect(audioMuteRightClickEnabler, SIGNAL(rightClick()), this, SLOT(audioSelect())); + ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999); @@ -268,8 +297,6 @@ SSBDemodGUI::SSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban m_channelMarker.setBandwidth(6000); m_channelMarker.setCenterFrequency(0); m_channelMarker.setTitle("SSB Demodulator"); - m_channelMarker.setUDPAddress("127.0.0.1"); - m_channelMarker.setUDPSendPort(9999); m_channelMarker.blockSignals(false); m_channelMarker.setVisible(true); // activate signal on the last setting only @@ -284,6 +311,7 @@ SSBDemodGUI::SSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor())); connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor())); + connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); ui->spectrumGUI->setBuddies(m_spectrumVis->getInputMessageQueue(), m_spectrumVis, ui->glSpectrum); @@ -293,7 +321,7 @@ SSBDemodGUI::SSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban m_iconDSBLSB.addPixmap(QPixmap("://lsb.png"), QIcon::Normal, QIcon::Off); displaySettings(); - applyBandwidths(true); // does applySettings(true) + applyBandwidths(5 - ui->spanLog2->value(), true); // does applySettings(true) } SSBDemodGUI::~SSBDemodGUI() @@ -316,7 +344,7 @@ void SSBDemodGUI::applySettings(bool force) if (m_doApplySettings) { SSBDemod::MsgConfigureChannelizer* channelConfigMsg = SSBDemod::MsgConfigureChannelizer::create( - 48000, m_channelMarker.getCenterFrequency()); + m_ssbDemod->getAudioSampleRate(), m_channelMarker.getCenterFrequency()); m_ssbDemod->getInputMessageQueue()->push(channelConfigMsg); SSBDemod::MsgConfigureSSBDemod* message = SSBDemod::MsgConfigureSSBDemod::create( m_settings, force); @@ -324,14 +352,14 @@ void SSBDemodGUI::applySettings(bool force) } } -void SSBDemodGUI::applyBandwidths(bool force) +void SSBDemodGUI::applyBandwidths(int spanLog2, bool force) { bool dsb = ui->dsb->isChecked(); - int spanLog2 = ui->spanLog2->value(); - m_spectrumRate = 48000 / (1<spanLog2->value(); + m_spectrumRate = m_ssbDemod->getAudioSampleRate() / (1<BW->value(); int lw = ui->lowCut->value(); - int bwMax = 480/(1<getAudioSampleRate() / (100*(1<BW->blockSignals(true); ui->dsb->setChecked(m_settings.m_dsb); - ui->spanLog2->setValue(m_settings.m_spanLog2); + ui->spanLog2->setValue(5 - m_settings.m_spanLog2); ui->BW->setValue(m_settings.m_rfBandwidth / 100.0); QString s = QString::number(m_settings.m_rfBandwidth/1000.0, 'f', 1); @@ -512,16 +539,10 @@ void SSBDemodGUI::displaySettings() ui->agcThresholdGate->setValue(m_settings.m_agcThresholdGate); s = QString::number(ui->agcThresholdGate->value(), 'f', 0); ui->agcThresholdGateText->setText(s); - ui->copyAudioToUDP->setChecked(m_settings.m_copyAudioToUDP); blockApplySettings(false); } -void SSBDemodGUI::displayUDPAddress() -{ - ui->copyAudioToUDP->setToolTip(QString("Copy audio output to UDP %1:%2").arg(m_settings.m_udpAddress).arg(m_settings.m_udpPort)); -} - void SSBDemodGUI::displayAGCPowerThreshold(int value) { if (value == SSBDemodSettings::m_minPowerThresholdDB) @@ -545,6 +566,19 @@ void SSBDemodGUI::enterEvent(QEvent*) m_channelMarker.setHighlighted(true); } +void SSBDemodGUI::audioSelect() +{ + qDebug("SSBDemodGUI::audioSelect"); + AudioSelectDialog audioSelect(DSPEngine::instance()->getAudioDeviceManager(), m_settings.m_audioDeviceName); + audioSelect.exec(); + + if (audioSelect.m_selected) + { + m_settings.m_audioDeviceName = audioSelect.m_audioDeviceName; + applySettings(); + } +} + void SSBDemodGUI::tick() { double magsqAvg, magsqPeak; diff --git a/plugins/channelrx/demodssb/ssbdemodgui.h b/plugins/channelrx/demodssb/ssbdemodgui.h index 79c15f4fa..20639178c 100644 --- a/plugins/channelrx/demodssb/ssbdemodgui.h +++ b/plugins/channelrx/demodssb/ssbdemodgui.h @@ -70,9 +70,8 @@ private: bool blockApplySettings(bool block); void applySettings(bool force = false); - void applyBandwidths(bool force = false); + void applyBandwidths(int spanLog2, bool force = false); void displaySettings(); - void displayUDPAddress(); void displayAGCPowerThreshold(int value); @@ -95,9 +94,10 @@ private slots: void on_audioMute_toggled(bool checked); void on_spanLog2_valueChanged(int value); void on_flipSidebands_clicked(bool checked); - void on_copyAudioToUDP_toggled(bool copy); void onWidgetRolled(QWidget* widget, bool rollDown); void onMenuDialogCalled(const QPoint& p); + void handleInputMessages(); + void audioSelect(); void tick(); }; diff --git a/plugins/channelrx/demodssb/ssbdemodgui.ui b/plugins/channelrx/demodssb/ssbdemodgui.ui index df8594703..a481293b3 100644 --- a/plugins/channelrx/demodssb/ssbdemodgui.ui +++ b/plugins/channelrx/demodssb/ssbdemodgui.ui @@ -6,25 +6,25 @@ 0 0 - 400 + 412 190
- + 0 0 - 400 + 412 0 - Sans Serif + Liberation Sans 9 @@ -36,13 +36,13 @@ 0 0 - 398 + 410 171 - 398 + 410 0 @@ -101,7 +101,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -273,7 +273,7 @@ - Monospace + Liberation Mono 8 @@ -283,11 +283,20 @@ - - 0 - + + + 50 + 0 + + + + + 50 + 16777215 + + Span @@ -299,10 +308,86 @@ Demod frequency span - 1 + 0 - 5 + 4 + + + 1 + + + 2 + + + 2 + + + Qt::Horizontal + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + 6.0k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + Low cut + + + + + + + + 16777215 + 16 + + + + Highpass filter cutoff frequency (SSB) + + + -60 + + + 60 1 @@ -310,24 +395,36 @@ 3 - - 3 - Qt::Horizontal - true + false - - true + + QSlider::NoTicks + + + 5 - + + + + 50 + 0 + + + + + 50 + 16777215 + + - 6.0k + 0.3k Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -555,88 +652,6 @@ - - - - - - - 50 - 0 - - - - - 50 - 16777215 - - - - Low cut - - - - - - - - 16777215 - 16 - - - - Highpass filter cutoff frequency (SSB) - - - -60 - - - 60 - - - 1 - - - 3 - - - Qt::Horizontal - - - false - - - QSlider::TicksAbove - - - 5 - - - - - - - - 50 - 0 - - - - - 50 - 16777215 - - - - 0.3k - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - @@ -698,7 +713,7 @@ - Togle AGC + Toggle AGC AGC @@ -818,7 +833,7 @@ - Power threshiold gate (ms) + Power threshold gate (ms) 20 @@ -840,7 +855,7 @@ - Power threshiold gate (ms) + Power threshold gate (ms) 00 @@ -863,16 +878,6 @@ - - - - Copy audio to UDP - - - U - - - @@ -933,7 +938,7 @@ - Monospace + Liberation Mono 8 diff --git a/plugins/channelrx/demodssb/ssbdemodsettings.cpp b/plugins/channelrx/demodssb/ssbdemodsettings.cpp index b1f805e6b..fbc4b7689 100644 --- a/plugins/channelrx/demodssb/ssbdemodsettings.cpp +++ b/plugins/channelrx/demodssb/ssbdemodsettings.cpp @@ -44,7 +44,6 @@ void SSBDemodSettings::resetToDefaults() m_audioMute = false; m_agc = false; m_agcClamping = false; - m_copyAudioToUDP = false; m_agcPowerThreshold = -40; m_agcThresholdGate = 4; m_agcTimeLog2 = 7; @@ -53,11 +52,9 @@ void SSBDemodSettings::resetToDefaults() m_volume = 3.0; m_spanLog2 = 3; m_inputFrequencyOffset = 0; - m_audioSampleRate = DSPEngine::instance()->getAudioSampleRate(); - m_udpAddress = "127.0.0.1"; - m_udpPort = 9999; m_rgbColor = QColor(0, 255, 0).rgb(); m_title = "SSB Demodulator"; + m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; } QByteArray SSBDemodSettings::serialize() const @@ -83,6 +80,7 @@ QByteArray SSBDemodSettings::serialize() const s.writeS32(14, m_agcThresholdGate); s.writeBool(15, m_agcClamping); s.writeString(16, m_title); + s.writeString(17, m_audioDeviceName); return s.final(); } @@ -127,6 +125,7 @@ bool SSBDemodSettings::deserialize(const QByteArray& data) d.readS32(14, &m_agcThresholdGate, 4); d.readBool(15, &m_agcClamping, false); d.readString(16, &m_title, "SSB Demodulator"); + d.readString(17, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName); return true; } diff --git a/plugins/channelrx/demodssb/ssbdemodsettings.h b/plugins/channelrx/demodssb/ssbdemodsettings.h index fda0e1fad..7b6ac2035 100644 --- a/plugins/channelrx/demodssb/ssbdemodsettings.h +++ b/plugins/channelrx/demodssb/ssbdemodsettings.h @@ -24,7 +24,6 @@ class Serializable; struct SSBDemodSettings { qint32 m_inputFrequencyOffset; - quint32 m_audioSampleRate; Real m_rfBandwidth; Real m_lowCutoff; Real m_volume; @@ -33,16 +32,14 @@ struct SSBDemodSettings bool m_audioFlipChannels; bool m_dsb; bool m_audioMute; - bool m_copyAudioToUDP; bool m_agc; bool m_agcClamping; int m_agcTimeLog2; int m_agcPowerThreshold; int m_agcThresholdGate; - QString m_udpAddress; - quint16 m_udpPort; quint32 m_rgbColor; QString m_title; + QString m_audioDeviceName; Serializable *m_channelMarker; Serializable *m_spectrumGUI; diff --git a/plugins/channelrx/demodssb/ssbplugin.cpp b/plugins/channelrx/demodssb/ssbplugin.cpp index c0c40dac6..9a9ab8e9c 100644 --- a/plugins/channelrx/demodssb/ssbplugin.cpp +++ b/plugins/channelrx/demodssb/ssbplugin.cpp @@ -3,12 +3,14 @@ #include #include #include "plugin/pluginapi.h" +#ifndef SERVER_MODE #include "ssbdemodgui.h" +#endif #include "ssbdemod.h" const PluginDescriptor SSBPlugin::m_pluginDescriptor = { QString("SSB Demodulator"), - QString("3.12.0"), + QString("4.1.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, @@ -34,10 +36,19 @@ void SSBPlugin::initPlugin(PluginAPI* pluginAPI) m_pluginAPI->registerRxChannel(SSBDemod::m_channelIdURI, SSBDemod::m_channelId, this); } +#ifdef SERVER_MODE +PluginInstanceGUI* SSBPlugin::createRxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSink *rxChannel __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* SSBPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) { return SSBDemodGUI::create(m_pluginAPI, deviceUISet, rxChannel); } +#endif BasebandSampleSink* SSBPlugin::createRxChannelBS(DeviceSourceAPI *deviceAPI) { diff --git a/plugins/channelrx/demodssb/ssbplugin.h b/plugins/channelrx/demodssb/ssbplugin.h index b965d5c1c..f76f8e73d 100644 --- a/plugins/channelrx/demodssb/ssbplugin.h +++ b/plugins/channelrx/demodssb/ssbplugin.h @@ -10,7 +10,7 @@ class BasebandSampleSink; class SSBPlugin : public QObject, PluginInterface { Q_OBJECT Q_INTERFACES(PluginInterface) - Q_PLUGIN_METADATA(IID "de.maintech.sdrangelove.channel.ssb") + Q_PLUGIN_METADATA(IID "sdrangel.channel.ssbdemod") public: explicit SSBPlugin(QObject* parent = NULL); diff --git a/plugins/channelrx/demodwfm/CMakeLists.txt b/plugins/channelrx/demodwfm/CMakeLists.txt index 625af826b..a2f04465c 100644 --- a/plugins/channelrx/demodwfm/CMakeLists.txt +++ b/plugins/channelrx/demodwfm/CMakeLists.txt @@ -1,5 +1,7 @@ project(wfm) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + set(wfm_SOURCES wfmdemod.cpp wfmdemodgui.cpp @@ -21,6 +23,7 @@ set(wfm_FORMS include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) #include(${QT_USE_FILE}) @@ -43,6 +46,6 @@ target_link_libraries(demodwfm sdrgui ) -qt5_use_modules(demodwfm Core Widgets) +target_link_libraries(demodwfm Qt5::Core Qt5::Widgets) -install(TARGETS demodwfm DESTINATION lib/plugins/channelrx) \ No newline at end of file +install(TARGETS demodwfm DESTINATION lib/plugins/channelrx) diff --git a/plugins/channelrx/demodwfm/demodwfm.pro b/plugins/channelrx/demodwfm/demodwfm.pro index c705ff3cb..ebb9c8859 100644 --- a/plugins/channelrx/demodwfm/demodwfm.pro +++ b/plugins/channelrx/demodwfm/demodwfm.pro @@ -18,8 +18,10 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui +INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug @@ -38,5 +40,6 @@ FORMS += wfmdemodgui.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui +LIBS += -L../../../swagger/$${build_subdir} -lswagger RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/plugins/channelrx/demodwfm/readme.md b/plugins/channelrx/demodwfm/readme.md index 64e98375a..fab4bef2d 100644 --- a/plugins/channelrx/demodwfm/readme.md +++ b/plugins/channelrx/demodwfm/readme.md @@ -10,7 +10,7 @@ This plugin can be used to listen to a wideband or narrowband FM modulated signa

1: Frequency shift from center frequency of reception

-Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

3: Channel power

@@ -38,6 +38,8 @@ This is the volume of the audio signal from 0.0 (mute) to 10.0 (maximum). It can This is the squelch threshold in dB. The average total power received in the signal bandwidth before demodulation is compared to this value and the squelch input is open above this value. It can be varied continuously in 0.1 dB steps from 0.0 to -100.0 dB using the dial button. -

9: Audio mute

+

9: Audio mute and select audio output

-Use this button to toggle audio mute for this channel. The button will light up in green if the squelch is open. This helps identifying which channels are active in a multi-channel configuration. \ No newline at end of file +Left click on this button to toggle audio mute for this channel. The button will light up in green if the squelch is open. This helps identifying which channels are active in a multi-channel configuration. + +If you right click on this button this will open a dialog to select the audio output device. diff --git a/plugins/channelrx/demodwfm/wfmdemod.cpp b/plugins/channelrx/demodwfm/wfmdemod.cpp index 66d2dd2f5..3b1d49619 100644 --- a/plugins/channelrx/demodwfm/wfmdemod.cpp +++ b/plugins/channelrx/demodwfm/wfmdemod.cpp @@ -21,19 +21,25 @@ #include #include +#include "SWGChannelSettings.h" +#include "SWGWFMDemodSettings.h" +#include "SWGChannelReport.h" +#include "SWGWFMDemodReport.h" + #include #include "dsp/threadedbasebandsamplesink.h" #include "device/devicesourceapi.h" #include "audio/audiooutput.h" #include "dsp/dspengine.h" #include "dsp/dspcommands.h" +#include "util/db.h" #include "wfmdemod.h" MESSAGE_CLASS_DEFINITION(WFMDemod::MsgConfigureWFMDemod, Message) MESSAGE_CLASS_DEFINITION(WFMDemod::MsgConfigureChannelizer, Message) -const QString WFMDemod::m_channelIdURI = "de.maintech.sdrangelove.channel.wfm"; +const QString WFMDemod::m_channelIdURI = "sdrangel.channel.wfmdemod"; const QString WFMDemod::m_channelId = "WFMDemod"; const int WFMDemod::m_udpBlockSize = 512; @@ -58,33 +64,27 @@ WFMDemod::WFMDemod(DeviceSourceAPI* deviceAPI) : m_audioBuffer.resize(16384); m_audioBufferFill = 0; - DSPEngine::instance()->addAudioSink(&m_audioFifo); - m_udpBufferAudio = new UDPSink(this, m_udpBlockSize, m_settings.m_udpPort); + DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo, getInputMessageQueue()); + m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate(); + + applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); m_channelizer = new DownChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); m_deviceAPI->addThreadedSink(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - - applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); - applySettings(m_settings, true); } WFMDemod::~WFMDemod() { - if (m_rfFilter) - { - delete m_rfFilter; - } - - DSPEngine::instance()->removeAudioSink(&m_audioFifo); + DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo); m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSink(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; - - delete m_udpBufferAudio; + delete m_rfFilter; } void WFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst __attribute__((unused))) @@ -107,57 +107,54 @@ void WFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto for (int i = 0 ; i < rf_out; i++) { - demod = m_phaseDiscri.phaseDiscriminatorDelta(rf[i], msq, fmDev); + msq = rf[i].real()*rf[i].real() + rf[i].imag()*rf[i].imag(); Real magsq = msq / (SDR_RX_SCALED*SDR_RX_SCALED); + m_magsqSum += magsq; + m_movingAverage(magsq); - m_movingAverage(magsq); - m_magsqSum += magsq; - - if (magsq > m_magsqPeak) - { + if (magsq > m_magsqPeak) { m_magsqPeak = magsq; } m_magsqCount++; - if((Real) m_movingAverage >= m_squelchLevel) - m_squelchState = m_settings.m_rfBandwidth / 20; // decay rate - - if (m_squelchState > 0) - { - m_squelchState--; - m_squelchOpen = true; - } - else - { - demod = 0; - m_squelchOpen = false; - } - - if (m_settings.m_audioMute) + if (magsq >= m_squelchLevel) { + if (m_squelchState < m_settings.m_rfBandwidth / 10) { // twice attack and decay rate + m_squelchState++; + } + } + else + { + if (m_squelchState > 0) { + m_squelchState--; + } + } + + m_squelchOpen = (m_squelchState > (m_settings.m_rfBandwidth / 20)); + + if (m_squelchOpen && !m_settings.m_audioMute) { // squelch open and not mute + demod = m_phaseDiscri.phaseDiscriminatorDelta(rf[i], msq, fmDev); + } else { demod = 0; } Complex e(demod, 0); - if(m_interpolator.decimate(&m_interpolatorDistanceRemain, e, &ci)) + if (m_interpolator.decimate(&m_interpolatorDistanceRemain, e, &ci)) { qint16 sample = (qint16)(ci.real() * 3276.8f * m_settings.m_volume); m_sampleBuffer.push_back(Sample(sample, sample)); m_audioBuffer[m_audioBufferFill].l = sample; m_audioBuffer[m_audioBufferFill].r = sample; - if (m_settings.m_copyAudioToUDP) { m_udpBufferAudio->write(sample); } - ++m_audioBufferFill; if(m_audioBufferFill >= m_audioBuffer.size()) { - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 1); + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); - if(res != m_audioBufferFill) - { + if (res != m_audioBufferFill) { qDebug("WFMDemod::feed: %u/%u audio samples written", res, m_audioBufferFill); } @@ -169,12 +166,11 @@ void WFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto } } - if(m_audioBufferFill > 0) + if (m_audioBufferFill > 0) { - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 1); + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); - if(res != m_audioBufferFill) - { + if (res != m_audioBufferFill) { qDebug("WFMDemod::feed: %u/%u tail samples written", res, m_audioBufferFill); } @@ -232,6 +228,27 @@ bool WFMDemod::handleMessage(const Message& cmd) return true; } + else if (BasebandSampleSink::MsgThreadedSink::match(cmd)) + { + BasebandSampleSink::MsgThreadedSink& cfg = (BasebandSampleSink::MsgThreadedSink&) cmd; + const QThread *thread = cfg.getThread(); + qDebug("WFMDemod::handleMessage: BasebandSampleSink::MsgThreadedSink: %p", thread); + return true; + } + else if (DSPConfigureAudio::match(cmd)) + { + DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd; + uint32_t sampleRate = cfg.getSampleRate(); + + qDebug() << "WFMDemod::handleMessage: DSPConfigureAudio:" + << " sampleRate: " << sampleRate; + + if (sampleRate != m_audioSampleRate) { + applyAudioSampleRate(sampleRate); + } + + return true; + } else if (DSPSignalNotification::match(cmd)) { return true; @@ -242,6 +259,21 @@ bool WFMDemod::handleMessage(const Message& cmd) } } +void WFMDemod::applyAudioSampleRate(int sampleRate) +{ + qDebug("WFMDemod::applyAudioSampleRate: %d", sampleRate); + + m_settingsMutex.lock(); + + m_interpolator.create(16, m_inputSampleRate, m_settings.m_afBandwidth); + m_interpolatorDistanceRemain = (Real) m_inputSampleRate / sampleRate; + m_interpolatorDistance = (Real) m_inputSampleRate / (Real) sampleRate; + + m_settingsMutex.unlock(); + + m_audioSampleRate = sampleRate; +} + void WFMDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force) { qDebug() << "WFMDemod::applyChannelSettings:" @@ -257,9 +289,11 @@ void WFMDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffse if ((inputSampleRate != m_inputSampleRate) || force) { qDebug() << "WFMDemod::applyChannelSettings: m_interpolator.create"; + m_settingsMutex.lock(); m_interpolator.create(16, inputSampleRate, m_settings.m_afBandwidth); - m_interpolatorDistanceRemain = (Real) inputSampleRate / (Real) m_settings.m_audioSampleRate; - m_interpolatorDistance = (Real) inputSampleRate / (Real) m_settings.m_audioSampleRate; + m_interpolatorDistanceRemain = (Real) inputSampleRate / (Real) m_audioSampleRate; + m_interpolatorDistance = (Real) inputSampleRate / (Real) m_audioSampleRate; + m_settingsMutex.unlock(); qDebug() << "WFMDemod::applySettings: m_rfFilter->create_filter"; Real lowCut = -(m_settings.m_rfBandwidth / 2.0) / inputSampleRate; Real hiCut = (m_settings.m_rfBandwidth / 2.0) / inputSampleRate; @@ -281,20 +315,17 @@ void WFMDemod::applySettings(const WFMDemodSettings& settings, bool force) << " m_afBandwidth: " << settings.m_afBandwidth << " m_volume: " << settings.m_volume << " m_squelch: " << settings.m_squelch - << " m_copyAudioToUDP: " << settings.m_copyAudioToUDP - << " m_udpAddress: " << settings.m_udpAddress - << " m_udpPort: " << settings.m_udpPort + << " m_audioDeviceName: " << settings.m_audioDeviceName << " force: " << force; - if((settings.m_audioSampleRate != m_settings.m_audioSampleRate) || - (settings.m_afBandwidth != m_settings.m_afBandwidth) || - (settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) + if((settings.m_afBandwidth != m_settings.m_afBandwidth) || + (settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) { m_settingsMutex.lock(); qDebug() << "WFMDemod::applySettings: m_interpolator.create"; m_interpolator.create(16, m_inputSampleRate, settings.m_afBandwidth); - m_interpolatorDistanceRemain = (Real) m_inputSampleRate / (Real) settings.m_audioSampleRate; - m_interpolatorDistance = (Real) m_inputSampleRate / (Real) settings.m_audioSampleRate; + m_interpolatorDistanceRemain = (Real) m_inputSampleRate / (Real) m_audioSampleRate; + m_interpolatorDistance = (Real) m_inputSampleRate / (Real) m_audioSampleRate; qDebug() << "WFMDemod::applySettings: m_rfFilter->create_filter"; Real lowCut = -(settings.m_rfBandwidth / 2.0) / m_inputSampleRate; Real hiCut = (settings.m_rfBandwidth / 2.0) / m_inputSampleRate; @@ -308,15 +339,20 @@ void WFMDemod::applySettings(const WFMDemodSettings& settings, bool force) if ((settings.m_squelch != m_settings.m_squelch) || force) { qDebug() << "WFMDemod::applySettings: set m_squelchLevel"; - m_squelchLevel = pow(10.0, settings.m_squelch / 20.0); - m_squelchLevel *= m_squelchLevel; + m_squelchLevel = pow(10.0, settings.m_squelch / 10.0); } - if ((m_settings.m_udpAddress != settings.m_udpAddress) - || (m_settings.m_udpPort != settings.m_udpPort) || force) + if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) { - m_udpBufferAudio->setAddress(const_cast(settings.m_udpAddress)); - m_udpBufferAudio->setPort(settings.m_udpPort); + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName); + //qDebug("AMDemod::applySettings: audioDeviceName: %s audioDeviceIndex: %d", qPrintable(settings.m_audioDeviceName), audioDeviceIndex); + audioDeviceManager->addAudioSink(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex); + uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex); + + if (m_audioSampleRate != audioSampleRate) { + applyAudioSampleRate(audioSampleRate); + } } m_settings = settings; @@ -344,3 +380,119 @@ bool WFMDemod::deserialize(const QByteArray& data) } } +int WFMDemod::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setWfmDemodSettings(new SWGSDRangel::SWGWFMDemodSettings()); + response.getWfmDemodSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int WFMDemod::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + WFMDemodSettings settings = m_settings; + bool frequencyOffsetChanged = false; + + if (channelSettingsKeys.contains("inputFrequencyOffset")) + { + settings.m_inputFrequencyOffset = response.getWfmDemodSettings()->getInputFrequencyOffset(); + frequencyOffsetChanged = true; + } + if (channelSettingsKeys.contains("rfBandwidth")) { + settings.m_rfBandwidth = response.getWfmDemodSettings()->getRfBandwidth(); + } + if (channelSettingsKeys.contains("afBandwidth")) { + settings.m_afBandwidth = response.getWfmDemodSettings()->getAfBandwidth(); + } + if (channelSettingsKeys.contains("volume")) { + settings.m_volume = response.getWfmDemodSettings()->getVolume(); + } + if (channelSettingsKeys.contains("squelch")) { + settings.m_squelch = response.getWfmDemodSettings()->getSquelch(); + } + if (channelSettingsKeys.contains("audioMute")) { + settings.m_audioMute = response.getWfmDemodSettings()->getAudioMute() != 0; + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getWfmDemodSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getWfmDemodSettings()->getTitle(); + } + if (channelSettingsKeys.contains("audioDeviceName")) { + settings.m_audioDeviceName = *response.getWfmDemodSettings()->getAudioDeviceName(); + } + + if (frequencyOffsetChanged) + { + MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( + requiredBW(settings.m_rfBandwidth), settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(channelConfigMsg); + } + + MsgConfigureWFMDemod *msg = MsgConfigureWFMDemod::create(settings, force); + m_inputMessageQueue.push(msg); + + qDebug("WFMDemod::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureWFMDemod *msgToGUI = MsgConfigureWFMDemod::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int WFMDemod::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setWfmDemodReport(new SWGSDRangel::SWGWFMDemodReport()); + response.getWfmDemodReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + +void WFMDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const WFMDemodSettings& settings) +{ + response.getWfmDemodSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getWfmDemodSettings()->setRfBandwidth(settings.m_rfBandwidth); + response.getWfmDemodSettings()->setAfBandwidth(settings.m_afBandwidth); + response.getWfmDemodSettings()->setVolume(settings.m_volume); + response.getWfmDemodSettings()->setSquelch(settings.m_squelch); + response.getWfmDemodSettings()->setAudioMute(settings.m_audioMute ? 1 : 0); + response.getWfmDemodSettings()->setRgbColor(settings.m_rgbColor); + + if (response.getWfmDemodSettings()->getTitle()) { + *response.getWfmDemodSettings()->getTitle() = settings.m_title; + } else { + response.getWfmDemodSettings()->setTitle(new QString(settings.m_title)); + } + + if (response.getWfmDemodSettings()->getAudioDeviceName()) { + *response.getWfmDemodSettings()->getAudioDeviceName() = settings.m_audioDeviceName; + } else { + response.getWfmDemodSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName)); + } +} + +void WFMDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + double magsqAvg, magsqPeak; + int nbMagsqSamples; + getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples); + + response.getWfmDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg)); + response.getWfmDemodReport()->setSquelch(m_squelchState > 0 ? 1 : 0); + response.getWfmDemodReport()->setAudioSampleRate(m_audioSampleRate); + response.getWfmDemodReport()->setChannelSampleRate(m_inputSampleRate); +} + diff --git a/plugins/channelrx/demodwfm/wfmdemod.h b/plugins/channelrx/demodwfm/wfmdemod.h index 7cb60b43b..fa707a600 100644 --- a/plugins/channelrx/demodwfm/wfmdemod.h +++ b/plugins/channelrx/demodwfm/wfmdemod.h @@ -109,19 +109,59 @@ public: void getMagSqLevels(double& avg, double& peak, int& nbSamples) { - avg = m_magsqCount == 0 ? 1e-10 : m_magsqSum / m_magsqCount; - m_magsq = avg; - peak = m_magsqPeak == 0.0 ? 1e-10 : m_magsqPeak; + if (m_magsqCount > 0) + { + m_magsq = m_magsqSum / m_magsqCount; + m_magSqLevelStore.m_magsq = m_magsq; + m_magSqLevelStore.m_magsqPeak = m_magsqPeak; + } + + avg = m_magSqLevelStore.m_magsq; + peak = m_magSqLevelStore.m_magsqPeak; nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; + m_magsqSum = 0.0f; m_magsqPeak = 0.0f; m_magsqCount = 0; } + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + + static int requiredBW(int rfBW) + { + if (rfBW <= 48000) { + return 48000; + } else { + return (3*rfBW)/2; + } + } + static const QString m_channelIdURI; static const QString m_channelId; private: + struct MagSqLevelsStore + { + MagSqLevelsStore() : + m_magsq(1e-12), + m_magsqPeak(1e-12) + {} + double m_magsq; + double m_magsqPeak; + }; + enum RateState { RSInitialFill, RSRunning @@ -134,6 +174,7 @@ private: int m_inputSampleRate; int m_inputFrequencyOffset; WFMDemodSettings m_settings; + quint32 m_audioSampleRate; NCO m_nco; Interpolator m_interpolator; //!< Interpolator between sample rate sent from DSP engine and requested RF bandwidth (rational) @@ -148,14 +189,13 @@ private: double m_magsqSum; double m_magsqPeak; int m_magsqCount; + MagSqLevelsStore m_magSqLevelStore; - Real m_lastArgument; MovingAverageUtil m_movingAverage; Real m_fmExcursion; AudioVector m_audioBuffer; uint m_audioBufferFill; - UDPSink *m_udpBufferAudio; AudioFifo m_audioFifo; SampleVector m_sampleBuffer; @@ -165,8 +205,12 @@ private: static const int m_udpBlockSize; + void applyAudioSampleRate(int sampleRate); void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); void applySettings(const WFMDemodSettings& settings, bool force = false); + + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const WFMDemodSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); }; #endif // INCLUDE_WFMDEMOD_H diff --git a/plugins/channelrx/demodwfm/wfmdemodgui.cpp b/plugins/channelrx/demodwfm/wfmdemodgui.cpp index 3280f8420..77ec241dd 100644 --- a/plugins/channelrx/demodwfm/wfmdemodgui.cpp +++ b/plugins/channelrx/demodwfm/wfmdemodgui.cpp @@ -13,6 +13,8 @@ #include "util/simpleserializer.h" #include "util/db.h" #include "gui/basicchannelsettingsdialog.h" +#include "gui/crightclickenabler.h" +#include "gui/audioselectdialog.h" #include "mainwindow.h" #include "wfmdemod.h" @@ -75,7 +77,33 @@ bool WFMDemodGUI::deserialize(const QByteArray& data) bool WFMDemodGUI::handleMessage(const Message& message __attribute__((unused))) { - return false; + if (WFMDemod::MsgConfigureWFMDemod::match(message)) + { + qDebug("WFMDemodGUI::handleMessage: WFMDemod::MsgConfigureWFMDemod"); + const WFMDemod::MsgConfigureWFMDemod& cfg = (WFMDemod::MsgConfigureWFMDemod&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else + { + return false; + } +} + +void WFMDemodGUI::handleInputMessages() +{ + Message* message; + + while ((message = getInputMessageQueue()->pop()) != 0) + { + if (handleMessage(*message)) + { + delete message; + } + } } void WFMDemodGUI::channelMarkerChangedByCursor() @@ -131,12 +159,6 @@ void WFMDemodGUI::on_audioMute_toggled(bool checked) applySettings(); } -void WFMDemodGUI::on_copyAudioToUDP_toggled(bool checked) -{ - m_settings.m_copyAudioToUDP = checked; - applySettings(); -} - void WFMDemodGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rollDown __attribute__((unused))) { } @@ -148,14 +170,11 @@ void WFMDemodGUI::onMenuDialogCalled(const QPoint &p) dialog.exec(); m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); - m_settings.m_udpAddress = m_channelMarker.getUDPAddress(), - m_settings.m_udpPort = m_channelMarker.getUDPSendPort(), m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); m_settings.m_title = m_channelMarker.getTitle(); setWindowTitle(m_settings.m_title); setTitleColor(m_settings.m_rgbColor); - displayUDPAddress(); applySettings(); } @@ -172,11 +191,16 @@ WFMDemodGUI::WFMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban setAttribute(Qt::WA_DeleteOnClose, true); connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); + connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); m_wfmDemod = (WFMDemod*) rxChannel; //new WFMDemod(m_deviceUISet->m_deviceSourceAPI); + m_wfmDemod->setMessageQueueToGUI(getInputMessageQueue()); connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); + CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->audioMute); + connect(audioMuteRightClickEnabler, SIGNAL(rightClick()), this, SLOT(audioSelect())); + ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999); @@ -194,8 +218,6 @@ WFMDemodGUI::WFMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban m_channelMarker.setBandwidth(WFMDemodSettings::getRFBW(4)); m_channelMarker.setCenterFrequency(0); m_channelMarker.setTitle("WFM Demodulator"); - m_channelMarker.setUDPAddress("127.0.0.1"); - m_channelMarker.setUDPSendPort(9999); m_channelMarker.setColor(m_settings.m_rgbColor); m_channelMarker.blockSignals(false); m_channelMarker.setVisible(true); // activate signal on the last setting only @@ -232,7 +254,7 @@ void WFMDemodGUI::applySettings(bool force) if (m_doApplySettings) { WFMDemod::MsgConfigureChannelizer *msgChan = WFMDemod::MsgConfigureChannelizer::create( - requiredBW(WFMDemodSettings::getRFBW(ui->rfBW->currentIndex())), + WFMDemod::requiredBW(WFMDemodSettings::getRFBW(ui->rfBW->currentIndex())), m_channelMarker.getCenterFrequency()); m_wfmDemod->getInputMessageQueue()->push(msgChan); @@ -252,7 +274,6 @@ void WFMDemodGUI::displaySettings() setTitleColor(m_settings.m_rgbColor); setWindowTitle(m_channelMarker.getTitle()); - displayUDPAddress(); blockApplySettings(true); @@ -269,12 +290,9 @@ void WFMDemodGUI::displaySettings() ui->squelch->setValue(m_settings.m_squelch); ui->squelchText->setText(QString("%1 dB").arg(m_settings.m_squelch)); - blockApplySettings(false); -} + ui->audioMute->setChecked(m_settings.m_audioMute); -void WFMDemodGUI::displayUDPAddress() -{ - ui->copyAudioToUDP->setToolTip(QString("Copy audio output to UDP %1:%2").arg(m_settings.m_udpAddress).arg(m_settings.m_udpPort)); + blockApplySettings(false); } void WFMDemodGUI::leaveEvent(QEvent*) @@ -287,6 +305,19 @@ void WFMDemodGUI::enterEvent(QEvent*) m_channelMarker.setHighlighted(true); } +void WFMDemodGUI::audioSelect() +{ + qDebug("WFMDemodGUI::audioSelect"); + AudioSelectDialog audioSelect(DSPEngine::instance()->getAudioDeviceManager(), m_settings.m_audioDeviceName); + audioSelect.exec(); + + if (audioSelect.m_selected) + { + m_settings.m_audioDeviceName = audioSelect.m_audioDeviceName; + applySettings(); + } +} + void WFMDemodGUI::tick() { double magsqAvg, magsqPeak; diff --git a/plugins/channelrx/demodwfm/wfmdemodgui.h b/plugins/channelrx/demodwfm/wfmdemodgui.h index a9b85ba32..c7998cc8e 100644 --- a/plugins/channelrx/demodwfm/wfmdemodgui.h +++ b/plugins/channelrx/demodwfm/wfmdemodgui.h @@ -59,20 +59,10 @@ private: void blockApplySettings(bool block); void applySettings(bool force = false); void displaySettings(); - void displayUDPAddress(); void leaveEvent(QEvent*); void enterEvent(QEvent*); - static int requiredBW(int rfBW) - { - if (rfBW <= 48000) { - return 48000; - } else { - return (3*rfBW)/2; - } - } - private slots: void on_deltaFrequency_changed(qint64 value); void on_rfBW_currentIndexChanged(int index); @@ -80,9 +70,10 @@ private slots: void on_volume_valueChanged(int value); void on_squelch_valueChanged(int value); void on_audioMute_toggled(bool checked); - void on_copyAudioToUDP_toggled(bool copy); void onWidgetRolled(QWidget* widget, bool rollDown); void onMenuDialogCalled(const QPoint& p); + void handleInputMessages(); + void audioSelect(); void tick(); }; diff --git a/plugins/channelrx/demodwfm/wfmdemodgui.ui b/plugins/channelrx/demodwfm/wfmdemodgui.ui index ab6ee3888..3373fd7a7 100644 --- a/plugins/channelrx/demodwfm/wfmdemodgui.ui +++ b/plugins/channelrx/demodwfm/wfmdemodgui.ui @@ -24,7 +24,7 @@
- Sans Serif + Liberation Sans 9 @@ -107,7 +107,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -195,7 +195,7 @@ - Monospace + Liberation Mono 8 @@ -374,18 +374,11 @@
- - - - Copy audio to UDP - - - U - - - + + Left: audio mute Right: select audio device + @@ -423,11 +416,6 @@
gui/valuedialz.h
1 - - ButtonSwitch - QToolButton -
gui/buttonswitch.h
-
diff --git a/plugins/channelrx/demodwfm/wfmdemodsettings.cpp b/plugins/channelrx/demodwfm/wfmdemodsettings.cpp index 104c293ad..ed50294d9 100644 --- a/plugins/channelrx/demodwfm/wfmdemodsettings.cpp +++ b/plugins/channelrx/demodwfm/wfmdemodsettings.cpp @@ -42,12 +42,9 @@ void WFMDemodSettings::resetToDefaults() m_volume = 2.0; m_squelch = -60.0; m_audioMute = false; - m_audioSampleRate = DSPEngine::instance()->getAudioSampleRate(); - m_copyAudioToUDP = false; - m_udpAddress = "127.0.0.1"; - m_udpPort = 9999; m_rgbColor = QColor(0, 0, 255).rgb(); m_title = "WFM Demodulator"; + m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; } QByteArray WFMDemodSettings::serialize() const @@ -60,11 +57,13 @@ QByteArray WFMDemodSettings::serialize() const s.writeS32(5, m_squelch); s.writeU32(7, m_rgbColor); s.writeString(8, m_title); + s.writeString(9, m_audioDeviceName); if (m_channelMarker) { s.writeBlob(11, m_channelMarker->serialize()); } + return s.final(); } @@ -96,6 +95,7 @@ bool WFMDemodSettings::deserialize(const QByteArray& data) m_squelch = tmp; d.readU32(7, &m_rgbColor); d.readString(8, &m_title, "WFM Demodulator"); + d.readString(9, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName); d.readBlob(11, &bytetmp); diff --git a/plugins/channelrx/demodwfm/wfmdemodsettings.h b/plugins/channelrx/demodwfm/wfmdemodsettings.h index 9c643d12b..96e62c944 100644 --- a/plugins/channelrx/demodwfm/wfmdemodsettings.h +++ b/plugins/channelrx/demodwfm/wfmdemodsettings.h @@ -30,12 +30,9 @@ struct WFMDemodSettings Real m_volume; Real m_squelch; bool m_audioMute; - quint32 m_audioSampleRate; - bool m_copyAudioToUDP; - QString m_udpAddress; - quint16 m_udpPort; quint32 m_rgbColor; QString m_title; + QString m_audioDeviceName; Serializable *m_channelMarker; diff --git a/plugins/channelrx/demodwfm/wfmplugin.cpp b/plugins/channelrx/demodwfm/wfmplugin.cpp index 062f2490d..55c366b16 100644 --- a/plugins/channelrx/demodwfm/wfmplugin.cpp +++ b/plugins/channelrx/demodwfm/wfmplugin.cpp @@ -3,12 +3,14 @@ #include #include "plugin/pluginapi.h" +#ifndef SERVER_MODE #include "wfmdemodgui.h" +#endif #include "wfmdemod.h" const PluginDescriptor WFMPlugin::m_pluginDescriptor = { QString("WFM Demodulator"), - QString("3.12.0"), + QString("4.1.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, @@ -34,10 +36,19 @@ void WFMPlugin::initPlugin(PluginAPI* pluginAPI) m_pluginAPI->registerRxChannel(WFMDemod::m_channelIdURI, WFMDemod::m_channelId, this); } +#ifdef SERVER_MODE +PluginInstanceGUI* WFMPlugin::createRxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSink *rxChannel __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* WFMPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) { return WFMDemodGUI::create(m_pluginAPI, deviceUISet, rxChannel); } +#endif BasebandSampleSink* WFMPlugin::createRxChannelBS(DeviceSourceAPI *deviceAPI) { diff --git a/plugins/channelrx/demodwfm/wfmplugin.h b/plugins/channelrx/demodwfm/wfmplugin.h index c2676ce11..5b5e3be2a 100644 --- a/plugins/channelrx/demodwfm/wfmplugin.h +++ b/plugins/channelrx/demodwfm/wfmplugin.h @@ -10,7 +10,7 @@ class BasebandSampleSink; class WFMPlugin : public QObject, PluginInterface { Q_OBJECT Q_INTERFACES(PluginInterface) - Q_PLUGIN_METADATA(IID "de.maintech.sdrangelove.channel.wfm") + Q_PLUGIN_METADATA(IID "sdrangel.channel.wfmdemod") public: explicit WFMPlugin(QObject* parent = NULL); diff --git a/plugins/channelrx/tcpsrc/CMakeLists.txt b/plugins/channelrx/tcpsrc/CMakeLists.txt deleted file mode 100644 index 6986907b6..000000000 --- a/plugins/channelrx/tcpsrc/CMakeLists.txt +++ /dev/null @@ -1,48 +0,0 @@ -project(tcpsrc) - -set(tcpsrc_SOURCES - tcpsrc.cpp - tcpsrcgui.cpp - tcpsrcplugin.cpp - tcpsrcsettings.cpp -) - -set(tcpsrc_HEADERS - tcpsrc.h - tcpsrcgui.h - tcpsrcplugin.h - tcpsrcsettings.h -) - -set(tcpsrc_FORMS - tcpsrcgui.ui -) - -include_directories( - . - ${CMAKE_CURRENT_BINARY_DIR} -) - -#include(${QT_USE_FILE}) -add_definitions(${QT_DEFINITIONS}) -add_definitions(-DQT_PLUGIN) -add_definitions(-DQT_SHARED) - -#qt5_wrap_cpp(tcpsrc_HEADERS_MOC ${tcpsrc_HEADERS}) -qt5_wrap_ui(tcpsrc_FORMS_HEADERS ${tcpsrc_FORMS}) - -add_library(demodtcpsrc SHARED - ${tcpsrc_SOURCES} - ${tcpsrc_HEADERS_MOC} - ${tcpsrc_FORMS_HEADERS} -) - -target_link_libraries(demodtcpsrc - ${QT_LIBRARIES} - sdrbase - sdrgui -) - -qt5_use_modules(demodtcpsrc Core Widgets Network) - -install(TARGETS demodtcpsrc DESTINATION lib/plugins/channelrx) diff --git a/plugins/channelrx/tcpsrc/tcpsrc.cpp b/plugins/channelrx/tcpsrc/tcpsrc.cpp deleted file mode 100644 index 6b3d7a49a..000000000 --- a/plugins/channelrx/tcpsrc/tcpsrc.cpp +++ /dev/null @@ -1,488 +0,0 @@ -// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // -// (C) 2015 John Greb // -// // -// This program is free software; you can redistribute it and/or modify // -// it under the terms of the GNU General Public License as published by // -// the Free Software Foundation as version 3 of the License, or // -// // -// This program is distributed in the hope that it will be useful, // -// but WITHOUT ANY WARRANTY; without even the implied warranty of // -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // -// GNU General Public License V3 for more details. // -// // -// You should have received a copy of the GNU General Public License // -// along with this program. If not, see . // -/////////////////////////////////////////////////////////////////////////////////// - -#include "tcpsrc.h" - -#include -#include - -#include "dsp/downchannelizer.h" -#include "dsp/threadedbasebandsamplesink.h" -#include "dsp/dspcommands.h" -#include "device/devicesourceapi.h" - -#include "tcpsrcgui.h" - -MESSAGE_CLASS_DEFINITION(TCPSrc::MsgConfigureTCPSrc, Message) -MESSAGE_CLASS_DEFINITION(TCPSrc::MsgConfigureChannelizer, Message) -MESSAGE_CLASS_DEFINITION(TCPSrc::MsgTCPSrcConnection, Message) -MESSAGE_CLASS_DEFINITION(TCPSrc::MsgTCPSrcSpectrum, Message) - -const QString TCPSrc::m_channelIdURI = "sdrangel.channel.tcpsrc"; -const QString TCPSrc::m_channelId = "TCPSrc"; - -TCPSrc::TCPSrc(DeviceSourceAPI* deviceAPI) : - ChannelSinkAPI(m_channelIdURI), - m_deviceAPI(deviceAPI), - m_absoluteFrequencyOffset(0), - m_settingsMutex(QMutex::Recursive) -{ - setObjectName(m_channelId); - - m_inputSampleRate = 96000; - m_sampleFormat = TCPSrcSettings::FormatSSB; - m_outputSampleRate = 48000; - m_rfBandwidth = 32000; - m_tcpServer = 0; - m_tcpPort = 9999; - m_nco.setFreq(0, m_inputSampleRate); - m_interpolator.create(16, m_inputSampleRate, m_rfBandwidth / 2.0); - m_sampleDistanceRemain = m_inputSampleRate / m_outputSampleRate; - m_spectrum = 0; - m_spectrumEnabled = false; - m_nextSSBId = 0; - m_nextS16leId = 0; - - m_last = 0; - m_this = 0; - m_scale = 0; - m_volume = 0; - m_magsq = 0; - - m_sampleBufferSSB.resize(tcpFftLen); - TCPFilter = new fftfilt(0.3 / 48.0, 16.0 / 48.0, tcpFftLen); - - m_channelizer = new DownChannelizer(this); - m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); - m_deviceAPI->addThreadedSink(m_threadedChannelizer); - m_deviceAPI->addChannelAPI(this); -} - -TCPSrc::~TCPSrc() -{ - if (TCPFilter) delete TCPFilter; - - m_deviceAPI->removeChannelAPI(this); - m_deviceAPI->removeThreadedSink(m_threadedChannelizer); - delete m_threadedChannelizer; - delete m_channelizer; -} - -void TCPSrc::setSpectrum(MessageQueue* messageQueue, bool enabled) -{ - Message* cmd = MsgTCPSrcSpectrum::create(enabled); - messageQueue->push(cmd); -} - -void TCPSrc::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly) -{ - Complex ci; - fftfilt::cmplx* sideband; - Real l, r; - - m_sampleBuffer.clear(); - - m_settingsMutex.lock(); - - // Rtl-Sdr uses full 16-bit scale; FCDPP does not - int rescale = (1 << m_volume); - - for(SampleVector::const_iterator it = begin; it < end; ++it) { - Complex c(it->real(), it->imag()); - c *= m_nco.nextIQ(); - - if(m_interpolator.decimate(&m_sampleDistanceRemain, c, &ci)) - { - m_magsq = ((ci.real()*ci.real() + ci.imag()*ci.imag())*rescale*rescale) / (SDR_RX_SCALED*SDR_RX_SCALED); - m_sampleBuffer.push_back(Sample(ci.real() * rescale, ci.imag() * rescale)); - m_sampleDistanceRemain += m_inputSampleRate / m_outputSampleRate; - } - } - - if((m_spectrum != 0) && (m_spectrumEnabled)) - { - m_spectrum->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), positiveOnly); - } - - for(int i = 0; i < m_s16leSockets.count(); i++) - { - m_s16leSockets[i].socket->write((const char*)&m_sampleBuffer[0], m_sampleBuffer.size() * 4); - } - - if((m_sampleFormat == TCPSrcSettings::FormatSSB) && (m_ssbSockets.count() > 0)) { - for(SampleVector::const_iterator it = m_sampleBuffer.begin(); it != m_sampleBuffer.end(); ++it) { - //Complex cj(it->real() / 30000.0, it->imag() / 30000.0); - Complex cj(it->real(), it->imag()); - int n_out = TCPFilter->runSSB(cj, &sideband, true); - if (n_out) { - for (int i = 0; i < n_out; i+=2) { - //l = (sideband[i].real() + sideband[i].imag()) * 0.7 * 32000.0; - //r = (sideband[i+1].real() + sideband[i+1].imag()) * 0.7 * 32000.0; - l = (sideband[i].real() + sideband[i].imag()) * 0.7; - r = (sideband[i+1].real() + sideband[i+1].imag()) * 0.7; - m_sampleBufferSSB.push_back(Sample(l, r)); - } - for(int i = 0; i < m_ssbSockets.count(); i++) - m_ssbSockets[i].socket->write((const char*)&m_sampleBufferSSB[0], n_out * 2); - m_sampleBufferSSB.clear(); - } - } - } - - if((m_sampleFormat == TCPSrcSettings::FormatNFM) && (m_ssbSockets.count() > 0)) { - for(SampleVector::const_iterator it = m_sampleBuffer.begin(); it != m_sampleBuffer.end(); ++it) { - Complex cj(it->real() / SDR_RX_SCALEF, it->imag() / SDR_RX_SCALEF); - // An FFT filter here is overkill, but was already set up for SSB - int n_out = TCPFilter->runFilt(cj, &sideband); - if (n_out) { - Real sum = 1.0; - for (int i = 0; i < n_out; i+=2) { - l = m_this.real() * (m_last.imag() - sideband[i].imag()) - - m_this.imag() * (m_last.real() - sideband[i].real()); - m_last = sideband[i]; - r = m_last.real() * (m_this.imag() - sideband[i+1].imag()) - - m_last.imag() * (m_this.real() - sideband[i+1].real()); - m_this = sideband[i+1]; - m_sampleBufferSSB.push_back(Sample(l * m_scale, r * m_scale)); - sum += m_this.real() * m_this.real() + m_this.imag() * m_this.imag(); - } - // TODO: correct levels - m_scale = 24000 * tcpFftLen / sum; - for(int i = 0; i < m_ssbSockets.count(); i++) - m_ssbSockets[i].socket->write((const char*)&m_sampleBufferSSB[0], n_out * 2); - m_sampleBufferSSB.clear(); - } - } - } - - m_settingsMutex.unlock(); -} - -void TCPSrc::start() -{ - m_tcpServer = new QTcpServer(); - connect(m_tcpServer, SIGNAL(newConnection()), this, SLOT(onNewConnection())); - connect(m_tcpServer, SIGNAL(acceptError(QAbstractSocket::SocketError)), this, SLOT(onTcpServerError(QAbstractSocket::SocketError))); - m_tcpServer->listen(QHostAddress::Any, m_tcpPort); -} - -void TCPSrc::stop() -{ - closeAllSockets(&m_ssbSockets); - closeAllSockets(&m_s16leSockets); - - if(m_tcpServer->isListening()) - m_tcpServer->close(); - delete m_tcpServer; -} - -bool TCPSrc::handleMessage(const Message& cmd) -{ - if (DownChannelizer::MsgChannelizerNotification::match(cmd)) - { - DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd; - - m_settingsMutex.lock(); - - m_inputSampleRate = notif.getSampleRate(); - m_nco.setFreq(-notif.getFrequencyOffset(), m_inputSampleRate); - m_interpolator.create(16, m_inputSampleRate, m_rfBandwidth / 2.0); - m_sampleDistanceRemain = m_inputSampleRate / m_outputSampleRate; - - m_settingsMutex.unlock(); - - qDebug() << "TCPSrc::handleMessage: MsgChannelizerNotification: m_inputSampleRate: " << m_inputSampleRate - << " frequencyOffset: " << notif.getFrequencyOffset(); - - return true; - } - else if (MsgConfigureChannelizer::match(cmd)) - { - MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; - - m_channelizer->configure(m_channelizer->getInputMessageQueue(), - cfg.getSampleRate(), - cfg.getCenterFrequency()); - - qDebug() << "TCPSrc::handleMessage: MsgConfigureChannelizer:" - << " sampleRate: " << cfg.getSampleRate() - << " centerFrequency: " << cfg.getCenterFrequency(); - - return true; - } - else if (MsgConfigureTCPSrc::match(cmd)) - { - MsgConfigureTCPSrc& cfg = (MsgConfigureTCPSrc&) cmd; - - TCPSrcSettings settings = cfg.getSettings(); - - // These settings are set with DownChannelizer::MsgChannelizerNotification - m_absoluteFrequencyOffset = settings.m_inputFrequencyOffset; - settings.m_inputSampleRate = m_settings.m_inputSampleRate; - settings.m_inputFrequencyOffset = m_settings.m_inputFrequencyOffset; - - m_settingsMutex.lock(); - - m_sampleFormat = settings.m_sampleFormat; - m_outputSampleRate = settings.m_outputSampleRate; - m_rfBandwidth = settings.m_rfBandwidth; - - if (settings.m_tcpPort != m_tcpPort) - { - m_tcpPort = settings.m_tcpPort; - - if(m_tcpServer->isListening()) - { - m_tcpServer->close(); - } - - m_tcpServer->listen(QHostAddress::Any, m_tcpPort); - } - - m_volume = settings.m_volume; - m_interpolator.create(16, m_inputSampleRate, m_rfBandwidth / 2.0); - m_sampleDistanceRemain = m_inputSampleRate / m_outputSampleRate; - - if (m_sampleFormat == TCPSrcSettings::FormatSSB) - { - TCPFilter->create_filter(0.3 / 48.0, m_rfBandwidth / 2.0 / m_outputSampleRate); - } - else - { - TCPFilter->create_filter(0.0, m_rfBandwidth / 2.0 / m_outputSampleRate); - } - - m_settingsMutex.unlock(); - - qDebug() << "TCPSrc::handleMessage: MsgConfigureTCPSrc:" - << " m_sampleFormat: " << m_sampleFormat - << " m_outputSampleRate: " << m_outputSampleRate - << " m_rfBandwidth: " << m_rfBandwidth - << " m_volume: " << m_volume; - - m_settings = settings; - - return true; - } - else if (MsgTCPSrcSpectrum::match(cmd)) - { - MsgTCPSrcSpectrum& spc = (MsgTCPSrcSpectrum&) cmd; - - m_spectrumEnabled = spc.getEnabled(); - - qDebug() << "TCPSrc::handleMessage: MsgTCPSrcSpectrum: m_spectrumEnabled: " << m_spectrumEnabled; - - return true; - } - else if (MsgTCPSrcConnection::match(cmd)) - { - MsgTCPSrcConnection& con = (MsgTCPSrcConnection&) cmd; - - qDebug() << "TCPSrc::handleMessage: MsgTCPSrcConnection" - << " connect: " << con.getConnect() - << " id: " << con.getID() - << " peer address: " << con.getPeerAddress() - << " peer port: " << con.getPeerPort(); - - if (con.getConnect()) - { - processNewConnection(); - } - else - { - processDeconnection(); - } - } - else if (DSPSignalNotification::match(cmd)) - { - return true; - } - else - { - if(m_spectrum != 0) - { - return m_spectrum->handleMessage(cmd); - } - else - { - return false; - } - } - - return false; -} - -void TCPSrc::closeAllSockets(Sockets* sockets) -{ - for(int i = 0; i < sockets->count(); ++i) - { - MsgTCPSrcConnection* msg = MsgTCPSrcConnection::create(false, sockets->at(i).id, QHostAddress(), 0); - getInputMessageQueue()->push(msg); - - if (getMessageQueueToGUI()) { // Propagate to GUI - getMessageQueueToGUI()->push(msg); - } - - sockets->at(i).socket->close(); - } -} - -void TCPSrc::onNewConnection() -{ - qDebug("TCPSrc::onNewConnection"); - processNewConnection(); -} - -void TCPSrc::processNewConnection() -{ - qDebug("TCPSrc::processNewConnection"); - - while(m_tcpServer->hasPendingConnections()) - { - qDebug("TCPSrc::processNewConnection: has a pending connection"); - QTcpSocket* connection = m_tcpServer->nextPendingConnection(); - connection->setSocketOption(QAbstractSocket:: KeepAliveOption, 1); - connect(connection, SIGNAL(disconnected()), this, SLOT(onDisconnected())); - - switch(m_sampleFormat) { - - case TCPSrcSettings::FormatNFM: - case TCPSrcSettings::FormatSSB: - { - quint32 id = (TCPSrcSettings::FormatSSB << 24) | m_nextSSBId; - m_nextSSBId = (m_nextSSBId + 1) & 0xffffff; - m_ssbSockets.push_back(Socket(id, connection)); - - if (getMessageQueueToGUI()) // Notify GUI of peer details - { - MsgTCPSrcConnection* msg = MsgTCPSrcConnection::create(true, id, connection->peerAddress(), connection->peerPort()); - getMessageQueueToGUI()->push(msg); - } - - break; - } - - case TCPSrcSettings::FormatS16LE: - { - qDebug("TCPSrc::processNewConnection: establish new S16LE connection"); - quint32 id = (TCPSrcSettings::FormatS16LE << 24) | m_nextS16leId; - m_nextS16leId = (m_nextS16leId + 1) & 0xffffff; - m_s16leSockets.push_back(Socket(id, connection)); - - if (getMessageQueueToGUI()) // Notify GUI of peer details - { - MsgTCPSrcConnection* msg = MsgTCPSrcConnection::create(true, id, connection->peerAddress(), connection->peerPort()); - getMessageQueueToGUI()->push(msg); - } - - break; - } - - default: - delete connection; - break; - } - } -} - -void TCPSrc::onDisconnected() -{ - qDebug("TCPSrc::onDisconnected"); - MsgTCPSrcConnection *cmd = MsgTCPSrcConnection::create(false, 0, QHostAddress::Any, 0); - getInputMessageQueue()->push(cmd); - - if (getMessageQueueToGUI()) { // Propagate to GUI - getMessageQueueToGUI()->push(cmd); - } - -} - -void TCPSrc::processDeconnection() -{ - quint32 id; - QTcpSocket* socket = 0; - - qDebug("TCPSrc::processDeconnection"); - - for(int i = 0; i < m_ssbSockets.count(); i++) - { - if(m_ssbSockets[i].socket == sender()) - { - id = m_ssbSockets[i].id; - socket = m_ssbSockets[i].socket; - socket->close(); - m_ssbSockets.removeAt(i); - break; - } - } - - if(socket == 0) - { - for(int i = 0; i < m_s16leSockets.count(); i++) - { - if(m_s16leSockets[i].socket == sender()) - { - qDebug("TCPSrc::processDeconnection: remove S16LE socket #%d", i); - - id = m_s16leSockets[i].id; - socket = m_s16leSockets[i].socket; - socket->close(); - m_s16leSockets.removeAt(i); - break; - } - } - } - - if(socket != 0) - { - MsgTCPSrcConnection* msg = MsgTCPSrcConnection::create(false, id, QHostAddress(), 0); - getInputMessageQueue()->push(msg); - - if (getMessageQueueToGUI()) { // Propagate to GUI - getMessageQueueToGUI()->push(msg); - } - - socket->deleteLater(); - } -} - -void TCPSrc::onTcpServerError(QAbstractSocket::SocketError socketError __attribute__((unused))) -{ - qDebug("TCPSrc::onTcpServerError: %s", qPrintable(m_tcpServer->errorString())); -} - -QByteArray TCPSrc::serialize() const -{ - return m_settings.serialize(); -} - -bool TCPSrc::deserialize(const QByteArray& data) -{ - if (m_settings.deserialize(data)) - { - MsgConfigureTCPSrc *msg = MsgConfigureTCPSrc::create(m_settings, true); - m_inputMessageQueue.push(msg); - return true; - } - else - { - m_settings.resetToDefaults(); - MsgConfigureTCPSrc *msg = MsgConfigureTCPSrc::create(m_settings, true); - m_inputMessageQueue.push(msg); - return false; - } -} - diff --git a/plugins/channelrx/tcpsrc/tcpsrc.h b/plugins/channelrx/tcpsrc/tcpsrc.h deleted file mode 100644 index 471a8c3e7..000000000 --- a/plugins/channelrx/tcpsrc/tcpsrc.h +++ /dev/null @@ -1,224 +0,0 @@ -#ifndef INCLUDE_TCPSRC_H -#define INCLUDE_TCPSRC_H - -#include -#include - -#include "dsp/basebandsamplesink.h" -#include "channel/channelsinkapi.h" -#include "dsp/nco.h" -#include "dsp/fftfilt.h" -#include "dsp/interpolator.h" -#include "util/message.h" - -#include "tcpsrcsettings.h" - -#define tcpFftLen 2048 - -class QTcpServer; -class QTcpSocket; -class TCPSrcGUI; -class DeviceSourceAPI; -class ThreadedBasebandSampleSink; -class DownChannelizer; - -class TCPSrc : public BasebandSampleSink, public ChannelSinkAPI { - Q_OBJECT - -public: - class MsgConfigureTCPSrc : public Message { - MESSAGE_CLASS_DECLARATION - - public: - const TCPSrcSettings& getSettings() const { return m_settings; } - bool getForce() const { return m_force; } - - static MsgConfigureTCPSrc* create(const TCPSrcSettings& settings, bool force) - { - return new MsgConfigureTCPSrc(settings, force); - } - - private: - TCPSrcSettings m_settings; - bool m_force; - - MsgConfigureTCPSrc(const TCPSrcSettings& settings, bool force) : - Message(), - m_settings(settings), - m_force(force) - { - } - }; - - class MsgConfigureChannelizer : public Message { - MESSAGE_CLASS_DECLARATION - - public: - int getSampleRate() const { return m_sampleRate; } - int getCenterFrequency() const { return m_centerFrequency; } - - static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency) - { - return new MsgConfigureChannelizer(sampleRate, centerFrequency); - } - - private: - int m_sampleRate; - int m_centerFrequency; - - MsgConfigureChannelizer(int sampleRate, int centerFrequency) : - Message(), - m_sampleRate(sampleRate), - m_centerFrequency(centerFrequency) - { } - }; - - class MsgTCPSrcConnection : public Message { - MESSAGE_CLASS_DECLARATION - - public: - bool getConnect() const { return m_connect; } - quint32 getID() const { return m_id; } - const QHostAddress& getPeerAddress() const { return m_peerAddress; } - int getPeerPort() const { return m_peerPort; } - - static MsgTCPSrcConnection* create(bool connect, quint32 id, const QHostAddress& peerAddress, int peerPort) - { - return new MsgTCPSrcConnection(connect, id, peerAddress, peerPort); - } - - private: - bool m_connect; - quint32 m_id; - QHostAddress m_peerAddress; - int m_peerPort; - - MsgTCPSrcConnection(bool connect, quint32 id, const QHostAddress& peerAddress, int peerPort) : - Message(), - m_connect(connect), - m_id(id), - m_peerAddress(peerAddress), - m_peerPort(peerPort) - { } - }; - - TCPSrc(DeviceSourceAPI* m_deviceAPI); - virtual ~TCPSrc(); - virtual void destroy() { delete this; } - void setSpectrum(BasebandSampleSink* spectrum) { m_spectrum = spectrum; } - - void setSpectrum(MessageQueue* messageQueue, bool enabled); - double getMagSq() const { return m_magsq; } - - virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); - virtual void start(); - virtual void stop(); - virtual bool handleMessage(const Message& cmd); - - virtual void getIdentifier(QString& id) { id = objectName(); } - virtual void getTitle(QString& title) { title = m_settings.m_title; } - virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; } - - virtual QByteArray serialize() const; - virtual bool deserialize(const QByteArray& data); - - static const QString m_channelIdURI; - static const QString m_channelId; - -protected: - class MsgTCPSrcSpectrum : public Message { - MESSAGE_CLASS_DECLARATION - - public: - bool getEnabled() const { return m_enabled; } - - static MsgTCPSrcSpectrum* create(bool enabled) - { - return new MsgTCPSrcSpectrum(enabled); - } - - private: - bool m_enabled; - - MsgTCPSrcSpectrum(bool enabled) : - Message(), - m_enabled(enabled) - { } - }; - class MsgTCPConnection : public Message { - MESSAGE_CLASS_DECLARATION - - public: - bool getConnect() const { return m_connect; } - - static MsgTCPConnection* create(bool connect) - { - return new MsgTCPConnection(connect); - } - - private: - bool m_connect; - - MsgTCPConnection(bool connect) : - Message(), - m_connect(connect) - { } - }; - - DeviceSourceAPI* m_deviceAPI; - ThreadedBasebandSampleSink* m_threadedChannelizer; - DownChannelizer* m_channelizer; - - TCPSrcSettings m_settings; - int m_absoluteFrequencyOffset; - - int m_inputSampleRate; - - int m_sampleFormat; - Real m_outputSampleRate; - Real m_rfBandwidth; - int m_tcpPort; - int m_volume; - double m_magsq; - - Real m_scale; - Complex m_last, m_this; - - NCO m_nco; - Interpolator m_interpolator; - Real m_sampleDistanceRemain; - fftfilt* TCPFilter; - - SampleVector m_sampleBuffer; - SampleVector m_sampleBufferSSB; - BasebandSampleSink* m_spectrum; - bool m_spectrumEnabled; - - QTcpServer* m_tcpServer; - struct Socket { - quint32 id; - QTcpSocket* socket; - Socket(quint32 _id, QTcpSocket* _socket) : - id(_id), - socket(_socket) - { } - }; - typedef QList Sockets; - Sockets m_ssbSockets; - Sockets m_s16leSockets; - quint32 m_nextSSBId; - quint32 m_nextS16leId; - - QMutex m_settingsMutex; - - void closeAllSockets(Sockets* sockets); - void processNewConnection(); - void processDeconnection(); - -protected slots: - void onNewConnection(); - void onDisconnected(); - void onTcpServerError(QAbstractSocket::SocketError socketError); -}; - -#endif // INCLUDE_TCPSRC_H diff --git a/plugins/channelrx/tcpsrc/tcpsrc.pro b/plugins/channelrx/tcpsrc/tcpsrc.pro deleted file mode 100644 index 0b50cace3..000000000 --- a/plugins/channelrx/tcpsrc/tcpsrc.pro +++ /dev/null @@ -1,42 +0,0 @@ -#-------------------------------------------------------- -# -# Pro file for Android and Windows builds with Qt Creator -# -#-------------------------------------------------------- - -TEMPLATE = lib -CONFIG += plugin - -QT += core gui widgets multimedia network opengl - -TARGET = tcpsrc - -DEFINES += USE_SSE2=1 -QMAKE_CXXFLAGS += -msse2 -DEFINES += USE_SSE4_1=1 -QMAKE_CXXFLAGS += -msse4.1 -QMAKE_CXXFLAGS += -std=c++11 - -INCLUDEPATH += $$PWD -INCLUDEPATH += ../../../sdrbase -INCLUDEPATH += ../../../sdrgui - -CONFIG(Release):build_subdir = release -CONFIG(Debug):build_subdir = debug - -SOURCES += tcpsrc.cpp\ - tcpsrcgui.cpp\ - tcpsrcplugin.cpp\ - tcpsrcsettings.cpp - -HEADERS += tcpsrc.h\ - tcpsrcgui.h\ - tcpsrcplugin.h\ - tcpsrcsettings.h - -FORMS += tcpsrcgui.ui - -LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase -LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui - -RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/plugins/channelrx/tcpsrc/tcpsrcgui.cpp b/plugins/channelrx/tcpsrc/tcpsrcgui.cpp deleted file mode 100644 index 804b92a33..000000000 --- a/plugins/channelrx/tcpsrc/tcpsrcgui.cpp +++ /dev/null @@ -1,397 +0,0 @@ -#include "tcpsrcgui.h" - -#include -#include "device/deviceuiset.h" -#include "plugin/pluginapi.h" -#include "dsp/spectrumvis.h" -#include "dsp/dspengine.h" -#include "util/simpleserializer.h" -#include "util/db.h" -#include "ui_tcpsrcgui.h" -#include "mainwindow.h" -#include "tcpsrc.h" - -TCPSrcGUI* TCPSrcGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) -{ - TCPSrcGUI* gui = new TCPSrcGUI(pluginAPI, deviceUISet, rxChannel); - return gui; -} - -void TCPSrcGUI::destroy() -{ - delete this; -} - -void TCPSrcGUI::setName(const QString& name) -{ - setObjectName(name); -} - -qint64 TCPSrcGUI::getCenterFrequency() const -{ - return m_channelMarker.getCenterFrequency(); -} - -void TCPSrcGUI::setCenterFrequency(qint64 centerFrequency) -{ - m_channelMarker.setCenterFrequency(centerFrequency); - applySettings(); -} - -QString TCPSrcGUI::getName() const -{ - return objectName(); -} - -void TCPSrcGUI::resetToDefaults() -{ - m_settings.resetToDefaults(); - displaySettings(); - applySettings(); -} - -QByteArray TCPSrcGUI::serialize() const -{ - return m_settings.serialize(); -} - -bool TCPSrcGUI::deserialize(const QByteArray& data) -{ - if(m_settings.deserialize(data)) - { - qDebug("TCPSrcGUI::deserialize: m_squelchGate: %d", m_settings.m_squelchGate); - displaySettings(); - applySettings(); - return true; - } else { - resetToDefaults(); - return false; - } -} - -bool TCPSrcGUI::handleMessage(const Message& message) -{ - if (TCPSrc::MsgTCPSrcConnection::match(message)) - { - TCPSrc::MsgTCPSrcConnection& con = (TCPSrc::MsgTCPSrcConnection&) message; - - if(con.getConnect()) - { - addConnection(con.getID(), con.getPeerAddress(), con.getPeerPort()); - } - else - { - delConnection(con.getID()); - } - - qDebug() << "TCPSrcGUI::handleMessage: TCPSrc::MsgTCPSrcConnection: " << con.getConnect() - << " ID: " << con.getID() - << " peerAddress: " << con.getPeerAddress() - << " peerPort: " << con.getPeerPort(); - - return true; - } - else - { - return false; - } -} - -void TCPSrcGUI::channelMarkerChangedByCursor() -{ - ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); - m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); - applySettings(); -} - -void TCPSrcGUI::channelMarkerHighlightedByCursor() -{ - setHighlighted(m_channelMarker.getHighlighted()); -} - -void TCPSrcGUI::tick() -{ - double powDb = CalcDb::dbPower(m_tcpSrc->getMagSq()); - m_channelPowerDbAvg(powDb); - ui->channelPower->setText(QString::number(m_channelPowerDbAvg.asDouble(), 'f', 1)); -} - -TCPSrcGUI::TCPSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) : - RollupWidget(parent), - ui(new Ui::TCPSrcGUI), - m_pluginAPI(pluginAPI), - m_deviceUISet(deviceUISet), - m_tcpSrc(0), - m_channelMarker(this), - m_rfBandwidthChanged(false), - m_doApplySettings(true) -{ - ui->setupUi(this); - ui->connectedClientsBox->hide(); - connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); - setAttribute(Qt::WA_DeleteOnClose, true); - - m_spectrumVis = new SpectrumVis(SDR_RX_SCALEF, ui->glSpectrum); - m_tcpSrc = (TCPSrc*) rxChannel; //new TCPSrc(m_deviceUISet->m_deviceSourceAPI); - m_tcpSrc->setSpectrum(m_spectrumVis); - - ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); - ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); - ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999); - - ui->glSpectrum->setCenterFrequency(0); - ui->glSpectrum->setSampleRate(ui->sampleRate->text().toInt()); - ui->glSpectrum->setDisplayWaterfall(true); - ui->glSpectrum->setDisplayMaxHold(true); - m_spectrumVis->configure(m_spectrumVis->getInputMessageQueue(), 64, 10, FFTWindow::BlackmanHarris); - - ui->glSpectrum->connectTimer(MainWindow::getInstance()->getMasterTimer()); - connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); - - m_channelMarker.blockSignals(true); - m_channelMarker.setBandwidth(16000); - m_channelMarker.setCenterFrequency(0); - m_channelMarker.setColor(m_settings.m_rgbColor); - m_channelMarker.blockSignals(false); - m_channelMarker.setVisible(true); // activate signal on the last setting only - - setTitleColor(m_channelMarker.getColor()); - - m_deviceUISet->registerRxChannelInstance(TCPSrc::m_channelIdURI, this); - m_deviceUISet->addChannelMarker(&m_channelMarker); - m_deviceUISet->addRollupWidget(this); - - connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor())); - connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor())); - - ui->spectrumGUI->setBuddies(m_spectrumVis->getInputMessageQueue(), m_spectrumVis, ui->glSpectrum); - - m_settings.setSpectrumGUI(ui->spectrumGUI); - m_settings.setChannelMarker(&m_channelMarker); - - displaySettings(); - applySettings(); -} - -TCPSrcGUI::~TCPSrcGUI() -{ - m_deviceUISet->removeRxChannelInstance(this); - delete m_tcpSrc; // TODO: check this: when the GUI closes it has to delete the demodulator - delete m_spectrumVis; - delete ui; -} - -void TCPSrcGUI::blockApplySettings(bool block) -{ - m_doApplySettings = !block; -} - -void TCPSrcGUI::applySettings() -{ - if (m_doApplySettings) - { - ui->glSpectrum->setSampleRate(m_settings.m_outputSampleRate); - - TCPSrc::MsgConfigureChannelizer* channelConfigMsg = TCPSrc::MsgConfigureChannelizer::create( - m_settings.m_outputSampleRate, m_channelMarker.getCenterFrequency()); - m_tcpSrc->getInputMessageQueue()->push(channelConfigMsg); - - TCPSrc::MsgConfigureTCPSrc* message = TCPSrc::MsgConfigureTCPSrc::create( m_settings, false); - m_tcpSrc->getInputMessageQueue()->push(message); - } -} - -void TCPSrcGUI::on_deltaFrequency_changed(qint64 value) -{ - m_channelMarker.setCenterFrequency(value); - m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); - applySettings(); -} - -void TCPSrcGUI::on_sampleFormat_currentIndexChanged(int index) -{ - setSampleFormat(index); - - ui->applyBtn->setEnabled(true); - ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); -} - -void TCPSrcGUI::on_sampleRate_textEdited(const QString& arg1 __attribute__((unused))) -{ - bool ok; - Real outputSampleRate = ui->sampleRate->text().toDouble(&ok); - - if((!ok) || (outputSampleRate < 1000)) - { - m_settings.m_outputSampleRate = 48000; - ui->sampleRate->setText(QString("%1").arg(outputSampleRate, 0)); - } - else - { - m_settings.m_outputSampleRate = outputSampleRate; - } - - ui->applyBtn->setEnabled(true); - ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); -} - -void TCPSrcGUI::on_rfBandwidth_textEdited(const QString& arg1 __attribute__((unused))) -{ - bool ok; - Real rfBandwidth = ui->rfBandwidth->text().toDouble(&ok); - - if((!ok) || (rfBandwidth > m_settings.m_outputSampleRate)) - { - m_settings.m_rfBandwidth = m_settings.m_outputSampleRate; - ui->rfBandwidth->setText(QString("%1").arg(m_settings.m_rfBandwidth, 0)); - } - else - { - m_settings.m_rfBandwidth = rfBandwidth; - } - - m_rfBandwidthChanged = true; - - ui->applyBtn->setEnabled(true); - ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); -} - -void TCPSrcGUI::on_tcpPort_textEdited(const QString& arg1 __attribute__((unused))) -{ - bool ok; - int tcpPort = ui->tcpPort->text().toInt(&ok); - - if((!ok) || (tcpPort < 1) || (tcpPort > 65535)) - { - m_settings.m_tcpPort = 9999; - ui->tcpPort->setText(QString("%1").arg(m_settings.m_tcpPort, 0)); - } - else - { - m_settings.m_tcpPort = tcpPort; - } - - ui->applyBtn->setEnabled(true); - ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); -} - -void TCPSrcGUI::on_applyBtn_clicked() -{ - if (m_rfBandwidthChanged) - { - blockApplySettings(true); - m_channelMarker.setBandwidth((int) m_settings.m_rfBandwidth); - m_rfBandwidthChanged = false; - blockApplySettings(false); - } - - ui->glSpectrum->setSampleRate(m_settings.m_outputSampleRate); - - ui->applyBtn->setEnabled(false); - ui->applyBtn->setStyleSheet("QPushButton { background:rgb(79,79,79); }"); - - applySettings(); -} - -void TCPSrcGUI::displaySettings() -{ - m_channelMarker.blockSignals(true); - m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset); - m_channelMarker.setBandwidth(m_settings.m_rfBandwidth); - m_channelMarker.setTitle(m_settings.m_title); - m_channelMarker.blockSignals(false); - m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only - - setTitleColor(m_settings.m_rgbColor); - setWindowTitle(m_channelMarker.getTitle()); - - blockApplySettings(true); - - ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); - - ui->sampleRate->setText(QString("%1").arg(m_settings.m_outputSampleRate, 0)); - setSampleFormatIndex(m_settings.m_sampleFormat); - - ui->rfBandwidth->setText(QString("%1").arg(m_settings.m_rfBandwidth, 0)); - - ui->volume->setValue(m_settings.m_volume); - ui->volumeText->setText(QString("%1").arg(ui->volume->value())); - - ui->glSpectrum->setSampleRate(m_settings.m_outputSampleRate); - - blockApplySettings(false); -} - -void TCPSrcGUI::setSampleFormatIndex(const TCPSrcSettings::SampleFormat& sampleFormat) -{ - switch(sampleFormat) - { - case TCPSrcSettings::FormatS16LE: - ui->sampleFormat->setCurrentIndex(0); - break; - case TCPSrcSettings::FormatNFM: - ui->sampleFormat->setCurrentIndex(1); - break; - case TCPSrcSettings::FormatSSB: - ui->sampleFormat->setCurrentIndex(2); - break; - default: - ui->sampleFormat->setCurrentIndex(0); - break; - } -} - -void TCPSrcGUI::setSampleFormat(int index) -{ - switch(index) - { - case 0: - m_settings.m_sampleFormat = TCPSrcSettings::FormatS16LE; - break; - case 1: - m_settings.m_sampleFormat = TCPSrcSettings::FormatNFM; - break; - case 2: - m_settings.m_sampleFormat = TCPSrcSettings::FormatSSB; - break; - default: - m_settings.m_sampleFormat = TCPSrcSettings::FormatS16LE; - break; - } -} - -void TCPSrcGUI::on_volume_valueChanged(int value) -{ - ui->volume->setValue(value); - ui->volumeText->setText(QString("%1").arg(value)); - ui->applyBtn->setEnabled(true); -} - -void TCPSrcGUI::onWidgetRolled(QWidget* widget, bool rollDown) -{ - if ((widget == ui->spectrumBox) && (m_tcpSrc != 0)) - { - m_tcpSrc->setSpectrum(m_tcpSrc->getInputMessageQueue(), rollDown); - } -} - -void TCPSrcGUI::addConnection(quint32 id, const QHostAddress& peerAddress, int peerPort) -{ - QStringList l; - l.append(QString("%1:%2").arg(peerAddress.toString()).arg(peerPort)); - new QTreeWidgetItem(ui->connections, l, id); - ui->connectedClientsBox->setWindowTitle(tr("Connected Clients (%1)").arg(ui->connections->topLevelItemCount())); -} - -void TCPSrcGUI::delConnection(quint32 id) -{ - for(int i = 0; i < ui->connections->topLevelItemCount(); i++) - { - if(ui->connections->topLevelItem(i)->type() == (int)id) - { - delete ui->connections->topLevelItem(i); - ui->connectedClientsBox->setWindowTitle(tr("Connected Clients (%1)").arg(ui->connections->topLevelItemCount())); - return; - } - } -} diff --git a/plugins/channelrx/tcpsrc/tcpsrcgui.h b/plugins/channelrx/tcpsrc/tcpsrcgui.h deleted file mode 100644 index c671a7d80..000000000 --- a/plugins/channelrx/tcpsrc/tcpsrcgui.h +++ /dev/null @@ -1,92 +0,0 @@ -#ifndef INCLUDE_TCPSRCGUI_H -#define INCLUDE_TCPSRCGUI_H - -#include -#include - -#include "gui/rollupwidget.h" -#include "dsp/channelmarker.h" -#include "util/movingaverage.h" -#include "util/messagequeue.h" - -#include "tcpsrc.h" -#include "tcpsrcsettings.h" - -class PluginAPI; -class DeviceUISet; -class TCPSrc; -class SpectrumVis; - -namespace Ui { - class TCPSrcGUI; -} - -class TCPSrcGUI : public RollupWidget, public PluginInstanceGUI { - Q_OBJECT - -public: - static TCPSrcGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel); - virtual void destroy(); - - void setName(const QString& name); - QString getName() const; - virtual qint64 getCenterFrequency() const; - virtual void setCenterFrequency(qint64 centerFrequency); - - void resetToDefaults(); - QByteArray serialize() const; - bool deserialize(const QByteArray& data); - virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } - virtual bool handleMessage(const Message& message); - -public slots: - void channelMarkerChangedByCursor(); - void channelMarkerHighlightedByCursor(); - -private: - Ui::TCPSrcGUI* ui; - PluginAPI* m_pluginAPI; - DeviceUISet* m_deviceUISet; - TCPSrc* m_tcpSrc; - ChannelMarker m_channelMarker; - MovingAverageUtil m_channelPowerDbAvg; - - // settings - TCPSrcSettings m_settings; - TCPSrcSettings::SampleFormat m_sampleFormat; - Real m_outputSampleRate; - Real m_rfBandwidth; - int m_boost; - int m_tcpPort; - bool m_rfBandwidthChanged; - bool m_doApplySettings; - - // RF path - SpectrumVis* m_spectrumVis; - MessageQueue m_inputMessageQueue; - - explicit TCPSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); - virtual ~TCPSrcGUI(); - - void blockApplySettings(bool block); - void applySettings(); - void displaySettings(); - void setSampleFormat(int index); - void setSampleFormatIndex(const TCPSrcSettings::SampleFormat& sampleFormat); - - void addConnection(quint32 id, const QHostAddress& peerAddress, int peerPort); - void delConnection(quint32 id); - -private slots: - void on_deltaFrequency_changed(qint64 value); - void on_sampleFormat_currentIndexChanged(int index); - void on_sampleRate_textEdited(const QString& arg1); - void on_rfBandwidth_textEdited(const QString& arg1); - void on_tcpPort_textEdited(const QString& arg1); - void on_applyBtn_clicked(); - void onWidgetRolled(QWidget* widget, bool rollDown); - void on_volume_valueChanged(int value); - void tick(); -}; - -#endif // INCLUDE_TCPSRCGUI_H diff --git a/plugins/channelrx/tcpsrc/tcpsrcgui.ui b/plugins/channelrx/tcpsrc/tcpsrcgui.ui deleted file mode 100644 index 33df165f7..000000000 --- a/plugins/channelrx/tcpsrc/tcpsrcgui.ui +++ /dev/null @@ -1,480 +0,0 @@ - - - TCPSrcGUI - - - - 0 - 0 - 400 - 443 - - - - - Sans Serif - 9 - - - - TCP Sample Source - - - - - 10 - 5 - 201 - 142 - - - - Settings - - - - 2 - - - 2 - - - 2 - - - 2 - - - 3 - - - - - Sample Format - - - - - - - Sample format - - - 0 - - - - S16LE I/Q - - - - - S16LE NFM - - - - - S16LE SSB - - - - - - - - Channel bandwidth - - - 32000 - - - - - - - RF Bandwidth (Hz) - - - - - - - Samplerate (Hz) - - - - - - - TCP Port - - - - - - - Data stream sample rate - - - 48000 - - - - - - - TCP port number - - - 9999 - - - - - - - false - - - Apply changes - - - Apply - - - - - - - 2 - - - - - - 16 - 16777215 - - - - Df - - - - - - - - 0 - 0 - - - - - 32 - 16 - - - - - DejaVu Sans Mono - 12 - - - - PointingHandCursor - - - Qt::StrongFocus - - - Demod shift frequency from center in Hz - - - - - - - - - - - - 26 - 26 - 26 - - - - - - - 255 - 255 - 255 - - - - - - - - - 26 - 26 - 26 - - - - - - - 255 - 255 - 255 - - - - - - - - - 118 - 118 - 117 - - - - - - - 255 - 255 - 255 - - - - - - - - - 8 - - - - Hz - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Channel power - - - Qt::LeftToRight - - - 0.0 - - - - - - - dB - - - - - - - - - - - Vol - - - - - - - Amplitude boost - - - 3 - - - 1 - - - Qt::Horizontal - - - - - - - 0 - - - - - - - - - - - 15 - 160 - 231 - 156 - - - - Channel Spectrum - - - - 3 - - - 2 - - - 2 - - - 2 - - - 2 - - - - - - Sans Serif - 9 - - - - - - - - - - - - - 15 - 330 - 274 - 101 - - - - Connected Clients (0) - - - - 3 - - - 2 - - - 2 - - - 2 - - - 2 - - - - - - 400 - 100 - - - - false - - - false - - - false - - - - IP:Port - - - - - - - - - - RollupWidget - QWidget -
gui/rollupwidget.h
- 1 -
- - GLSpectrum - QWidget -
gui/glspectrum.h
- 1 -
- - GLSpectrumGUI - QWidget -
gui/glspectrumgui.h
- 1 -
- - ValueDialZ - QWidget -
gui/valuedialz.h
- 1 -
-
- - sampleFormat - tcpPort - sampleRate - rfBandwidth - applyBtn - connections - - - -
diff --git a/plugins/channelrx/tcpsrc/tcpsrcplugin.cpp b/plugins/channelrx/tcpsrc/tcpsrcplugin.cpp deleted file mode 100644 index d7a0193c7..000000000 --- a/plugins/channelrx/tcpsrc/tcpsrcplugin.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "tcpsrcplugin.h" - -#include -#include "plugin/pluginapi.h" - -#include "tcpsrcgui.h" -#include "tcpsrc.h" - -const PluginDescriptor TCPSrcPlugin::m_pluginDescriptor = { - QString("TCP Channel Source"), - QString("3.12.0"), - QString("(c) Edouard Griffiths, F4EXB"), - QString("https://github.com/f4exb/sdrangel"), - true, - QString("https://github.com/f4exb/sdrangel") -}; - -TCPSrcPlugin::TCPSrcPlugin(QObject* parent) : - QObject(parent), - m_pluginAPI(0) -{ -} - -const PluginDescriptor& TCPSrcPlugin::getPluginDescriptor() const -{ - return m_pluginDescriptor; -} - -void TCPSrcPlugin::initPlugin(PluginAPI* pluginAPI) -{ - m_pluginAPI = pluginAPI; - - // register TCP Channel Source - m_pluginAPI->registerRxChannel(TCPSrc::m_channelIdURI, TCPSrc::m_channelId, this); -} - -PluginInstanceGUI* TCPSrcPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) -{ - return TCPSrcGUI::create(m_pluginAPI, deviceUISet, rxChannel); -} - -BasebandSampleSink* TCPSrcPlugin::createRxChannelBS(DeviceSourceAPI *deviceAPI) -{ - return new TCPSrc(deviceAPI); -} - -ChannelSinkAPI* TCPSrcPlugin::createRxChannelCS(DeviceSourceAPI *deviceAPI) -{ - return new TCPSrc(deviceAPI); -} - diff --git a/plugins/channelrx/tcpsrc/tcpsrcplugin.h b/plugins/channelrx/tcpsrc/tcpsrcplugin.h deleted file mode 100644 index 74d3d5996..000000000 --- a/plugins/channelrx/tcpsrc/tcpsrcplugin.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef INCLUDE_TCPSRCPLUGIN_H -#define INCLUDE_TCPSRCPLUGIN_H - -#include -#include "plugin/plugininterface.h" - -class DeviceUISet; -class BasebandSampleSink; - -class TCPSrcPlugin : public QObject, PluginInterface { - Q_OBJECT - Q_INTERFACES(PluginInterface) - Q_PLUGIN_METADATA(IID "sdrangel.demod.tcpsrc") - -public: - explicit TCPSrcPlugin(QObject* parent = NULL); - - const PluginDescriptor& getPluginDescriptor() const; - void initPlugin(PluginAPI* pluginAPI); - - virtual PluginInstanceGUI* createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel); - virtual BasebandSampleSink* createRxChannelBS(DeviceSourceAPI *deviceAPI); - virtual ChannelSinkAPI* createRxChannelCS(DeviceSourceAPI *deviceAPI); - -private: - static const PluginDescriptor m_pluginDescriptor; - - PluginAPI* m_pluginAPI; -}; - -#endif // INCLUDE_TCPSRCPLUGIN_H diff --git a/plugins/channelrx/tcpsrc/tcpsrcsettings.cpp b/plugins/channelrx/tcpsrc/tcpsrcsettings.cpp deleted file mode 100644 index 711454926..000000000 --- a/plugins/channelrx/tcpsrc/tcpsrcsettings.cpp +++ /dev/null @@ -1,150 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2017 Edouard Griffiths, F4EXB // -// // -// This program is free software; you can redistribute it and/or modify // -// it under the terms of the GNU General Public License as published by // -// the Free Software Foundation as version 3 of the License, or // -// // -// This program is distributed in the hope that it will be useful, // -// but WITHOUT ANY WARRANTY; without even the implied warranty of // -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // -// GNU General Public License V3 for more details. // -// // -// You should have received a copy of the GNU General Public License // -// along with this program. If not, see . // -/////////////////////////////////////////////////////////////////////////////////// - -#include - -#include "dsp/dspengine.h" -#include "util/simpleserializer.h" -#include "settings/serializable.h" -#include "tcpsrcsettings.h" - -TCPSrcSettings::TCPSrcSettings() : - m_channelMarker(0), - m_spectrumGUI(0) -{ - resetToDefaults(); -} - -void TCPSrcSettings::resetToDefaults() -{ - m_outputSampleRate = 48000; - m_sampleFormat = FormatS16LE; - m_inputSampleRate = 48000; - m_inputFrequencyOffset = 0; - m_rfBandwidth = 12500; - m_tcpPort = 9999; - m_fmDeviation = 2500; - m_channelMute = false; - m_gain = 1.0; - m_squelchdB = -60; - m_squelchGate = 0.0; - m_squelchEnabled = true; - m_agc = false; - m_audioActive = false; - m_audioStereo = false; - m_volume = 20; - m_udpAddress = "127.0.0.1"; - m_udpPort = 9999; - m_audioPort = 9998; - m_rgbColor = QColor(225, 25, 99).rgb(); - m_title = "TCP Source"; -} - -QByteArray TCPSrcSettings::serialize() const -{ - SimpleSerializer s(1); - s.writeS32(2, m_inputFrequencyOffset); - s.writeS32(3, (int) m_sampleFormat); - s.writeReal(4, m_outputSampleRate); - s.writeReal(5, m_rfBandwidth); - s.writeS32(6, m_tcpPort); - - if (m_channelMarker) { - s.writeBlob(10, m_channelMarker->serialize()); - } - - if (m_spectrumGUI) { - s.writeBlob(7, m_spectrumGUI->serialize()); - } - - s.writeS32(8, m_gain*10.0); - s.writeU32(9, m_rgbColor); - s.writeBool(11, m_audioActive); - s.writeS32(12, m_volume); - s.writeBool(14, m_audioStereo); - s.writeS32(15, m_fmDeviation); - s.writeS32(16, m_squelchdB); - s.writeS32(17, m_squelchGate); - s.writeBool(18, m_agc); - s.writeString(19, m_title); - - return s.final(); - -} - -bool TCPSrcSettings::deserialize(const QByteArray& data) -{ - SimpleDeserializer d(data); - - if (!d.isValid()) - { - resetToDefaults(); - return false; - } - - if (d.getVersion() == 1) - { - QByteArray bytetmp; - QString strtmp; - int32_t s32tmp; - - if (m_channelMarker) { - d.readBlob(10, &bytetmp); - m_channelMarker->deserialize(bytetmp); - } - - d.readS32(2, &s32tmp, 0); - m_inputFrequencyOffset = s32tmp; - - d.readS32(3, &s32tmp, FormatS16LE); - - if ((s32tmp >= 0) && (s32tmp < (int) FormatNone)) { - m_sampleFormat = (SampleFormat) s32tmp; - } else { - m_sampleFormat = FormatS16LE; - } - - d.readReal(4, &m_outputSampleRate, 48000.0); - d.readReal(5, &m_rfBandwidth, 32000.0); - d.readS32(6, &s32tmp, 10); - m_tcpPort = s32tmp < 1024 ? 9999 : s32tmp % (1<<16); - - if (m_spectrumGUI) { - d.readBlob(7, &bytetmp); - m_spectrumGUI->deserialize(bytetmp); - } - - d.readS32(8, &s32tmp, 10); - m_gain = s32tmp / 10.0; - d.readU32(9, &m_rgbColor); - d.readBool(11, &m_audioActive, false); - d.readS32(12, &m_volume, 0); - d.readBool(14, &m_audioStereo, false); - d.readS32(15, &m_fmDeviation, 2500); - d.readS32(16, &m_squelchdB, -60); - d.readS32(17, &m_squelchGate, 5); - d.readBool(18, &m_agc, false); - d.readString(19, &m_title, "TCP Source"); - - return true; - } - else - { - resetToDefaults(); - return false; - } -} - diff --git a/plugins/channeltx/udpsink/CMakeLists.txt b/plugins/channelrx/udpsink/CMakeLists.txt similarity index 65% rename from plugins/channeltx/udpsink/CMakeLists.txt rename to plugins/channelrx/udpsink/CMakeLists.txt index b856fa4af..eabe5e963 100644 --- a/plugins/channeltx/udpsink/CMakeLists.txt +++ b/plugins/channelrx/udpsink/CMakeLists.txt @@ -1,11 +1,11 @@ project(udpsink) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + set(udpsink_SOURCES udpsink.cpp udpsinkgui.cpp udpsinkplugin.cpp - udpsinkudphandler.cpp - udpsinkmsg.cpp udpsinksettings.cpp ) @@ -13,8 +13,6 @@ set(udpsink_HEADERS udpsink.h udpsinkgui.h udpsinkplugin.h - udpsinkudphandler.h - udpsinkmsg.h udpsinksettings.h ) @@ -25,6 +23,7 @@ set(udpsink_FORMS include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) #include(${QT_USE_FILE}) @@ -34,18 +33,18 @@ add_definitions(-DQT_SHARED) qt5_wrap_ui(udpsink_FORMS_HEADERS ${udpsink_FORMS}) -add_library(modudpsink SHARED +add_library(udpsink SHARED ${udpsink_SOURCES} ${udpsink_HEADERS_MOC} ${udpsink_FORMS_HEADERS} ) -target_link_libraries(modudpsink +target_link_libraries(udpsink ${QT_LIBRARIES} sdrbase sdrgui ) -qt5_use_modules(modudpsink Core Widgets Network) +target_link_libraries(udpsink Qt5::Core Qt5::Widgets Qt5::Network) -install(TARGETS modudpsink DESTINATION lib/plugins/channeltx) +install(TARGETS udpsink DESTINATION lib/plugins/channelrx) diff --git a/plugins/channelrx/udpsrc/readme.md b/plugins/channelrx/udpsink/readme.md similarity index 89% rename from plugins/channelrx/udpsrc/readme.md rename to plugins/channelrx/udpsink/readme.md index f26dc0f63..b98dbe43b 100644 --- a/plugins/channelrx/udpsrc/readme.md +++ b/plugins/channelrx/udpsink/readme.md @@ -1,8 +1,8 @@ -

UDP source plugin

+

UDP sink plugin

Introduction

-By "source" one should undetstand a source of samples for the outside of SDRangel application. An UDP connection is established from the plugin to the given address and port and samples are directed to it. +By "sink" one should understand a sink for samples coming from the device baseband. An UDP connection is established from the plugin to the given address and port and samples are directed to it. The UDP block size or UDP payload size is fixed at 512 bytes. @@ -12,11 +12,11 @@ This plugin is available for Linux and Mac O/S only.

Interface

-![UDP Source plugin GUI](../../../doc/img/UDPsrc_plugin.png) +![UDP Sink plugin GUI](../../../doc/img/UDPsink_plugin.png)

1: Frequency shift from center frequency of reception

-Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

2: Input channel power

@@ -41,13 +41,13 @@ Sample rate in samples per second of the signal that is sent over UDP. The actua Left: combo box to specify the type of samples that are sent over UDP: - `I/Q`: Raw I/Q samples. Use it with software that accepts I/Q data as input like GNUradio with the `UDP source` block. The output is interleaved I and Q samples - - `NFM`: AF of FM demodulated signal. Use it with software that takes the FM demodulated audio or the discriminator output of a radio as input. Make sure you specify the appropriate signal bandwidth (see 7) according to the AF bandwidth needs. The output is a repetition of NFM samples on real part and on imaginary part this facilitates integration wtih software expecting a stereo type of input with the same samples on L and R channels. With GNURadio just use a complex to real block. + - `NFM`: AF of FM demodulated signal. Use it with software that takes the FM demodulated audio or the discriminator output of a radio as input. Make sure you specify the appropriate signal bandwidth (see 7) according to the AF bandwidth needs. The output is a repetition of NFM samples on real part and on imaginary part this facilitates integration with software expecting a stereo type of input with the same samples on L and R channels. With GNURadio just use a complex to real block. - `NFM Mono`: This is the same as above but only one sample is output for one NFM sample. This can be used with software that accept a mono type of input like `dsd` or `multimon`. - `USB`: AF of USB demodulated signal. Use it with software that uses a SSB demodulated signal as input i.e. software that is based on the audio output of a SSB radio. The output is the I/Q binaural output of the demodulator. - `LSB`: AF of LSB demodulated signal. Use it with software that uses a SSB demodulated signal as input i.e. software that is based on the audio output of a SSB radio. The output is the I/Q binaural output of the demodulator. - `LSB Mono`: AF of the LSB part of a SSB demodulated signal as "mono" (I+Q)*0.7 samples that is one sample per demodulator output sample. This can be used with software that accepts mono type of input. - `USB Mono`: AF of the USB part of a SSB demodulated signal as "mono" (I+Q)*0.7 samples that is one sample per demodulator output sample. This can be used with software that accepts mono type of input. - - `AM Mono`: AF of the enveloppe demodulated signal i.e. channel magnitude or sqrt(I² + Q²) as "mono" samples that is one sample per demodulator output sample. This can be used with software that accepts mono type of input. + - `AM Mono`: AF of the envelope demodulated signal i.e. channel magnitude or sqrt(I² + Q²) as "mono" samples that is one sample per demodulator output sample. This can be used with software that accepts mono type of input. - `AM !DC Mono`: Same as above but with a DC block based on magnitude average over a 5 ms period - `AM BPF Mono`: Same as AM Mono but raw magnitude signal is passed through a bandpass filter with lower cutoff at 300 Hz and higher cutoff at RF bandwidth frequency @@ -68,7 +68,7 @@ This is the maximum expected FM deviation in Hz for NFM demodulated samples. The

9: AGC and audio feedback control

-![UDP Source plugin GUI AGC](../../../doc/img/UDPsrc_plugin_agc.png) +![UDP Sink plugin GUI AGC](../../../doc/img/UDPsink_plugin_agc.png)

9.1: Toggle AGC

@@ -106,7 +106,7 @@ This gain is applied to the samples just before they are sent via UDP. The gain

13: Squelch

-![UDP Source plugin GUI Squelch](../../../doc/img/UDPsrc_plugin_sq.png) +![UDP Sink plugin GUI Squelch](../../../doc/img/UDPsink_plugin_sq.png)

13.1: Squelch indicator

diff --git a/plugins/channelrx/udpsrc/udpsrc.cpp b/plugins/channelrx/udpsink/udpsink.cpp similarity index 59% rename from plugins/channelrx/udpsrc/udpsrc.cpp rename to plugins/channelrx/udpsink/udpsink.cpp index 9a0fd98ff..040c054df 100644 --- a/plugins/channelrx/udpsrc/udpsrc.cpp +++ b/plugins/channelrx/udpsink/udpsink.cpp @@ -18,6 +18,11 @@ #include #include +#include "SWGChannelSettings.h" +#include "SWGUDPSinkSettings.h" +#include "SWGChannelReport.h" +#include "SWGUDPSinkReport.h" + #include "dsp/dspengine.h" #include "util/db.h" #include "dsp/downchannelizer.h" @@ -25,19 +30,18 @@ #include "dsp/dspcommands.h" #include "device/devicesourceapi.h" -#include "udpsrcgui.h" -#include "udpsrc.h" +#include "udpsink.h" -const Real UDPSrc::m_agcTarget = 16384.0f; +const Real UDPSink::m_agcTarget = 16384.0f; -MESSAGE_CLASS_DEFINITION(UDPSrc::MsgConfigureUDPSrc, Message) -MESSAGE_CLASS_DEFINITION(UDPSrc::MsgConfigureChannelizer, Message) -MESSAGE_CLASS_DEFINITION(UDPSrc::MsgUDPSrcSpectrum, Message) +MESSAGE_CLASS_DEFINITION(UDPSink::MsgConfigureUDPSource, Message) +MESSAGE_CLASS_DEFINITION(UDPSink::MsgConfigureChannelizer, Message) +MESSAGE_CLASS_DEFINITION(UDPSink::MsgUDPSinkSpectrum, Message) -const QString UDPSrc::m_channelIdURI = "sdrangel.channel.udpsrc"; -const QString UDPSrc::m_channelId = "UDPSrc"; +const QString UDPSink::m_channelIdURI = "sdrangel.channel.udpsink"; +const QString UDPSink::m_channelId = "UDPSink"; -UDPSrc::UDPSrc(DeviceSourceAPI *deviceAPI) : +UDPSink::UDPSink(DeviceSourceAPI *deviceAPI) : ChannelSinkAPI(m_channelIdURI), m_deviceAPI(deviceAPI), m_inputSampleRate(48000), @@ -58,10 +62,9 @@ UDPSrc::UDPSrc(DeviceSourceAPI *deviceAPI) : { setObjectName(m_channelId); - m_udpBuffer16 = new UDPSink(this, udpBlockSize, m_settings.m_udpPort); - m_udpBufferMono16 = new UDPSink(this, udpBlockSize, m_settings.m_udpPort); - m_udpBuffer24 = new UDPSink(this, udpBlockSize, m_settings.m_udpPort); - m_udpBufferMono24 = new UDPSink(this, udpBlockSize, m_settings.m_udpPort); + m_udpBuffer16 = new UDPSinkUtil(this, udpBlockSize, m_settings.m_udpPort); + m_udpBufferMono16 = new UDPSinkUtil(this, udpBlockSize, m_settings.m_udpPort); + m_udpBuffer24 = new UDPSinkUtil(this, udpBlockSize, m_settings.m_udpPort); m_audioSocket = new QUdpSocket(this); m_udpAudioBuf = new char[m_udpAudioPayloadSize]; @@ -87,12 +90,12 @@ UDPSrc::UDPSrc(DeviceSourceAPI *deviceAPI) : if (m_audioSocket->bind(QHostAddress::LocalHost, m_settings.m_audioPort)) { - qDebug("UDPSrc::UDPSrc: bind audio socket to port %d", m_settings.m_audioPort); + qDebug("UDPSink::UDPSink: bind audio socket to port %d", m_settings.m_audioPort); connect(m_audioSocket, SIGNAL(readyRead()), this, SLOT(audioReadyRead()), Qt::QueuedConnection); } else { - qWarning("UDPSrc::UDPSrc: cannot bind audio port"); + qWarning("UDPSink::UDPSink: cannot bind audio port"); } m_agc.setClampMax(SDR_RX_SCALED*SDR_RX_SCALED); @@ -100,38 +103,37 @@ UDPSrc::UDPSrc(DeviceSourceAPI *deviceAPI) : //DSPEngine::instance()->addAudioSink(&m_audioFifo); + applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); + m_channelizer = new DownChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); m_deviceAPI->addThreadedSink(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - - applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); - applySettings(m_settings, true); } -UDPSrc::~UDPSrc() +UDPSink::~UDPSink() { delete m_audioSocket; delete m_udpBuffer24; - delete m_udpBufferMono24; delete m_udpBuffer16; delete m_udpBufferMono16; delete[] m_udpAudioBuf; - if (UDPFilter) delete UDPFilter; - if (m_settings.m_audioActive) DSPEngine::instance()->removeAudioSink(&m_audioFifo); + DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo); m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSink(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; + delete UDPFilter; } -void UDPSrc::setSpectrum(MessageQueue* messageQueue, bool enabled) +void UDPSink::setSpectrum(MessageQueue* messageQueue, bool enabled) { - Message* cmd = MsgUDPSrcSpectrum::create(enabled); + Message* cmd = MsgUDPSinkSpectrum::create(enabled); messageQueue->push(cmd); } -void UDPSrc::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly) +void UDPSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly) { Complex ci; fftfilt::cmplx* sideband; @@ -151,9 +153,10 @@ void UDPSrc::feed(const SampleVector::const_iterator& begin, const SampleVector: double agcFactor = 1.0; if ((m_settings.m_agc) && - (m_settings.m_sampleFormat != UDPSrcSettings::FormatNFM) && - (m_settings.m_sampleFormat != UDPSrcSettings::FormatNFMMono) && - (m_settings.m_sampleFormat != UDPSrcSettings::FormatIQ)) + (m_settings.m_sampleFormat != UDPSinkSettings::FormatNFM) && + (m_settings.m_sampleFormat != UDPSinkSettings::FormatNFMMono) && + (m_settings.m_sampleFormat != UDPSinkSettings::FormatIQ16) && + (m_settings.m_sampleFormat != UDPSinkSettings::FormatIQ24)) { agcFactor = m_agc.feedAndGetValue(ci); inMagSq = m_agc.getMagSq(); @@ -173,7 +176,7 @@ void UDPSrc::feed(const SampleVector::const_iterator& begin, const SampleVector: calculateSquelch(m_inMagsq); - if (m_settings.m_sampleFormat == UDPSrcSettings::FormatLSB) // binaural LSB + if (m_settings.m_sampleFormat == UDPSinkSettings::FormatLSB) // binaural LSB { ci *= agcFactor; int n_out = UDPFilter->runSSB(ci, &sideband, false); @@ -189,7 +192,7 @@ void UDPSrc::feed(const SampleVector::const_iterator& begin, const SampleVector: } } } - if (m_settings.m_sampleFormat == UDPSrcSettings::FormatUSB) // binaural USB + if (m_settings.m_sampleFormat == UDPSinkSettings::FormatUSB) // binaural USB { ci *= agcFactor; int n_out = UDPFilter->runSSB(ci, &sideband, true); @@ -205,19 +208,19 @@ void UDPSrc::feed(const SampleVector::const_iterator& begin, const SampleVector: } } } - else if (m_settings.m_sampleFormat == UDPSrcSettings::FormatNFM) + else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatNFM) { Real discri = m_squelchOpen ? m_phaseDiscri.phaseDiscriminator(ci) * m_settings.m_gain : 0; udpWriteNorm(discri, discri); m_outMovingAverage.feed(discri*discri); } - else if (m_settings.m_sampleFormat == UDPSrcSettings::FormatNFMMono) + else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatNFMMono) { Real discri = m_squelchOpen ? m_phaseDiscri.phaseDiscriminator(ci) * m_settings.m_gain : 0; udpWriteNormMono(discri); m_outMovingAverage.feed(discri*discri); } - else if (m_settings.m_sampleFormat == UDPSrcSettings::FormatLSBMono) // Monaural LSB + else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatLSBMono) // Monaural LSB { ci *= agcFactor; int n_out = UDPFilter->runSSB(ci, &sideband, false); @@ -232,7 +235,7 @@ void UDPSrc::feed(const SampleVector::const_iterator& begin, const SampleVector: } } } - else if (m_settings.m_sampleFormat == UDPSrcSettings::FormatUSBMono) // Monaural USB + else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatUSBMono) // Monaural USB { ci *= agcFactor; int n_out = UDPFilter->runSSB(ci, &sideband, true); @@ -247,14 +250,14 @@ void UDPSrc::feed(const SampleVector::const_iterator& begin, const SampleVector: } } } - else if (m_settings.m_sampleFormat == UDPSrcSettings::FormatAMMono) + else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatAMMono) { Real amplitude = m_squelchOpen ? sqrt(inMagSq) * agcFactor * m_settings.m_gain : 0; FixReal demod = (FixReal) amplitude; udpWriteMono(demod); m_outMovingAverage.feed((amplitude/SDR_RX_SCALEF)*(amplitude/SDR_RX_SCALEF)); } - else if (m_settings.m_sampleFormat == UDPSrcSettings::FormatAMNoDCMono) + else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatAMNoDCMono) { if (m_squelchOpen) { @@ -271,7 +274,7 @@ void UDPSrc::feed(const SampleVector::const_iterator& begin, const SampleVector: m_outMovingAverage.feed(0); } } - else if (m_settings.m_sampleFormat == UDPSrcSettings::FormatAMBPFMono) + else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatAMBPFMono) { if (m_squelchOpen) { @@ -307,7 +310,7 @@ void UDPSrc::feed(const SampleVector::const_iterator& begin, const SampleVector: } } - //qDebug() << "UDPSrc::feed: " << m_sampleBuffer.size() * 4; + //qDebug() << "UDPSink::feed: " << m_sampleBuffer.size() * 4; if((m_spectrum != 0) && (m_spectrumEnabled)) { @@ -317,22 +320,22 @@ void UDPSrc::feed(const SampleVector::const_iterator& begin, const SampleVector: m_settingsMutex.unlock(); } -void UDPSrc::start() +void UDPSink::start() { m_phaseDiscri.reset(); applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); } -void UDPSrc::stop() +void UDPSink::stop() { } -bool UDPSrc::handleMessage(const Message& cmd) +bool UDPSink::handleMessage(const Message& cmd) { if (DownChannelizer::MsgChannelizerNotification::match(cmd)) { DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd; - qDebug() << "UDPSrc::handleMessage: MsgChannelizerNotification: m_inputSampleRate: " << notif.getSampleRate() + qDebug() << "UDPSink::handleMessage: MsgChannelizerNotification: m_inputSampleRate: " << notif.getSampleRate() << " frequencyOffset: " << notif.getFrequencyOffset(); applyChannelSettings(notif.getSampleRate(), notif.getFrequencyOffset()); @@ -343,7 +346,7 @@ bool UDPSrc::handleMessage(const Message& cmd) else if (MsgConfigureChannelizer::match(cmd)) { MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; - qDebug() << "UDPSrc::handleMessage: MsgConfigureChannelizer:" + qDebug() << "UDPSink::handleMessage: MsgConfigureChannelizer:" << " sampleRate: " << cfg.getSampleRate() << " centerFrequency: " << cfg.getCenterFrequency(); @@ -353,22 +356,22 @@ bool UDPSrc::handleMessage(const Message& cmd) return true; } - else if (MsgConfigureUDPSrc::match(cmd)) + else if (MsgConfigureUDPSource::match(cmd)) { - MsgConfigureUDPSrc& cfg = (MsgConfigureUDPSrc&) cmd; - qDebug("UDPSrc::handleMessage: MsgConfigureUDPSrc"); + MsgConfigureUDPSource& cfg = (MsgConfigureUDPSource&) cmd; + qDebug("UDPSink::handleMessage: MsgConfigureUDPSource"); applySettings(cfg.getSettings(), cfg.getForce()); return true; } - else if (MsgUDPSrcSpectrum::match(cmd)) + else if (MsgUDPSinkSpectrum::match(cmd)) { - MsgUDPSrcSpectrum& spc = (MsgUDPSrcSpectrum&) cmd; + MsgUDPSinkSpectrum& spc = (MsgUDPSinkSpectrum&) cmd; m_spectrumEnabled = spc.getEnabled(); - qDebug() << "UDPSrc::handleMessage: MsgUDPSrcSpectrum: m_spectrumEnabled: " << m_spectrumEnabled; + qDebug() << "UDPSink::handleMessage: MsgUDPSinkSpectrum: m_spectrumEnabled: " << m_spectrumEnabled; return true; } @@ -389,13 +392,13 @@ bool UDPSrc::handleMessage(const Message& cmd) } } -void UDPSrc::audioReadyRead() +void UDPSink::audioReadyRead() { while (m_audioSocket->hasPendingDatagrams()) { qint64 pendingDataSize = m_audioSocket->pendingDatagramSize(); qint64 udpReadBytes = m_audioSocket->readDatagram(m_udpAudioBuf, pendingDataSize, 0, 0); - //qDebug("UDPSrc::audioReadyRead: %lld", udpReadBytes); + //qDebug("UDPSink::audioReadyRead: %lld", udpReadBytes); if (m_settings.m_audioActive) { @@ -411,11 +414,11 @@ void UDPSrc::audioReadyRead() if (m_audioBufferFill >= m_audioBuffer.size()) { - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 1); + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); if (res != m_audioBufferFill) { - qDebug("UDPSrc::audioReadyRead: (stereo) lost %u samples", m_audioBufferFill - res); + qDebug("UDPSink::audioReadyRead: (stereo) lost %u samples", m_audioBufferFill - res); } m_audioBufferFill = 0; @@ -433,11 +436,11 @@ void UDPSrc::audioReadyRead() if (m_audioBufferFill >= m_audioBuffer.size()) { - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 1); + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); if (res != m_audioBufferFill) { - qDebug("UDPSrc::audioReadyRead: (mono) lost %u samples", m_audioBufferFill - res); + qDebug("UDPSink::audioReadyRead: (mono) lost %u samples", m_audioBufferFill - res); } m_audioBufferFill = 0; @@ -445,21 +448,21 @@ void UDPSrc::audioReadyRead() } } - if (m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 0) != m_audioBufferFill) + if (m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill) != m_audioBufferFill) { - qDebug("UDPSrc::audioReadyRead: lost samples"); + qDebug("UDPSink::audioReadyRead: lost samples"); } m_audioBufferFill = 0; } } - //qDebug("UDPSrc::audioReadyRead: done"); + //qDebug("UDPSink::audioReadyRead: done"); } -void UDPSrc::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force) +void UDPSink::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force) { - qDebug() << "UDPSrc::applyChannelSettings:" + qDebug() << "UDPSink::applyChannelSettings:" << " inputSampleRate: " << inputSampleRate << " inputFrequencyOffset: " << inputFrequencyOffset; @@ -481,9 +484,9 @@ void UDPSrc::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, m_inputFrequencyOffset = inputFrequencyOffset; } -void UDPSrc::applySettings(const UDPSrcSettings& settings, bool force) +void UDPSink::applySettings(const UDPSinkSettings& settings, bool force) { - qDebug() << "UDPSrc::applySettings:" + qDebug() << "UDPSink::applySettings:" << " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset << " m_audioActive: " << settings.m_audioActive << " m_audioStereo: " << settings.m_audioStereo @@ -494,7 +497,6 @@ void UDPSrc::applySettings(const UDPSrcSettings& settings, bool force) << " m_squelchGate" << settings.m_squelchGate << " m_agc" << settings.m_agc << " m_sampleFormat: " << settings.m_sampleFormat - << " m_sampleSize: " << 16 + settings.m_sampleSize*8 << " m_outputSampleRate: " << settings.m_outputSampleRate << " m_rfBandwidth: " << settings.m_rfBandwidth << " m_fmDeviation: " << settings.m_fmDeviation @@ -512,10 +514,10 @@ void UDPSrc::applySettings(const UDPSrcSettings& settings, bool force) m_interpolator.create(16, m_inputSampleRate, settings.m_rfBandwidth / 2.0); m_sampleDistanceRemain = m_inputSampleRate / settings.m_outputSampleRate; - if ((settings.m_sampleFormat == UDPSrcSettings::FormatLSB) || - (settings.m_sampleFormat == UDPSrcSettings::FormatLSBMono) || - (settings.m_sampleFormat == UDPSrcSettings::FormatUSB) || - (settings.m_sampleFormat == UDPSrcSettings::FormatUSBMono)) + if ((settings.m_sampleFormat == UDPSinkSettings::FormatLSB) || + (settings.m_sampleFormat == UDPSinkSettings::FormatLSBMono) || + (settings.m_sampleFormat == UDPSinkSettings::FormatUSB) || + (settings.m_sampleFormat == UDPSinkSettings::FormatUSBMono)) { m_squelchGate = settings.m_outputSampleRate * 0.05; } @@ -526,7 +528,7 @@ void UDPSrc::applySettings(const UDPSrcSettings& settings, bool force) m_squelchRelease = (settings.m_outputSampleRate * settings.m_squelchGate) / 100; initSquelch(m_squelchOpen); - m_agc.resize(settings.m_outputSampleRate * 0.2, m_agcTarget); // Fixed 200 ms + m_agc.resize(settings.m_outputSampleRate/5, settings.m_outputSampleRate/20, m_agcTarget); // Fixed 200 ms int stepDownDelay = (settings.m_outputSampleRate * (settings.m_squelchGate == 0 ? 1 : settings.m_squelchGate))/100; m_agc.setStepDownDelay(stepDownDelay); m_agc.setGate(settings.m_outputSampleRate * 0.05); @@ -543,20 +545,20 @@ void UDPSrc::applySettings(const UDPSrcSettings& settings, bool force) if (settings.m_audioActive) { m_audioBufferFill = 0; - DSPEngine::instance()->addAudioSink(&m_audioFifo); + DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo, getInputMessageQueue()); } else { - DSPEngine::instance()->removeAudioSink(&m_audioFifo); + DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo); } } if ((settings.m_squelchGate != m_settings.m_squelchGate) || force) { - if ((settings.m_sampleFormat == UDPSrcSettings::FormatLSB) || - (settings.m_sampleFormat == UDPSrcSettings::FormatLSBMono) || - (settings.m_sampleFormat == UDPSrcSettings::FormatUSB) || - (settings.m_sampleFormat == UDPSrcSettings::FormatUSBMono)) + if ((settings.m_sampleFormat == UDPSinkSettings::FormatLSB) || + (settings.m_sampleFormat == UDPSinkSettings::FormatLSBMono) || + (settings.m_sampleFormat == UDPSinkSettings::FormatUSB) || + (settings.m_sampleFormat == UDPSinkSettings::FormatUSBMono)) { m_squelchGate = settings.m_outputSampleRate * 0.05; } @@ -582,7 +584,6 @@ void UDPSrc::applySettings(const UDPSrcSettings& settings, bool force) m_udpBuffer16->setAddress(const_cast(settings.m_udpAddress)); m_udpBufferMono16->setAddress(const_cast(settings.m_udpAddress)); m_udpBuffer24->setAddress(const_cast(settings.m_udpAddress)); - m_udpBufferMono24->setAddress(const_cast(settings.m_udpAddress)); } if ((settings.m_udpPort != m_settings.m_udpPort) || force) @@ -590,7 +591,6 @@ void UDPSrc::applySettings(const UDPSrcSettings& settings, bool force) m_udpBuffer16->setPort(settings.m_udpPort); m_udpBufferMono16->setPort(settings.m_udpPort); m_udpBuffer24->setPort(settings.m_udpPort); - m_udpBufferMono24->setPort(settings.m_udpPort); } if ((settings.m_audioPort != m_settings.m_audioPort) || force) @@ -602,11 +602,11 @@ void UDPSrc::applySettings(const UDPSrcSettings& settings, bool force) if (m_audioSocket->bind(QHostAddress::LocalHost, settings.m_audioPort)) { connect(m_audioSocket, SIGNAL(readyRead()), this, SLOT(audioReadyRead()), Qt::QueuedConnection); - qDebug("UDPSrc::handleMessage: audio socket bound to port %d", settings.m_audioPort); + qDebug("UDPSink::handleMessage: audio socket bound to port %d", settings.m_audioPort); } else { - qWarning("UDPSrc::handleMessage: cannot bind audio socket"); + qWarning("UDPSink::handleMessage: cannot bind audio socket"); } } @@ -620,24 +620,178 @@ void UDPSrc::applySettings(const UDPSrcSettings& settings, bool force) m_settings = settings; } -QByteArray UDPSrc::serialize() const +QByteArray UDPSink::serialize() const { return m_settings.serialize(); } -bool UDPSrc::deserialize(const QByteArray& data) +bool UDPSink::deserialize(const QByteArray& data) { if (m_settings.deserialize(data)) { - MsgConfigureUDPSrc *msg = MsgConfigureUDPSrc::create(m_settings, true); + MsgConfigureUDPSource *msg = MsgConfigureUDPSource::create(m_settings, true); m_inputMessageQueue.push(msg); return true; } else { m_settings.resetToDefaults(); - MsgConfigureUDPSrc *msg = MsgConfigureUDPSrc::create(m_settings, true); + MsgConfigureUDPSource *msg = MsgConfigureUDPSource::create(m_settings, true); m_inputMessageQueue.push(msg); return false; } } + +int UDPSink::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setUdpSinkSettings(new SWGSDRangel::SWGUDPSinkSettings()); + response.getUdpSinkSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int UDPSink::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + UDPSinkSettings settings = m_settings; + bool frequencyOffsetChanged = false; + + if (channelSettingsKeys.contains("outputSampleRate")) { + settings.m_outputSampleRate = response.getUdpSinkSettings()->getOutputSampleRate(); + } + if (channelSettingsKeys.contains("sampleFormat")) { + settings.m_sampleFormat = (UDPSinkSettings::SampleFormat) response.getUdpSinkSettings()->getSampleFormat(); + } + if (channelSettingsKeys.contains("inputFrequencyOffset")) + { + settings.m_inputFrequencyOffset = response.getUdpSinkSettings()->getInputFrequencyOffset(); + frequencyOffsetChanged = true; + } + if (channelSettingsKeys.contains("rfBandwidth")) { + settings.m_rfBandwidth = response.getUdpSinkSettings()->getRfBandwidth(); + } + if (channelSettingsKeys.contains("fmDeviation")) { + settings.m_fmDeviation = response.getUdpSinkSettings()->getFmDeviation(); + } + if (channelSettingsKeys.contains("channelMute")) { + settings.m_channelMute = response.getUdpSinkSettings()->getChannelMute() != 0; + } + if (channelSettingsKeys.contains("gain")) { + settings.m_gain = response.getUdpSinkSettings()->getGain(); + } + if (channelSettingsKeys.contains("squelchDB")) { + settings.m_squelchdB = response.getUdpSinkSettings()->getSquelchDb(); + } + if (channelSettingsKeys.contains("squelchGate")) { + settings.m_squelchGate = response.getUdpSinkSettings()->getSquelchGate(); + } + if (channelSettingsKeys.contains("squelchEnabled")) { + settings.m_squelchEnabled = response.getUdpSinkSettings()->getSquelchEnabled() != 0; + } + if (channelSettingsKeys.contains("agc")) { + settings.m_agc = response.getUdpSinkSettings()->getAgc() != 0; + } + if (channelSettingsKeys.contains("audioActive")) { + settings.m_audioActive = response.getUdpSinkSettings()->getAudioActive() != 0; + } + if (channelSettingsKeys.contains("audioStereo")) { + settings.m_audioStereo = response.getUdpSinkSettings()->getAudioStereo() != 0; + } + if (channelSettingsKeys.contains("volume")) { + settings.m_volume = response.getUdpSinkSettings()->getVolume(); + } + if (channelSettingsKeys.contains("udpAddress")) { + settings.m_udpAddress = *response.getUdpSinkSettings()->getUdpAddress(); + } + if (channelSettingsKeys.contains("udpPort")) { + settings.m_udpPort = response.getUdpSinkSettings()->getUdpPort(); + } + if (channelSettingsKeys.contains("audioPort")) { + settings.m_audioPort = response.getUdpSinkSettings()->getAudioPort(); + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getUdpSinkSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getUdpSinkSettings()->getTitle(); + } + + if (frequencyOffsetChanged) + { + UDPSink::MsgConfigureChannelizer *msgChan = UDPSink::MsgConfigureChannelizer::create( + (int) settings.m_outputSampleRate, + (int) settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(msgChan); + } + + MsgConfigureUDPSource *msg = MsgConfigureUDPSource::create(settings, force); + m_inputMessageQueue.push(msg); + + qDebug("getUdpSinkSettings::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureUDPSource *msgToGUI = MsgConfigureUDPSource::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int UDPSink::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setUdpSinkReport(new SWGSDRangel::SWGUDPSinkReport()); + response.getUdpSinkReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + +void UDPSink::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const UDPSinkSettings& settings) +{ + response.getUdpSinkSettings()->setOutputSampleRate(settings.m_outputSampleRate); + response.getUdpSinkSettings()->setSampleFormat((int) settings.m_sampleFormat); + response.getUdpSinkSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getUdpSinkSettings()->setRfBandwidth(settings.m_rfBandwidth); + response.getUdpSinkSettings()->setFmDeviation(settings.m_fmDeviation); + response.getUdpSinkSettings()->setChannelMute(settings.m_channelMute ? 1 : 0); + response.getUdpSinkSettings()->setGain(settings.m_gain); + response.getUdpSinkSettings()->setSquelchDb(settings.m_squelchdB); + response.getUdpSinkSettings()->setSquelchGate(settings.m_squelchGate); + response.getUdpSinkSettings()->setSquelchEnabled(settings.m_squelchEnabled ? 1 : 0); + response.getUdpSinkSettings()->setAgc(settings.m_agc ? 1 : 0); + response.getUdpSinkSettings()->setAudioActive(settings.m_audioActive ? 1 : 0); + response.getUdpSinkSettings()->setAudioStereo(settings.m_audioStereo ? 1 : 0); + response.getUdpSinkSettings()->setVolume(settings.m_volume); + + if (response.getUdpSinkSettings()->getUdpAddress()) { + *response.getUdpSinkSettings()->getUdpAddress() = settings.m_udpAddress; + } else { + response.getUdpSinkSettings()->setUdpAddress(new QString(settings.m_udpAddress)); + } + + response.getUdpSinkSettings()->setUdpPort(settings.m_udpPort); + response.getUdpSinkSettings()->setAudioPort(settings.m_audioPort); + response.getUdpSinkSettings()->setRgbColor(settings.m_rgbColor); + + if (response.getUdpSinkSettings()->getTitle()) { + *response.getUdpSinkSettings()->getTitle() = settings.m_title; + } else { + response.getUdpSinkSettings()->setTitle(new QString(settings.m_title)); + } +} + +void UDPSink::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + response.getUdpSinkReport()->setChannelPowerDb(CalcDb::dbPower(getInMagSq())); + response.getUdpSinkReport()->setOutputPowerDb(CalcDb::dbPower(getMagSq())); + response.getUdpSinkReport()->setSquelch(m_squelchOpen ? 1 : 0); + response.getUdpSinkReport()->setInputSampleRate(m_inputSampleRate); +} diff --git a/plugins/channelrx/udpsrc/udpsrc.h b/plugins/channelrx/udpsink/udpsink.h similarity index 76% rename from plugins/channelrx/udpsrc/udpsrc.h rename to plugins/channelrx/udpsink/udpsink.h index 9e9864d4e..5923226b0 100644 --- a/plugins/channelrx/udpsrc/udpsrc.h +++ b/plugins/channelrx/udpsink/udpsink.h @@ -30,38 +30,38 @@ #include "dsp/movingaverage.h" #include "dsp/agc.h" #include "dsp/bandpass.h" -#include "util/udpsink.h" +#include "util/udpsinkutil.h" #include "util/message.h" #include "audio/audiofifo.h" -#include "udpsrcsettings.h" +#include "udpsinksettings.h" class QUdpSocket; class DeviceSourceAPI; class ThreadedBasebandSampleSink; class DownChannelizer; -class UDPSrc : public BasebandSampleSink, public ChannelSinkAPI { +class UDPSink : public BasebandSampleSink, public ChannelSinkAPI { Q_OBJECT public: - class MsgConfigureUDPSrc : public Message { + class MsgConfigureUDPSource : public Message { MESSAGE_CLASS_DECLARATION public: - const UDPSrcSettings& getSettings() const { return m_settings; } + const UDPSinkSettings& getSettings() const { return m_settings; } bool getForce() const { return m_force; } - static MsgConfigureUDPSrc* create(const UDPSrcSettings& settings, bool force) + static MsgConfigureUDPSource* create(const UDPSinkSettings& settings, bool force) { - return new MsgConfigureUDPSrc(settings, force); + return new MsgConfigureUDPSource(settings, force); } private: - UDPSrcSettings m_settings; + UDPSinkSettings m_settings; bool m_force; - MsgConfigureUDPSrc(const UDPSrcSettings& settings, bool force) : + MsgConfigureUDPSource(const UDPSinkSettings& settings, bool force) : Message(), m_settings(settings), m_force(force) @@ -92,8 +92,8 @@ public: { } }; - UDPSrc(DeviceSourceAPI *deviceAPI); - virtual ~UDPSrc(); + UDPSink(DeviceSourceAPI *deviceAPI); + virtual ~UDPSink(); virtual void destroy() { delete this; } void setSpectrum(BasebandSampleSink* spectrum) { m_spectrum = spectrum; } @@ -114,6 +114,20 @@ public: virtual QByteArray serialize() const; virtual bool deserialize(const QByteArray& data); + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + static const QString m_channelIdURI; static const QString m_channelId; static const int udpBlockSize = 512; // UDP block size in number of bytes @@ -122,21 +136,21 @@ public slots: void audioReadyRead(); protected: - class MsgUDPSrcSpectrum : public Message { + class MsgUDPSinkSpectrum : public Message { MESSAGE_CLASS_DECLARATION public: bool getEnabled() const { return m_enabled; } - static MsgUDPSrcSpectrum* create(bool enabled) + static MsgUDPSinkSpectrum* create(bool enabled) { - return new MsgUDPSrcSpectrum(enabled); + return new MsgUDPSinkSpectrum(enabled); } private: bool m_enabled; - MsgUDPSrcSpectrum(bool enabled) : + MsgUDPSinkSpectrum(bool enabled) : Message(), m_enabled(enabled) { } @@ -164,7 +178,7 @@ protected: int m_inputSampleRate; int m_inputFrequencyOffset; - UDPSrcSettings m_settings; + UDPSinkSettings m_settings; QUdpSocket *m_audioSocket; @@ -183,10 +197,9 @@ protected: fftfilt* UDPFilter; SampleVector m_sampleBuffer; - UDPSink *m_udpBuffer16; - UDPSink *m_udpBufferMono16; - UDPSink *m_udpBuffer24; - UDPSink *m_udpBufferMono24; + UDPSinkUtil *m_udpBuffer16; + UDPSinkUtil *m_udpBufferMono16; + UDPSinkUtil *m_udpBuffer24; AudioVector m_audioBuffer; uint m_audioBufferFill; @@ -217,7 +230,10 @@ protected: QMutex m_settingsMutex; void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = true); - void applySettings(const UDPSrcSettings& settings, bool force = false); + void applySettings(const UDPSinkSettings& settings, bool force = false); + + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const UDPSinkSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); inline void calculateSquelch(double value) { @@ -281,18 +297,22 @@ protected: { if (SDR_RX_SAMP_SZ == 16) { - if (m_settings.m_sampleSize == UDPSrcSettings::Size16bits) { + if (m_settings.m_sampleFormat == UDPSinkSettings::FormatIQ16) { m_udpBuffer16->write(Sample16(real, imag)); - } else if (m_settings.m_sampleSize == UDPSrcSettings::Size24bits) { + } else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatIQ24) { m_udpBuffer24->write(Sample24(real<<8, imag<<8)); + } else { + m_udpBuffer16->write(Sample16(real, imag)); } } else if (SDR_RX_SAMP_SZ == 24) { - if (m_settings.m_sampleSize == UDPSrcSettings::Size16bits) { + if (m_settings.m_sampleFormat == UDPSinkSettings::FormatIQ16) { m_udpBuffer16->write(Sample16(real>>8, imag>>8)); - } else if (m_settings.m_sampleSize == UDPSrcSettings::Size24bits) { + } else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatIQ24) { m_udpBuffer24->write(Sample24(real, imag)); + } else { + m_udpBuffer16->write(Sample16(real>>8, imag>>8)); } } } @@ -301,38 +321,22 @@ protected: { if (SDR_RX_SAMP_SZ == 16) { - if (m_settings.m_sampleSize == UDPSrcSettings::Size16bits) { - m_udpBufferMono16->write(sample); - } else if (m_settings.m_sampleSize == UDPSrcSettings::Size24bits) { - m_udpBufferMono24->write(sample<<8); - } + m_udpBufferMono16->write(sample); } else if (SDR_RX_SAMP_SZ == 24) { - if (m_settings.m_sampleSize == UDPSrcSettings::Size16bits) { - m_udpBufferMono16->write(sample>>8); - } else if (m_settings.m_sampleSize == UDPSrcSettings::Size24bits) { - m_udpBufferMono24->write(sample); - } + m_udpBufferMono16->write(sample>>8); } } void udpWriteNorm(Real real, Real imag) { - if (m_settings.m_sampleSize == UDPSrcSettings::Size16bits) { - m_udpBuffer16->write(Sample16(real*32768.0, imag*32768.0)); - } else if (m_settings.m_sampleSize == UDPSrcSettings::Size24bits) { - m_udpBuffer24->write(Sample24(real*8388608.0, imag*8388608.0)); - } + m_udpBuffer16->write(Sample16(real*32768.0, imag*32768.0)); } void udpWriteNormMono(Real sample) { - if (m_settings.m_sampleSize == UDPSrcSettings::Size16bits) { - m_udpBufferMono16->write(sample*32768.0); - } else if (m_settings.m_sampleSize == UDPSrcSettings::Size24bits) { - m_udpBufferMono24->write(sample*8388608.0); - } + m_udpBufferMono16->write(sample*32768.0); } }; diff --git a/plugins/channeltx/udpsink/udpsink.pro b/plugins/channelrx/udpsink/udpsink.pro similarity index 76% rename from plugins/channeltx/udpsink/udpsink.pro rename to plugins/channelrx/udpsink/udpsink.pro index 4904bbc8c..534f2f6cc 100644 --- a/plugins/channeltx/udpsink/udpsink.pro +++ b/plugins/channelrx/udpsink/udpsink.pro @@ -1,13 +1,13 @@ #-------------------------------------------------------- # -# Pro file for Windows builds with Qt Creator +# Pro file for Android and Windows builds with Qt Creator # #-------------------------------------------------------- TEMPLATE = lib CONFIG += plugin -QT += core gui widgets multimedia opengl network +QT += core gui widgets multimedia network opengl TARGET = udpsink @@ -18,8 +18,10 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui +INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug @@ -27,20 +29,17 @@ CONFIG(Debug):build_subdir = debug SOURCES += udpsink.cpp\ udpsinkgui.cpp\ udpsinkplugin.cpp\ - udpsinkmsg.cpp\ - udpsinkudphandler.cpp\ udpsinksettings.cpp HEADERS += udpsink.h\ udpsinkgui.h\ udpsinkplugin.h\ - udpsinkmsg.h\ - udpsinkudphandler.h\ udpsinksettings.h FORMS += udpsinkgui.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui +LIBS += -L../../../swagger/$${build_subdir} -lswagger RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/plugins/channelrx/udpsrc/udpsrcgui.cpp b/plugins/channelrx/udpsink/udpsinkgui.cpp similarity index 64% rename from plugins/channelrx/udpsrc/udpsrcgui.cpp rename to plugins/channelrx/udpsink/udpsinkgui.cpp index d2f40764a..eed9fcb54 100644 --- a/plugins/channelrx/udpsrc/udpsrcgui.cpp +++ b/plugins/channelrx/udpsink/udpsinkgui.cpp @@ -15,8 +15,6 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "udpsrcgui.h" - #include "device/devicesourceapi.h" #include "device/deviceuiset.h" #include "plugin/pluginapi.h" @@ -25,44 +23,45 @@ #include "util/simpleserializer.h" #include "util/db.h" #include "gui/basicchannelsettingsdialog.h" -#include "ui_udpsrcgui.h" +#include "ui_udpsinkgui.h" #include "mainwindow.h" -#include "udpsrc.h" +#include "udpsink.h" +#include "udpsinkgui.h" -UDPSrcGUI* UDPSrcGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) +UDPSinkGUI* UDPSinkGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) { - UDPSrcGUI* gui = new UDPSrcGUI(pluginAPI, deviceUISet, rxChannel); + UDPSinkGUI* gui = new UDPSinkGUI(pluginAPI, deviceUISet, rxChannel); return gui; } -void UDPSrcGUI::destroy() +void UDPSinkGUI::destroy() { delete this; } -void UDPSrcGUI::setName(const QString& name) +void UDPSinkGUI::setName(const QString& name) { setObjectName(name); } -qint64 UDPSrcGUI::getCenterFrequency() const +qint64 UDPSinkGUI::getCenterFrequency() const { return m_channelMarker.getCenterFrequency(); } -void UDPSrcGUI::setCenterFrequency(qint64 centerFrequency) +void UDPSinkGUI::setCenterFrequency(qint64 centerFrequency) { m_channelMarker.setCenterFrequency(centerFrequency); applySettings(); } -QString UDPSrcGUI::getName() const +QString UDPSinkGUI::getName() const { return objectName(); } -void UDPSrcGUI::resetToDefaults() +void UDPSinkGUI::resetToDefaults() { m_settings.resetToDefaults(); displaySettings(); @@ -70,12 +69,12 @@ void UDPSrcGUI::resetToDefaults() applySettings(true); } -QByteArray UDPSrcGUI::serialize() const +QByteArray UDPSinkGUI::serialize() const { return m_settings.serialize(); } -bool UDPSrcGUI::deserialize(const QByteArray& data) +bool UDPSinkGUI::deserialize(const QByteArray& data) { if(m_settings.deserialize(data)) { @@ -89,38 +88,62 @@ bool UDPSrcGUI::deserialize(const QByteArray& data) } } -bool UDPSrcGUI::handleMessage(const Message& message __attribute__((unused))) +bool UDPSinkGUI::handleMessage(const Message& message ) { - qDebug() << "UDPSrcGUI::handleMessage"; - return false; + if (UDPSink::MsgConfigureUDPSource::match(message)) + { + const UDPSink::MsgConfigureUDPSource& cfg = (UDPSink::MsgConfigureUDPSource&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else + { + return false; + } } -void UDPSrcGUI::channelMarkerChangedByCursor() +void UDPSinkGUI::handleSourceMessages() +{ + Message* message; + + while ((message = getInputMessageQueue()->pop()) != 0) + { + if (handleMessage(*message)) + { + delete message; + } + } +} + +void UDPSinkGUI::channelMarkerChangedByCursor() { ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); applySettingsImmediate(); } -void UDPSrcGUI::channelMarkerHighlightedByCursor() +void UDPSinkGUI::channelMarkerHighlightedByCursor() { setHighlighted(m_channelMarker.getHighlighted()); } -void UDPSrcGUI::tick() +void UDPSinkGUI::tick() { if (m_tickCount % 4 == 0) { // m_channelPowerAvg.feed(m_udpSrc->getMagSq()); // double powDb = CalcDb::dbPower(m_channelPowerAvg.average()); - double powDb = CalcDb::dbPower(m_udpSrc->getMagSq()); + double powDb = CalcDb::dbPower(m_udpSink->getMagSq()); ui->channelPower->setText(QString::number(powDb, 'f', 1)); - m_inPowerAvg.feed(m_udpSrc->getInMagSq()); + m_inPowerAvg.feed(m_udpSink->getInMagSq()); double inPowDb = CalcDb::dbPower(m_inPowerAvg.average()); ui->inputPower->setText(QString::number(inPowDb, 'f', 1)); } - if (m_udpSrc->getSquelchOpen()) { + if (m_udpSink->getSquelchOpen()) { ui->squelchLabel->setStyleSheet("QLabel { background-color : green; }"); } else { ui->squelchLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }"); @@ -129,12 +152,12 @@ void UDPSrcGUI::tick() m_tickCount++; } -UDPSrcGUI::UDPSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) : +UDPSinkGUI::UDPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) : RollupWidget(parent), - ui(new Ui::UDPSrcGUI), + ui(new Ui::UDPSinkGUI), m_pluginAPI(pluginAPI), m_deviceUISet(deviceUISet), - m_udpSrc(0), + m_udpSink(0), m_channelMarker(this), m_channelPowerAvg(4, 1e-10), m_inPowerAvg(4, 1e-10), @@ -148,8 +171,9 @@ UDPSrcGUI::UDPSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam setAttribute(Qt::WA_DeleteOnClose, true); m_spectrumVis = new SpectrumVis(SDR_RX_SCALEF, ui->glSpectrum); - m_udpSrc = (UDPSrc*) rxChannel; //new UDPSrc(m_deviceUISet->m_deviceSourceAPI); - m_udpSrc->setSpectrum(m_spectrumVis); + m_udpSink = (UDPSink*) rxChannel; //new UDPSrc(m_deviceUISet->m_deviceSourceAPI); + m_udpSink->setSpectrum(m_spectrumVis); + m_udpSink->setMessageQueueToGUI(getInputMessageQueue()); ui->fmDeviation->setEnabled(false); @@ -162,7 +186,13 @@ UDPSrcGUI::UDPSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam ui->glSpectrum->setSampleRate(ui->sampleRate->text().toInt()); ui->glSpectrum->setDisplayWaterfall(true); ui->glSpectrum->setDisplayMaxHold(true); - m_spectrumVis->configure(m_spectrumVis->getInputMessageQueue(), 64, 10, FFTWindow::BlackmanHarris); + m_spectrumVis->configure(m_spectrumVis->getInputMessageQueue(), + 64, // FFT size + 10, // overlapping % + 0, // number of averaging samples + 0, // no averaging + FFTWindow::BlackmanHarris, + false); // logarithmic scale ui->glSpectrum->connectTimer(MainWindow::getInstance()->getMasterTimer()); connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); @@ -171,9 +201,6 @@ UDPSrcGUI::UDPSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam m_channelMarker.setBandwidth(16000); m_channelMarker.setCenterFrequency(0); m_channelMarker.setTitle("UDP Sample Source"); - m_channelMarker.setUDPAddress("127.0.0.1"); - m_channelMarker.setUDPSendPort(9999); - m_channelMarker.setUDPReceivePort(9998); m_channelMarker.setColor(m_settings.m_rgbColor); m_channelMarker.blockSignals(false); m_channelMarker.setVisible(true); // activate signal on the last setting only @@ -183,12 +210,13 @@ UDPSrcGUI::UDPSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam m_settings.setChannelMarker(&m_channelMarker); m_settings.setSpectrumGUI(ui->spectrumGUI); - m_deviceUISet->registerRxChannelInstance(UDPSrc::m_channelIdURI, this); + m_deviceUISet->registerRxChannelInstance(UDPSink::m_channelIdURI, this); m_deviceUISet->addChannelMarker(&m_channelMarker); m_deviceUISet->addRollupWidget(this); connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor())); connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor())); + connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages())); ui->spectrumGUI->setBuddies(m_spectrumVis->getInputMessageQueue(), m_spectrumVis, ui->glSpectrum); @@ -197,20 +225,20 @@ UDPSrcGUI::UDPSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam applySettings(true); } -UDPSrcGUI::~UDPSrcGUI() +UDPSinkGUI::~UDPSinkGUI() { m_deviceUISet->removeRxChannelInstance(this); - delete m_udpSrc; // TODO: check this: when the GUI closes it has to delete the demodulator + delete m_udpSink; // TODO: check this: when the GUI closes it has to delete the demodulator delete m_spectrumVis; delete ui; } -void UDPSrcGUI::blockApplySettings(bool block) +void UDPSinkGUI::blockApplySettings(bool block) { m_doApplySettings = !block; } -void UDPSrcGUI::displaySettings() +void UDPSinkGUI::displaySettings() { m_channelMarker.blockSignals(true); m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset); @@ -221,17 +249,22 @@ void UDPSrcGUI::displaySettings() setTitleColor(m_settings.m_rgbColor); setWindowTitle(m_channelMarker.getTitle()); - displayUDPAddress(); + + blockApplySettings(true); ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); ui->sampleRate->setText(QString("%1").arg(m_settings.m_outputSampleRate, 0)); setSampleFormatIndex(m_settings.m_sampleFormat); + ui->outputUDPAddress->setText(m_settings.m_udpAddress); + ui->outputUDPPort->setText(tr("%1").arg(m_settings.m_udpPort)); + ui->inputUDPAudioPort->setText(tr("%1").arg(m_settings.m_audioPort)); + ui->squelch->setValue(m_settings.m_squelchdB); ui->squelchText->setText(tr("%1").arg(ui->squelch->value()*1.0, 0, 'f', 0)); - qDebug("UDPSrcGUI::deserialize: m_squelchGate: %d", m_settings.m_squelchGate); + qDebug("UDPSinkGUI::deserialize: m_squelchGate: %d", m_settings.m_squelchGate); ui->squelchGate->setValue(m_settings.m_squelchGate); ui->squelchGateText->setText(tr("%1").arg(m_settings.m_squelchGate*10.0, 0, 'f', 0)); @@ -248,140 +281,145 @@ void UDPSrcGUI::displaySettings() ui->gain->setValue(m_settings.m_gain*10.0); ui->gainText->setText(tr("%1").arg(ui->gain->value()/10.0, 0, 'f', 1)); + ui->applyBtn->setEnabled(false); + ui->applyBtn->setStyleSheet("QPushButton { background:rgb(79,79,79); }"); + + blockApplySettings(false); + ui->glSpectrum->setSampleRate(m_settings.m_outputSampleRate); - - displayUDPAddress(); } -void UDPSrcGUI::displayUDPAddress() -{ - ui->addressText->setText(tr("%1:%2/%3").arg(m_channelMarker.getUDPAddress()).arg(m_channelMarker.getUDPSendPort()).arg(m_channelMarker.getUDPReceivePort())); -} - -void UDPSrcGUI::setSampleFormatIndex(const UDPSrcSettings::SampleFormat& sampleFormat) +void UDPSinkGUI::setSampleFormatIndex(const UDPSinkSettings::SampleFormat& sampleFormat) { switch(sampleFormat) { - case UDPSrcSettings::FormatIQ: + case UDPSinkSettings::FormatIQ16: ui->sampleFormat->setCurrentIndex(0); break; - case UDPSrcSettings::FormatNFM: + case UDPSinkSettings::FormatIQ24: ui->sampleFormat->setCurrentIndex(1); break; - case UDPSrcSettings::FormatNFMMono: + case UDPSinkSettings::FormatNFM: ui->sampleFormat->setCurrentIndex(2); break; - case UDPSrcSettings::FormatLSB: + case UDPSinkSettings::FormatNFMMono: ui->sampleFormat->setCurrentIndex(3); break; - case UDPSrcSettings::FormatUSB: + case UDPSinkSettings::FormatLSB: ui->sampleFormat->setCurrentIndex(4); break; - case UDPSrcSettings::FormatLSBMono: + case UDPSinkSettings::FormatUSB: ui->sampleFormat->setCurrentIndex(5); break; - case UDPSrcSettings::FormatUSBMono: + case UDPSinkSettings::FormatLSBMono: ui->sampleFormat->setCurrentIndex(6); break; - case UDPSrcSettings::FormatAMMono: + case UDPSinkSettings::FormatUSBMono: ui->sampleFormat->setCurrentIndex(7); break; - case UDPSrcSettings::FormatAMNoDCMono: + case UDPSinkSettings::FormatAMMono: ui->sampleFormat->setCurrentIndex(8); break; - case UDPSrcSettings::FormatAMBPFMono: + case UDPSinkSettings::FormatAMNoDCMono: ui->sampleFormat->setCurrentIndex(9); break; + case UDPSinkSettings::FormatAMBPFMono: + ui->sampleFormat->setCurrentIndex(10); + break; default: ui->sampleFormat->setCurrentIndex(0); break; } } -void UDPSrcGUI::setSampleFormat(int index) +void UDPSinkGUI::setSampleFormat(int index) { switch(index) { case 0: - m_settings.m_sampleFormat = UDPSrcSettings::FormatIQ; + m_settings.m_sampleFormat = UDPSinkSettings::FormatIQ16; ui->fmDeviation->setEnabled(false); break; case 1: - m_settings.m_sampleFormat = UDPSrcSettings::FormatNFM; - ui->fmDeviation->setEnabled(true); + m_settings.m_sampleFormat = UDPSinkSettings::FormatIQ24; + ui->fmDeviation->setEnabled(false); break; case 2: - m_settings.m_sampleFormat = UDPSrcSettings::FormatNFMMono; + m_settings.m_sampleFormat = UDPSinkSettings::FormatNFM; ui->fmDeviation->setEnabled(true); break; case 3: - m_settings.m_sampleFormat = UDPSrcSettings::FormatLSB; - ui->fmDeviation->setEnabled(false); + m_settings.m_sampleFormat = UDPSinkSettings::FormatNFMMono; + ui->fmDeviation->setEnabled(true); break; case 4: - m_settings.m_sampleFormat = UDPSrcSettings::FormatUSB; + m_settings.m_sampleFormat = UDPSinkSettings::FormatLSB; ui->fmDeviation->setEnabled(false); break; case 5: - m_settings.m_sampleFormat = UDPSrcSettings::FormatLSBMono; + m_settings.m_sampleFormat = UDPSinkSettings::FormatUSB; ui->fmDeviation->setEnabled(false); break; case 6: - m_settings.m_sampleFormat = UDPSrcSettings::FormatUSBMono; + m_settings.m_sampleFormat = UDPSinkSettings::FormatLSBMono; ui->fmDeviation->setEnabled(false); break; case 7: - m_settings.m_sampleFormat = UDPSrcSettings::FormatAMMono; + m_settings.m_sampleFormat = UDPSinkSettings::FormatUSBMono; ui->fmDeviation->setEnabled(false); break; case 8: - m_settings.m_sampleFormat = UDPSrcSettings::FormatAMNoDCMono; + m_settings.m_sampleFormat = UDPSinkSettings::FormatAMMono; ui->fmDeviation->setEnabled(false); break; case 9: - m_settings.m_sampleFormat = UDPSrcSettings::FormatAMBPFMono; + m_settings.m_sampleFormat = UDPSinkSettings::FormatAMNoDCMono; + ui->fmDeviation->setEnabled(false); + break; + case 10: + m_settings.m_sampleFormat = UDPSinkSettings::FormatAMBPFMono; ui->fmDeviation->setEnabled(false); break; default: - m_settings.m_sampleFormat = UDPSrcSettings::FormatIQ; + m_settings.m_sampleFormat = UDPSinkSettings::FormatIQ16; ui->fmDeviation->setEnabled(false); break; } } -void UDPSrcGUI::applySettingsImmediate(bool force) +void UDPSinkGUI::applySettingsImmediate(bool force) { if (m_doApplySettings) { - UDPSrc::MsgConfigureUDPSrc* message = UDPSrc::MsgConfigureUDPSrc::create( m_settings, force); - m_udpSrc->getInputMessageQueue()->push(message); + UDPSink::MsgConfigureUDPSource* message = UDPSink::MsgConfigureUDPSource::create( m_settings, force); + m_udpSink->getInputMessageQueue()->push(message); } } -void UDPSrcGUI::applySettings(bool force) +void UDPSinkGUI::applySettings(bool force) { if (m_doApplySettings) { - UDPSrc::MsgConfigureChannelizer* channelConfigMsg = UDPSrc::MsgConfigureChannelizer::create( + UDPSink::MsgConfigureChannelizer* channelConfigMsg = UDPSink::MsgConfigureChannelizer::create( m_settings.m_outputSampleRate, m_channelMarker.getCenterFrequency()); - m_udpSrc->getInputMessageQueue()->push(channelConfigMsg); + m_udpSink->getInputMessageQueue()->push(channelConfigMsg); - UDPSrc::MsgConfigureUDPSrc* message = UDPSrc::MsgConfigureUDPSrc::create( m_settings, force); - m_udpSrc->getInputMessageQueue()->push(message); + UDPSink::MsgConfigureUDPSource* message = UDPSink::MsgConfigureUDPSource::create( m_settings, force); + m_udpSink->getInputMessageQueue()->push(message); ui->applyBtn->setEnabled(false); ui->applyBtn->setStyleSheet("QPushButton { background:rgb(79,79,79); }"); } } -void UDPSrcGUI::on_deltaFrequency_changed(qint64 value) +void UDPSinkGUI::on_deltaFrequency_changed(qint64 value) { m_channelMarker.setCenterFrequency(value); m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); applySettings(); } -void UDPSrcGUI::on_sampleFormat_currentIndexChanged(int index) +void UDPSinkGUI::on_sampleFormat_currentIndexChanged(int index) { if ((index == 1) || (index == 2)) { ui->fmDeviation->setEnabled(true); @@ -395,19 +433,46 @@ void UDPSrcGUI::on_sampleFormat_currentIndexChanged(int index) ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); } -void UDPSrcGUI::on_sampleSize_currentIndexChanged(int index) +void UDPSinkGUI::on_outputUDPAddress_editingFinished() { - if ((index < 0) || (index >= UDPSrcSettings::SizeNone)) { - return; + m_settings.m_udpAddress = ui->outputUDPAddress->text(); + ui->applyBtn->setEnabled(true); + ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); +} + +void UDPSinkGUI::on_outputUDPPort_editingFinished() +{ + bool ok; + quint16 udpPort = ui->outputUDPPort->text().toInt(&ok); + + if((!ok) || (udpPort < 1024)) { + udpPort = 9998; } - m_settings.m_sampleSize = (UDPSrcSettings::SampleSize) index; + m_settings.m_udpPort = udpPort; + ui->outputUDPPort->setText(tr("%1").arg(m_settings.m_udpPort)); ui->applyBtn->setEnabled(true); ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); } -void UDPSrcGUI::on_sampleRate_textEdited(const QString& arg1 __attribute__((unused))) +void UDPSinkGUI::on_inputUDPAudioPort_editingFinished() +{ + bool ok; + quint16 udpPort = ui->inputUDPAudioPort->text().toInt(&ok); + + if((!ok) || (udpPort < 1024)) { + udpPort = 9997; + } + + m_settings.m_audioPort = udpPort; + ui->inputUDPAudioPort->setText(tr("%1").arg(m_settings.m_audioPort)); + + ui->applyBtn->setEnabled(true); + ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); +} + +void UDPSinkGUI::on_sampleRate_textEdited(const QString& arg1 __attribute__((unused))) { bool ok; Real outputSampleRate = ui->sampleRate->text().toDouble(&ok); @@ -426,7 +491,7 @@ void UDPSrcGUI::on_sampleRate_textEdited(const QString& arg1 __attribute__((unus ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); } -void UDPSrcGUI::on_rfBandwidth_textEdited(const QString& arg1 __attribute__((unused))) +void UDPSinkGUI::on_rfBandwidth_textEdited(const QString& arg1 __attribute__((unused))) { bool ok; Real rfBandwidth = ui->rfBandwidth->text().toDouble(&ok); @@ -447,7 +512,7 @@ void UDPSrcGUI::on_rfBandwidth_textEdited(const QString& arg1 __attribute__((unu ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); } -void UDPSrcGUI::on_fmDeviation_textEdited(const QString& arg1 __attribute__((unused))) +void UDPSinkGUI::on_fmDeviation_textEdited(const QString& arg1 __attribute__((unused))) { bool ok; int fmDeviation = ui->fmDeviation->text().toInt(&ok); @@ -466,7 +531,7 @@ void UDPSrcGUI::on_fmDeviation_textEdited(const QString& arg1 __attribute__((unu ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); } -void UDPSrcGUI::on_applyBtn_clicked() +void UDPSinkGUI::on_applyBtn_clicked() { if (m_rfBandwidthChanged) { @@ -479,39 +544,39 @@ void UDPSrcGUI::on_applyBtn_clicked() applySettings(); } -void UDPSrcGUI::on_audioActive_toggled(bool active) +void UDPSinkGUI::on_audioActive_toggled(bool active) { m_settings.m_audioActive = active; applySettingsImmediate(); } -void UDPSrcGUI::on_audioStereo_toggled(bool stereo) +void UDPSinkGUI::on_audioStereo_toggled(bool stereo) { m_settings.m_audioStereo = stereo; applySettingsImmediate(); } -void UDPSrcGUI::on_agc_toggled(bool agc) +void UDPSinkGUI::on_agc_toggled(bool agc) { m_settings.m_agc = agc; applySettingsImmediate(); } -void UDPSrcGUI::on_gain_valueChanged(int value) +void UDPSinkGUI::on_gain_valueChanged(int value) { m_settings.m_gain = value / 10.0; ui->gainText->setText(tr("%1").arg(value/10.0, 0, 'f', 1)); applySettingsImmediate(); } -void UDPSrcGUI::on_volume_valueChanged(int value) +void UDPSinkGUI::on_volume_valueChanged(int value) { m_settings.m_volume = value; ui->volumeText->setText(QString("%1").arg(value)); applySettingsImmediate(); } -void UDPSrcGUI::on_squelch_valueChanged(int value) +void UDPSinkGUI::on_squelch_valueChanged(int value) { m_settings.m_squelchdB = value; @@ -529,22 +594,22 @@ void UDPSrcGUI::on_squelch_valueChanged(int value) applySettingsImmediate(); } -void UDPSrcGUI::on_squelchGate_valueChanged(int value) +void UDPSinkGUI::on_squelchGate_valueChanged(int value) { m_settings.m_squelchGate = value; ui->squelchGateText->setText(tr("%1").arg(value*10.0, 0, 'f', 0)); applySettingsImmediate(); } -void UDPSrcGUI::onWidgetRolled(QWidget* widget, bool rollDown) +void UDPSinkGUI::onWidgetRolled(QWidget* widget, bool rollDown) { - if ((widget == ui->spectrumBox) && (m_udpSrc != 0)) + if ((widget == ui->spectrumBox) && (m_udpSink != 0)) { - m_udpSrc->setSpectrum(m_udpSrc->getInputMessageQueue(), rollDown); + m_udpSink->setSpectrum(m_udpSink->getInputMessageQueue(), rollDown); } } -void UDPSrcGUI::onMenuDialogCalled(const QPoint &p) +void UDPSinkGUI::onMenuDialogCalled(const QPoint &p) { BasicChannelSettingsDialog dialog(&m_channelMarker, this); dialog.move(p); @@ -552,24 +617,21 @@ void UDPSrcGUI::onMenuDialogCalled(const QPoint &p) m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); - m_settings.m_udpAddress = m_channelMarker.getUDPAddress(), - m_settings.m_udpPort = m_channelMarker.getUDPSendPort(), m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); m_settings.m_title = m_channelMarker.getTitle(); setWindowTitle(m_settings.m_title); setTitleColor(m_settings.m_rgbColor); - displayUDPAddress(); applySettingsImmediate(); } -void UDPSrcGUI::leaveEvent(QEvent*) +void UDPSinkGUI::leaveEvent(QEvent*) { m_channelMarker.setHighlighted(false); } -void UDPSrcGUI::enterEvent(QEvent*) +void UDPSinkGUI::enterEvent(QEvent*) { m_channelMarker.setHighlighted(true); } diff --git a/plugins/channelrx/udpsrc/udpsrcgui.h b/plugins/channelrx/udpsink/udpsinkgui.h similarity index 83% rename from plugins/channelrx/udpsrc/udpsrcgui.h rename to plugins/channelrx/udpsink/udpsinkgui.h index abeac01d6..eec9ddd07 100644 --- a/plugins/channelrx/udpsrc/udpsrcgui.h +++ b/plugins/channelrx/udpsink/udpsinkgui.h @@ -25,23 +25,23 @@ #include "dsp/movingaverage.h" #include "util/messagequeue.h" -#include "udpsrc.h" -#include "udpsrcsettings.h" +#include "udpsink.h" +#include "udpsinksettings.h" class PluginAPI; class DeviceUISet; -class UDPSrc; +class UDPSink; class SpectrumVis; namespace Ui { - class UDPSrcGUI; + class UDPSinkGUI; } -class UDPSrcGUI : public RollupWidget, public PluginInstanceGUI { +class UDPSinkGUI : public RollupWidget, public PluginInstanceGUI { Q_OBJECT public: - static UDPSrcGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel); + static UDPSinkGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel); virtual void destroy(); void setName(const QString& name); @@ -60,11 +60,11 @@ public slots: void channelMarkerHighlightedByCursor(); private: - Ui::UDPSrcGUI* ui; + Ui::UDPSinkGUI* ui; PluginAPI* m_pluginAPI; DeviceUISet* m_deviceUISet; - UDPSrc* m_udpSrc; - UDPSrcSettings m_settings; + UDPSink* m_udpSink; + UDPSinkSettings m_settings; ChannelMarker m_channelMarker; MovingAverage m_channelPowerAvg; MovingAverage m_inPowerAvg; @@ -78,24 +78,26 @@ private: // RF path SpectrumVis* m_spectrumVis; - explicit UDPSrcGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); - virtual ~UDPSrcGUI(); + explicit UDPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); + virtual ~UDPSinkGUI(); void blockApplySettings(bool block); void applySettings(bool force = false); void applySettingsImmediate(bool force = false); void displaySettings(); - void displayUDPAddress(); void setSampleFormat(int index); - void setSampleFormatIndex(const UDPSrcSettings::SampleFormat& sampleFormat); + void setSampleFormatIndex(const UDPSinkSettings::SampleFormat& sampleFormat); void leaveEvent(QEvent*); void enterEvent(QEvent*); private slots: + void handleSourceMessages(); void on_deltaFrequency_changed(qint64 value); void on_sampleFormat_currentIndexChanged(int index); - void on_sampleSize_currentIndexChanged(int index); + void on_outputUDPAddress_editingFinished(); + void on_outputUDPPort_editingFinished(); + void on_inputUDPAudioPort_editingFinished(); void on_sampleRate_textEdited(const QString& arg1); void on_rfBandwidth_textEdited(const QString& arg1); void on_fmDeviation_textEdited(const QString& arg1); diff --git a/plugins/channelrx/udpsrc/udpsrcgui.ui b/plugins/channelrx/udpsink/udpsinkgui.ui similarity index 84% rename from plugins/channelrx/udpsrc/udpsrcgui.ui rename to plugins/channelrx/udpsink/udpsinkgui.ui index 8b50c2c30..8071b8c59 100644 --- a/plugins/channelrx/udpsrc/udpsrcgui.ui +++ b/plugins/channelrx/udpsink/udpsinkgui.ui @@ -1,12 +1,12 @@ - UDPSrcGUI - + UDPSinkGUI + 0 0 - 354 + 383 355 @@ -24,28 +24,28 @@ - Sans Serif + Liberation Sans 9 - UDP Sample Source + UDP Sample Sink - UDP Sample Source + UDP Sample Sink 2 2 - 350 + 380 142 - 350 + 380 0 @@ -148,7 +148,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -331,7 +331,7 @@ - + 30 @@ -339,23 +339,77 @@ - Addr + Ad - + - 170 + 120 0 - - UDP <address>:<data port>/<audio port> + + Qt::ClickFocus + + + Destination UDP address + + + 000.000.000.000 - 00.000.000.000:0000/0000 + 127.0.0.1 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + P + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + Qt::ClickFocus + + + Destination UDP port + + + 00000 + + + 9998 @@ -378,6 +432,18 @@ + + + 120 + 0 + + + + + 120 + 16777215 + + Samples format @@ -386,7 +452,12 @@ - I/Q + I/Q 16bits + + + + + I/Q 24bits @@ -437,20 +508,51 @@ - - - Samples size + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Au + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + Qt::ClickFocus + + + Audio input UDP port + + + 00000 + + + 9997 - - - 16 bits - - - - - 24 bits - - @@ -749,8 +851,8 @@ - Sans Serif - 9 + Liberation Mono + 8 diff --git a/plugins/channeltx/udpsink/udpsinkplugin.cpp b/plugins/channelrx/udpsink/udpsinkplugin.cpp similarity index 72% rename from plugins/channeltx/udpsink/udpsinkplugin.cpp rename to plugins/channelrx/udpsink/udpsinkplugin.cpp index 60449a9b4..b95e8f087 100644 --- a/plugins/channeltx/udpsink/udpsinkplugin.cpp +++ b/plugins/channelrx/udpsink/udpsinkplugin.cpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2017 F4EXB // +// Copyright (C) 2015 F4EXB // // written by Edouard Griffiths // // // // This program is free software; you can redistribute it and/or modify // @@ -15,16 +15,18 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "udpsinkplugin.h" - #include #include "plugin/pluginapi.h" +#ifndef SERVER_MODE #include "udpsinkgui.h" +#endif +#include "udpsink.h" +#include "udpsinkplugin.h" const PluginDescriptor UDPSinkPlugin::m_pluginDescriptor = { QString("UDP Channel Sink"), - QString("3.12.0"), + QString("4.1.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, @@ -47,22 +49,30 @@ void UDPSinkPlugin::initPlugin(PluginAPI* pluginAPI) m_pluginAPI = pluginAPI; // register TCP Channel Source - m_pluginAPI->registerTxChannel(UDPSink::m_channelIdURI, UDPSink::m_channelId, this); + m_pluginAPI->registerRxChannel(UDPSink::m_channelIdURI, UDPSink::m_channelId, this); } -PluginInstanceGUI* UDPSinkPlugin::createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel) +#ifdef SERVER_MODE +PluginInstanceGUI* UDPSinkPlugin::createRxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSink *rxChannel __attribute__((unused))) { - return UDPSinkGUI::create(m_pluginAPI, deviceUISet, txChannel); + return 0; } +#else +PluginInstanceGUI* UDPSinkPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) +{ + return UDPSinkGUI::create(m_pluginAPI, deviceUISet, rxChannel); +} +#endif -BasebandSampleSource* UDPSinkPlugin::createTxChannelBS(DeviceSinkAPI *deviceAPI) +BasebandSampleSink* UDPSinkPlugin::createRxChannelBS(DeviceSourceAPI *deviceAPI) { return new UDPSink(deviceAPI); } -ChannelSourceAPI* UDPSinkPlugin::createTxChannelCS(DeviceSinkAPI *deviceAPI) +ChannelSinkAPI* UDPSinkPlugin::createRxChannelCS(DeviceSourceAPI *deviceAPI) { return new UDPSink(deviceAPI); } - diff --git a/plugins/channelrx/udpsrc/udpsrcplugin.h b/plugins/channelrx/udpsink/udpsinkplugin.h similarity index 95% rename from plugins/channelrx/udpsrc/udpsrcplugin.h rename to plugins/channelrx/udpsink/udpsinkplugin.h index 9c3ca2de6..c8b360392 100644 --- a/plugins/channelrx/udpsrc/udpsrcplugin.h +++ b/plugins/channelrx/udpsink/udpsinkplugin.h @@ -24,13 +24,13 @@ class DeviceUISet; class BasebandSampleSink; -class UDPSrcPlugin : public QObject, PluginInterface { +class UDPSinkPlugin : public QObject, PluginInterface { Q_OBJECT Q_INTERFACES(PluginInterface) Q_PLUGIN_METADATA(IID "sdrangel.demod.udpsrc") public: - explicit UDPSrcPlugin(QObject* parent = 0); + explicit UDPSinkPlugin(QObject* parent = 0); const PluginDescriptor& getPluginDescriptor() const; void initPlugin(PluginAPI* pluginAPI); diff --git a/plugins/channelrx/udpsrc/udpsrcsettings.cpp b/plugins/channelrx/udpsink/udpsinksettings.cpp similarity index 81% rename from plugins/channelrx/udpsrc/udpsrcsettings.cpp rename to plugins/channelrx/udpsink/udpsinksettings.cpp index eb28df536..fdf24a6d8 100644 --- a/plugins/channelrx/udpsrc/udpsrcsettings.cpp +++ b/plugins/channelrx/udpsink/udpsinksettings.cpp @@ -19,20 +19,19 @@ #include "dsp/dspengine.h" #include "util/simpleserializer.h" #include "settings/serializable.h" -#include "udpsrcsettings.h" +#include "udpsinksettings.h" -UDPSrcSettings::UDPSrcSettings() : +UDPSinkSettings::UDPSinkSettings() : m_channelMarker(0), m_spectrumGUI(0) { resetToDefaults(); } -void UDPSrcSettings::resetToDefaults() +void UDPSinkSettings::resetToDefaults() { m_outputSampleRate = 48000; - m_sampleFormat = FormatIQ; - m_sampleSize = Size16bits; + m_sampleFormat = FormatIQ16; m_inputFrequencyOffset = 0; m_rfBandwidth = 12500; m_fmDeviation = 2500; @@ -46,13 +45,13 @@ void UDPSrcSettings::resetToDefaults() m_audioStereo = false; m_volume = 20; m_udpAddress = "127.0.0.1"; - m_udpPort = 9999; - m_audioPort = 9998; + m_udpPort = 9998; + m_audioPort = 9997; m_rgbColor = QColor(225, 25, 99).rgb(); - m_title = "UDP Sample Source"; + m_title = "UDP Sample Sink"; } -QByteArray UDPSrcSettings::serialize() const +QByteArray UDPSinkSettings::serialize() const { SimpleSerializer s(1); s.writeS32(2, m_inputFrequencyOffset); @@ -78,13 +77,15 @@ QByteArray UDPSrcSettings::serialize() const s.writeS32(17, m_squelchGate); s.writeBool(18, m_agc); s.writeString(19, m_title); - s.writeS32(20, (int) m_sampleFormat); + s.writeString(20, m_udpAddress); + s.writeU32(21, m_udpPort); + s.writeU32(22, m_audioPort); return s.final(); } -bool UDPSrcSettings::deserialize(const QByteArray& data) +bool UDPSinkSettings::deserialize(const QByteArray& data) { SimpleDeserializer d(data); @@ -99,6 +100,7 @@ bool UDPSrcSettings::deserialize(const QByteArray& data) QByteArray bytetmp; QString strtmp; int32_t s32tmp; + quint32 u32tmp; if (m_channelMarker) { d.readBlob(6, &bytetmp); @@ -108,12 +110,12 @@ bool UDPSrcSettings::deserialize(const QByteArray& data) d.readS32(2, &s32tmp, 0); m_inputFrequencyOffset = s32tmp; - d.readS32(3, &s32tmp, FormatIQ); + d.readS32(3, &s32tmp, FormatIQ16); if ((s32tmp >= 0) && (s32tmp < (int) FormatNone)) { m_sampleFormat = (SampleFormat) s32tmp; } else { - m_sampleFormat = FormatIQ; + m_sampleFormat = FormatIQ16; } d.readReal(4, &m_outputSampleRate, 48000.0); @@ -135,13 +137,22 @@ bool UDPSrcSettings::deserialize(const QByteArray& data) d.readS32(17, &m_squelchGate, 5); d.readBool(18, &m_agc, false); d.readString(19, &m_title, "UDP Sample Source"); + d.readString(20, &m_udpAddress, "127.0.0.1"); - d.readS32(20, &s32tmp, Size16bits); + d.readU32(21, &u32tmp, 9998); - if ((s32tmp >= 0) && (s32tmp < (int) SizeNone)) { - m_sampleSize = (SampleSize) s32tmp; + if ((u32tmp > 1024) & (u32tmp < 65538)) { + m_udpPort = u32tmp; } else { - m_sampleSize = Size16bits; + m_udpPort = 9998; + } + + d.readU32(22, &u32tmp, 9997); + + if ((u32tmp > 1024) & (u32tmp < 65538)) { + m_audioPort = u32tmp; + } else { + m_audioPort = 9997; } return true; diff --git a/plugins/channelrx/udpsrc/udpsrcsettings.h b/plugins/channelrx/udpsink/udpsinksettings.h similarity index 92% rename from plugins/channelrx/udpsrc/udpsrcsettings.h rename to plugins/channelrx/udpsink/udpsinksettings.h index 45ee20a14..c825ead71 100644 --- a/plugins/channelrx/udpsrc/udpsrcsettings.h +++ b/plugins/channelrx/udpsink/udpsinksettings.h @@ -21,12 +21,13 @@ #include #include -struct Serializable; +class Serializable; -struct UDPSrcSettings +struct UDPSinkSettings { enum SampleFormat { - FormatIQ, + FormatIQ16, + FormatIQ24, FormatNFM, FormatNFMMono, FormatLSB, @@ -39,15 +40,8 @@ struct UDPSrcSettings FormatNone }; - enum SampleSize { - Size16bits, - Size24bits, - SizeNone - }; - float m_outputSampleRate; SampleFormat m_sampleFormat; - SampleSize m_sampleSize; int64_t m_inputFrequencyOffset; float m_rfBandwidth; int m_fmDeviation; @@ -71,7 +65,7 @@ struct UDPSrcSettings Serializable *m_channelMarker; Serializable *m_spectrumGUI; - UDPSrcSettings(); + UDPSinkSettings(); void resetToDefaults(); void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; } void setSpectrumGUI(Serializable *spectrumGUI) { m_spectrumGUI = spectrumGUI; } diff --git a/plugins/channelrx/udpsrc/CMakeLists.txt b/plugins/channelrx/udpsrc/CMakeLists.txt deleted file mode 100644 index c3bd55b6e..000000000 --- a/plugins/channelrx/udpsrc/CMakeLists.txt +++ /dev/null @@ -1,47 +0,0 @@ -project(udpsrc) - -set(udpsrc_SOURCES - udpsrc.cpp - udpsrcgui.cpp - udpsrcplugin.cpp - udpsrcsettings.cpp -) - -set(udpsrc_HEADERS - udpsrc.h - udpsrcgui.h - udpsrcplugin.h - udpsrcsettings.h -) - -set(udpsrc_FORMS - udpsrcgui.ui -) - -include_directories( - . - ${CMAKE_CURRENT_BINARY_DIR} -) - -#include(${QT_USE_FILE}) -add_definitions(${QT_DEFINITIONS}) -add_definitions(-DQT_PLUGIN) -add_definitions(-DQT_SHARED) - -qt5_wrap_ui(udpsrc_FORMS_HEADERS ${udpsrc_FORMS}) - -add_library(demodudpsrc SHARED - ${udpsrc_SOURCES} - ${udpsrc_HEADERS_MOC} - ${udpsrc_FORMS_HEADERS} -) - -target_link_libraries(demodudpsrc - ${QT_LIBRARIES} - sdrbase - sdrgui -) - -qt5_use_modules(demodudpsrc Core Widgets Network) - -install(TARGETS demodudpsrc DESTINATION lib/plugins/channelrx) diff --git a/plugins/channelrx/udpsrc/udpsrc.pro b/plugins/channelrx/udpsrc/udpsrc.pro deleted file mode 100644 index 87118283f..000000000 --- a/plugins/channelrx/udpsrc/udpsrc.pro +++ /dev/null @@ -1,42 +0,0 @@ -#-------------------------------------------------------- -# -# Pro file for Android and Windows builds with Qt Creator -# -#-------------------------------------------------------- - -TEMPLATE = lib -CONFIG += plugin - -QT += core gui widgets multimedia network opengl - -TARGET = udpsrc - -DEFINES += USE_SSE2=1 -QMAKE_CXXFLAGS += -msse2 -DEFINES += USE_SSE4_1=1 -QMAKE_CXXFLAGS += -msse4.1 -QMAKE_CXXFLAGS += -std=c++11 - -INCLUDEPATH += $$PWD -INCLUDEPATH += ../../../sdrbase -INCLUDEPATH += ../../../sdrgui - -CONFIG(Release):build_subdir = release -CONFIG(Debug):build_subdir = debug - -SOURCES += udpsrc.cpp\ - udpsrcgui.cpp\ - udpsrcplugin.cpp\ - udpsrcsettings.cpp - -HEADERS += udpsrc.h\ - udpsrcgui.h\ - udpsrcplugin.h\ - udpsrcsettings.h - -FORMS += udpsrcgui.ui - -LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase -LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui - -RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/plugins/channeltx/CMakeLists.txt b/plugins/channeltx/CMakeLists.txt index 30118e09e..7c4977aef 100644 --- a/plugins/channeltx/CMakeLists.txt +++ b/plugins/channeltx/CMakeLists.txt @@ -1,13 +1,22 @@ project(mod) -find_package(OpenCV) - add_subdirectory(modam) add_subdirectory(modnfm) add_subdirectory(modssb) add_subdirectory(modwfm) -add_subdirectory(udpsink) +add_subdirectory(udpsource) +find_package(CM256cc) +if(CM256CC_FOUND) + add_subdirectory(daemonsource) +endif(CM256CC_FOUND) + +find_package(OpenCV) if (OpenCV_FOUND) -add_subdirectory(modatv) + add_subdirectory(modatv) endif() + +if (BUILD_DEBIAN) + add_subdirectory(daemonsource) +endif (BUILD_DEBIAN) + diff --git a/plugins/channeltx/daemonsource/CMakeLists.txt b/plugins/channeltx/daemonsource/CMakeLists.txt new file mode 100644 index 000000000..f8e0522de --- /dev/null +++ b/plugins/channeltx/daemonsource/CMakeLists.txt @@ -0,0 +1,83 @@ +project(daemonsource) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +if (HAS_SSSE3) + message(STATUS "DaemonSource: use SSSE3 SIMD" ) +elseif (HAS_NEON) + message(STATUS "DaemonSource: use Neon SIMD" ) +else() + message(STATUS "DaemonSource: Unsupported architecture") + return() +endif() + +set(daemonsource_SOURCES + daemonsource.cpp + daemonsourcethread.cpp + daemonsourcegui.cpp + daemonsourceplugin.cpp + daemonsourcesettings.cpp +) + +set(daemonsource_HEADERS + daemonsource.h + daemonsourcethread.h + daemonsourcegui.h + daemonsourceplugin.h + daemonsourcesettings.h +) + +set(daemonsource_FORMS + daemonsourcegui.ui +) + +#include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +qt5_wrap_ui(daemonsource_FORMS_HEADERS ${daemonsource_FORMS}) + +add_library(daemonsource SHARED + ${daemonsource_SOURCES} + ${daemonsource_HEADERS_MOC} + ${daemonsource_FORMS_HEADERS} +) + +if (BUILD_DEBIAN) +target_include_directories(daemonsource PUBLIC + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${LIBCM256CCSRC} +) +else (BUILD_DEBIAN) +target_include_directories(daemonsource PUBLIC + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CM256CC_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +if (BUILD_DEBIAN) +target_link_libraries(daemonsource + ${QT_LIBRARIES} + cm256cc + sdrbase + sdrgui + swagger +) +else (BUILD_DEBIAN) +target_link_libraries(daemonsource + ${QT_LIBRARIES} + ${CM256CC_LIBRARIES} + sdrbase + sdrgui + swagger +) +endif (BUILD_DEBIAN) + +target_link_libraries(daemonsource Qt5::Core Qt5::Widgets Qt5::Network) + +install(TARGETS daemonsource DESTINATION lib/plugins/channeltx) diff --git a/plugins/channeltx/daemonsource/daemonsource.cpp b/plugins/channeltx/daemonsource/daemonsource.cpp new file mode 100644 index 000000000..d23e1e100 --- /dev/null +++ b/plugins/channeltx/daemonsource/daemonsource.cpp @@ -0,0 +1,453 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#include + +#include "SWGChannelSettings.h" +#include "SWGChannelReport.h" +#include "SWGDaemonSourceReport.h" + +#include "dsp/devicesamplesink.h" +#include "device/devicesinkapi.h" +#include "dsp/upchannelizer.h" +#include "dsp/threadedbasebandsamplesource.h" + +#include "daemonsourcethread.h" +#include "daemonsource.h" + +MESSAGE_CLASS_DEFINITION(DaemonSource::MsgSampleRateNotification, Message) +MESSAGE_CLASS_DEFINITION(DaemonSource::MsgConfigureDaemonSource, Message) +MESSAGE_CLASS_DEFINITION(DaemonSource::MsgQueryStreamData, Message) +MESSAGE_CLASS_DEFINITION(DaemonSource::MsgReportStreamData, Message) + +const QString DaemonSource::m_channelIdURI = "sdrangel.channeltx.daemonsource"; +const QString DaemonSource::m_channelId ="DaemonSource"; + +DaemonSource::DaemonSource(DeviceSinkAPI *deviceAPI) : + ChannelSourceAPI(m_channelIdURI), + m_deviceAPI(deviceAPI), + m_sourceThread(0), + m_running(false), + m_nbCorrectableErrors(0), + m_nbUncorrectableErrors(0) +{ + setObjectName(m_channelId); + + connect(&m_dataQueue, SIGNAL(dataBlockEnqueued()), this, SLOT(handleData()), Qt::QueuedConnection); + m_cm256p = m_cm256.isInitialized() ? &m_cm256 : 0; + m_currentMeta.init(); + + m_channelizer = new UpChannelizer(this); + m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this); + m_deviceAPI->addThreadedSource(m_threadedChannelizer); + m_deviceAPI->addChannelAPI(this); +} + +DaemonSource::~DaemonSource() +{ + m_deviceAPI->removeChannelAPI(this); + m_deviceAPI->removeThreadedSource(m_threadedChannelizer); + delete m_threadedChannelizer; + delete m_channelizer; +} + +void DaemonSource::pull(Sample& sample) +{ + m_dataReadQueue.readSample(sample, true); // true is scale for Tx +} + +void DaemonSource::pullAudio(int nbSamples __attribute__((unused))) +{ +} + +void DaemonSource::start() +{ + qDebug("DaemonSource::start"); + + if (m_running) { + stop(); + } + + m_sourceThread = new DaemonSourceThread(&m_dataQueue); + m_sourceThread->startStop(true); + m_sourceThread->dataBind(m_settings.m_dataAddress, m_settings.m_dataPort); + m_running = true; +} + +void DaemonSource::stop() +{ + qDebug("DaemonSource::stop"); + + if (m_sourceThread != 0) + { + m_sourceThread->startStop(false); + m_sourceThread->deleteLater(); + m_sourceThread = 0; + } + + m_running = false; +} + +void DaemonSource::setDataLink(const QString& dataAddress, uint16_t dataPort) +{ + DaemonSourceSettings settings = m_settings; + settings.m_dataAddress = dataAddress; + settings.m_dataPort = dataPort; + + MsgConfigureDaemonSource *msg = MsgConfigureDaemonSource::create(settings, false); + m_inputMessageQueue.push(msg); +} + +bool DaemonSource::handleMessage(const Message& cmd) +{ + if (UpChannelizer::MsgChannelizerNotification::match(cmd)) + { + UpChannelizer::MsgChannelizerNotification& notif = (UpChannelizer::MsgChannelizerNotification&) cmd; + qDebug() << "DaemonSource::handleMessage: MsgChannelizerNotification:" + << " basebandSampleRate: " << notif.getBasebandSampleRate() + << " outputSampleRate: " << notif.getSampleRate() + << " inputFrequencyOffset: " << notif.getFrequencyOffset(); + + if (m_guiMessageQueue) + { + MsgSampleRateNotification *msg = MsgSampleRateNotification::create(notif.getBasebandSampleRate()); + m_guiMessageQueue->push(msg); + } + + return true; + } + else if (MsgConfigureDaemonSource::match(cmd)) + { + MsgConfigureDaemonSource& cfg = (MsgConfigureDaemonSource&) cmd; + qDebug() << "MsgConfigureDaemonSource::handleMessage: MsgConfigureDaemonSource"; + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } + else if (MsgQueryStreamData::match(cmd)) + { + if (m_guiMessageQueue) + { + struct timeval tv; + gettimeofday(&tv, 0); + + MsgReportStreamData *msg = MsgReportStreamData::create( + tv.tv_sec, + tv.tv_usec, + m_dataReadQueue.size(), + m_dataReadQueue.length(), + m_dataReadQueue.readSampleCount(), + m_nbCorrectableErrors, + m_nbUncorrectableErrors, + m_currentMeta.m_nbOriginalBlocks, + m_currentMeta.m_nbFECBlocks, + m_currentMeta.m_centerFrequency, + m_currentMeta.m_sampleRate); + m_guiMessageQueue->push(msg); + } + + return true; + } + return false; +} + +QByteArray DaemonSource::serialize() const +{ + return m_settings.serialize(); +} + +bool DaemonSource::deserialize(const QByteArray& data __attribute__((unused))) +{ + if (m_settings.deserialize(data)) + { + MsgConfigureDaemonSource *msg = MsgConfigureDaemonSource::create(m_settings, true); + m_inputMessageQueue.push(msg); + return true; + } + else + { + m_settings.resetToDefaults(); + MsgConfigureDaemonSource *msg = MsgConfigureDaemonSource::create(m_settings, true); + m_inputMessageQueue.push(msg); + return false; + } +} + +void DaemonSource::applySettings(const DaemonSourceSettings& settings, bool force) +{ + qDebug() << "DaemonSource::applySettings:" + << " m_dataAddress: " << settings.m_dataAddress + << " m_dataPort: " << settings.m_dataPort + << " force: " << force; + + bool change = false; + + if ((m_settings.m_dataAddress != settings.m_dataAddress) || force) { + change = true; + } + + if ((m_settings.m_dataPort != settings.m_dataPort) || force) { + change = true; + } + + if (change && m_sourceThread) { + m_sourceThread->dataBind(settings.m_dataAddress, settings.m_dataPort); + } + + m_settings = settings; +} + +void DaemonSource::handleDataBlock(SDRDaemonDataBlock* dataBlock __attribute__((unused))) +{ + if (dataBlock->m_rxControlBlock.m_blockCount < SDRDaemonNbOrginalBlocks) + { + qWarning("DaemonSource::handleDataBlock: incomplete data block: not processing"); + } + else + { + int blockCount = 0; + + for (int blockIndex = 0; blockIndex < 256; blockIndex++) + { + if ((blockIndex == 0) && (dataBlock->m_rxControlBlock.m_metaRetrieved)) + { + m_cm256DescriptorBlocks[blockCount].Index = 0; + m_cm256DescriptorBlocks[blockCount].Block = (void *) &(dataBlock->m_superBlocks[0].m_protectedBlock); + blockCount++; + } + else if (dataBlock->m_superBlocks[blockIndex].m_header.m_blockIndex != 0) + { + m_cm256DescriptorBlocks[blockCount].Index = dataBlock->m_superBlocks[blockIndex].m_header.m_blockIndex; + m_cm256DescriptorBlocks[blockCount].Block = (void *) &(dataBlock->m_superBlocks[blockIndex].m_protectedBlock); + blockCount++; + } + } + + //qDebug("DaemonSource::handleDataBlock: frame: %u blocks: %d", dataBlock.m_rxControlBlock.m_frameIndex, blockCount); + + // Need to use the CM256 recovery + if (m_cm256p &&(dataBlock->m_rxControlBlock.m_originalCount < SDRDaemonNbOrginalBlocks)) + { + qDebug("DaemonSource::handleDataBlock: %d recovery blocks", dataBlock->m_rxControlBlock.m_recoveryCount); + CM256::cm256_encoder_params paramsCM256; + paramsCM256.BlockBytes = sizeof(SDRDaemonProtectedBlock); // never changes + paramsCM256.OriginalCount = SDRDaemonNbOrginalBlocks; // never changes + + if (m_currentMeta.m_tv_sec == 0) { + paramsCM256.RecoveryCount = dataBlock->m_rxControlBlock.m_recoveryCount; + } else { + paramsCM256.RecoveryCount = m_currentMeta.m_nbFECBlocks; + } + + // update counters + if (dataBlock->m_rxControlBlock.m_originalCount < SDRDaemonNbOrginalBlocks - paramsCM256.RecoveryCount) { + m_nbUncorrectableErrors += SDRDaemonNbOrginalBlocks - paramsCM256.RecoveryCount - dataBlock->m_rxControlBlock.m_originalCount; + } else { + m_nbCorrectableErrors += dataBlock->m_rxControlBlock.m_recoveryCount; + } + + if (m_cm256.cm256_decode(paramsCM256, m_cm256DescriptorBlocks)) // CM256 decode + { + qWarning() << "DaemonSource::handleDataBlock: decode CM256 error:" + << " m_originalCount: " << dataBlock->m_rxControlBlock.m_originalCount + << " m_recoveryCount: " << dataBlock->m_rxControlBlock.m_recoveryCount; + } + else + { + for (int ir = 0; ir < dataBlock->m_rxControlBlock.m_recoveryCount; ir++) // restore missing blocks + { + int recoveryIndex = SDRDaemonNbOrginalBlocks - dataBlock->m_rxControlBlock.m_recoveryCount + ir; + int blockIndex = m_cm256DescriptorBlocks[recoveryIndex].Index; + SDRDaemonProtectedBlock *recoveredBlock = + (SDRDaemonProtectedBlock *) m_cm256DescriptorBlocks[recoveryIndex].Block; + memcpy((void *) &(dataBlock->m_superBlocks[blockIndex].m_protectedBlock), recoveredBlock, sizeof(SDRDaemonProtectedBlock)); + if ((blockIndex == 0) && !dataBlock->m_rxControlBlock.m_metaRetrieved) { + dataBlock->m_rxControlBlock.m_metaRetrieved = true; + } + } + } + } + + // Validate block zero and retrieve its data + if (dataBlock->m_rxControlBlock.m_metaRetrieved) + { + SDRDaemonMetaDataFEC *metaData = (SDRDaemonMetaDataFEC *) &(dataBlock->m_superBlocks[0].m_protectedBlock); + boost::crc_32_type crc32; + crc32.process_bytes(metaData, 20); + + if (crc32.checksum() == metaData->m_crc32) + { + if (!(m_currentMeta == *metaData)) + { + printMeta("DaemonSource::handleDataBlock", metaData); + + if (m_currentMeta.m_sampleRate != metaData->m_sampleRate) + { + m_channelizer->configure(m_channelizer->getInputMessageQueue(), metaData->m_sampleRate, 0); + m_dataReadQueue.setSize(calculateDataReadQueueSize(metaData->m_sampleRate)); + } + } + + m_currentMeta = *metaData; + } + else + { + qWarning() << "DaemonSource::handleDataBlock: recovered meta: invalid CRC32"; + } + } + + m_dataReadQueue.push(dataBlock); // Push into R/W buffer + } +} + +void DaemonSource::handleData() +{ + SDRDaemonDataBlock* dataBlock; + + while (m_running && ((dataBlock = m_dataQueue.pop()) != 0)) { + handleDataBlock(dataBlock); + } +} + +void DaemonSource::printMeta(const QString& header, SDRDaemonMetaDataFEC *metaData) +{ + qDebug().noquote() << header << ": " + << "|" << metaData->m_centerFrequency + << ":" << metaData->m_sampleRate + << ":" << (int) (metaData->m_sampleBytes & 0xF) + << ":" << (int) metaData->m_sampleBits + << ":" << (int) metaData->m_nbOriginalBlocks + << ":" << (int) metaData->m_nbFECBlocks + << "|" << metaData->m_tv_sec + << ":" << metaData->m_tv_usec + << "|"; +} + +uint32_t DaemonSource::calculateDataReadQueueSize(int sampleRate) +{ + // scale for 20 blocks at 48 kS/s. Take next even number. + uint32_t maxSize = sampleRate / 2400; + maxSize = (maxSize % 2 == 0) ? maxSize : maxSize + 1; + qDebug("DaemonSource::calculateDataReadQueueSize: set max queue size to %u blocks", maxSize); + return maxSize; +} + +int DaemonSource::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setDaemonSourceSettings(new SWGSDRangel::SWGDaemonSourceSettings()); + response.getDaemonSourceSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int DaemonSource::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + DaemonSourceSettings settings = m_settings; + + if (channelSettingsKeys.contains("dataAddress")) { + settings.m_dataAddress = *response.getDaemonSourceSettings()->getDataAddress(); + } + if (channelSettingsKeys.contains("dataPort")) + { + int dataPort = response.getDaemonSourceSettings()->getDataPort(); + + if ((dataPort < 1024) || (dataPort > 65535)) { + settings.m_dataPort = 9090; + } else { + settings.m_dataPort = dataPort; + } + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getDaemonSourceSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getDaemonSourceSettings()->getTitle(); + } + + MsgConfigureDaemonSource *msg = MsgConfigureDaemonSource::create(settings, force); + m_inputMessageQueue.push(msg); + + qDebug("DaemonSource::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureDaemonSource *msgToGUI = MsgConfigureDaemonSource::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int DaemonSource::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setDaemonSourceReport(new SWGSDRangel::SWGDaemonSourceReport()); + response.getDaemonSourceReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + +void DaemonSource::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const DaemonSourceSettings& settings) +{ + if (response.getDaemonSourceSettings()->getDataAddress()) { + *response.getDaemonSourceSettings()->getDataAddress() = settings.m_dataAddress; + } else { + response.getDaemonSourceSettings()->setDataAddress(new QString(settings.m_dataAddress)); + } + + response.getDaemonSourceSettings()->setDataPort(settings.m_dataPort); + response.getDaemonSourceSettings()->setRgbColor(settings.m_rgbColor); + + if (response.getDaemonSourceSettings()->getTitle()) { + *response.getDaemonSourceSettings()->getTitle() = settings.m_title; + } else { + response.getDaemonSourceSettings()->setTitle(new QString(settings.m_title)); + } +} + +void DaemonSource::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + struct timeval tv; + gettimeofday(&tv, 0); + + response.getDaemonSourceReport()->setTvSec(tv.tv_sec); + response.getDaemonSourceReport()->setTvUSec(tv.tv_usec); + response.getDaemonSourceReport()->setQueueSize(m_dataReadQueue.size()); + response.getDaemonSourceReport()->setQueueLength(m_dataReadQueue.length()); + response.getDaemonSourceReport()->setSamplesCount(m_dataReadQueue.readSampleCount()); + response.getDaemonSourceReport()->setCorrectableErrorsCount(m_nbCorrectableErrors); + response.getDaemonSourceReport()->setUncorrectableErrorsCount(m_nbUncorrectableErrors); + response.getDaemonSourceReport()->setNbOriginalBlocks(m_currentMeta.m_nbOriginalBlocks); + response.getDaemonSourceReport()->setNbFecBlocks(m_currentMeta.m_nbFECBlocks); + response.getDaemonSourceReport()->setCenterFreq(m_currentMeta.m_centerFrequency); + response.getDaemonSourceReport()->setSampleRate(m_currentMeta.m_sampleRate); + response.getDaemonSourceReport()->setDeviceCenterFreq(m_deviceAPI->getSampleSink()->getCenterFrequency()/1000); + response.getDaemonSourceReport()->setDeviceSampleRate(m_deviceAPI->getSampleSink()->getSampleRate()); +} + diff --git a/plugins/channeltx/daemonsource/daemonsource.h b/plugins/channeltx/daemonsource/daemonsource.h new file mode 100644 index 000000000..25237fa25 --- /dev/null +++ b/plugins/channeltx/daemonsource/daemonsource.h @@ -0,0 +1,247 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRC_H_ +#define PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRC_H_ + +#include + +#include "cm256.h" + +#include "dsp/basebandsamplesource.h" +#include "channel/channelsourceapi.h" +#include "util/message.h" + +#include "daemonsourcesettings.h" +#include "channel/sdrdaemondataqueue.h" +#include "channel/sdrdaemondatablock.h" +#include "channel/sdrdaemondatareadqueue.h" + +class ThreadedBasebandSampleSource; +class UpChannelizer; +class DeviceSinkAPI; +class DaemonSourceThread; +class SDRDaemonDataBlock; + +class DaemonSource : public BasebandSampleSource, public ChannelSourceAPI { + Q_OBJECT + +public: + class MsgConfigureDaemonSource : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const DaemonSourceSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureDaemonSource* create(const DaemonSourceSettings& settings, bool force) + { + return new MsgConfigureDaemonSource(settings, force); + } + + private: + DaemonSourceSettings m_settings; + bool m_force; + + MsgConfigureDaemonSource(const DaemonSourceSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgSampleRateNotification : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgSampleRateNotification* create(int sampleRate) { + return new MsgSampleRateNotification(sampleRate); + } + + int getSampleRate() const { return m_sampleRate; } + + private: + + MsgSampleRateNotification(int sampleRate) : + Message(), + m_sampleRate(sampleRate) + { } + + int m_sampleRate; + }; + + class MsgQueryStreamData : public Message { + MESSAGE_CLASS_DECLARATION + public: + static MsgQueryStreamData* create() { + return new MsgQueryStreamData(); + } + private: + MsgQueryStreamData() : Message() {} + }; + + class MsgReportStreamData : public Message { + MESSAGE_CLASS_DECLARATION + + public: + uint32_t get_tv_sec() const { return m_tv_sec; } + uint32_t get_tv_usec() const { return m_tv_usec; } + uint32_t get_queueSize() const { return m_queueSize; } + uint32_t get_queueLength() const { return m_queueLength; } + uint32_t get_readSamplesCount() const { return m_readSamplesCount; } + uint32_t get_nbCorrectableErrors() const { return m_nbCorrectableErrors; } + uint32_t get_nbUncorrectableErrors() const { return m_nbUncorrectableErrors; } + uint32_t get_nbOriginalBlocks() const { return m_nbOriginalBlocks; } + uint32_t get_nbFECBlocks() const { return m_nbFECBlocks; } + uint32_t get_centerFreq() const { return m_centerFreq; } + uint32_t get_sampleRate() const { return m_sampleRate; } + + static MsgReportStreamData* create( + uint32_t tv_sec, + uint32_t tv_usec, + uint32_t queueSize, + uint32_t queueLength, + uint32_t readSamplesCount, + uint32_t nbCorrectableErrors, + uint32_t nbUncorrectableErrors, + uint32_t nbOriginalBlocks, + uint32_t nbFECBlocks, + uint32_t centerFreq, + uint32_t sampleRate) + { + return new MsgReportStreamData( + tv_sec, + tv_usec, + queueSize, + queueLength, + readSamplesCount, + nbCorrectableErrors, + nbUncorrectableErrors, + nbOriginalBlocks, + nbFECBlocks, + centerFreq, + sampleRate); + } + + protected: + uint32_t m_tv_sec; + uint32_t m_tv_usec; + uint32_t m_queueSize; + uint32_t m_queueLength; + uint32_t m_readSamplesCount; + uint32_t m_nbCorrectableErrors; + uint32_t m_nbUncorrectableErrors; + uint32_t m_nbOriginalBlocks; + uint32_t m_nbFECBlocks; + uint32_t m_centerFreq; + uint32_t m_sampleRate; + + MsgReportStreamData( + uint32_t tv_sec, + uint32_t tv_usec, + uint32_t queueSize, + uint32_t queueLength, + uint32_t readSamplesCount, + uint32_t nbCorrectableErrors, + uint32_t nbUncorrectableErrors, + uint32_t nbOriginalBlocks, + uint32_t nbFECBlocks, + uint32_t centerFreq, + uint32_t sampleRate) : + Message(), + m_tv_sec(tv_sec), + m_tv_usec(tv_usec), + m_queueSize(queueSize), + m_queueLength(queueLength), + m_readSamplesCount(readSamplesCount), + m_nbCorrectableErrors(nbCorrectableErrors), + m_nbUncorrectableErrors(nbUncorrectableErrors), + m_nbOriginalBlocks(nbOriginalBlocks), + m_nbFECBlocks(nbFECBlocks), + m_centerFreq(centerFreq), + m_sampleRate(sampleRate) + { } + }; + + DaemonSource(DeviceSinkAPI *deviceAPI); + ~DaemonSource(); + + virtual void destroy() { delete this; } + + virtual void pull(Sample& sample); + virtual void pullAudio(int nbSamples); + virtual void start(); + virtual void stop(); + virtual bool handleMessage(const Message& cmd); + + virtual void getIdentifier(QString& id) { id = objectName(); } + virtual void getTitle(QString& title) { title = m_settings.m_title; } + virtual qint64 getCenterFrequency() const { return 0; } + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + + void setDataLink(const QString& dataAddress, uint16_t dataPort); + + static const QString m_channelIdURI; + static const QString m_channelId; + +private: + DeviceSinkAPI* m_deviceAPI; + ThreadedBasebandSampleSource* m_threadedChannelizer; + UpChannelizer* m_channelizer; + SDRDaemonDataQueue m_dataQueue; + DaemonSourceThread *m_sourceThread; + CM256 m_cm256; + CM256 *m_cm256p; + bool m_running; + + DaemonSourceSettings m_settings; + + CM256::cm256_block m_cm256DescriptorBlocks[2*SDRDaemonNbOrginalBlocks]; //!< CM256 decoder descriptors (block addresses and block indexes) + SDRDaemonMetaDataFEC m_currentMeta; + + SDRDaemonDataReadQueue m_dataReadQueue; + + uint32_t m_nbCorrectableErrors; //!< count of correctable errors in number of blocks + uint32_t m_nbUncorrectableErrors; //!< count of uncorrectable errors in number of blocks + + void applySettings(const DaemonSourceSettings& settings, bool force = false); + void handleDataBlock(SDRDaemonDataBlock *dataBlock); + void printMeta(const QString& header, SDRDaemonMetaDataFEC *metaData); + uint32_t calculateDataReadQueueSize(int sampleRate); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const DaemonSourceSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); + +private slots: + void handleData(); +}; + +#endif // PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRC_H_ diff --git a/plugins/channeltx/daemonsource/daemonsourcegui.cpp b/plugins/channeltx/daemonsource/daemonsourcegui.cpp new file mode 100644 index 000000000..10ba93ba5 --- /dev/null +++ b/plugins/channeltx/daemonsource/daemonsourcegui.cpp @@ -0,0 +1,380 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "device/devicesinkapi.h" +#include "device/deviceuiset.h" +#include "gui/basicchannelsettingsdialog.h" +#include "mainwindow.h" + +#include "daemonsource.h" +#include "ui_daemonsourcegui.h" +#include "daemonsourcegui.h" + +DaemonSourceGUI* DaemonSourceGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx) +{ + DaemonSourceGUI* gui = new DaemonSourceGUI(pluginAPI, deviceUISet, channelTx); + return gui; +} + +void DaemonSourceGUI::destroy() +{ + delete this; +} + +void DaemonSourceGUI::setName(const QString& name) +{ + setObjectName(name); +} + +QString DaemonSourceGUI::getName() const +{ + return objectName(); +} + +qint64 DaemonSourceGUI::getCenterFrequency() const { + return 0; +} + +void DaemonSourceGUI::setCenterFrequency(qint64 centerFrequency __attribute__((unused))) +{ +} + +void DaemonSourceGUI::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + applySettings(true); +} + +QByteArray DaemonSourceGUI::serialize() const +{ + return m_settings.serialize(); +} + +bool DaemonSourceGUI::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) { + displaySettings(); + applySettings(true); + return true; + } else { + resetToDefaults(); + return false; + } +} + +bool DaemonSourceGUI::handleMessage(const Message& message) +{ + if (DaemonSource::MsgSampleRateNotification::match(message)) + { + DaemonSource::MsgSampleRateNotification& notif = (DaemonSource::MsgSampleRateNotification&) message; + m_channelMarker.setBandwidth(notif.getSampleRate()); + return true; + } + else if (DaemonSource::MsgConfigureDaemonSource::match(message)) + { + const DaemonSource::MsgConfigureDaemonSource& cfg = (DaemonSource::MsgConfigureDaemonSource&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else if (DaemonSource::MsgReportStreamData::match(message)) + { + const DaemonSource::MsgReportStreamData& report = (DaemonSource::MsgReportStreamData&) message; + ui->sampleRate->setText(QString("%1").arg(report.get_sampleRate())); + QString nominalNbBlocksText = QString("%1/%2") + .arg(report.get_nbOriginalBlocks() + report.get_nbFECBlocks()) + .arg(report.get_nbFECBlocks()); + ui->nominalNbBlocksText->setText(nominalNbBlocksText); + QString queueLengthText = QString("%1/%2").arg(report.get_queueLength()).arg(report.get_queueSize()); + ui->queueLengthText->setText(queueLengthText); + int queueLengthPercent = (report.get_queueLength()*100)/report.get_queueSize(); + ui->queueLengthGauge->setValue(queueLengthPercent); + int unrecoverableCount = report.get_nbUncorrectableErrors(); + int recoverableCount = report.get_nbCorrectableErrors(); + uint64_t timestampUs = report.get_tv_sec()*1000000ULL + report.get_tv_usec(); + + if (!m_resetCounts) + { + int recoverableCountDelta = recoverableCount - m_lastCountRecovered; + int unrecoverableCountDelta = unrecoverableCount - m_lastCountUnrecoverable; + displayEventStatus(recoverableCountDelta, unrecoverableCountDelta); + m_countRecovered += recoverableCountDelta; + m_countUnrecoverable += unrecoverableCountDelta; + displayEventCounts(); + } + + uint32_t sampleCountDelta, sampleCount; + sampleCount = report.get_readSamplesCount(); + + if (sampleCount < m_lastSampleCount) { + sampleCountDelta = (0xFFFFFFFFU - sampleCount) + m_lastSampleCount + 1; + } else { + sampleCountDelta = sampleCount - m_lastSampleCount; + } + + if (sampleCountDelta == 0) { + ui->allFramesDecoded->setStyleSheet("QToolButton { background-color : blue; }"); + } + + double remoteStreamRate = sampleCountDelta*1e6 / (double) (timestampUs - m_lastTimestampUs); + + if (remoteStreamRate != 0) { + ui->streamRateText->setText(QString("%1").arg(remoteStreamRate, 0, 'f', 0)); + } + + m_resetCounts = false; + m_lastCountRecovered = recoverableCount; + m_lastCountUnrecoverable = unrecoverableCount; + m_lastSampleCount = sampleCount; + m_lastTimestampUs = timestampUs; + + return true; + } + else + { + return false; + } +} + +DaemonSourceGUI::DaemonSourceGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx __attribute__((unused)), QWidget* parent) : + RollupWidget(parent), + ui(new Ui::DaemonSourceGUI), + m_pluginAPI(pluginAPI), + m_deviceUISet(deviceUISet), + m_countUnrecoverable(0), + m_countRecovered(0), + m_lastCountUnrecoverable(0), + m_lastCountRecovered(0), + m_lastSampleCount(0), + m_lastTimestampUs(0), + m_resetCounts(true), + m_tickCount(0) +{ + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose, true); + connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); + + m_daemonSrc = (DaemonSource*) channelTx; + m_daemonSrc->setMessageQueueToGUI(getInputMessageQueue()); + + connect(&(m_deviceUISet->m_deviceSinkAPI->getMasterTimer()), SIGNAL(timeout()), this, SLOT(tick())); + + m_channelMarker.blockSignals(true); + m_channelMarker.setColor(m_settings.m_rgbColor); + m_channelMarker.setCenterFrequency(0); + m_channelMarker.setTitle("Daemon source"); + m_channelMarker.blockSignals(false); + m_channelMarker.setVisible(true); // activate signal on the last setting only + + m_settings.setChannelMarker(&m_channelMarker); + + m_deviceUISet->registerTxChannelInstance(DaemonSource::m_channelIdURI, this); + m_deviceUISet->addChannelMarker(&m_channelMarker); + m_deviceUISet->addRollupWidget(this); + + connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor())); + connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages())); + + m_time.start(); + + displaySettings(); + applySettings(true); +} + +DaemonSourceGUI::~DaemonSourceGUI() +{ + m_deviceUISet->removeTxChannelInstance(this); + delete m_daemonSrc; + delete ui; +} + +void DaemonSourceGUI::blockApplySettings(bool block) +{ + m_doApplySettings = !block; +} + +void DaemonSourceGUI::applySettings(bool force) +{ + if (m_doApplySettings) + { + setTitleColor(m_channelMarker.getColor()); + + DaemonSource::MsgConfigureDaemonSource* message = DaemonSource::MsgConfigureDaemonSource::create(m_settings, force); + m_daemonSrc->getInputMessageQueue()->push(message); + } +} + +void DaemonSourceGUI::displaySettings() +{ + m_channelMarker.blockSignals(true); + m_channelMarker.setCenterFrequency(0); + m_channelMarker.setTitle(m_settings.m_title); + m_channelMarker.setBandwidth(5000); // TODO + m_channelMarker.blockSignals(false); + m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only + + setTitleColor(m_settings.m_rgbColor); + setWindowTitle(m_channelMarker.getTitle()); + + blockApplySettings(true); + ui->dataAddress->setText(m_settings.m_dataAddress); + ui->dataPort->setText(tr("%1").arg(m_settings.m_dataPort)); + blockApplySettings(false); +} + +void DaemonSourceGUI::leaveEvent(QEvent*) +{ + m_channelMarker.setHighlighted(false); +} + +void DaemonSourceGUI::enterEvent(QEvent*) +{ + m_channelMarker.setHighlighted(true); +} + +void DaemonSourceGUI::handleSourceMessages() +{ + Message* message; + + while ((message = getInputMessageQueue()->pop()) != 0) + { + if (handleMessage(*message)) + { + delete message; + } + } +} + +void DaemonSourceGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rollDown __attribute__((unused))) +{ +} + +void DaemonSourceGUI::onMenuDialogCalled(const QPoint &p) +{ + BasicChannelSettingsDialog dialog(&m_channelMarker, this); + dialog.move(p); + dialog.exec(); + + m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); + m_settings.m_title = m_channelMarker.getTitle(); + + setWindowTitle(m_settings.m_title); + setTitleColor(m_settings.m_rgbColor); + + applySettings(); +} + +void DaemonSourceGUI::on_dataAddress_returnPressed() +{ + m_settings.m_dataAddress = ui->dataAddress->text(); + applySettings(); +} + +void DaemonSourceGUI::on_dataPort_returnPressed() +{ + bool dataOk; + int dataPort = ui->dataPort->text().toInt(&dataOk); + + if((!dataOk) || (dataPort < 1024) || (dataPort > 65535)) + { + return; + } + else + { + m_settings.m_dataPort = dataPort; + } + + applySettings(); +} + +void DaemonSourceGUI::on_dataApplyButton_clicked(bool checked __attribute__((unused))) +{ + m_settings.m_dataAddress = ui->dataAddress->text(); + + bool dataOk; + int udpDataPort = ui->dataPort->text().toInt(&dataOk); + + if((dataOk) && (udpDataPort >= 1024) && (udpDataPort < 65535)) + { + m_settings.m_dataPort = udpDataPort; + } + + applySettings(); +} + +void DaemonSourceGUI::on_eventCountsReset_clicked(bool checked __attribute__((unused))) +{ + m_countUnrecoverable = 0; + m_countRecovered = 0; + m_time.start(); + displayEventCounts(); + displayEventTimer(); +} + +void DaemonSourceGUI::displayEventCounts() +{ + QString nstr = QString("%1").arg(m_countUnrecoverable, 3, 10, QChar('0')); + ui->eventUnrecText->setText(nstr); + nstr = QString("%1").arg(m_countRecovered, 3, 10, QChar('0')); + ui->eventRecText->setText(nstr); +} + +void DaemonSourceGUI::displayEventStatus(int recoverableCount, int unrecoverableCount) +{ + + if (unrecoverableCount == 0) + { + if (recoverableCount == 0) { + ui->allFramesDecoded->setStyleSheet("QToolButton { background-color : green; }"); + } else { + ui->allFramesDecoded->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + } + } + else + { + ui->allFramesDecoded->setStyleSheet("QToolButton { background-color : red; }"); + } +} + +void DaemonSourceGUI::displayEventTimer() +{ + int elapsedTimeMillis = m_time.elapsed(); + QTime recordLength(0, 0, 0, 0); + recordLength = recordLength.addSecs(elapsedTimeMillis/1000); + QString s_time = recordLength.toString("HH:mm:ss"); + ui->eventCountsTimeText->setText(s_time); +} + +void DaemonSourceGUI::tick() +{ + if (++m_tickCount == 20) // once per second + { + DaemonSource::MsgQueryStreamData *msg = DaemonSource::MsgQueryStreamData::create(); + m_daemonSrc->getInputMessageQueue()->push(msg); + + displayEventTimer(); + + m_tickCount = 0; + } +} + +void DaemonSourceGUI::channelMarkerChangedByCursor() +{ +} diff --git a/plugins/channeltx/daemonsource/daemonsourcegui.h b/plugins/channeltx/daemonsource/daemonsourcegui.h new file mode 100644 index 000000000..f77c10404 --- /dev/null +++ b/plugins/channeltx/daemonsource/daemonsourcegui.h @@ -0,0 +1,106 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRCGUI_H_ +#define PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRCGUI_H_ + +#include + +#include "plugin/plugininstancegui.h" +#include "dsp/channelmarker.h" +#include "gui/rollupwidget.h" +#include "util/messagequeue.h" + +#include "daemonsourcesettings.h" + +class PluginAPI; +class DeviceUISet; +class BasebandSampleSource; +class DaemonSource; + +namespace Ui { + class DaemonSourceGUI; +} + +class DaemonSourceGUI : public RollupWidget, public PluginInstanceGUI { + Q_OBJECT + +public: + static DaemonSourceGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx); + virtual void destroy(); + + void setName(const QString& name); + QString getName() const; + virtual qint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + virtual bool handleMessage(const Message& message); + +public slots: + void channelMarkerChangedByCursor(); + +private: + Ui::DaemonSourceGUI* ui; + PluginAPI* m_pluginAPI; + DeviceUISet* m_deviceUISet; + ChannelMarker m_channelMarker; + DaemonSourceSettings m_settings; + bool m_doApplySettings; + + DaemonSource* m_daemonSrc; + MessageQueue m_inputMessageQueue; + + uint32_t m_countUnrecoverable; + uint32_t m_countRecovered; + uint32_t m_lastCountUnrecoverable; + uint32_t m_lastCountRecovered; + uint32_t m_lastSampleCount; + uint64_t m_lastTimestampUs; + bool m_resetCounts; + QTime m_time; + uint32_t m_tickCount; + + explicit DaemonSourceGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent = 0); + virtual ~DaemonSourceGUI(); + + void blockApplySettings(bool block); + void applySettings(bool force = false); + void displaySettings(); + + void leaveEvent(QEvent*); + void enterEvent(QEvent*); + + void displayEventCounts(); + void displayEventStatus(int recoverableCount, int unrecoverableCount); + void displayEventTimer(); + +private slots: + void handleSourceMessages(); + void on_dataAddress_returnPressed(); + void on_dataPort_returnPressed(); + void on_dataApplyButton_clicked(bool checked); + void onWidgetRolled(QWidget* widget, bool rollDown); + void onMenuDialogCalled(const QPoint& p); + void on_eventCountsReset_clicked(bool checked); + void tick(); +}; + + +#endif /* PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRCGUI_H_ */ diff --git a/plugins/channeltx/daemonsource/daemonsourcegui.ui b/plugins/channeltx/daemonsource/daemonsourcegui.ui new file mode 100644 index 000000000..9baae1bf1 --- /dev/null +++ b/plugins/channeltx/daemonsource/daemonsourcegui.ui @@ -0,0 +1,420 @@ + + + DaemonSourceGUI + + + + 0 + 0 + 320 + 140 + + + + + 0 + 0 + + + + + 320 + 140 + + + + + 320 + 16777215 + + + + + Liberation Sans + 9 + + + + Daemon source + + + + + 10 + 10 + 301 + 121 + + + + Settings + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + + + 30 + 0 + + + + Data + + + + + + + + 120 + 0 + + + + Local data listener address + + + 000.000.000.000 + + + 0... + + + + + + + : + + + + + + + + 50 + 16777215 + + + + Local data listener port + + + 00000 + + + 0 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 30 + 16777215 + + + + Set local data listener address and port + + + Set + + + + + + + + + + + SR + + + + + + + + 60 + 0 + + + + Stream nominal sample rate + + + 0000000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + S/s + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 50 + 0 + + + + Nb total blocks / Nb FEC blocks + + + 000/00 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + false + + + Frames status: green = all original received, none = some recovered by FEC, red = some lost, blue = remote not streaming + + + + + + + :/locked.png:/locked.png + + + + + + + + 50 + 0 + + + + Stream actual sample rate + + + 0000000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 20 + 0 + + + + S/s + + + + + + + + 22 + 16777215 + + + + 0 + + + + + + + + 25 + 0 + + + + Number of unrecoverable errors since event counts reset + + + 000 + + + + + + + + 25 + 0 + + + + Number of correctable errors since event counts reset + + + 000 + + + + + + + Time since last event counts reset + + + 00:00:00 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + QL + + + + + + + + 16777215 + 14 + + + + Queue length gauge + + + 24 + + + + + + + + 50 + 0 + + + + Queued data blocks / Queue size in data blocks + + + 000/000 + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + RollupWidget + QWidget +
gui/rollupwidget.h
+ 1 +
+
+ + + + +
diff --git a/plugins/channeltx/daemonsource/daemonsourceplugin.cpp b/plugins/channeltx/daemonsource/daemonsourceplugin.cpp new file mode 100644 index 000000000..805ad8e89 --- /dev/null +++ b/plugins/channeltx/daemonsource/daemonsourceplugin.cpp @@ -0,0 +1,79 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include "plugin/pluginapi.h" + +#ifndef SERVER_MODE +#include "daemonsourcegui.h" +#endif +#include "daemonsource.h" +#include "daemonsourceplugin.h" + +const PluginDescriptor DaemonSourcePlugin::m_pluginDescriptor = { + QString("Daemon channel source"), + QString("4.1.0"), + QString("(c) Edouard Griffiths, F4EXB"), + QString("https://github.com/f4exb/sdrangel"), + true, + QString("https://github.com/f4exb/sdrangel") +}; + +DaemonSourcePlugin::DaemonSourcePlugin(QObject* parent) : + QObject(parent), + m_pluginAPI(0) +{ +} + +const PluginDescriptor& DaemonSourcePlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void DaemonSourcePlugin::initPlugin(PluginAPI* pluginAPI) +{ + m_pluginAPI = pluginAPI; + + // register source + m_pluginAPI->registerTxChannel(DaemonSource::m_channelIdURI, DaemonSource::m_channelId, this); +} + +#ifdef SERVER_MODE +PluginInstanceGUI* DaemonSourcePlugin::createTxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSource *txChannel __attribute__((unused))) +{ + return 0; +} +#else +PluginInstanceGUI* DaemonSourcePlugin::createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel) +{ + return DaemonSourceGUI::create(m_pluginAPI, deviceUISet, txChannel); +} +#endif + +BasebandSampleSource* DaemonSourcePlugin::createTxChannelBS(DeviceSinkAPI *deviceAPI) +{ + return new DaemonSource(deviceAPI); +} + +ChannelSourceAPI* DaemonSourcePlugin::createTxChannelCS(DeviceSinkAPI *deviceAPI) +{ + return new DaemonSource(deviceAPI); +} + + + diff --git a/plugins/samplesource/airspyhfi/airspyhfiplugin.h b/plugins/channeltx/daemonsource/daemonsourceplugin.h similarity index 57% rename from plugins/samplesource/airspyhfi/airspyhfiplugin.h rename to plugins/channeltx/daemonsource/daemonsourceplugin.h index 33b73bc69..7477f2193 100644 --- a/plugins/samplesource/airspyhfi/airspyhfiplugin.h +++ b/plugins/channeltx/daemonsource/daemonsourceplugin.h @@ -14,40 +14,34 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef INCLUDE_AIRSPYHFIPLUGIN_H -#define INCLUDE_AIRSPYHFIPLUGIN_H +#ifndef PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRCPLUGIN_H_ +#define PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRCPLUGIN_H_ #include #include "plugin/plugininterface.h" -#define AIRSPYHFI_DEVICE_TYPE_ID "sdrangel.samplesource.airspyhfi" +class DeviceUISet; +class BasebandSampleSource; -class PluginAPI; - -class AirspyHFIPlugin : public QObject, public PluginInterface { - Q_OBJECT - Q_INTERFACES(PluginInterface) - Q_PLUGIN_METADATA(IID AIRSPYHFI_DEVICE_TYPE_ID) +class DaemonSourcePlugin : public QObject, PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID "sdrangel.channeltx.daemonsrc") public: - explicit AirspyHFIPlugin(QObject* parent = 0); + explicit DaemonSourcePlugin(QObject* parent = 0); - const PluginDescriptor& getPluginDescriptor() const; - void initPlugin(PluginAPI* pluginAPI); + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); - virtual SamplingDevices enumSampleSources(); - virtual PluginInstanceGUI* createSampleSourcePluginInstanceGUI( - const QString& sourceId, - QWidget **widget, - DeviceUISet *deviceUISet); - virtual DeviceSampleSource* createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI); - - static const QString m_hardwareID; - static const QString m_deviceTypeID; - static const int m_maxDevices; + virtual PluginInstanceGUI* createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel); + virtual BasebandSampleSource* createTxChannelBS(DeviceSinkAPI *deviceAPI); + virtual ChannelSourceAPI* createTxChannelCS(DeviceSinkAPI *deviceAPI); private: - static const PluginDescriptor m_pluginDescriptor; + static const PluginDescriptor m_pluginDescriptor; + + PluginAPI* m_pluginAPI; }; -#endif // INCLUDE_AIRSPYHFIPLUGIN_H +#endif /* PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRCPLUGIN_H_ */ diff --git a/plugins/channeltx/daemonsource/daemonsourcesettings.cpp b/plugins/channeltx/daemonsource/daemonsourcesettings.cpp new file mode 100644 index 000000000..e55462390 --- /dev/null +++ b/plugins/channeltx/daemonsource/daemonsourcesettings.cpp @@ -0,0 +1,81 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "util/simpleserializer.h" +#include "settings/serializable.h" +#include "daemonsourcesettings.h" + +DaemonSourceSettings::DaemonSourceSettings() +{ + resetToDefaults(); +} + +void DaemonSourceSettings::resetToDefaults() +{ + m_dataAddress = "127.0.0.1"; + m_dataPort = 9090; + m_rgbColor = QColor(140, 4, 4).rgb(); + m_title = "Daemon source"; +} + +QByteArray DaemonSourceSettings::serialize() const +{ + SimpleSerializer s(1); + s.writeString(1, m_dataAddress); + s.writeU32(2, m_dataPort); + s.writeU32(3, m_rgbColor); + s.writeString(4, m_title); + + return s.final(); +} + +bool DaemonSourceSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if(!d.isValid()) + { + resetToDefaults(); + return false; + } + + if(d.getVersion() == 1) + { + uint32_t tmp; + QString strtmp; + + d.readString(1, &m_dataAddress, "127.0.0.1"); + d.readU32(2, &tmp, 0); + + if ((tmp > 1023) && (tmp < 65535)) { + m_dataPort = tmp; + } else { + m_dataPort = 9090; + } + + d.readU32(3, &m_rgbColor, QColor(0, 255, 255).rgb()); + d.readString(4, &m_title, "Daemon source"); + + return true; + } + else + { + resetToDefaults(); + return false; + } +} diff --git a/plugins/channeltx/daemonsource/daemonsourcesettings.h b/plugins/channeltx/daemonsource/daemonsourcesettings.h new file mode 100644 index 000000000..df1ab6736 --- /dev/null +++ b/plugins/channeltx/daemonsource/daemonsourcesettings.h @@ -0,0 +1,41 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRCSETTINGS_H_ +#define PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRCSETTINGS_H_ + +#include +#include + +class Serializable; + +struct DaemonSourceSettings +{ + QString m_dataAddress; //!< Listening (local) data address + uint16_t m_dataPort; //!< Listening data port + quint32 m_rgbColor; + QString m_title; + + Serializable *m_channelMarker; + + DaemonSourceSettings(); + void resetToDefaults(); + void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; } + QByteArray serialize() const; + bool deserialize(const QByteArray& data); +}; + +#endif /* PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRCSETTINGS_H_ */ diff --git a/plugins/channeltx/daemonsource/daemonsourcethread.cpp b/plugins/channeltx/daemonsource/daemonsourcethread.cpp new file mode 100644 index 000000000..4feb60ef4 --- /dev/null +++ b/plugins/channeltx/daemonsource/daemonsourcethread.cpp @@ -0,0 +1,188 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include "cm256.h" + +#include "channel/sdrdaemondataqueue.h" +#include "channel/sdrdaemondatablock.h" + +#include "daemonsourcethread.h" + +MESSAGE_CLASS_DEFINITION(DaemonSourceThread::MsgStartStop, Message) +MESSAGE_CLASS_DEFINITION(DaemonSourceThread::MsgDataBind, Message) + +DaemonSourceThread::DaemonSourceThread(SDRDaemonDataQueue *dataQueue, QObject* parent) : + QThread(parent), + m_running(false), + m_dataQueue(dataQueue), + m_address(QHostAddress::LocalHost), + m_socket(0) +{ + std::fill(m_dataBlocks, m_dataBlocks+4, (SDRDaemonDataBlock *) 0); + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); +} + +DaemonSourceThread::~DaemonSourceThread() +{ + qDebug("DaemonSourceThread::~DaemonSourceThread"); +} + +void DaemonSourceThread::startStop(bool start) +{ + MsgStartStop *msg = MsgStartStop::create(start); + m_inputMessageQueue.push(msg); +} + +void DaemonSourceThread::dataBind(const QString& address, uint16_t port) +{ + MsgDataBind *msg = MsgDataBind::create(address, port); + m_inputMessageQueue.push(msg); +} + +void DaemonSourceThread::startWork() +{ + qDebug("DaemonSourceThread::startWork"); + m_startWaitMutex.lock(); + m_socket = new QUdpSocket(this); + start(); + while(!m_running) + m_startWaiter.wait(&m_startWaitMutex, 100); + m_startWaitMutex.unlock(); +} + +void DaemonSourceThread::stopWork() +{ + qDebug("DaemonSourceThread::stopWork"); + delete m_socket; + m_socket = 0; + m_running = false; + wait(); +} + +void DaemonSourceThread::run() +{ + qDebug("DaemonSourceThread::run: begin"); + m_running = true; + m_startWaiter.wakeAll(); + + while (m_running) + { + sleep(1); // Do nothing as everything is in the data handler (dequeuer) + } + + m_running = false; + qDebug("DaemonSourceThread::run: end"); +} + + +void DaemonSourceThread::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + if (MsgStartStop::match(*message)) + { + MsgStartStop* notif = (MsgStartStop*) message; + qDebug("DaemonSourceThread::handleInputMessages: MsgStartStop: %s", notif->getStartStop() ? "start" : "stop"); + + if (notif->getStartStop()) { + startWork(); + } else { + stopWork(); + } + + delete message; + } + else if (MsgDataBind::match(*message)) + { + MsgDataBind* notif = (MsgDataBind*) message; + qDebug("DaemonSourceThread::handleInputMessages: MsgDataBind: %s:%d", qPrintable(notif->getAddress().toString()), notif->getPort()); + + if (m_socket) + { + disconnect(m_socket, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams())); + m_socket->bind(notif->getAddress(), notif->getPort()); + connect(m_socket, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams())); + } + } + } +} + +void DaemonSourceThread::readPendingDatagrams() +{ + SDRDaemonSuperBlock superBlock; + qint64 size; + + while (m_socket->hasPendingDatagrams()) + { + QHostAddress sender; + quint16 senderPort = 0; + //qint64 pendingDataSize = m_socket->pendingDatagramSize(); + size = m_socket->readDatagram((char *) &superBlock, (long long int) sizeof(SDRDaemonSuperBlock), &sender, &senderPort); + + if (size == sizeof(SDRDaemonSuperBlock)) + { + unsigned int dataBlockIndex = superBlock.m_header.m_frameIndex % m_nbDataBlocks; + + // create the first block for this index + if (m_dataBlocks[dataBlockIndex] == 0) { + m_dataBlocks[dataBlockIndex] = new SDRDaemonDataBlock(); + } + + if (m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex < 0) + { + // initialize virgin block with the frame index + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex = superBlock.m_header.m_frameIndex; + } + else + { + // if the frame index is not the same for the same slot it means we are starting a new frame + uint32_t frameIndex = m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex; + + if (superBlock.m_header.m_frameIndex != frameIndex) + { + //qDebug("DaemonSourceThread::readPendingDatagrams: push frame %u", frameIndex); + m_dataQueue->push(m_dataBlocks[dataBlockIndex]); + m_dataBlocks[dataBlockIndex] = new SDRDaemonDataBlock(); + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_frameIndex = superBlock.m_header.m_frameIndex; + } + } + + m_dataBlocks[dataBlockIndex]->m_superBlocks[superBlock.m_header.m_blockIndex] = superBlock; + + if (superBlock.m_header.m_blockIndex == 0) { + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_metaRetrieved = true; + } + + if (superBlock.m_header.m_blockIndex < SDRDaemonNbOrginalBlocks) { + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_originalCount++; + } else { + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_recoveryCount++; + } + + m_dataBlocks[dataBlockIndex]->m_rxControlBlock.m_blockCount++; + } + else + { + qWarning("DaemonSourceThread::readPendingDatagrams: wrong super block size not processing"); + } + } +} + diff --git a/plugins/channeltx/daemonsource/daemonsourcethread.h b/plugins/channeltx/daemonsource/daemonsourcethread.h new file mode 100644 index 000000000..a5371bed7 --- /dev/null +++ b/plugins/channeltx/daemonsource/daemonsourcethread.h @@ -0,0 +1,109 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRCTHREAD_H_ +#define PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRCTHREAD_H_ + +#include +#include +#include +#include + +#include "util/message.h" +#include "util/messagequeue.h" + +class SDRDaemonDataQueue; +class SDRDaemonDataBlock; +class QUdpSocket; + +class DaemonSourceThread : public QThread { + Q_OBJECT +public: + class MsgStartStop : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getStartStop() const { return m_startStop; } + + static MsgStartStop* create(bool startStop) { + return new MsgStartStop(startStop); + } + + protected: + bool m_startStop; + + MsgStartStop(bool startStop) : + Message(), + m_startStop(startStop) + { } + }; + + class MsgDataBind : public Message { + MESSAGE_CLASS_DECLARATION + + public: + QHostAddress getAddress() const { return m_address; } + uint16_t getPort() const { return m_port; } + + static MsgDataBind* create(const QString& address, uint16_t port) { + return new MsgDataBind(address, port); + } + + protected: + QHostAddress m_address; + uint16_t m_port; + + MsgDataBind(const QString& address, uint16_t port) : + Message(), + m_port(port) + { + m_address.setAddress(address); + } + }; + + DaemonSourceThread(SDRDaemonDataQueue *dataQueue, QObject* parent = 0); + ~DaemonSourceThread(); + + void startStop(bool start); + void dataBind(const QString& address, uint16_t port); + +private: + QMutex m_startWaitMutex; + QWaitCondition m_startWaiter; + bool m_running; + + MessageQueue m_inputMessageQueue; + SDRDaemonDataQueue *m_dataQueue; + + QHostAddress m_address; + QUdpSocket *m_socket; + + static const uint32_t m_nbDataBlocks = 4; //!< number of data blocks in the ring buffer + SDRDaemonDataBlock *m_dataBlocks[m_nbDataBlocks]; //!< ring buffer of data blocks indexed by frame affinity + + void startWork(); + void stopWork(); + + void run(); + +private slots: + void handleInputMessages(); + void readPendingDatagrams(); +}; + + + +#endif /* PLUGINS_CHANNELTX_DAEMONSRC_DAEMONSRCTHREAD_H_ */ diff --git a/plugins/channeltx/daemonsource/readme.md b/plugins/channeltx/daemonsource/readme.md new file mode 100644 index 000000000..8a68a4db1 --- /dev/null +++ b/plugins/channeltx/daemonsource/readme.md @@ -0,0 +1,79 @@ +

Daemon source channel plugin

+ +

Introduction

+ +This plugin receives I/Q samples from UDP and copies them to the baseband to be transmitted by the sink output device. It uses SDRDaemon format and possible FEC protection. + +It is present only in Linux binary releases. + +

Build

+ +The plugin will be built only if the [CM256cc library](https://github.com/f4exb/cm256cc) is installed in your system. For CM256cc library you will have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_INCLUDE_DIR=/opt/install/cm256cc/include/cm256cc -DCM256CC_LIBRARIES=/opt/install/cm256cc/lib/libcm256cc.so` to the cmake commands. + +

Interface

+ +![Daemon source channel plugin GUI](../../../doc/img/DaemonSource.png) + +

1: Data local address

+ +IP address of the local network interface from where the I/Q samples are fetched via UDP + +

2: Data local port

+ +Local port from where the I/Q samples are fetched via UDP + +

3: Validation button

+ +When the return key is hit within the address (1) or port (2) the changes are effective immediately. You can also use this button to set again these values. + +

4: Stream sample rate

+ +Stream sample rate as specified in the stream meta data + +

5: Stream status

+ +![Daemon source channel plugin GUI](../../../doc/img/DaemonSource_5.png) + +

5.1: Total number of frames and number of FEC blocks

+ +This is the total number of frames and number of FEC blocks separated by a slash '/' as sent in the meta data block thus acknowledged by the distant server. When you set the number of FEC blocks with (4.1) the effect may not be immediate and this information can be used to monitor when it gets effectively set in the distant server. + +A frame consists of 128 data blocks (1 meta data block followed by 127 I/Q data blocks) and a variable number of FEC blocks used to protect the UDP transmission with a Cauchy MDS block erasure correction. + +Using the Cauchy MDS block erasure correction ensures that if at least the number of data blocks (128) is received per complete frame then all lost blocks in any position can be restored. For example if 8 FEC blocks are used then 136 blocks are transmitted per frame. If only 130 blocks (128 or greater) are received then data can be recovered. If only 127 blocks (or less) are received then none of the lost blocks can be recovered. + +

5.2: Stream status

+ +The color of the icon indicates stream status: + + - Green: all original blocks have been received for all frames during the last polling timeframe (ex: 136) + - No color: some original blocks were reconstructed from FEC blocks for some frames during the last polling timeframe (ex: between 128 and 135) + - Red: some original blocks were definitely lost for some frames during the last polling timeframe (ex: less than 128) + +

5.3: Actual stream sample rate

+ +This is the sample rate calculated using the counter of samples between two consecutive polls + +

5.4: Reset events counters

+ +This push button can be used to reset the events counters (5.5 and 5.6) and reset the event counts timer (5.7) + +

5.5: Unrecoverable error events counter

+ +This counter counts the unrecoverable error conditions found (i.e. 4.4 lower than 128) since the last counters reset. + +

5.6: Recoverable error events counter

+ +This counter counts the unrecoverable error conditions found (i.e. 4.4 between 128 and 128 plus the number of FEC blocks) since the last counters reset. + +

5.7: events counters timer

+ +This HH:mm:ss time display shows the time since the reset events counters button (5.4) was pushed. + +

6: Transmitter queue length gauge

+ +This is ratio of the reported number of data frame blocks in the remote queue over the total number of blocks in the queue. + +

7: Transmitter queue length status

+ +This is the detail of the ratio shown in the gauge. Each frame block is a block of 127 ✕ 126 samples (16 bit I or Q samples) or 127 ✕ 63 samples (24 bit I or Q samples). diff --git a/plugins/channeltx/modam/CMakeLists.txt b/plugins/channeltx/modam/CMakeLists.txt index 0be430f84..eb2d22c4d 100644 --- a/plugins/channeltx/modam/CMakeLists.txt +++ b/plugins/channeltx/modam/CMakeLists.txt @@ -1,46 +1,51 @@ -project(modam) - -set(modam_SOURCES - ammod.cpp - ammodgui.cpp - ammodplugin.cpp - ammodsettings.cpp -) - -set(modam_HEADERS - ammod.h - ammodgui.h - ammodplugin.h - ammodsettings.h -) - -set(modam_FORMS - ammodgui.ui -) - -include_directories( - . - ${CMAKE_CURRENT_BINARY_DIR} -) - -add_definitions(${QT_DEFINITIONS}) -add_definitions(-DQT_PLUGIN) -add_definitions(-DQT_SHARED) - -qt5_wrap_ui(modam_FORMS_HEADERS ${modam_FORMS}) - -add_library(modam SHARED - ${modam_SOURCES} - ${modam_HEADERS_MOC} - ${modam_FORMS_HEADERS} -) - -target_link_libraries(modam - ${QT_LIBRARIES} - sdrbase - sdrgui -) - -qt5_use_modules(modam Core Widgets) - -install(TARGETS modam DESTINATION lib/plugins/channeltx) \ No newline at end of file +project(modam) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +set(modam_SOURCES + ammod.cpp + ammodgui.cpp + ammodplugin.cpp + ammodsettings.cpp +) + +set(modam_HEADERS + ammod.h + ammodgui.h + ammodplugin.h + ammodsettings.h +) + +set(modam_FORMS + ammodgui.ui +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +qt5_wrap_ui(modam_FORMS_HEADERS ${modam_FORMS}) + +add_library(modam SHARED + ${modam_SOURCES} + ${modam_HEADERS_MOC} + ${modam_FORMS_HEADERS} +) + +target_link_libraries(modam + ${QT_LIBRARIES} + sdrbase + sdrgui + swagger +) + +target_link_libraries(modam Qt5::Core Qt5::Widgets) + +install(TARGETS modam DESTINATION lib/plugins/channeltx) + diff --git a/plugins/channeltx/modam/ammod.cpp b/plugins/channeltx/modam/ammod.cpp index ac96b2a95..6fe798e1c 100644 --- a/plugins/channeltx/modam/ammod.cpp +++ b/plugins/channeltx/modam/ammod.cpp @@ -23,17 +23,21 @@ #include #include +#include "SWGChannelSettings.h" +#include "SWGChannelReport.h" +#include "SWGAMModReport.h" + #include "dsp/upchannelizer.h" #include "dsp/dspengine.h" #include "dsp/threadedbasebandsamplesource.h" #include "dsp/dspcommands.h" #include "device/devicesinkapi.h" +#include "util/db.h" MESSAGE_CLASS_DEFINITION(AMMod::MsgConfigureAMMod, Message) MESSAGE_CLASS_DEFINITION(AMMod::MsgConfigureChannelizer, Message) MESSAGE_CLASS_DEFINITION(AMMod::MsgConfigureFileSourceName, Message) MESSAGE_CLASS_DEFINITION(AMMod::MsgConfigureFileSourceSeek, Message) -MESSAGE_CLASS_DEFINITION(AMMod::MsgConfigureAFInput, Message) MESSAGE_CLASS_DEFINITION(AMMod::MsgConfigureFileSourceStreamTiming, Message) MESSAGE_CLASS_DEFINITION(AMMod::MsgReportFileSourceStreamData, Message) MESSAGE_CLASS_DEFINITION(AMMod::MsgReportFileSourceStreamTiming, Message) @@ -53,7 +57,6 @@ AMMod::AMMod(DeviceSinkAPI *deviceAPI) : m_fileSize(0), m_recordLength(0), m_sampleRate(48000), - m_afInput(AMModInputNone), m_levelCalcCount(0), m_peakLevel(0.0f), m_levelSum(0.0f) @@ -65,21 +68,22 @@ AMMod::AMMod(DeviceSinkAPI *deviceAPI) : m_magsq = 0.0; - m_toneNco.setFreq(1000.0, m_settings.m_audioSampleRate); - DSPEngine::instance()->addAudioSource(&m_audioFifo); + DSPEngine::instance()->getAudioDeviceManager()->addAudioSource(&m_audioFifo, getInputMessageQueue()); + m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getInputSampleRate(); + m_toneNco.setFreq(1000.0, m_audioSampleRate); // CW keyer - m_cwKeyer.setSampleRate(m_settings.m_audioSampleRate); + m_cwKeyer.setSampleRate(m_audioSampleRate); m_cwKeyer.setWPM(13); m_cwKeyer.setMode(CWKeyerSettings::CWNone); + applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); + m_channelizer = new UpChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this); m_deviceAPI->addThreadedSource(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - - applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true); - applySettings(m_settings, true); } AMMod::~AMMod() @@ -88,7 +92,7 @@ AMMod::~AMMod() m_deviceAPI->removeThreadedSource(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; - DSPEngine::instance()->removeAudioSource(&m_audioFifo); + DSPEngine::instance()->getAudioDeviceManager()->removeAudioSource(&m_audioFifo); } void AMMod::pull(Sample& sample) @@ -139,14 +143,14 @@ void AMMod::pull(Sample& sample) void AMMod::pullAudio(int nbSamples) { // qDebug("AMMod::pullAudio: %d", nbSamples); - unsigned int nbAudioSamples = nbSamples * ((Real) m_settings.m_audioSampleRate / (Real) m_basebandSampleRate); + unsigned int nbAudioSamples = nbSamples * ((Real) m_audioSampleRate / (Real) m_basebandSampleRate); if (nbAudioSamples > m_audioBuffer.size()) { m_audioBuffer.resize(nbAudioSamples); } - m_audioFifo.read(reinterpret_cast(&m_audioBuffer[0]), nbAudioSamples, 10); + m_audioFifo.read(reinterpret_cast(&m_audioBuffer[0]), nbAudioSamples); m_audioBufferFill = 0; } @@ -164,12 +168,12 @@ void AMMod::modulateSample() void AMMod::pullAF(Real& sample) { - switch (m_afInput) + switch (m_settings.m_modAFInput) { - case AMModInputTone: + case AMModSettings::AMModInputTone: sample = m_toneNco.next(); break; - case AMModInputFile: + case AMModSettings::AMModInputFile: // sox f4exb_call.wav --encoding float --endian little f4exb_call.raw // ffplay -f f32le -ar 48k -ac 1 f4exb_call.raw if (m_ifstream.is_open()) @@ -198,10 +202,10 @@ void AMMod::pullAF(Real& sample) sample = 0.0f; } break; - case AMModInputAudio: + case AMModSettings::AMModInputAudio: sample = ((m_audioBuffer[m_audioBufferFill].l + m_audioBuffer[m_audioBufferFill].r) / 65536.0f) * m_settings.m_volumeFactor; break; - case AMModInputCWTone: + case AMModSettings::AMModInputCWTone: Real fadeFactor; if (m_cwKeyer.getSample()) @@ -222,7 +226,7 @@ void AMMod::pullAF(Real& sample) } } break; - case AMModInputNone: + case AMModSettings::AMModInputNone: default: sample = 0.0f; break; @@ -312,13 +316,6 @@ bool AMMod::handleMessage(const Message& cmd) return true; } - else if (MsgConfigureAFInput::match(cmd)) - { - MsgConfigureAFInput& conf = (MsgConfigureAFInput&) cmd; - m_afInput = conf.getAFInput(); - - return true; - } else if (MsgConfigureFileSourceStreamTiming::match(cmd)) { std::size_t samplesCount; @@ -335,6 +332,20 @@ bool AMMod::handleMessage(const Message& cmd) return true; } + else if (DSPConfigureAudio::match(cmd)) + { + DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd; + uint32_t sampleRate = cfg.getSampleRate(); + + qDebug() << "AMMod::handleMessage: DSPConfigureAudio:" + << " sampleRate: " << sampleRate; + + if (sampleRate != m_audioSampleRate) { + applyAudioSampleRate(sampleRate); + } + + return true; + } else if (DSPSignalNotification::match(cmd)) { return true; @@ -380,6 +391,28 @@ void AMMod::seekFileStream(int seekPercentage) } } +void AMMod::applyAudioSampleRate(int sampleRate) +{ + qDebug("AMMod::applyAudioSampleRate: %d", sampleRate); + + MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( + sampleRate, m_settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(channelConfigMsg); + + m_settingsMutex.lock(); + + m_interpolatorDistanceRemain = 0; + m_interpolatorConsumed = false; + m_interpolatorDistance = (Real) sampleRate / (Real) m_outputSampleRate; + m_interpolator.create(48, sampleRate, m_settings.m_rfBandwidth / 2.2, 3.0); + m_toneNco.setFreq(m_settings.m_toneFrequency, sampleRate); + m_cwKeyer.setSampleRate(sampleRate); + + m_settingsMutex.unlock(); + + m_audioSampleRate = sampleRate; +} + void AMMod::applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force) { qDebug() << "AMMod::applyChannelSettings:" @@ -400,8 +433,8 @@ void AMMod::applyChannelSettings(int basebandSampleRate, int outputSampleRate, i m_settingsMutex.lock(); m_interpolatorDistanceRemain = 0; m_interpolatorConsumed = false; - m_interpolatorDistance = (Real) m_settings.m_audioSampleRate / (Real) outputSampleRate; - m_interpolator.create(48, m_settings.m_audioSampleRate, m_settings.m_rfBandwidth / 2.2, 3.0); + m_interpolatorDistance = (Real) m_audioSampleRate / (Real) outputSampleRate; + m_interpolator.create(48, m_audioSampleRate, m_settings.m_rfBandwidth / 2.2, 3.0); m_settingsMutex.unlock(); } @@ -419,30 +452,38 @@ void AMMod::applySettings(const AMModSettings& settings, bool force) << " m_toneFrequency: " << settings.m_toneFrequency << " m_volumeFactor: " << settings.m_volumeFactor << " m_audioMute: " << settings.m_channelMute - << " m_playLoop: " << settings.m_playLoop; + << " m_playLoop: " << settings.m_playLoop + << " m_modAFInput " << settings.m_modAFInput + << " m_audioDeviceName: " << settings.m_audioDeviceName + << " force: " << force; - if((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || - (settings.m_audioSampleRate != m_settings.m_audioSampleRate) || force) + if((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) { m_settingsMutex.lock(); m_interpolatorDistanceRemain = 0; m_interpolatorConsumed = false; - m_interpolatorDistance = (Real) settings.m_audioSampleRate / (Real) m_outputSampleRate; - m_interpolator.create(48, settings.m_audioSampleRate, settings.m_rfBandwidth / 2.2, 3.0); + m_interpolatorDistance = (Real) m_audioSampleRate / (Real) m_outputSampleRate; + m_interpolator.create(48, m_audioSampleRate, settings.m_rfBandwidth / 2.2, 3.0); m_settingsMutex.unlock(); } - if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || - (settings.m_audioSampleRate != m_settings.m_audioSampleRate) || force) + if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || force) { m_settingsMutex.lock(); - m_toneNco.setFreq(settings.m_toneFrequency, settings.m_audioSampleRate); + m_toneNco.setFreq(settings.m_toneFrequency, m_audioSampleRate); m_settingsMutex.unlock(); } - if ((settings.m_audioSampleRate != m_settings.m_audioSampleRate) || force) + if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) { - m_cwKeyer.setSampleRate(settings.m_audioSampleRate); + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + int audioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_audioDeviceName); + audioDeviceManager->addAudioSource(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex); + uint32_t audioSampleRate = audioDeviceManager->getInputSampleRate(audioDeviceIndex); + + if (m_audioSampleRate != audioSampleRate) { + applyAudioSampleRate(audioSampleRate); + } } m_settings = settings; @@ -469,3 +510,174 @@ bool AMMod::deserialize(const QByteArray& data) return false; } } + +int AMMod::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setAmModSettings(new SWGSDRangel::SWGAMModSettings()); + response.getAmModSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int AMMod::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + AMModSettings settings = m_settings; + bool frequencyOffsetChanged = false; + + if (channelSettingsKeys.contains("channelMute")) { + settings.m_channelMute = response.getAmModSettings()->getChannelMute() != 0; + } + if (channelSettingsKeys.contains("inputFrequencyOffset")) + { + settings.m_inputFrequencyOffset = response.getAmModSettings()->getInputFrequencyOffset(); + frequencyOffsetChanged = true; + } + if (channelSettingsKeys.contains("modAFInput")) { + settings.m_modAFInput = (AMModSettings::AMModInputAF) response.getAmModSettings()->getModAfInput(); + } + if (channelSettingsKeys.contains("audioDeviceName")) { + settings.m_audioDeviceName = *response.getAmModSettings()->getAudioDeviceName(); + } + if (channelSettingsKeys.contains("playLoop")) { + settings.m_playLoop = response.getAmModSettings()->getPlayLoop() != 0; + } + if (channelSettingsKeys.contains("rfBandwidth")) { + settings.m_rfBandwidth = response.getAmModSettings()->getRfBandwidth(); + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getAmModSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getAmModSettings()->getTitle(); + } + if (channelSettingsKeys.contains("toneFrequency")) { + settings.m_toneFrequency = response.getAmModSettings()->getToneFrequency(); + } + if (channelSettingsKeys.contains("volumeFactor")) { + settings.m_volumeFactor = response.getAmModSettings()->getVolumeFactor(); + } + if (channelSettingsKeys.contains("modFactor")) { + settings.m_modFactor = response.getAmModSettings()->getModFactor(); + } + + if (channelSettingsKeys.contains("cwKeyer")) + { + SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = response.getAmModSettings()->getCwKeyer(); + CWKeyerSettings cwKeyerSettings = m_cwKeyer.getSettings(); + + if (channelSettingsKeys.contains("cwKeyer.loop")) { + cwKeyerSettings.m_loop = apiCwKeyerSettings->getLoop() != 0; + } + if (channelSettingsKeys.contains("cwKeyer.mode")) { + cwKeyerSettings.m_mode = (CWKeyerSettings::CWMode) apiCwKeyerSettings->getMode(); + } + if (channelSettingsKeys.contains("cwKeyer.text")) { + cwKeyerSettings.m_text = *apiCwKeyerSettings->getText(); + } + if (channelSettingsKeys.contains("cwKeyer.sampleRate")) { + cwKeyerSettings.m_sampleRate = apiCwKeyerSettings->getSampleRate(); + } + if (channelSettingsKeys.contains("cwKeyer.wpm")) { + cwKeyerSettings.m_wpm = apiCwKeyerSettings->getWpm(); + } + + m_cwKeyer.setLoop(cwKeyerSettings.m_loop); + m_cwKeyer.setMode(cwKeyerSettings.m_mode); + m_cwKeyer.setSampleRate(cwKeyerSettings.m_sampleRate); + m_cwKeyer.setText(cwKeyerSettings.m_text); + m_cwKeyer.setWPM(cwKeyerSettings.m_wpm); + + if (m_guiMessageQueue) // forward to GUI if any + { + CWKeyer::MsgConfigureCWKeyer *msgCwKeyer = CWKeyer::MsgConfigureCWKeyer::create(cwKeyerSettings, force); + m_guiMessageQueue->push(msgCwKeyer); + } + } + + if (frequencyOffsetChanged) + { + AMMod::MsgConfigureChannelizer *msgChan = AMMod::MsgConfigureChannelizer::create( + m_audioSampleRate, settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(msgChan); + } + + MsgConfigureAMMod *msg = MsgConfigureAMMod::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureAMMod *msgToGUI = MsgConfigureAMMod::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int AMMod::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setAmModReport(new SWGSDRangel::SWGAMModReport()); + response.getAmModReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + +void AMMod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const AMModSettings& settings) +{ + response.getAmModSettings()->setChannelMute(settings.m_channelMute ? 1 : 0); + response.getAmModSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getAmModSettings()->setModAfInput((int) settings.m_modAFInput); + response.getAmModSettings()->setPlayLoop(settings.m_playLoop ? 1 : 0); + response.getAmModSettings()->setRfBandwidth(settings.m_rfBandwidth); + response.getAmModSettings()->setModFactor(settings.m_modFactor); + response.getAmModSettings()->setRgbColor(settings.m_rgbColor); + + if (response.getAmModSettings()->getTitle()) { + *response.getAmModSettings()->getTitle() = settings.m_title; + } else { + response.getAmModSettings()->setTitle(new QString(settings.m_title)); + } + + response.getAmModSettings()->setToneFrequency(settings.m_toneFrequency); + response.getAmModSettings()->setVolumeFactor(settings.m_volumeFactor); + + if (!response.getAmModSettings()->getCwKeyer()) { + response.getAmModSettings()->setCwKeyer(new SWGSDRangel::SWGCWKeyerSettings); + } + + SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = response.getAmModSettings()->getCwKeyer(); + const CWKeyerSettings& cwKeyerSettings = m_cwKeyer.getSettings(); + apiCwKeyerSettings->setLoop(cwKeyerSettings.m_loop ? 1 : 0); + apiCwKeyerSettings->setMode((int) cwKeyerSettings.m_mode); + apiCwKeyerSettings->setSampleRate(cwKeyerSettings.m_sampleRate); + + if (apiCwKeyerSettings->getText()) { + *apiCwKeyerSettings->getText() = cwKeyerSettings.m_text; + } else { + apiCwKeyerSettings->setText(new QString(cwKeyerSettings.m_text)); + } + + apiCwKeyerSettings->setWpm(cwKeyerSettings.m_wpm); + + if (response.getAmModSettings()->getAudioDeviceName()) { + *response.getAmModSettings()->getAudioDeviceName() = settings.m_audioDeviceName; + } else { + response.getAmModSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName)); + } +} + +void AMMod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + response.getAmModReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq())); + response.getAmModReport()->setAudioSampleRate(m_audioSampleRate); + response.getAmModReport()->setChannelSampleRate(m_outputSampleRate); +} diff --git a/plugins/channeltx/modam/ammod.h b/plugins/channeltx/modam/ammod.h index 1ed8d3651..4e41f52bf 100644 --- a/plugins/channeltx/modam/ammod.h +++ b/plugins/channeltx/modam/ammod.h @@ -89,15 +89,6 @@ public: { } }; - typedef enum - { - AMModInputNone, - AMModInputTone, - AMModInputFile, - AMModInputAudio, - AMModInputCWTone - } AMModInputAF; - class MsgConfigureFileSourceName : public Message { MESSAGE_CLASS_DECLARATION @@ -157,27 +148,6 @@ public: { } }; - class MsgConfigureAFInput : public Message - { - MESSAGE_CLASS_DECLARATION - - public: - AMModInputAF getAFInput() const { return m_afInput; } - - static MsgConfigureAFInput* create(AMModInputAF afInput) - { - return new MsgConfigureAFInput(afInput); - } - - private: - AMModInputAF m_afInput; - - MsgConfigureAFInput(AMModInputAF afInput) : - Message(), - m_afInput(afInput) - { } - }; - class MsgReportFileSourceStreamTiming : public Message { MESSAGE_CLASS_DECLARATION @@ -238,13 +208,25 @@ public: virtual void getIdentifier(QString& id) { id = objectName(); } virtual void getTitle(QString& title) { title = m_settings.m_title; } - virtual void setName(const QString& name) { setObjectName(name); } - virtual QString getName() const { return objectName(); } virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; } virtual QByteArray serialize() const; virtual bool deserialize(const QByteArray& data); + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + double getMagSq() const { return m_magsq; } CWKeyer *getCWKeyer() { return &m_cwKeyer; } @@ -276,6 +258,7 @@ private: int m_outputSampleRate; int m_inputFrequencyOffset; AMModSettings m_settings; + quint32 m_audioSampleRate; NCO m_carrierNco; NCOF m_toneNco; @@ -301,7 +284,6 @@ private: quint32 m_recordLength; //!< record length in seconds computed from file size int m_sampleRate; - AMModInputAF m_afInput; quint32 m_levelCalcCount; Real m_peakLevel; Real m_levelSum; @@ -309,6 +291,7 @@ private: static const int m_levelNbSamples; + void applyAudioSampleRate(int sampleRate); void applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force = false); void applySettings(const AMModSettings& settings, bool force = false); void pullAF(Real& sample); @@ -316,6 +299,8 @@ private: void modulateSample(); void openFileStream(); void seekFileStream(int seekPercentage); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const AMModSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); }; diff --git a/plugins/channeltx/modam/ammodgui.cpp b/plugins/channeltx/modam/ammodgui.cpp index 79027a693..25270da64 100644 --- a/plugins/channeltx/modam/ammodgui.cpp +++ b/plugins/channeltx/modam/ammodgui.cpp @@ -20,19 +20,22 @@ #include #include -#include "ammodgui.h" - #include "device/devicesinkapi.h" #include "device/deviceuiset.h" #include "dsp/upchannelizer.h" -#include "ui_ammodgui.h" #include "plugin/pluginapi.h" #include "util/simpleserializer.h" #include "util/db.h" #include "dsp/dspengine.h" +#include "gui/crightclickenabler.h" +#include "gui/audioselectdialog.h" +#include "gui/basicchannelsettingsdialog.h" #include "mainwindow.h" +#include "ui_ammodgui.h" +#include "ammodgui.h" + AMModGUI* AMModGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx) { AMModGUI* gui = new AMModGUI(pluginAPI, deviceUISet, channelTx); @@ -104,6 +107,21 @@ bool AMModGUI::handleMessage(const Message& message) updateWithStreamTime(); return true; } + else if (AMMod::MsgConfigureAMMod::match(message)) + { + const AMMod::MsgConfigureAMMod& cfg = (AMMod::MsgConfigureAMMod&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else if (CWKeyer::MsgConfigureCWKeyer::match(message)) + { + const CWKeyer::MsgConfigureCWKeyer& cfg = (CWKeyer::MsgConfigureCWKeyer&) message; + ui->cwKeyerGUI->displaySettings(cfg.getSettings()); + return true; + } else { return false; @@ -184,9 +202,8 @@ void AMModGUI::on_play_toggled(bool checked) ui->tone->setEnabled(!checked); // release other source inputs ui->morseKeyer->setEnabled(!checked); ui->mic->setEnabled(!checked); - m_modAFInput = checked ? AMMod::AMModInputFile : AMMod::AMModInputNone; - AMMod::MsgConfigureAFInput* message = AMMod::MsgConfigureAFInput::create(m_modAFInput); - m_amMod->getInputMessageQueue()->push(message); + m_settings.m_modAFInput = checked ? AMModSettings::AMModInputFile : AMModSettings::AMModInputNone; + applySettings(); ui->navTimeSlider->setEnabled(!checked); m_enableNavTime = !checked; } @@ -196,9 +213,8 @@ void AMModGUI::on_tone_toggled(bool checked) ui->play->setEnabled(!checked); // release other source inputs ui->morseKeyer->setEnabled(!checked); ui->mic->setEnabled(!checked); - m_modAFInput = checked ? AMMod::AMModInputTone : AMMod::AMModInputNone; - AMMod::MsgConfigureAFInput* message = AMMod::MsgConfigureAFInput::create(m_modAFInput); - m_amMod->getInputMessageQueue()->push(message); + m_settings.m_modAFInput = checked ? AMModSettings::AMModInputTone : AMModSettings::AMModInputNone; + applySettings(); } void AMModGUI::on_morseKeyer_toggled(bool checked) @@ -206,9 +222,8 @@ void AMModGUI::on_morseKeyer_toggled(bool checked) ui->play->setEnabled(!checked); // release other source inputs ui->tone->setEnabled(!checked); // release other source inputs ui->mic->setEnabled(!checked); - m_modAFInput = checked ? AMMod::AMModInputCWTone : AMMod::AMModInputNone; - AMMod::MsgConfigureAFInput* message = AMMod::MsgConfigureAFInput::create(m_modAFInput); - m_amMod->getInputMessageQueue()->push(message); + m_settings.m_modAFInput = checked ? AMModSettings::AMModInputCWTone : AMModSettings::AMModInputNone; + applySettings(); } void AMModGUI::on_mic_toggled(bool checked) @@ -216,9 +231,8 @@ void AMModGUI::on_mic_toggled(bool checked) ui->play->setEnabled(!checked); // release other source inputs ui->morseKeyer->setEnabled(!checked); ui->tone->setEnabled(!checked); // release other source inputs - m_modAFInput = checked ? AMMod::AMModInputAudio : AMMod::AMModInputNone; - AMMod::MsgConfigureAFInput* message = AMMod::MsgConfigureAFInput::create(m_modAFInput); - m_amMod->getInputMessageQueue()->push(message); + m_settings.m_modAFInput = checked ? AMModSettings::AMModInputAudio : AMModSettings::AMModInputNone; + applySettings(); } void AMModGUI::on_navTimeSlider_valueChanged(int value) @@ -237,7 +251,7 @@ void AMModGUI::on_navTimeSlider_valueChanged(int value) void AMModGUI::on_showFileDialog_clicked(bool checked __attribute__((unused))) { QString fileName = QFileDialog::getOpenFileName(this, - tr("Open raw audio file"), ".", tr("Raw audio Files (*.raw)")); + tr("Open raw audio file"), ".", tr("Raw audio Files (*.raw)"), 0, QFileDialog::DontUseNativeDialog); if (fileName != "") { @@ -259,6 +273,22 @@ void AMModGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool roll { } +void AMModGUI::onMenuDialogCalled(const QPoint &p) +{ + BasicChannelSettingsDialog dialog(&m_channelMarker, this); + dialog.move(p); + dialog.exec(); + + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); + m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); + m_settings.m_title = m_channelMarker.getTitle(); + + setWindowTitle(m_settings.m_title); + setTitleColor(m_settings.m_rgbColor); + + applySettings(); +} + AMModGUI::AMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent) : RollupWidget(parent), ui(new Ui::AMModGUI), @@ -270,18 +300,21 @@ AMModGUI::AMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampl m_recordSampleRate(48000), m_samplesCount(0), m_tickCount(0), - m_enableNavTime(false), - m_modAFInput(AMMod::AMModInputNone) + m_enableNavTime(false) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose, true); connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); m_amMod = (AMMod*) channelTx; //new AMMod(m_deviceUISet->m_deviceSinkAPI); m_amMod->setMessageQueueToGUI(getInputMessageQueue()); connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); + CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->mic); + connect(audioMuteRightClickEnabler, SIGNAL(rightClick()), this, SLOT(audioSelect())); + ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999); @@ -291,8 +324,6 @@ AMModGUI::AMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampl m_channelMarker.setBandwidth(5000); m_channelMarker.setCenterFrequency(0); m_channelMarker.setTitle("AM Modulator"); - m_channelMarker.setUDPAddress("127.0.0.1"); - m_channelMarker.setUDPSendPort(9999); m_channelMarker.blockSignals(false); m_channelMarker.setVisible(true); // activate signal on the last setting only @@ -351,6 +382,7 @@ void AMModGUI::displaySettings() { m_channelMarker.blockSignals(true); m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset); + m_channelMarker.setTitle(m_settings.m_title); m_channelMarker.setBandwidth(m_settings.m_rfBandwidth); m_channelMarker.blockSignals(false); m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only @@ -378,6 +410,16 @@ void AMModGUI::displaySettings() ui->channelMute->setChecked(m_settings.m_channelMute); ui->playLoop->setChecked(m_settings.m_playLoop); + ui->tone->setEnabled((m_settings.m_modAFInput == AMModSettings::AMModInputAF::AMModInputTone) || (m_settings.m_modAFInput == AMModSettings::AMModInputAF::AMModInputNone)); + ui->mic->setEnabled((m_settings.m_modAFInput == AMModSettings::AMModInputAF::AMModInputAudio) || (m_settings.m_modAFInput == AMModSettings::AMModInputAF::AMModInputNone)); + ui->play->setEnabled((m_settings.m_modAFInput == AMModSettings::AMModInputAF::AMModInputFile) || (m_settings.m_modAFInput == AMModSettings::AMModInputAF::AMModInputNone)); + ui->morseKeyer->setEnabled((m_settings.m_modAFInput == AMModSettings::AMModInputAF::AMModInputCWTone) || (m_settings.m_modAFInput == AMModSettings::AMModInputAF::AMModInputNone)); + + ui->tone->setChecked(m_settings.m_modAFInput == AMModSettings::AMModInputAF::AMModInputTone); + ui->mic->setChecked(m_settings.m_modAFInput == AMModSettings::AMModInputAF::AMModInputAudio); + ui->play->setChecked(m_settings.m_modAFInput == AMModSettings::AMModInputAF::AMModInputFile); + ui->morseKeyer->setChecked(m_settings.m_modAFInput == AMModSettings::AMModInputAF::AMModInputCWTone); + blockApplySettings(false); } @@ -391,13 +433,26 @@ void AMModGUI::enterEvent(QEvent*) m_channelMarker.setHighlighted(true); } +void AMModGUI::audioSelect() +{ + qDebug("AMModGUI::audioSelect"); + AudioSelectDialog audioSelect(DSPEngine::instance()->getAudioDeviceManager(), m_settings.m_audioDeviceName, true); // true for input + audioSelect.exec(); + + if (audioSelect.m_selected) + { + m_settings.m_audioDeviceName = audioSelect.m_audioDeviceName; + applySettings(); + } +} + void AMModGUI::tick() { double powDb = CalcDb::dbPower(m_amMod->getMagSq()); m_channelPowerDbAvg(powDb); ui->channelPower->setText(tr("%1 dB").arg(m_channelPowerDbAvg.asDouble(), 0, 'f', 1)); - if (((++m_tickCount & 0xf) == 0) && (m_modAFInput == AMMod::AMModInputFile)) + if (((++m_tickCount & 0xf) == 0) && (m_settings.m_modAFInput == AMModSettings::AMModInputFile)) { AMMod::MsgConfigureFileSourceStreamTiming* message = AMMod::MsgConfigureFileSourceStreamTiming::create(); m_amMod->getInputMessageQueue()->push(message); @@ -408,7 +463,7 @@ void AMModGUI::updateWithStreamData() { QTime recordLength(0, 0, 0, 0); recordLength = recordLength.addSecs(m_recordLength); - QString s_time = recordLength.toString("hh:mm:ss"); + QString s_time = recordLength.toString("HH:mm:ss"); ui->recordLengthText->setText(s_time); updateWithStreamTime(); } @@ -427,8 +482,8 @@ void AMModGUI::updateWithStreamTime() QTime t(0, 0, 0, 0); t = t.addSecs(t_sec); t = t.addMSecs(t_msec); - QString s_timems = t.toString("hh:mm:ss.zzz"); - QString s_time = t.toString("hh:mm:ss"); + QString s_timems = t.toString("HH:mm:ss.zzz"); + QString s_time = t.toString("HH:mm:ss"); ui->relTimeText->setText(s_timems); if (!m_enableNavTime) diff --git a/plugins/channeltx/modam/ammodgui.h b/plugins/channeltx/modam/ammodgui.h index 82b4efbf3..9220f9886 100644 --- a/plugins/channeltx/modam/ammodgui.h +++ b/plugins/channeltx/modam/ammodgui.h @@ -74,7 +74,6 @@ private: int m_samplesCount; std::size_t m_tickCount; bool m_enableNavTime; - AMMod::AMModInputAF m_modAFInput; MessageQueue m_inputMessageQueue; explicit AMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent = 0); @@ -108,8 +107,10 @@ private slots: void on_showFileDialog_clicked(bool checked); void onWidgetRolled(QWidget* widget, bool rollDown); + void onMenuDialogCalled(const QPoint& p); void configureFileName(); + void audioSelect(); void tick(); }; diff --git a/plugins/channeltx/modam/ammodgui.ui b/plugins/channeltx/modam/ammodgui.ui index 1b98dafa4..a6fb4f3a6 100644 --- a/plugins/channeltx/modam/ammodgui.ui +++ b/plugins/channeltx/modam/ammodgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -56,16 +56,7 @@ 3 - - 2 - - - 2 - - - 2 - - + 2 @@ -104,7 +95,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -358,6 +349,12 @@ 0 + + + Liberation Mono + 8 + + Level (% full range) top trace: average, bottom trace: instantaneous peak, tip: peak hold @@ -449,7 +446,7 @@ - Audio input + Left: Source audio input Right: Select audio input device ... diff --git a/plugins/channeltx/modam/ammodplugin.cpp b/plugins/channeltx/modam/ammodplugin.cpp index e2f5d903f..23d880d4e 100644 --- a/plugins/channeltx/modam/ammodplugin.cpp +++ b/plugins/channeltx/modam/ammodplugin.cpp @@ -15,16 +15,17 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include #include "plugin/pluginapi.h" +#ifndef SERVER_MODE #include "ammodgui.h" +#endif #include "ammod.h" #include "ammodplugin.h" const PluginDescriptor AMModPlugin::m_pluginDescriptor = { QString("AM Modulator"), - QString("3.12.0"), + QString("4.1.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, @@ -50,10 +51,19 @@ void AMModPlugin::initPlugin(PluginAPI* pluginAPI) m_pluginAPI->registerTxChannel(AMMod::m_channelIdURI, AMMod::m_channelId, this); } +#ifdef SERVER_MODE +PluginInstanceGUI* AMModPlugin::createTxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSource *txChannel __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* AMModPlugin::createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel) { return AMModGUI::create(m_pluginAPI, deviceUISet, txChannel); } +#endif BasebandSampleSource* AMModPlugin::createTxChannelBS(DeviceSinkAPI *deviceAPI) { diff --git a/plugins/channeltx/modam/ammodsettings.cpp b/plugins/channeltx/modam/ammodsettings.cpp index 0265c9536..da1d78a2e 100644 --- a/plugins/channeltx/modam/ammodsettings.cpp +++ b/plugins/channeltx/modam/ammodsettings.cpp @@ -34,12 +34,13 @@ void AMModSettings::resetToDefaults() m_rfBandwidth = 12500.0; m_modFactor = 0.2f; m_toneFrequency = 1000.0f; - m_audioSampleRate = DSPEngine::instance()->getAudioSampleRate(); m_volumeFactor = 1.0f; m_channelMute = false; m_playLoop = false; m_rgbColor = QColor(255, 255, 0).rgb(); m_title = "AM Modulator"; + m_modAFInput = AMModInputAF::AMModInputNone; + m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; } QByteArray AMModSettings::serialize() const @@ -62,6 +63,8 @@ QByteArray AMModSettings::serialize() const } s.writeString(9, m_title); + s.writeString(10, m_audioDeviceName); + s.writeS32(11, (int) m_modAFInput); return s.final(); } @@ -100,6 +103,14 @@ bool AMModSettings::deserialize(const QByteArray& data) } d.readString(9, &m_title, "AM Modulator"); + d.readString(10, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName); + + d.readS32(11, &tmp, 0); + if ((tmp < 0) || (tmp > (int) AMModInputAF::AMModInputTone)) { + m_modAFInput = AMModInputNone; + } else { + m_modAFInput = (AMModInputAF) tmp; + } return true; } diff --git a/plugins/channeltx/modam/ammodsettings.h b/plugins/channeltx/modam/ammodsettings.h index ab915d7bc..0ed6b435e 100644 --- a/plugins/channeltx/modam/ammodsettings.h +++ b/plugins/channeltx/modam/ammodsettings.h @@ -23,16 +23,26 @@ class Serializable; struct AMModSettings { + typedef enum + { + AMModInputNone, + AMModInputTone, + AMModInputFile, + AMModInputAudio, + AMModInputCWTone + } AMModInputAF; + qint64 m_inputFrequencyOffset; Real m_rfBandwidth; float m_modFactor; float m_toneFrequency; float m_volumeFactor; - quint32 m_audioSampleRate; bool m_channelMute; bool m_playLoop; quint32 m_rgbColor; QString m_title; + AMModInputAF m_modAFInput; + QString m_audioDeviceName; Serializable *m_channelMarker; Serializable *m_cwKeyerGUI; diff --git a/plugins/channeltx/modam/modam.pro b/plugins/channeltx/modam/modam.pro index 239a16ddf..3eb914740 100644 --- a/plugins/channeltx/modam/modam.pro +++ b/plugins/channeltx/modam/modam.pro @@ -18,8 +18,10 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui +INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug @@ -38,5 +40,6 @@ FORMS += ammodgui.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui +LIBS += -L../../../swagger/$${build_subdir} -lswagger RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/plugins/channeltx/modam/readme.md b/plugins/channeltx/modam/readme.md index 9f766ec36..4978f07e0 100644 --- a/plugins/channeltx/modam/readme.md +++ b/plugins/channeltx/modam/readme.md @@ -10,7 +10,7 @@ This plugin can be used to generate a narrowband amplitude modulated signal. "Na

1: Frequency shift from center frequency of transmission

-Use the wheels to adjust the frequency shift in Hz from the center frequency of transmission. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the frequency shift in Hz from the center frequency of transmission. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

3: Channel power

@@ -38,7 +38,7 @@ This is the volume of the audio signal from 0.0 (mute) to 2.0 (maximum). It can - bottom bar (brown): instantaneous peak value - tip vertical bar (bright red): peak hold value -You should aim at keepimg the peak value below 100% using the volume control +You should aim at keeping the peak value below 100% using the volume control

9: Input source control

@@ -56,9 +56,11 @@ Switches to the Morse keyer input. You must switch it off to make other inputs a Adjusts the tone frequency from 0.1 to 2.5 kHz in 0.01 kHz steps -

9.4: Audio input select

+

9.4: Audio input select and select audio input device

-Switches to the audio input. You must switch it off to make other inputs available. +Left click to switch to the audio input. You must switch it off to make other inputs available. + +Right click to select audio input device. See [audio management documentation](../../../sdrgui/audio.md) for details.

10: CW (Morse) text

@@ -131,4 +133,4 @@ This is the audio file play length in time units

17: Play file position slider

-This slider can be used to randomly set the currennt position in the file when file play is in pause state (button 14.3) \ No newline at end of file +This slider can be used to randomly set the current position in the file when file play is in pause state (button 14.3) diff --git a/plugins/channeltx/modatv/CMakeLists.txt b/plugins/channeltx/modatv/CMakeLists.txt index 4d89f8fe7..f3ef7ac29 100644 --- a/plugins/channeltx/modatv/CMakeLists.txt +++ b/plugins/channeltx/modatv/CMakeLists.txt @@ -1,5 +1,7 @@ project(modatv) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + set(modatv_SOURCES atvmod.cpp atvmodgui.cpp @@ -24,6 +26,7 @@ include_directories( . ${OpenCV_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) add_definitions(${QT_DEFINITIONS}) @@ -43,8 +46,9 @@ target_link_libraries(modatv ${QT_LIBRARIES} sdrbase sdrgui + swagger ) -qt5_use_modules(modatv Core Widgets) +target_link_libraries(modatv Qt5::Core Qt5::Widgets) -install(TARGETS modatv DESTINATION lib/plugins/channeltx) \ No newline at end of file +install(TARGETS modatv DESTINATION lib/plugins/channeltx) diff --git a/plugins/channeltx/modatv/atvmod.cpp b/plugins/channeltx/modatv/atvmod.cpp index 01ae25a16..6c98853dd 100644 --- a/plugins/channeltx/modatv/atvmod.cpp +++ b/plugins/channeltx/modatv/atvmod.cpp @@ -17,12 +17,17 @@ #include #include +#include "SWGChannelSettings.h" +#include "SWGChannelReport.h" +#include "SWGATVModReport.h" + #include "opencv2/imgproc/imgproc.hpp" #include "dsp/upchannelizer.h" #include "dsp/threadedbasebandsamplesource.h" #include "dsp/dspcommands.h" #include "device/devicesinkapi.h" +#include "util/db.h" #include "atvmod.h" @@ -37,8 +42,6 @@ MESSAGE_CLASS_DEFINITION(ATVMod::MsgReportVideoFileSourceStreamData, Message) MESSAGE_CLASS_DEFINITION(ATVMod::MsgConfigureCameraIndex, Message) MESSAGE_CLASS_DEFINITION(ATVMod::MsgConfigureCameraData, Message) MESSAGE_CLASS_DEFINITION(ATVMod::MsgReportCameraData, Message) -MESSAGE_CLASS_DEFINITION(ATVMod::MsgConfigureOverlayText, Message) -MESSAGE_CLASS_DEFINITION(ATVMod::MsgConfigureShowOverlayText, Message) MESSAGE_CLASS_DEFINITION(ATVMod::MsgReportEffectiveSampleRate, Message) const QString ATVMod::m_channelIdURI = "sdrangel.channeltx.modatv"; @@ -68,7 +71,7 @@ ATVMod::ATVMod(DeviceSinkAPI *deviceAPI) : m_videoEOF(false), m_videoOK(false), m_cameraIndex(-1), - m_showOverlayText(false), + //m_showOverlayText(false), m_SSBFilter(0), m_SSBFilterBuffer(0), m_SSBFilterBufferIndex(0), @@ -90,13 +93,13 @@ ATVMod::ATVMod(DeviceSinkAPI *deviceAPI) : m_interpolatorDistanceRemain = 0.0f; m_interpolatorDistance = 1.0f; + applyChannelSettings(m_outputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); // does applyStandard() too; + m_channelizer = new UpChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this); m_deviceAPI->addThreadedSource(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - - applyChannelSettings(m_outputSampleRate, m_inputFrequencyOffset, true); - applySettings(m_settings, true); // does applyStandard() too; } ATVMod::~ATVMod() @@ -107,6 +110,10 @@ ATVMod::~ATVMod() m_deviceAPI->removeThreadedSource(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; + delete m_SSBFilter; + delete m_DSBFilter; + delete[] m_SSBFilterBuffer; + delete[] m_DSBFilterBuffer; } void ATVMod::pullAudio(int nbSamples __attribute__((unused))) @@ -320,7 +327,7 @@ void ATVMod::pullVideo(Real& sample) if (!colorFrame.empty()) // some frames may not come out properly { - if (m_showOverlayText) { + if (m_settings.m_showOverlayText) { mixImageAndText(colorFrame); } @@ -440,7 +447,7 @@ void ATVMod::pullVideo(Real& sample) if (!colorFrame.empty()) // some frames may not come out properly { - if (m_showOverlayText) { + if (m_settings.m_showOverlayText) { mixImageAndText(colorFrame); } @@ -448,7 +455,7 @@ void ATVMod::pullVideo(Real& sample) resizeCamera(); } - if (camera.m_videoFPSCount < camera.m_videoFPSManualEnable ? camera.m_videoFPSManual : camera.m_videoFPS) + if (camera.m_videoFPSCount < (camera.m_videoFPSManualEnable ? camera.m_videoFPSManual : camera.m_videoFPS)) { camera.m_videoPrevFPSCount = (int) camera.m_videoFPSCount; camera.m_videoFPSCount += (camera.m_videoFPSManualEnable ? camera.m_videoFPSqManual : camera.m_videoFPSq); @@ -456,7 +463,7 @@ void ATVMod::pullVideo(Real& sample) else { camera.m_videoPrevFPSCount = 0; - camera.m_videoFPSCount = camera.m_videoFPSManualEnable ? camera.m_videoFPSqManual : camera.m_videoFPSq; + camera.m_videoFPSCount = (camera.m_videoFPSManualEnable ? camera.m_videoFPSqManual : camera.m_videoFPSq); } } } @@ -609,34 +616,6 @@ bool ATVMod::handleMessage(const Message& cmd) return true; } - else if (MsgConfigureOverlayText::match(cmd)) - { - MsgConfigureOverlayText& cfg = (MsgConfigureOverlayText&) cmd; - m_overlayText = cfg.getOverlayText().toStdString(); - return true; - } - else if (MsgConfigureShowOverlayText::match(cmd)) - { - MsgConfigureShowOverlayText& cfg = (MsgConfigureShowOverlayText&) cmd; - bool showOverlayText = cfg.getShowOverlayText(); - - if (!m_imageFromFile.empty()) - { - m_imageFromFile.copyTo(m_imageOriginal); - - if (showOverlayText) { - qDebug("ATVMod::handleMessage: overlay text"); - mixImageAndText(m_imageOriginal); - } else{ - qDebug("ATVMod::handleMessage: clear text"); - } - - resizeImage(); - } - - m_showOverlayText = showOverlayText; - return true; - } else if (DSPSignalNotification::match(cmd)) { return true; @@ -818,14 +797,20 @@ void ATVMod::openImage(const QString& fileName) if (m_imageOK) { + m_imageFileName = fileName; m_imageFromFile.copyTo(m_imageOriginal); - if (m_showOverlayText) { + if (m_settings.m_showOverlayText) { mixImageAndText(m_imageOriginal); } resizeImage(); } + else + { + m_imageFileName.clear(); + qDebug("ATVMod::openImage: cannot open image file %s", qPrintable(fileName)); + } } void ATVMod::openVideo(const QString& fileName) @@ -836,6 +821,7 @@ void ATVMod::openVideo(const QString& fileName) if (m_videoOK) { + m_videoFileName = fileName; m_videoFPS = m_video.get(CV_CAP_PROP_FPS); m_videoWidth = (int) m_video.get(CV_CAP_PROP_FRAME_WIDTH); m_videoHeight = (int) m_video.get(CV_CAP_PROP_FRAME_HEIGHT); @@ -863,7 +849,8 @@ void ATVMod::openVideo(const QString& fileName) } else { - qDebug("ATVMod::openVideo: cannot open video file"); + m_videoFileName.clear(); + qDebug("ATVMod::openVideo: cannot open video file %s", qPrintable(fileName)); } } @@ -1019,13 +1006,13 @@ void ATVMod::mixImageAndText(cv::Mat& image) int baseline=0; fontScale = fontScale < 4.0f ? 4.0f : fontScale; // minimum size - cv::Size textSize = cv::getTextSize(m_overlayText, fontFace, fontScale, thickness, &baseline); + cv::Size textSize = cv::getTextSize(m_settings.m_overlayText.toStdString(), fontFace, fontScale, thickness, &baseline); baseline += thickness; // position the text in the top left corner cv::Point textOrg(6, textSize.height+10); // then put the text itself - cv::putText(image, m_overlayText, textOrg, fontFace, fontScale, cv::Scalar::all(255*m_settings.m_uniformLevel), thickness, CV_AA); + cv::putText(image, m_settings.m_overlayText.toStdString(), textOrg, fontFace, fontScale, cv::Scalar::all(255*m_settings.m_uniformLevel), thickness, CV_AA); } void ATVMod::applyChannelSettings(int outputSampleRate, int inputFrequencyOffset, bool force) @@ -1101,6 +1088,7 @@ void ATVMod::applySettings(const ATVModSettings& settings, bool force) << " m_rfScalingFactor: " << settings.m_rfScalingFactor << " m_fmExcursion: " << settings.m_fmExcursion << " m_forceDecimator: " << settings.m_forceDecimator + << " m_showOverlayText: " << settings.m_showOverlayText << " force: " << force; if ((settings.m_atvStd != m_settings.m_atvStd) @@ -1153,6 +1141,23 @@ void ATVMod::applySettings(const ATVModSettings& settings, bool force) m_settingsMutex.unlock(); } + if ((settings.m_showOverlayText != m_settings.m_showOverlayText) || force) + { + if (!m_imageFromFile.empty()) + { + m_imageFromFile.copyTo(m_imageOriginal); + + if (settings.m_showOverlayText) { + qDebug("ATVMod::applySettings: set overlay text"); + mixImageAndText(m_imageOriginal); + } else{ + qDebug("ATVMod::applySettings: clear overlay text"); + } + + resizeImage(); + } + } + m_settings = settings; } @@ -1177,3 +1182,201 @@ bool ATVMod::deserialize(const QByteArray& data) return false; } } + +int ATVMod::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setAtvModSettings(new SWGSDRangel::SWGATVModSettings()); + response.getAtvModSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int ATVMod::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + ATVModSettings settings = m_settings; + bool frequencyOffsetChanged = false; + + if (channelSettingsKeys.contains("inputFrequencyOffset")) + { + settings.m_inputFrequencyOffset = response.getAtvModSettings()->getInputFrequencyOffset(); + frequencyOffsetChanged = true; + } + if (channelSettingsKeys.contains("rfBandwidth")) { + settings.m_rfBandwidth = response.getAtvModSettings()->getRfBandwidth(); + } + if (channelSettingsKeys.contains("rfOppBandwidth")) { + settings.m_rfOppBandwidth = response.getAtvModSettings()->getRfBandwidth(); + } + if (channelSettingsKeys.contains("atvStd")) { + settings.m_atvStd = (ATVModSettings::ATVStd) response.getAtvModSettings()->getAtvStd(); + } + if (channelSettingsKeys.contains("nbLines")) { + settings.m_nbLines = response.getAtvModSettings()->getNbLines(); + } + if (channelSettingsKeys.contains("fps")) { + settings.m_fps = response.getAtvModSettings()->getFps(); + } + if (channelSettingsKeys.contains("atvModInput")) { + settings.m_atvModInput = (ATVModSettings::ATVModInput) response.getAtvModSettings()->getAtvModInput(); + } + if (channelSettingsKeys.contains("uniformLevel")) { + settings.m_uniformLevel = response.getAtvModSettings()->getUniformLevel(); + } + if (channelSettingsKeys.contains("atvModulation")) { + settings.m_atvModulation = (ATVModSettings::ATVModulation) response.getAtvModSettings()->getAtvModulation(); + } + if (channelSettingsKeys.contains("videoPlayLoop")) { + settings.m_videoPlayLoop = response.getAtvModSettings()->getVideoPlayLoop() != 0; + } + if (channelSettingsKeys.contains("videoPlay")) { + settings.m_videoPlay = response.getAtvModSettings()->getVideoPlay() != 0; + } + if (channelSettingsKeys.contains("cameraPlay")) { + settings.m_cameraPlay = response.getAtvModSettings()->getCameraPlay() != 0; + } + if (channelSettingsKeys.contains("channelMute")) { + settings.m_channelMute = response.getAtvModSettings()->getChannelMute() != 0; + } + if (channelSettingsKeys.contains("invertedVideo")) { + settings.m_invertedVideo = response.getAtvModSettings()->getInvertedVideo() != 0; + } + if (channelSettingsKeys.contains("rfScalingFactor")) { + settings.m_rfScalingFactor = response.getAtvModSettings()->getRfScalingFactor(); + } + if (channelSettingsKeys.contains("fmExcursion")) { + settings.m_fmExcursion = response.getAtvModSettings()->getFmExcursion(); + } + if (channelSettingsKeys.contains("forceDecimator")) { + settings.m_forceDecimator = response.getAtvModSettings()->getForceDecimator() != 0; + } + if (channelSettingsKeys.contains("showOverlayText")) { + settings.m_showOverlayText = response.getAtvModSettings()->getShowOverlayText() != 0; + } + if (channelSettingsKeys.contains("overlayText")) { + settings.m_overlayText = *response.getAtvModSettings()->getOverlayText(); + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getAtvModSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getAtvModSettings()->getTitle(); + } + + if (frequencyOffsetChanged) + { + ATVMod::MsgConfigureChannelizer *msgChan = ATVMod::MsgConfigureChannelizer::create( + settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(msgChan); + } + + MsgConfigureATVMod *msg = MsgConfigureATVMod::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureATVMod *msgToGUI = MsgConfigureATVMod::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + if (channelSettingsKeys.contains("imageFileName")) + { + MsgConfigureImageFileName *msg = MsgConfigureImageFileName::create( + *response.getAtvModSettings()->getImageFileName()); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureImageFileName *msgToGUI = MsgConfigureImageFileName::create( + *response.getAtvModSettings()->getImageFileName()); + m_guiMessageQueue->push(msgToGUI); + } + } + + if (channelSettingsKeys.contains("videoFileName")) + { + MsgConfigureVideoFileName *msg = MsgConfigureVideoFileName::create( + *response.getAtvModSettings()->getVideoFileName()); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureVideoFileName *msgToGUI = MsgConfigureVideoFileName::create( + *response.getAtvModSettings()->getVideoFileName()); + m_guiMessageQueue->push(msgToGUI); + } + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int ATVMod::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setAtvModReport(new SWGSDRangel::SWGATVModReport()); + response.getAtvModReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + +void ATVMod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const ATVModSettings& settings) +{ + response.getAtvModSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getAtvModSettings()->setRfBandwidth(settings.m_rfBandwidth); + response.getAtvModSettings()->setRfOppBandwidth(settings.m_rfOppBandwidth); + response.getAtvModSettings()->setAtvStd(settings.m_atvStd); + response.getAtvModSettings()->setNbLines(settings.m_nbLines); + response.getAtvModSettings()->setFps(settings.m_fps); + response.getAtvModSettings()->setAtvModInput(settings.m_atvModInput); + response.getAtvModSettings()->setUniformLevel(settings.m_uniformLevel); + response.getAtvModSettings()->setAtvModulation(settings.m_atvModulation); + response.getAtvModSettings()->setVideoPlayLoop(settings.m_videoPlayLoop ? 1 : 0); + response.getAtvModSettings()->setVideoPlay(settings.m_videoPlay ? 1 : 0); + response.getAtvModSettings()->setCameraPlay(settings.m_cameraPlay ? 1 : 0); + response.getAtvModSettings()->setChannelMute(settings.m_channelMute ? 1 : 0); + response.getAtvModSettings()->setInvertedVideo(settings.m_invertedVideo ? 1 : 0); + response.getAtvModSettings()->setRfScalingFactor(settings.m_rfScalingFactor); + response.getAtvModSettings()->setFmExcursion(settings.m_fmExcursion); + response.getAtvModSettings()->setForceDecimator(settings.m_forceDecimator ? 1 : 0); + response.getAtvModSettings()->setShowOverlayText(settings.m_showOverlayText ? 1 : 0); + + if (response.getAtvModSettings()->getOverlayText()) { + *response.getAtvModSettings()->getOverlayText() = settings.m_overlayText; + } else { + response.getAtvModSettings()->setOverlayText(new QString(settings.m_overlayText)); + } + + response.getAtvModSettings()->setRgbColor(settings.m_rgbColor); + + if (response.getAtvModSettings()->getTitle()) { + *response.getAtvModSettings()->getTitle() = settings.m_title; + } else { + response.getAtvModSettings()->setTitle(new QString(settings.m_title)); + } + + if (response.getAtvModSettings()->getImageFileName()) { + *response.getAtvModSettings()->getImageFileName() = m_imageFileName; + } else { + response.getAtvModSettings()->setImageFileName(new QString(m_imageFileName)); + } + + if (response.getAtvModSettings()->getVideoFileName()) { + *response.getAtvModSettings()->getVideoFileName() = m_videoFileName; + } else { + response.getAtvModSettings()->setVideoFileName(new QString(m_videoFileName)); + } +} + +void ATVMod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + response.getAtvModReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq())); + response.getAtvModReport()->setChannelSampleRate(m_outputSampleRate); +} diff --git a/plugins/channeltx/modatv/atvmod.h b/plugins/channeltx/modatv/atvmod.h index ff3c1fab4..1e8913dc9 100644 --- a/plugins/channeltx/modatv/atvmod.h +++ b/plugins/channeltx/modatv/atvmod.h @@ -324,48 +324,6 @@ public: { } }; - class MsgConfigureOverlayText : public Message - { - MESSAGE_CLASS_DECLARATION - - public: - const QString& getOverlayText() const { return m_overlayText; } - - static MsgConfigureOverlayText* create(const QString& overlayText) - { - return new MsgConfigureOverlayText(overlayText); - } - - private: - QString m_overlayText; - - MsgConfigureOverlayText(const QString& overlayText) : - Message(), - m_overlayText(overlayText) - { } - }; - - class MsgConfigureShowOverlayText : public Message - { - MESSAGE_CLASS_DECLARATION - - public: - bool getShowOverlayText() const { return m_showOverlayText; } - - static MsgConfigureShowOverlayText* create(bool showOverlayText) - { - return new MsgConfigureShowOverlayText(showOverlayText); - } - - private: - bool m_showOverlayText; - - MsgConfigureShowOverlayText(bool showOverlayText) : - Message(), - m_showOverlayText(showOverlayText) - { } - }; - class MsgReportEffectiveSampleRate : public Message { MESSAGE_CLASS_DECLARATION @@ -404,13 +362,25 @@ public: virtual void getIdentifier(QString& id) { id = objectName(); } virtual void getTitle(QString& title) { title = m_settings.m_title; } - virtual void setName(const QString& name) { setObjectName(name); } - virtual QString getName() const { return objectName(); } virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; } virtual QByteArray serialize() const; virtual bool deserialize(const QByteArray& data); + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + int getEffectiveSampleRate() const { return m_tvSampleRate; }; double getMagSq() const { return m_movingAverage.asDouble(); } void getCameraNumbers(std::vector& numbers); @@ -541,7 +511,8 @@ private: int m_cameraIndex; //!< curent camera index in list of available cameras std::string m_overlayText; - bool m_showOverlayText; + QString m_imageFileName; + QString m_videoFileName; // Used for standard SSB fftfilt* m_SSBFilter; @@ -583,6 +554,9 @@ private: void resizeCamera(); void mixImageAndText(cv::Mat& image); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const ATVModSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); + inline void pullImageLine(Real& sample, bool noHSync = false) { if (m_horizontalCount < m_pointsPerSync) // sync pulse diff --git a/plugins/channeltx/modatv/atvmodgui.cpp b/plugins/channeltx/modatv/atvmodgui.cpp index 61329486c..1059423a1 100644 --- a/plugins/channeltx/modatv/atvmodgui.cpp +++ b/plugins/channeltx/modatv/atvmodgui.cpp @@ -29,6 +29,7 @@ #include "util/simpleserializer.h" #include "dsp/dspengine.h" #include "util/db.h" +#include "gui/basicchannelsettingsdialog.h" #include "mainwindow.h" #include "ui_atvmodgui.h" @@ -149,6 +150,27 @@ bool ATVModGUI::handleMessage(const Message& message) setRFFiltersSlidersRange(sampleRate); return true; } + else if (ATVMod::MsgConfigureATVMod::match(message)) + { + const ATVMod::MsgConfigureATVMod& cfg = (ATVMod::MsgConfigureATVMod&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else if (ATVMod::MsgConfigureImageFileName::match(message)) + { + const ATVMod::MsgConfigureImageFileName& cfg = (ATVMod::MsgConfigureImageFileName&) message; + ui->imageFileText->setText(cfg.getFileName()); + return true; + } + else if (ATVMod::MsgConfigureVideoFileName::match(message)) + { + const ATVMod::MsgConfigureVideoFileName& cfg = (ATVMod::MsgConfigureVideoFileName&) message; + ui->videoFileText->setText(cfg.getFileName()); + return true; + } else { return false; @@ -475,7 +497,7 @@ void ATVModGUI::on_forceDecimator_toggled(bool checked) void ATVModGUI::on_imageFileDialog_clicked(bool checked __attribute__((unused))) { QString fileName = QFileDialog::getOpenFileName(this, - tr("Open image file"), ".", tr("Image Files (*.png *.jpg *.bmp *.gif *.tiff)")); + tr("Open image file"), ".", tr("Image Files (*.png *.jpg *.bmp *.gif *.tiff)"), 0, QFileDialog::DontUseNativeDialog); if (fileName != "") { @@ -488,7 +510,7 @@ void ATVModGUI::on_imageFileDialog_clicked(bool checked __attribute__((unused))) void ATVModGUI::on_videoFileDialog_clicked(bool checked __attribute__((unused))) { QString fileName = QFileDialog::getOpenFileName(this, - tr("Open video file"), ".", tr("Video Files (*.avi *.mpg *.mp4 *.mov *.m4v *.mkv *.vob *.wmv)")); + tr("Open video file"), ".", tr("Video Files (*.avi *.mpg *.mp4 *.mov *.m4v *.mkv *.vob *.wmv)"), 0, QFileDialog::DontUseNativeDialog); if (fileName != "") { @@ -554,15 +576,14 @@ void ATVModGUI::on_cameraManualFPS_valueChanged(int value) void ATVModGUI::on_overlayTextShow_toggled(bool checked) { - ATVMod::MsgConfigureShowOverlayText* message = ATVMod::MsgConfigureShowOverlayText::create(checked); - m_atvMod->getInputMessageQueue()->push(message); + m_settings.m_showOverlayText = checked; + applySettings(); } void ATVModGUI::on_overlayText_textEdited(const QString& arg1 __attribute__((unused))) { m_settings.m_overlayText = arg1; - ATVMod::MsgConfigureOverlayText* message = ATVMod::MsgConfigureOverlayText::create(ui->overlayText->text()); - m_atvMod->getInputMessageQueue()->push(message); + applySettings(); } void ATVModGUI::configureImageFileName() @@ -583,6 +604,22 @@ void ATVModGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rol { } +void ATVModGUI::onMenuDialogCalled(const QPoint &p) +{ + BasicChannelSettingsDialog dialog(&m_channelMarker, this); + dialog.move(p); + dialog.exec(); + + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); + m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); + m_settings.m_title = m_channelMarker.getTitle(); + + setWindowTitle(m_settings.m_title); + setTitleColor(m_settings.m_rgbColor); + + applySettings(); +} + ATVModGUI::ATVModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent) : RollupWidget(parent), ui(new Ui::ATVModGUI), @@ -601,6 +638,7 @@ ATVModGUI::ATVModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose, true); connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); m_atvMod = (ATVMod*) channelTx; //new ATVMod(m_deviceUISet->m_deviceSinkAPI); m_atvMod->setMessageQueueToGUI(getInputMessageQueue()); @@ -616,8 +654,6 @@ ATVModGUI::ATVModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam m_channelMarker.setBandwidth(5000); m_channelMarker.setCenterFrequency(0); m_channelMarker.setTitle("ATV Modulator"); - m_channelMarker.setUDPAddress("127.0.0.1"); - m_channelMarker.setUDPSendPort(9999); m_channelMarker.blockSignals(false); m_channelMarker.setVisible(true); // activate signal on the last setting only @@ -678,6 +714,7 @@ void ATVModGUI::displaySettings() { m_channelMarker.blockSignals(true); m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset); + m_channelMarker.setTitle(m_settings.m_title); setChannelMarkerBandwidth(); m_channelMarker.blockSignals(false); m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only @@ -724,9 +761,11 @@ void ATVModGUI::displaySettings() ui->uniformLevelText->setText(QString("%1").arg(ui->uniformLevel->value())); ui->overlayText->setText(m_settings.m_overlayText); + ui->overlayTextShow->setChecked(m_settings.m_showOverlayText); - ATVMod::MsgConfigureOverlayText* message = ATVMod::MsgConfigureOverlayText::create(ui->overlayText->text()); - m_atvMod->getInputMessageQueue()->push(message); + ui->playCamera->setChecked(m_settings.m_cameraPlay); + ui->playVideo->setChecked(m_settings.m_videoPlay); + ui->playLoop->setChecked(m_settings.m_videoPlayLoop); blockApplySettings(false); } @@ -758,7 +797,7 @@ void ATVModGUI::updateWithStreamData() { QTime recordLength(0, 0, 0, 0); recordLength = recordLength.addSecs(m_videoLength / m_videoFrameRate); - QString s_time = recordLength.toString("hh:mm:ss"); + QString s_time = recordLength.toString("HH:mm:ss"); ui->recordLengthText->setText(s_time); updateWithStreamTime(); } @@ -778,8 +817,8 @@ void ATVModGUI::updateWithStreamTime() QTime t(0, 0, 0, 0); t = t.addSecs(t_sec); t = t.addMSecs(t_msec); - QString s_timems = t.toString("hh:mm:ss.zzz"); - QString s_time = t.toString("hh:mm:ss"); + QString s_timems = t.toString("HH:mm:ss.zzz"); + QString s_time = t.toString("HH:mm:ss"); ui->relTimeText->setText(s_timems); if (!m_enableNavTime) diff --git a/plugins/channeltx/modatv/atvmodgui.h b/plugins/channeltx/modatv/atvmodgui.h index 6d262dca4..0a637bb77 100644 --- a/plugins/channeltx/modatv/atvmodgui.h +++ b/plugins/channeltx/modatv/atvmodgui.h @@ -129,6 +129,7 @@ private slots: void on_overlayText_textEdited(const QString& arg1); void onWidgetRolled(QWidget* widget, bool rollDown); + void onMenuDialogCalled(const QPoint& p); void configureImageFileName(); void configureVideoFileName(); diff --git a/plugins/channeltx/modatv/atvmodgui.ui b/plugins/channeltx/modatv/atvmodgui.ui index e311e0f1d..ce45e6cdc 100644 --- a/plugins/channeltx/modatv/atvmodgui.ui +++ b/plugins/channeltx/modatv/atvmodgui.ui @@ -24,7 +24,7 @@
- Sans Serif + Liberation Sans 9 @@ -50,16 +50,7 @@ 3 - - 2 - - - 2 - - - 2 - - + 2 @@ -98,7 +89,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -130,7 +121,7 @@ - Force decimaor usage + Force decimator usage @@ -503,6 +494,12 @@ 32 + + + Liberation Mono + 8 + + video signal level in % of 0:1 range diff --git a/plugins/channeltx/modatv/atvmodplugin.cpp b/plugins/channeltx/modatv/atvmodplugin.cpp index b663bf2e9..4aa4b1919 100644 --- a/plugins/channeltx/modatv/atvmodplugin.cpp +++ b/plugins/channeltx/modatv/atvmodplugin.cpp @@ -15,16 +15,17 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include #include "plugin/pluginapi.h" +#ifndef SERVER_MODE #include "atvmodgui.h" +#endif #include "atvmod.h" #include "atvmodplugin.h" const PluginDescriptor ATVModPlugin::m_pluginDescriptor = { QString("ATV Modulator"), - QString("3.12.0"), + QString("4.0.7"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, @@ -50,10 +51,19 @@ void ATVModPlugin::initPlugin(PluginAPI* pluginAPI) m_pluginAPI->registerTxChannel(ATVMod::m_channelIdURI, ATVMod::m_channelId, this); } +#ifdef SERVER_MODE +PluginInstanceGUI* ATVModPlugin::createTxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSource *txChannel __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* ATVModPlugin::createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel) { return ATVModGUI::create(m_pluginAPI, deviceUISet, txChannel); } +#endif BasebandSampleSource* ATVModPlugin::createTxChannelBS(DeviceSinkAPI *deviceAPI) { diff --git a/plugins/channeltx/modatv/atvmodsettings.cpp b/plugins/channeltx/modatv/atvmodsettings.cpp index dc32d0371..e74212334 100644 --- a/plugins/channeltx/modatv/atvmodsettings.cpp +++ b/plugins/channeltx/modatv/atvmodsettings.cpp @@ -46,11 +46,10 @@ void ATVModSettings::resetToDefaults() m_rfScalingFactor = 0.891235351562f * SDR_TX_SCALEF; // -1dB m_fmExcursion = 0.5f; // half bandwidth m_forceDecimator = false; + m_showOverlayText = false; m_overlayText = "ATV"; m_rgbColor = QColor(255, 255, 255).rgb(); m_title = "ATV Modulator"; - m_udpAddress = "127.0.0.1"; - m_udpPort = 9999; } QByteArray ATVModSettings::serialize() const diff --git a/plugins/channeltx/modatv/atvmodsettings.h b/plugins/channeltx/modatv/atvmodsettings.h index 2293a8ea5..633c179fd 100644 --- a/plugins/channeltx/modatv/atvmodsettings.h +++ b/plugins/channeltx/modatv/atvmodsettings.h @@ -21,7 +21,7 @@ #include #include -struct Serializable; +class Serializable; struct ATVModSettings { @@ -75,13 +75,11 @@ struct ATVModSettings float m_rfScalingFactor; //!< Scaling factor from +/-1 to +/-2^15 float m_fmExcursion; //!< FM excursion factor relative to full bandwidth bool m_forceDecimator; //!< Forces decimator even when channel and source sample rates are equal + bool m_showOverlayText; //!< Show overlay text on image QString m_overlayText; quint32 m_rgbColor; QString m_title; - QString m_udpAddress; - uint16_t m_udpPort; - Serializable *m_channelMarker; ATVModSettings(); diff --git a/plugins/channeltx/modatv/modatv.pro b/plugins/channeltx/modatv/modatv.pro index 464796e74..04adb5fe7 100644 --- a/plugins/channeltx/modatv/modatv.pro +++ b/plugins/channeltx/modatv/modatv.pro @@ -18,6 +18,7 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui diff --git a/plugins/channeltx/modatv/readme.md b/plugins/channeltx/modatv/readme.md index 761a1ed5d..1e06e64b1 100644 --- a/plugins/channeltx/modatv/readme.md +++ b/plugins/channeltx/modatv/readme.md @@ -4,7 +4,7 @@ This plugin can be used to generate an analog TV signal mostly used in amateur radio. It is limited to black and white images as only the luminance (256 levels) is supported. -There is no sound either. You coud imagine using any of the plugins supporting audio to create a mixed signal. This is not working well however for various reasons. It is better to use two physical transmitters and two physical receivers. +There is no sound either. You could imagine using any of the plugins supporting audio to create a mixed signal. This is not working well however for various reasons. It is better to use two physical transmitters and two physical receivers. In practice 4 MS/s with about 300 points per line is the lowest sample rate that produces a standard image quality. Lower sample rates and line definition produce low quality images that may still be acceptable for experiments. The plugin offers to go as low as 32 lines and 8 FPS for NBTV experiments. NBTV stands for Narrow Band TeleVision see: [Wikipedia article](https://en.wikipedia.org/wiki/Narrow-bandwidth_television) and [NBTV.org](http://www.nbtv.org/) @@ -14,7 +14,7 @@ In practice 4 MS/s with about 300 points per line is the lowest sample rate that

1: Frequency shift from center frequency of transmission

-Use the wheels to adjust the frequency shift in Hz from the center frequency of transmission. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the frequency shift in Hz from the center frequency of transmission. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

3: Sample rate data

@@ -58,7 +58,7 @@ The video signal can modulate the carrier in the following modes: - AM: Amplitude modulation. Modulation index is 90%. - FM: Frequency modulation. Excursion is a percentage of the bandwidth available given the channel sample rate. This percentage is controlled by button (2). e.g. at 25% for 4 MS/s sample rate this is 1 MHz (±0.5 MHz) - USB: SSB upper side band: video signal is transposed only in positive frequencies including DC component - - LSB: SSB lower side band: video signal is transposed only in megative frequencies excluding DC component + - LSB: SSB lower side band: video signal is transposed only in negative frequencies excluding DC component - VUSB: SSB upper sideband with vestigial lower sideband. The cutoff frequency of the lower sideband is controlled by slider (3) - VLSB: SSB lower sideband with vestigial upper sideband. The cutoff frequency of the upper sideband is controlled by slider (3) @@ -66,7 +66,7 @@ The video signal can modulate the carrier in the following modes: Use this button to control FM deviation in FM modulation mode. This is a percentage of total available channel bandwidth. e.g for the sample rate of 2997 kS/s of the screenshot and a percentage of 19% this yields a full deviation of 2997 × 0.19 = 569.43 kHz that is ±284.715 kHz -☞ You can adjust this value and see the result for yourseelf. A good starting point is half of the signal bandwidth. +☞ You can adjust this value and see the result for yourself. A good starting point is half of the signal bandwidth.

A.3: Opposite sideband FFT filter cutoff

@@ -90,13 +90,13 @@ The cutoff frequency in kHz is displayed on the right of the slider

A.5: Modulated signal level before filtering stages

-This button controls the scaling from the +1/-1 modulated signal level to the -32768/+32768 2 bytes samples. This is useful to control the saturation of the FFT or FIR filters. Looking at the output spectrum you can precisely control the limit above which distorsion appears. +This button controls the scaling from the +1/-1 modulated signal level to the -32768/+32768 2 bytes samples. This is useful to control the saturation of the FFT or FIR filters. Looking at the output spectrum you can precisely control the limit above which distortion appears.

A.6: Video signal level meter

This is the level meter fed with the video signal. Units are the percentage of the 0.0 to 1.0 modulating video signal. -

A.7: Nuber of lines

+

A.7: Number of lines

This controls the number of lines per full frame. Choice is between 640, 625, 525, 480, 405, 360, 343, 240, 180, 120, 90, 60 and 32 lines. @@ -108,18 +108,18 @@ This controls the number of full frames per second. Choice is between 30, 25, 20

A.9: TV Standard

-This controls the frame synchronization schem and number of black lines: +This controls the frame synchronization scheme and number of black lines: - PAL625: this is the PAL 625 lines standard with 25 FPS. Since only black and white (luminance) is supported this corresponds to any of the B,G,I or L PAL standards - PAL525: this is the PAL 525 lines standard with 30 FPS. This corresponds to the PAL M standard. - PAL405: this loosely corresponds to the British 405 lines system and is similar to PAL for synchronization. This mode has only 7 black lines. - ShI: this is an experimental mode that uses the least possible vertical sync lines as possible. That is one line for a long synchronization pulse and one line at a higher level (0.7) to reset the vertical sync condition. Thus only 2 lines are consumed for vertical sync and the rest is left to the image. In this mode the frames are interleaved - ShNI: this is the same as above but with non interleaved frames. - - HSkip: this is the horizontal sync skip technique for vertical synchronization. This has been in use in the first TV experiments with a small number of lines. This method just skips one horizontal synchronization pluse to mark the last or the first line (here it is the last). This method does not use any full line for vertical sync and all lines can be used for the image thus it suits the modes with a small number of lines. With more lines however the risk of missing pulses gets higher in adverse conditions because the pulses get shorter and may get swallowed by a stray pulse or a stray pulse can be taken for a valid one. In this case two images might get out of sync instead of just two lines. In practice this is suitable up to 90~120 lines. + - HSkip: this is the horizontal sync skip technique for vertical synchronization. This has been in use in the first TV experiments with a small number of lines. This method just skips one horizontal synchronization pulse to mark the last or the first line (here it is the last). This method does not use any full line for vertical sync and all lines can be used for the image thus it suits the modes with a small number of lines. With more lines however the risk of missing pulses gets higher in adverse conditions because the pulses get shorter and may get swallowed by a stray pulse or a stray pulse can be taken for a valid one. In this case two images might get out of sync instead of just two lines. In practice this is suitable up to 90~120 lines. ☞ Interleaved mode requires an odd number of lines because the system recognizes the even and odd frames depending on a odd or even number of lines respectively for the half images -☞ For non interlaved mode all standards are supposed to work for any number of lines. You may experiment with any and see if it fits your purpose. However it will be easier to obtain good or optimal results in general with the following recommendations: +☞ For non interleaved mode all standards are supposed to work for any number of lines. You may experiment with any and see if it fits your purpose. However it will be easier to obtain good or optimal results in general with the following recommendations:
@@ -234,7 +234,7 @@ This is the video file play length in time units

12. Video file position slider

-This slider can be used to randomly set the currennt position in the file when file play is in pause state (button 16). When video plays the slider moves according to the current position. +This slider can be used to randomly set the current position in the file when file play is in pause state (button 16). When video plays the slider moves according to the current position.

23. Play/Pause camera

@@ -264,4 +264,4 @@ Use this button to switch between system camera FPS (off) and manual camera FPS

19. Manual camera FPS adjust

-Use this dial button to adjust camera FPS manually between 2 and 30 FPS in 0.1 FPS steps. The manual FPS value appears on the right of the button. \ No newline at end of file +Use this dial button to adjust camera FPS manually between 2 and 30 FPS in 0.1 FPS steps. The manual FPS value appears on the right of the button. diff --git a/plugins/channeltx/modnfm/CMakeLists.txt b/plugins/channeltx/modnfm/CMakeLists.txt index f7060fcd0..5355274b6 100644 --- a/plugins/channeltx/modnfm/CMakeLists.txt +++ b/plugins/channeltx/modnfm/CMakeLists.txt @@ -45,6 +45,6 @@ target_link_libraries(modnfm swagger ) -qt5_use_modules(modnfm Core Widgets) +target_link_libraries(modnfm Qt5::Core Qt5::Widgets) -install(TARGETS modnfm DESTINATION lib/plugins/channeltx) \ No newline at end of file +install(TARGETS modnfm DESTINATION lib/plugins/channeltx) diff --git a/plugins/channeltx/modnfm/modnfm.pro b/plugins/channeltx/modnfm/modnfm.pro index 073b23c34..ec9622875 100644 --- a/plugins/channeltx/modnfm/modnfm.pro +++ b/plugins/channeltx/modnfm/modnfm.pro @@ -18,6 +18,7 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client diff --git a/plugins/channeltx/modnfm/nfmmod.cpp b/plugins/channeltx/modnfm/nfmmod.cpp index 4a3c9d825..894d4ffb5 100644 --- a/plugins/channeltx/modnfm/nfmmod.cpp +++ b/plugins/channeltx/modnfm/nfmmod.cpp @@ -20,6 +20,8 @@ #include "SWGChannelSettings.h" #include "SWGCWKeyerSettings.h" +#include "SWGChannelReport.h" +#include "SWGNFMModReport.h" #include #include @@ -30,6 +32,7 @@ #include "dsp/dspcommands.h" #include "device/devicesinkapi.h" #include "dsp/threadedbasebandsamplesource.h" +#include "util/db.h" #include "nfmmod.h" @@ -68,27 +71,30 @@ NFMMod::NFMMod(DeviceSinkAPI *deviceAPI) : m_magsq = 0.0; - m_toneNco.setFreq(1000.0, m_settings.m_audioSampleRate); - m_ctcssNco.setFreq(88.5, m_settings.m_audioSampleRate); - DSPEngine::instance()->addAudioSource(&m_audioFifo); + DSPEngine::instance()->getAudioDeviceManager()->addAudioSource(&m_audioFifo, getInputMessageQueue()); + m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getInputSampleRate(); + + m_lowpass.create(301, m_audioSampleRate, 250.0); + m_toneNco.setFreq(1000.0, m_audioSampleRate); + m_ctcssNco.setFreq(88.5, m_audioSampleRate); // CW keyer - m_cwKeyer.setSampleRate(m_settings.m_audioSampleRate); + m_cwKeyer.setSampleRate(m_audioSampleRate); m_cwKeyer.setWPM(13); m_cwKeyer.setMode(CWKeyerSettings::CWNone); + applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); + m_channelizer = new UpChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this); m_deviceAPI->addThreadedSource(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - - applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true); - applySettings(m_settings, true); } NFMMod::~NFMMod() { - DSPEngine::instance()->removeAudioSource(&m_audioFifo); + DSPEngine::instance()->getAudioDeviceManager()->removeAudioSource(&m_audioFifo); m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSource(m_threadedChannelizer); delete m_threadedChannelizer; @@ -142,14 +148,14 @@ void NFMMod::pull(Sample& sample) void NFMMod::pullAudio(int nbSamples) { - unsigned int nbSamplesAudio = nbSamples * ((Real) m_settings.m_audioSampleRate / (Real) m_basebandSampleRate); + unsigned int nbSamplesAudio = nbSamples * ((Real) m_audioSampleRate / (Real) m_basebandSampleRate); if (nbSamplesAudio > m_audioBuffer.size()) { m_audioBuffer.resize(nbSamplesAudio); } - m_audioFifo.read(reinterpret_cast(&m_audioBuffer[0]), nbSamplesAudio, 10); + m_audioFifo.read(reinterpret_cast(&m_audioBuffer[0]), nbSamplesAudio); m_audioBufferFill = 0; } @@ -163,12 +169,12 @@ void NFMMod::modulateSample() if (m_settings.m_ctcssOn) { - m_modPhasor += (m_settings.m_fmDeviation / (float) m_settings.m_audioSampleRate) * (0.85f * m_bandpass.filter(t) + 0.15f * 378.0f * m_ctcssNco.next()) * (M_PI / 378.0f); + m_modPhasor += (m_settings.m_fmDeviation / (float) m_audioSampleRate) * (0.85f * m_bandpass.filter(t) + 0.15f * 378.0f * m_ctcssNco.next()) * (M_PI / 378.0f); } else { // 378 = 302 * 1.25; 302 = number of filter taps (established experimentally) - m_modPhasor += (m_settings.m_fmDeviation / (float) m_settings.m_audioSampleRate) * m_bandpass.filter(t) * (M_PI / 378.0f); + m_modPhasor += (m_settings.m_fmDeviation / (float) m_audioSampleRate) * m_bandpass.filter(t) * (M_PI / 378.0f); } m_modSample.real(cos(m_modPhasor) * 0.891235351562f * SDR_TX_SCALEF); // -1 dB @@ -179,10 +185,10 @@ void NFMMod::pullAF(Real& sample) { switch (m_settings.m_modAFInput) { - case NFMModInputTone: + case NFMModSettings::NFMModInputTone: sample = m_toneNco.next(); break; - case NFMModInputFile: + case NFMModSettings::NFMModInputFile: // sox f4exb_call.wav --encoding float --endian little f4exb_call.raw // ffplay -f f32le -ar 48k -ac 1 f4exb_call.raw if (m_ifstream.is_open()) @@ -211,10 +217,10 @@ void NFMMod::pullAF(Real& sample) sample = 0.0f; } break; - case NFMModInputAudio: + case NFMModSettings::NFMModInputAudio: sample = ((m_audioBuffer[m_audioBufferFill].l + m_audioBuffer[m_audioBufferFill].r) / 65536.0f) * m_settings.m_volumeFactor; break; - case NFMModInputCWTone: + case NFMModSettings::NFMModInputCWTone: Real fadeFactor; if (m_cwKeyer.getSample()) @@ -235,7 +241,7 @@ void NFMMod::pullAF(Real& sample) } } break; - case NFMModInputNone: + case NFMModSettings::NFMModInputNone: default: sample = 0.0f; break; @@ -342,6 +348,20 @@ bool NFMMod::handleMessage(const Message& cmd) return true; } + else if (DSPConfigureAudio::match(cmd)) + { + DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd; + uint32_t sampleRate = cfg.getSampleRate(); + + qDebug() << "NFMMod::handleMessage: DSPConfigureAudio:" + << " sampleRate: " << sampleRate; + + if (sampleRate != m_audioSampleRate) { + applyAudioSampleRate(sampleRate); + } + + return true; + } else if (DSPSignalNotification::match(cmd)) { return true; @@ -387,6 +407,31 @@ void NFMMod::seekFileStream(int seekPercentage) } } +void NFMMod::applyAudioSampleRate(int sampleRate) +{ + qDebug("NFMMod::applyAudioSampleRate: %d", sampleRate); + + MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( + sampleRate, m_settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(channelConfigMsg); + + m_settingsMutex.lock(); + + m_interpolatorDistanceRemain = 0; + m_interpolatorConsumed = false; + m_interpolatorDistance = (Real) sampleRate / (Real) m_outputSampleRate; + m_interpolator.create(48, sampleRate, m_settings.m_rfBandwidth / 2.2, 3.0); + m_lowpass.create(301, sampleRate, 250.0); + m_bandpass.create(301, sampleRate, 300.0, m_settings.m_afBandwidth); + m_toneNco.setFreq(m_settings.m_toneFrequency, sampleRate); + m_ctcssNco.setFreq(NFMModSettings::getCTCSSFreq(m_settings.m_ctcssIndex), sampleRate); + m_cwKeyer.setSampleRate(sampleRate); + + m_settingsMutex.unlock(); + + m_audioSampleRate = sampleRate; +} + void NFMMod::applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force) { qDebug() << "NFMMod::applyChannelSettings:" @@ -407,8 +452,8 @@ void NFMMod::applyChannelSettings(int basebandSampleRate, int outputSampleRate, m_settingsMutex.lock(); m_interpolatorDistanceRemain = 0; m_interpolatorConsumed = false; - m_interpolatorDistance = (Real) m_settings.m_audioSampleRate / (Real) outputSampleRate; - m_interpolator.create(48, m_settings.m_audioSampleRate, m_settings.m_rfBandwidth / 2.2, 3.0); + m_interpolatorDistance = (Real) m_audioSampleRate / (Real) outputSampleRate; + m_interpolator.create(48, m_audioSampleRate, m_settings.m_rfBandwidth / 2.2, 3.0); m_settingsMutex.unlock(); } @@ -431,49 +476,53 @@ void NFMMod::applySettings(const NFMModSettings& settings, bool force) << " m_channelMute: " << settings.m_channelMute << " m_playLoop: " << settings.m_playLoop << " m_modAFInout " << settings.m_modAFInput + << " m_audioDeviceName: " << settings.m_audioDeviceName << " force: " << force; - if((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || - (settings.m_audioSampleRate != m_settings.m_audioSampleRate) || force) + if((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) { m_settingsMutex.lock(); m_interpolatorDistanceRemain = 0; m_interpolatorConsumed = false; - m_interpolatorDistance = (Real) settings.m_audioSampleRate / (Real) m_outputSampleRate; - m_interpolator.create(48, settings.m_audioSampleRate, settings.m_rfBandwidth / 2.2, 3.0); + m_interpolatorDistance = (Real) m_audioSampleRate / (Real) m_outputSampleRate; + m_interpolator.create(48, m_audioSampleRate, settings.m_rfBandwidth / 2.2, 3.0); m_settingsMutex.unlock(); } - if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || - (settings.m_audioSampleRate != m_settings.m_audioSampleRate) || force) + if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || force) { m_settingsMutex.lock(); - m_lowpass.create(301, settings.m_audioSampleRate, 250.0); - m_bandpass.create(301, settings.m_audioSampleRate, 300.0, settings.m_afBandwidth); + m_lowpass.create(301, m_audioSampleRate, 250.0); + m_bandpass.create(301, m_audioSampleRate, 300.0, settings.m_afBandwidth); m_settingsMutex.unlock(); } - if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || - (settings.m_audioSampleRate != m_settings.m_audioSampleRate) || force) + if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || force) { m_settingsMutex.lock(); - m_toneNco.setFreq(settings.m_toneFrequency, settings.m_audioSampleRate); + m_toneNco.setFreq(settings.m_toneFrequency, m_audioSampleRate); m_settingsMutex.unlock(); } - if ((settings.m_audioSampleRate != m_settings.m_audioSampleRate) || force) - { - m_cwKeyer.setSampleRate(settings.m_audioSampleRate); - } - - if ((settings.m_ctcssIndex != m_settings.m_ctcssIndex) || - (settings.m_audioSampleRate != m_settings.m_audioSampleRate) || force) + if ((settings.m_ctcssIndex != m_settings.m_ctcssIndex) || force) { m_settingsMutex.lock(); - m_ctcssNco.setFreq(NFMModSettings::getCTCSSFreq(settings.m_ctcssIndex), settings.m_audioSampleRate); + m_ctcssNco.setFreq(NFMModSettings::getCTCSSFreq(settings.m_ctcssIndex), m_audioSampleRate); m_settingsMutex.unlock(); } + if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) + { + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + int audioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_audioDeviceName); + audioDeviceManager->addAudioSource(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex); + uint32_t audioSampleRate = audioDeviceManager->getInputSampleRate(audioDeviceIndex); + + if (m_audioSampleRate != audioSampleRate) { + applyAudioSampleRate(audioSampleRate); + } + } + m_settings = settings; } @@ -518,7 +567,7 @@ int NFMMod::webapiSettingsPutPatch( SWGSDRangel::SWGChannelSettings& response, QString& errorMessage __attribute__((unused))) { - NFMModSettings settings; + NFMModSettings settings = m_settings; bool frequencyOffsetChanged = false; // for (int i = 0; i < channelSettingsKeys.size(); i++) { @@ -528,9 +577,6 @@ int NFMMod::webapiSettingsPutPatch( if (channelSettingsKeys.contains("afBandwidth")) { settings.m_afBandwidth = response.getNfmModSettings()->getAfBandwidth(); } - if (channelSettingsKeys.contains("audioSampleRate")) { - settings.m_audioSampleRate = response.getNfmModSettings()->getAudioSampleRate(); - } if (channelSettingsKeys.contains("channelMute")) { settings.m_channelMute = response.getNfmModSettings()->getChannelMute() != 0; } @@ -607,7 +653,7 @@ int NFMMod::webapiSettingsPutPatch( if (frequencyOffsetChanged) { NFMMod::MsgConfigureChannelizer *msgChan = NFMMod::MsgConfigureChannelizer::create( - 48000, settings.m_inputFrequencyOffset); + m_audioSampleRate, settings.m_inputFrequencyOffset); m_inputMessageQueue.push(msgChan); } @@ -625,10 +671,19 @@ int NFMMod::webapiSettingsPutPatch( return 200; } +int NFMMod::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setNfmModReport(new SWGSDRangel::SWGNFMModReport()); + response.getNfmModReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + void NFMMod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const NFMModSettings& settings) { response.getNfmModSettings()->setAfBandwidth(settings.m_afBandwidth); - response.getNfmModSettings()->setAudioSampleRate(settings.m_audioSampleRate); response.getNfmModSettings()->setChannelMute(settings.m_channelMute ? 1 : 0); response.getNfmModSettings()->setCtcssIndex(settings.m_ctcssIndex); response.getNfmModSettings()->setCtcssOn(settings.m_ctcssOn ? 1 : 0); @@ -664,5 +719,18 @@ void NFMMod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& respon apiCwKeyerSettings->setText(new QString(cwKeyerSettings.m_text)); } + if (response.getNfmModSettings()->getAudioDeviceName()) { + *response.getNfmModSettings()->getAudioDeviceName() = settings.m_audioDeviceName; + } else { + response.getNfmModSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName)); + } + apiCwKeyerSettings->setWpm(cwKeyerSettings.m_wpm); } + +void NFMMod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + response.getNfmModReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq())); + response.getNfmModReport()->setAudioSampleRate(m_audioSampleRate); + response.getNfmModReport()->setChannelSampleRate(m_outputSampleRate); +} diff --git a/plugins/channeltx/modnfm/nfmmod.h b/plugins/channeltx/modnfm/nfmmod.h index 1a7c84039..8a42d7a8b 100644 --- a/plugins/channeltx/modnfm/nfmmod.h +++ b/plugins/channeltx/modnfm/nfmmod.h @@ -91,15 +91,6 @@ public: { } }; - typedef enum - { - NFMModInputNone, - NFMModInputTone, - NFMModInputFile, - NFMModInputAudio, - NFMModInputCWTone - } NFMModInputAF; - class MsgConfigureFileSourceName : public Message { MESSAGE_CLASS_DECLARATION @@ -219,8 +210,6 @@ public: virtual void getIdentifier(QString& id) { id = objectName(); } virtual void getTitle(QString& title) { title = m_settings.m_title; } - virtual void setName(const QString& name) { setObjectName(name); } - virtual QString getName() const { return objectName(); } virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; } virtual QByteArray serialize() const; @@ -236,6 +225,10 @@ public: SWGSDRangel::SWGChannelSettings& response, QString& errorMessage); + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + double getMagSq() const { return m_magsq; } CWKeyer *getCWKeyer() { return &m_cwKeyer; } @@ -267,6 +260,7 @@ private: int m_outputSampleRate; int m_inputFrequencyOffset; NFMModSettings m_settings; + quint32 m_audioSampleRate; NCO m_carrierNco; NCOF m_toneNco; @@ -296,13 +290,14 @@ private: quint32 m_recordLength; //!< record length in seconds computed from file size int m_sampleRate; - NFMModInputAF m_afInput; + NFMModSettings::NFMModInputAF m_afInput; quint32 m_levelCalcCount; Real m_peakLevel; Real m_levelSum; CWKeyer m_cwKeyer; static const int m_levelNbSamples; + void applyAudioSampleRate(int sampleRate); void applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force = false); void applySettings(const NFMModSettings& settings, bool force = false); void pullAF(Real& sample); @@ -311,6 +306,7 @@ private: void openFileStream(); void seekFileStream(int seekPercentage); void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const NFMModSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); }; diff --git a/plugins/channeltx/modnfm/nfmmodgui.cpp b/plugins/channeltx/modnfm/nfmmodgui.cpp index 7d4553a67..6db1738a8 100644 --- a/plugins/channeltx/modnfm/nfmmodgui.cpp +++ b/plugins/channeltx/modnfm/nfmmodgui.cpp @@ -26,6 +26,9 @@ #include "util/simpleserializer.h" #include "util/db.h" #include "dsp/dspengine.h" +#include "gui/crightclickenabler.h" +#include "gui/audioselectdialog.h" +#include "gui/basicchannelsettingsdialog.h" #include "mainwindow.h" #include "ui_nfmmodgui.h" @@ -252,7 +255,7 @@ void NFMModGUI::on_navTimeSlider_valueChanged(int value) void NFMModGUI::on_showFileDialog_clicked(bool checked __attribute__((unused))) { QString fileName = QFileDialog::getOpenFileName(this, - tr("Open raw audio file"), ".", tr("Raw audio Files (*.raw)")); + tr("Open raw audio file"), ".", tr("Raw audio Files (*.raw)"), 0, QFileDialog::DontUseNativeDialog); if (fileName != "") { @@ -287,6 +290,22 @@ void NFMModGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rol { } +void NFMModGUI::onMenuDialogCalled(const QPoint &p) +{ + BasicChannelSettingsDialog dialog(&m_channelMarker, this); + dialog.move(p); + dialog.exec(); + + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); + m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); + m_settings.m_title = m_channelMarker.getTitle(); + + setWindowTitle(m_settings.m_title); + setTitleColor(m_settings.m_rgbColor); + + applySettings(); +} + NFMModGUI::NFMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent) : RollupWidget(parent), ui(new Ui::NFMModGUI), @@ -314,12 +333,16 @@ NFMModGUI::NFMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam blockApplySettings(false); connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); m_nfmMod = (NFMMod*) channelTx; //new NFMMod(m_deviceUISet->m_deviceSinkAPI); m_nfmMod->setMessageQueueToGUI(getInputMessageQueue()); connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); + CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->mic); + connect(audioMuteRightClickEnabler, SIGNAL(rightClick()), this, SLOT(audioSelect())); + ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999); @@ -329,8 +352,6 @@ NFMModGUI::NFMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam m_channelMarker.setBandwidth(12500); m_channelMarker.setCenterFrequency(0); m_channelMarker.setTitle("NFM Modulator"); - m_channelMarker.setUDPAddress("127.0.0.1"); - m_channelMarker.setUDPSendPort(9999); m_channelMarker.blockSignals(false); m_channelMarker.setVisible(true); // activate signal on the last setting only @@ -391,6 +412,7 @@ void NFMModGUI::displaySettings() { m_channelMarker.blockSignals(true); m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset); + m_channelMarker.setTitle(m_settings.m_title); m_channelMarker.setBandwidth(m_settings.m_rfBandwidth); m_channelMarker.blockSignals(false); m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only @@ -446,6 +468,19 @@ void NFMModGUI::enterEvent(QEvent*) m_channelMarker.setHighlighted(true); } +void NFMModGUI::audioSelect() +{ + qDebug("NFMModGUI::audioSelect"); + AudioSelectDialog audioSelect(DSPEngine::instance()->getAudioDeviceManager(), m_settings.m_audioDeviceName, true); // true for input + audioSelect.exec(); + + if (audioSelect.m_selected) + { + m_settings.m_audioDeviceName = audioSelect.m_audioDeviceName; + applySettings(); + } +} + void NFMModGUI::tick() { double powDb = CalcDb::dbPower(m_nfmMod->getMagSq()); @@ -463,7 +498,7 @@ void NFMModGUI::updateWithStreamData() { QTime recordLength(0, 0, 0, 0); recordLength = recordLength.addSecs(m_recordLength); - QString s_time = recordLength.toString("hh:mm:ss"); + QString s_time = recordLength.toString("HH:mm:ss"); ui->recordLengthText->setText(s_time); updateWithStreamTime(); } @@ -482,8 +517,8 @@ void NFMModGUI::updateWithStreamTime() QTime t(0, 0, 0, 0); t = t.addSecs(t_sec); t = t.addMSecs(t_msec); - QString s_timems = t.toString("hh:mm:ss.zzz"); - QString s_time = t.toString("hh:mm:ss"); + QString s_timems = t.toString("HH:mm:ss.zzz"); + QString s_time = t.toString("HH:mm:ss"); ui->relTimeText->setText(s_timems); if (!m_enableNavTime) diff --git a/plugins/channeltx/modnfm/nfmmodgui.h b/plugins/channeltx/modnfm/nfmmodgui.h index d07a7b1a0..0e4a45ba7 100644 --- a/plugins/channeltx/modnfm/nfmmodgui.h +++ b/plugins/channeltx/modnfm/nfmmodgui.h @@ -72,7 +72,7 @@ private: int m_samplesCount; std::size_t m_tickCount; bool m_enableNavTime; - NFMMod::NFMModInputAF m_modAFInput; + NFMModSettings::NFMModInputAF m_modAFInput; MessageQueue m_inputMessageQueue; explicit NFMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent = 0); @@ -110,8 +110,10 @@ private slots: void on_ctcssOn_toggled(bool checked); void onWidgetRolled(QWidget* widget, bool rollDown); + void onMenuDialogCalled(const QPoint& p); void configureFileName(); + void audioSelect(); void tick(); }; diff --git a/plugins/channeltx/modnfm/nfmmodgui.ui b/plugins/channeltx/modnfm/nfmmodgui.ui index 3805fd725..d6c935ba3 100644 --- a/plugins/channeltx/modnfm/nfmmodgui.ui +++ b/plugins/channeltx/modnfm/nfmmodgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -56,16 +56,7 @@ 3 - - 2 - - - 2 - - - 2 - - + 2 @@ -104,7 +95,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -398,6 +389,12 @@ 0 + + + Liberation Mono + 8 + + Level (% full range) top trace: average, bottom trace: instantaneous peak, tip: peak hold diff --git a/plugins/channeltx/modnfm/nfmmodplugin.cpp b/plugins/channeltx/modnfm/nfmmodplugin.cpp index d020c3594..8f231d52c 100644 --- a/plugins/channeltx/modnfm/nfmmodplugin.cpp +++ b/plugins/channeltx/modnfm/nfmmodplugin.cpp @@ -15,15 +15,17 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include #include "plugin/pluginapi.h" +#ifndef SERVER_MODE #include "nfmmodgui.h" +#endif +#include "nfmmod.h" #include "nfmmodplugin.h" const PluginDescriptor NFMModPlugin::m_pluginDescriptor = { QString("NFM Modulator"), - QString("3.12.0"), + QString("4.1.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channeltx/modnfm/nfmmodsettings.cpp b/plugins/channeltx/modnfm/nfmmodsettings.cpp index a35e7158b..874c11a6e 100644 --- a/plugins/channeltx/modnfm/nfmmodsettings.cpp +++ b/plugins/channeltx/modnfm/nfmmodsettings.cpp @@ -50,7 +50,6 @@ void NFMModSettings::resetToDefaults() m_rfBandwidth = 12500.0f; m_fmDeviation = 5000.0f; m_toneFrequency = 1000.0f; - m_audioSampleRate = DSPEngine::instance()->getAudioSampleRate(); m_volumeFactor = 1.0f; m_channelMute = false; m_playLoop = false; @@ -59,6 +58,7 @@ void NFMModSettings::resetToDefaults() m_rgbColor = QColor(255, 0, 0).rgb(); m_title = "NFM Modulator"; m_modAFInput = NFMModInputAF::NFMModInputNone; + m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; } QByteArray NFMModSettings::serialize() const @@ -85,6 +85,7 @@ QByteArray NFMModSettings::serialize() const s.writeS32(10, m_ctcssIndex); s.writeString(12, m_title); s.writeS32(13, (int) m_modAFInput); + s.writeString(14, m_audioDeviceName); return s.final(); } @@ -135,6 +136,8 @@ bool NFMModSettings::deserialize(const QByteArray& data) m_modAFInput = (NFMModInputAF) tmp; } + d.readString(14, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName); + return true; } else diff --git a/plugins/channeltx/modnfm/nfmmodsettings.h b/plugins/channeltx/modnfm/nfmmodsettings.h index 889267b1d..83f264be8 100644 --- a/plugins/channeltx/modnfm/nfmmodsettings.h +++ b/plugins/channeltx/modnfm/nfmmodsettings.h @@ -43,7 +43,6 @@ struct NFMModSettings float m_fmDeviation; float m_toneFrequency; float m_volumeFactor; - quint32 m_audioSampleRate; bool m_channelMute; bool m_playLoop; bool m_ctcssOn; @@ -51,6 +50,7 @@ struct NFMModSettings quint32 m_rgbColor; QString m_title; NFMModInputAF m_modAFInput; + QString m_audioDeviceName; Serializable *m_channelMarker; Serializable *m_cwKeyerGUI; diff --git a/plugins/channeltx/modnfm/readme.md b/plugins/channeltx/modnfm/readme.md index 6e3f7b3a6..8e6395abb 100644 --- a/plugins/channeltx/modnfm/readme.md +++ b/plugins/channeltx/modnfm/readme.md @@ -8,9 +8,9 @@ This plugin can be used to generate a narrowband frequency modulated signal. "Na ![NFM Modulator plugin GUI](../../../doc/img/NFMMod_plugin.png) -

1: Frequency shift from center frequency of transmission/h3> +

1: Frequency shift from center frequency of transmission

-Use the wheels to adjust the frequency shift in Hz from the center frequency of transmission. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the frequency shift in Hz from the center frequency of transmission. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

3: Channel power

@@ -42,7 +42,7 @@ This is the volume of the audio signal from 0.0 (mute) to 2.0 (maximum). It can - bottom bar (brown): instantaneous peak value - tip vertical bar (bright red): peak hold value -You should aim at keepimg the peak value below 100% using the volume control +You should aim at keeping the peak value below 100% using the volume control

10: Input source control

@@ -60,9 +60,11 @@ Switches to the Morse keyer input. You must switch it off to make other inputs a Adjusts the tone frequency from 0.1 to 2.5 kHz in 0.01 kHz steps -

10.4: Audio input select

+

10.4: Audio input select and select audio input device

-Switches to the audio input. You must switch it off to make other inputs available. +Left click to switch to the audio input. You must switch it off to make other inputs available. + +Right click to select audio input device. See [audio management documentation](../../../sdrgui/audio.md) for details.

11: CTCSS switch

@@ -143,4 +145,4 @@ This is the audio file play length in time units

21: Play file position slider

-This slider can be used to randomly set the currennt position in the file when file play is in pause state (button 18.3) \ No newline at end of file +This slider can be used to randomly set the current position in the file when file play is in pause state (button 18.3) diff --git a/plugins/channeltx/modssb/CMakeLists.txt b/plugins/channeltx/modssb/CMakeLists.txt index fc652b3a4..cfb683961 100644 --- a/plugins/channeltx/modssb/CMakeLists.txt +++ b/plugins/channeltx/modssb/CMakeLists.txt @@ -1,5 +1,7 @@ project(modssb) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + set(modssb_SOURCES ssbmod.cpp ssbmodgui.cpp @@ -21,6 +23,7 @@ set(modssb_FORMS include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) add_definitions(${QT_DEFINITIONS}) @@ -39,8 +42,9 @@ target_link_libraries(modssb ${QT_LIBRARIES} sdrbase sdrgui + swagger ) -qt5_use_modules(modssb Core Widgets) +target_link_libraries(modssb Qt5::Core Qt5::Widgets) -install(TARGETS modssb DESTINATION lib/plugins/channeltx) \ No newline at end of file +install(TARGETS modssb DESTINATION lib/plugins/channeltx) diff --git a/plugins/channeltx/modssb/modssb.pro b/plugins/channeltx/modssb/modssb.pro index 5bc58f2c4..460fc9dc9 100644 --- a/plugins/channeltx/modssb/modssb.pro +++ b/plugins/channeltx/modssb/modssb.pro @@ -18,8 +18,10 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui +INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug @@ -38,5 +40,6 @@ FORMS += ssbmodgui.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui +LIBS += -L../../../swagger/$${build_subdir} -lswagger RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/plugins/channeltx/modssb/readme.md b/plugins/channeltx/modssb/readme.md index d1f246133..bdced14a2 100644 --- a/plugins/channeltx/modssb/readme.md +++ b/plugins/channeltx/modssb/readme.md @@ -14,7 +14,7 @@ This plugin can be used to generate a single sideband or double sidebands modula

1: Frequency shift from center frequency of transmission

-Use the wheels to adjust the frequency shift in Hz from the center frequency of transmission. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the frequency shift in Hz from the center frequency of transmission. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

2: Channel power

@@ -53,7 +53,7 @@ The span value display is set as follows depending on the SSB or DSB mode: - In SSB mode: the span goes from zero to the upper (USB: positive frequencies) or lower (LSB: negative frequencies) limit and the absolute value of the limit is displayed. - In DSB mode: the span goes from the lower to the upper limit of same absolute value and ± the absolute value of the limit is displayed. -This is how the Span (7) and bandpass (8, 9) fitler controls look like in the 3 possible modes: +This is how the Span (7) and bandpass (8, 9) filter controls look like in the 3 possible modes: **DSB**: @@ -92,7 +92,7 @@ Values are expressed in kHz and step is 100 Hz. Values are expressed in kHz and step is 100 Hz. - - In SSB mode this is the lower cutoff (USB: positive frequencies) or higher cutoff (LSB: negative frequencies) of the in channel signe side band bandpass filter. + - In SSB mode this is the lower cutoff (USB: positive frequencies) or higher cutoff (LSB: negative frequencies) of the in channel single side band bandpass filter. - In DSB mode it is inactive and set to zero (double side band filter).

10: Volume

@@ -105,7 +105,7 @@ This is the volume of the audio signal from 0.0 (mute) to 2.0 (maximum). It can - bottom bar (brown): instantaneous peak value - tip vertical bar (bright red): peak hold value -You should aim at keepimg the peak value below 100% using the volume control +You should aim at keeping the peak value below 100% using the volume control

12: Audio compression

@@ -125,7 +125,7 @@ Use this button to toggle audio compressor on and off. In "on" mode the button i

12.2: AGC magnitude order

-This is the ratio to maximum signal magnitude aimed by the AGC. The higher the stronger is the compression but the signal will have more chances to get clamped and therefore will get more severly distorted. +This is the ratio to maximum signal magnitude aimed by the AGC. The higher the stronger is the compression but the signal will have more chances to get clamped and therefore will get more severely distorted. The default value is 0.2 which is rather mild. For normal voice you should not exceed 0.4 however the criteria is rather subjective. It is flexible enough to be tuned between 0 and 1 in 0.01 increments. @@ -137,7 +137,7 @@ The default value is 200 ms which is relatively "soft". Most practically useful

12.4: Power threshold

-in order to avoid small signals due to background noise or power wiggle to enter the system and raise to normal voice level a power based squelch is in place. This control allows to select a threshold in dB above which a signal will open the squalch if it lasts longer than the squelch gate (2.5). Default is -40 dB. +in order to avoid small signals due to background noise or power wiggle to enter the system and raise to normal voice level a power based squelch is in place. This control allows to select a threshold in dB above which a signal will open the squelch if it lasts longer than the squelch gate (2.5). Default is -40 dB.

12.5: Squelch gate

@@ -163,9 +163,11 @@ Switches to the Morse keyer input. You must switch it off to make other inputs a Adjusts the tone frequency from 0.1 to 2.5 kHz in 0.01 kHz steps -

13.4: Audio input select

+

13.4: Audio input select and select audio input device

-Switches to the audio input. You must switch it off to make other inputs available. +Left click to switch to the audio input. You must switch it off to make other inputs available. + +Right click to select audio input device. See [audio management documentation](../../../sdrgui/audio.md) for details.

14: CW (Morse) text

@@ -238,8 +240,8 @@ This is the audio file play length in time units

21: Play file position slider

-This slider can be used to randomly set the currennt position in the file when file play is in pause state (button 17.3) +This slider can be used to randomly set the current position in the file when file play is in pause state (button 17.3)

22: Channel spectrum display

-This is the channel spectrum display. Controls at the bottom of the panel are the same as with the central spectrum display. \ No newline at end of file +This is the channel spectrum display. Controls at the bottom of the panel are the same as with the central spectrum display. diff --git a/plugins/channeltx/modssb/ssbmod.cpp b/plugins/channeltx/modssb/ssbmod.cpp index ae997cd0f..2efda0df5 100644 --- a/plugins/channeltx/modssb/ssbmod.cpp +++ b/plugins/channeltx/modssb/ssbmod.cpp @@ -22,6 +22,11 @@ #include #include +#include + +#include "SWGChannelSettings.h" +#include "SWGChannelReport.h" +#include "SWGSSBModReport.h" #include "dsp/upchannelizer.h" #include "dsp/dspengine.h" @@ -34,7 +39,6 @@ MESSAGE_CLASS_DEFINITION(SSBMod::MsgConfigureSSBMod, Message) MESSAGE_CLASS_DEFINITION(SSBMod::MsgConfigureChannelizer, Message) MESSAGE_CLASS_DEFINITION(SSBMod::MsgConfigureFileSourceName, Message) MESSAGE_CLASS_DEFINITION(SSBMod::MsgConfigureFileSourceSeek, Message) -MESSAGE_CLASS_DEFINITION(SSBMod::MsgConfigureAFInput, Message) MESSAGE_CLASS_DEFINITION(SSBMod::MsgConfigureFileSourceStreamTiming, Message) MESSAGE_CLASS_DEFINITION(SSBMod::MsgReportFileSourceStreamData, Message) MESSAGE_CLASS_DEFINITION(SSBMod::MsgReportFileSourceStreamTiming, Message) @@ -62,20 +66,25 @@ SSBMod::SSBMod(DeviceSinkAPI *deviceAPI) : m_fileSize(0), m_recordLength(0), m_sampleRate(48000), - m_afInput(SSBModInputNone), m_levelCalcCount(0), m_peakLevel(0.0f), m_levelSum(0.0f), - m_inAGC(9600, 0.2, 1e-4) + m_inAGC(9600, 0.2, 1e-4), + m_agcStepLength(2400) { setObjectName(m_channelId); - m_SSBFilter = new fftfilt(m_settings.m_lowCutoff / m_settings.m_audioSampleRate, m_settings.m_bandwidth / m_settings.m_audioSampleRate, m_ssbFftLen); - m_DSBFilter = new fftfilt((2.0f * m_settings.m_bandwidth) / m_settings.m_audioSampleRate, 2 * m_ssbFftLen); + DSPEngine::instance()->getAudioDeviceManager()->addAudioSource(&m_audioFifo, getInputMessageQueue()); + m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getInputSampleRate(); + + m_SSBFilter = new fftfilt(m_settings.m_lowCutoff / m_audioSampleRate, m_settings.m_bandwidth / m_audioSampleRate, m_ssbFftLen); + m_DSBFilter = new fftfilt((2.0f * m_settings.m_bandwidth) / m_audioSampleRate, 2 * m_ssbFftLen); m_SSBFilterBuffer = new Complex[m_ssbFftLen>>1]; // filter returns data exactly half of its size m_DSBFilterBuffer = new Complex[m_ssbFftLen]; - memset(m_SSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen>>1)); - memset(m_DSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen)); + std::fill(m_SSBFilterBuffer, m_SSBFilterBuffer+(m_ssbFftLen>>1), Complex{0,0}); + std::fill(m_DSBFilterBuffer, m_DSBFilterBuffer+m_ssbFftLen, Complex{0,0}); +// memset(m_SSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen>>1)); +// memset(m_DSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen)); m_audioBuffer.resize(1<<14); m_audioBufferFill = 0; @@ -87,11 +96,10 @@ SSBMod::SSBMod(DeviceSinkAPI *deviceAPI) : m_magsq = 0.0; - m_toneNco.setFreq(1000.0, m_settings.m_audioSampleRate); - DSPEngine::instance()->addAudioSource(&m_audioFifo); + m_toneNco.setFreq(1000.0, m_audioSampleRate); // CW keyer - m_cwKeyer.setSampleRate(m_settings.m_audioSampleRate); + m_cwKeyer.setSampleRate(48000); m_cwKeyer.setWPM(13); m_cwKeyer.setMode(CWKeyerSettings::CWNone); @@ -99,39 +107,28 @@ SSBMod::SSBMod(DeviceSinkAPI *deviceAPI) : m_inAGC.setStepDownDelay(m_settings.m_agcThresholdDelay); m_inAGC.setClamping(true); + applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); + m_channelizer = new UpChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this); m_deviceAPI->addThreadedSource(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - - applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true); - applySettings(m_settings, true); } SSBMod::~SSBMod() { - if (m_SSBFilter) { - delete m_SSBFilter; - } - - if (m_DSBFilter) { - delete m_DSBFilter; - } - - if (m_SSBFilterBuffer) { - delete m_SSBFilterBuffer; - } - - if (m_DSBFilterBuffer) { - delete m_DSBFilterBuffer; - } - - DSPEngine::instance()->removeAudioSource(&m_audioFifo); + DSPEngine::instance()->getAudioDeviceManager()->removeAudioSource(&m_audioFifo); m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSource(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; + + delete m_SSBFilter; + delete m_DSBFilter; + delete[] m_SSBFilterBuffer; + delete[] m_DSBFilterBuffer; } void SSBMod::pull(Sample& sample) @@ -175,14 +172,14 @@ void SSBMod::pull(Sample& sample) void SSBMod::pullAudio(int nbSamples) { - unsigned int nbSamplesAudio = nbSamples * ((Real) m_settings.m_audioSampleRate / (Real) m_basebandSampleRate); + unsigned int nbSamplesAudio = nbSamples * ((Real) m_audioSampleRate / (Real) m_basebandSampleRate); if (nbSamplesAudio > m_audioBuffer.size()) { m_audioBuffer.resize(nbSamplesAudio); } - m_audioFifo.read(reinterpret_cast(&m_audioBuffer[0]), nbSamplesAudio, 10); + m_audioFifo.read(reinterpret_cast(&m_audioBuffer[0]), nbSamplesAudio); m_audioBufferFill = 0; } @@ -209,9 +206,9 @@ void SSBMod::pullAF(Complex& sample) int decim = 1<<(m_settings.m_spanLog2 - 1); unsigned char decim_mask = decim - 1; // counter LSB bit mask for decimation by 2^(m_scaleLog2 - 1) - switch (m_afInput) + switch (m_settings.m_modAFInput) { - case SSBModInputTone: + case SSBModSettings::SSBModInputTone: if (m_settings.m_dsb) { Real t = m_toneNco.next()/1.25; @@ -227,7 +224,7 @@ void SSBMod::pullAF(Complex& sample) } } break; - case SSBModInputFile: + case SSBModSettings::SSBModInputFile: // Monaural (mono): // sox f4exb_call.wav --encoding float --endian little f4exb_call.raw // ffplay -f f32le -ar 48k -ac 1 f4exb_call.raw @@ -293,7 +290,7 @@ void SSBMod::pullAF(Complex& sample) ci.imag(0.0f); } break; - case SSBModInputAudio: + case SSBModSettings::SSBModInputAudio: if (m_settings.m_audioBinaural) { if (m_settings.m_audioFlipChannels) @@ -324,7 +321,7 @@ void SSBMod::pullAF(Complex& sample) } break; - case SSBModInputCWTone: + case SSBModSettings::SSBModInputCWTone: Real fadeFactor; if (m_cwKeyer.getSample()) @@ -374,12 +371,15 @@ void SSBMod::pullAF(Complex& sample) } break; - case SSBModInputNone: + case SSBModSettings::SSBModInputNone: default: + sample.real(0.0f); + sample.imag(0.0f); break; } - if ((m_afInput == SSBModInputFile) || (m_afInput == SSBModInputAudio)) // real audio + if ((m_settings.m_modAFInput == SSBModSettings::SSBModInputFile) + || (m_settings.m_modAFInput == SSBModSettings::SSBModInputAudio)) // real audio { if (m_settings.m_dsb) { @@ -437,7 +437,8 @@ void SSBMod::pullAF(Complex& sample) } } } // Real audio - else if ((m_afInput == SSBModInputTone) || (m_afInput == SSBModInputCWTone)) // tone + else if ((m_settings.m_modAFInput == SSBModSettings::SSBModInputTone) + || (m_settings.m_modAFInput == SSBModSettings::SSBModInputCWTone)) // tone { m_sum += sample; @@ -563,13 +564,6 @@ bool SSBMod::handleMessage(const Message& cmd) return true; } - else if (MsgConfigureAFInput::match(cmd)) - { - MsgConfigureAFInput& conf = (MsgConfigureAFInput&) cmd; - m_afInput = conf.getAFInput(); - - return true; - } else if (MsgConfigureFileSourceStreamTiming::match(cmd)) { std::size_t samplesCount; @@ -580,9 +574,26 @@ bool SSBMod::handleMessage(const Message& cmd) samplesCount = m_ifstream.tellg() / sizeof(Real); } - MsgReportFileSourceStreamTiming *report; - report = MsgReportFileSourceStreamTiming::create(samplesCount); - getMessageQueueToGUI()->push(report); + if (getMessageQueueToGUI()) + { + MsgReportFileSourceStreamTiming *report; + report = MsgReportFileSourceStreamTiming::create(samplesCount); + getMessageQueueToGUI()->push(report); + } + + return true; + } + else if (DSPConfigureAudio::match(cmd)) + { + DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd; + uint32_t sampleRate = cfg.getSampleRate(); + + qDebug() << "SSBMod::handleMessage: DSPConfigureAudio:" + << " sampleRate: " << sampleRate; + + if (sampleRate != m_audioSampleRate) { + applyAudioSampleRate(sampleRate); + } return true; } @@ -613,9 +624,12 @@ void SSBMod::openFileStream() << " fileSize: " << m_fileSize << "bytes" << " length: " << m_recordLength << " seconds"; - MsgReportFileSourceStreamData *report; - report = MsgReportFileSourceStreamData::create(m_sampleRate, m_recordLength); - getMessageQueueToGUI()->push(report); + if (getMessageQueueToGUI()) + { + MsgReportFileSourceStreamData *report; + report = MsgReportFileSourceStreamData::create(m_sampleRate, m_recordLength); + getMessageQueueToGUI()->push(report); + } } void SSBMod::seekFileStream(int seekPercentage) @@ -631,6 +645,70 @@ void SSBMod::seekFileStream(int seekPercentage) } } +void SSBMod::applyAudioSampleRate(int sampleRate) +{ + qDebug("SSBMod::applyAudioSampleRate: %d", sampleRate); + + + MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( + sampleRate, m_settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(channelConfigMsg); + + m_settingsMutex.lock(); + + m_interpolatorDistanceRemain = 0; + m_interpolatorConsumed = false; + m_interpolatorDistance = (Real) sampleRate / (Real) m_outputSampleRate; + m_interpolator.create(48, sampleRate, m_settings.m_bandwidth, 3.0); + + float band = m_settings.m_bandwidth; + float lowCutoff = m_settings.m_lowCutoff; + bool usb = m_settings.m_usb; + + if (band < 0) // negative means LSB + { + band = -band; // turn to positive + lowCutoff = -lowCutoff; + usb = false; // and take note of side band + } + else + { + usb = true; + } + + if (band < 100.0f) // at least 100 Hz + { + band = 100.0f; + lowCutoff = 0; + } + + if (band - lowCutoff < 100.0f) { + lowCutoff = band - 100.0f; + } + + m_SSBFilter->create_filter(lowCutoff / sampleRate, band / sampleRate); + m_DSBFilter->create_dsb_filter((2.0f * band) / sampleRate); + + m_settings.m_bandwidth = band; + m_settings.m_lowCutoff = lowCutoff; + m_settings.m_usb = usb; + + m_toneNco.setFreq(m_settings.m_toneFrequency, sampleRate); + m_cwKeyer.setSampleRate(sampleRate); + + m_agcStepLength = std::min(sampleRate/20, m_settings.m_agcTime/2); // 50 ms or half the AGC length whichever is smaller + + m_settingsMutex.unlock(); + + m_audioSampleRate = sampleRate; + + if (getMessageQueueToGUI()) + { + DSPConfigureAudio *cfg = new DSPConfigureAudio(m_audioSampleRate); + getMessageQueueToGUI()->push(cfg); + } +} + void SSBMod::applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force) { qDebug() << "SSBMod::applyChannelSettings:" @@ -651,8 +729,8 @@ void SSBMod::applyChannelSettings(int basebandSampleRate, int outputSampleRate, m_settingsMutex.lock(); m_interpolatorDistanceRemain = 0; m_interpolatorConsumed = false; - m_interpolatorDistance = (Real) m_settings.m_audioSampleRate / (Real) outputSampleRate; - m_interpolator.create(48, m_settings.m_audioSampleRate, m_settings.m_bandwidth, 3.0); + m_interpolatorDistance = (Real) m_audioSampleRate / (Real) outputSampleRate; + m_interpolator.create(48, m_audioSampleRate, m_settings.m_bandwidth, 3.0); m_settingsMutex.unlock(); } @@ -668,8 +746,7 @@ void SSBMod::applySettings(const SSBModSettings& settings, bool force) bool usb = settings.m_usb; if ((settings.m_bandwidth != m_settings.m_bandwidth) || - (settings.m_lowCutoff != m_settings.m_lowCutoff) || - (settings.m_audioSampleRate != m_settings.m_audioSampleRate) || force) + (settings.m_lowCutoff != m_settings.m_lowCutoff) || force) { if (band < 0) // negative means LSB { @@ -695,25 +772,17 @@ void SSBMod::applySettings(const SSBModSettings& settings, bool force) m_settingsMutex.lock(); m_interpolatorDistanceRemain = 0; m_interpolatorConsumed = false; - m_interpolatorDistance = (Real) settings.m_audioSampleRate / (Real) m_outputSampleRate; - m_interpolator.create(48, settings.m_audioSampleRate, band, 3.0); - m_SSBFilter->create_filter(lowCutoff / settings.m_audioSampleRate, band / settings.m_audioSampleRate); - m_DSBFilter->create_dsb_filter((2.0f * band) / settings.m_audioSampleRate); + m_interpolatorDistance = (Real) m_audioSampleRate / (Real) m_outputSampleRate; + m_interpolator.create(48, m_audioSampleRate, band, 3.0); + m_SSBFilter->create_filter(lowCutoff / m_audioSampleRate, band / m_audioSampleRate); + m_DSBFilter->create_dsb_filter((2.0f * band) / m_audioSampleRate); m_settingsMutex.unlock(); } - if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || - (settings.m_audioSampleRate != m_settings.m_audioSampleRate) || force) + if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || force) { m_settingsMutex.lock(); - m_toneNco.setFreq(settings.m_toneFrequency, settings.m_audioSampleRate); - m_settingsMutex.unlock(); - } - - if ((settings.m_audioSampleRate != m_settings.m_audioSampleRate) || force) - { - m_settingsMutex.lock(); - m_cwKeyer.setSampleRate(settings.m_audioSampleRate); + m_toneNco.setFreq(settings.m_toneFrequency, m_audioSampleRate); m_settingsMutex.unlock(); } @@ -721,12 +790,14 @@ void SSBMod::applySettings(const SSBModSettings& settings, bool force) { if (settings.m_dsb) { - memset(m_DSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen)); + std::fill(m_DSBFilterBuffer, m_DSBFilterBuffer+m_ssbFftLen, Complex{0,0}); + //memset(m_DSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen)); m_DSBFilterBufferIndex = 0; } else { - memset(m_SSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen>>1)); + std::fill(m_SSBFilterBuffer, m_SSBFilterBuffer+(m_ssbFftLen>>1), Complex{0,0}); + //memset(m_SSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen>>1)); m_SSBFilterBufferIndex = 0; } } @@ -735,7 +806,7 @@ void SSBMod::applySettings(const SSBModSettings& settings, bool force) (settings.m_agcOrder != m_settings.m_agcOrder) || force) { m_settingsMutex.lock(); - m_inAGC.resize(settings.m_agcTime, settings.m_agcOrder); + m_inAGC.resize(settings.m_agcTime, m_agcStepLength, settings.m_agcOrder); m_settingsMutex.unlock(); } @@ -759,6 +830,18 @@ void SSBMod::applySettings(const SSBModSettings& settings, bool force) m_inAGC.setStepDownDelay(settings.m_agcThresholdDelay); } + if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) + { + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + int audioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_audioDeviceName); + audioDeviceManager->addAudioSource(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex); + uint32_t audioSampleRate = audioDeviceManager->getInputSampleRate(audioDeviceIndex); + + if (m_audioSampleRate != audioSampleRate) { + applyAudioSampleRate(audioSampleRate); + } + } + m_settings = settings; m_settings.m_bandwidth = band; m_settings.m_lowCutoff = lowCutoff; @@ -786,3 +869,223 @@ bool SSBMod::deserialize(const QByteArray& data) return false; } } + +int SSBMod::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setSsbModSettings(new SWGSDRangel::SWGSSBModSettings()); + response.getSsbModSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int SSBMod::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + SSBModSettings settings = m_settings; + bool frequencyOffsetChanged = false; + + if (channelSettingsKeys.contains("inputFrequencyOffset")) + { + settings.m_inputFrequencyOffset = response.getSsbModSettings()->getInputFrequencyOffset(); + frequencyOffsetChanged = true; + } + if (channelSettingsKeys.contains("bandwidth")) { + settings.m_bandwidth = response.getSsbModSettings()->getBandwidth(); + } + if (channelSettingsKeys.contains("lowCutoff")) { + settings.m_lowCutoff = response.getSsbModSettings()->getLowCutoff(); + } + if (channelSettingsKeys.contains("usb")) { + settings.m_usb = response.getSsbModSettings()->getUsb() != 0; + } + if (channelSettingsKeys.contains("toneFrequency")) { + settings.m_toneFrequency = response.getSsbModSettings()->getToneFrequency(); + } + if (channelSettingsKeys.contains("volumeFactor")) { + settings.m_volumeFactor = response.getSsbModSettings()->getVolumeFactor(); + } + if (channelSettingsKeys.contains("spanLog2")) { + settings.m_spanLog2 = response.getSsbModSettings()->getSpanLog2(); + } + if (channelSettingsKeys.contains("audioBinaural")) { + settings.m_audioBinaural = response.getSsbModSettings()->getAudioBinaural() != 0; + } + if (channelSettingsKeys.contains("audioFlipChannels")) { + settings.m_audioFlipChannels = response.getSsbModSettings()->getAudioFlipChannels() != 0; + } + if (channelSettingsKeys.contains("dsb")) { + settings.m_dsb = response.getSsbModSettings()->getDsb() != 0; + } + if (channelSettingsKeys.contains("audioMute")) { + settings.m_audioMute = response.getSsbModSettings()->getAudioMute() != 0; + } + if (channelSettingsKeys.contains("playLoop")) { + settings.m_playLoop = response.getSsbModSettings()->getPlayLoop() != 0; + } + if (channelSettingsKeys.contains("agc")) { + settings.m_agc = response.getSsbModSettings()->getAgc() != 0; + } + if (channelSettingsKeys.contains("agcOrder")) { + settings.m_agcOrder = response.getSsbModSettings()->getAgcOrder(); + } + if (channelSettingsKeys.contains("agcTime")) { + settings.m_agcTime = response.getSsbModSettings()->getAgcTime(); + } + if (channelSettingsKeys.contains("agcThresholdEnable")) { + settings.m_agcThresholdEnable = response.getSsbModSettings()->getAgcThresholdEnable() != 0; + } + if (channelSettingsKeys.contains("agcThreshold")) { + settings.m_agcThreshold = response.getSsbModSettings()->getAgcThreshold(); + } + if (channelSettingsKeys.contains("agcThresholdGate")) { + settings.m_agcThresholdGate = response.getSsbModSettings()->getAgcThresholdGate(); + } + if (channelSettingsKeys.contains("agcThresholdDelay")) { + settings.m_agcThresholdDelay = response.getSsbModSettings()->getAgcThresholdDelay(); + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getSsbModSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getSsbModSettings()->getTitle(); + } + if (channelSettingsKeys.contains("modAFInput")) { + settings.m_modAFInput = (SSBModSettings::SSBModInputAF) response.getSsbModSettings()->getModAfInput(); + } + if (channelSettingsKeys.contains("audioDeviceName")) { + settings.m_audioDeviceName = *response.getSsbModSettings()->getAudioDeviceName(); + } + + if (channelSettingsKeys.contains("cwKeyer")) + { + SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = response.getSsbModSettings()->getCwKeyer(); + CWKeyerSettings cwKeyerSettings = m_cwKeyer.getSettings(); + + if (channelSettingsKeys.contains("cwKeyer.loop")) { + cwKeyerSettings.m_loop = apiCwKeyerSettings->getLoop() != 0; + } + if (channelSettingsKeys.contains("cwKeyer.mode")) { + cwKeyerSettings.m_mode = (CWKeyerSettings::CWMode) apiCwKeyerSettings->getMode(); + } + if (channelSettingsKeys.contains("cwKeyer.text")) { + cwKeyerSettings.m_text = *apiCwKeyerSettings->getText(); + } + if (channelSettingsKeys.contains("cwKeyer.sampleRate")) { + cwKeyerSettings.m_sampleRate = apiCwKeyerSettings->getSampleRate(); + } + if (channelSettingsKeys.contains("cwKeyer.wpm")) { + cwKeyerSettings.m_wpm = apiCwKeyerSettings->getWpm(); + } + + m_cwKeyer.setLoop(cwKeyerSettings.m_loop); + m_cwKeyer.setMode(cwKeyerSettings.m_mode); + m_cwKeyer.setSampleRate(cwKeyerSettings.m_sampleRate); + m_cwKeyer.setText(cwKeyerSettings.m_text); + m_cwKeyer.setWPM(cwKeyerSettings.m_wpm); + + if (m_guiMessageQueue) // forward to GUI if any + { + CWKeyer::MsgConfigureCWKeyer *msgCwKeyer = CWKeyer::MsgConfigureCWKeyer::create(cwKeyerSettings, force); + m_guiMessageQueue->push(msgCwKeyer); + } + } + + if (frequencyOffsetChanged) + { + SSBMod::MsgConfigureChannelizer *msgChan = SSBMod::MsgConfigureChannelizer::create( + m_audioSampleRate, settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(msgChan); + } + + MsgConfigureSSBMod *msg = MsgConfigureSSBMod::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureSSBMod *msgToGUI = MsgConfigureSSBMod::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int SSBMod::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setSsbModReport(new SWGSDRangel::SWGSSBModReport()); + response.getSsbModReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + +void SSBMod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const SSBModSettings& settings) +{ + response.getSsbModSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getSsbModSettings()->setBandwidth(settings.m_bandwidth); + response.getSsbModSettings()->setLowCutoff(settings.m_lowCutoff); + response.getSsbModSettings()->setUsb(settings.m_usb ? 1 : 0); + response.getSsbModSettings()->setToneFrequency(settings.m_toneFrequency); + response.getSsbModSettings()->setVolumeFactor(settings.m_volumeFactor); + response.getSsbModSettings()->setSpanLog2(settings.m_spanLog2); + response.getSsbModSettings()->setAudioBinaural(settings.m_audioBinaural ? 1 : 0); + response.getSsbModSettings()->setAudioFlipChannels(settings.m_audioFlipChannels ? 1 : 0); + response.getSsbModSettings()->setDsb(settings.m_dsb ? 1 : 0); + response.getSsbModSettings()->setAudioMute(settings.m_audioMute ? 1 : 0); + response.getSsbModSettings()->setPlayLoop(settings.m_playLoop ? 1 : 0); + response.getSsbModSettings()->setAgc(settings.m_agc ? 1 : 0); + response.getSsbModSettings()->setAgcOrder(settings.m_agcOrder); + response.getSsbModSettings()->setAgcTime(settings.m_agcTime); + response.getSsbModSettings()->setAgcThresholdEnable(settings.m_agcThresholdEnable ? 1 : 0); + response.getSsbModSettings()->setAgcThreshold(settings.m_agcThreshold); + response.getSsbModSettings()->setAgcThresholdGate(settings.m_agcThresholdGate); + response.getSsbModSettings()->setAgcThresholdDelay(settings.m_agcThresholdDelay); + response.getSsbModSettings()->setRgbColor(settings.m_rgbColor); + + if (response.getSsbModSettings()->getTitle()) { + *response.getSsbModSettings()->getTitle() = settings.m_title; + } else { + response.getSsbModSettings()->setTitle(new QString(settings.m_title)); + } + + response.getSsbModSettings()->setModAfInput((int) settings.m_modAFInput); + + if (response.getSsbModSettings()->getAudioDeviceName()) { + *response.getSsbModSettings()->getAudioDeviceName() = settings.m_audioDeviceName; + } else { + response.getSsbModSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName)); + } + + if (!response.getSsbModSettings()->getCwKeyer()) { + response.getSsbModSettings()->setCwKeyer(new SWGSDRangel::SWGCWKeyerSettings); + } + + SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = response.getSsbModSettings()->getCwKeyer(); + const CWKeyerSettings& cwKeyerSettings = m_cwKeyer.getSettings(); + apiCwKeyerSettings->setLoop(cwKeyerSettings.m_loop ? 1 : 0); + apiCwKeyerSettings->setMode((int) cwKeyerSettings.m_mode); + apiCwKeyerSettings->setSampleRate(cwKeyerSettings.m_sampleRate); + + if (apiCwKeyerSettings->getText()) { + *apiCwKeyerSettings->getText() = cwKeyerSettings.m_text; + } else { + apiCwKeyerSettings->setText(new QString(cwKeyerSettings.m_text)); + } + + apiCwKeyerSettings->setWpm(cwKeyerSettings.m_wpm); +} + +void SSBMod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + response.getSsbModReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq())); + response.getSsbModReport()->setAudioSampleRate(m_audioSampleRate); + response.getSsbModReport()->setChannelSampleRate(m_outputSampleRate); +} + diff --git a/plugins/channeltx/modssb/ssbmod.h b/plugins/channeltx/modssb/ssbmod.h index 3929813a7..6dbd4760c 100644 --- a/plugins/channeltx/modssb/ssbmod.h +++ b/plugins/channeltx/modssb/ssbmod.h @@ -44,15 +44,6 @@ class SSBMod : public BasebandSampleSource, public ChannelSourceAPI { Q_OBJECT public: - typedef enum - { - SSBModInputNone, - SSBModInputTone, - SSBModInputFile, - SSBModInputAudio, - SSBModInputCWTone - } SSBModInputAF; - class MsgConfigureSSBMod : public Message { MESSAGE_CLASS_DECLARATION @@ -158,27 +149,6 @@ public: { } }; - class MsgConfigureAFInput : public Message - { - MESSAGE_CLASS_DECLARATION - - public: - SSBModInputAF getAFInput() const { return m_afInput; } - - static MsgConfigureAFInput* create(SSBModInputAF afInput) - { - return new MsgConfigureAFInput(afInput); - } - - private: - SSBModInputAF m_afInput; - - MsgConfigureAFInput(SSBModInputAF afInput) : - Message(), - m_afInput(afInput) - { } - }; - class MsgReportFileSourceStreamTiming : public Message { MESSAGE_CLASS_DECLARATION @@ -241,13 +211,26 @@ public: virtual void getIdentifier(QString& id) { id = objectName(); } virtual void getTitle(QString& title) { title = m_settings.m_title; } - virtual void setName(const QString& name) { setObjectName(name); } - virtual QString getName() const { return objectName(); } virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; } virtual QByteArray serialize() const; virtual bool deserialize(const QByteArray& data); + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + + uint32_t getAudioSampleRate() const { return m_audioSampleRate; } double getMagSq() const { return m_magsq; } CWKeyer *getCWKeyer() { return &m_cwKeyer; } @@ -279,6 +262,7 @@ private: int m_outputSampleRate; int m_inputFrequencyOffset; SSBModSettings m_settings; + quint32 m_audioSampleRate; NCOF m_carrierNco; NCOF m_toneNco; @@ -317,16 +301,17 @@ private: quint32 m_recordLength; //!< record length in seconds computed from file size int m_sampleRate; - SSBModInputAF m_afInput; quint32 m_levelCalcCount; Real m_peakLevel; Real m_levelSum; CWKeyer m_cwKeyer; MagAGC m_inAGC; + int m_agcStepLength; static const int m_levelNbSamples; + void applyAudioSampleRate(int sampleRate); void applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force = false); void applySettings(const SSBModSettings& settings, bool force = false); void pullAF(Complex& sample); @@ -334,6 +319,8 @@ private: void modulateSample(); void openFileStream(); void seekFileStream(int seekPercentage); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const SSBModSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); }; diff --git a/plugins/channeltx/modssb/ssbmodgui.cpp b/plugins/channeltx/modssb/ssbmodgui.cpp index 2ad877ce9..b15818d42 100644 --- a/plugins/channeltx/modssb/ssbmodgui.cpp +++ b/plugins/channeltx/modssb/ssbmodgui.cpp @@ -30,6 +30,10 @@ #include "util/simpleserializer.h" #include "util/db.h" #include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "gui/crightclickenabler.h" +#include "gui/audioselectdialog.h" +#include "gui/basicchannelsettingsdialog.h" #include "mainwindow.h" SSBModGUI* SSBModGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx) @@ -80,14 +84,14 @@ bool SSBModGUI::deserialize(const QByteArray& data) { qDebug("SSBModGUI::deserialize"); displaySettings(); - applyBandwidths(true); // does applySettings(true) + applyBandwidths(5 - ui->spanLog2->value(), true); // does applySettings(true) return true; } else { m_settings.resetToDefaults(); displaySettings(); - applyBandwidths(true); // does applySettings(true) + applyBandwidths(5 - ui->spanLog2->value(), true); // does applySettings(true) return false; } } @@ -108,6 +112,27 @@ bool SSBModGUI::handleMessage(const Message& message) updateWithStreamTime(); return true; } + else if (DSPConfigureAudio::match(message)) + { + qDebug("SSBModGUI::handleMessage: DSPConfigureAudio: %d", m_ssbMod->getAudioSampleRate()); + applyBandwidths(5 - ui->spanLog2->value()); // will update spectrum details with new sample rate + return true; + } + else if (SSBMod::MsgConfigureSSBMod::match(message)) + { + const SSBMod::MsgConfigureSSBMod& cfg = (SSBMod::MsgConfigureSSBMod&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else if (CWKeyer::MsgConfigureCWKeyer::match(message)) + { + const CWKeyer::MsgConfigureCWKeyer& cfg = (CWKeyer::MsgConfigureCWKeyer&) message; + ui->cwKeyerGUI->displaySettings(cfg.getSettings()); + return true; + } else { return false; @@ -124,8 +149,6 @@ void SSBModGUI::channelMarkerChangedByCursor() void SSBModGUI::channelMarkerUpdate() { m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); - m_settings.m_udpAddress = m_channelMarker.getUDPAddress(); - m_settings.m_udpPort = m_channelMarker.getUDPReceivePort(); displaySettings(); applySettings(); } @@ -161,7 +184,7 @@ void SSBModGUI::on_flipSidebands_clicked(bool checked __attribute__((unused))) void SSBModGUI::on_dsb_toggled(bool dsb) { ui->flipSidebands->setEnabled(!dsb); - applyBandwidths(); + applyBandwidths(5 - ui->spanLog2->value()); } void SSBModGUI::on_audioBinaural_toggled(bool checked) @@ -178,21 +201,21 @@ void SSBModGUI::on_audioFlipChannels_toggled(bool checked) void SSBModGUI::on_spanLog2_valueChanged(int value) { - if ((value < 1) || (value > 5)) { + if ((value < 0) || (value > 4)) { return; } - applyBandwidths(); + applyBandwidths(5 - value); } void SSBModGUI::on_BW_valueChanged(int value __attribute__((unused))) { - applyBandwidths(); + applyBandwidths(5 - ui->spanLog2->value()); } void SSBModGUI::on_lowCut_valueChanged(int value __attribute__((unused))) { - applyBandwidths(); + applyBandwidths(5 - ui->spanLog2->value()); } void SSBModGUI::on_toneFrequency_valueChanged(int value) @@ -226,9 +249,8 @@ void SSBModGUI::on_play_toggled(bool checked) ui->tone->setEnabled(!checked); // release other source inputs ui->morseKeyer->setEnabled(!checked); ui->mic->setEnabled(!checked); - m_modAFInput = checked ? SSBMod::SSBModInputFile : SSBMod::SSBModInputNone; - SSBMod::MsgConfigureAFInput* message = SSBMod::MsgConfigureAFInput::create(m_modAFInput); - m_ssbMod->getInputMessageQueue()->push(message); + m_settings.m_modAFInput = checked ? SSBModSettings::SSBModInputFile : SSBModSettings::SSBModInputNone; + applySettings(); ui->navTimeSlider->setEnabled(!checked); m_enableNavTime = !checked; } @@ -238,9 +260,8 @@ void SSBModGUI::on_tone_toggled(bool checked) ui->play->setEnabled(!checked); // release other source inputs ui->morseKeyer->setEnabled(!checked); ui->mic->setEnabled(!checked); - m_modAFInput = checked ? SSBMod::SSBModInputTone : SSBMod::SSBModInputNone; - SSBMod::MsgConfigureAFInput* message = SSBMod::MsgConfigureAFInput::create(m_modAFInput); - m_ssbMod->getInputMessageQueue()->push(message); + m_settings.m_modAFInput = checked ? SSBModSettings::SSBModInputTone : SSBModSettings::SSBModInputNone; + applySettings(); } void SSBModGUI::on_morseKeyer_toggled(bool checked) @@ -248,9 +269,8 @@ void SSBModGUI::on_morseKeyer_toggled(bool checked) ui->play->setEnabled(!checked); // release other source inputs ui->tone->setEnabled(!checked); // release other source inputs ui->mic->setEnabled(!checked); - m_modAFInput = checked ? SSBMod::SSBModInputCWTone : SSBMod::SSBModInputNone; - SSBMod::MsgConfigureAFInput* message = SSBMod::MsgConfigureAFInput::create(m_modAFInput); - m_ssbMod->getInputMessageQueue()->push(message); + m_settings.m_modAFInput = checked ? SSBModSettings::SSBModInputCWTone : SSBModSettings::SSBModInputNone; + applySettings(); } void SSBModGUI::on_mic_toggled(bool checked) @@ -258,9 +278,8 @@ void SSBModGUI::on_mic_toggled(bool checked) ui->play->setEnabled(!checked); // release other source inputs ui->morseKeyer->setEnabled(!checked); ui->tone->setEnabled(!checked); // release other source inputs - m_modAFInput = checked ? SSBMod::SSBModInputAudio : SSBMod::SSBModInputNone; - SSBMod::MsgConfigureAFInput* message = SSBMod::MsgConfigureAFInput::create(m_modAFInput); - m_ssbMod->getInputMessageQueue()->push(message); + m_settings.m_modAFInput = checked ? SSBModSettings::SSBModInputAudio : SSBModSettings::SSBModInputNone; + applySettings(); } void SSBModGUI::on_agc_toggled(bool checked) @@ -322,7 +341,7 @@ void SSBModGUI::on_navTimeSlider_valueChanged(int value) void SSBModGUI::on_showFileDialog_clicked(bool checked __attribute__((unused))) { QString fileName = QFileDialog::getOpenFileName(this, - tr("Open raw audio file"), ".", tr("Raw audio Files (*.raw)")); + tr("Open raw audio file"), ".", tr("Raw audio Files (*.raw)"), 0, QFileDialog::DontUseNativeDialog); if (fileName != "") { @@ -344,6 +363,22 @@ void SSBModGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rol { } +void SSBModGUI::onMenuDialogCalled(const QPoint &p) +{ + BasicChannelSettingsDialog dialog(&m_channelMarker, this); + dialog.move(p); + dialog.exec(); + + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); + m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); + m_settings.m_title = m_channelMarker.getTitle(); + + setWindowTitle(m_settings.m_title); + setTitleColor(m_settings.m_rgbColor); + + applySettings(); +} + SSBModGUI::SSBModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent) : RollupWidget(parent), ui(new Ui::SSBModGUI), @@ -356,12 +391,12 @@ SSBModGUI::SSBModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam m_recordSampleRate(48000), m_samplesCount(0), m_tickCount(0), - m_enableNavTime(false), - m_modAFInput(SSBMod::SSBModInputNone) + m_enableNavTime(false) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose, true); connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); m_spectrumVis = new SpectrumVis(SDR_TX_SCALEF, ui->glSpectrum); m_ssbMod = (SSBMod*) channelTx; //new SSBMod(m_deviceUISet->m_deviceSinkAPI); @@ -379,6 +414,9 @@ SSBModGUI::SSBModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); + CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->mic); + connect(audioMuteRightClickEnabler, SIGNAL(rightClick()), this, SLOT(audioSelect())); + ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999); @@ -389,8 +427,6 @@ SSBModGUI::SSBModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam m_channelMarker.setSidebands(ChannelMarker::usb); m_channelMarker.setCenterFrequency(0); m_channelMarker.setTitle("SSB Modulator"); - m_channelMarker.setUDPAddress("127.0.0.1"); - m_channelMarker.setUDPSendPort(9999); m_channelMarker.blockSignals(false); m_channelMarker.setVisible(true); @@ -418,12 +454,12 @@ SSBModGUI::SSBModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam m_iconDSBLSB.addPixmap(QPixmap("://lsb.png"), QIcon::Normal, QIcon::Off); displaySettings(); - applyBandwidths(true); // does applySettings(true) + applyBandwidths(5 - ui->spanLog2->value(), true); // does applySettings(true) } SSBModGUI::~SSBModGUI() { - m_deviceUISet->removeRxChannelInstance(this); + m_deviceUISet->removeTxChannelInstance(this); delete m_ssbMod; // TODO: check this: when the GUI closes it has to delete the modulator delete m_spectrumVis; delete ui; @@ -449,14 +485,13 @@ void SSBModGUI::applySettings(bool force) } } -void SSBModGUI::applyBandwidths(bool force) +void SSBModGUI::applyBandwidths(int spanLog2, bool force) { bool dsb = ui->dsb->isChecked(); - int spanLog2 = ui->spanLog2->value(); - m_spectrumRate = 48000 / (1<getAudioSampleRate() / (1<BW->value(); int lw = ui->lowCut->value(); - int bwMax = 480/(1<getAudioSampleRate() / (100*(1<BW->blockSignals(true); ui->dsb->setChecked(m_settings.m_dsb); - ui->spanLog2->setValue(m_settings.m_spanLog2); + ui->spanLog2->setValue(5 - m_settings.m_spanLog2); ui->BW->setValue(roundf(m_settings.m_bandwidth/100.0)); s = QString::number(m_settings.m_bandwidth/1000.0, 'f', 1); @@ -640,6 +676,20 @@ void SSBModGUI::displaySettings() ui->volume->setValue(m_settings.m_volumeFactor * 10.0); ui->volumeText->setText(QString("%1").arg(m_settings.m_volumeFactor, 0, 'f', 1)); + ui->tone->setEnabled((m_settings.m_modAFInput == SSBModSettings::SSBModInputAF::SSBModInputTone) + || (m_settings.m_modAFInput == SSBModSettings::SSBModInputAF::SSBModInputNone)); + ui->mic->setEnabled((m_settings.m_modAFInput == SSBModSettings::SSBModInputAF::SSBModInputAudio) + || (m_settings.m_modAFInput == SSBModSettings::SSBModInputAF::SSBModInputNone)); + ui->play->setEnabled((m_settings.m_modAFInput == SSBModSettings::SSBModInputAF::SSBModInputFile) + || (m_settings.m_modAFInput == SSBModSettings::SSBModInputAF::SSBModInputNone)); + ui->morseKeyer->setEnabled((m_settings.m_modAFInput == SSBModSettings::SSBModInputAF::SSBModInputCWTone) + || (m_settings.m_modAFInput == SSBModSettings::SSBModInputAF::SSBModInputNone)); + + ui->tone->setChecked(m_settings.m_modAFInput == SSBModSettings::SSBModInputAF::SSBModInputTone); + ui->mic->setChecked(m_settings.m_modAFInput == SSBModSettings::SSBModInputAF::SSBModInputAudio); + ui->play->setChecked(m_settings.m_modAFInput == SSBModSettings::SSBModInputAF::SSBModInputFile); + ui->morseKeyer->setChecked(m_settings.m_modAFInput == SSBModSettings::SSBModInputAF::SSBModInputCWTone); + blockApplySettings(false); } @@ -668,13 +718,26 @@ void SSBModGUI::enterEvent(QEvent*) m_channelMarker.setHighlighted(true); } +void SSBModGUI::audioSelect() +{ + qDebug("SSBModGUI::audioSelect"); + AudioSelectDialog audioSelect(DSPEngine::instance()->getAudioDeviceManager(), m_settings.m_audioDeviceName, true); // true for input + audioSelect.exec(); + + if (audioSelect.m_selected) + { + m_settings.m_audioDeviceName = audioSelect.m_audioDeviceName; + applySettings(); + } +} + void SSBModGUI::tick() { double powDb = CalcDb::dbPower(m_ssbMod->getMagSq()); m_channelPowerDbAvg(powDb); ui->channelPower->setText(tr("%1 dB").arg(m_channelPowerDbAvg.asDouble(), 0, 'f', 1)); - if (((++m_tickCount & 0xf) == 0) && (m_modAFInput == SSBMod::SSBModInputFile)) + if (((++m_tickCount & 0xf) == 0) && (m_settings.m_modAFInput == SSBModSettings::SSBModInputFile)) { SSBMod::MsgConfigureFileSourceStreamTiming* message = SSBMod::MsgConfigureFileSourceStreamTiming::create(); m_ssbMod->getInputMessageQueue()->push(message); @@ -685,7 +748,7 @@ void SSBModGUI::updateWithStreamData() { QTime recordLength(0, 0, 0, 0); recordLength = recordLength.addSecs(m_recordLength); - QString s_time = recordLength.toString("hh:mm:ss"); + QString s_time = recordLength.toString("HH:mm:ss"); ui->recordLengthText->setText(s_time); updateWithStreamTime(); } @@ -704,8 +767,8 @@ void SSBModGUI::updateWithStreamTime() QTime t(0, 0, 0, 0); t = t.addSecs(t_sec); t = t.addMSecs(t_msec); - QString s_timems = t.toString("hh:mm:ss.zzz"); - QString s_time = t.toString("hh:mm:ss"); + QString s_timems = t.toString("HH:mm:ss.zzz"); + QString s_time = t.toString("HH:mm:ss"); ui->relTimeText->setText(s_timems); if (!m_enableNavTime) diff --git a/plugins/channeltx/modssb/ssbmodgui.h b/plugins/channeltx/modssb/ssbmodgui.h index 55ba7360c..d972f2c2c 100644 --- a/plugins/channeltx/modssb/ssbmodgui.h +++ b/plugins/channeltx/modssb/ssbmodgui.h @@ -77,7 +77,6 @@ private: int m_samplesCount; std::size_t m_tickCount; bool m_enableNavTime; - SSBMod::SSBModInputAF m_modAFInput; MessageQueue m_inputMessageQueue; QIcon m_iconDSBUSB; @@ -88,7 +87,7 @@ private: bool blockApplySettings(bool block); void applySettings(bool force = false); - void applyBandwidths(bool force = false); + void applyBandwidths(int spanLog2, bool force = false); void displaySettings(); void displayAGCPowerThreshold(); void updateWithStreamData(); @@ -127,8 +126,10 @@ private slots: void on_showFileDialog_clicked(bool checked); void onWidgetRolled(QWidget* widget, bool rollDown); + void onMenuDialogCalled(const QPoint& p); void configureFileName(); + void audioSelect(); void tick(); }; diff --git a/plugins/channeltx/modssb/ssbmodgui.ui b/plugins/channeltx/modssb/ssbmodgui.ui index 8b51bf8a9..b5d2a6112 100644 --- a/plugins/channeltx/modssb/ssbmodgui.ui +++ b/plugins/channeltx/modssb/ssbmodgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -53,16 +53,7 @@ 3 - - 2 - - - 2 - - - 2 - - + 2 @@ -101,7 +92,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -252,6 +243,18 @@ + + + 50 + 0 + + + + + 50 + 16777215 + + Span @@ -263,10 +266,92 @@ Spectrum display frequency span - 1 + 0 - 5 + 4 + + + 1 + + + 2 + + + 2 + + + Qt::Horizontal + + + false + + + false + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + 6.0k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + Low cut + + + + + + + + 16777215 + 16 + + + + Highpass filter cutoff frequency (SSB) + + + -60 + + + 60 1 @@ -274,24 +359,33 @@ 3 - - 3 - Qt::Horizontal - - true + + QSlider::NoTicks - - true + + 5 - + + + + 50 + 0 + + + + + 50 + 16777215 + + - 6.0k + 0.3k Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -522,85 +616,6 @@ - - - - - - - 50 - 0 - - - - - 50 - 16777215 - - - - Low cut - - - - - - - - 16777215 - 16 - - - - Highpass filter cutoff frequency (SSB) - - - -60 - - - 60 - - - 1 - - - 3 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - 5 - - - - - - - - 50 - 0 - - - - - 50 - 16777215 - - - - 0.3k - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - @@ -680,6 +695,12 @@ 0 + + + Liberation Mono + 8 + + Level (% full range) top trace: average, bottom trace: instantaneous peak, tip: peak hold @@ -711,7 +732,7 @@ - AGC volume order in faraction of maximum amplitude + AGC volume order in fraction of maximum amplitude 100 @@ -727,7 +748,7 @@ - AGC volume order in faraction of maximum amplitude + AGC volume order in fraction of maximum amplitude 0.00 @@ -1224,16 +1245,7 @@ 2 - - 3 - - - 3 - - - 3 - - + 3 @@ -1246,7 +1258,7 @@ - Monospace + Liberation Mono 8 diff --git a/plugins/channeltx/modssb/ssbmodplugin.cpp b/plugins/channeltx/modssb/ssbmodplugin.cpp index 8f68befb3..cfd279871 100644 --- a/plugins/channeltx/modssb/ssbmodplugin.cpp +++ b/plugins/channeltx/modssb/ssbmodplugin.cpp @@ -15,16 +15,17 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include #include "plugin/pluginapi.h" +#ifndef SERVER_MODE #include "ssbmodgui.h" +#endif #include "ssbmod.h" #include "ssbmodplugin.h" const PluginDescriptor SSBModPlugin::m_pluginDescriptor = { QString("SSB Modulator"), - QString("3.12.0"), + QString("4.1.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, @@ -50,10 +51,19 @@ void SSBModPlugin::initPlugin(PluginAPI* pluginAPI) m_pluginAPI->registerTxChannel(SSBMod::m_channelIdURI, SSBMod::m_channelId, this); } +#ifdef SERVER_MODE +PluginInstanceGUI* SSBModPlugin::createTxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSource *txChannel __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* SSBModPlugin::createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel) { return SSBModGUI::create(m_pluginAPI, deviceUISet, txChannel); } +#endif BasebandSampleSource* SSBModPlugin::createTxChannelBS(DeviceSinkAPI *deviceAPI) { diff --git a/plugins/channeltx/modssb/ssbmodsettings.cpp b/plugins/channeltx/modssb/ssbmodsettings.cpp index 6f3df4b8c..34b76d2fc 100644 --- a/plugins/channeltx/modssb/ssbmodsettings.cpp +++ b/plugins/channeltx/modssb/ssbmodsettings.cpp @@ -51,7 +51,6 @@ void SSBModSettings::resetToDefaults() m_usb = true; m_toneFrequency = 1000.0; m_volumeFactor = 1.0; - m_audioSampleRate = DSPEngine::instance()->getAudioSampleRate(); m_spanLog2 = 3; m_audioBinaural = false; m_audioFlipChannels = false; @@ -66,9 +65,9 @@ void SSBModSettings::resetToDefaults() m_agcThresholdGate = 192; m_agcThresholdDelay = 2400; m_rgbColor = QColor(0, 255, 0).rgb(); - m_udpAddress = "127.0.0.1"; - m_udpPort = 9999; m_title = "SSB Modulator"; + m_modAFInput = SSBModInputAF::SSBModInputNone; + m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; } QByteArray SSBModSettings::serialize() const @@ -106,6 +105,8 @@ QByteArray SSBModSettings::serialize() const } s.writeString(19, m_title); + s.writeString(20, m_audioDeviceName); + s.writeS32(21, (int) m_modAFInput); return s.final(); } @@ -171,6 +172,14 @@ bool SSBModSettings::deserialize(const QByteArray& data) } d.readString(19, &m_title, "SSB Modulator"); + d.readString(20, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName); + + d.readS32(21, &tmp, 0); + if ((tmp < 0) || (tmp > (int) SSBModInputAF::SSBModInputTone)) { + m_modAFInput = SSBModInputNone; + } else { + m_modAFInput = (SSBModInputAF) tmp; + } return true; } diff --git a/plugins/channeltx/modssb/ssbmodsettings.h b/plugins/channeltx/modssb/ssbmodsettings.h index 516d283e0..0163f1ef0 100644 --- a/plugins/channeltx/modssb/ssbmodsettings.h +++ b/plugins/channeltx/modssb/ssbmodsettings.h @@ -21,10 +21,19 @@ #include #include -struct Serializable; +class Serializable; struct SSBModSettings { + typedef enum + { + SSBModInputNone, + SSBModInputTone, + SSBModInputFile, + SSBModInputAudio, + SSBModInputCWTone + } SSBModInputAF; + static const int m_nbAGCTimeConstants; static const int m_agcTimeConstant[]; @@ -34,7 +43,6 @@ struct SSBModSettings bool m_usb; float m_toneFrequency; float m_volumeFactor; - quint32 m_audioSampleRate; int m_spanLog2; bool m_audioBinaural; bool m_audioFlipChannels; @@ -50,10 +58,9 @@ struct SSBModSettings int m_agcThresholdDelay; quint32 m_rgbColor; - QString m_udpAddress; - uint16_t m_udpPort; - QString m_title; + SSBModInputAF m_modAFInput; + QString m_audioDeviceName; Serializable *m_channelMarker; Serializable *m_spectrumGUI; diff --git a/plugins/channeltx/modwfm/CMakeLists.txt b/plugins/channeltx/modwfm/CMakeLists.txt index d712b8597..800e33353 100644 --- a/plugins/channeltx/modwfm/CMakeLists.txt +++ b/plugins/channeltx/modwfm/CMakeLists.txt @@ -1,5 +1,7 @@ project(modwfm) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + set(modwfm_SOURCES wfmmod.cpp wfmmodgui.cpp @@ -21,6 +23,7 @@ set(modwfm_FORMS include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) add_definitions(${QT_DEFINITIONS}) @@ -39,8 +42,9 @@ target_link_libraries(modwfm ${QT_LIBRARIES} sdrbase sdrgui + swagger ) -qt5_use_modules(modwfm Core Widgets) +target_link_libraries(modwfm Qt5::Core Qt5::Widgets) -install(TARGETS modwfm DESTINATION lib/plugins/channeltx) \ No newline at end of file +install(TARGETS modwfm DESTINATION lib/plugins/channeltx) diff --git a/plugins/channeltx/modwfm/modwfm.pro b/plugins/channeltx/modwfm/modwfm.pro index abe2358bd..4ab4b513c 100644 --- a/plugins/channeltx/modwfm/modwfm.pro +++ b/plugins/channeltx/modwfm/modwfm.pro @@ -18,8 +18,10 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui +INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug @@ -38,5 +40,6 @@ FORMS += wfmmodgui.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui +LIBS += -L../../../swagger/$${build_subdir} -lswagger RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/plugins/channeltx/modwfm/readme.md b/plugins/channeltx/modwfm/readme.md index 1ce694997..5f540bbd7 100644 --- a/plugins/channeltx/modwfm/readme.md +++ b/plugins/channeltx/modwfm/readme.md @@ -10,7 +10,7 @@ This plugin can be used to generate a wideband frequency modulated signal. "Wide

1: Frequency shift from center frequency of transmission

-Use the wheels to adjust the frequency shift in Hz from the center frequency of transmission. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the frequency shift in Hz from the center frequency of transmission. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

3: Channel power

@@ -42,7 +42,7 @@ This is the volume of the audio signal from 0.0 (mute) to 2.0 (maximum). It can - bottom bar (brown): instantaneous peak value - tip vertical bar (bright red): peak hold value -You should aim at keepimg the peak value below 100% using the volume control +You should aim at keeping the peak value below 100% using the volume control

10: Input source control

@@ -60,9 +60,11 @@ Switches to the Morse keyer input. You must switch it off to make other inputs a Adjusts the tone frequency from 0.1 to 2.5 kHz in 0.01 kHz steps -

10.4: Audio input select

+

10.4: Audio input select and select audio input device

-Switches to the audio input. You must switch it off to make other inputs available. +Left click to switch to the audio input. You must switch it off to make other inputs available. + +Right click to select audio input device. See [audio management documentation](../../../sdrgui/audio.md) for details.

11: CW (Morse) text

@@ -135,4 +137,4 @@ This is the audio file play length in time units

18: Play file position slider

-This slider can be used to randomly set the currennt position in the file when file play is in pause state (button 15.3) \ No newline at end of file +This slider can be used to randomly set the current position in the file when file play is in pause state (button 15.3) diff --git a/plugins/channeltx/modwfm/wfmmod.cpp b/plugins/channeltx/modwfm/wfmmod.cpp index ed30f65c6..916781f0d 100644 --- a/plugins/channeltx/modwfm/wfmmod.cpp +++ b/plugins/channeltx/modwfm/wfmmod.cpp @@ -22,11 +22,16 @@ #include #include +#include "SWGChannelSettings.h" +#include "SWGChannelReport.h" +#include "SWGAMModReport.h" + #include "dsp/upchannelizer.h" #include "dsp/dspengine.h" #include "dsp/threadedbasebandsamplesource.h" #include "dsp/dspcommands.h" #include "device/devicesinkapi.h" +#include "util/db.h" #include "wfmmod.h" @@ -34,7 +39,6 @@ MESSAGE_CLASS_DEFINITION(WFMMod::MsgConfigureWFMMod, Message) MESSAGE_CLASS_DEFINITION(WFMMod::MsgConfigureChannelizer, Message) MESSAGE_CLASS_DEFINITION(WFMMod::MsgConfigureFileSourceName, Message) MESSAGE_CLASS_DEFINITION(WFMMod::MsgConfigureFileSourceSeek, Message) -MESSAGE_CLASS_DEFINITION(WFMMod::MsgConfigureAFInput, Message) MESSAGE_CLASS_DEFINITION(WFMMod::MsgConfigureFileSourceStreamTiming, Message) MESSAGE_CLASS_DEFINITION(WFMMod::MsgReportFileSourceStreamData, Message) MESSAGE_CLASS_DEFINITION(WFMMod::MsgReportFileSourceStreamTiming, Message) @@ -56,7 +60,6 @@ WFMMod::WFMMod(DeviceSinkAPI *deviceAPI) : m_fileSize(0), m_recordLength(0), m_sampleRate(48000), - m_afInput(WFMModInputNone), m_levelCalcCount(0), m_peakLevel(0.0f), m_levelSum(0.0f) @@ -65,7 +68,8 @@ WFMMod::WFMMod(DeviceSinkAPI *deviceAPI) : m_rfFilter = new fftfilt(-62500.0 / 384000.0, 62500.0 / 384000.0, m_rfFilterFFTLength); m_rfFilterBuffer = new Complex[m_rfFilterFFTLength]; - memset(m_rfFilterBuffer, 0, sizeof(Complex)*(m_rfFilterFFTLength)); + std::fill(m_rfFilterBuffer, m_rfFilterBuffer+m_rfFilterFFTLength, Complex{0,0}); + //memset(m_rfFilterBuffer, 0, sizeof(Complex)*(m_rfFilterFFTLength)); m_rfFilterBufferIndex = 0; m_audioBuffer.resize(1<<14); @@ -73,9 +77,10 @@ WFMMod::WFMMod(DeviceSinkAPI *deviceAPI) : m_magsq = 0.0; - m_toneNco.setFreq(1000.0, m_settings.m_audioSampleRate); + DSPEngine::instance()->getAudioDeviceManager()->addAudioSource(&m_audioFifo, getInputMessageQueue()); + m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getInputSampleRate(); + m_toneNcoRF.setFreq(1000.0, m_outputSampleRate); - DSPEngine::instance()->addAudioSource(&m_audioFifo); // CW keyer m_cwKeyer.setSampleRate(m_outputSampleRate); @@ -83,24 +88,24 @@ WFMMod::WFMMod(DeviceSinkAPI *deviceAPI) : m_cwKeyer.setMode(CWKeyerSettings::CWNone); m_cwKeyer.reset(); + applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); + m_channelizer = new UpChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this); m_deviceAPI->addThreadedSource(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - - applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true); - applySettings(m_settings, true); } WFMMod::~WFMMod() { - delete m_rfFilter; - delete[] m_rfFilterBuffer; - DSPEngine::instance()->removeAudioSource(&m_audioFifo); + DSPEngine::instance()->getAudioDeviceManager()->removeAudioSource(&m_audioFifo); m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSource(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; + delete m_rfFilter; + delete[] m_rfFilterBuffer; } void WFMMod::pull(Sample& sample) @@ -118,7 +123,8 @@ void WFMMod::pull(Sample& sample) m_settingsMutex.lock(); - if ((m_afInput == WFMModInputFile) || (m_afInput == WFMModInputAudio)) + if ((m_settings.m_modAFInput == WFMModSettings::WFMModInputFile) + || (m_settings.m_modAFInput == WFMModSettings::WFMModInputAudio)) { if (m_interpolator.interpolate(&m_interpolatorDistanceRemain, m_modSample, &ri)) { @@ -164,26 +170,26 @@ void WFMMod::pull(Sample& sample) void WFMMod::pullAudio(int nbSamples) { - unsigned int nbSamplesAudio = nbSamples * ((Real) m_settings.m_audioSampleRate / (Real) m_basebandSampleRate); + unsigned int nbSamplesAudio = nbSamples * ((Real) m_audioSampleRate / (Real) m_basebandSampleRate); if (nbSamplesAudio > m_audioBuffer.size()) { m_audioBuffer.resize(nbSamplesAudio); } - m_audioFifo.read(reinterpret_cast(&m_audioBuffer[0]), nbSamplesAudio, 10); + m_audioFifo.read(reinterpret_cast(&m_audioBuffer[0]), nbSamplesAudio); m_audioBufferFill = 0; } void WFMMod::pullAF(Complex& sample) { - switch (m_afInput) + switch (m_settings.m_modAFInput) { - case WFMModInputTone: + case WFMModSettings::WFMModInputTone: sample.real(m_toneNcoRF.next() * m_settings.m_volumeFactor); sample.imag(0.0f); break; - case WFMModInputFile: + case WFMModSettings::WFMModInputFile: // sox f4exb_call.wav --encoding float --endian little f4exb_call.raw // ffplay -f f32le -ar 48k -ac 1 f4exb_call.raw if (m_ifstream.is_open()) @@ -216,13 +222,13 @@ void WFMMod::pullAF(Complex& sample) sample.imag(0.0f); } break; - case WFMModInputAudio: + case WFMModSettings::WFMModInputAudio: { sample.real(((m_audioBuffer[m_audioBufferFill].l + m_audioBuffer[m_audioBufferFill].r) / 65536.0f) * m_settings.m_volumeFactor); sample.imag(0.0f); } break; - case WFMModInputCWTone: + case WFMModSettings::WFMModInputCWTone: Real fadeFactor; if (m_cwKeyer.getSample()) @@ -246,7 +252,7 @@ void WFMMod::pullAF(Complex& sample) } } break; - case WFMModInputNone: + case WFMModSettings::WFMModInputNone: default: sample.real(0.0f); sample.imag(0.0f); @@ -336,13 +342,6 @@ bool WFMMod::handleMessage(const Message& cmd) return true; } - else if (MsgConfigureAFInput::match(cmd)) - { - MsgConfigureAFInput& conf = (MsgConfigureAFInput&) cmd; - m_afInput = conf.getAFInput(); - - return true; - } else if (MsgConfigureFileSourceStreamTiming::match(cmd)) { std::size_t samplesCount; @@ -359,6 +358,20 @@ bool WFMMod::handleMessage(const Message& cmd) return true; } + else if (DSPConfigureAudio::match(cmd)) + { + DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd; + uint32_t sampleRate = cfg.getSampleRate(); + + qDebug() << "WFMMod::handleMessage: DSPConfigureAudio:" + << " sampleRate: " << sampleRate; + + if (sampleRate != m_audioSampleRate) { + applyAudioSampleRate(sampleRate); + } + + return true; + } else if (DSPSignalNotification::match(cmd)) { return true; @@ -404,6 +417,20 @@ void WFMMod::seekFileStream(int seekPercentage) } } +void WFMMod::applyAudioSampleRate(int sampleRate) +{ + qDebug("WFMMod::applyAudioSampleRate: %d", sampleRate); + + m_settingsMutex.lock(); + m_interpolatorDistanceRemain = 0; + m_interpolatorConsumed = false; + m_interpolatorDistance = (Real) sampleRate / (Real) m_outputSampleRate; + m_interpolator.create(48, sampleRate, m_settings.m_rfBandwidth / 2.2, 3.0); + m_settingsMutex.unlock(); + + m_audioSampleRate = sampleRate; +} + void WFMMod::applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force) { qDebug() << "WFMMod::applyChannelSettings:" @@ -424,8 +451,8 @@ void WFMMod::applyChannelSettings(int basebandSampleRate, int outputSampleRate, m_settingsMutex.lock(); m_interpolatorDistanceRemain = 0; m_interpolatorConsumed = false; - m_interpolatorDistance = (Real) m_settings.m_audioSampleRate / (Real) outputSampleRate; - m_interpolator.create(48, m_settings.m_audioSampleRate, m_settings.m_rfBandwidth / 2.2, 3.0); + m_interpolatorDistance = (Real) m_audioSampleRate / (Real) outputSampleRate; + m_interpolator.create(48, m_audioSampleRate, m_settings.m_rfBandwidth / 2.2, 3.0); Real lowCut = -(m_settings.m_rfBandwidth / 2.0) / outputSampleRate; Real hiCut = (m_settings.m_rfBandwidth / 2.0) / outputSampleRate; m_rfFilter->create_filter(lowCut, hiCut); @@ -451,16 +478,16 @@ void WFMMod::applySettings(const WFMModSettings& settings, bool force) << " m_toneFrequency: " << settings.m_toneFrequency << " m_channelMute: " << settings.m_channelMute << " m_playLoop: " << settings.m_playLoop + << " m_audioDeviceName: " << settings.m_audioDeviceName << " force: " << force; - if((settings.m_audioSampleRate != m_settings.m_audioSampleRate) || - (settings.m_afBandwidth != m_settings.m_afBandwidth) || force) + if((settings.m_afBandwidth != m_settings.m_afBandwidth) || force) { m_settingsMutex.lock(); m_interpolatorDistanceRemain = 0; m_interpolatorConsumed = false; - m_interpolatorDistance = (Real) settings.m_audioSampleRate / (Real) m_outputSampleRate; - m_interpolator.create(48, settings.m_audioSampleRate, settings.m_rfBandwidth / 2.2, 3.0); + m_interpolatorDistance = (Real) m_audioSampleRate / (Real) m_outputSampleRate; + m_interpolator.create(48, m_audioSampleRate, settings.m_rfBandwidth / 2.2, 3.0); m_settingsMutex.unlock(); } @@ -480,6 +507,18 @@ void WFMMod::applySettings(const WFMModSettings& settings, bool force) m_settingsMutex.unlock(); } + if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) + { + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + int audioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_audioDeviceName); + audioDeviceManager->addAudioSource(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex); + uint32_t audioSampleRate = audioDeviceManager->getInputSampleRate(audioDeviceIndex); + + if (m_audioSampleRate != audioSampleRate) { + applyAudioSampleRate(audioSampleRate); + } + } + m_settings = settings; } @@ -504,3 +543,177 @@ bool WFMMod::deserialize(const QByteArray& data) return false; } } + +int WFMMod::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setWfmModSettings(new SWGSDRangel::SWGWFMModSettings()); + response.getWfmModSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int WFMMod::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + WFMModSettings settings = m_settings; + bool channelizerChange = false; + + if (channelSettingsKeys.contains("channelMute")) { + settings.m_channelMute = response.getWfmModSettings()->getChannelMute() != 0; + } + if (channelSettingsKeys.contains("inputFrequencyOffset")) + { + settings.m_inputFrequencyOffset = response.getWfmModSettings()->getInputFrequencyOffset(); + channelizerChange = true; + } + if (channelSettingsKeys.contains("modAFInput")) { + settings.m_modAFInput = (WFMModSettings::WFMModInputAF) response.getWfmModSettings()->getModAfInput(); + } + if (channelSettingsKeys.contains("playLoop")) { + settings.m_playLoop = response.getWfmModSettings()->getPlayLoop() != 0; + } + if (channelSettingsKeys.contains("rfBandwidth")) { + settings.m_rfBandwidth = response.getWfmModSettings()->getRfBandwidth(); + channelizerChange = true; + } + if (channelSettingsKeys.contains("afBandwidth")) { + settings.m_afBandwidth = response.getWfmModSettings()->getAfBandwidth(); + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getWfmModSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getWfmModSettings()->getTitle(); + } + if (channelSettingsKeys.contains("toneFrequency")) { + settings.m_toneFrequency = response.getWfmModSettings()->getToneFrequency(); + } + if (channelSettingsKeys.contains("volumeFactor")) { + settings.m_volumeFactor = response.getWfmModSettings()->getVolumeFactor(); + } + if (channelSettingsKeys.contains("fmDeviation")) { + settings.m_fmDeviation = response.getWfmModSettings()->getFmDeviation(); + } + + if (channelSettingsKeys.contains("cwKeyer")) + { + SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = response.getWfmModSettings()->getCwKeyer(); + CWKeyerSettings cwKeyerSettings = m_cwKeyer.getSettings(); + + if (channelSettingsKeys.contains("cwKeyer.loop")) { + cwKeyerSettings.m_loop = apiCwKeyerSettings->getLoop() != 0; + } + if (channelSettingsKeys.contains("cwKeyer.mode")) { + cwKeyerSettings.m_mode = (CWKeyerSettings::CWMode) apiCwKeyerSettings->getMode(); + } + if (channelSettingsKeys.contains("cwKeyer.text")) { + cwKeyerSettings.m_text = *apiCwKeyerSettings->getText(); + } + if (channelSettingsKeys.contains("cwKeyer.sampleRate")) { + cwKeyerSettings.m_sampleRate = apiCwKeyerSettings->getSampleRate(); + } + if (channelSettingsKeys.contains("cwKeyer.wpm")) { + cwKeyerSettings.m_wpm = apiCwKeyerSettings->getWpm(); + } + + m_cwKeyer.setLoop(cwKeyerSettings.m_loop); + m_cwKeyer.setMode(cwKeyerSettings.m_mode); + m_cwKeyer.setSampleRate(cwKeyerSettings.m_sampleRate); + m_cwKeyer.setText(cwKeyerSettings.m_text); + m_cwKeyer.setWPM(cwKeyerSettings.m_wpm); + + if (m_guiMessageQueue) // forward to GUI if any + { + CWKeyer::MsgConfigureCWKeyer *msgCwKeyer = CWKeyer::MsgConfigureCWKeyer::create(cwKeyerSettings, force); + m_guiMessageQueue->push(msgCwKeyer); + } + } + + if (channelizerChange) + { + WFMMod::MsgConfigureChannelizer *msgChan = WFMMod::MsgConfigureChannelizer::create( + settings.m_rfBandwidth, settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(msgChan); + } + + MsgConfigureWFMMod *msg = MsgConfigureWFMMod::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureWFMMod *msgToGUI = MsgConfigureWFMMod::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int WFMMod::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setWfmModReport(new SWGSDRangel::SWGWFMModReport()); + response.getWfmModReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + +void WFMMod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const WFMModSettings& settings) +{ + response.getWfmModSettings()->setChannelMute(settings.m_channelMute ? 1 : 0); + response.getWfmModSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getWfmModSettings()->setModAfInput((int) settings.m_modAFInput); + response.getWfmModSettings()->setPlayLoop(settings.m_playLoop ? 1 : 0); + response.getWfmModSettings()->setRfBandwidth(settings.m_rfBandwidth); + response.getWfmModSettings()->setAfBandwidth(settings.m_afBandwidth); + response.getWfmModSettings()->setFmDeviation(settings.m_fmDeviation); + response.getWfmModSettings()->setRgbColor(settings.m_rgbColor); + + if (response.getWfmModSettings()->getTitle()) { + *response.getWfmModSettings()->getTitle() = settings.m_title; + } else { + response.getWfmModSettings()->setTitle(new QString(settings.m_title)); + } + + response.getWfmModSettings()->setToneFrequency(settings.m_toneFrequency); + response.getWfmModSettings()->setVolumeFactor(settings.m_volumeFactor); + + if (!response.getWfmModSettings()->getCwKeyer()) { + response.getWfmModSettings()->setCwKeyer(new SWGSDRangel::SWGCWKeyerSettings); + } + + SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = response.getWfmModSettings()->getCwKeyer(); + const CWKeyerSettings& cwKeyerSettings = m_cwKeyer.getSettings(); + apiCwKeyerSettings->setLoop(cwKeyerSettings.m_loop ? 1 : 0); + apiCwKeyerSettings->setMode((int) cwKeyerSettings.m_mode); + apiCwKeyerSettings->setSampleRate(cwKeyerSettings.m_sampleRate); + + if (apiCwKeyerSettings->getText()) { + *apiCwKeyerSettings->getText() = cwKeyerSettings.m_text; + } else { + apiCwKeyerSettings->setText(new QString(cwKeyerSettings.m_text)); + } + + apiCwKeyerSettings->setWpm(cwKeyerSettings.m_wpm); + + if (response.getWfmModSettings()->getAudioDeviceName()) { + *response.getWfmModSettings()->getAudioDeviceName() = settings.m_audioDeviceName; + } else { + response.getWfmModSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName)); + } +} + +void WFMMod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + response.getWfmModReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq())); + response.getWfmModReport()->setAudioSampleRate(m_audioSampleRate); + response.getWfmModReport()->setChannelSampleRate(m_outputSampleRate); +} + diff --git a/plugins/channeltx/modwfm/wfmmod.h b/plugins/channeltx/modwfm/wfmmod.h index b324c79dc..f742e7b26 100644 --- a/plugins/channeltx/modwfm/wfmmod.h +++ b/plugins/channeltx/modwfm/wfmmod.h @@ -44,15 +44,6 @@ class WFMMod : public BasebandSampleSource, public ChannelSourceAPI { Q_OBJECT public: - typedef enum - { - WFMModInputNone, - WFMModInputTone, - WFMModInputFile, - WFMModInputAudio, - WFMModInputCWTone - } WFMModInputAF; - class MsgConfigureWFMMod : public Message { MESSAGE_CLASS_DECLARATION @@ -158,27 +149,6 @@ public: { } }; - class MsgConfigureAFInput : public Message - { - MESSAGE_CLASS_DECLARATION - - public: - WFMModInputAF getAFInput() const { return m_afInput; } - - static MsgConfigureAFInput* create(WFMModInputAF afInput) - { - return new MsgConfigureAFInput(afInput); - } - - private: - WFMModInputAF m_afInput; - - MsgConfigureAFInput(WFMModInputAF afInput) : - Message(), - m_afInput(afInput) - { } - }; - class MsgReportFileSourceStreamTiming : public Message { MESSAGE_CLASS_DECLARATION @@ -239,13 +209,25 @@ public: virtual void getIdentifier(QString& id) { id = objectName(); } virtual void getTitle(QString& title) { title = m_settings.m_title; } - virtual void setName(const QString& name) { setObjectName(name); } - virtual QString getName() const { return objectName(); } virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; } virtual QByteArray serialize() const; virtual bool deserialize(const QByteArray& data); + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + double getMagSq() const { return m_magsq; } CWKeyer *getCWKeyer() { return &m_cwKeyer; } @@ -277,9 +259,9 @@ private: int m_outputSampleRate; int m_inputFrequencyOffset; WFMModSettings m_settings; + quint32 m_audioSampleRate; NCO m_carrierNco; - NCOF m_toneNco; NCOF m_toneNcoRF; float m_modPhasor; //!< baseband modulator phasor Complex m_modSample; @@ -309,19 +291,21 @@ private: quint32 m_recordLength; //!< record length in seconds computed from file size int m_sampleRate; - WFMModInputAF m_afInput; quint32 m_levelCalcCount; Real m_peakLevel; Real m_levelSum; CWKeyer m_cwKeyer; static const int m_levelNbSamples; + void applyAudioSampleRate(int sampleRate); void applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force = false); void applySettings(const WFMModSettings& settings, bool force = false); void pullAF(Complex& sample); void calculateLevel(const Real& sample); void openFileStream(); void seekFileStream(int seekPercentage); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const WFMModSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); }; diff --git a/plugins/channeltx/modwfm/wfmmodgui.cpp b/plugins/channeltx/modwfm/wfmmodgui.cpp index d6a62b91a..b5cf6d6c2 100644 --- a/plugins/channeltx/modwfm/wfmmodgui.cpp +++ b/plugins/channeltx/modwfm/wfmmodgui.cpp @@ -28,6 +28,9 @@ #include "util/simpleserializer.h" #include "util/db.h" #include "dsp/dspengine.h" +#include "gui/crightclickenabler.h" +#include "gui/audioselectdialog.h" +#include "gui/basicchannelsettingsdialog.h" #include "mainwindow.h" #include "ui_wfmmodgui.h" @@ -104,6 +107,21 @@ bool WFMModGUI::handleMessage(const Message& message) updateWithStreamTime(); return true; } + else if (WFMMod::MsgConfigureWFMMod::match(message)) + { + const WFMMod::MsgConfigureWFMMod& cfg = (WFMMod::MsgConfigureWFMMod&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else if (CWKeyer::MsgConfigureCWKeyer::match(message)) + { + const CWKeyer::MsgConfigureCWKeyer& cfg = (CWKeyer::MsgConfigureCWKeyer&) message; + ui->cwKeyerGUI->displaySettings(cfg.getSettings()); + return true; + } else { return false; @@ -190,9 +208,8 @@ void WFMModGUI::on_play_toggled(bool checked) ui->tone->setEnabled(!checked); // release other source inputs ui->mic->setEnabled(!checked); ui->morseKeyer->setEnabled(!checked); - m_modAFInput = checked ? WFMMod::WFMModInputFile : WFMMod::WFMModInputNone; - WFMMod::MsgConfigureAFInput* message = WFMMod::MsgConfigureAFInput::create(m_modAFInput); - m_wfmMod->getInputMessageQueue()->push(message); + m_settings.m_modAFInput = checked ? WFMModSettings::WFMModInputFile : WFMModSettings::WFMModInputNone; + applySettings(); ui->navTimeSlider->setEnabled(!checked); m_enableNavTime = !checked; } @@ -202,9 +219,8 @@ void WFMModGUI::on_tone_toggled(bool checked) ui->play->setEnabled(!checked); // release other source inputs ui->mic->setEnabled(!checked); ui->morseKeyer->setEnabled(!checked); - m_modAFInput = checked ? WFMMod::WFMModInputTone : WFMMod::WFMModInputNone; - WFMMod::MsgConfigureAFInput* message = WFMMod::MsgConfigureAFInput::create(m_modAFInput); - m_wfmMod->getInputMessageQueue()->push(message); + m_settings.m_modAFInput = checked ? WFMModSettings::WFMModInputTone : WFMModSettings::WFMModInputNone; + applySettings(); } void WFMModGUI::on_morseKeyer_toggled(bool checked) @@ -212,9 +228,8 @@ void WFMModGUI::on_morseKeyer_toggled(bool checked) ui->tone->setEnabled(!checked); // release other source inputs ui->mic->setEnabled(!checked); ui->play->setEnabled(!checked); - m_modAFInput = checked ? WFMMod::WFMModInputCWTone : WFMMod::WFMModInputNone; - WFMMod::MsgConfigureAFInput* message = WFMMod::MsgConfigureAFInput::create(m_modAFInput); - m_wfmMod->getInputMessageQueue()->push(message); + m_settings.m_modAFInput = checked ? WFMModSettings::WFMModInputCWTone : WFMModSettings::WFMModInputNone; + applySettings(); } void WFMModGUI::on_mic_toggled(bool checked) @@ -222,9 +237,8 @@ void WFMModGUI::on_mic_toggled(bool checked) ui->play->setEnabled(!checked); // release other source inputs ui->tone->setEnabled(!checked); // release other source inputs ui->morseKeyer->setEnabled(!checked); - m_modAFInput = checked ? WFMMod::WFMModInputAudio : WFMMod::WFMModInputNone; - WFMMod::MsgConfigureAFInput* message = WFMMod::MsgConfigureAFInput::create(m_modAFInput); - m_wfmMod->getInputMessageQueue()->push(message); + m_settings.m_modAFInput = checked ? WFMModSettings::WFMModInputAudio : WFMModSettings::WFMModInputNone; + applySettings(); } void WFMModGUI::on_navTimeSlider_valueChanged(int value) @@ -243,7 +257,7 @@ void WFMModGUI::on_navTimeSlider_valueChanged(int value) void WFMModGUI::on_showFileDialog_clicked(bool checked __attribute__((unused))) { QString fileName = QFileDialog::getOpenFileName(this, - tr("Open raw audio file"), ".", tr("Raw audio Files (*.raw)")); + tr("Open raw audio file"), ".", tr("Raw audio Files (*.raw)"), 0, QFileDialog::DontUseNativeDialog); if (fileName != "") { @@ -265,6 +279,22 @@ void WFMModGUI::onWidgetRolled(QWidget* widget __attribute__((unused)), bool rol { } +void WFMModGUI::onMenuDialogCalled(const QPoint &p) +{ + BasicChannelSettingsDialog dialog(&m_channelMarker, this); + dialog.move(p); + dialog.exec(); + + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); + m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); + m_settings.m_title = m_channelMarker.getTitle(); + + setWindowTitle(m_settings.m_title); + setTitleColor(m_settings.m_rgbColor); + + applySettings(); +} + WFMModGUI::WFMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent) : RollupWidget(parent), ui(new Ui::WFMModGUI), @@ -276,8 +306,7 @@ WFMModGUI::WFMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam m_recordSampleRate(48000), m_samplesCount(0), m_tickCount(0), - m_enableNavTime(false), - m_modAFInput(WFMMod::WFMModInputNone) + m_enableNavTime(false) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose, true); @@ -293,12 +322,16 @@ WFMModGUI::WFMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam blockApplySettings(false); connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); m_wfmMod = (WFMMod*) channelTx; //new WFMMod(m_deviceUISet->m_deviceSinkAPI); m_wfmMod->setMessageQueueToGUI(getInputMessageQueue()); connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); + CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->mic); + connect(audioMuteRightClickEnabler, SIGNAL(rightClick()), this, SLOT(audioSelect())); + ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999); @@ -308,8 +341,6 @@ WFMModGUI::WFMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam m_channelMarker.setBandwidth(125000); m_channelMarker.setCenterFrequency(0); m_channelMarker.setTitle("WFM Modulator"); - m_channelMarker.setUDPAddress("127.0.0.1"); - m_channelMarker.setUDPSendPort(9999); m_channelMarker.blockSignals(false); m_channelMarker.setVisible(true); // activate signal on the last setting only @@ -370,6 +401,7 @@ void WFMModGUI::displaySettings() { m_channelMarker.blockSignals(true); m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset); + m_channelMarker.setTitle(m_settings.m_title); m_channelMarker.setBandwidth(m_settings.m_rfBandwidth); m_channelMarker.blockSignals(false); m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only @@ -398,6 +430,16 @@ void WFMModGUI::displaySettings() ui->channelMute->setChecked(m_settings.m_channelMute); ui->playLoop->setChecked(m_settings.m_playLoop); + ui->tone->setEnabled((m_settings.m_modAFInput == WFMModSettings::WFMModInputAF::WFMModInputTone) || (m_settings.m_modAFInput == WFMModSettings::WFMModInputAF::WFMModInputNone)); + ui->mic->setEnabled((m_settings.m_modAFInput == WFMModSettings::WFMModInputAF::WFMModInputAudio) || (m_settings.m_modAFInput == WFMModSettings::WFMModInputAF::WFMModInputNone)); + ui->play->setEnabled((m_settings.m_modAFInput == WFMModSettings::WFMModInputAF::WFMModInputFile) || (m_settings.m_modAFInput == WFMModSettings::WFMModInputAF::WFMModInputNone)); + ui->morseKeyer->setEnabled((m_settings.m_modAFInput == WFMModSettings::WFMModInputAF::WFMModInputCWTone) || (m_settings.m_modAFInput == WFMModSettings::WFMModInputAF::WFMModInputNone)); + + ui->tone->setChecked(m_settings.m_modAFInput == WFMModSettings::WFMModInputAF::WFMModInputTone); + ui->mic->setChecked(m_settings.m_modAFInput == WFMModSettings::WFMModInputAF::WFMModInputAudio); + ui->play->setChecked(m_settings.m_modAFInput == WFMModSettings::WFMModInputAF::WFMModInputFile); + ui->morseKeyer->setChecked(m_settings.m_modAFInput == WFMModSettings::WFMModInputAF::WFMModInputCWTone); + blockApplySettings(false); } @@ -411,13 +453,26 @@ void WFMModGUI::enterEvent(QEvent*) m_channelMarker.setHighlighted(true); } +void WFMModGUI::audioSelect() +{ + qDebug("WFMModGUI::audioSelect"); + AudioSelectDialog audioSelect(DSPEngine::instance()->getAudioDeviceManager(), m_settings.m_audioDeviceName, true); // true for input + audioSelect.exec(); + + if (audioSelect.m_selected) + { + m_settings.m_audioDeviceName = audioSelect.m_audioDeviceName; + applySettings(); + } +} + void WFMModGUI::tick() { double powDb = CalcDb::dbPower(m_wfmMod->getMagSq()); m_channelPowerDbAvg(powDb); ui->channelPower->setText(tr("%1 dB").arg(m_channelPowerDbAvg.asDouble(), 0, 'f', 1)); - if (((++m_tickCount & 0xf) == 0) && (m_modAFInput == WFMMod::WFMModInputFile)) + if (((++m_tickCount & 0xf) == 0) && (m_settings.m_modAFInput == WFMModSettings::WFMModInputFile)) { WFMMod::MsgConfigureFileSourceStreamTiming* message = WFMMod::MsgConfigureFileSourceStreamTiming::create(); m_wfmMod->getInputMessageQueue()->push(message); @@ -428,7 +483,7 @@ void WFMModGUI::updateWithStreamData() { QTime recordLength(0, 0, 0, 0); recordLength = recordLength.addSecs(m_recordLength); - QString s_time = recordLength.toString("hh:mm:ss"); + QString s_time = recordLength.toString("HH:mm:ss"); ui->recordLengthText->setText(s_time); updateWithStreamTime(); } @@ -447,8 +502,8 @@ void WFMModGUI::updateWithStreamTime() QTime t(0, 0, 0, 0); t = t.addSecs(t_sec); t = t.addMSecs(t_msec); - QString s_timems = t.toString("hh:mm:ss.zzz"); - QString s_time = t.toString("hh:mm:ss"); + QString s_timems = t.toString("HH:mm:ss.zzz"); + QString s_time = t.toString("HH:mm:ss"); ui->relTimeText->setText(s_timems); if (!m_enableNavTime) diff --git a/plugins/channeltx/modwfm/wfmmodgui.h b/plugins/channeltx/modwfm/wfmmodgui.h index aca95366b..b5f39e2f0 100644 --- a/plugins/channeltx/modwfm/wfmmodgui.h +++ b/plugins/channeltx/modwfm/wfmmodgui.h @@ -76,7 +76,6 @@ private: int m_samplesCount; std::size_t m_tickCount; bool m_enableNavTime; - WFMMod::WFMModInputAF m_modAFInput; MessageQueue m_inputMessageQueue; explicit WFMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent = 0); @@ -120,8 +119,10 @@ private slots: void on_showFileDialog_clicked(bool checked); void onWidgetRolled(QWidget* widget, bool rollDown); + void onMenuDialogCalled(const QPoint& p); void configureFileName(); + void audioSelect(); void tick(); }; diff --git a/plugins/channeltx/modwfm/wfmmodgui.ui b/plugins/channeltx/modwfm/wfmmodgui.ui index 5291a1f21..e69ef7ee6 100644 --- a/plugins/channeltx/modwfm/wfmmodgui.ui +++ b/plugins/channeltx/modwfm/wfmmodgui.ui @@ -24,7 +24,7 @@
- Sans Serif + Liberation Sans 9 @@ -56,16 +56,7 @@ 3 - - 2 - - - 2 - - - 2 - - + 2 @@ -104,7 +95,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -398,6 +389,12 @@ 0
+ + + Liberation Mono + 8 + + Level (% full range) top trace: average, bottom trace: instantaneous peak, tip: peak hold diff --git a/plugins/channeltx/modwfm/wfmmodplugin.cpp b/plugins/channeltx/modwfm/wfmmodplugin.cpp index ae13046d8..b526dc703 100644 --- a/plugins/channeltx/modwfm/wfmmodplugin.cpp +++ b/plugins/channeltx/modwfm/wfmmodplugin.cpp @@ -15,16 +15,17 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include #include "plugin/pluginapi.h" +#ifndef SERVER_MODE #include "wfmmodgui.h" +#endif #include "wfmmod.h" #include "wfmmodplugin.h" const PluginDescriptor WFMModPlugin::m_pluginDescriptor = { QString("WFM Modulator"), - QString("3.12.0"), + QString("4.1.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, @@ -50,10 +51,19 @@ void WFMModPlugin::initPlugin(PluginAPI* pluginAPI) m_pluginAPI->registerTxChannel(WFMMod::m_channelIdURI, WFMMod::m_channelId, this); } +#ifdef SERVER_MODE +PluginInstanceGUI* WFMModPlugin::createTxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSource *txChannel __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* WFMModPlugin::createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel) { return WFMModGUI::create(m_pluginAPI, deviceUISet, txChannel); } +#endif BasebandSampleSource* WFMModPlugin::createTxChannelBS(DeviceSinkAPI *deviceAPI) { diff --git a/plugins/channeltx/modwfm/wfmmodsettings.cpp b/plugins/channeltx/modwfm/wfmmodsettings.cpp index 864db9264..6f9f06873 100644 --- a/plugins/channeltx/modwfm/wfmmodsettings.cpp +++ b/plugins/channeltx/modwfm/wfmmodsettings.cpp @@ -42,12 +42,13 @@ void WFMModSettings::resetToDefaults() m_afBandwidth = 15000.0f; m_fmDeviation = 50000.0f; m_toneFrequency = 1000.0f; - m_audioSampleRate = DSPEngine::instance()->getAudioSampleRate(); m_volumeFactor = 1.0f; m_channelMute = false; m_playLoop = false; m_rgbColor = QColor(0, 0, 255).rgb(); m_title = "WFM Modulator"; + m_modAFInput = WFMModInputNone; + m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; } QByteArray WFMModSettings::serialize() const @@ -71,6 +72,8 @@ QByteArray WFMModSettings::serialize() const } s.writeString(10, m_title); + s.writeString(11, m_audioDeviceName); + s.writeS32(12, (int) m_modAFInput); return s.final(); } @@ -110,6 +113,14 @@ bool WFMModSettings::deserialize(const QByteArray& data) } d.readString(10, &m_title, "WFM Modulator"); + d.readString(11, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName); + + d.readS32(12, &tmp, 0); + if ((tmp < 0) || (tmp > (int) WFMModInputAF::WFMModInputTone)) { + m_modAFInput = WFMModInputNone; + } else { + m_modAFInput = (WFMModInputAF) tmp; + } return true; } diff --git a/plugins/channeltx/modwfm/wfmmodsettings.h b/plugins/channeltx/modwfm/wfmmodsettings.h index f83b8cf22..d511e69db 100644 --- a/plugins/channeltx/modwfm/wfmmodsettings.h +++ b/plugins/channeltx/modwfm/wfmmodsettings.h @@ -23,6 +23,15 @@ class Serializable; struct WFMModSettings { + typedef enum + { + WFMModInputNone, + WFMModInputTone, + WFMModInputFile, + WFMModInputAudio, + WFMModInputCWTone + } WFMModInputAF; + static const int m_nbRfBW; static const int m_rfBW[]; @@ -32,11 +41,12 @@ struct WFMModSettings float m_fmDeviation; float m_toneFrequency; float m_volumeFactor; - quint32 m_audioSampleRate; bool m_channelMute; bool m_playLoop; quint32 m_rgbColor; QString m_title; + WFMModInputAF m_modAFInput; + QString m_audioDeviceName; Serializable *m_channelMarker; Serializable *m_cwKeyerGUI; diff --git a/plugins/channeltx/udpsource/CMakeLists.txt b/plugins/channeltx/udpsource/CMakeLists.txt new file mode 100644 index 000000000..9a2ab1e02 --- /dev/null +++ b/plugins/channeltx/udpsource/CMakeLists.txt @@ -0,0 +1,55 @@ +project(udpsource) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +set(udpsource_SOURCES + udpsource.cpp + udpsourcegui.cpp + udpsourceplugin.cpp + udpsourceudphandler.cpp + udpsourcemsg.cpp + udpsourcesettings.cpp +) + +set(udpsource_HEADERS + udpsource.h + udpsourcegui.h + udpsourceplugin.h + udpsourceudphandler.h + udpsourcemsg.h + udpsourcesettings.h +) + +set(udpsource_FORMS + udpsourcegui.ui +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +#include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +qt5_wrap_ui(udpsource_FORMS_HEADERS ${udpsource_FORMS}) + +add_library(udpsource SHARED + ${udpsource_SOURCES} + ${udpsource_HEADERS_MOC} + ${udpsource_FORMS_HEADERS} +) + +target_link_libraries(udpsource + ${QT_LIBRARIES} + sdrbase + sdrgui + swagger +) + +target_link_libraries(udpsource Qt5::Core Qt5::Widgets Qt5::Network) + +install(TARGETS udpsource DESTINATION lib/plugins/channeltx) diff --git a/plugins/channeltx/udpsink/readme.md b/plugins/channeltx/udpsource/readme.md similarity index 82% rename from plugins/channeltx/udpsink/readme.md rename to plugins/channeltx/udpsource/readme.md index 8c81da302..69ddd8b1b 100644 --- a/plugins/channeltx/udpsink/readme.md +++ b/plugins/channeltx/udpsource/readme.md @@ -1,8 +1,8 @@ -

UDP sink plugin

+

UDP Source plugin

Introduction

-By "sink" one should undetstand a sink of samples for the outside of SDRangel application. An external application establishes an UDP connection to the plugin at the given address and port and samples are directed to it. In fact it can also come frome SDRangel itself using the UDP source plugin +By "source" one should understand a source of samples that feed the baseband of the transmitting device. An external application establishes an UDP connection to the plugin at the given address and port and samples are directed to it. In fact it can also come from SDRangel itself using the UDP source plugin The UDP block size or UDP payload size is optimized for 512 bytes but other sizes are acceptable. @@ -10,15 +10,15 @@ This plugin is available for Linux and Mac O/S only.

Interface

-![UDP Sink plugin GUI](../../../doc/img/UDPsink_plugin.png) +![UDP Source plugin GUI](../../../doc/img/UDPsource_plugin.png)

1: Frequency shift from center frequency of reception

-Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

2: Input channel power

-Total power in dB relative to a +/- 1.0 amplitude signal received fron UDP. +Total power in dB relative to a +/- 1.0 amplitude signal received from UDP.

3: Output channel power

@@ -26,7 +26,7 @@ Total power in dB relative to a +/- 1.0 amplitude signal sent in the channel.

4: Channel mute

-Use this button to switch off the RF on the channel. The background of the button lits in green when a signal can be sent. +Use this button to switch off the RF on the channel. The background of the button lights in green when a signal can be sent.

5: UDP address and port

@@ -36,13 +36,13 @@ The display is in the format `address:data port`

6: Input sample rate

-Sample rate in samples per second of the signal that is recveived on UDP. The actual byte rate depends on the type of sample which corresponds to a number of bytes per sample. +Sample rate in samples per second of the signal that is received on UDP. The actual byte rate depends on the type of sample which corresponds to a number of bytes per sample.

7: Type of samples

Combo box to specify the type of samples that are received and sent in the channel. - - `S16LE I/Q`: Raw I/Q samples on signed 16 bits integers with Little Endian layout. Use it with software that sends I/Q data as output like GNUradio with the `UDP source` block. The output is interleaved I and Q samples. It can also match the UDP source plugin with the same `S16LE I/Q` format and can be used for linear transposition. + - `SnLE I/Q`: Raw I/Q samples on signed 16 or 24 bits integers with Little Endian layout. Use it with software that sends I/Q data as output like GNUradio with the `UDP source` block. The output is interleaved I and Q samples. It can also match the UDP source plugin with the same `S16LE I/Q` format and compiled for the same sample I/Q size and can be used for linear transposition. - `S16LE NFM`: receives 16 bits signed integers on 1 (mono) or 2 (stereo) channels with Little Endian layout. It produces a mono signal with narrow bandwidth FM modulation. Stereo input channels are mixed before modulation. There is no DC block so it can be used with modulating signals where the DC component is important like digital signals. - `S16LE LSB`: Takes a 1 (mono) or 2 (stereo) channels AF signal and produces a LSB modulated signal. Stereo input channels are mixed before modulation. - `S16LE USB`: Takes a 1 (mono) or 2 (stereo) channels AF signal and produces a USB modulated signal. Stereo input channels are mixed before modulation. @@ -62,7 +62,7 @@ This is the maximum FM deviation in Hz for a +/- 1.0 amplitude modulating signal

11: AM percentage modulation

-this is the AM precentage modulation when a +/- 1.0 amplitude modulating signal is applied. herefore it is active only for `S16LE AM Mono` sample format. +this is the AM percentage modulation when a +/- 1.0 amplitude modulating signal is applied. Therefore it is active only for `S16LE AM Mono` sample format.

12: Apply (validation) button

@@ -86,7 +86,7 @@ The button sets the delay after which a signal constantly above the squelch thre

14: signal amplitude percentage of maximum

-The gain (15) should be adjusted so that the peak amplitude (small red vertical bar) never exceeds 100%. Above 100% the signal is clipper which results in distorsion. +The gain (15) should be adjusted so that the peak amplitude (small red vertical bar) never exceeds 100%. Above 100% the signal is clipper which results in distortion.

15: Input and output Gains

diff --git a/plugins/channeltx/udpsink/udpsink.cpp b/plugins/channeltx/udpsource/udpsource.cpp similarity index 63% rename from plugins/channeltx/udpsink/udpsink.cpp rename to plugins/channeltx/udpsource/udpsource.cpp index 24060a85f..38b25e946 100644 --- a/plugins/channeltx/udpsink/udpsink.cpp +++ b/plugins/channeltx/udpsource/udpsource.cpp @@ -16,24 +16,28 @@ #include +#include "SWGChannelSettings.h" +#include "SWGChannelReport.h" +#include "SWGUDPSourceReport.h" + #include "device/devicesinkapi.h" #include "dsp/upchannelizer.h" #include "dsp/threadedbasebandsamplesource.h" #include "dsp/dspcommands.h" #include "util/db.h" -#include "udpsinkmsg.h" -#include "udpsink.h" +#include "udpsource.h" +#include "udpsourcemsg.h" -MESSAGE_CLASS_DEFINITION(UDPSink::MsgConfigureUDPSink, Message) -MESSAGE_CLASS_DEFINITION(UDPSink::MsgConfigureChannelizer, Message) -MESSAGE_CLASS_DEFINITION(UDPSink::MsgUDPSinkSpectrum, Message) -MESSAGE_CLASS_DEFINITION(UDPSink::MsgResetReadIndex, Message) +MESSAGE_CLASS_DEFINITION(UDPSource::MsgConfigureUDPSource, Message) +MESSAGE_CLASS_DEFINITION(UDPSource::MsgConfigureChannelizer, Message) +MESSAGE_CLASS_DEFINITION(UDPSource::MsgUDPSourceSpectrum, Message) +MESSAGE_CLASS_DEFINITION(UDPSource::MsgResetReadIndex, Message) -const QString UDPSink::m_channelIdURI = "sdrangel.channeltx.udpsink"; -const QString UDPSink::m_channelId = "UDPSink"; +const QString UDPSource::m_channelIdURI = "sdrangel.channeltx.udpsource"; +const QString UDPSource::m_channelId = "UDPSource"; -UDPSink::UDPSink(DeviceSinkAPI *deviceAPI) : +UDPSource::UDPSource(DeviceSinkAPI *deviceAPI) : ChannelSourceAPI(m_channelIdURI), m_deviceAPI(deviceAPI), m_basebandSampleRate(48000), @@ -67,37 +71,37 @@ UDPSink::UDPSink(DeviceSinkAPI *deviceAPI) : m_SSBFilter = new fftfilt(m_settings.m_lowCutoff / m_settings.m_inputSampleRate, m_settings.m_rfBandwidth / m_settings.m_inputSampleRate, m_ssbFftLen); m_SSBFilterBuffer = new Complex[m_ssbFftLen>>1]; // filter returns data exactly half of its size + applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); + m_channelizer = new UpChannelizer(this); m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this); m_deviceAPI->addThreadedSource(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); - - applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true); - applySettings(m_settings, true); } -UDPSink::~UDPSink() +UDPSource::~UDPSource() { - delete[] m_SSBFilterBuffer; - delete m_SSBFilter; m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSource(m_threadedChannelizer); delete m_threadedChannelizer; delete m_channelizer; + delete m_SSBFilter; + delete[] m_SSBFilterBuffer; } -void UDPSink::start() +void UDPSource::start() { m_udpHandler.start(); applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true); } -void UDPSink::stop() +void UDPSource::stop() { m_udpHandler.stop(); } -void UDPSink::pull(Sample& sample) +void UDPSource::pull(Sample& sample) { if (m_settings.m_channelMute) { @@ -143,9 +147,9 @@ void UDPSink::pull(Sample& sample) sample.m_imag = (FixReal) ci.imag(); } -void UDPSink::modulateSample() +void UDPSource::modulateSample() { - if (m_settings.m_sampleFormat == UDPSinkSettings::FormatS16LE) // Linear I/Q transponding + if (m_settings.m_sampleFormat == UDPSourceSettings::FormatSnLE) // Linear I/Q transponding { Sample s; @@ -169,9 +173,9 @@ void UDPSink::modulateSample() m_modSample.imag(0.0f); } } - else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatNFM) + else if (m_settings.m_sampleFormat == UDPSourceSettings::FormatNFM) { - FixReal t; + qint16 t; readMonoSample(t); m_inMovingAverage.feed((t*t)/1073741824.0); @@ -192,9 +196,9 @@ void UDPSink::modulateSample() m_modSample.imag(0.0f); } } - else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatAM) + else if (m_settings.m_sampleFormat == UDPSourceSettings::FormatAM) { - FixReal t; + qint16 t; readMonoSample(t); m_inMovingAverage.feed((t*t)/(SDR_TX_SCALED*SDR_TX_SCALED)); m_inMagsq = m_inMovingAverage.average(); @@ -213,9 +217,9 @@ void UDPSink::modulateSample() m_modSample.imag(0.0f); } } - else if ((m_settings.m_sampleFormat == UDPSinkSettings::FormatLSB) || (m_settings.m_sampleFormat == UDPSinkSettings::FormatUSB)) + else if ((m_settings.m_sampleFormat == UDPSourceSettings::FormatLSB) || (m_settings.m_sampleFormat == UDPSourceSettings::FormatUSB)) { - FixReal t; + qint16 t; Complex c, ci; fftfilt::cmplx *filtered; int n_out = 0; @@ -231,7 +235,7 @@ void UDPSink::modulateSample() ci.real((t / SDR_TX_SCALEF) * m_settings.m_gainOut); ci.imag(0.0f); - n_out = m_SSBFilter->runSSB(ci, &filtered, (m_settings.m_sampleFormat == UDPSinkSettings::FormatUSB)); + n_out = m_SSBFilter->runSSB(ci, &filtered, (m_settings.m_sampleFormat == UDPSourceSettings::FormatUSB)); if (n_out > 0) { @@ -275,7 +279,7 @@ void UDPSink::modulateSample() } } -void UDPSink::calculateLevel(Real sample) +void UDPSource::calculateLevel(Real sample) { if (m_levelCalcCount < m_levelNbSamples) { @@ -294,7 +298,7 @@ void UDPSink::calculateLevel(Real sample) } } -void UDPSink::calculateLevel(Complex sample) +void UDPSource::calculateLevel(Complex sample) { Real t = std::abs(sample); @@ -314,12 +318,12 @@ void UDPSink::calculateLevel(Complex sample) } } -bool UDPSink::handleMessage(const Message& cmd) +bool UDPSource::handleMessage(const Message& cmd) { if (UpChannelizer::MsgChannelizerNotification::match(cmd)) { UpChannelizer::MsgChannelizerNotification& notif = (UpChannelizer::MsgChannelizerNotification&) cmd; - qDebug() << "UDPSink::handleMessage: MsgChannelizerNotification"; + qDebug() << "UDPSource::handleMessage: MsgChannelizerNotification"; applyChannelSettings(notif.getBasebandSampleRate(), notif.getSampleRate(), notif.getFrequencyOffset()); @@ -328,7 +332,7 @@ bool UDPSink::handleMessage(const Message& cmd) else if (MsgConfigureChannelizer::match(cmd)) { MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; - qDebug() << "UDPSink::handleMessage: MsgConfigureChannelizer:" + qDebug() << "UDPSource::handleMessage: MsgConfigureChannelizer:" << " sampleRate: " << cfg.getSampleRate() << " centerFrequency: " << cfg.getCenterFrequency(); @@ -338,18 +342,18 @@ bool UDPSink::handleMessage(const Message& cmd) return true; } - else if (MsgConfigureUDPSink::match(cmd)) + else if (MsgConfigureUDPSource::match(cmd)) { - MsgConfigureUDPSink& cfg = (MsgConfigureUDPSink&) cmd; - qDebug() << "UDPSink::handleMessage: MsgConfigureUDPSink"; + MsgConfigureUDPSource& cfg = (MsgConfigureUDPSource&) cmd; + qDebug() << "UDPSource::handleMessage: MsgConfigureUDPSource"; applySettings(cfg.getSettings(), cfg.getForce()); return true; } - else if (UDPSinkMessages::MsgSampleRateCorrection::match(cmd)) + else if (UDPSourceMessages::MsgSampleRateCorrection::match(cmd)) { - UDPSinkMessages::MsgSampleRateCorrection& cfg = (UDPSinkMessages::MsgSampleRateCorrection&) cmd; + UDPSourceMessages::MsgSampleRateCorrection& cfg = (UDPSourceMessages::MsgSampleRateCorrection&) cmd; Real newSampleRate = m_actualInputSampleRate + cfg.getCorrectionFactor() * m_actualInputSampleRate; // exclude values too way out nominal sample rate (20%) @@ -374,7 +378,7 @@ bool UDPSink::handleMessage(const Message& cmd) if (m_sampleRateAvgCounter == m_sampleRateAverageItems) { float avgRate = m_sampleRateSum / m_sampleRateAverageItems; - qDebug("UDPSink::handleMessage: MsgSampleRateCorrection: corr: %+.6f new rate: %.0f: avg rate: %.0f", + qDebug("UDPSource::handleMessage: MsgSampleRateCorrection: corr: %+.6f new rate: %.0f: avg rate: %.0f", cfg.getCorrectionFactor(), m_actualInputSampleRate, avgRate); @@ -384,7 +388,7 @@ bool UDPSink::handleMessage(const Message& cmd) } // else // { -// qDebug("UDPSink::handleMessage: MsgSampleRateCorrection: corr: %+.6f new rate: %.0f", +// qDebug("UDPSource::handleMessage: MsgSampleRateCorrection: corr: %+.6f new rate: %.0f", // cfg.getCorrectionFactor(), // m_actualInputSampleRate); // } @@ -399,11 +403,11 @@ bool UDPSink::handleMessage(const Message& cmd) return true; } - else if (MsgUDPSinkSpectrum::match(cmd)) + else if (MsgUDPSourceSpectrum::match(cmd)) { - MsgUDPSinkSpectrum& spc = (MsgUDPSinkSpectrum&) cmd; + MsgUDPSourceSpectrum& spc = (MsgUDPSourceSpectrum&) cmd; m_spectrumEnabled = spc.getEnabled(); - qDebug() << "UDPSink::handleMessage: MsgUDPSinkSpectrum: m_spectrumEnabled: " << m_spectrumEnabled; + qDebug() << "UDPSource::handleMessage: MsgUDPSourceSpectrum: m_spectrumEnabled: " << m_spectrumEnabled; return true; } @@ -413,7 +417,7 @@ bool UDPSink::handleMessage(const Message& cmd) m_udpHandler.resetReadIndex(); m_settingsMutex.unlock(); - qDebug() << "UDPSink::handleMessage: MsgResetReadIndex"; + qDebug() << "UDPSource::handleMessage: MsgResetReadIndex"; return true; } @@ -434,21 +438,21 @@ bool UDPSink::handleMessage(const Message& cmd) } } -void UDPSink::setSpectrum(bool enabled) +void UDPSource::setSpectrum(bool enabled) { - Message* cmd = MsgUDPSinkSpectrum::create(enabled); + Message* cmd = MsgUDPSourceSpectrum::create(enabled); getInputMessageQueue()->push(cmd); } -void UDPSink::resetReadIndex() +void UDPSource::resetReadIndex() { Message* cmd = MsgResetReadIndex::create(); getInputMessageQueue()->push(cmd); } -void UDPSink::applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force) +void UDPSource::applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force) { - qDebug() << "UDPSink::applyChannelSettings:" + qDebug() << "UDPSource::applyChannelSettings:" << " basebandSampleRate: " << basebandSampleRate << " outputSampleRate: " << outputSampleRate << " inputFrequencyOffset: " << inputFrequencyOffset; @@ -476,13 +480,14 @@ void UDPSink::applyChannelSettings(int basebandSampleRate, int outputSampleRate, m_inputFrequencyOffset = inputFrequencyOffset; } -void UDPSink::applySettings(const UDPSinkSettings& settings, bool force) +void UDPSource::applySettings(const UDPSourceSettings& settings, bool force) { - qDebug() << "UDPSink::applySettings:" + qDebug() << "UDPSource::applySettings:" << " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset << " m_sampleFormat: " << settings.m_sampleFormat << " m_inputSampleRate: " << settings.m_inputSampleRate << " m_rfBandwidth: " << settings.m_rfBandwidth + << " m_lowCutoff: " << settings.m_lowCutoff << " m_fmDeviation: " << settings.m_fmDeviation << " m_udpAddressStr: " << settings.m_udpAddress << " m_udpPort: " << settings.m_udpPort @@ -497,6 +502,7 @@ void UDPSink::applySettings(const UDPSinkSettings& settings, bool force) << " force: " << force; if((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || + (settings.m_lowCutoff != m_settings.m_lowCutoff) || (settings.m_inputSampleRate != m_settings.m_inputSampleRate) || force) { m_settingsMutex.lock(); @@ -569,24 +575,178 @@ void UDPSink::applySettings(const UDPSinkSettings& settings, bool force) m_settings = settings; } -QByteArray UDPSink::serialize() const +QByteArray UDPSource::serialize() const { return m_settings.serialize(); } -bool UDPSink::deserialize(const QByteArray& data) +bool UDPSource::deserialize(const QByteArray& data) { if (m_settings.deserialize(data)) { - MsgConfigureUDPSink *msg = MsgConfigureUDPSink::create(m_settings, true); + MsgConfigureUDPSource *msg = MsgConfigureUDPSource::create(m_settings, true); m_inputMessageQueue.push(msg); return true; } else { m_settings.resetToDefaults(); - MsgConfigureUDPSink *msg = MsgConfigureUDPSink::create(m_settings, true); + MsgConfigureUDPSource *msg = MsgConfigureUDPSource::create(m_settings, true); m_inputMessageQueue.push(msg); return false; } } + +int UDPSource::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setUdpSourceSettings(new SWGSDRangel::SWGUDPSourceSettings()); + response.getUdpSourceSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int UDPSource::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage __attribute__((unused))) +{ + UDPSourceSettings settings = m_settings; + bool frequencyOffsetChanged = false; + + if (channelSettingsKeys.contains("sampleFormat")) { + settings.m_sampleFormat = (UDPSourceSettings::SampleFormat) response.getUdpSourceSettings()->getSampleFormat(); + } + if (channelSettingsKeys.contains("inputSampleRate")) { + settings.m_inputSampleRate = response.getUdpSourceSettings()->getInputSampleRate(); + } + if (channelSettingsKeys.contains("inputFrequencyOffset")) + { + settings.m_inputFrequencyOffset = response.getUdpSourceSettings()->getInputFrequencyOffset(); + frequencyOffsetChanged = true; + } + if (channelSettingsKeys.contains("rfBandwidth")) { + settings.m_rfBandwidth = response.getUdpSourceSettings()->getRfBandwidth(); + } + if (channelSettingsKeys.contains("lowCutoff")) { + settings.m_lowCutoff = response.getUdpSourceSettings()->getLowCutoff(); + } + if (channelSettingsKeys.contains("fmDeviation")) { + settings.m_fmDeviation = response.getUdpSourceSettings()->getFmDeviation(); + } + if (channelSettingsKeys.contains("amModFactor")) { + settings.m_amModFactor = response.getUdpSourceSettings()->getAmModFactor(); + } + if (channelSettingsKeys.contains("channelMute")) { + settings.m_channelMute = response.getUdpSourceSettings()->getChannelMute() != 0; + } + if (channelSettingsKeys.contains("gainIn")) { + settings.m_gainIn = response.getUdpSourceSettings()->getGainIn(); + } + if (channelSettingsKeys.contains("gainOut")) { + settings.m_gainOut = response.getUdpSourceSettings()->getGainOut(); + } + if (channelSettingsKeys.contains("squelch")) { + settings.m_squelch = response.getUdpSourceSettings()->getSquelch(); + } + if (channelSettingsKeys.contains("squelchGate")) { + settings.m_squelchGate = response.getUdpSourceSettings()->getSquelchGate(); + } + if (channelSettingsKeys.contains("squelchEnabled")) { + settings.m_squelchEnabled = response.getUdpSourceSettings()->getSquelchEnabled() != 0; + } + if (channelSettingsKeys.contains("autoRWBalance")) { + settings.m_autoRWBalance = response.getUdpSourceSettings()->getAutoRwBalance() != 0; + } + if (channelSettingsKeys.contains("stereoInput")) { + settings.m_stereoInput = response.getUdpSourceSettings()->getStereoInput() != 0; + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getUdpSourceSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("udpAddress")) { + settings.m_udpAddress = *response.getUdpSourceSettings()->getUdpAddress(); + } + if (channelSettingsKeys.contains("udpPort")) { + settings.m_udpPort = response.getUdpSourceSettings()->getUdpPort(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getUdpSourceSettings()->getTitle(); + } + + if (frequencyOffsetChanged) + { + UDPSource::MsgConfigureChannelizer *msgChan = UDPSource::MsgConfigureChannelizer::create( + settings.m_inputSampleRate, + settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(msgChan); + } + + MsgConfigureUDPSource *msg = MsgConfigureUDPSource::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureUDPSource *msgToGUI = MsgConfigureUDPSource::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int UDPSource::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setUdpSourceReport(new SWGSDRangel::SWGUDPSourceReport()); + response.getUdpSourceReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + +void UDPSource::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const UDPSourceSettings& settings) +{ + response.getUdpSourceSettings()->setSampleFormat((int) settings.m_sampleFormat); + response.getUdpSourceSettings()->setInputSampleRate(settings.m_inputSampleRate); + response.getUdpSourceSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getUdpSourceSettings()->setRfBandwidth(settings.m_rfBandwidth); + response.getUdpSourceSettings()->setLowCutoff(settings.m_lowCutoff); + response.getUdpSourceSettings()->setFmDeviation(settings.m_fmDeviation); + response.getUdpSourceSettings()->setAmModFactor(settings.m_amModFactor); + response.getUdpSourceSettings()->setChannelMute(settings.m_channelMute ? 1 : 0); + response.getUdpSourceSettings()->setGainIn(settings.m_gainIn); + response.getUdpSourceSettings()->setGainOut(settings.m_gainOut); + response.getUdpSourceSettings()->setSquelch(settings.m_squelch); + response.getUdpSourceSettings()->setSquelchGate(settings.m_squelchGate); + response.getUdpSourceSettings()->setSquelchEnabled(settings.m_squelchEnabled ? 1 : 0); + response.getUdpSourceSettings()->setAutoRwBalance(settings.m_autoRWBalance ? 1 : 0); + response.getUdpSourceSettings()->setStereoInput(settings.m_stereoInput ? 1 : 0); + response.getUdpSourceSettings()->setRgbColor(settings.m_rgbColor); + + if (response.getUdpSourceSettings()->getUdpAddress()) { + *response.getUdpSourceSettings()->getUdpAddress() = settings.m_udpAddress; + } else { + response.getUdpSourceSettings()->setUdpAddress(new QString(settings.m_udpAddress)); + } + + response.getUdpSourceSettings()->setUdpPort(settings.m_udpPort); + + if (response.getUdpSourceSettings()->getTitle()) { + *response.getUdpSourceSettings()->getTitle() = settings.m_title; + } else { + response.getUdpSourceSettings()->setTitle(new QString(settings.m_title)); + } +} + +void UDPSource::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + response.getUdpSourceReport()->setInputPowerDb(CalcDb::dbPower(getInMagSq())); + response.getUdpSourceReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq())); + response.getUdpSourceReport()->setSquelch(m_squelchOpen ? 1 : 0); + response.getUdpSourceReport()->setBufferGauge(getBufferGauge()); + response.getUdpSourceReport()->setChannelSampleRate(m_outputSampleRate); +} diff --git a/plugins/channeltx/udpsink/udpsink.h b/plugins/channeltx/udpsource/udpsource.h similarity index 79% rename from plugins/channeltx/udpsink/udpsink.h rename to plugins/channeltx/udpsource/udpsource.h index 910936b33..809834f82 100644 --- a/plugins/channeltx/udpsink/udpsink.h +++ b/plugins/channeltx/udpsource/udpsource.h @@ -14,8 +14,8 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef PLUGINS_CHANNELTX_UDPSINK_UDPSINK_H_ -#define PLUGINS_CHANNELTX_UDPSINK_UDPSINK_H_ +#ifndef PLUGINS_CHANNELTX_UDPSINK_UDPSOURCE_H_ +#define PLUGINS_CHANNELTX_UDPSINK_UDPSOURCE_H_ #include @@ -28,34 +28,34 @@ #include "dsp/fftfilt.h" #include "util/message.h" -#include "udpsinkudphandler.h" -#include "udpsinksettings.h" +#include "udpsourcesettings.h" +#include "udpsourceudphandler.h" class DeviceSinkAPI; class ThreadedBasebandSampleSource; class UpChannelizer; -class UDPSink : public BasebandSampleSource, public ChannelSourceAPI { +class UDPSource : public BasebandSampleSource, public ChannelSourceAPI { Q_OBJECT public: - class MsgConfigureUDPSink : public Message { + class MsgConfigureUDPSource : public Message { MESSAGE_CLASS_DECLARATION public: - const UDPSinkSettings& getSettings() const { return m_settings; } + const UDPSourceSettings& getSettings() const { return m_settings; } bool getForce() const { return m_force; } - static MsgConfigureUDPSink* create(const UDPSinkSettings& settings, bool force) + static MsgConfigureUDPSource* create(const UDPSourceSettings& settings, bool force) { - return new MsgConfigureUDPSink(settings, force); + return new MsgConfigureUDPSource(settings, force); } private: - UDPSinkSettings m_settings; + UDPSourceSettings m_settings; bool m_force; - MsgConfigureUDPSink(const UDPSinkSettings& settings, bool force) : + MsgConfigureUDPSource(const UDPSourceSettings& settings, bool force) : Message(), m_settings(settings), m_force(force) @@ -86,8 +86,8 @@ public: { } }; - UDPSink(DeviceSinkAPI *deviceAPI); - virtual ~UDPSink(); + UDPSource(DeviceSinkAPI *deviceAPI); + virtual ~UDPSource(); virtual void destroy() { delete this; } void setSpectrumSink(BasebandSampleSink* spectrum) { m_spectrum = spectrum; } @@ -99,13 +99,25 @@ public: virtual void getIdentifier(QString& id) { id = objectName(); } virtual void getTitle(QString& title) { title = m_settings.m_title; } - virtual void setName(const QString& name) { setObjectName(name); } - virtual QString getName() const { return objectName(); } virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; } virtual QByteArray serialize() const; virtual bool deserialize(const QByteArray& data); + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + double getMagSq() const { return m_magsq; } double getInMagSq() const { return m_inMagsq; } int32_t getBufferGauge() const { return m_udpHandler.getBufferGauge(); } @@ -127,21 +139,21 @@ signals: void levelChanged(qreal rmsLevel, qreal peakLevel, int numSamples); private: - class MsgUDPSinkSpectrum : public Message { + class MsgUDPSourceSpectrum : public Message { MESSAGE_CLASS_DECLARATION public: bool getEnabled() const { return m_enabled; } - static MsgUDPSinkSpectrum* create(bool enabled) + static MsgUDPSourceSpectrum* create(bool enabled) { - return new MsgUDPSinkSpectrum(enabled); + return new MsgUDPSourceSpectrum(enabled); } private: bool m_enabled; - MsgUDPSinkSpectrum(bool enabled) : + MsgUDPSourceSpectrum(bool enabled) : Message(), m_enabled(enabled) { } @@ -171,7 +183,7 @@ private: int m_basebandSampleRate; Real m_outputSampleRate; int m_inputFrequencyOffset; - UDPSinkSettings m_settings; + UDPSourceSettings m_settings; Real m_squelch; @@ -194,7 +206,7 @@ private: MovingAverage m_movingAverage; MovingAverage m_inMovingAverage; - UDPSinkUDPHandler m_udpHandler; + UDPSourceUDPHandler m_udpHandler; Real m_actualInputSampleRate; //!< sample rate with UDP buffer skew compensation double m_sampleRateSum; int m_sampleRateAvgCounter; @@ -220,11 +232,14 @@ private: static const int m_ssbFftLen = 1024; void applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force = false); - void applySettings(const UDPSinkSettings& settings, bool force = false); + void applySettings(const UDPSourceSettings& settings, bool force = false); void modulateSample(); void calculateLevel(Real sample); void calculateLevel(Complex sample); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const UDPSourceSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); + inline void calculateSquelch(double value) { if ((!m_settings.m_squelchEnabled) || (value > m_squelch)) @@ -283,14 +298,14 @@ private: } } - inline void readMonoSample(FixReal& t) + inline void readMonoSample(qint16& t) { - Sample s; if (m_settings.m_stereoInput) { - m_udpHandler.readSample(s); - t = ((s.m_real + s.m_imag) * m_settings.m_gainIn) / 2; + AudioSample a; + m_udpHandler.readSample(a); + t = ((a.l + a.r) * m_settings.m_gainIn) / 2; } else { @@ -303,4 +318,4 @@ private: -#endif /* PLUGINS_CHANNELTX_UDPSINK_UDPSINK_H_ */ +#endif /* PLUGINS_CHANNELTX_UDPSINK_UDPSOURCE_H_ */ diff --git a/plugins/samplesource/airspyhfi/airspyhfi.pro b/plugins/channeltx/udpsource/udpsource.pro similarity index 55% rename from plugins/samplesource/airspyhfi/airspyhfi.pro rename to plugins/channeltx/udpsource/udpsource.pro index 547c86b8e..835fb8fe8 100644 --- a/plugins/samplesource/airspyhfi/airspyhfi.pro +++ b/plugins/channeltx/udpsource/udpsource.pro @@ -1,23 +1,15 @@ #-------------------------------------------------------- # -# Pro file for Android and Windows builds with Qt Creator +# Pro file for Windows builds with Qt Creator # #-------------------------------------------------------- TEMPLATE = lib CONFIG += plugin -QT += core gui widgets multimedia opengl +QT += core gui widgets multimedia opengl network -TARGET = inputairspyhfi - -CONFIG(MINGW32):LIBAIRSPYHFSRC = "D:\softs\airspyhf" -CONFIG(MINGW64):LIBAIRSPYHFSRC = "D:\softs\airspyhf" -INCLUDEPATH += $$PWD -INCLUDEPATH += ../../../sdrbase -INCLUDEPATH += ../../../sdrgui -INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client -INCLUDEPATH += $$LIBAIRSPYHFSRC +TARGET = udpsource DEFINES += USE_SSE2=1 QMAKE_CXXFLAGS += -msse2 @@ -25,26 +17,33 @@ DEFINES += USE_SSE4_1=1 QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 +INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports +INCLUDEPATH += ../../../sdrbase +INCLUDEPATH += ../../../sdrgui +INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client + CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug -SOURCES += airspyhfigui.cpp\ - airspyhfiinput.cpp\ - airspyhfiplugin.cpp\ - airspyhfisettings.cpp\ - airspyhfithread.cpp +SOURCES += udpsource.cpp\ + udpsourcegui.cpp\ + udpsourceplugin.cpp\ + udpsourcemsg.cpp\ + udpsourceudphandler.cpp\ + udpsourcesettings.cpp -HEADERS += airspyhfigui.h\ - airspyhfiinput.h\ - airspyhfiplugin.h\ - airspyhfisettings.h\ - airspyhfithread.h +HEADERS += udpsource.h\ + udpsourcegui.h\ + udpsourceplugin.h\ + udpsourcemsg.h\ + udpsourceudphandler.h\ + udpsourcesettings.h -FORMS += airspyhfigui.ui +FORMS += udpsourcegui.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui LIBS += -L../../../swagger/$${build_subdir} -lswagger -LIBS += -L../../../libairspyhf/$${build_subdir} -llibairspyhf RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/plugins/channeltx/udpsink/udpsinkgui.cpp b/plugins/channeltx/udpsource/udpsourcegui.cpp similarity index 70% rename from plugins/channeltx/udpsink/udpsinkgui.cpp rename to plugins/channeltx/udpsource/udpsourcegui.cpp index f6e01794c..fefa33893 100644 --- a/plugins/channeltx/udpsink/udpsinkgui.cpp +++ b/plugins/channeltx/udpsource/udpsourcegui.cpp @@ -14,6 +14,8 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include "udpsourcegui.h" + #include "device/devicesinkapi.h" #include "device/deviceuiset.h" #include "dsp/spectrumvis.h" @@ -24,53 +26,52 @@ #include "plugin/pluginapi.h" #include "mainwindow.h" -#include "udpsinkgui.h" -#include "ui_udpsinkgui.h" +#include "ui_udpsourcegui.h" -UDPSinkGUI* UDPSinkGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx) +UDPSourceGUI* UDPSourceGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx) { - UDPSinkGUI* gui = new UDPSinkGUI(pluginAPI, deviceUISet, channelTx); + UDPSourceGUI* gui = new UDPSourceGUI(pluginAPI, deviceUISet, channelTx); return gui; } -void UDPSinkGUI::destroy() +void UDPSourceGUI::destroy() { delete this; } -void UDPSinkGUI::setName(const QString& name) +void UDPSourceGUI::setName(const QString& name) { setObjectName(name); } -QString UDPSinkGUI::getName() const +QString UDPSourceGUI::getName() const { return objectName(); } -qint64 UDPSinkGUI::getCenterFrequency() const { +qint64 UDPSourceGUI::getCenterFrequency() const { return m_channelMarker.getCenterFrequency(); } -void UDPSinkGUI::setCenterFrequency(qint64 centerFrequency) +void UDPSourceGUI::setCenterFrequency(qint64 centerFrequency) { m_channelMarker.setCenterFrequency(centerFrequency); applySettings(); } -void UDPSinkGUI::resetToDefaults() +void UDPSourceGUI::resetToDefaults() { m_settings.resetToDefaults(); displaySettings(); applySettings(true); } -QByteArray UDPSinkGUI::serialize() const +QByteArray UDPSourceGUI::serialize() const { return m_settings.serialize(); } -bool UDPSinkGUI::deserialize(const QByteArray& data) +bool UDPSourceGUI::deserialize(const QByteArray& data) { if(m_settings.deserialize(data)) { @@ -83,13 +84,24 @@ bool UDPSinkGUI::deserialize(const QByteArray& data) } } -bool UDPSinkGUI::handleMessage(const Message& message __attribute__((unused))) +bool UDPSourceGUI::handleMessage(const Message& message) { - qDebug() << "UDPSinkGUI::handleMessage"; - return false; + if (UDPSource::MsgConfigureUDPSource::match(message)) + { + const UDPSource::MsgConfigureUDPSource& cfg = (UDPSource::MsgConfigureUDPSource&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else + { + return false; + } } -void UDPSinkGUI::handleSourceMessages() +void UDPSourceGUI::handleSourceMessages() { Message* message; @@ -102,9 +114,9 @@ void UDPSinkGUI::handleSourceMessages() } } -UDPSinkGUI::UDPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent) : +UDPSourceGUI::UDPSourceGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent) : RollupWidget(parent), - ui(new Ui::UDPSinkGUI), + ui(new Ui::UDPSourceGUI), m_pluginAPI(pluginAPI), m_deviceUISet(deviceUISet), m_tickCount(0), @@ -118,9 +130,9 @@ UDPSinkGUI::UDPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandS setAttribute(Qt::WA_DeleteOnClose, true); m_spectrumVis = new SpectrumVis(SDR_TX_SCALEF, ui->glSpectrum); - m_udpSink = (UDPSink*) channelTx; //new UDPSink(m_deviceUISet->m_deviceSinkAPI); - m_udpSink->setSpectrumSink(m_spectrumVis); - m_udpSink->setMessageQueueToGUI(getInputMessageQueue()); + m_udpSource = (UDPSource*) channelTx; + m_udpSource->setSpectrumSink(m_spectrumVis); + m_udpSource->setMessageQueueToGUI(getInputMessageQueue()); ui->fmDeviation->setEnabled(false); ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); @@ -131,7 +143,13 @@ UDPSinkGUI::UDPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandS ui->glSpectrum->setSampleRate(ui->sampleRate->text().toInt()); ui->glSpectrum->setDisplayWaterfall(true); ui->glSpectrum->setDisplayMaxHold(true); - m_spectrumVis->configure(m_spectrumVis->getInputMessageQueue(), 64, 10, FFTWindow::BlackmanHarris); + m_spectrumVis->configure(m_spectrumVis->getInputMessageQueue(), + 64, // FFT size + 10, // overlapping % + 0, // number of averaging samples + 0, // no averaging + FFTWindow::BlackmanHarris, + false); // logarithmic scale ui->glSpectrum->connectTimer(MainWindow::getInstance()->getMasterTimer()); connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); @@ -144,7 +162,7 @@ UDPSinkGUI::UDPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandS m_channelMarker.blockSignals(false); m_channelMarker.setVisible(true); // activate signal on the last setting only - m_deviceUISet->registerTxChannelInstance(UDPSink::m_channelIdURI, this); + m_deviceUISet->registerTxChannelInstance(UDPSource::m_channelIdURI, this); m_deviceUISet->addChannelMarker(&m_channelMarker); m_deviceUISet->addRollupWidget(this); @@ -153,49 +171,47 @@ UDPSinkGUI::UDPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandS ui->spectrumGUI->setBuddies(m_spectrumVis->getInputMessageQueue(), m_spectrumVis, ui->glSpectrum); connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages())); - connect(m_udpSink, SIGNAL(levelChanged(qreal, qreal, int)), ui->volumeMeter, SLOT(levelChanged(qreal, qreal, int))); + connect(m_udpSource, SIGNAL(levelChanged(qreal, qreal, int)), ui->volumeMeter, SLOT(levelChanged(qreal, qreal, int))); displaySettings(); applySettings(true); } -UDPSinkGUI::~UDPSinkGUI() +UDPSourceGUI::~UDPSourceGUI() { m_deviceUISet->removeTxChannelInstance(this); - delete m_udpSink; // TODO: check this: when the GUI closes it has to delete the modulator + delete m_udpSource; // TODO: check this: when the GUI closes it has to delete the modulator delete m_spectrumVis; delete ui; } -void UDPSinkGUI::blockApplySettings(bool block) +void UDPSourceGUI::blockApplySettings(bool block) { m_doApplySettings = !block; } -void UDPSinkGUI::applySettings(bool force) +void UDPSourceGUI::applySettings(bool force) { if (m_doApplySettings) { - UDPSink::MsgConfigureChannelizer *msgChan = UDPSink::MsgConfigureChannelizer::create( + UDPSource::MsgConfigureChannelizer *msgChan = UDPSource::MsgConfigureChannelizer::create( m_settings.m_inputSampleRate, m_settings.m_inputFrequencyOffset); - m_udpSink->getInputMessageQueue()->push(msgChan); + m_udpSource->getInputMessageQueue()->push(msgChan); - UDPSink::MsgConfigureUDPSink* message = UDPSink::MsgConfigureUDPSink::create( m_settings, force); - m_udpSink->getInputMessageQueue()->push(message); + UDPSource::MsgConfigureUDPSource* message = UDPSource::MsgConfigureUDPSource::create( m_settings, force); + m_udpSource->getInputMessageQueue()->push(message); ui->applyBtn->setEnabled(false); ui->applyBtn->setStyleSheet("QPushButton { background:rgb(79,79,79); }"); } } -void UDPSinkGUI::displaySettings() +void UDPSourceGUI::displaySettings() { m_channelMarker.blockSignals(true); m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset); m_channelMarker.setBandwidth(m_settings.m_rfBandwidth); - m_channelMarker.setUDPAddress(m_settings.m_udpAddress); - m_channelMarker.setUDPReceivePort(m_settings.m_udpPort); // activate signal on the last setting only m_channelMarker.blockSignals(false); m_channelMarker.setColor(m_settings.m_rgbColor); @@ -234,34 +250,38 @@ void UDPSinkGUI::displaySettings() ui->squelchGateText->setText(tr("%1").arg(roundf(m_settings.m_squelchGate * 1000.0), 0, 'f', 0)); ui->squelchGate->setValue(roundf(m_settings.m_squelchGate * 100.0)); - ui->addressText->setText(tr("%1:%2").arg(m_settings.m_udpAddress).arg(m_settings.m_udpPort)); + ui->localUDPAddress->setText(m_settings.m_udpAddress); + ui->localUDPPort->setText(tr("%1").arg(m_settings.m_udpPort)); + + ui->applyBtn->setEnabled(false); + ui->applyBtn->setStyleSheet("QPushButton { background:rgb(79,79,79); }"); blockApplySettings(false); } -void UDPSinkGUI::channelMarkerChangedByCursor() +void UDPSourceGUI::channelMarkerChangedByCursor() { ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); applySettings(); } -void UDPSinkGUI::on_deltaFrequency_changed(qint64 value) +void UDPSourceGUI::on_deltaFrequency_changed(qint64 value) { m_settings.m_inputFrequencyOffset = value; m_channelMarker.setCenterFrequency(value); applySettings(); } -void UDPSinkGUI::on_sampleFormat_currentIndexChanged(int index) +void UDPSourceGUI::on_sampleFormat_currentIndexChanged(int index) { - if ((index == (int) UDPSinkSettings::FormatNFM)) { + if (index == (int) UDPSourceSettings::FormatNFM) { ui->fmDeviation->setEnabled(true); } else { ui->fmDeviation->setEnabled(false); } - if (index == (int) UDPSinkSettings::FormatAM) { + if (index == (int) UDPSourceSettings::FormatAM) { ui->amModPercent->setEnabled(true); } else { ui->amModPercent->setEnabled(false); @@ -273,7 +293,30 @@ void UDPSinkGUI::on_sampleFormat_currentIndexChanged(int index) ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); } -void UDPSinkGUI::on_sampleRate_textEdited(const QString& arg1 __attribute__((unused))) +void UDPSourceGUI::on_localUDPAddress_editingFinished() +{ + m_settings.m_udpAddress = ui->localUDPAddress->text(); + ui->applyBtn->setEnabled(true); + ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); +} + +void UDPSourceGUI::on_localUDPPort_editingFinished() +{ + bool ok; + quint16 udpPort = ui->localUDPPort->text().toInt(&ok); + + if((!ok) || (udpPort < 1024)) { + udpPort = 9998; + } + + m_settings.m_udpPort = udpPort; + ui->localUDPPort->setText(tr("%1").arg(m_settings.m_udpPort)); + + ui->applyBtn->setEnabled(true); + ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); +} + +void UDPSourceGUI::on_sampleRate_textEdited(const QString& arg1 __attribute__((unused))) { bool ok; Real inputSampleRate = ui->sampleRate->text().toDouble(&ok); @@ -289,7 +332,7 @@ void UDPSinkGUI::on_sampleRate_textEdited(const QString& arg1 __attribute__((unu ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); } -void UDPSinkGUI::on_rfBandwidth_textEdited(const QString& arg1 __attribute__((unused))) +void UDPSourceGUI::on_rfBandwidth_textEdited(const QString& arg1 __attribute__((unused))) { bool ok; Real rfBandwidth = ui->rfBandwidth->text().toDouble(&ok); @@ -310,7 +353,7 @@ void UDPSinkGUI::on_rfBandwidth_textEdited(const QString& arg1 __attribute__((un ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); } -void UDPSinkGUI::on_fmDeviation_textEdited(const QString& arg1 __attribute__((unused))) +void UDPSourceGUI::on_fmDeviation_textEdited(const QString& arg1 __attribute__((unused))) { bool ok; int fmDeviation = ui->fmDeviation->text().toInt(&ok); @@ -326,7 +369,7 @@ void UDPSinkGUI::on_fmDeviation_textEdited(const QString& arg1 __attribute__((un ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); } -void UDPSinkGUI::on_amModPercent_textEdited(const QString& arg1 __attribute__((unused))) +void UDPSourceGUI::on_amModPercent_textEdited(const QString& arg1 __attribute__((unused))) { bool ok; int amModPercent = ui->amModPercent->text().toInt(&ok); @@ -343,21 +386,21 @@ void UDPSinkGUI::on_amModPercent_textEdited(const QString& arg1 __attribute__((u ui->applyBtn->setStyleSheet("QPushButton { background-color : green; }"); } -void UDPSinkGUI::on_gainIn_valueChanged(int value) +void UDPSourceGUI::on_gainIn_valueChanged(int value) { m_settings.m_gainIn = value / 10.0; ui->gainInText->setText(tr("%1").arg(m_settings.m_gainIn, 0, 'f', 1)); applySettings(); } -void UDPSinkGUI::on_gainOut_valueChanged(int value) +void UDPSourceGUI::on_gainOut_valueChanged(int value) { m_settings.m_gainOut = value / 10.0; ui->gainOutText->setText(tr("%1").arg(m_settings.m_gainOut, 0, 'f', 1)); applySettings(); } -void UDPSinkGUI::on_squelch_valueChanged(int value) +void UDPSourceGUI::on_squelch_valueChanged(int value) { m_settings.m_squelchEnabled = (value != -100); m_settings.m_squelch = value * 1.0; @@ -371,20 +414,20 @@ void UDPSinkGUI::on_squelch_valueChanged(int value) applySettings(); } -void UDPSinkGUI::on_squelchGate_valueChanged(int value) +void UDPSourceGUI::on_squelchGate_valueChanged(int value) { m_settings.m_squelchGate = value / 100.0; ui->squelchGateText->setText(tr("%1").arg(roundf(value * 10.0), 0, 'f', 0)); applySettings(); } -void UDPSinkGUI::on_channelMute_toggled(bool checked) +void UDPSourceGUI::on_channelMute_toggled(bool checked) { m_settings.m_channelMute = checked; applySettings(); } -void UDPSinkGUI::on_applyBtn_clicked() +void UDPSourceGUI::on_applyBtn_clicked() { if (m_rfBandwidthChanged) { @@ -397,63 +440,60 @@ void UDPSinkGUI::on_applyBtn_clicked() applySettings(); } -void UDPSinkGUI::on_resetUDPReadIndex_clicked() +void UDPSourceGUI::on_resetUDPReadIndex_clicked() { - m_udpSink->resetReadIndex(); + m_udpSource->resetReadIndex(); } -void UDPSinkGUI::on_autoRWBalance_toggled(bool checked) +void UDPSourceGUI::on_autoRWBalance_toggled(bool checked) { m_settings.m_autoRWBalance = checked; applySettings(); } -void UDPSinkGUI::on_stereoInput_toggled(bool checked) +void UDPSourceGUI::on_stereoInput_toggled(bool checked) { m_settings.m_stereoInput = checked; applySettings(); } -void UDPSinkGUI::onWidgetRolled(QWidget* widget, bool rollDown) +void UDPSourceGUI::onWidgetRolled(QWidget* widget, bool rollDown) { - if ((widget == ui->spectrumBox) && (m_udpSink != 0)) + if ((widget == ui->spectrumBox) && (m_udpSource != 0)) { - m_udpSink->setSpectrum(rollDown); + m_udpSource->setSpectrum(rollDown); } } -void UDPSinkGUI::onMenuDialogCalled(const QPoint &p) +void UDPSourceGUI::onMenuDialogCalled(const QPoint &p) { BasicChannelSettingsDialog dialog(&m_channelMarker, this); dialog.move(p); dialog.exec(); m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); - m_settings.m_udpAddress = m_channelMarker.getUDPAddress(), - m_settings.m_udpPort = m_channelMarker.getUDPReceivePort(), m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); setWindowTitle(m_channelMarker.getTitle()); setTitleColor(m_settings.m_rgbColor); - ui->addressText->setText(tr("%1:%2").arg(m_settings.m_udpAddress).arg(m_settings.m_udpPort)); applySettings(); } -void UDPSinkGUI::leaveEvent(QEvent*) +void UDPSourceGUI::leaveEvent(QEvent*) { m_channelMarker.setHighlighted(false); } -void UDPSinkGUI::enterEvent(QEvent*) +void UDPSourceGUI::enterEvent(QEvent*) { m_channelMarker.setHighlighted(true); } -void UDPSinkGUI::tick() +void UDPSourceGUI::tick() { - m_channelPowerAvg(m_udpSink->getMagSq()); - m_inPowerAvg(m_udpSink->getInMagSq()); + m_channelPowerAvg(m_udpSource->getMagSq()); + m_inPowerAvg(m_udpSource->getInMagSq()); if (m_tickCount % 4 == 0) { @@ -463,13 +503,13 @@ void UDPSinkGUI::tick() ui->inputPower->setText(tr("%1").arg(inPowDb, 0, 'f', 1)); } - int32_t bufferGauge = m_udpSink->getBufferGauge(); + int32_t bufferGauge = m_udpSource->getBufferGauge(); ui->bufferGaugeNegative->setValue((bufferGauge < 0 ? -bufferGauge : 0)); ui->bufferGaugePositive->setValue((bufferGauge < 0 ? 0 : bufferGauge)); QString s = QString::number(bufferGauge, 'f', 0); ui->bufferRWBalanceText->setText(tr("%1").arg(s)); - if (m_udpSink->getSquelchOpen()) { + if (m_udpSource->getSquelchOpen()) { ui->channelMute->setStyleSheet("QToolButton { background-color : green; }"); } else { ui->channelMute->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); @@ -478,32 +518,32 @@ void UDPSinkGUI::tick() m_tickCount++; } -void UDPSinkGUI::setSampleFormatIndex(const UDPSinkSettings::SampleFormat& sampleFormat) +void UDPSourceGUI::setSampleFormatIndex(const UDPSourceSettings::SampleFormat& sampleFormat) { switch(sampleFormat) { - case UDPSinkSettings::FormatS16LE: + case UDPSourceSettings::FormatSnLE: ui->sampleFormat->setCurrentIndex(0); ui->fmDeviation->setEnabled(false); ui->stereoInput->setChecked(true); ui->stereoInput->setEnabled(false); break; - case UDPSinkSettings::FormatNFM: + case UDPSourceSettings::FormatNFM: ui->sampleFormat->setCurrentIndex(1); ui->fmDeviation->setEnabled(true); ui->stereoInput->setEnabled(true); break; - case UDPSinkSettings::FormatLSB: + case UDPSourceSettings::FormatLSB: ui->sampleFormat->setCurrentIndex(2); ui->fmDeviation->setEnabled(false); ui->stereoInput->setEnabled(true); break; - case UDPSinkSettings::FormatUSB: + case UDPSourceSettings::FormatUSB: ui->sampleFormat->setCurrentIndex(3); ui->fmDeviation->setEnabled(false); ui->stereoInput->setEnabled(true); break; - case UDPSinkSettings::FormatAM: + case UDPSourceSettings::FormatAM: ui->sampleFormat->setCurrentIndex(4); ui->fmDeviation->setEnabled(false); ui->stereoInput->setEnabled(true); @@ -517,38 +557,38 @@ void UDPSinkGUI::setSampleFormatIndex(const UDPSinkSettings::SampleFormat& sampl } } -void UDPSinkGUI::setSampleFormat(int index) +void UDPSourceGUI::setSampleFormat(int index) { switch(index) { case 0: - m_settings.m_sampleFormat = UDPSinkSettings::FormatS16LE; + m_settings.m_sampleFormat = UDPSourceSettings::FormatSnLE; ui->fmDeviation->setEnabled(false); ui->stereoInput->setChecked(true); ui->stereoInput->setEnabled(false); break; case 1: - m_settings.m_sampleFormat = UDPSinkSettings::FormatNFM; + m_settings.m_sampleFormat = UDPSourceSettings::FormatNFM; ui->fmDeviation->setEnabled(true); ui->stereoInput->setEnabled(true); break; case 2: - m_settings.m_sampleFormat = UDPSinkSettings::FormatLSB; + m_settings.m_sampleFormat = UDPSourceSettings::FormatLSB; ui->fmDeviation->setEnabled(false); ui->stereoInput->setEnabled(true); break; case 3: - m_settings.m_sampleFormat = UDPSinkSettings::FormatUSB; + m_settings.m_sampleFormat = UDPSourceSettings::FormatUSB; ui->fmDeviation->setEnabled(false); ui->stereoInput->setEnabled(true); break; case 4: - m_settings.m_sampleFormat = UDPSinkSettings::FormatAM; + m_settings.m_sampleFormat = UDPSourceSettings::FormatAM; ui->fmDeviation->setEnabled(false); ui->stereoInput->setEnabled(true); break; default: - m_settings.m_sampleFormat = UDPSinkSettings::FormatS16LE; + m_settings.m_sampleFormat = UDPSourceSettings::FormatSnLE; ui->fmDeviation->setEnabled(false); ui->stereoInput->setChecked(true); ui->stereoInput->setEnabled(false); diff --git a/plugins/channeltx/udpsink/udpsinkgui.h b/plugins/channeltx/udpsource/udpsourcegui.h similarity index 81% rename from plugins/channeltx/udpsink/udpsinkgui.h rename to plugins/channeltx/udpsource/udpsourcegui.h index c70001189..6658da30d 100644 --- a/plugins/channeltx/udpsink/udpsinkgui.h +++ b/plugins/channeltx/udpsource/udpsourcegui.h @@ -14,8 +14,8 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef PLUGINS_CHANNELTX_UDPSINK_UDPSINKGUI_H_ -#define PLUGINS_CHANNELTX_UDPSINK_UDPSINKGUI_H_ +#ifndef PLUGINS_CHANNELTX_UDPSINK_UDPSOURCEGUI_H_ +#define PLUGINS_CHANNELTX_UDPSINK_UDPSOURCEGUI_H_ #include #include @@ -25,8 +25,8 @@ #include "util/messagequeue.h" #include "util/movingaverage.h" -#include "udpsink.h" -#include "udpsinksettings.h" +#include "udpsource.h" +#include "udpsourcesettings.h" class PluginAPI; class DeviceUISet; @@ -34,14 +34,14 @@ class BasebandSampleSource; class SpectrumVis; namespace Ui { - class UDPSinkGUI; + class UDPSourceGUI; } -class UDPSinkGUI : public RollupWidget, public PluginInstanceGUI { +class UDPSourceGUI : public RollupWidget, public PluginInstanceGUI { Q_OBJECT public: - static UDPSinkGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx); + static UDPSourceGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx); virtual void destroy(); void setName(const QString& name); @@ -58,30 +58,30 @@ public slots: void channelMarkerChangedByCursor(); private: - Ui::UDPSinkGUI* ui; + Ui::UDPSourceGUI* ui; PluginAPI* m_pluginAPI; DeviceUISet* m_deviceUISet; SpectrumVis* m_spectrumVis; - UDPSink* m_udpSink; + UDPSource* m_udpSource; MovingAverageUtil m_channelPowerAvg; MovingAverageUtil m_inPowerAvg; uint32_t m_tickCount; ChannelMarker m_channelMarker; // settings - UDPSinkSettings m_settings; + UDPSourceSettings m_settings; bool m_rfBandwidthChanged; bool m_doApplySettings; MessageQueue m_inputMessageQueue; - explicit UDPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent = NULL); - virtual ~UDPSinkGUI(); + explicit UDPSourceGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent = NULL); + virtual ~UDPSourceGUI(); void blockApplySettings(bool block); void applySettings(bool force = false); void displaySettings(); void setSampleFormat(int index); - void setSampleFormatIndex(const UDPSinkSettings::SampleFormat& sampleFormat); + void setSampleFormatIndex(const UDPSourceSettings::SampleFormat& sampleFormat); void leaveEvent(QEvent*); void enterEvent(QEvent*); @@ -90,6 +90,8 @@ private slots: void handleSourceMessages(); void on_deltaFrequency_changed(qint64 value); void on_sampleFormat_currentIndexChanged(int index); + void on_localUDPAddress_editingFinished(); + void on_localUDPPort_editingFinished(); void on_sampleRate_textEdited(const QString& arg1); void on_rfBandwidth_textEdited(const QString& arg1); void on_fmDeviation_textEdited(const QString& arg1); @@ -108,4 +110,4 @@ private slots: void tick(); }; -#endif /* PLUGINS_CHANNELTX_UDPSINK_UDPSINKGUI_H_ */ +#endif /* PLUGINS_CHANNELTX_UDPSINK_UDPSOURCEGUI_H_ */ diff --git a/plugins/channeltx/udpsink/udpsinkgui.ui b/plugins/channeltx/udpsource/udpsourcegui.ui similarity index 90% rename from plugins/channeltx/udpsink/udpsinkgui.ui rename to plugins/channeltx/udpsource/udpsourcegui.ui index e046480bf..245163d9e 100644 --- a/plugins/channeltx/udpsink/udpsinkgui.ui +++ b/plugins/channeltx/udpsource/udpsourcegui.ui @@ -1,18 +1,18 @@ - UDPSinkGUI - + UDPSourceGUI + 0 0 - 388 + 395 403 - 342 + 395 0 @@ -24,31 +24,31 @@ - Sans Serif + Liberation Sans 9 - UDP Sample Sink + UDP Source Sink -1 - UDP Sample Sink + UDP Source Sink 2 2 - 380 + 390 141 - 380 + 390 0 @@ -107,7 +107,7 @@ - 36 + 40 0 @@ -208,6 +208,12 @@ 0 + + + Liberation Mono + 8 + + Amplitude meter in % of maximum @@ -522,10 +528,10 @@
- + - 60 - 16777215 + 26 + 0 @@ -618,6 +624,9 @@ + + Qt::ClickFocus + Mute/Unmute channel (green when on air) @@ -670,7 +679,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -779,7 +788,42 @@ - + + + A + + + + + + + + 110 + 0 + + + + + 110 + 16777215 + + + + Qt::ClickFocus + + + Local UDP address + + + 000.000.000.000 + + + 127.0.0.1 + + + + + 35 @@ -787,26 +831,54 @@ - Addr + : - + - 150 + 50 0 + + + 50 + 16777215 + + + + Qt::ClickFocus + + + false + - Receiving UDP address and port + Local UDP port + + + 00000 - 000.000.000.000:00000 + 9998 + + + + Qt::Horizontal + + + + 40 + 20 + + + + @@ -834,7 +906,7 @@ - S16LE I/Q + SnLE I/Q @@ -913,7 +985,7 @@ - Sans Serif + Liberation Mono 9 diff --git a/plugins/channeltx/udpsink/udpsinkmsg.cpp b/plugins/channeltx/udpsource/udpsourcemsg.cpp similarity index 92% rename from plugins/channeltx/udpsink/udpsinkmsg.cpp rename to plugins/channeltx/udpsource/udpsourcemsg.cpp index 87f3cdbb4..1645f3172 100644 --- a/plugins/channeltx/udpsink/udpsinkmsg.cpp +++ b/plugins/channeltx/udpsource/udpsourcemsg.cpp @@ -14,7 +14,7 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "udpsinkmsg.h" +#include "udpsourcemsg.h" -MESSAGE_CLASS_DEFINITION(UDPSinkMessages::MsgSampleRateCorrection, Message) +MESSAGE_CLASS_DEFINITION(UDPSourceMessages::MsgSampleRateCorrection, Message) diff --git a/plugins/channeltx/udpsink/udpsinkmsg.h b/plugins/channeltx/udpsource/udpsourcemsg.h similarity index 91% rename from plugins/channeltx/udpsink/udpsinkmsg.h rename to plugins/channeltx/udpsource/udpsourcemsg.h index efe062461..2df91e5a5 100644 --- a/plugins/channeltx/udpsink/udpsinkmsg.h +++ b/plugins/channeltx/udpsource/udpsourcemsg.h @@ -14,15 +14,15 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef PLUGINS_CHANNELTX_UDPSINK_UDPSINKMSG_H_ -#define PLUGINS_CHANNELTX_UDPSINK_UDPSINKMSG_H_ +#ifndef PLUGINS_CHANNELTX_UDPSINK_UDPSOURCEMSG_H_ +#define PLUGINS_CHANNELTX_UDPSINK_UDPSOURCEMSG_H_ #include "util/message.h" /** - * Message(s) used to communicate back from UDPSinkUDPHandler to UDPSink + * Message(s) used to communicate back from UDPSinkUDPHandler to UDPSource */ -class UDPSinkMessages +class UDPSourceMessages { public: class MsgSampleRateCorrection : public Message { @@ -50,4 +50,4 @@ public: }; -#endif /* PLUGINS_CHANNELTX_UDPSINK_UDPSINKMSG_H_ */ +#endif /* PLUGINS_CHANNELTX_UDPSINK_UDPSOURCEMSG_H_ */ diff --git a/plugins/channelrx/udpsrc/udpsrcplugin.cpp b/plugins/channeltx/udpsource/udpsourceplugin.cpp similarity index 58% rename from plugins/channelrx/udpsrc/udpsrcplugin.cpp rename to plugins/channeltx/udpsource/udpsourceplugin.cpp index 032b7868b..2727ae797 100644 --- a/plugins/channelrx/udpsrc/udpsrcplugin.cpp +++ b/plugins/channeltx/udpsource/udpsourceplugin.cpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2015 F4EXB // +// Copyright (C) 2017 F4EXB // // written by Edouard Griffiths // // // // This program is free software; you can redistribute it and/or modify // @@ -15,54 +15,66 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "udpsrcplugin.h" +#include "udpsourceplugin.h" #include #include "plugin/pluginapi.h" -#include "udpsrcgui.h" -#include "udpsrc.h" +#ifndef SERVER_MODE +#include "udpsourcegui.h" +#endif +#include "udpsource.h" -const PluginDescriptor UDPSrcPlugin::m_pluginDescriptor = { +const PluginDescriptor UDPSourcePlugin::m_pluginDescriptor = { QString("UDP Channel Source"), - QString("3.12.0"), + QString("4.1.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, QString("https://github.com/f4exb/sdrangel") }; -UDPSrcPlugin::UDPSrcPlugin(QObject* parent) : +UDPSourcePlugin::UDPSourcePlugin(QObject* parent) : QObject(parent), m_pluginAPI(0) { } -const PluginDescriptor& UDPSrcPlugin::getPluginDescriptor() const +const PluginDescriptor& UDPSourcePlugin::getPluginDescriptor() const { return m_pluginDescriptor; } -void UDPSrcPlugin::initPlugin(PluginAPI* pluginAPI) +void UDPSourcePlugin::initPlugin(PluginAPI* pluginAPI) { m_pluginAPI = pluginAPI; // register TCP Channel Source - m_pluginAPI->registerRxChannel(UDPSrc::m_channelIdURI, UDPSrc::m_channelId, this); + m_pluginAPI->registerTxChannel(UDPSource::m_channelIdURI, UDPSource::m_channelId, this); } -PluginInstanceGUI* UDPSrcPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) +#ifdef SERVER_MODE +PluginInstanceGUI* UDPSourcePlugin::createTxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSource *txChannel __attribute__((unused))) { - return UDPSrcGUI::create(m_pluginAPI, deviceUISet, rxChannel); + return 0; } - -BasebandSampleSink* UDPSrcPlugin::createRxChannelBS(DeviceSourceAPI *deviceAPI) +#else +PluginInstanceGUI* UDPSourcePlugin::createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel) { - return new UDPSrc(deviceAPI); + return UDPSourceGUI::create(m_pluginAPI, deviceUISet, txChannel); } +#endif -ChannelSinkAPI* UDPSrcPlugin::createRxChannelCS(DeviceSourceAPI *deviceAPI) +BasebandSampleSource* UDPSourcePlugin::createTxChannelBS(DeviceSinkAPI *deviceAPI) { - return new UDPSrc(deviceAPI); + return new UDPSource(deviceAPI); } +ChannelSourceAPI* UDPSourcePlugin::createTxChannelCS(DeviceSinkAPI *deviceAPI) +{ + return new UDPSource(deviceAPI); +} + + diff --git a/plugins/channeltx/udpsink/udpsinkplugin.h b/plugins/channeltx/udpsource/udpsourceplugin.h similarity index 95% rename from plugins/channeltx/udpsink/udpsinkplugin.h rename to plugins/channeltx/udpsource/udpsourceplugin.h index 2932f7055..7e5cfc4b9 100644 --- a/plugins/channeltx/udpsink/udpsinkplugin.h +++ b/plugins/channeltx/udpsource/udpsourceplugin.h @@ -24,13 +24,13 @@ class DeviceUISet; class BasebandSampleSource; -class UDPSinkPlugin : public QObject, PluginInterface { +class UDPSourcePlugin : public QObject, PluginInterface { Q_OBJECT Q_INTERFACES(PluginInterface) Q_PLUGIN_METADATA(IID "sdrangel.channeltx.udpsink") public: - explicit UDPSinkPlugin(QObject* parent = 0); + explicit UDPSourcePlugin(QObject* parent = 0); const PluginDescriptor& getPluginDescriptor() const; void initPlugin(PluginAPI* pluginAPI); diff --git a/plugins/channeltx/udpsink/udpsinksettings.cpp b/plugins/channeltx/udpsource/udpsourcesettings.cpp similarity index 92% rename from plugins/channeltx/udpsink/udpsinksettings.cpp rename to plugins/channeltx/udpsource/udpsourcesettings.cpp index df9f16af4..c7acbd0a5 100644 --- a/plugins/channeltx/udpsink/udpsinksettings.cpp +++ b/plugins/channeltx/udpsource/udpsourcesettings.cpp @@ -14,23 +14,24 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include "udpsourcesettings.h" + #include #include "dsp/dspengine.h" #include "util/simpleserializer.h" #include "settings/serializable.h" -#include "udpsinksettings.h" -UDPSinkSettings::UDPSinkSettings() : +UDPSourceSettings::UDPSourceSettings() : m_channelMarker(0), m_spectrumGUI(0) { resetToDefaults(); } -void UDPSinkSettings::resetToDefaults() +void UDPSourceSettings::resetToDefaults() { - m_sampleFormat = FormatS16LE; + m_sampleFormat = FormatSnLE; m_inputSampleRate = 48000; m_inputFrequencyOffset = 0; m_rfBandwidth = 12500; @@ -46,12 +47,12 @@ void UDPSinkSettings::resetToDefaults() m_stereoInput = false; m_squelchEnabled = true; m_udpAddress = "127.0.0.1"; - m_udpPort = 9999; + m_udpPort = 9998; m_rgbColor = QColor(225, 25, 99).rgb(); - m_title = "UDP Sample Sink"; + m_title = "UDP Sample Source"; } -QByteArray UDPSinkSettings::serialize() const +QByteArray UDPSourceSettings::serialize() const { SimpleSerializer s(1); s.writeS32(2, m_inputFrequencyOffset); @@ -82,7 +83,7 @@ QByteArray UDPSinkSettings::serialize() const return s.final(); } -bool UDPSinkSettings::deserialize(const QByteArray& data) +bool UDPSourceSettings::deserialize(const QByteArray& data) { SimpleDeserializer d(data); @@ -145,12 +146,12 @@ bool UDPSinkSettings::deserialize(const QByteArray& data) m_gainIn = s32tmp / 10.0; d.readString(18, &m_udpAddress, "127.0.0.1"); - d.readU32(19, &u32tmp, 10); + d.readU32(19, &u32tmp, 9998); if ((u32tmp > 1024) & (u32tmp < 65538)) { m_udpPort = u32tmp; } else { - m_udpPort = 9999; + m_udpPort = 9998; } d.readString(20, &m_title, "UDP Sample Sink"); diff --git a/plugins/channeltx/udpsink/udpsinksettings.h b/plugins/channeltx/udpsource/udpsourcesettings.h similarity index 88% rename from plugins/channeltx/udpsink/udpsinksettings.h rename to plugins/channeltx/udpsource/udpsourcesettings.h index ae44f6b9a..4a7be431f 100644 --- a/plugins/channeltx/udpsink/udpsinksettings.h +++ b/plugins/channeltx/udpsource/udpsourcesettings.h @@ -14,19 +14,21 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef PLUGINS_CHANNELTX_UDPSINK_UDPSINKSETTINGS_H_ -#define PLUGINS_CHANNELTX_UDPSINK_UDPSINKSETTINGS_H_ +#ifndef PLUGINS_CHANNELTX_UDPSINK_UDPSOURCESETTINGS_H_ +#define PLUGINS_CHANNELTX_UDPSINK_UDPSOURCESETTINGS_H_ #include #include #include -struct Serializable; +#include "dsp/dsptypes.h" -struct UDPSinkSettings +class Serializable; + +struct UDPSourceSettings { enum SampleFormat { - FormatS16LE, + FormatSnLE, FormatNFM, FormatLSB, FormatUSB, @@ -59,7 +61,7 @@ struct UDPSinkSettings Serializable *m_channelMarker; Serializable *m_spectrumGUI; - UDPSinkSettings(); + UDPSourceSettings(); void resetToDefaults(); void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; } void setSpectrumGUI(Serializable *spectrumGUI) { m_spectrumGUI = spectrumGUI; } @@ -70,4 +72,4 @@ struct UDPSinkSettings -#endif /* PLUGINS_CHANNELTX_UDPSINK_UDPSINKSETTINGS_H_ */ +#endif /* PLUGINS_CHANNELTX_UDPSINK_UDPSOURCESETTINGS_H_ */ diff --git a/plugins/channeltx/udpsink/udpsinkudphandler.cpp b/plugins/channeltx/udpsource/udpsourceudphandler.cpp similarity index 63% rename from plugins/channeltx/udpsink/udpsinkudphandler.cpp rename to plugins/channeltx/udpsource/udpsourceudphandler.cpp index ef84c4c24..a341d8f9c 100644 --- a/plugins/channeltx/udpsink/udpsinkudphandler.cpp +++ b/plugins/channeltx/udpsource/udpsourceudphandler.cpp @@ -14,15 +14,17 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include "udpsourceudphandler.h" + #include #include +#include -#include "udpsinkmsg.h" -#include "udpsinkudphandler.h" +#include "udpsourcemsg.h" -MESSAGE_CLASS_DEFINITION(UDPSinkUDPHandler::MsgUDPAddressAndPort, Message) +MESSAGE_CLASS_DEFINITION(UDPSourceUDPHandler::MsgUDPAddressAndPort, Message) -UDPSinkUDPHandler::UDPSinkUDPHandler() : +UDPSourceUDPHandler::UDPSourceUDPHandler() : m_dataSocket(0), m_dataAddress(QHostAddress::LocalHost), m_remoteAddress(QHostAddress::LocalHost), @@ -32,7 +34,7 @@ UDPSinkUDPHandler::UDPSinkUDPHandler() : m_udpDumpIndex(0), m_nbUDPFrames(m_minNbUDPFrames), m_nbAllocatedUDPFrames(m_minNbUDPFrames), - m_writeIndex(0), + m_writeFrameIndex(0), m_readFrameIndex(m_minNbUDPFrames/2), m_readIndex(0), m_rwDelta(m_minNbUDPFrames/2), @@ -41,17 +43,18 @@ UDPSinkUDPHandler::UDPSinkUDPHandler() : m_feedbackMessageQueue(0) { m_udpBuf = new udpBlk_t[m_minNbUDPFrames]; + std::fill(m_udpDump, m_udpDump + m_udpBlockSize + 8192, 0); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleMessages())); } -UDPSinkUDPHandler::~UDPSinkUDPHandler() +UDPSourceUDPHandler::~UDPSourceUDPHandler() { delete[] m_udpBuf; } -void UDPSinkUDPHandler::start() +void UDPSourceUDPHandler::start() { - qDebug("UDPSinkUDPHandler::start"); + qDebug("UDPSourceUDPHandler::start"); if (!m_dataSocket) { @@ -63,21 +66,21 @@ void UDPSinkUDPHandler::start() if (m_dataSocket->bind(m_dataAddress, m_dataPort)) { - qDebug("UDPSinkUDPHandler::start: bind data socket to %s:%d", m_dataAddress.toString().toStdString().c_str(), m_dataPort); - connect(m_dataSocket, SIGNAL(readyRead()), this, SLOT(dataReadyRead()), Qt::QueuedConnection); // , Qt::QueuedConnection + qDebug("UDPSourceUDPHandler::start: bind data socket to %s:%d", m_dataAddress.toString().toStdString().c_str(), m_dataPort); + connect(m_dataSocket, SIGNAL(readyRead()), this, SLOT(dataReadyRead())); // , Qt::QueuedConnection gets stuck since Qt 5.8.0 m_dataConnected = true; } else { - qWarning("UDPSinkUDPHandler::start: cannot bind data socket to %s:%d", m_dataAddress.toString().toStdString().c_str(), m_dataPort); + qWarning("UDPSourceUDPHandler::start: cannot bind data socket to %s:%d", m_dataAddress.toString().toStdString().c_str(), m_dataPort); m_dataConnected = false; } } } -void UDPSinkUDPHandler::stop() +void UDPSourceUDPHandler::stop() { - qDebug("UDPSinkUDPHandler::stop"); + qDebug("UDPSourceUDPHandler::stop"); if (m_dataConnected) { @@ -92,7 +95,7 @@ void UDPSinkUDPHandler::stop() } } -void UDPSinkUDPHandler::dataReadyRead() +void UDPSourceUDPHandler::dataReadyRead() { while (m_dataSocket->hasPendingDatagrams() && m_dataConnected) { @@ -101,7 +104,7 @@ void UDPSinkUDPHandler::dataReadyRead() if (bytesRead < 0) { - qWarning("UDPSinkUDPHandler::dataReadyRead: UDP read error"); + qWarning("UDPSourceUDPHandler::dataReadyRead: UDP read error"); } else { @@ -125,33 +128,47 @@ void UDPSinkUDPHandler::dataReadyRead() } } -void UDPSinkUDPHandler::moveData(char *blk) +void UDPSourceUDPHandler::moveData(char *blk) { - memcpy(m_udpBuf[m_writeIndex], blk, m_udpBlockSize); + memcpy(m_udpBuf[m_writeFrameIndex], blk, m_udpBlockSize); - if (m_writeIndex < m_nbUDPFrames - 1) { - m_writeIndex++; + if (m_writeFrameIndex < m_nbUDPFrames - 1) { + m_writeFrameIndex++; } else { - m_writeIndex = 0; + m_writeFrameIndex = 0; } } -void UDPSinkUDPHandler::readSample(FixReal &t) +void UDPSourceUDPHandler::readSample(qint16 &t) { - if (m_readFrameIndex == m_writeIndex) // block until more writes + if (m_readFrameIndex == m_writeFrameIndex) // block until more writes { t = 0; } else { - memcpy(&t, &m_udpBuf[m_readFrameIndex][m_readIndex], sizeof(FixReal)); - advanceReadPointer((int) sizeof(FixReal)); + memcpy(&t, &m_udpBuf[m_readFrameIndex][m_readIndex], sizeof(qint16)); + advanceReadPointer((int) sizeof(qint16)); } } -void UDPSinkUDPHandler::readSample(Sample &s) +void UDPSourceUDPHandler::readSample(AudioSample &a) { - if (m_readFrameIndex == m_writeIndex) // block until more writes + if (m_readFrameIndex == m_writeFrameIndex) // block until more writes + { + a.l = 0; + a.r = 0; + } + else + { + memcpy(&a, &m_udpBuf[m_readFrameIndex][m_readIndex], sizeof(AudioSample)); + advanceReadPointer((int) sizeof(AudioSample)); + } +} + +void UDPSourceUDPHandler::readSample(Sample &s) +{ + if (m_readFrameIndex == m_writeFrameIndex) // block until more writes { s.m_real = 0; s.m_imag = 0; @@ -163,7 +180,7 @@ void UDPSinkUDPHandler::readSample(Sample &s) } } -void UDPSinkUDPHandler::advanceReadPointer(int nbBytes) +void UDPSourceUDPHandler::advanceReadPointer(int nbBytes) { if (m_readIndex < m_udpBlockSize - 2*nbBytes) { @@ -179,9 +196,10 @@ void UDPSinkUDPHandler::advanceReadPointer(int nbBytes) } else { - m_rwDelta = m_writeIndex; // raw R/W delta estimate - float d = (m_rwDelta - (m_nbUDPFrames/2))/(float) m_nbUDPFrames; - //qDebug("UDPSinkUDPHandler::advanceReadPointer: w: %02d d: %f", m_writeIndex, d); + m_rwDelta = m_writeFrameIndex; // raw R/W delta estimate + int nbUDPFrames2 = m_nbUDPFrames/2; + float d = (m_rwDelta - nbUDPFrames2)/(float) m_nbUDPFrames; + //qDebug("UDPSourceUDPHandler::advanceReadPointer: w: %02d d: %f", m_writeIndex, d); if ((d < -0.45) || (d > 0.45)) { @@ -192,7 +210,7 @@ void UDPSinkUDPHandler::advanceReadPointer(int nbBytes) float dd = d - m_d; // derivative float c = (d / 15.0) + (dd / 20.0); // damping and scaling c = c < -0.05 ? -0.05 : c > 0.05 ? 0.05 : c; // limit - UDPSinkMessages::MsgSampleRateCorrection *msg = UDPSinkMessages::MsgSampleRateCorrection::create(c, d); + UDPSourceMessages::MsgSampleRateCorrection *msg = UDPSourceMessages::MsgSampleRateCorrection::create(c, d); if (m_autoRWBalance && m_feedbackMessageQueue) { m_feedbackMessageQueue->push(msg); @@ -205,20 +223,20 @@ void UDPSinkUDPHandler::advanceReadPointer(int nbBytes) } } -void UDPSinkUDPHandler::configureUDPLink(const QString& address, quint16 port) +void UDPSourceUDPHandler::configureUDPLink(const QString& address, quint16 port) { Message* msg = MsgUDPAddressAndPort::create(address, port); m_inputMessageQueue.push(msg); } -void UDPSinkUDPHandler::applyUDPLink(const QString& address, quint16 port) +void UDPSourceUDPHandler::applyUDPLink(const QString& address, quint16 port) { - qDebug("UDPSinkUDPHandler::configureUDPLink: %s:%d", address.toStdString().c_str(), port); + qDebug("UDPSourceUDPHandler::configureUDPLink: %s:%d", address.toStdString().c_str(), port); bool addressOK = m_dataAddress.setAddress(address); if (!addressOK) { - qWarning("UDPSinkUDPHandler::configureUDPLink: invalid address %s. Set to localhost.", address.toStdString().c_str()); + qWarning("UDPSourceUDPHandler::configureUDPLink: invalid address %s. Set to localhost.", address.toStdString().c_str()); m_dataAddress = QHostAddress::LocalHost; } @@ -228,18 +246,18 @@ void UDPSinkUDPHandler::applyUDPLink(const QString& address, quint16 port) start(); } -void UDPSinkUDPHandler::resetReadIndex() +void UDPSourceUDPHandler::resetReadIndex() { - m_readFrameIndex = (m_writeIndex + (m_nbUDPFrames/2)) % m_nbUDPFrames; + m_readFrameIndex = (m_writeFrameIndex + (m_nbUDPFrames/2)) % m_nbUDPFrames; m_rwDelta = m_nbUDPFrames/2; m_readIndex = 0; m_d = 0.0f; } -void UDPSinkUDPHandler::resizeBuffer(float sampleRate) +void UDPSourceUDPHandler::resizeBuffer(float sampleRate) { int halfNbFrames = std::max((sampleRate / 375.0), (m_minNbUDPFrames / 2.0)); - qDebug("UDPSinkUDPHandler::resizeBuffer: nb_frames: %d", 2*halfNbFrames); + qDebug("UDPSourceUDPHandler::resizeBuffer: nb_frames: %d", 2*halfNbFrames); if (2*halfNbFrames > m_nbAllocatedUDPFrames) { @@ -249,12 +267,12 @@ void UDPSinkUDPHandler::resizeBuffer(float sampleRate) } m_nbUDPFrames = 2*halfNbFrames; - m_writeIndex = 0; + m_writeFrameIndex = 0; resetReadIndex(); } -void UDPSinkUDPHandler::handleMessages() +void UDPSourceUDPHandler::handleMessages() { Message* message; @@ -267,11 +285,11 @@ void UDPSinkUDPHandler::handleMessages() } } -bool UDPSinkUDPHandler::handleMessage(const Message& cmd) +bool UDPSourceUDPHandler::handleMessage(const Message& cmd) { - if (UDPSinkUDPHandler::MsgUDPAddressAndPort::match(cmd)) + if (UDPSourceUDPHandler::MsgUDPAddressAndPort::match(cmd)) { - UDPSinkUDPHandler::MsgUDPAddressAndPort& notif = (UDPSinkUDPHandler::MsgUDPAddressAndPort&) cmd; + UDPSourceUDPHandler::MsgUDPAddressAndPort& notif = (UDPSourceUDPHandler::MsgUDPAddressAndPort&) cmd; applyUDPLink(notif.getAddress(), notif.getPort()); return true; } diff --git a/plugins/channeltx/udpsink/udpsinkudphandler.h b/plugins/channeltx/udpsource/udpsourceudphandler.h similarity index 88% rename from plugins/channeltx/udpsink/udpsinkudphandler.h rename to plugins/channeltx/udpsource/udpsourceudphandler.h index 52de91143..13b3faf60 100644 --- a/plugins/channeltx/udpsink/udpsinkudphandler.h +++ b/plugins/channeltx/udpsource/udpsourceudphandler.h @@ -14,8 +14,8 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef PLUGINS_CHANNELTX_UDPSINK_UDPSINKUDPHANDLER_H_ -#define PLUGINS_CHANNELTX_UDPSINK_UDPSINKUDPHANDLER_H_ +#ifndef PLUGINS_CHANNELTX_UDPSINK_UDPSOURCEUDPHANDLER_H_ +#define PLUGINS_CHANNELTX_UDPSINK_UDPSOURCEUDPHANDLER_H_ #include #include @@ -27,12 +27,12 @@ #include "util/message.h" #include "util/messagequeue.h" -class UDPSinkUDPHandler : public QObject +class UDPSourceUDPHandler : public QObject { Q_OBJECT public: - UDPSinkUDPHandler(); - virtual ~UDPSinkUDPHandler(); + UDPSourceUDPHandler(); + virtual ~UDPSourceUDPHandler(); void start(); void stop(); @@ -40,8 +40,9 @@ public: void resetReadIndex(); void resizeBuffer(float sampleRate); - void readSample(FixReal &t); - void readSample(Sample &s); + void readSample(qint16 &t); //!< audio mono + void readSample(AudioSample &a); //!< audio stereo + void readSample(Sample &s); //!< I/Q stream void setAutoRWBalance(bool autoRWBalance) { m_autoRWBalance = autoRWBalance; } void setFeedbackMessageQueue(MessageQueue *messageQueue) { m_feedbackMessageQueue = messageQueue; } @@ -104,7 +105,7 @@ private: int m_udpDumpIndex; int m_nbUDPFrames; int m_nbAllocatedUDPFrames; - int m_writeIndex; + int m_writeFrameIndex; int m_readFrameIndex; int m_readIndex; int m_rwDelta; @@ -119,4 +120,4 @@ private slots: -#endif /* PLUGINS_CHANNELTX_UDPSINK_UDPSINKUDPHANDLER_H_ */ +#endif /* PLUGINS_CHANNELTX_UDPSINK_UDPSOURCEUDPHANDLER_H_ */ diff --git a/plugins/samplesink/CMakeLists.txt b/plugins/samplesink/CMakeLists.txt index 179ba540e..a57d121a4 100644 --- a/plugins/samplesink/CMakeLists.txt +++ b/plugins/samplesink/CMakeLists.txt @@ -4,7 +4,8 @@ find_package(LibUSB) find_package(LibBLADERF) if(LIBUSB_FOUND AND LIBBLADERF_FOUND) - add_subdirectory(bladerfoutput) + add_subdirectory(bladerf1output) + add_subdirectory(bladerf2output) endif(LIBUSB_FOUND AND LIBBLADERF_FOUND) find_package(LibHACKRF) @@ -23,19 +24,23 @@ if(LIBUSB_FOUND AND LIBIIO_FOUND) endif(LIBUSB_FOUND AND LIBIIO_FOUND) find_package(CM256cc) -find_package(LibNANOMSG) -if(CM256CC_FOUND AND LIBNANOMSG_FOUND) +if(CM256CC_FOUND) add_subdirectory(sdrdaemonsink) -endif(CM256CC_FOUND AND LIBNANOMSG_FOUND) +endif(CM256CC_FOUND) + +find_package(SoapySDR) +if(LIBUSB_FOUND AND SOAPYSDR_FOUND) + add_subdirectory(soapysdroutput) +endif() if (BUILD_DEBIAN) - add_subdirectory(bladerfoutput) + add_subdirectory(bladerf1output) + add_subdirectory(bladerf2output) add_subdirectory(hackrfoutput) add_subdirectory(limesdroutput) - if (LIBNANOMSG_FOUND) - add_subdirectory(sdrdaemonsink) - endif (LIBNANOMSG_FOUND) add_subdirectory(plutosdroutput) + add_subdirectory(sdrdaemonsink) + add_subdirectory(soapysdroutput) endif (BUILD_DEBIAN) add_subdirectory(filesink) diff --git a/plugins/samplesink/bladerf1output/CMakeLists.txt b/plugins/samplesink/bladerf1output/CMakeLists.txt new file mode 100644 index 000000000..1041a3f90 --- /dev/null +++ b/plugins/samplesink/bladerf1output/CMakeLists.txt @@ -0,0 +1,80 @@ +project(bladerf1output) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +set(bladerf1output_SOURCES + bladerf1outputgui.cpp + bladerf1output.cpp + bladerf1outputplugin.cpp + bladerf1outputsettings.cpp + bladerf1outputthread.cpp +) + +set(bladerf1output_HEADERS + bladerf1outputgui.h + bladerf1output.h + bladerf1soutputplugin.h + bladerf1outputsettings.h + bladerf1outputthread.h +) + +set(bladerf1output_FORMS + bladerf1outputgui.ui +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERFLIBSRC}/include + ${LIBBLADERFLIBSRC}/src +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERF_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +#include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +#qt4_wrap_cpp(bladerf1output_HEADERS_MOC ${bladerf1output_HEADERS}) +qt5_wrap_ui(bladerf1output_FORMS_HEADERS ${bladerf1output_FORMS}) + +add_library(outputbladerf1 SHARED + ${bladerf1output_SOURCES} + ${bladerf1output_HEADERS_MOC} + ${bladerf1output_FORMS_HEADERS} +) + +if (BUILD_DEBIAN) +target_link_libraries(outputbladerf1 + ${QT_LIBRARIES} + bladerf + sdrbase + sdrgui + swagger + bladerf1device +) +else (BUILD_DEBIAN) +target_link_libraries(outputbladerf1 + ${QT_LIBRARIES} + ${LIBBLADERF_LIBRARIES} + sdrbase + sdrgui + swagger + bladerf1device +) +endif (BUILD_DEBIAN) + +target_link_libraries(outputbladerf1 Qt5::Core Qt5::Widgets) + +install(TARGETS outputbladerf1 DESTINATION lib/plugins/samplesink) diff --git a/plugins/samplesink/bladerfoutput/bladerfoutput.cpp b/plugins/samplesink/bladerf1output/bladerf1output.cpp similarity index 71% rename from plugins/samplesink/bladerfoutput/bladerfoutput.cpp rename to plugins/samplesink/bladerf1output/bladerf1output.cpp index 1b3f08147..75d15f593 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutput.cpp +++ b/plugins/samplesink/bladerf1output/bladerf1output.cpp @@ -14,6 +14,8 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include "bladerf1output.h" + #include #include #include @@ -21,22 +23,18 @@ #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" -#include "util/simpleserializer.h" #include "dsp/dspcommands.h" #include "dsp/dspengine.h" #include "device/devicesinkapi.h" #include "device/devicesourceapi.h" -#include "bladerf/devicebladerfshared.h" +#include "bladerf1/devicebladerf1shared.h" +#include "bladerf1outputthread.h" -#include "bladerfoutput.h" -#include "bladerfoutputgui.h" -#include "bladerfoutputthread.h" +MESSAGE_CLASS_DEFINITION(Bladerf1Output::MsgConfigureBladerf1, Message) +MESSAGE_CLASS_DEFINITION(Bladerf1Output::MsgStartStop, Message) +MESSAGE_CLASS_DEFINITION(Bladerf1Output::MsgReportBladerf1, Message) -MESSAGE_CLASS_DEFINITION(BladerfOutput::MsgConfigureBladerf, Message) -MESSAGE_CLASS_DEFINITION(BladerfOutput::MsgStartStop, Message) -MESSAGE_CLASS_DEFINITION(BladerfOutput::MsgReportBladerf, Message) - -BladerfOutput::BladerfOutput(DeviceSinkAPI *deviceAPI) : +Bladerf1Output::Bladerf1Output(DeviceSinkAPI *deviceAPI) : m_deviceAPI(deviceAPI), m_settings(), m_dev(0), @@ -49,19 +47,19 @@ BladerfOutput::BladerfOutput(DeviceSinkAPI *deviceAPI) : m_deviceAPI->setBuddySharedPtr(&m_sharedParams); } -BladerfOutput::~BladerfOutput() +Bladerf1Output::~Bladerf1Output() { if (m_running) stop(); closeDevice(); m_deviceAPI->setBuddySharedPtr(0); } -void BladerfOutput::destroy() +void Bladerf1Output::destroy() { delete this; } -bool BladerfOutput::openDevice() +bool Bladerf1Output::openDevice() { if (m_dev != 0) { @@ -75,7 +73,7 @@ bool BladerfOutput::openDevice() if (m_deviceAPI->getSourceBuddies().size() > 0) { DeviceSourceAPI *sourceBuddy = m_deviceAPI->getSourceBuddies()[0]; - DeviceBladeRFParams *buddySharedParams = (DeviceBladeRFParams *) sourceBuddy->getBuddySharedPtr(); + DeviceBladeRF1Params *buddySharedParams = (DeviceBladeRF1Params *) sourceBuddy->getBuddySharedPtr(); if (buddySharedParams == 0) { @@ -94,7 +92,7 @@ bool BladerfOutput::openDevice() } else { - if (!DeviceBladeRF::open_bladerf(&m_dev, qPrintable(m_deviceAPI->getSampleSinkSerial()))) + if (!DeviceBladeRF1::open_bladerf(&m_dev, qPrintable(m_deviceAPI->getSampleSinkSerial()))) { qCritical("BladerfOutput::start: could not open BladeRF %s", qPrintable(m_deviceAPI->getSampleSinkSerial())); return false; @@ -104,7 +102,7 @@ bool BladerfOutput::openDevice() } // TODO: adjust USB transfer data according to sample rate - if ((res = bladerf_sync_config(m_dev, BLADERF_MODULE_TX, BLADERF_FORMAT_SC16_Q11, 64, 8192, 32, 10000)) < 0) + if ((res = bladerf_sync_config(m_dev, BLADERF_TX_X1, BLADERF_FORMAT_SC16_Q11, 64, 8192, 32, 10000)) < 0) { qCritical("BladerfOutput::start: bladerf_sync_config with return code %d", res); return false; @@ -119,12 +117,12 @@ bool BladerfOutput::openDevice() return true; } -void BladerfOutput::init() +void Bladerf1Output::init() { applySettings(m_settings, true); } -bool BladerfOutput::start() +bool Bladerf1Output::start() { // QMutexLocker mutexLocker(&m_mutex); @@ -134,12 +132,7 @@ bool BladerfOutput::start() if (m_running) stop(); - if((m_bladerfThread = new BladerfOutputThread(m_dev, &m_sampleSourceFifo)) == 0) - { - qCritical("BladerfOutput::start: out of memory"); - stop(); - return false; - } + m_bladerfThread = new Bladerf1OutputThread(m_dev, &m_sampleSourceFifo); // mutexLocker.unlock(); applySettings(m_settings, true); @@ -154,7 +147,7 @@ bool BladerfOutput::start() return true; } -void BladerfOutput::closeDevice() +void Bladerf1Output::closeDevice() { int res; @@ -181,7 +174,7 @@ void BladerfOutput::closeDevice() m_dev = 0; } -void BladerfOutput::stop() +void Bladerf1Output::stop() { // QMutexLocker mutexLocker(&m_mutex); if (m_bladerfThread != 0) @@ -194,12 +187,12 @@ void BladerfOutput::stop() m_running = false; } -QByteArray BladerfOutput::serialize() const +QByteArray Bladerf1Output::serialize() const { return m_settings.serialize(); } -bool BladerfOutput::deserialize(const QByteArray& data) +bool Bladerf1Output::deserialize(const QByteArray& data) { bool success = true; @@ -209,54 +202,54 @@ bool BladerfOutput::deserialize(const QByteArray& data) success = false; } - MsgConfigureBladerf* message = MsgConfigureBladerf::create(m_settings, true); + MsgConfigureBladerf1* message = MsgConfigureBladerf1::create(m_settings, true); m_inputMessageQueue.push(message); if (m_guiMessageQueue) { - MsgConfigureBladerf* messageToGUI = MsgConfigureBladerf::create(m_settings, true); + MsgConfigureBladerf1* messageToGUI = MsgConfigureBladerf1::create(m_settings, true); m_guiMessageQueue->push(messageToGUI); } return success; } -const QString& BladerfOutput::getDeviceDescription() const +const QString& Bladerf1Output::getDeviceDescription() const { return m_deviceDescription; } -int BladerfOutput::getSampleRate() const +int Bladerf1Output::getSampleRate() const { int rate = m_settings.m_devSampleRate; return (rate / (1<push(messageToGUI); } } -bool BladerfOutput::handleMessage(const Message& message) +bool Bladerf1Output::handleMessage(const Message& message) { - if (MsgConfigureBladerf::match(message)) + if (MsgConfigureBladerf1::match(message)) { - MsgConfigureBladerf& conf = (MsgConfigureBladerf&) message; + MsgConfigureBladerf1& conf = (MsgConfigureBladerf1&) message; qDebug() << "BladerfOutput::handleMessage: MsgConfigureBladerf"; if (!applySettings(conf.getSettings(), conf.getForce())) @@ -276,13 +269,11 @@ bool BladerfOutput::handleMessage(const Message& message) if (m_deviceAPI->initGeneration()) { m_deviceAPI->startGeneration(); - DSPEngine::instance()->startAudioInput(); } } else { m_deviceAPI->stopGeneration(); - DSPEngine::instance()->stopAudioInput(); } return true; @@ -293,7 +284,7 @@ bool BladerfOutput::handleMessage(const Message& message) } } -bool BladerfOutput::applySettings(const BladeRFOutputSettings& settings, bool force) +bool Bladerf1Output::applySettings(const BladeRF1OutputSettings& settings, bool force) { bool forwardChange = false; bool suspendOwnThread = false; @@ -326,13 +317,13 @@ bool BladerfOutput::applySettings(const BladeRFOutputSettings& settings, bool fo if (settings.m_log2Interp >= 5) { - fifoSize = DeviceBladeRFShared::m_sampleFifoMinSize32; + fifoSize = DeviceBladeRF1Shared::m_sampleFifoMinSize32; } else { fifoSize = std::max( - (int) ((settings.m_devSampleRate/(1<init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +void Bladerf1Output::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const BladeRF1OutputSettings& settings) +{ + response.getBladeRf1OutputSettings()->setCenterFrequency(settings.m_centerFrequency); + response.getBladeRf1OutputSettings()->setDevSampleRate(settings.m_devSampleRate); + response.getBladeRf1OutputSettings()->setVga1(settings.m_vga1); + response.getBladeRf1OutputSettings()->setVga2(settings.m_vga2); + response.getBladeRf1OutputSettings()->setBandwidth(settings.m_bandwidth); + response.getBladeRf1OutputSettings()->setLog2Interp(settings.m_log2Interp); + response.getBladeRf1OutputSettings()->setXb200(settings.m_xb200 ? 1 : 0); + response.getBladeRf1OutputSettings()->setXb200Path((int) settings.m_xb200Path); + response.getBladeRf1OutputSettings()->setXb200Filter((int) settings.m_xb200Filter); +} + +int Bladerf1Output::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage __attribute__((unused))) +{ + BladeRF1OutputSettings settings = m_settings; + + if (deviceSettingsKeys.contains("centerFrequency")) { + settings.m_centerFrequency = response.getBladeRf1OutputSettings()->getCenterFrequency(); + } + if (deviceSettingsKeys.contains("devSampleRate")) { + settings.m_devSampleRate = response.getBladeRf1OutputSettings()->getDevSampleRate(); + } + if (deviceSettingsKeys.contains("vga1")) { + settings.m_vga1 = response.getBladeRf1OutputSettings()->getVga1(); + } + if (deviceSettingsKeys.contains("vga2")) { + settings.m_vga2 = response.getBladeRf1OutputSettings()->getVga2(); + } + if (deviceSettingsKeys.contains("bandwidth")) { + settings.m_bandwidth = response.getBladeRf1OutputSettings()->getBandwidth(); + } + if (deviceSettingsKeys.contains("log2Interp")) { + settings.m_log2Interp = response.getBladeRf1OutputSettings()->getLog2Interp(); + } + if (deviceSettingsKeys.contains("xb200")) { + settings.m_xb200 = response.getBladeRf1OutputSettings()->getXb200() == 0 ? 0 : 1; + } + if (deviceSettingsKeys.contains("xb200Path")) { + settings.m_xb200Path = static_cast(response.getBladeRf1OutputSettings()->getXb200Path()); + } + if (deviceSettingsKeys.contains("xb200Filter")) { + settings.m_xb200Filter = static_cast(response.getBladeRf1OutputSettings()->getXb200Filter()); + } + + MsgConfigureBladerf1 *msg = MsgConfigureBladerf1::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureBladerf1 *msgToGUI = MsgConfigureBladerf1::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +int Bladerf1Output::webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage __attribute__((unused))) { @@ -550,7 +613,7 @@ int BladerfOutput::webapiRunGet( return 200; } -int BladerfOutput::webapiRun( +int Bladerf1Output::webapiRun( bool run, SWGSDRangel::SWGDeviceState& response, QString& errorMessage __attribute__((unused))) diff --git a/plugins/samplesink/bladerfoutput/bladerfoutput.h b/plugins/samplesink/bladerf1output/bladerf1output.h similarity index 67% rename from plugins/samplesink/bladerfoutput/bladerfoutput.h rename to plugins/samplesink/bladerf1output/bladerf1output.h index 256df577a..2a71e825d 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutput.h +++ b/plugins/samplesink/bladerf1output/bladerf1output.h @@ -18,36 +18,35 @@ #define INCLUDE_BLADERFOUTPUT_H #include -#include "bladerf/devicebladerf.h" -#include "bladerf/devicebladerfparam.h" - #include #include -#include "bladerfoutputsettings.h" +#include "../../../devices/bladerf1/devicebladerf1.h" +#include "../../../devices/bladerf1/devicebladerf1param.h" +#include "bladerf1outputsettings.h" class DeviceSinkAPI; -class BladerfOutputThread; +class Bladerf1OutputThread; -class BladerfOutput : public DeviceSampleSink { +class Bladerf1Output : public DeviceSampleSink { public: - class MsgConfigureBladerf : public Message { + class MsgConfigureBladerf1 : public Message { MESSAGE_CLASS_DECLARATION public: - const BladeRFOutputSettings& getSettings() const { return m_settings; } + const BladeRF1OutputSettings& getSettings() const { return m_settings; } bool getForce() const { return m_force; } - static MsgConfigureBladerf* create(const BladeRFOutputSettings& settings, bool force) + static MsgConfigureBladerf1* create(const BladeRF1OutputSettings& settings, bool force) { - return new MsgConfigureBladerf(settings, force); + return new MsgConfigureBladerf1(settings, force); } private: - BladeRFOutputSettings m_settings; + BladeRF1OutputSettings m_settings; bool m_force; - MsgConfigureBladerf(const BladeRFOutputSettings& settings, bool force) : + MsgConfigureBladerf1(const BladeRF1OutputSettings& settings, bool force) : Message(), m_settings(settings), m_force(force) @@ -73,25 +72,25 @@ public: { } }; - class MsgReportBladerf : public Message { + class MsgReportBladerf1 : public Message { MESSAGE_CLASS_DECLARATION public: - static MsgReportBladerf* create() + static MsgReportBladerf1* create() { - return new MsgReportBladerf(); + return new MsgReportBladerf1(); } protected: - MsgReportBladerf() : + MsgReportBladerf1() : Message() { } }; - BladerfOutput(DeviceSinkAPI *deviceAPI); - virtual ~BladerfOutput(); + Bladerf1Output(DeviceSinkAPI *deviceAPI); + virtual ~Bladerf1Output(); virtual void destroy(); virtual void init(); @@ -109,6 +108,16 @@ public: virtual bool handleMessage(const Message& message); + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + virtual int webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage); @@ -121,15 +130,16 @@ public: private: bool openDevice(); void closeDevice(); - bool applySettings(const BladeRFOutputSettings& settings, bool force); + bool applySettings(const BladeRF1OutputSettings& settings, bool force); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const BladeRF1OutputSettings& settings); DeviceSinkAPI *m_deviceAPI; QMutex m_mutex; - BladeRFOutputSettings m_settings; + BladeRF1OutputSettings m_settings; struct bladerf* m_dev; - BladerfOutputThread* m_bladerfThread; + Bladerf1OutputThread* m_bladerfThread; QString m_deviceDescription; - DeviceBladeRFParams m_sharedParams; + DeviceBladeRF1Params m_sharedParams; bool m_running; }; diff --git a/plugins/samplesource/bladerfinput/bladerfinput.pro b/plugins/samplesink/bladerf1output/bladerf1output.pro similarity index 59% rename from plugins/samplesource/bladerfinput/bladerfinput.pro rename to plugins/samplesink/bladerf1output/bladerf1output.pro index 9275a3542..325942932 100644 --- a/plugins/samplesource/bladerfinput/bladerfinput.pro +++ b/plugins/samplesink/bladerf1output/bladerf1output.pro @@ -9,7 +9,7 @@ CONFIG += plugin QT += core gui widgets multimedia opengl -TARGET = inputbladerf +TARGET = outputbladerf1 DEFINES += USE_SSE2=1 QMAKE_CXXFLAGS += -msse2 @@ -17,36 +17,37 @@ DEFINES += USE_SSE4_1=1 QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 -CONFIG(MINGW32):LIBBLADERFSRC = "D:\softs\bladeRF\host\libraries\libbladeRF\include" -CONFIG(MINGW64):LIBBLADERFSRC = "D:\softs\bladeRF\host\libraries\libbladeRF\include" +CONFIG(MINGW32):LIBBLADERF = "C:\Programs\bladeRF" +CONFIG(MINGW64):LIBBLADERF = "C:\Programs\bladeRF" INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client INCLUDEPATH += ../../../devices -INCLUDEPATH += $$LIBBLADERFSRC +INCLUDEPATH += $$LIBBLADERF/include CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug -SOURCES += bladerfinputgui.cpp\ - bladerfinput.cpp\ - bladerfinputplugin.cpp\ - bladerfinputsettings.cpp\ - bladerfinputthread.cpp +SOURCES += bladerf1outputgui.cpp\ + bladerf1output.cpp\ + bladerf1outputplugin.cpp\ + bladerf1outputsettings.cpp\ + bladerf1outputthread.cpp -HEADERS += bladerfinputgui.h\ - bladerfinput.h\ - bladerfinputplugin.h\ - bladerfinputsettings.h\ - bladerfinputthread.h +HEADERS += bladerf1outputgui.h\ + bladerf1output.h\ + bladerf1outputplugin.h\ + bladerf1outputsettings.h\ + bladerf1outputthread.h -FORMS += bladerfinputgui.ui +FORMS += bladerf1outputgui.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui LIBS += -L../../../swagger/$${build_subdir} -lswagger -LIBS += -L../../../libbladerf/$${build_subdir} -llibbladerf +LIBS += -L$$LIBBLADERF/lib -lbladeRF LIBS += -L../../../devices/$${build_subdir} -ldevices RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/plugins/samplesink/bladerfoutput/bladerfoutputgui.cpp b/plugins/samplesink/bladerf1output/bladerf1outputgui.cpp similarity index 79% rename from plugins/samplesink/bladerfoutput/bladerfoutputgui.cpp rename to plugins/samplesink/bladerf1output/bladerf1outputgui.cpp index 12939e2bb..76562676f 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutputgui.cpp +++ b/plugins/samplesink/bladerf1output/bladerf1outputgui.cpp @@ -19,28 +19,27 @@ #include -#include "ui_bladerfoutputgui.h" +#include "ui_bladerf1outputgui.h" #include "gui/colormapper.h" #include "gui/glspectrum.h" #include "dsp/dspengine.h" #include "dsp/dspcommands.h" #include "device/devicesinkapi.h" #include "device/deviceuiset.h" -#include "bladerfoutputgui.h" -#include "bladerf/devicebladerfvalues.h" +#include "bladerf1outputgui.h" -BladerfOutputGui::BladerfOutputGui(DeviceUISet *deviceUISet, QWidget* parent) : +Bladerf1OutputGui::Bladerf1OutputGui(DeviceUISet *deviceUISet, QWidget* parent) : QWidget(parent), - ui(new Ui::BladerfOutputGui), + ui(new Ui::Bladerf1OutputGui), m_deviceUISet(deviceUISet), m_doApplySettings(true), m_forceSettings(true), m_settings(), m_deviceSampleSink(NULL), m_sampleRate(0), - m_lastEngineState((DSPDeviceSinkEngine::State)-1) + m_lastEngineState(DSPDeviceSinkEngine::StNotStarted) { - m_deviceSampleSink = (BladerfOutput*) m_deviceUISet->m_deviceSinkAPI->getSampleSink(); + m_deviceSampleSink = (Bladerf1Output*) m_deviceUISet->m_deviceSinkAPI->getSampleSink(); ui->setupUi(this); ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); @@ -62,57 +61,54 @@ BladerfOutputGui::BladerfOutputGui(DeviceUISet *deviceUISet, QWidget* parent) : displaySettings(); - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceUISet->m_deviceSinkAPI->getDeviceUID()); - connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); } -BladerfOutputGui::~BladerfOutputGui() +Bladerf1OutputGui::~Bladerf1OutputGui() { delete ui; } -void BladerfOutputGui::destroy() +void Bladerf1OutputGui::destroy() { delete this; } -void BladerfOutputGui::setName(const QString& name) +void Bladerf1OutputGui::setName(const QString& name) { setObjectName(name); } -QString BladerfOutputGui::getName() const +QString Bladerf1OutputGui::getName() const { return objectName(); } -void BladerfOutputGui::resetToDefaults() +void Bladerf1OutputGui::resetToDefaults() { m_settings.resetToDefaults(); displaySettings(); sendSettings(); } -qint64 BladerfOutputGui::getCenterFrequency() const +qint64 Bladerf1OutputGui::getCenterFrequency() const { return m_settings.m_centerFrequency; } -void BladerfOutputGui::setCenterFrequency(qint64 centerFrequency) +void Bladerf1OutputGui::setCenterFrequency(qint64 centerFrequency) { m_settings.m_centerFrequency = centerFrequency; displaySettings(); sendSettings(); } -QByteArray BladerfOutputGui::serialize() const +QByteArray Bladerf1OutputGui::serialize() const { return m_settings.serialize(); } -bool BladerfOutputGui::deserialize(const QByteArray& data) +bool Bladerf1OutputGui::deserialize(const QByteArray& data) { if(m_settings.deserialize(data)) { displaySettings(); @@ -125,25 +121,25 @@ bool BladerfOutputGui::deserialize(const QByteArray& data) } } -bool BladerfOutputGui::handleMessage(const Message& message) +bool Bladerf1OutputGui::handleMessage(const Message& message) { - if (BladerfOutput::MsgConfigureBladerf::match(message)) + if (Bladerf1Output::MsgConfigureBladerf1::match(message)) { - const BladerfOutput::MsgConfigureBladerf& cfg = (BladerfOutput::MsgConfigureBladerf&) message; + const Bladerf1Output::MsgConfigureBladerf1& cfg = (Bladerf1Output::MsgConfigureBladerf1&) message; m_settings = cfg.getSettings(); blockApplySettings(true); displaySettings(); blockApplySettings(false); return true; } - else if (BladerfOutput::MsgReportBladerf::match(message)) + else if (Bladerf1Output::MsgReportBladerf1::match(message)) { displaySettings(); return true; } - else if (BladerfOutput::MsgStartStop::match(message)) + else if (Bladerf1Output::MsgStartStop::match(message)) { - BladerfOutput::MsgStartStop& notif = (BladerfOutput::MsgStartStop&) message; + Bladerf1Output::MsgStartStop& notif = (Bladerf1Output::MsgStartStop&) message; blockApplySettings(true); ui->startStop->setChecked(notif.getStartStop()); blockApplySettings(false); @@ -155,7 +151,7 @@ bool BladerfOutputGui::handleMessage(const Message& message) } } -void BladerfOutputGui::handleInputMessages() +void Bladerf1OutputGui::handleInputMessages() { Message* message; @@ -183,14 +179,14 @@ void BladerfOutputGui::handleInputMessages() } } -void BladerfOutputGui::updateSampleRateAndFrequency() +void Bladerf1OutputGui::updateSampleRateAndFrequency() { m_deviceUISet->getSpectrum()->setSampleRate(m_sampleRate); m_deviceUISet->getSpectrum()->setCenterFrequency(m_deviceCenterFrequency); ui->deviceRateLabel->setText(QString("%1k").arg(QString::number(m_sampleRate/1000.0, 'g', 5))); } -void BladerfOutputGui::displaySettings() +void Bladerf1OutputGui::displaySettings() { ui->centerFrequency->setValue(m_settings.m_centerFrequency / 1000); ui->sampleRate->setValue(m_settings.m_devSampleRate); @@ -209,32 +205,32 @@ void BladerfOutputGui::displaySettings() ui->xb200->setCurrentIndex(getXb200Index(m_settings.m_xb200, m_settings.m_xb200Path, m_settings.m_xb200Filter)); } -void BladerfOutputGui::sendSettings() +void Bladerf1OutputGui::sendSettings() { if(!m_updateTimer.isActive()) m_updateTimer.start(100); } -void BladerfOutputGui::on_centerFrequency_changed(quint64 value) +void Bladerf1OutputGui::on_centerFrequency_changed(quint64 value) { m_settings.m_centerFrequency = value * 1000; sendSettings(); } -void BladerfOutputGui::on_sampleRate_changed(quint64 value) +void Bladerf1OutputGui::on_sampleRate_changed(quint64 value) { m_settings.m_devSampleRate = value; sendSettings(); } -void BladerfOutputGui::on_bandwidth_currentIndexChanged(int index) +void Bladerf1OutputGui::on_bandwidth_currentIndexChanged(int index) { int newbw = BladerfBandwidths::getBandwidth(index); m_settings.m_bandwidth = newbw * 1000; sendSettings(); } -void BladerfOutputGui::on_interp_currentIndexChanged(int index) +void Bladerf1OutputGui::on_interp_currentIndexChanged(int index) { if ((index <0) || (index > 6)) return; @@ -242,7 +238,7 @@ void BladerfOutputGui::on_interp_currentIndexChanged(int index) sendSettings(); } -void BladerfOutputGui::on_vga1_valueChanged(int value) +void Bladerf1OutputGui::on_vga1_valueChanged(int value) { if ((value < BLADERF_TXVGA1_GAIN_MIN) || (value > BLADERF_TXVGA1_GAIN_MAX)) return; @@ -252,7 +248,7 @@ void BladerfOutputGui::on_vga1_valueChanged(int value) sendSettings(); } -void BladerfOutputGui::on_vga2_valueChanged(int value) +void Bladerf1OutputGui::on_vga2_valueChanged(int value) { if ((value < BLADERF_TXVGA2_GAIN_MIN) || (value > BLADERF_TXVGA2_GAIN_MAX)) return; @@ -262,7 +258,7 @@ void BladerfOutputGui::on_vga2_valueChanged(int value) sendSettings(); } -void BladerfOutputGui::on_xb200_currentIndexChanged(int index) +void Bladerf1OutputGui::on_xb200_currentIndexChanged(int index) { if (index == 1) // bypass { @@ -322,25 +318,25 @@ void BladerfOutputGui::on_xb200_currentIndexChanged(int index) sendSettings(); } -void BladerfOutputGui::on_startStop_toggled(bool checked) +void Bladerf1OutputGui::on_startStop_toggled(bool checked) { if (m_doApplySettings) { - BladerfOutput::MsgStartStop *message = BladerfOutput::MsgStartStop::create(checked); + Bladerf1Output::MsgStartStop *message = Bladerf1Output::MsgStartStop::create(checked); m_deviceSampleSink->getInputMessageQueue()->push(message); } } -void BladerfOutputGui::updateHardware() +void Bladerf1OutputGui::updateHardware() { qDebug() << "BladerfGui::updateHardware"; - BladerfOutput::MsgConfigureBladerf* message = BladerfOutput::MsgConfigureBladerf::create( m_settings, m_forceSettings); + Bladerf1Output::MsgConfigureBladerf1* message = Bladerf1Output::MsgConfigureBladerf1::create( m_settings, m_forceSettings); m_deviceSampleSink->getInputMessageQueue()->push(message); m_forceSettings = false; m_updateTimer.stop(); } -void BladerfOutputGui::updateStatus() +void Bladerf1OutputGui::updateStatus() { int state = m_deviceUISet->m_deviceSinkAPI->state(); @@ -369,7 +365,7 @@ void BladerfOutputGui::updateStatus() } } -unsigned int BladerfOutputGui::getXb200Index(bool xb_200, bladerf_xb200_path xb200Path, bladerf_xb200_filter xb200Filter) +unsigned int Bladerf1OutputGui::getXb200Index(bool xb_200, bladerf_xb200_path xb200Path, bladerf_xb200_filter xb200Filter) { if (xb_200) { diff --git a/plugins/samplesink/bladerfoutput/bladerfoutputgui.h b/plugins/samplesink/bladerf1output/bladerf1outputgui.h similarity index 91% rename from plugins/samplesink/bladerfoutput/bladerfoutputgui.h rename to plugins/samplesink/bladerf1output/bladerf1outputgui.h index 1cd815e48..a3dc5988a 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutputgui.h +++ b/plugins/samplesink/bladerf1output/bladerf1outputgui.h @@ -23,21 +23,21 @@ #include "util/messagequeue.h" -#include "bladerfoutput.h" +#include "bladerf1output.h" class DeviceSampleSink; class DeviceUISet; namespace Ui { - class BladerfOutputGui; + class Bladerf1OutputGui; } -class BladerfOutputGui : public QWidget, public PluginInstanceGUI { +class Bladerf1OutputGui : public QWidget, public PluginInstanceGUI { Q_OBJECT public: - explicit BladerfOutputGui(DeviceUISet *deviceUISet, QWidget* parent = 0); - virtual ~BladerfOutputGui(); + explicit Bladerf1OutputGui(DeviceUISet *deviceUISet, QWidget* parent = 0); + virtual ~Bladerf1OutputGui(); virtual void destroy(); void setName(const QString& name); @@ -52,12 +52,12 @@ public: virtual bool handleMessage(const Message& message); private: - Ui::BladerfOutputGui* ui; + Ui::Bladerf1OutputGui* ui; DeviceUISet* m_deviceUISet; bool m_doApplySettings; bool m_forceSettings; - BladeRFOutputSettings m_settings; + BladeRF1OutputSettings m_settings; QTimer m_updateTimer; QTimer m_statusTimer; DeviceSampleSink* m_deviceSampleSink; diff --git a/plugins/samplesink/bladerfoutput/bladerfoutputgui.ui b/plugins/samplesink/bladerf1output/bladerf1outputgui.ui similarity index 98% rename from plugins/samplesink/bladerfoutput/bladerfoutputgui.ui rename to plugins/samplesink/bladerf1output/bladerf1outputgui.ui index d6902578b..ec2607191 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutputgui.ui +++ b/plugins/samplesink/bladerf1output/bladerf1outputgui.ui @@ -1,7 +1,7 @@ - BladerfOutputGui - + Bladerf1OutputGui + 0 @@ -24,12 +24,12 @@ - Sans Serif + Liberation Sans 9 - BladeRF + BladeRF1 @@ -127,7 +127,7 @@ - DejaVu Sans Mono + Liberation Mono 20 @@ -328,7 +328,7 @@ - Bitstream Vera Sans Mono + Liberation Mono 12 diff --git a/plugins/samplesink/bladerf1output/bladerf1outputplugin.cpp b/plugins/samplesink/bladerf1output/bladerf1outputplugin.cpp new file mode 100644 index 000000000..cfcbc4a92 --- /dev/null +++ b/plugins/samplesink/bladerf1output/bladerf1outputplugin.cpp @@ -0,0 +1,149 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include "plugin/pluginapi.h" +#include "util/simpleserializer.h" +#include + +#include "bladerf1outputplugin.h" + +#ifdef SERVER_MODE +#include "bladerf1output.h" +#else +#include "bladerf1outputgui.h" +#endif + +const PluginDescriptor Bladerf1OutputPlugin::m_pluginDescriptor = { + QString("BladeRF1 Output"), + QString("4.2.0"), + QString("(c) Edouard Griffiths, F4EXB"), + QString("https://github.com/f4exb/sdrangel"), + true, + QString("https://github.com/f4exb/sdrangel") +}; + +const QString Bladerf1OutputPlugin::m_hardwareID = "BladeRF1"; +const QString Bladerf1OutputPlugin::m_deviceTypeID = BLADERF1OUTPUT_DEVICE_TYPE_ID; + +Bladerf1OutputPlugin::Bladerf1OutputPlugin(QObject* parent) : + QObject(parent) +{ +} + +const PluginDescriptor& Bladerf1OutputPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void Bladerf1OutputPlugin::initPlugin(PluginAPI* pluginAPI) +{ + pluginAPI->registerSampleSink(m_deviceTypeID, this); +} + +PluginInterface::SamplingDevices Bladerf1OutputPlugin::enumSampleSinks() +{ + SamplingDevices result; + struct bladerf_devinfo *devinfo = 0; + + int count = bladerf_get_device_list(&devinfo); + + if (devinfo) + { + for(int i = 0; i < count; i++) + { + struct bladerf *dev; + + int status = bladerf_open_with_devinfo(&dev, &devinfo[i]); + + if (status == BLADERF_ERR_NODEV) + { + qCritical("BladerfOutputPlugin::enumSampleSinks: No device at index %d", i); + continue; + } + else if (status != 0) + { + qCritical("BladerfOutputPlugin::enumSampleSinks: Failed to open device at index %d", i); + continue; + } + + const char *boardName = bladerf_get_board_name(dev); + + if (strcmp(boardName, "bladerf1") == 0) + { + QString displayedName(QString("BladeRF1[%1] %2").arg(devinfo[i].instance).arg(devinfo[i].serial)); + + result.append(SamplingDevice(displayedName, + m_hardwareID, + m_deviceTypeID, + QString(devinfo[i].serial), + i, + PluginInterface::SamplingDevice::PhysicalDevice, + false, + 1, + 0)); + + } + + bladerf_close(dev); + } + + bladerf_free_device_list(devinfo); // Valgrind memcheck + } + + return result; +} + +#ifdef SERVER_MODE +PluginInstanceGUI* Bladerf1OutputPlugin::createSampleSinkPluginInstanceGUI( + const QString& sinkId __attribute__((unused)), + QWidget **widget __attribute__((unused)), + DeviceUISet *deviceUISet __attribute__((unused))) +{ + return 0; +} +#else +PluginInstanceGUI* Bladerf1OutputPlugin::createSampleSinkPluginInstanceGUI( + const QString& sinkId, + QWidget **widget, + DeviceUISet *deviceUISet) +{ + if(sinkId == m_deviceTypeID) + { + Bladerf1OutputGui* gui = new Bladerf1OutputGui(deviceUISet); + *widget = gui; + return gui; + } + else + { + return 0; + } +} +#endif + +DeviceSampleSink* Bladerf1OutputPlugin::createSampleSinkPluginInstanceOutput(const QString& sinkId, DeviceSinkAPI *deviceAPI) +{ + if(sinkId == m_deviceTypeID) + { + Bladerf1Output* output = new Bladerf1Output(deviceAPI); + return output; + } + else + { + return 0; + } +} diff --git a/plugins/samplesink/bladerfoutput/bladerfoutputplugin.h b/plugins/samplesink/bladerf1output/bladerf1outputplugin.h similarity index 88% rename from plugins/samplesink/bladerfoutput/bladerfoutputplugin.h rename to plugins/samplesink/bladerf1output/bladerf1outputplugin.h index a2e057995..0a99e80cc 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutputplugin.h +++ b/plugins/samplesink/bladerf1output/bladerf1outputplugin.h @@ -22,15 +22,15 @@ class PluginAPI; -#define BLADERFOUTPUT_DEVICE_TYPE_ID "sdrangel.samplesource.bladerfoutput" +#define BLADERF1OUTPUT_DEVICE_TYPE_ID "sdrangel.samplesource.bladerf1output" -class BladerfOutputPlugin : public QObject, public PluginInterface { +class Bladerf1OutputPlugin : public QObject, public PluginInterface { Q_OBJECT Q_INTERFACES(PluginInterface) - Q_PLUGIN_METADATA(IID BLADERFOUTPUT_DEVICE_TYPE_ID) + Q_PLUGIN_METADATA(IID BLADERF1OUTPUT_DEVICE_TYPE_ID) public: - explicit BladerfOutputPlugin(QObject* parent = NULL); + explicit Bladerf1OutputPlugin(QObject* parent = NULL); const PluginDescriptor& getPluginDescriptor() const; void initPlugin(PluginAPI* pluginAPI); diff --git a/plugins/samplesink/bladerfoutput/bladerfoutputsettings.cpp b/plugins/samplesink/bladerf1output/bladerf1outputsettings.cpp similarity index 90% rename from plugins/samplesink/bladerfoutput/bladerfoutputsettings.cpp rename to plugins/samplesink/bladerf1output/bladerf1outputsettings.cpp index 768659fd6..1ff12929c 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutputsettings.cpp +++ b/plugins/samplesink/bladerf1output/bladerf1outputsettings.cpp @@ -14,17 +14,18 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include "bladerf1outputsettings.h" + #include #include "util/simpleserializer.h" -#include "bladerfoutputsettings.h" -BladeRFOutputSettings::BladeRFOutputSettings() +BladeRF1OutputSettings::BladeRF1OutputSettings() { resetToDefaults(); } -void BladeRFOutputSettings::resetToDefaults() +void BladeRF1OutputSettings::resetToDefaults() { m_centerFrequency = 435000*1000; m_devSampleRate = 3072000; @@ -37,7 +38,7 @@ void BladeRFOutputSettings::resetToDefaults() m_xb200Filter = BLADERF_XB200_AUTO_1DB; } -QByteArray BladeRFOutputSettings::serialize() const +QByteArray BladeRF1OutputSettings::serialize() const { SimpleSerializer s(1); @@ -53,7 +54,7 @@ QByteArray BladeRFOutputSettings::serialize() const return s.final(); } -bool BladeRFOutputSettings::deserialize(const QByteArray& data) +bool BladeRF1OutputSettings::deserialize(const QByteArray& data) { SimpleDeserializer d(data); diff --git a/plugins/samplesink/bladerfoutput/bladerfoutputsettings.h b/plugins/samplesink/bladerf1output/bladerf1outputsettings.h similarity index 96% rename from plugins/samplesink/bladerfoutput/bladerfoutputsettings.h rename to plugins/samplesink/bladerf1output/bladerf1outputsettings.h index ca51895e6..07b1115ed 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutputsettings.h +++ b/plugins/samplesink/bladerf1output/bladerf1outputsettings.h @@ -20,7 +20,7 @@ #include #include -struct BladeRFOutputSettings { +struct BladeRF1OutputSettings { quint64 m_centerFrequency; qint32 m_devSampleRate; qint32 m_vga1; @@ -31,7 +31,7 @@ struct BladeRFOutputSettings { bladerf_xb200_path m_xb200Path; bladerf_xb200_filter m_xb200Filter; - BladeRFOutputSettings(); + BladeRF1OutputSettings(); void resetToDefaults(); QByteArray serialize() const; bool deserialize(const QByteArray& data); diff --git a/plugins/samplesink/bladerfoutput/bladerfoutputthread.cpp b/plugins/samplesink/bladerf1output/bladerf1outputthread.cpp similarity index 84% rename from plugins/samplesink/bladerfoutput/bladerfoutputthread.cpp rename to plugins/samplesink/bladerf1output/bladerf1outputthread.cpp index 94eddc475..db4f66b04 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutputthread.cpp +++ b/plugins/samplesink/bladerf1output/bladerf1outputthread.cpp @@ -14,28 +14,30 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include "bladerf1outputthread.h" + #include #include -#include "bladerfoutputthread.h" +#include -BladerfOutputThread::BladerfOutputThread(struct bladerf* dev, SampleSourceFifo* sampleFifo, QObject* parent) : +Bladerf1OutputThread::Bladerf1OutputThread(struct bladerf* dev, SampleSourceFifo* sampleFifo, QObject* parent) : QThread(parent), m_running(false), m_dev(dev), m_sampleFifo(sampleFifo), - m_log2Interp(0), - m_fcPos(0) + m_log2Interp(0) { + std::fill(m_buf, m_buf + 2*BLADERFOUTPUT_BLOCKSIZE, 0); } -BladerfOutputThread::~BladerfOutputThread() +Bladerf1OutputThread::~Bladerf1OutputThread() { stopWork(); } -void BladerfOutputThread::startWork() +void Bladerf1OutputThread::startWork() { m_startWaitMutex.lock(); start(); @@ -44,18 +46,18 @@ void BladerfOutputThread::startWork() m_startWaitMutex.unlock(); } -void BladerfOutputThread::stopWork() +void Bladerf1OutputThread::stopWork() { m_running = false; wait(); } -void BladerfOutputThread::setLog2Interpolation(unsigned int log2_interp) +void Bladerf1OutputThread::setLog2Interpolation(unsigned int log2_interp) { m_log2Interp = log2_interp; } -void BladerfOutputThread::run() +void Bladerf1OutputThread::run() { int res; @@ -77,7 +79,7 @@ void BladerfOutputThread::run() } // Interpolate according to specified log2 (ex: log2=4 => decim=16) -void BladerfOutputThread::callback(qint16* buf, qint32 len) +void Bladerf1OutputThread::callback(qint16* buf, qint32 len) { SampleVector::iterator beginRead; m_sampleFifo->readAdvance(beginRead, len/(1< m_interpolators; diff --git a/plugins/samplesink/bladerfoutput/readme.md b/plugins/samplesink/bladerf1output/readme.md similarity index 79% rename from plugins/samplesink/bladerfoutput/readme.md rename to plugins/samplesink/bladerf1output/readme.md index 04b86a7c4..fde65d313 100644 --- a/plugins/samplesink/bladerfoutput/readme.md +++ b/plugins/samplesink/bladerf1output/readme.md @@ -1,20 +1,22 @@ -

BladeRF output plugin

+

BladeRF classic (v1) output plugin

Introduction

-This output sample sink plugin sends its samples to a [BladeRF device](https://www.nuand.com/). +This output sample sink plugin sends its samples to a [BladeRF1 device](https://www.nuand.com/bladerf-1). -Warning to Windows users: concurrent use of Rx and Tx does not work correctly hence full duplex is not fully operational. For best results use BladeRF as a half duplex device like HackRF i.e. do not run Tx and Rx concurrently. +Warning to Windows users: concurrent use of Rx and Tx does not work correctly hence full duplex is not fully operational. For best results use BladeRF as a half duplex device like HackRF i.e. do not run Tx and Rx concurrently. Anyway from version 4.2.0 using LibbladeRF v.2 this is available in Linux distributions only.

Build

The plugin will be built only if the [BladeRF host library](https://github.com/Nuand/bladeRF) is installed in your system. If you build it from source and install it in a custom location say: `/opt/install/libbladeRF` you will have to add `-DLIBBLADERF_INCLUDE_DIR=/opt/install/libbladeRF/include -DLIBBLADERF_LIBRARIES=/opt/install/libbladeRF/lib/libbladeRF.so` to the cmake command line. -The BladeRF Host library is also provided by many Linux distributions and is built in the SDRangel binary releases. +Note that since version 4.2.0 the libbladeRF v2 (specifically the git tag 2018.08) is used. + +The BladeRF Host library is also provided by many Linux distributions (check its version) and is built in the SDRangel binary releases.

Interface

-![BladeRF output plugin GUI](../../../doc/img/BladeRFOutput_plugin.png) +![BladeRF1 output plugin GUI](../../../doc/img/BladeRF1Output_plugin.png)

1: Start/Stop

@@ -22,7 +24,7 @@ Device start / stop button. - Blue triangle icon: device is ready and can be started - Red square icon: device is running and can be stopped - - Magenta (or pink) square icon: an error occured. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again. + - Magenta (or pink) square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again.

2: Baseband sample rate

@@ -32,11 +34,11 @@ Transmission latency depends essentially in the delay in the sample FIFO. The FI For interpolation by 32 the size is fixed at 150000 samples, Delay is 150000 / B where B is the baseband sample rate. Below is the delay in seconds vs baseband sample rate in kS/s from 48 to 500 kS/s: -![BladeRF output plugin FIFO delay 32](../../../doc/img/BladeRFOutput_plugin_fifodly_32.png) +![BladeRF1 output plugin FIFO delay 32](../../../doc/img/BladeRF1Output_plugin_fifodly_32.png) For lower interpolation rates the size is calculated to give a fixed delay of 250 ms or 75000 samples whichever is bigger. Below is the delay in seconds vs baseband sample rate in kS/s from 48 to 400 kS/s. The 250 ms delay is reached at 300 kS/s: -![BladeRF output plugin FIFO delay other](../../../doc/img/BladeRFOutput_plugin_fifodly_other.png) +![BladeRF1 output plugin FIFO delay other](../../../doc/img/BladeRF1Output_plugin_fifodly_other.png)

3: Frequency

@@ -72,7 +74,7 @@ This controls the optional XB-200 add-on when it is fitted to the BladeRF main b This is the BladeRF device DAC sample rate in S/s. -Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

7: Tx filter bandwidth

@@ -84,4 +86,4 @@ The VGA1 (relative) gain can be adjusted from -35 dB to -4 dB in 1 dB steps. The

9: Variable gain amplifier #2 gain

-The VGA2 gain can be adjusted from 0 dB to 25 dB in 1 dB steps. The VGA2 is inside the LMS6002D chip and is placed after the RF mixer. It can be considered as the PA (Power AMplifier). The maximum output power when both VGA1 and VGA2 are at their maximum is about 4 mW (6 dBm). \ No newline at end of file +The VGA2 gain can be adjusted from 0 dB to 25 dB in 1 dB steps. The VGA2 is inside the LMS6002D chip and is placed after the RF mixer. It can be considered as the PA (Power Amplifier). The maximum output power when both VGA1 and VGA2 are at their maximum is about 4 mW (6 dBm). diff --git a/plugins/samplesink/bladerf2output/CMakeLists.txt b/plugins/samplesink/bladerf2output/CMakeLists.txt new file mode 100644 index 000000000..0572c08fa --- /dev/null +++ b/plugins/samplesink/bladerf2output/CMakeLists.txt @@ -0,0 +1,80 @@ +project(bladerf2output) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +set(bladerf2output_SOURCES + bladerf2outputgui.cpp + bladerf2output.cpp + bladerf2outputplugin.cpp + bladerf2outputsettings.cpp + bladerf2outputthread.cpp +) + +set(bladerf2output_HEADERS + bladerf2outputgui.h + bladerf2output.h + bladerf2outputplugin.h + bladerf2outputsettings.h + bladerf2outputthread.h +) + +set(bladerf2output_FORMS + bladerf2outputgui.ui +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERFLIBSRC}/include + ${LIBBLADERFLIBSRC}/src +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERF_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +#include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +#qt4_wrap_cpp(bladerf2output_HEADERS_MOC ${bladerf2output_HEADERS}) +qt5_wrap_ui(bladerf2output_FORMS_HEADERS ${bladerf2output_FORMS}) + +add_library(outputbladerf2 SHARED + ${bladerf2output_SOURCES} + ${bladerf2output_HEADERS_MOC} + ${bladerf2output_FORMS_HEADERS} +) + +if (BUILD_DEBIAN) +target_link_libraries(outputbladerf2 + ${QT_LIBRARIES} + bladerf + sdrbase + sdrgui + swagger + bladerf2device +) +else (BUILD_DEBIAN) +target_link_libraries(outputbladerf2 + ${QT_LIBRARIES} + ${LIBBLADERF_LIBRARIES} + sdrbase + sdrgui + swagger + bladerf2device +) +endif (BUILD_DEBIAN) + +target_link_libraries(outputbladerf2 Qt5::Core Qt5::Widgets) + +install(TARGETS outputbladerf2 DESTINATION lib/plugins/samplesink) diff --git a/plugins/samplesink/bladerf2output/bladerf2output.cpp b/plugins/samplesink/bladerf2output/bladerf2output.cpp new file mode 100644 index 000000000..8ebec2a37 --- /dev/null +++ b/plugins/samplesink/bladerf2output/bladerf2output.cpp @@ -0,0 +1,1045 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + + +#include +#include +#include + +#include "SWGDeviceState.h" +#include "SWGDeviceSettings.h" +#include "SWGBladeRF2InputSettings.h" +#include "SWGDeviceReport.h" +#include "SWGBladeRF2OutputReport.h" + +#include "dsp/dspcommands.h" +#include "dsp/dspengine.h" +#include "device/devicesinkapi.h" +#include "device/devicesourceapi.h" +#include "bladerf2/devicebladerf2shared.h" +#include "bladerf2/devicebladerf2.h" + +#include "bladerf2outputthread.h" +#include "bladerf2output.h" + +MESSAGE_CLASS_DEFINITION(BladeRF2Output::MsgConfigureBladeRF2, Message) +MESSAGE_CLASS_DEFINITION(BladeRF2Output::MsgStartStop, Message) +MESSAGE_CLASS_DEFINITION(BladeRF2Output::MsgReportGainRange, Message) + +BladeRF2Output::BladeRF2Output(DeviceSinkAPI *deviceAPI) : + m_deviceAPI(deviceAPI), + m_settings(), + m_dev(0), + m_thread(0), + m_deviceDescription("BladeRF2Output"), + m_running(false) +{ + openDevice(); +} + +BladeRF2Output::~BladeRF2Output() +{ + if (m_running) { + stop(); + } + + closeDevice(); +} + +void BladeRF2Output::destroy() +{ + delete this; +} + +bool BladeRF2Output::openDevice() +{ + m_sampleSourceFifo.resize(m_settings.m_devSampleRate/(1<<(m_settings.m_log2Interp <= 4 ? m_settings.m_log2Interp : 4))); + + // look for Tx buddies and get reference to the device object + if (m_deviceAPI->getSinkBuddies().size() > 0) // look sink sibling first + { + qDebug("BladeRF2Output::openDevice: look in Tx buddies"); + + DeviceSinkAPI *sinkBuddy = m_deviceAPI->getSinkBuddies()[0]; + DeviceBladeRF2Shared *deviceBladeRF2Shared = (DeviceBladeRF2Shared*) sinkBuddy->getBuddySharedPtr(); + + if (deviceBladeRF2Shared == 0) + { + qCritical("BladeRF2Output::openDevice: the sink buddy shared pointer is null"); + return false; + } + + DeviceBladeRF2 *device = deviceBladeRF2Shared->m_dev; + + if (device == 0) + { + qCritical("BladeRF2Output::openDevice: cannot get device pointer from Tx buddy"); + return false; + } + + m_deviceShared.m_dev = device; + } + // look for Rx buddies and get reference to the device object + else if (m_deviceAPI->getSourceBuddies().size() > 0) // then source + { + qDebug("BladeRF2Output::openDevice: look in Rx buddies"); + + DeviceSourceAPI *sourceBuddy = m_deviceAPI->getSourceBuddies()[0]; + DeviceBladeRF2Shared *deviceBladeRF2Shared = (DeviceBladeRF2Shared*) sourceBuddy->getBuddySharedPtr(); + + if (deviceBladeRF2Shared == 0) + { + qCritical("BladeRF2Output::openDevice: the source buddy shared pointer is null"); + return false; + } + + DeviceBladeRF2 *device = deviceBladeRF2Shared->m_dev; + + if (device == 0) + { + qCritical("BladeRF2Output::openDevice: cannot get device pointer from Rx buddy"); + return false; + } + + m_deviceShared.m_dev = device; + } + // There are no buddies then create the first BladeRF2 device + else + { + qDebug("BladeRF2Output::openDevice: open device here"); + + m_deviceShared.m_dev = new DeviceBladeRF2(); + char serial[256]; + strcpy(serial, qPrintable(m_deviceAPI->getSampleSinkSerial())); + + if (!m_deviceShared.m_dev->open(serial)) + { + qCritical("BladeRF2Output::openDevice: cannot open BladeRF2 device"); + return false; + } + } + + m_deviceShared.m_channel = m_deviceAPI->getItemIndex(); // publicly allocate channel + m_deviceShared.m_sink = this; + m_deviceAPI->setBuddySharedPtr(&m_deviceShared); // propagate common parameters to API + return true; +} + +void BladeRF2Output::closeDevice() +{ + if (m_deviceShared.m_dev == 0) { // was never open + return; + } + + if (m_running) { + stop(); + } + + if (m_thread) { // stills own the thread => transfer to a buddy + moveThreadToBuddy(); + } + + m_deviceShared.m_channel = -1; // publicly release channel + m_deviceShared.m_sink = 0; + + // No buddies so effectively close the device + + if ((m_deviceAPI->getSinkBuddies().size() == 0) && (m_deviceAPI->getSourceBuddies().size() == 0)) + { + m_deviceShared.m_dev->close(); + delete m_deviceShared.m_dev; + m_deviceShared.m_dev = 0; + } +} + +void BladeRF2Output::init() +{ + applySettings(m_settings, true); +} + +BladeRF2OutputThread *BladeRF2Output::findThread() +{ + if (m_thread == 0) // this does not own the thread + { + BladeRF2OutputThread *bladeRF2OutputThread = 0; + + // find a buddy that has allocated the thread + const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies(); + std::vector::const_iterator it = sinkBuddies.begin(); + + for (; it != sinkBuddies.end(); ++it) + { + BladeRF2Output *buddySink = ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_sink; + + if (buddySink) + { + bladeRF2OutputThread = buddySink->getThread(); + + if (bladeRF2OutputThread) { + break; + } + } + } + + return bladeRF2OutputThread; + } + else + { + return m_thread; // own thread + } +} + +void BladeRF2Output::moveThreadToBuddy() +{ + const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies(); + std::vector::const_iterator it = sinkBuddies.begin(); + + for (; it != sinkBuddies.end(); ++it) + { + BladeRF2Output *buddySink = ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_sink; + + if (buddySink) + { + buddySink->setThread(m_thread); + m_thread = 0; // zero for others + } + } +} + +bool BladeRF2Output::start() +{ + // There is a single thread per physical device (Tx side). This thread is unique and referenced by a unique + // buddy in the group of sink buddies associated with this physical device. + // + // This start method is responsible for managing the thread and channel enabling when the streaming of a Tx channel is started + // + // It checks the following conditions + // - the thread is allocated or not (by itself or one of its buddies). If it is it grabs the thread pointer. + // - the requested channel is the first (0) or the following (just 1 in BladeRF 2 case) + // + // The BladeRF support library lets you work in two possible modes: + // - Single Output (SO) with only one channel streaming. This HAS to be channel 0. + // - Multiple Output (MO) with two channels streaming using interleaved samples. It MUST be in this configuration if channel 1 + // is used. When we will run with only channel 1 streaming from the client perspective the channel 0 will actually be enabled + // and streaming but zero samples will be sent to it. + // + // It manages the transition form SO where only one channel (the first or channel 0) should be running to the + // Multiple Output (MO) if the requested channel is 1. More generally it checks if the requested channel is within the current + // channel range allocated in the thread or past it. To perform the transition it stops the thread, deletes it and creates a new one. + // It marks the thread as needing start. + // + // If the requested channel is within the thread channel range (this thread being already allocated) it simply removes its FIFO reference + // so that the samples are not taken from the FIFO anymore and leaves the thread unchanged (no stop, no delete/new) + // + // If there is no thread allocated it creates a new one with a number of channels that fits the requested channel. That is + // 1 if channel 0 is requested (SO mode) and 2 if channel 1 is requested (MO mode). It marks the thread as needing start. + // + // Eventually it registers the FIFO in the thread. If the thread has to be started it enables the channels up to the number of channels + // allocated in the thread and starts the thread. + + if (!m_deviceShared.m_dev) + { + qDebug("BladeRF2Output::start: no device object"); + return false; + } + + int requestedChannel = m_deviceAPI->getItemIndex(); + BladeRF2OutputThread *bladeRF2OutputThread = findThread(); + bool needsStart = false; + + if (bladeRF2OutputThread) // if thread is already allocated + { + qDebug("BladeRF2Output::start: thread is already allocated"); + + int nbOriginalChannels = bladeRF2OutputThread->getNbChannels(); + + if (requestedChannel+1 > nbOriginalChannels) // expansion by deleting and re-creating the thread + { + qDebug("BladeRF2Output::start: expand channels. Re-allocate thread and take ownership"); + + SampleSourceFifo **fifos = new SampleSourceFifo*[nbOriginalChannels]; + unsigned int *log2Interps = new unsigned int[nbOriginalChannels]; + + for (int i = 0; i < nbOriginalChannels; i++) // save original FIFO references and data + { + fifos[i] = bladeRF2OutputThread->getFifo(i); + log2Interps[i] = bladeRF2OutputThread->getLog2Interpolation(i); + } + + bladeRF2OutputThread->stopWork(); + delete bladeRF2OutputThread; + bladeRF2OutputThread = new BladeRF2OutputThread(m_deviceShared.m_dev->getDev(), requestedChannel+1); + m_thread = bladeRF2OutputThread; // take ownership + + for (int i = 0; i < nbOriginalChannels; i++) // restore original FIFO references + { + bladeRF2OutputThread->setFifo(i, fifos[i]); + bladeRF2OutputThread->setLog2Interpolation(i, log2Interps[i]); + } + + // remove old thread address from buddies (reset in all buddies) + const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies(); + std::vector::const_iterator it = sinkBuddies.begin(); + + for (; it != sinkBuddies.end(); ++it) { + ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_sink->setThread(0); + } + + // close all channels + for (int i = bladeRF2OutputThread->getNbChannels()-1; i >= 0; i--) { + m_deviceShared.m_dev->closeTx(i); + } + + needsStart = true; + } + else + { + qDebug("BladeRF2Output::start: keep buddy thread"); + } + } + else // first allocation + { + qDebug("BladeRF2Output::start: allocate thread and take ownership"); + bladeRF2OutputThread = new BladeRF2OutputThread(m_deviceShared.m_dev->getDev(), requestedChannel+1); + m_thread = bladeRF2OutputThread; // take ownership + needsStart = true; + } + + bladeRF2OutputThread->setFifo(requestedChannel, &m_sampleSourceFifo); + bladeRF2OutputThread->setLog2Interpolation(requestedChannel, m_settings.m_log2Interp); + + applySettings(m_settings, true); // re-apply forcibly to set sample rate with the new number of channels + + if (needsStart) + { + qDebug("BladeRF2Output::start: enabling channel(s) and (re)starting the thread"); + + for (unsigned int i = 0; i < bladeRF2OutputThread->getNbChannels(); i++) // open all channels + { + if (!m_deviceShared.m_dev->openTx(i)) { + qCritical("BladeRF2Output::start: channel %u cannot be enabled", i); + } + } + + bladeRF2OutputThread->startWork(); + } + + qDebug("BladeRF2Output::start: started"); + m_running = true; + + return true; +} + +void BladeRF2Output::stop() +{ + // This stop method is responsible for managing the thread and channel disabling when the streaming of + // a Tx channel is stopped + // + // If the thread is currently managing only one channel (SO mode). The thread can be just stopped and deleted. + // Then the channel is closed (disabled). + // + // If the thread is currently managing many channels (MO mode) and we are removing the last channel. The transition + // from MO to SO or reduction of MO size is handled by stopping the thread, deleting it and creating a new one + // with one channel less if (and only if) there is still a channel active. + // + // If the thread is currently managing many channels (MO mode) but the channel being stopped is not the last + // channel then the FIFO reference is simply removed from the thread so that this FIFO will not be used anymore. + // In this case the channel is not closed (disabled) so that other channels can continue with the + // same configuration. The device continues streaming on this channel but the samples are set to all zeros. + + if (!m_running) { + return; + } + + int requestedChannel = m_deviceAPI->getItemIndex(); + BladeRF2OutputThread *bladeRF2OutputThread = findThread(); + + if (bladeRF2OutputThread == 0) { // no thread allocated + return; + } + + int nbOriginalChannels = bladeRF2OutputThread->getNbChannels(); + + if (nbOriginalChannels == 1) // SO mode => just stop and delete the thread + { + qDebug("BladeRF2Output::stop: SO mode. Just stop and delete the thread"); + bladeRF2OutputThread->stopWork(); + delete bladeRF2OutputThread; + m_thread = 0; + + // remove old thread address from buddies (reset in all buddies) + const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies(); + std::vector::const_iterator it = sinkBuddies.begin(); + + for (; it != sinkBuddies.end(); ++it) { + ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_sink->setThread(0); + } + + m_deviceShared.m_dev->closeTx(0); // close the unique channel + } + else if (requestedChannel == nbOriginalChannels - 1) // remove last MO channel => reduce by deleting and re-creating the thread + { + qDebug("BladeRF2Output::stop: MO mode. Reduce by deleting and re-creating the thread"); + bladeRF2OutputThread->stopWork(); + SampleSourceFifo **fifos = new SampleSourceFifo*[nbOriginalChannels-1]; + unsigned int *log2Interps = new unsigned int[nbOriginalChannels-1]; + bool stillActiveFIFO = false; + + for (int i = 0; i < nbOriginalChannels-1; i++) // save original FIFO references + { + fifos[i] = bladeRF2OutputThread->getFifo(i); + stillActiveFIFO = stillActiveFIFO || (bladeRF2OutputThread->getFifo(i) != 0); + log2Interps[i] = bladeRF2OutputThread->getLog2Interpolation(i); + } + + delete bladeRF2OutputThread; + m_thread = 0; + + if (stillActiveFIFO) + { + bladeRF2OutputThread = new BladeRF2OutputThread(m_deviceShared.m_dev->getDev(), nbOriginalChannels-1); + m_thread = bladeRF2OutputThread; // take ownership + + for (int i = 0; i < nbOriginalChannels-1; i++) // restore original FIFO references + { + bladeRF2OutputThread->setFifo(i, fifos[i]); + bladeRF2OutputThread->setLog2Interpolation(i, log2Interps[i]); + } + } + else + { + qDebug("BladeRF2Output::stop: do not re-create thread as there are no more FIFOs active"); + } + + // remove old thread address from buddies (reset in all buddies) + const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies(); + std::vector::const_iterator it = sinkBuddies.begin(); + + for (; it != sinkBuddies.end(); ++it) { + ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_sink->setThread(0); + } + + // close all channels + for (int i = nbOriginalChannels-1; i >= 0; i--) { + m_deviceShared.m_dev->closeTx(i); + } + + if (stillActiveFIFO) + { + qDebug("BladeRF2Output::stop: enabling channel(s) and restarting the thread"); + + for (unsigned int i = 0; i < bladeRF2OutputThread->getNbChannels(); i++) // open all channels + { + if (!m_deviceShared.m_dev->openTx(i)) { + qCritical("BladeRF2Output::start: channel %u cannot be enabled", i); + } + } + + bladeRF2OutputThread->startWork(); + } + } + else // remove channel from existing thread + { + qDebug("BladeRF2Output::stop: MO mode. Not changing MO configuration. Just remove FIFO reference"); + bladeRF2OutputThread->setFifo(requestedChannel, 0); // remove FIFO + } + + applySettings(m_settings, true); // re-apply forcibly to set sample rate with the new number of channels + + m_running = false; +} + +QByteArray BladeRF2Output::serialize() const +{ + return m_settings.serialize(); +} + +bool BladeRF2Output::deserialize(const QByteArray& data) +{ + bool success = true; + + if (!m_settings.deserialize(data)) + { + m_settings.resetToDefaults(); + success = false; + } + + MsgConfigureBladeRF2* message = MsgConfigureBladeRF2::create(m_settings, true); + m_inputMessageQueue.push(message); + + if (m_guiMessageQueue) + { + MsgConfigureBladeRF2* messageToGUI = MsgConfigureBladeRF2::create(m_settings, true); + m_guiMessageQueue->push(messageToGUI); + } + + return success; +} + +const QString& BladeRF2Output::getDeviceDescription() const +{ + return m_deviceDescription; +} + +int BladeRF2Output::getSampleRate() const +{ + int rate = m_settings.m_devSampleRate; + return (rate / (1<push(messageToGUI); + } +} + +bool BladeRF2Output::setDeviceCenterFrequency(struct bladerf *dev, int requestedChannel, quint64 freq_hz, int loPpmTenths) +{ + qint64 df = ((qint64)freq_hz * loPpmTenths) / 10000000LL; + freq_hz += df; + + int status = bladerf_set_frequency(dev, BLADERF_CHANNEL_TX(requestedChannel), freq_hz); + + if (status < 0) { + qWarning("BladeRF2Output::setDeviceCenterFrequency: bladerf_set_frequency(%lld) failed: %s", + freq_hz, bladerf_strerror(status)); + return false; + } + else + { + qDebug("BladeRF2Output::setDeviceCenterFrequency: bladerf_set_frequency(%lld)", freq_hz); + return true; + } +} + +void BladeRF2Output::getFrequencyRange(uint64_t& min, uint64_t& max, int& step) +{ + if (m_deviceShared.m_dev) { + m_deviceShared.m_dev->getFrequencyRangeTx(min, max, step); + } +} + +void BladeRF2Output::getSampleRateRange(int& min, int& max, int& step) +{ + if (m_deviceShared.m_dev) { + m_deviceShared.m_dev->getSampleRateRangeTx(min, max, step); + } +} + +void BladeRF2Output::getBandwidthRange(int& min, int& max, int& step) +{ + if (m_deviceShared.m_dev) { + m_deviceShared.m_dev->getBandwidthRangeTx(min, max, step); + } +} + +void BladeRF2Output::getGlobalGainRange(int& min, int& max, int& step) +{ + if (m_deviceShared.m_dev) { + m_deviceShared.m_dev->getGlobalGainRangeTx(min, max, step); + } +} + +bool BladeRF2Output::handleMessage(const Message& message) +{ + if (MsgConfigureBladeRF2::match(message)) + { + MsgConfigureBladeRF2& conf = (MsgConfigureBladeRF2&) message; + qDebug() << "BladeRF2Output::handleMessage: MsgConfigureBladeRF2"; + + if (!applySettings(conf.getSettings(), conf.getForce())) + { + qDebug("BladeRF2Output::handleMessage: MsgConfigureBladeRF2 config error"); + } + + return true; + } + else if (DeviceBladeRF2Shared::MsgReportBuddyChange::match(message)) + { + DeviceBladeRF2Shared::MsgReportBuddyChange& report = (DeviceBladeRF2Shared::MsgReportBuddyChange&) message; + struct bladerf *dev = m_deviceShared.m_dev->getDev(); + BladeRF2OutputSettings settings = m_settings; + int status; + unsigned int tmp_uint; + bool tmp_bool; + + // evaluate changes that may have been introduced by changes in a buddy + + if (dev) // The BladeRF device must have been open to do so + { + int requestedChannel = m_deviceAPI->getItemIndex(); + + if (report.getRxElseTx()) // Rx buddy change: check for sample rate change only + { + settings.m_devSampleRate = report.getDevSampleRate(); +// status = bladerf_get_sample_rate(dev, BLADERF_CHANNEL_TX(requestedChannel), &tmp_uint); +// +// if (status < 0) { +// qCritical("BladeRF2Output::handleMessage: MsgReportBuddyChange: bladerf_get_sample_rate error: %s", bladerf_strerror(status)); +// } else { +// settings.m_devSampleRate = tmp_uint; +// } + } + else // Tx buddy change: check for: frequency, gain mode and value, bias tee, sample rate, bandwidth + { + settings.m_devSampleRate = report.getDevSampleRate(); + settings.m_LOppmTenths = report.getLOppmTenths(); + settings.m_centerFrequency = report.getCenterFrequency(); + + status = bladerf_get_bandwidth(dev, BLADERF_CHANNEL_TX(requestedChannel), &tmp_uint); + + if (status < 0) { + qCritical("BladeRF2Output::handleMessage: MsgReportBuddyChange: bladerf_get_bandwidth error: %s", bladerf_strerror(status)); + } else { + settings.m_bandwidth = tmp_uint; + } + + status = bladerf_get_bias_tee(dev, BLADERF_CHANNEL_TX(requestedChannel), &tmp_bool); + + if (status < 0) { + qCritical("BladeRF2Output::handleMessage: MsgReportBuddyChange: bladerf_get_bias_tee error: %s", bladerf_strerror(status)); + } else { + settings.m_biasTee = tmp_bool; + } + } + + // change DSP settings if buddy change introduced a change in center frequency or base rate + if ((settings.m_centerFrequency != m_settings.m_centerFrequency) || (settings.m_devSampleRate != m_settings.m_devSampleRate)) + { + int sampleRate = settings.m_devSampleRate/(1<getDeviceEngineInputMessageQueue()->push(notif); + } + + m_settings = settings; // acknowledge the new settings + + // propagate settings to GUI if any + if (getMessageQueueToGUI()) + { + MsgConfigureBladeRF2 *reportToGUI = MsgConfigureBladeRF2::create(m_settings, false); + getMessageQueueToGUI()->push(reportToGUI); + } + } + + return true; + } + else if (MsgStartStop::match(message)) + { + MsgStartStop& cmd = (MsgStartStop&) message; + qDebug() << "BladeRF2Input::handleMessage: MsgStartStop: " << (cmd.getStartStop() ? "start" : "stop"); + + if (cmd.getStartStop()) + { + if (m_deviceAPI->initGeneration()) + { + m_deviceAPI->startGeneration(); + } + } + else + { + m_deviceAPI->stopGeneration(); + } + + return true; + } + else + { + return false; + } +} + +bool BladeRF2Output::applySettings(const BladeRF2OutputSettings& settings, bool force) +{ + bool forwardChangeOwnDSP = false; + bool forwardChangeRxBuddies = false; + bool forwardChangeTxBuddies = false; + + struct bladerf *dev = m_deviceShared.m_dev->getDev(); + int requestedChannel = m_deviceAPI->getItemIndex(); + int nbChannels = getNbChannels(); + qint64 deviceCenterFrequency = settings.m_centerFrequency; + deviceCenterFrequency -= settings.m_transverterMode ? settings.m_transverterDeltaFrequency : 0; + deviceCenterFrequency = deviceCenterFrequency < 0 ? 0 : deviceCenterFrequency; + + if ((m_settings.m_devSampleRate != settings.m_devSampleRate) || (m_settings.m_log2Interp != settings.m_log2Interp) || force) + { + BladeRF2OutputThread *bladeRF2OutputThread = findThread(); + SampleSourceFifo *fifo = 0; + + if (bladeRF2OutputThread) + { + fifo = bladeRF2OutputThread->getFifo(requestedChannel); + bladeRF2OutputThread->setFifo(requestedChannel, 0); + } + + int fifoSize; + + if (settings.m_log2Interp >= 5) + { + fifoSize = DeviceBladeRF2Shared::m_sampleFifoMinSize32; + } + else + { + fifoSize = std::max( + (int) ((settings.m_devSampleRate/(1<setFifo(requestedChannel, &m_sampleSourceFifo); + } + } + + if ((m_settings.m_devSampleRate != settings.m_devSampleRate) || force) + { + forwardChangeOwnDSP = true; + forwardChangeRxBuddies = true; + forwardChangeTxBuddies = true; + + if (dev != 0) + { + unsigned int actualSamplerate; + + int status = bladerf_set_sample_rate(dev, BLADERF_CHANNEL_TX(requestedChannel), + settings.m_devSampleRate, + &actualSamplerate); + + if (status < 0) + { + qCritical("BladeRF2Output::applySettings: could not set sample rate: %d: %s", + settings.m_devSampleRate, bladerf_strerror(status)); + } + else + { + qDebug() << "BladeRF2Output::applySettings: bladerf_set_sample_rate: actual sample rate is " << actualSamplerate; + } + } + } + + if ((m_settings.m_bandwidth != settings.m_bandwidth) || force) + { + forwardChangeTxBuddies = true; + + if (dev != 0) + { + unsigned int actualBandwidth; + int status = bladerf_set_bandwidth(dev, BLADERF_CHANNEL_TX(requestedChannel), settings.m_bandwidth, &actualBandwidth); + + if(status < 0) + { + qCritical("BladeRF2Output::applySettings: could not set bandwidth: %d: %s", + settings.m_bandwidth, bladerf_strerror(status)); + } + else + { + qDebug() << "BladeRF2Output::applySettings: bladerf_set_bandwidth: actual bandwidth is " << actualBandwidth; + } + } + } + + if ((m_settings.m_log2Interp != settings.m_log2Interp) || force) + { + forwardChangeOwnDSP = true; + BladeRF2OutputThread *outputThread = findThread(); + + if (outputThread != 0) + { + outputThread->setLog2Interpolation(requestedChannel, settings.m_log2Interp); + qDebug() << "BladeRF2Output::applySettings: set interpolation to " << (1<push(msg); + } + } + } + } + + if ((m_settings.m_biasTee != settings.m_biasTee) || force) + { + forwardChangeTxBuddies = true; + m_deviceShared.m_dev->setBiasTeeTx(settings.m_biasTee); + } + + if ((m_settings.m_globalGain != settings.m_globalGain) || force) + { + forwardChangeTxBuddies = true; + + if (dev) + { +// qDebug("BladeRF2Output::applySettings: channel: %d gain: %d", requestedChannel, settings.m_globalGain); + int status = bladerf_set_gain(dev, BLADERF_CHANNEL_TX(requestedChannel), settings.m_globalGain); + + if (status < 0) { + qWarning("BladeRF2Output::applySettings: bladerf_set_gain(%d) failed: %s", + settings.m_globalGain, bladerf_strerror(status)); + } else { + qDebug("BladeRF2Output::applySettings: bladerf_set_gain(%d)", settings.m_globalGain); + } + } + } + + if (forwardChangeOwnDSP) + { + int sampleRate = settings.m_devSampleRate/(1<getDeviceEngineInputMessageQueue()->push(notif); + } + + if (forwardChangeRxBuddies) + { + // send to source buddies + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + std::vector::const_iterator itSource = sourceBuddies.begin(); + + for (; itSource != sourceBuddies.end(); ++itSource) + { + DeviceBladeRF2Shared::MsgReportBuddyChange *report = DeviceBladeRF2Shared::MsgReportBuddyChange::create( + settings.m_centerFrequency, + settings.m_LOppmTenths, + 2, + settings.m_devSampleRate, // need to forward actual rate to the Rx side + false); + (*itSource)->getSampleSourceInputMessageQueue()->push(report); + } + } + + if (forwardChangeTxBuddies) + { + // send to sink buddies + const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies(); + std::vector::const_iterator itSink = sinkBuddies.begin(); + + for (; itSink != sinkBuddies.end(); ++itSink) + { + DeviceBladeRF2Shared::MsgReportBuddyChange *report = DeviceBladeRF2Shared::MsgReportBuddyChange::create( + settings.m_centerFrequency, + settings.m_LOppmTenths, + 2, + settings.m_devSampleRate, + false); + (*itSink)->getSampleSinkInputMessageQueue()->push(report); + } + } + + m_settings = settings; + + qDebug() << "BladeRF2Output::applySettings: " + << " m_transverterMode: " << m_settings.m_transverterMode + << " m_transverterDeltaFrequency: " << m_settings.m_transverterDeltaFrequency + << " deviceCenterFrequency: " << deviceCenterFrequency + << " m_centerFrequency: " << m_settings.m_centerFrequency << " Hz" + << " m_LOppmTenths: " << m_settings.m_LOppmTenths + << " m_bandwidth: " << m_settings.m_bandwidth + << " m_log2Interp: " << m_settings.m_log2Interp + << " m_devSampleRate: " << m_settings.m_devSampleRate + << " nbChannels: " << nbChannels + << " m_globalGain: " << m_settings.m_globalGain + << " m_biasTee: " << m_settings.m_biasTee; + + return true; +} + +int BladeRF2Output::getNbChannels() +{ + BladeRF2OutputThread *bladeRF2OutputThread = findThread(); + + if (bladeRF2OutputThread) { + return bladeRF2OutputThread->getNbChannels(); + } else { + return 0; + } +} + +int BladeRF2Output::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setBladeRf2OutputSettings(new SWGSDRangel::SWGBladeRF2OutputSettings()); + response.getBladeRf2OutputSettings()->init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int BladeRF2Output::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage __attribute__((unused))) +{ + BladeRF2OutputSettings settings = m_settings; + + if (deviceSettingsKeys.contains("centerFrequency")) { + settings.m_centerFrequency = response.getBladeRf2OutputSettings()->getCenterFrequency(); + } + if (deviceSettingsKeys.contains("LOppmTenths")) { + settings.m_LOppmTenths = response.getBladeRf2OutputSettings()->getLOppmTenths(); + } + if (deviceSettingsKeys.contains("devSampleRate")) { + settings.m_devSampleRate = response.getBladeRf2OutputSettings()->getDevSampleRate(); + } + if (deviceSettingsKeys.contains("bandwidth")) { + settings.m_bandwidth = response.getBladeRf2OutputSettings()->getBandwidth(); + } + if (deviceSettingsKeys.contains("log2Interp")) { + settings.m_log2Interp = response.getBladeRf2OutputSettings()->getLog2Interp(); + } + if (deviceSettingsKeys.contains("biasTee")) { + settings.m_biasTee = response.getBladeRf2OutputSettings()->getBiasTee() != 0; + } + if (deviceSettingsKeys.contains("globalGain")) { + settings.m_globalGain = response.getBladeRf2OutputSettings()->getGlobalGain(); + } + if (deviceSettingsKeys.contains("transverterDeltaFrequency")) { + settings.m_transverterDeltaFrequency = response.getBladeRf2OutputSettings()->getTransverterDeltaFrequency(); + } + if (deviceSettingsKeys.contains("transverterMode")) { + settings.m_transverterMode = response.getBladeRf2OutputSettings()->getTransverterMode() != 0; + } + + MsgConfigureBladeRF2 *msg = MsgConfigureBladeRF2::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureBladeRF2 *msgToGUI = MsgConfigureBladeRF2::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +int BladeRF2Output::webapiReportGet(SWGSDRangel::SWGDeviceReport& response, QString& errorMessage __attribute__((unused))) +{ + response.setBladeRf2OutputReport(new SWGSDRangel::SWGBladeRF2OutputReport()); + response.getBladeRf2OutputReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} + +void BladeRF2Output::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const BladeRF2OutputSettings& settings) +{ + response.getBladeRf2OutputSettings()->setCenterFrequency(settings.m_centerFrequency); + response.getBladeRf2OutputSettings()->setLOppmTenths(settings.m_LOppmTenths); + response.getBladeRf2OutputSettings()->setDevSampleRate(settings.m_devSampleRate); + response.getBladeRf2OutputSettings()->setBandwidth(settings.m_bandwidth); + response.getBladeRf2OutputSettings()->setLog2Interp(settings.m_log2Interp); + response.getBladeRf2OutputSettings()->setBiasTee(settings.m_biasTee ? 1 : 0); + response.getBladeRf2OutputSettings()->setGlobalGain(settings.m_globalGain); + response.getBladeRf2OutputSettings()->setTransverterDeltaFrequency(settings.m_transverterDeltaFrequency); + response.getBladeRf2OutputSettings()->setTransverterMode(settings.m_transverterMode ? 1 : 0); +} + +void BladeRF2Output::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + DeviceBladeRF2 *device = m_deviceShared.m_dev; + + if (device) + { + int min, max, step; + uint64_t f_min, f_max; + + device->getBandwidthRangeTx(min, max, step); + + response.getBladeRf2OutputReport()->setBandwidthRange(new SWGSDRangel::SWGRange); + response.getBladeRf2OutputReport()->getBandwidthRange()->setMin(min); + response.getBladeRf2OutputReport()->getBandwidthRange()->setMax(max); + response.getBladeRf2OutputReport()->getBandwidthRange()->setStep(step); + + device->getFrequencyRangeTx(f_min, f_max, step); + + response.getBladeRf2OutputReport()->setFrequencyRange(new SWGSDRangel::SWGFrequencyRange); + response.getBladeRf2OutputReport()->getFrequencyRange()->setMin(f_min); + response.getBladeRf2OutputReport()->getFrequencyRange()->setMax(f_max); + response.getBladeRf2OutputReport()->getFrequencyRange()->setStep(step); + + device->getGlobalGainRangeTx(min, max, step); + + response.getBladeRf2OutputReport()->setGlobalGainRange(new SWGSDRangel::SWGRange); + response.getBladeRf2OutputReport()->getGlobalGainRange()->setMin(min); + response.getBladeRf2OutputReport()->getGlobalGainRange()->setMax(max); + response.getBladeRf2OutputReport()->getGlobalGainRange()->setStep(step); + + device->getSampleRateRangeTx(min, max, step); + + response.getBladeRf2OutputReport()->setSampleRateRange(new SWGSDRangel::SWGRange); + response.getBladeRf2OutputReport()->getSampleRateRange()->setMin(min); + response.getBladeRf2OutputReport()->getSampleRateRange()->setMax(max); + response.getBladeRf2OutputReport()->getSampleRateRange()->setStep(step); + } +} + +int BladeRF2Output::webapiRunGet( + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage __attribute__((unused))) +{ + m_deviceAPI->getDeviceEngineStateStr(*response.getState()); + return 200; +} + +int BladeRF2Output::webapiRun( + bool run, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage __attribute__((unused))) +{ + m_deviceAPI->getDeviceEngineStateStr(*response.getState()); + MsgStartStop *message = MsgStartStop::create(run); + m_inputMessageQueue.push(message); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgStartStop *msgToGUI = MsgStartStop::create(run); + m_guiMessageQueue->push(msgToGUI); + } + + return 200; +} + diff --git a/plugins/samplesink/bladerf2output/bladerf2output.h b/plugins/samplesink/bladerf2output/bladerf2output.h new file mode 100644 index 000000000..9b501aeaa --- /dev/null +++ b/plugins/samplesink/bladerf2output/bladerf2output.h @@ -0,0 +1,170 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUT_H_ +#define PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUT_H_ + +#include +#include + +#include "dsp/devicesamplesink.h" +#include "bladerf2/devicebladerf2shared.h" +#include "bladerf2outputsettings.h" + +class DeviceSinkAPI; +class BladeRF2OutputThread; +struct bladerf_gain_modes; + +class BladeRF2Output : public DeviceSampleSink { +public: + class MsgConfigureBladeRF2 : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const BladeRF2OutputSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureBladeRF2* create(const BladeRF2OutputSettings& settings, bool force) + { + return new MsgConfigureBladeRF2(settings, force); + } + + private: + BladeRF2OutputSettings m_settings; + bool m_force; + + MsgConfigureBladeRF2(const BladeRF2OutputSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgStartStop : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getStartStop() const { return m_startStop; } + + static MsgStartStop* create(bool startStop) { + return new MsgStartStop(startStop); + } + + protected: + bool m_startStop; + + MsgStartStop(bool startStop) : + Message(), + m_startStop(startStop) + { } + }; + + class MsgReportGainRange : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getMin() const { return m_min; } + int getMax() const { return m_max; } + int getStep() const { return m_step; } + + static MsgReportGainRange* create(int min, int max, int step) { + return new MsgReportGainRange(min, max, step); + } + + protected: + int m_min; + int m_max; + int m_step; + + MsgReportGainRange(int min, int max, int step) : + Message(), + m_min(min), + m_max(max), + m_step(step) + {} + }; + + BladeRF2Output(DeviceSinkAPI *deviceAPI); + virtual ~BladeRF2Output(); + virtual void destroy(); + + virtual void init(); + virtual bool start(); + virtual void stop(); + BladeRF2OutputThread *getThread() { return m_thread; } + void setThread(BladeRF2OutputThread *thread) { m_thread = thread; } + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual void setMessageQueueToGUI(MessageQueue *queue) { m_guiMessageQueue = queue; } + virtual const QString& getDeviceDescription() const; + virtual int getSampleRate() const; + virtual quint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + + void getFrequencyRange(uint64_t& min, uint64_t& max, int& step); + void getSampleRateRange(int& min, int& max, int& step); + void getBandwidthRange(int& min, int& max, int& step); + void getGlobalGainRange(int& min, int& max, int& step); + + virtual bool handleMessage(const Message& message); + + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + + virtual int webapiRunGet( + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage); + + virtual int webapiRun( + bool run, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage); + +private: + bool openDevice(); + void closeDevice(); + BladeRF2OutputThread *findThread(); + void moveThreadToBuddy(); + bool applySettings(const BladeRF2OutputSettings& settings, bool force); + int getNbChannels(); + bool setDeviceCenterFrequency(struct bladerf *dev, int requestedChannel, quint64 freq_hz, int loPpmTenths); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const BladeRF2OutputSettings& settings); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); + + DeviceSinkAPI *m_deviceAPI; + QMutex m_mutex; + BladeRF2OutputSettings m_settings; + struct bladerf* m_dev; + BladeRF2OutputThread* m_thread; + QString m_deviceDescription; + DeviceBladeRF2Shared m_deviceShared; + bool m_running; +}; + +#endif /* PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUT_H_ */ diff --git a/plugins/samplesink/bladerfoutput/bladerfoutput.pro b/plugins/samplesink/bladerf2output/bladerf2output.pro similarity index 59% rename from plugins/samplesink/bladerfoutput/bladerfoutput.pro rename to plugins/samplesink/bladerf2output/bladerf2output.pro index ba62f7337..8844d2926 100644 --- a/plugins/samplesink/bladerfoutput/bladerfoutput.pro +++ b/plugins/samplesink/bladerf2output/bladerf2output.pro @@ -9,7 +9,7 @@ CONFIG += plugin QT += core gui widgets multimedia opengl -TARGET = outputbladerf +TARGET = outputbladerf2 DEFINES += USE_SSE2=1 QMAKE_CXXFLAGS += -msse2 @@ -17,36 +17,37 @@ DEFINES += USE_SSE4_1=1 QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 -CONFIG(MINGW32):LIBBLADERFSRC = "D:\softs\bladeRF\host\libraries\libbladeRF\include" -CONFIG(MINGW64):LIBBLADERFSRC = "D:\softs\bladeRF\host\libraries\libbladeRF\include" +CONFIG(MINGW32):LIBBLADERF = "C:\Programs\bladeRF" +CONFIG(MINGW64):LIBBLADERF = "C:\Programs\bladeRF" INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client INCLUDEPATH += ../../../devices -INCLUDEPATH += $$LIBBLADERFSRC +INCLUDEPATH += $$LIBBLADERF/include CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug -SOURCES += bladerfoutputgui.cpp\ - bladerfoutput.cpp\ - bladerfoutputplugin.cpp\ - bladerfoutputsettings.cpp\ - bladerfoutputthread.cpp +SOURCES += bladerf2outputgui.cpp\ + bladerf2output.cpp\ + bladerf2outputplugin.cpp\ + bladerf2outputsettings.cpp\ + bladerf2outputthread.cpp -HEADERS += bladerfoutputgui.h\ - bladerfoutput.h\ - bladerfoutputplugin.h\ - bladerfoutputsettings.h\ - bladerfoutputthread.h +HEADERS += bladerf2outputgui.h\ + bladerf2output.h\ + bladerf2outputplugin.h\ + bladerf2outputsettings.h\ + bladerf2outputthread.h -FORMS += bladerfoutputgui.ui +FORMS += bladerf2outputgui.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui LIBS += -L../../../swagger/$${build_subdir} -lswagger -LIBS += -L../../../libbladerf/$${build_subdir} -llibbladerf +LIBS += -L$$LIBBLADERF/lib -lbladeRF LIBS += -L../../../devices/$${build_subdir} -ldevices RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/plugins/samplesink/bladerf2output/bladerf2outputgui.cpp b/plugins/samplesink/bladerf2output/bladerf2outputgui.cpp new file mode 100644 index 000000000..283a68cb0 --- /dev/null +++ b/plugins/samplesink/bladerf2output/bladerf2outputgui.cpp @@ -0,0 +1,370 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include + +#include "ui_bladerf2outputgui.h" +#include "gui/colormapper.h" +#include "gui/glspectrum.h" +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "device/devicesinkapi.h" +#include "device/deviceuiset.h" + +#include "bladerf2outputgui.h" + +BladeRF2OutputGui::BladeRF2OutputGui(DeviceUISet *deviceUISet, QWidget* parent) : + QWidget(parent), + ui(new Ui::BladeRF2OutputGui), + m_deviceUISet(deviceUISet), + m_doApplySettings(true), + m_forceSettings(true), + m_settings(), + m_sampleRate(0), + m_lastEngineState(DSPDeviceSinkEngine::StNotStarted) +{ + m_sampleSink = (BladeRF2Output*) m_deviceUISet->m_deviceSinkAPI->getSampleSink(); + int max, min, step; + uint64_t f_min, f_max; + + ui->setupUi(this); + + m_sampleSink->getFrequencyRange(f_min, f_max, step); + qDebug("BladeRF2OutputGui::BladeRF2OutputGui: getFrequencyRange: [%lu,%lu] step: %d", f_min, f_max, step); + ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); + ui->centerFrequency->setValueRange(7, f_min/1000, f_max/1000); + + m_sampleSink->getSampleRateRange(min, max, step); + qDebug("BladeRF2OutputGui::BladeRF2OutputGui: getSampleRateRange: [%d,%d] step: %d", min, max, step); + ui->sampleRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow)); + ui->sampleRate->setValueRange(8, min, max); + + m_sampleSink->getBandwidthRange(min, max, step); + qDebug("BladeRF2OutputGui::BladeRF2OutputGui: getBandwidthRange: [%d,%d] step: %d", min, max, step); + ui->bandwidth->setColorMapper(ColorMapper(ColorMapper::GrayYellow)); + ui->bandwidth->setValueRange(5, min/1000, max/1000); + + m_sampleSink->getGlobalGainRange(min, max, step); + qDebug("BladeRF2OutputGui::BladeRF2OutputGui: getGlobalGainRange: [%d,%d] step: %d", min, max, step); + ui->gain->setMinimum((min-max)/1000); + ui->gain->setMaximum(0); + ui->gain->setPageStep(1); + ui->gain->setSingleStep(1); + + connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); + connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); + m_statusTimer.start(500); + + displaySettings(); + + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSink->setMessageQueueToGUI(&m_inputMessageQueue); +} + +BladeRF2OutputGui::~BladeRF2OutputGui() +{ + delete ui; +} + +void BladeRF2OutputGui::destroy() +{ + delete this; +} + +void BladeRF2OutputGui::setName(const QString& name) +{ + setObjectName(name); +} + +QString BladeRF2OutputGui::getName() const +{ + return objectName(); +} + +void BladeRF2OutputGui::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + sendSettings(); +} + +qint64 BladeRF2OutputGui::getCenterFrequency() const +{ + return m_settings.m_centerFrequency; +} + +void BladeRF2OutputGui::setCenterFrequency(qint64 centerFrequency) +{ + m_settings.m_centerFrequency = centerFrequency; + displaySettings(); + sendSettings(); +} + +QByteArray BladeRF2OutputGui::serialize() const +{ + return m_settings.serialize(); +} + +bool BladeRF2OutputGui::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) { + displaySettings(); + m_forceSettings = true; + sendSettings(); + return true; + } else { + resetToDefaults(); + return false; + } +} + +void BladeRF2OutputGui::updateFrequencyLimits() +{ + // values in kHz + uint64_t f_min, f_max; + int step; + qint64 deltaFrequency = m_settings.m_transverterMode ? m_settings.m_transverterDeltaFrequency/1000 : 0; + m_sampleSink->getFrequencyRange(f_min, f_max, step); + qint64 minLimit = f_min/1000 + deltaFrequency; + qint64 maxLimit = f_max/1000 + deltaFrequency; + + minLimit = minLimit < 0 ? 0 : minLimit > 9999999 ? 9999999 : minLimit; + maxLimit = maxLimit < 0 ? 0 : maxLimit > 9999999 ? 9999999 : maxLimit; + + qDebug("BladeRF2OutputGui::updateFrequencyLimits: delta: %lld min: %lld max: %lld", deltaFrequency, minLimit, maxLimit); + + ui->centerFrequency->setValueRange(7, minLimit, maxLimit); +} + +void BladeRF2OutputGui::setCenterFrequencySetting(uint64_t kHzValue) +{ + int64_t centerFrequency = kHzValue*1000; + + m_settings.m_centerFrequency = centerFrequency < 0 ? 0 : (uint64_t) centerFrequency; + ui->centerFrequency->setToolTip(QString("Main center frequency in kHz (LO: %1 kHz)").arg(centerFrequency/1000)); +} + +bool BladeRF2OutputGui::handleMessage(const Message& message) +{ + if (BladeRF2Output::MsgConfigureBladeRF2::match(message)) + { + const BladeRF2Output::MsgConfigureBladeRF2& cfg = (BladeRF2Output::MsgConfigureBladeRF2&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + int min, max, step; + m_sampleSink->getGlobalGainRange(min, max, step); + ui->gain->setMinimum((min-max)/1000); + ui->gain->setMaximum(0); + ui->gain->setPageStep(1); + ui->gain->setSingleStep(1); + displaySettings(); + blockApplySettings(false); + + return true; + } + else if (BladeRF2Output::MsgReportGainRange::match(message)) + { + const BladeRF2Output::MsgReportGainRange& cfg = (BladeRF2Output::MsgReportGainRange&) message; + ui->gain->setMinimum((cfg.getMin()-cfg.getMax())/1000); + ui->gain->setMaximum(0); + ui->gain->setSingleStep(1); + ui->gain->setPageStep(1); + + return true; + } + else if (BladeRF2Output::MsgStartStop::match(message)) + { + BladeRF2Output::MsgStartStop& notif = (BladeRF2Output::MsgStartStop&) message; + blockApplySettings(true); + ui->startStop->setChecked(notif.getStartStop()); + blockApplySettings(false); + + return true; + } + else + { + return false; + } +} + +void BladeRF2OutputGui::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + qDebug("BladeRF2OutputGui::handleInputMessages: message: %s", message->getIdentifier()); + + if (DSPSignalNotification::match(*message)) + { + DSPSignalNotification* notif = (DSPSignalNotification*) message; + m_sampleRate = notif->getSampleRate(); + m_deviceCenterFrequency = notif->getCenterFrequency(); + qDebug("BladeRF2OutputGui::handleInputMessages: DSPSignalNotification: SampleRate:%d, CenterFrequency:%llu", notif->getSampleRate(), notif->getCenterFrequency()); + updateSampleRateAndFrequency(); + + delete message; + } + else + { + if (handleMessage(*message)) + { + delete message; + } + } + } +} + +void BladeRF2OutputGui::updateSampleRateAndFrequency() +{ + m_deviceUISet->getSpectrum()->setSampleRate(m_sampleRate); + m_deviceUISet->getSpectrum()->setCenterFrequency(m_deviceCenterFrequency); + ui->deviceRateLabel->setText(tr("%1k").arg(QString::number(m_sampleRate / 1000.0f, 'g', 5))); +} + +void BladeRF2OutputGui::displaySettings() +{ + blockApplySettings(true); + + ui->centerFrequency->setValue(m_settings.m_centerFrequency / 1000); + ui->LOppm->setValue(m_settings.m_LOppmTenths); + ui->LOppmText->setText(QString("%1").arg(QString::number(m_settings.m_LOppmTenths/10.0, 'f', 1))); + ui->sampleRate->setValue(m_settings.m_devSampleRate); + ui->bandwidth->setValue(m_settings.m_bandwidth / 1000); + + ui->interp->setCurrentIndex(m_settings.m_log2Interp); + + ui->gainText->setText(tr("%1 dB").arg(m_settings.m_globalGain)); + ui->gain->setValue(m_settings.m_globalGain); + + blockApplySettings(false); +} + +void BladeRF2OutputGui::sendSettings() +{ + if(!m_updateTimer.isActive()) + m_updateTimer.start(100); +} + +void BladeRF2OutputGui::on_centerFrequency_changed(quint64 value) +{ + m_settings.m_centerFrequency = value * 1000; + sendSettings(); +} + +void BladeRF2OutputGui::on_LOppm_valueChanged(int value) +{ + ui->LOppmText->setText(QString("%1").arg(QString::number(value/10.0, 'f', 1))); + m_settings.m_LOppmTenths = value; + sendSettings(); +} + +void BladeRF2OutputGui::on_sampleRate_changed(quint64 value) +{ + m_settings.m_devSampleRate = value; + sendSettings(); +} + +void BladeRF2OutputGui::on_biasTee_toggled(bool checked) +{ + m_settings.m_biasTee = checked; + sendSettings(); +} + +void BladeRF2OutputGui::on_bandwidth_changed(quint64 value) +{ + m_settings.m_bandwidth = value * 1000; + sendSettings(); +} + +void BladeRF2OutputGui::on_interp_currentIndexChanged(int index) +{ + if ((index <0) || (index > 6)) + return; + m_settings.m_log2Interp = index; + sendSettings(); +} + +void BladeRF2OutputGui::on_gain_valueChanged(int value) +{ + ui->gainText->setText(tr("%1 dB").arg(value)); + m_settings.m_globalGain = value; + sendSettings(); +} + +void BladeRF2OutputGui::on_transverter_clicked() +{ + m_settings.m_transverterMode = ui->transverter->getDeltaFrequencyAcive(); + m_settings.m_transverterDeltaFrequency = ui->transverter->getDeltaFrequency(); + qDebug("LimeSDRInputGUI::on_transverter_clicked: %lld Hz %s", m_settings.m_transverterDeltaFrequency, m_settings.m_transverterMode ? "on" : "off"); + updateFrequencyLimits(); + setCenterFrequencySetting(ui->centerFrequency->getValueNew()); + sendSettings(); +} + +void BladeRF2OutputGui::on_startStop_toggled(bool checked) +{ + if (m_doApplySettings) + { + BladeRF2Output::MsgStartStop *message = BladeRF2Output::MsgStartStop::create(checked); + m_sampleSink->getInputMessageQueue()->push(message); + } +} + +void BladeRF2OutputGui::updateHardware() +{ + if (m_doApplySettings) + { + qDebug() << "BladeRF2OutputGui::updateHardware"; + BladeRF2Output::MsgConfigureBladeRF2* message = BladeRF2Output::MsgConfigureBladeRF2::create(m_settings, m_forceSettings); + m_sampleSink->getInputMessageQueue()->push(message); + m_forceSettings = false; + m_updateTimer.stop(); + } +} + +void BladeRF2OutputGui::updateStatus() +{ + int state = m_deviceUISet->m_deviceSinkAPI->state(); + + if(m_lastEngineState != state) + { + switch(state) + { + case DSPDeviceSinkEngine::StNotStarted: + ui->startStop->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + break; + case DSPDeviceSinkEngine::StIdle: + ui->startStop->setStyleSheet("QToolButton { background-color : blue; }"); + break; + case DSPDeviceSinkEngine::StRunning: + ui->startStop->setStyleSheet("QToolButton { background-color : green; }"); + break; + case DSPDeviceSinkEngine::StError: + ui->startStop->setStyleSheet("QToolButton { background-color : red; }"); + QMessageBox::information(this, tr("Message"), m_deviceUISet->m_deviceSinkAPI->errorMessage()); + break; + default: + break; + } + + m_lastEngineState = state; + } +} + diff --git a/plugins/samplesource/airspyhfi/airspyhfigui.h b/plugins/samplesink/bladerf2output/bladerf2outputgui.h similarity index 51% rename from plugins/samplesource/airspyhfi/airspyhfigui.h rename to plugins/samplesink/bladerf2output/bladerf2outputgui.h index 537714667..7c3b9be78 100644 --- a/plugins/samplesource/airspyhfi/airspyhfigui.h +++ b/plugins/samplesink/bladerf2output/bladerf2outputgui.h @@ -14,82 +14,80 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef INCLUDE_AIRSPYHFIGUI_H -#define INCLUDE_AIRSPYHFIGUI_H +#ifndef PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUTGUI_H_ +#define PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUTGUI_H_ -#include #include #include +#include "plugin/plugininstancegui.h" #include "util/messagequeue.h" -#include "airspyhfiinput.h" +#include "bladerf2output.h" +class DeviceSampleSink; class DeviceUISet; namespace Ui { - class AirspyHFIGui; - class AirspyHFISampleRates; + class BladeRF2OutputGui; } -class AirspyHFIGui : public QWidget, public PluginInstanceGUI { - Q_OBJECT +class BladeRF2OutputGui : public QWidget, public PluginInstanceGUI { + Q_OBJECT public: - explicit AirspyHFIGui(DeviceUISet *deviceUISet, QWidget* parent = 0); - virtual ~AirspyHFIGui(); - virtual void destroy(); + explicit BladeRF2OutputGui(DeviceUISet *deviceUISet, QWidget* parent = 0); + virtual ~BladeRF2OutputGui(); + virtual void destroy(); - void setName(const QString& name); - QString getName() const; + void setName(const QString& name); + QString getName() const; - void resetToDefaults(); - virtual qint64 getCenterFrequency() const; - virtual void setCenterFrequency(qint64 centerFrequency); - QByteArray serialize() const; - bool deserialize(const QByteArray& data); - virtual MessageQueue* getInputMessageQueue() { return &m_inputMessageQueue; } - virtual bool handleMessage(const Message& message); - uint32_t getDevSampleRate(unsigned int index); - int getDevSampleRateIndex(uint32_t sampleRate); + void resetToDefaults(); + virtual qint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + virtual bool handleMessage(const Message& message); private: - Ui::AirspyHFIGui* ui; + Ui::BladeRF2OutputGui* ui; - DeviceUISet* m_deviceUISet; - bool m_doApplySettings; - bool m_forceSettings; - AirspyHFISettings m_settings; - QTimer m_updateTimer; - QTimer m_statusTimer; - std::vector m_rates; - DeviceSampleSource* m_sampleSource; + DeviceUISet* m_deviceUISet; + bool m_doApplySettings; + bool m_forceSettings; + BladeRF2OutputSettings m_settings; + QTimer m_updateTimer; + QTimer m_statusTimer; + BladeRF2Output* m_sampleSink; int m_sampleRate; quint64 m_deviceCenterFrequency; //!< Center frequency in device int m_lastEngineState; MessageQueue m_inputMessageQueue; void blockApplySettings(bool block) { m_doApplySettings = !block; } - void displaySettings(); - void displaySampleRates(); - void sendSettings(); + void displaySettings(); + void sendSettings(); void updateSampleRateAndFrequency(); void updateFrequencyLimits(); + void setCenterFrequencySetting(uint64_t kHzValue); private slots: - void on_centerFrequency_changed(quint64 value); + void handleInputMessages(); + void on_centerFrequency_changed(quint64 value); void on_LOppm_valueChanged(int value); - void on_resetLOppm_clicked(); - void on_autoCorr_currentIndexChanged(int index); - void on_sampleRate_currentIndexChanged(int index); - void on_decim_currentIndexChanged(int index); - void on_startStop_toggled(bool checked); - void on_record_toggled(bool checked); + void on_biasTee_toggled(bool checked); + void on_sampleRate_changed(quint64 value); + void on_bandwidth_changed(quint64 value); + void on_interp_currentIndexChanged(int index); + void on_gain_valueChanged(int value); + void on_startStop_toggled(bool checked); void on_transverter_clicked(); - void on_band_currentIndexChanged(int index); - void updateHardware(); + void updateHardware(); void updateStatus(); - void handleInputMessages(); }; -#endif // INCLUDE_AIRSPYHFIGUI_H + + +#endif /* PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUTGUI_H_ */ diff --git a/plugins/samplesink/bladerf2output/bladerf2outputgui.ui b/plugins/samplesink/bladerf2output/bladerf2outputgui.ui new file mode 100644 index 000000000..697d5e909 --- /dev/null +++ b/plugins/samplesink/bladerf2output/bladerf2outputgui.ui @@ -0,0 +1,517 @@ + + + BladeRF2OutputGui + + + + 0 + 0 + 350 + 200 + + + + + 0 + 0 + + + + + 350 + 200 + + + + + Liberation Sans + 9 + + + + BladeRF2 + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + 4 + + + + + + + + + start/stop acquisition + + + + + + + :/play.png + :/stop.png:/play.png + + + + + + + + + + + I/Q sample rate kS/s + + + 00000k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 20 + + + + PointingHandCursor + + + Qt::StrongFocus + + + Tuner center frequency in kHz + + + + + + + kHz + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + + + + LO ppm + + + + + + + Local Oscillator ppm correction + + + -20 + + + 20 + + + 1 + + + Qt::Horizontal + + + + + + + + 26 + 0 + + + + -0.0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Qt::Horizontal + + + + + + + + + kHz + + + + + + + + 0 + 0 + + + + BW + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 12 + + + + PointingHandCursor + + + RF bandwidth + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 24 + 24 + + + + Transverter frequency translation dialog + + + X + + + + + + + + + 2 + + + 2 + + + + + + 0 + 0 + + + + SR + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 12 + + + + PointingHandCursor + + + Device sample rate + + + + + + + S/s + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Int + + + + + + + + 50 + 16777215 + + + + Decimation factor + + + 0 + + + + 1 + + + + + 2 + + + + + 4 + + + + + 8 + + + + + 16 + + + + + 32 + + + + + + + + + + 3 + + + + + Gain value + + + Qt::Horizontal + + + + + + + Gain + + + + + + + + 45 + 0 + + + + 000 dB + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Bias Tee + + + BT + + + + + + + + + Qt::Horizontal + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + ValueDial + QWidget +
gui/valuedial.h
+ 1 +
+ + ButtonSwitch + QToolButton +
gui/buttonswitch.h
+
+ + TransverterButton + QPushButton +
gui/transverterbutton.h
+
+
+ + + + +
diff --git a/plugins/samplesink/bladerf2output/bladerf2outputplugin.cpp b/plugins/samplesink/bladerf2output/bladerf2outputplugin.cpp new file mode 100644 index 000000000..32c222514 --- /dev/null +++ b/plugins/samplesink/bladerf2output/bladerf2outputplugin.cpp @@ -0,0 +1,157 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include "plugin/pluginapi.h" +#include "util/simpleserializer.h" +#include "device/devicesourceapi.h" + +#include "bladerf2outputplugin.h" + +#ifdef SERVER_MODE +#include "bladerf2output.h" +#else +#include "bladerf2outputgui.h" +#endif + +const PluginDescriptor BladeRF2OutputPlugin::m_pluginDescriptor = { + QString("BladeRF2 Output"), + QString("4.3.0"), + QString("(c) Edouard Griffiths, F4EXB"), + QString("https://github.com/f4exb/sdrangel"), + true, + QString("https://github.com/f4exb/sdrangel") +}; + +const QString BladeRF2OutputPlugin::m_hardwareID = "BladeRF2"; +const QString BladeRF2OutputPlugin::m_deviceTypeID = BLADERF2OUTPUT_DEVICE_TYPE_ID; + +BladeRF2OutputPlugin::BladeRF2OutputPlugin(QObject* parent) : + QObject(parent) +{ +} + +const PluginDescriptor& BladeRF2OutputPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void BladeRF2OutputPlugin::initPlugin(PluginAPI* pluginAPI) +{ + pluginAPI->registerSampleSink(m_deviceTypeID, this); +} + +PluginInterface::SamplingDevices BladeRF2OutputPlugin::enumSampleSinks() +{ + SamplingDevices result; + struct bladerf_devinfo *devinfo = 0; + + int count = bladerf_get_device_list(&devinfo); + + if (devinfo) + { + for(int i = 0; i < count; i++) + { + struct bladerf *dev; + + int status = bladerf_open_with_devinfo(&dev, &devinfo[i]); + + if (status == BLADERF_ERR_NODEV) + { + qCritical("Bladerf2OutputPlugin::enumSampleSinks: No device at index %d", i); + continue; + } + else if (status != 0) + { + qCritical("Bladerf2OutputPlugin::enumSampleSinks: Failed to open device at index %d", i); + continue; + } + + const char *boardName = bladerf_get_board_name(dev); + + if (strcmp(boardName, "bladerf2") == 0) + { + unsigned int nbTxChannels = bladerf_get_channel_count(dev, BLADERF_TX); + + for (unsigned int j = 0; j < nbTxChannels; j++) + { + qDebug("Blderf2InputPlugin::enumSampleSinks: device #%d (%s) channel %u", i, devinfo[i].serial, j); + QString displayedName(QString("BladeRF2[%1:%2] %3").arg(devinfo[i].instance).arg(j).arg(devinfo[i].serial)); + result.append(SamplingDevice(displayedName, + m_hardwareID, + m_deviceTypeID, + QString(devinfo[i].serial), + i, + PluginInterface::SamplingDevice::PhysicalDevice, + false, + nbTxChannels, + j)); + } + } + + bladerf_close(dev); + } + + bladerf_free_device_list(devinfo); // Valgrind memcheck + } + + return result; +} + +#ifdef SERVER_MODE +PluginInstanceGUI* BladeRF2OutputPlugin::createSampleSinkPluginInstanceGUI( + const QString& sinkId __attribute__((unused)), + QWidget **widget __attribute__((unused)), + DeviceUISet *deviceUISet __attribute__((unused))) +{ + return 0; +} +#else +PluginInstanceGUI* BladeRF2OutputPlugin::createSampleSinkPluginInstanceGUI( + const QString& sinkId, + QWidget **widget, + DeviceUISet *deviceUISet) +{ + if(sinkId == m_deviceTypeID) + { + BladeRF2OutputGui* gui = new BladeRF2OutputGui(deviceUISet); + *widget = gui; + return gui; + } + else + { + return 0; + } +} +#endif + +DeviceSampleSink* BladeRF2OutputPlugin::createSampleSinkPluginInstanceOutput(const QString& sinkId, DeviceSinkAPI *deviceAPI) +{ + if(sinkId == m_deviceTypeID) + { + BladeRF2Output* output = new BladeRF2Output(deviceAPI); + return output; + } + else + { + return 0; + } +} + + + + diff --git a/plugins/samplesource/airspyhfi/airspyhfithread.h b/plugins/samplesink/bladerf2output/bladerf2outputplugin.h similarity index 52% rename from plugins/samplesource/airspyhfi/airspyhfithread.h rename to plugins/samplesink/bladerf2output/bladerf2outputplugin.h index 8893aa195..c4f943e57 100644 --- a/plugins/samplesource/airspyhfi/airspyhfithread.h +++ b/plugins/samplesink/bladerf2output/bladerf2outputplugin.h @@ -14,54 +14,41 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef INCLUDE_AIRSPYHFITHREAD_H -#define INCLUDE_AIRSPYHFITHREAD_H +#ifndef PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUTPLUGIN_H_ +#define PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUTPLUGIN_H_ -#include -#include -#include -#include +#include +#include "plugin/plugininterface.h" -#include "dsp/samplesinkfifo.h" -#include "dsp/decimators.h" +class PluginAPI; -#define AIRSPYHFI_BLOCKSIZE (1<<17) +#define BLADERF2OUTPUT_DEVICE_TYPE_ID "sdrangel.samplesink.bladerf2output" -class AirspyHFIThread : public QThread { - Q_OBJECT +class BladeRF2OutputPlugin : public QObject, public PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID BLADERF2OUTPUT_DEVICE_TYPE_ID) public: - AirspyHFIThread(airspyhf_device_t* dev, SampleSinkFifo* sampleFifo, QObject* parent = 0); - ~AirspyHFIThread(); + explicit BladeRF2OutputPlugin(QObject* parent = 0); - void startWork(); - void stopWork(); - void setSamplerate(uint32_t samplerate); - void setLog2Decimation(unsigned int log2_decim); + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + virtual SamplingDevices enumSampleSinks(); + + virtual PluginInstanceGUI* createSampleSinkPluginInstanceGUI( + const QString& sinkId, + QWidget **widget, + DeviceUISet *deviceUISet); + + virtual DeviceSampleSink* createSampleSinkPluginInstanceOutput(const QString& sinkId, DeviceSinkAPI *deviceAPI); + + static const QString m_hardwareID; + static const QString m_deviceTypeID; private: - QMutex m_startWaitMutex; - QWaitCondition m_startWaiter; - bool m_running; - - airspyhf_device_t* m_dev; - qint16 m_buf[2*AIRSPYHFI_BLOCKSIZE]; - SampleVector m_convertBuffer; - SampleSinkFifo* m_sampleFifo; - - int m_samplerate; - unsigned int m_log2Decim; - static AirspyHFIThread *m_this; - -#ifdef SDR_RX_SAMPLE_24BIT - Decimators m_decimators; -#else - Decimators m_decimators; -#endif - - void run(); - void callback(const qint16* buf, qint32 len); - static int rx_callback(airspyhf_transfer_t* transfer); + static const PluginDescriptor m_pluginDescriptor; }; -#endif // INCLUDE_AIRSPYHFITHREAD_H +#endif /* PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUTPLUGIN_H_ */ diff --git a/plugins/samplesource/airspyhfi/airspyhfisettings.cpp b/plugins/samplesink/bladerf2output/bladerf2outputsettings.cpp similarity index 56% rename from plugins/samplesource/airspyhfi/airspyhfisettings.cpp rename to plugins/samplesink/bladerf2output/bladerf2outputsettings.cpp index f0cc373d8..2ea428ef5 100644 --- a/plugins/samplesource/airspyhfi/airspyhfisettings.cpp +++ b/plugins/samplesink/bladerf2output/bladerf2outputsettings.cpp @@ -14,78 +14,76 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include "bladerf2outputsettings.h" + #include #include "util/simpleserializer.h" -#include "airspyhfisettings.h" -AirspyHFISettings::AirspyHFISettings() + +BladeRF2OutputSettings::BladeRF2OutputSettings() { - resetToDefaults(); + resetToDefaults(); } -void AirspyHFISettings::resetToDefaults() +void BladeRF2OutputSettings::resetToDefaults() { - m_centerFrequency = 7150*1000; - m_LOppmTenths = 0; - m_devSampleRateIndex = 0; - m_log2Decim = 0; + m_centerFrequency = 435000*1000; + m_LOppmTenths = 0; + m_devSampleRate = 3072000; + m_bandwidth = 1500000; + m_globalGain = -3; + m_biasTee = false; + m_log2Interp = 0; m_transverterMode = false; m_transverterDeltaFrequency = 0; - m_bandIndex = 0; - m_autoCorrOptions = AutoCorrNone; } -QByteArray AirspyHFISettings::serialize() const +QByteArray BladeRF2OutputSettings::serialize() const { - SimpleSerializer s(1); + SimpleSerializer s(1); - s.writeU32(1, m_devSampleRateIndex); - s.writeS32(2, m_LOppmTenths); - s.writeU32(3, m_log2Decim); - s.writeS32(4, (int) m_autoCorrOptions); + s.writeS32(1, m_devSampleRate); + s.writeS32(2, m_bandwidth); + s.writeS32(3, m_LOppmTenths); + s.writeS32(4, m_globalGain); + s.writeBool(5, m_biasTee); + s.writeU32(6, m_log2Interp); s.writeBool(7, m_transverterMode); s.writeS64(8, m_transverterDeltaFrequency); - s.writeU32(9, m_bandIndex); - return s.final(); + return s.final(); } -bool AirspyHFISettings::deserialize(const QByteArray& data) +bool BladeRF2OutputSettings::deserialize(const QByteArray& data) { - SimpleDeserializer d(data); + SimpleDeserializer d(data); - if (!d.isValid()) - { - resetToDefaults(); - return false; - } - - if (d.getVersion() == 1) - { - int intval; - quint32 uintval; - - d.readU32(1, &m_devSampleRateIndex, 0); - d.readS32(2, &m_LOppmTenths, 0); - d.readU32(3, &m_log2Decim, 0); - d.readS32(4, &intval, 0); - - if (intval < 0 || intval > (int) AutoCorrLast) { - m_autoCorrOptions = AutoCorrNone; - } else { - m_autoCorrOptions = (AutoCorrOptions) intval; - } + if (!d.isValid()) + { + resetToDefaults(); + return false; + } + if (d.getVersion() == 1) + { + d.readS32(1, &m_devSampleRate); + d.readS32(2, &m_bandwidth); + d.readS32(3, &m_LOppmTenths); + d.readS32(4, &m_globalGain); + d.readBool(5, &m_biasTee); + d.readU32(6, &m_log2Interp); d.readBool(7, &m_transverterMode, false); d.readS64(8, &m_transverterDeltaFrequency, 0); - d.readU32(9, &uintval, 0); - m_bandIndex = uintval > 1 ? 1 : uintval; - return true; - } - else - { - resetToDefaults(); - return false; - } + return true; + } + else + { + resetToDefaults(); + return false; + } } + + + + diff --git a/plugins/samplesink/bladerf2output/bladerf2outputsettings.h b/plugins/samplesink/bladerf2output/bladerf2outputsettings.h new file mode 100644 index 000000000..b83cc380e --- /dev/null +++ b/plugins/samplesink/bladerf2output/bladerf2outputsettings.h @@ -0,0 +1,41 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUTSETTINGS_H_ +#define PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUTSETTINGS_H_ + +#include + +struct BladeRF2OutputSettings { + quint64 m_centerFrequency; + int m_LOppmTenths; + qint32 m_devSampleRate; + qint32 m_bandwidth; + int m_globalGain; + bool m_biasTee; + quint32 m_log2Interp; + bool m_transverterMode; + qint64 m_transverterDeltaFrequency; + + BladeRF2OutputSettings(); + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); +}; + + + +#endif /* PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUTSETTINGS_H_ */ diff --git a/plugins/samplesink/bladerf2output/bladerf2outputthread.cpp b/plugins/samplesink/bladerf2output/bladerf2outputthread.cpp new file mode 100644 index 000000000..39df9332e --- /dev/null +++ b/plugins/samplesink/bladerf2output/bladerf2outputthread.cpp @@ -0,0 +1,243 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "dsp/samplesourcefifo.h" + +#include "bladerf2outputthread.h" + +BladeRF2OutputThread::BladeRF2OutputThread(struct bladerf* dev, unsigned int nbTxChannels, QObject* parent) : + QThread(parent), + m_running(false), + m_dev(dev), + m_nbChannels(nbTxChannels) +{ + qDebug("BladeRF2OutputThread::BladeRF2OutputThread"); + m_channels = new Channel[nbTxChannels]; + m_buf = new qint16[2*DeviceBladeRF2::blockSize*nbTxChannels]; +} + +BladeRF2OutputThread::~BladeRF2OutputThread() +{ + qDebug("BladeRF2OutputThread::~BladeRF2OutputThread"); + + if (m_running) { + stopWork(); + } + + delete[] m_buf; + delete[] m_channels; +} + +void BladeRF2OutputThread::startWork() +{ + m_startWaitMutex.lock(); + start(); + + while(!m_running) { + m_startWaiter.wait(&m_startWaitMutex, 100); + } + + m_startWaitMutex.unlock(); +} + +void BladeRF2OutputThread::stopWork() +{ + m_running = false; + wait(); +} + +void BladeRF2OutputThread::run() +{ + int res; + + m_running = true; + m_startWaiter.wakeAll(); + + unsigned int nbFifos = getNbFifos(); + + if ((m_nbChannels > 0) && (nbFifos > 0)) + { + int status; + + if (m_nbChannels > 1) { + status = bladerf_sync_config(m_dev, BLADERF_TX_X2, BLADERF_FORMAT_SC16_Q11, 128, 16384, 32, 1500); + } else { + status = bladerf_sync_config(m_dev, BLADERF_TX_X1, BLADERF_FORMAT_SC16_Q11, 64, 8192, 32, 1500); + } + + if (status < 0) + { + qCritical("BladeRF2OutputThread::run: cannot configure streams: %s", bladerf_strerror(status)); + } + else + { + qDebug("BladeRF2OutputThread::run: start running loop"); + + while (m_running) + { + if (m_nbChannels > 1) + { + callbackMO(m_buf, DeviceBladeRF2::blockSize); + res = bladerf_sync_tx(m_dev, m_buf, DeviceBladeRF2::blockSize*m_nbChannels, 0, 1500); + } + else + { + callbackSO(m_buf, DeviceBladeRF2::blockSize); + res = bladerf_sync_tx(m_dev, m_buf, DeviceBladeRF2::blockSize, 0, 1500); + } + + if (res < 0) + { + qCritical("BladeRF2OutputThread::run sync Rx error: %s", bladerf_strerror(res)); + break; + } + } + + qDebug("BladeRF2OutputThread::run: stop running loop"); + } + } + else + { + qWarning("BladeRF2OutputThread::run: no channels or FIFO allocated. Aborting"); + } + + + m_running = false; +} + +unsigned int BladeRF2OutputThread::getNbFifos() +{ + unsigned int fifoCount = 0; + + for (unsigned int i = 0; i < m_nbChannels; i++) + { + if (m_channels[i].m_sampleFifo) { + fifoCount++; + } + } + + return fifoCount; +} + +void BladeRF2OutputThread::setLog2Interpolation(unsigned int channel, unsigned int log2_interp) +{ + if (channel < m_nbChannels) { + m_channels[channel].m_log2Interp = log2_interp; + } +} + +unsigned int BladeRF2OutputThread::getLog2Interpolation(unsigned int channel) const +{ + if (channel < m_nbChannels) { + return m_channels[channel].m_log2Interp; + } else { + return 0; + } +} + +void BladeRF2OutputThread::setFifo(unsigned int channel, SampleSourceFifo *sampleFifo) +{ + if (channel < m_nbChannels) { + m_channels[channel].m_sampleFifo = sampleFifo; + } +} + +SampleSourceFifo *BladeRF2OutputThread::getFifo(unsigned int channel) +{ + if (channel < m_nbChannels) { + return m_channels[channel].m_sampleFifo; + } else { + return 0; + } +} + +void BladeRF2OutputThread::callbackMO(qint16* buf, qint32 samplesPerChannel) +{ + for (unsigned int channel = 0; channel < m_nbChannels; channel++) + { + if (m_channels[channel].m_sampleFifo) { + callbackSO(&buf[2*samplesPerChannel*channel], samplesPerChannel, channel); + } else { + std::fill(&buf[2*samplesPerChannel*channel], &buf[2*samplesPerChannel*channel]+2*samplesPerChannel, 0); // fill with zero samples + } + } + + // TODO: write a set of interpolators that can write interleaved samples in output directly + int status = bladerf_interleave_stream_buffer(BLADERF_TX_X2, BLADERF_FORMAT_SC16_Q11 , samplesPerChannel*m_nbChannels, (void *) buf); + + if (status < 0) + { + qCritical("BladeRF2OutputThread::callbackMI: cannot interleave buffer: %s", bladerf_strerror(status)); + return; + } +} + +// Interpolate according to specified log2 (ex: log2=4 => decim=16). len is a number of samples (not a number of I or Q) +void BladeRF2OutputThread::callbackSO(qint16* buf, qint32 len, unsigned int channel) +{ + if (m_channels[channel].m_sampleFifo) + { + float bal = m_channels[channel].m_sampleFifo->getRWBalance(); + + if (bal < -0.25) { + qDebug("BladeRF2OutputThread::callbackSO: read lags: %f", bal); + } else if (bal > 0.25) { + qDebug("BladeRF2OutputThread::callbackSO: read leads: %f", bal); + } + + SampleVector::iterator beginRead; + m_channels[channel].m_sampleFifo->readAdvance(beginRead, len/(1<. // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUTTHREAD_H_ +#define PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUTTHREAD_H_ + +#include +#include +#include +#include + +#include "bladerf2/devicebladerf2shared.h" +#include "dsp/interpolators.h" + +class SampleSinkFifo; + +class BladeRF2OutputThread : public QThread { + Q_OBJECT + +public: + BladeRF2OutputThread(struct bladerf* dev, unsigned int nbTxChannels, QObject* parent = 0); + ~BladeRF2OutputThread(); + + void startWork(); + void stopWork(); + bool isRunning() const { return m_running; } + unsigned int getNbChannels() const { return m_nbChannels; } + void setLog2Interpolation(unsigned int channel, unsigned int log2_interp); + unsigned int getLog2Interpolation(unsigned int channel) const; + void setFifo(unsigned int channel, SampleSourceFifo *sampleFifo); + SampleSourceFifo *getFifo(unsigned int channel); + +private: + struct Channel + { + SampleSourceFifo* m_sampleFifo; + unsigned int m_log2Interp; + Interpolators m_interpolators; + + Channel() : + m_sampleFifo(0), + m_log2Interp(0) + {} + + ~Channel() + {} + }; + + QMutex m_startWaitMutex; + QWaitCondition m_startWaiter; + bool m_running; + struct bladerf* m_dev; + + Channel *m_channels; //!< Array of channels dynamically allocated for the given number of Tx channels + qint16 *m_buf; //!< Full buffer for SISO or MIMO operation + unsigned int m_nbChannels; + + void run(); + unsigned int getNbFifos(); + void callbackSO(qint16* buf, qint32 len, unsigned int channel = 0); + void callbackMO(qint16* buf, qint32 samplesPerChannel); +}; + + + +#endif /* PLUGINS_SAMPLESINK_BLADERF2OUTPUT_BLADERF2OUTPUTTHREAD_H_ */ diff --git a/plugins/samplesink/bladerf2output/readme.md b/plugins/samplesink/bladerf2output/readme.md new file mode 100644 index 000000000..8719e7163 --- /dev/null +++ b/plugins/samplesink/bladerf2output/readme.md @@ -0,0 +1,108 @@ +

BladeRF 2.0 micro (v2) output plugin

+ +

Introduction

+ +This output sample sink plugin sends its samples to a [BladeRF2 device](https://www.nuand.com/bladerf-2). This is available since v4.2.0. + +

Build

+ +The plugin will be built only if the [BladeRF host library](https://github.com/Nuand/bladeRF) is installed in your system. If you build it from source and install it in a custom location say: `/opt/install/libbladeRF` you will have to add `-DLIBBLADERF_INCLUDE_DIR=/opt/install/libbladeRF/include -DLIBBLADERF_LIBRARIES=/opt/install/libbladeRF/lib/libbladeRF.so` to the cmake command line. + +Note that the libbladeRF v2 (specifically the git tag 2018.08) is used. The FPGA image v0.7.3 should be used accordingly. The FPGA .rbf file should be copied to the folder where the `sdrangel` binary resides. You can download FPGA images from [here](https://www.nuand.com/fpga_images/) + +The BladeRF Host library is also provided by many Linux distributions (check its version) and is built in the SDRangel binary releases. + +

Interface

+ +![BladeRF2 output plugin GUI](../../../doc/img/BladeRF2Output_plugin.png) + +

1: Start/Stop

+ +Device start / stop button. + + - Blue triangle icon: device is ready and can be started + - Red square icon: device is running and can be stopped + - Magenta (or pink) square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again. + +

2: Baseband sample rate

+ +This is the baseband sample rate in kS/s before interpolation (4) to produce the final stream that is sent to the BladeRF device. Thus this is the device sample rate (6) divided by the interpolation factor (4). + +Transmission latency depends essentially in the delay in the sample FIFO. The FIFO size is calculated as follows: + +For interpolation by 32 the size is fixed at 150000 samples, Delay is 150000 / B where B is the baseband sample rate. Below is the delay in seconds vs baseband sample rate in kS/s from 48 to 500 kS/s: + +![BladeRF1 output plugin FIFO delay 32](../../../doc/img/BladeRF1Output_plugin_fifodly_32.png) + +For lower interpolation rates the size is calculated to give a fixed delay of 250 ms or 75000 samples whichever is bigger. Below is the delay in seconds vs baseband sample rate in kS/s from 48 to 400 kS/s. The 250 ms delay is reached at 300 kS/s: + +![BladeRF1 output plugin FIFO delay other](../../../doc/img/BladeRF1Output_plugin_fifodly_other.png) + +

3: Frequency

+ +This is the center frequency of transmission in kHz. The center frequency is the same for all Tx channels. The GUI of the sibling channel if present is adjusted automatically. + +

4: LO ppm correction

+ +Use this slider to adjust LO correction in ppm. It can be varied from -20.0 to 20.0 in 0.1 steps and is applied in software. This applies to the oscillator that controls both the Rx and Tx frequency therefore it is also changed on the related Rx and Tx plugin(s) if they are active. + +

5: Tx filter bandwidth

+ +This is the Tx filter bandwidth in kHz. Minimum and maximum values are adjusted automatically. Normal range is from 200 kHz to 56 MHz. The Tx filter bandwidth is the same for all Tx channels. The GUI of the sibling channel if present is adjusted automatically. + +

6: Transverter mode open dialog

+ +This button opens a dialog to set the transverter mode frequency translation options: + +![SDR Daemon source input stream transverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) + +Note that if you mouse over the button a tooltip appears that displays the translating frequency and if translation is enabled or disabled. When the frequency translation is enabled the button is lit. + +

6.1: Translating frequency

+ +You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. + +The frequency set in the device is the frequency on the main dial (1) minus this frequency. Thus it is positive for up converters and negative for down converters. + +For example with a mixer at 120 MHz for HF operation you would set the value to -120,000,000 Hz so that if the main dial frequency is set to 7,130 kHz the PlutoSDR will be set to 127.130 MHz. + +If you use an up converter to transmit at the 6 cm band narrowband center frequency of 5670 MHz with the PlutoSDR set at 432 MHz you would set the translating frequency to 5760 - 432 = 5328 MHz thus dial +5,328,000,000 Hz. + +For bands even higher in the frequency spectrum the GHz digits are not really significant so you can have them set at 1 GHz. Thus to transmit at the 10368 MHz frequency with 432 MHz for the PlutoSDR you would set the translating frequency to 1368 - 432 = 936 MHz. Note that in this case the frequency of the LO used in the mixer of the transverter is set at 9936 MHz. + +The Hz precision allows a fine tuning of the transverter LO offset + +

6.2: Translating frequency enable/disable

+ +Use this toggle button to activate or deactivate the frequency translation + +

6.3: Confirmation buttons

+ +Use these buttons to confirm ("OK") or dismiss ("Cancel") your changes. + +

7: Device sample rate

+ +This is the BladeRF device DAC sample rate in S/s. + +Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. + +

8: Interpolation factor

+ +The baseband stream is interpolated by this value before being sent to the BladeRF device. Possible values are: + + - **1**: no interpolation + - **2**: multiply baseband stream sample rate by 2 + - **4**: multiply baseband stream sample rate by 4 + - **8**: multiply baseband stream sample rate by 8 + - **16**: multiply baseband stream sample rate by 16 + - **32**: multiply baseband stream sample rate by 32 + +The main samples buffer is based on the baseband sample rate and will introduce ~500ms delay for interpolation by 16 or lower and ~1s for interpolation by 32. + +

9: Gain control

+ +Use this slider to adjust gain in manual mode. The gain varies from -89 to 0 dB in 1 dB steps. Thus this is in fact an attenuator + +

10: Bias tee control

+ +Use this toggle button to activate or de-activate the bias tee. Note that according to BladeRF v2 specs the bias tee is simultanously present on all Tx RF ports. The GUI of the sibling channel if present is adjusted automatically. diff --git a/plugins/samplesink/bladerfoutput/CMakeLists.txt b/plugins/samplesink/bladerfoutput/CMakeLists.txt deleted file mode 100644 index 0f4d99354..000000000 --- a/plugins/samplesink/bladerfoutput/CMakeLists.txt +++ /dev/null @@ -1,80 +0,0 @@ -project(bladerfoutput) - -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") - -set(bladerfoutput_SOURCES - bladerfoutputgui.cpp - bladerfoutput.cpp - bladerfoutputplugin.cpp - bladerfoutputsettings.cpp - bladerfoutputthread.cpp -) - -set(bladerfoutput_HEADERS - bladerfoutputgui.h - bladerfoutput.h - bladerfoutputplugin.h - bladerfoutputsettings.h - bladerfoutputthread.h -) - -set(bladerfoutput_FORMS - bladerfoutputgui.ui -) - -if (BUILD_DEBIAN) -include_directories( - . - ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client - ${CMAKE_SOURCE_DIR}/devices - ${LIBBLADERFLIBSRC}/include - ${LIBBLADERFLIBSRC}/src -) -else (BUILD_DEBIAN) -include_directories( - . - ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client - ${CMAKE_SOURCE_DIR}/devices - ${LIBBLADERF_INCLUDE_DIR} -) -endif (BUILD_DEBIAN) - -#include(${QT_USE_FILE}) -add_definitions(${QT_DEFINITIONS}) -add_definitions(-DQT_PLUGIN) -add_definitions(-DQT_SHARED) - -#qt4_wrap_cpp(bladerfoutput_HEADERS_MOC ${bladerfoutput_HEADERS}) -qt5_wrap_ui(bladerfoutput_FORMS_HEADERS ${bladerfoutput_FORMS}) - -add_library(outputbladerf SHARED - ${bladerfoutput_SOURCES} - ${bladerfoutput_HEADERS_MOC} - ${bladerfoutput_FORMS_HEADERS} -) - -if (BUILD_DEBIAN) -target_link_libraries(outputbladerf - ${QT_LIBRARIES} - bladerf - sdrbase - sdrgui - swagger - bladerfdevice -) -else (BUILD_DEBIAN) -target_link_libraries(outputbladerf - ${QT_LIBRARIES} - ${LIBBLADERF_LIBRARIES} - sdrbase - sdrgui - swagger - bladerfdevice -) -endif (BUILD_DEBIAN) - -qt5_use_modules(outputbladerf Core Widgets) - -install(TARGETS outputbladerf DESTINATION lib/plugins/samplesink) diff --git a/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp b/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp deleted file mode 100644 index 31e626882..000000000 --- a/plugins/samplesink/bladerfoutput/bladerfoutputplugin.cpp +++ /dev/null @@ -1,113 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2015 Edouard Griffiths, F4EXB // -// // -// This program is free software; you can redistribute it and/or modify // -// it under the terms of the GNU General Public License as published by // -// the Free Software Foundation as version 3 of the License, or // -// // -// This program is distributed in the hope that it will be useful, // -// but WITHOUT ANY WARRANTY; without even the implied warranty of // -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // -// GNU General Public License V3 for more details. // -// // -// You should have received a copy of the GNU General Public License // -// along with this program. If not, see . // -/////////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include "plugin/pluginapi.h" -#include "util/simpleserializer.h" -#include - -#include "bladerfoutputplugin.h" -#include "bladerfoutputgui.h" - -const PluginDescriptor BladerfOutputPlugin::m_pluginDescriptor = { - QString("BladeRF Output"), - QString("3.9.0"), - QString("(c) Edouard Griffiths, F4EXB"), - QString("https://github.com/f4exb/sdrangel"), - true, - QString("https://github.com/f4exb/sdrangel") -}; - -const QString BladerfOutputPlugin::m_hardwareID = "BladeRF"; -const QString BladerfOutputPlugin::m_deviceTypeID = BLADERFOUTPUT_DEVICE_TYPE_ID; - -BladerfOutputPlugin::BladerfOutputPlugin(QObject* parent) : - QObject(parent) -{ -} - -const PluginDescriptor& BladerfOutputPlugin::getPluginDescriptor() const -{ - return m_pluginDescriptor; -} - -void BladerfOutputPlugin::initPlugin(PluginAPI* pluginAPI) -{ - pluginAPI->registerSampleSink(m_deviceTypeID, this); -} - -PluginInterface::SamplingDevices BladerfOutputPlugin::enumSampleSinks() -{ - SamplingDevices result; - struct bladerf_devinfo *devinfo = 0; - - int count = bladerf_get_device_list(&devinfo); - - for(int i = 0; i < count; i++) - { - QString displayedName(QString("BladeRF[%1] %2").arg(devinfo[i].instance).arg(devinfo[i].serial)); - - result.append(SamplingDevice(displayedName, - m_hardwareID, - m_deviceTypeID, - QString(devinfo[i].serial), - i, - PluginInterface::SamplingDevice::PhysicalDevice, - false, - 1, - 0)); - } - - if (devinfo) - { - bladerf_free_device_list(devinfo); // Valgrind memcheck - } - - return result; -} - -PluginInstanceGUI* BladerfOutputPlugin::createSampleSinkPluginInstanceGUI( - const QString& sinkId, - QWidget **widget, - DeviceUISet *deviceUISet) -{ - if(sinkId == m_deviceTypeID) - { - BladerfOutputGui* gui = new BladerfOutputGui(deviceUISet); - *widget = gui; - return gui; - } - else - { - return 0; - } -} - -DeviceSampleSink* BladerfOutputPlugin::createSampleSinkPluginInstanceOutput(const QString& sinkId, DeviceSinkAPI *deviceAPI) -{ - if(sinkId == m_deviceTypeID) - { - BladerfOutput* output = new BladerfOutput(deviceAPI); - return output; - } - else - { - return 0; - } - -} diff --git a/plugins/samplesink/filesink/CMakeLists.txt b/plugins/samplesink/filesink/CMakeLists.txt index e8ddcf468..ccedfe7af 100644 --- a/plugins/samplesink/filesink/CMakeLists.txt +++ b/plugins/samplesink/filesink/CMakeLists.txt @@ -47,6 +47,6 @@ target_link_libraries(outputfilesink swagger ) -qt5_use_modules(outputfilesink Core Widgets) +target_link_libraries(outputfilesink Qt5::Core Qt5::Widgets) install(TARGETS outputfilesink DESTINATION lib/plugins/samplesink) diff --git a/plugins/samplesink/filesink/filesink.pro b/plugins/samplesink/filesink/filesink.pro index 18e83f81d..370183c48 100644 --- a/plugins/samplesink/filesink/filesink.pro +++ b/plugins/samplesink/filesink/filesink.pro @@ -18,6 +18,7 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client diff --git a/plugins/samplesink/filesink/filesinkgui.cpp b/plugins/samplesink/filesink/filesinkgui.cpp index c56bb4105..c05d525c0 100644 --- a/plugins/samplesink/filesink/filesinkgui.cpp +++ b/plugins/samplesink/filesink/filesinkgui.cpp @@ -49,7 +49,7 @@ FileSinkGui::FileSinkGui(DeviceUISet *deviceUISet, QWidget* parent) : m_startingTimeStamp(0), m_samplesCount(0), m_tickCount(0), - m_lastEngineState((DSPDeviceSinkEngine::State)-1) + m_lastEngineState(DSPDeviceSinkEngine::StNotStarted) { ui->setupUi(this); @@ -287,7 +287,7 @@ void FileSinkGui::on_startStop_toggled(bool checked) void FileSinkGui::on_showFileDialog_clicked(bool checked __attribute__((unused))) { QString fileName = QFileDialog::getSaveFileName(this, - tr("Save I/Q record file"), ".", tr("SDR I/Q Files (*.sdriq)")); + tr("Save I/Q record file"), ".", tr("SDR I/Q Files (*.sdriq)"), 0, QFileDialog::DontUseNativeDialog); if (fileName != "") { @@ -322,7 +322,7 @@ void FileSinkGui::updateWithStreamTime() QTime t(0, 0, 0, 0); t = t.addSecs(t_sec); t = t.addMSecs(t_msec); - QString s_timems = t.toString("hh:mm:ss.zzz"); + QString s_timems = t.toString("HH:mm:ss.zzz"); ui->relTimeText->setText(s_timems); } diff --git a/plugins/samplesink/filesink/filesinkgui.ui b/plugins/samplesink/filesink/filesinkgui.ui index b500b42d4..415c9e3a9 100644 --- a/plugins/samplesink/filesink/filesinkgui.ui +++ b/plugins/samplesink/filesink/filesinkgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -130,7 +130,7 @@ - DejaVu Sans Mono + Liberation Mono 20 @@ -304,7 +304,7 @@ - DejaVu Sans Mono + Liberation Mono 12 diff --git a/plugins/samplesink/filesink/filesinkoutput.cpp b/plugins/samplesink/filesink/filesinkoutput.cpp index 1e1d302b2..d73747c1a 100644 --- a/plugins/samplesink/filesink/filesinkoutput.cpp +++ b/plugins/samplesink/filesink/filesinkoutput.cpp @@ -68,12 +68,15 @@ void FileSinkOutput::openFileStream() m_ofstream.open(m_fileName.toStdString().c_str(), std::ios::binary); + FileRecord::Header header; int actualSampleRate = m_settings.m_sampleRate * (1<setSamplerate(m_settings.m_sampleRate); m_fileSinkThread->setLog2Interpolation(m_settings.m_log2Interp); m_fileSinkThread->connectTimer(m_masterTimer); @@ -219,13 +216,11 @@ bool FileSinkOutput::handleMessage(const Message& message) if (m_deviceAPI->initGeneration()) { m_deviceAPI->startGeneration(); - DSPEngine::instance()->startAudioInput(); } } else { m_deviceAPI->stopGeneration(); - DSPEngine::instance()->stopAudioInput(); } return true; diff --git a/plugins/samplesink/filesink/filesinkplugin.cpp b/plugins/samplesink/filesink/filesinkplugin.cpp index 1944c6bed..66248021e 100644 --- a/plugins/samplesink/filesink/filesinkplugin.cpp +++ b/plugins/samplesink/filesink/filesinkplugin.cpp @@ -29,7 +29,7 @@ const PluginDescriptor FileSinkPlugin::m_pluginDescriptor = { QString("File sink output"), - QString("3.9.0"), + QString("4.2.1"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesink/filesink/readme.md b/plugins/samplesink/filesink/readme.md index cede1d66b..d65fb0b60 100644 --- a/plugins/samplesink/filesink/readme.md +++ b/plugins/samplesink/filesink/readme.md @@ -26,7 +26,7 @@ Device start / stop button. - Blue triangle icon: device is ready and can be started - Red square icon: device is running and can be stopped - - Magenta (or pink) square icon: an error occured + - Magenta (or pink) square icon: an error occurred

2: File stream sample rate

@@ -52,8 +52,8 @@ The baseband stream is interpolated by this value before being written to file. This is the baseband sample rate before interpolation in S/s. -Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

8: Time counter

-This is the recording time count in HH:MM:SS.SSS \ No newline at end of file +This is the recording time count in HH:MM:SS.SSS diff --git a/plugins/samplesink/hackrfoutput/CMakeLists.txt b/plugins/samplesink/hackrfoutput/CMakeLists.txt index 7a0ea9af9..87bbe5234 100644 --- a/plugins/samplesink/hackrfoutput/CMakeLists.txt +++ b/plugins/samplesink/hackrfoutput/CMakeLists.txt @@ -76,6 +76,6 @@ target_link_libraries(outputhackrf ) endif (BUILD_DEBIAN) -qt5_use_modules(outputhackrf Core Widgets) +target_link_libraries(outputhackrf Qt5::Core Qt5::Widgets) install(TARGETS outputhackrf DESTINATION lib/plugins/samplesink) diff --git a/plugins/samplesink/hackrfoutput/hackrfoutput.cpp b/plugins/samplesink/hackrfoutput/hackrfoutput.cpp index 7e497f8f2..3ae7437b1 100644 --- a/plugins/samplesink/hackrfoutput/hackrfoutput.cpp +++ b/plugins/samplesink/hackrfoutput/hackrfoutput.cpp @@ -116,12 +116,7 @@ bool HackRFOutput::start() if (m_running) stop(); - if((m_hackRFThread = new HackRFOutputThread(m_dev, &m_sampleSourceFifo)) == 0) - { - qCritical("HackRFOutput::start: out of memory"); - stop(); - return false; - } + m_hackRFThread = new HackRFOutputThread(m_dev, &m_sampleSourceFifo); // mutexLocker.unlock(); @@ -262,13 +257,11 @@ bool HackRFOutput::handleMessage(const Message& message) if (m_deviceAPI->initGeneration()) { m_deviceAPI->startGeneration(); - DSPEngine::instance()->startAudioInput(); } } else { m_deviceAPI->stopGeneration(); - DSPEngine::instance()->stopAudioInput(); } return true; diff --git a/plugins/samplesink/hackrfoutput/hackrfoutput.pro b/plugins/samplesink/hackrfoutput/hackrfoutput.pro index c619666ac..3fe253caf 100644 --- a/plugins/samplesink/hackrfoutput/hackrfoutput.pro +++ b/plugins/samplesink/hackrfoutput/hackrfoutput.pro @@ -17,9 +17,10 @@ DEFINES += USE_SSE4_1=1 QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 -CONFIG(MINGW32):LIBHACKRFSRC = "D:\softs\hackrf\host" -CONFIG(MINGW64):LIBHACKRFSRC = "D:\softs\hackrf\host" +CONFIG(MINGW32):LIBHACKRFSRC = "C:\softs\hackrf\host" +CONFIG(MINGW64):LIBHACKRFSRC = "C:\softs\hackrf\host" INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client diff --git a/plugins/samplesink/hackrfoutput/hackrfoutputgui.cpp b/plugins/samplesink/hackrfoutput/hackrfoutputgui.cpp index bee0e306a..153df00d9 100644 --- a/plugins/samplesink/hackrfoutput/hackrfoutputgui.cpp +++ b/plugins/samplesink/hackrfoutput/hackrfoutputgui.cpp @@ -40,7 +40,7 @@ HackRFOutputGui::HackRFOutputGui(DeviceUISet *deviceUISet, QWidget* parent) : m_forceSettings(true), m_settings(), m_deviceSampleSink(0), - m_lastEngineState((DSPDeviceSinkEngine::State)-1), + m_lastEngineState(DSPDeviceSinkEngine::StNotStarted), m_doApplySettings(true) { m_deviceSampleSink = (HackRFOutput*) m_deviceUISet->m_deviceSinkAPI->getSampleSink(); @@ -50,7 +50,7 @@ HackRFOutputGui::HackRFOutputGui(DeviceUISet *deviceUISet, QWidget* parent) : ui->centerFrequency->setValueRange(7, 0U, 7250000U); ui->sampleRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow)); - ui->sampleRate->setValueRange(8, 2400000U, 20000000U); + ui->sampleRate->setValueRange(8, 1000000U, 20000000U); connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); diff --git a/plugins/samplesink/hackrfoutput/hackrfoutputgui.ui b/plugins/samplesink/hackrfoutput/hackrfoutputgui.ui index 1c98afb3c..858dd7c71 100644 --- a/plugins/samplesink/hackrfoutput/hackrfoutputgui.ui +++ b/plugins/samplesink/hackrfoutput/hackrfoutputgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -130,7 +130,7 @@ - DejaVu Sans Mono + Liberation Mono 20 @@ -191,7 +191,7 @@ - Local Oscullator ppm correction + Local Oscillator ppm correction -300 @@ -273,7 +273,7 @@ - RF bandpas filter + RF bandpass filter @@ -320,7 +320,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -400,6 +400,11 @@ 32 + + + 64 + +
diff --git a/plugins/samplesink/hackrfoutput/hackrfoutputplugin.cpp b/plugins/samplesink/hackrfoutput/hackrfoutputplugin.cpp index 75bf0f0aa..6314d7cb5 100644 --- a/plugins/samplesink/hackrfoutput/hackrfoutputplugin.cpp +++ b/plugins/samplesink/hackrfoutput/hackrfoutputplugin.cpp @@ -30,7 +30,7 @@ const PluginDescriptor HackRFOutputPlugin::m_pluginDescriptor = { QString("HackRF Output"), - QString("3.9.0"), + QString("3.14.5"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesink/hackrfoutput/hackrfoutputthread.cpp b/plugins/samplesink/hackrfoutput/hackrfoutputthread.cpp index 12f497976..0223c33b8 100644 --- a/plugins/samplesink/hackrfoutput/hackrfoutputthread.cpp +++ b/plugins/samplesink/hackrfoutput/hackrfoutputthread.cpp @@ -29,6 +29,7 @@ HackRFOutputThread::HackRFOutputThread(hackrf_device* dev, SampleSourceFifo* sam m_sampleFifo(sampleFifo), m_log2Interp(0) { + std::fill(m_buf, m_buf + 2*HACKRF_BLOCKSIZE, 0); } HackRFOutputThread::~HackRFOutputThread() diff --git a/plugins/samplesink/hackrfoutput/readme.md b/plugins/samplesink/hackrfoutput/readme.md index e2754c06d..6db204276 100644 --- a/plugins/samplesink/hackrfoutput/readme.md +++ b/plugins/samplesink/hackrfoutput/readme.md @@ -20,7 +20,7 @@ Device start / stop button. - Blue triangle icon: device is ready and can be started - Red square icon: device is running and can be stopped - - Magenta (or pink) square icon: an error occured. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again. + - Magenta (or pink) square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again. If you have the Rx open in another tab and it is running then it will be stopped automatically before the Tx starts. In a similar manner the Tx will be stopped before the Rx is started from the Rx tab. @@ -52,6 +52,7 @@ The baseband stream is interpolated by this value before being sent to the HackR - **8**: multiply baseband stream sample rate by 8 - **16**: multiply baseband stream sample rate by 16 - **32**: multiply baseband stream sample rate by 32 + - **64**: multiply baseband stream sample rate by 64 The main samples buffer is based on the baseband sample rate and will introduce ~500ms delay for interpolation by 16 or lower and ~1s for interpolation by 32. @@ -74,7 +75,7 @@ According to HackRF documentation the output power when the PA is engaged and th This is the HackRF device DAC sample rate in S/s. -Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

9: Tx filter bandwidth

@@ -82,4 +83,4 @@ This is the Tx filter bandwidth in kHz. Possible values are: 1750, 2500, 3500, 5

10: Tx variable gain amplifier gain

-The Tx VGA gain can be adjusted from 0 dB to 47 dB in 1 dB steps. See (7) for an indication on maximum output power. \ No newline at end of file +The Tx VGA gain can be adjusted from 0 dB to 47 dB in 1 dB steps. See (7) for an indication on maximum output power. diff --git a/plugins/samplesink/limesdroutput/CMakeLists.txt b/plugins/samplesink/limesdroutput/CMakeLists.txt index 8b92052e2..eed28022d 100644 --- a/plugins/samplesink/limesdroutput/CMakeLists.txt +++ b/plugins/samplesink/limesdroutput/CMakeLists.txt @@ -82,6 +82,6 @@ target_link_libraries(outputlimesdr ) endif (BUILD_DEBIAN) -qt5_use_modules(outputlimesdr Core Widgets) +target_link_libraries(outputlimesdr Qt5::Core Qt5::Widgets) install(TARGETS outputlimesdr DESTINATION lib/plugins/samplesink) diff --git a/plugins/samplesink/limesdroutput/limesdroutput.cpp b/plugins/samplesink/limesdroutput/limesdroutput.cpp index 7b2aac50a..a166d488c 100644 --- a/plugins/samplesink/limesdroutput/limesdroutput.cpp +++ b/plugins/samplesink/limesdroutput/limesdroutput.cpp @@ -23,6 +23,8 @@ #include "SWGDeviceSettings.h" #include "SWGLimeSdrOutputSettings.h" #include "SWGDeviceState.h" +#include "SWGDeviceReport.h" +#include "SWGLimeSdrOutputReport.h" #include "device/devicesourceapi.h" #include "device/devicesinkapi.h" @@ -111,9 +113,6 @@ bool LimeSDROutput::openDevice() // check if the requested channel is busy and abort if so (should not happen if device management is working correctly) - char *busyChannels = new char[deviceParams->m_nbTxChannels]; - memset(busyChannels, 0, deviceParams->m_nbTxChannels); - for (unsigned int i = 0; i < m_deviceAPI->getSinkBuddies().size(); i++) { DeviceSinkAPI *buddy = m_deviceAPI->getSinkBuddies()[i]; @@ -122,13 +121,11 @@ bool LimeSDROutput::openDevice() if (buddyShared->m_channel == requestedChannel) { qCritical("LimeSDROutput::openDevice: cannot open busy channel %u", requestedChannel); - delete[] busyChannels; return false; } } m_deviceShared.m_channel = requestedChannel; // acknowledge the requested channel - delete[] busyChannels; } // look for Rx buddies and get reference to common parameters // take the first Rx channel @@ -370,20 +367,12 @@ bool LimeSDROutput::start() return false; } - applySettings(m_settings, true); - // start / stop streaming is done in the thread. - if ((m_limeSDROutputThread = new LimeSDROutputThread(&m_streamId, &m_sampleSourceFifo)) == 0) - { - qCritical("LimeSDROutput::start: cannot create thread"); - stop(); - return false; - } - else - { - qDebug("LimeSDROutput::start: thread created"); - } + m_limeSDROutputThread = new LimeSDROutputThread(&m_streamId, &m_sampleSourceFifo); + qDebug("LimeSDROutput::start: thread created"); + + applySettings(m_settings, true); m_limeSDROutputThread->setLog2Interpolation(m_settings.m_log2SoftInterp); @@ -452,13 +441,13 @@ int LimeSDROutput::getSampleRate() const quint64 LimeSDROutput::getCenterFrequency() const { - return m_settings.m_centerFrequency; + return m_settings.m_centerFrequency + (m_settings.m_ncoEnable ? m_settings.m_ncoFrequency : 0); } void LimeSDROutput::setCenterFrequency(qint64 centerFrequency) { LimeSDROutputSettings settings = m_settings; - settings.m_centerFrequency = centerFrequency; + settings.m_centerFrequency = centerFrequency - (m_settings.m_ncoEnable ? m_settings.m_ncoFrequency : 0); MsgConfigureLimeSDR* message = MsgConfigureLimeSDR::create(settings, false); m_inputMessageQueue.push(message); @@ -475,31 +464,28 @@ std::size_t LimeSDROutput::getChannelIndex() return m_deviceShared.m_channel; } -void LimeSDROutput::getLORange(float& minF, float& maxF, float& stepF) const +void LimeSDROutput::getLORange(float& minF, float& maxF) const { lms_range_t range = m_deviceShared.m_deviceParams->m_loRangeTx; minF = range.min; maxF = range.max; - stepF = range.step; - qDebug("LimeSDROutput::getLORange: min: %f max: %f step: %f", range.min, range.max, range.step); + qDebug("LimeSDROutput::getLORange: min: %f max: %f", range.min, range.max); } -void LimeSDROutput::getSRRange(float& minF, float& maxF, float& stepF) const +void LimeSDROutput::getSRRange(float& minF, float& maxF) const { lms_range_t range = m_deviceShared.m_deviceParams->m_srRangeTx; minF = range.min; maxF = range.max; - stepF = range.step; - qDebug("LimeSDROutput::getSRRange: min: %f max: %f step: %f", range.min, range.max, range.step); + qDebug("LimeSDROutput::getSRRange: min: %f max: %f", range.min, range.max); } -void LimeSDROutput::getLPRange(float& minF, float& maxF, float& stepF) const +void LimeSDROutput::getLPRange(float& minF, float& maxF) const { lms_range_t range = m_deviceShared.m_deviceParams->m_lpfRangeTx; minF = range.min; maxF = range.max; - stepF = range.step; - qDebug("LimeSDROutput::getLPRange: min: %f max: %f step: %f", range.min, range.max, range.step); + qDebug("LimeSDROutput::getLPRange: min: %f max: %f", range.min, range.max); } uint32_t LimeSDROutput::getHWLog2Interp() const @@ -531,13 +517,11 @@ bool LimeSDROutput::handleMessage(const Message& message) if (m_deviceAPI->initGeneration()) { m_deviceAPI->startGeneration(); - DSPEngine::instance()->startAudioInput(); } } else { m_deviceAPI->stopGeneration(); - DSPEngine::instance()->stopAudioInput(); } return true; @@ -606,9 +590,12 @@ bool LimeSDROutput::handleMessage(const Message& message) m_settings.m_extClock = report.getExtClock(); m_settings.m_extClockFreq = report.getExtClockFeq(); - DeviceLimeSDRShared::MsgReportClockSourceChange *reportToGUI = DeviceLimeSDRShared::MsgReportClockSourceChange::create( - m_settings.m_extClock, m_settings.m_extClockFreq); - getMessageQueueToGUI()->push(reportToGUI); + if (getMessageQueueToGUI()) + { + DeviceLimeSDRShared::MsgReportClockSourceChange *reportToGUI = DeviceLimeSDRShared::MsgReportClockSourceChange::create( + m_settings.m_extClock, m_settings.m_extClockFreq); + getMessageQueueToGUI()->push(reportToGUI); + } return true; } @@ -719,6 +706,10 @@ bool LimeSDROutput::applySettings(const LimeSDROutputSettings& settings, bool fo double clockGenFreq = 0.0; // QMutexLocker mutexLocker(&m_mutex); + qint64 deviceCenterFrequency = settings.m_centerFrequency; + deviceCenterFrequency -= settings.m_transverterMode ? settings.m_transverterDeltaFrequency : 0; + deviceCenterFrequency = deviceCenterFrequency < 0 ? 0 : deviceCenterFrequency; + if (LMS_GetClockFreq(m_deviceShared.m_deviceParams->getDevice(), LMS_CLOCK_CGEN, &clockGenFreq) != 0) { qCritical("LimeSDROutput::applySettings: could not get clock gen frequency"); @@ -870,35 +861,35 @@ bool LimeSDROutput::applySettings(const LimeSDROutputSettings& settings, bool fo settings.m_antennaPath)) { doCalibration = true; - qDebug("LimeSDRInput::applySettings: set antenna path to %d", + qDebug("LimeSDROutput::applySettings: set antenna path to %d", (int) settings.m_antennaPath); } else { - qCritical("LimeSDRInput::applySettings: could not set antenna path to %d", + qCritical("LimeSDROutput::applySettings: could not set antenna path to %d", (int) settings.m_antennaPath); } } } - if ((m_settings.m_centerFrequency != settings.m_centerFrequency) || force) + if ((m_settings.m_centerFrequency != settings.m_centerFrequency) + || (m_settings.m_transverterMode != settings.m_transverterMode) + || (m_settings.m_transverterDeltaFrequency != settings.m_transverterDeltaFrequency) + || force) { forwardChangeTxDSP = true; if (m_deviceShared.m_deviceParams->getDevice() != 0 && m_channelAcquired) { - if (LMS_SetLOFrequency(m_deviceShared.m_deviceParams->getDevice(), - LMS_CH_TX, - m_deviceShared.m_channel, // same for both channels anyway but switches antenna port automatically - settings.m_centerFrequency) < 0) + if (LMS_SetClockFreq(m_deviceShared.m_deviceParams->getDevice(), LMS_CLOCK_SXT, deviceCenterFrequency) < 0) { - qCritical("LimeSDROutput::applySettings: could not set frequency to %lu", settings.m_centerFrequency); + qCritical("LimeSDROutput::applySettings: could not set frequency to %lld", deviceCenterFrequency); } else { doCalibration = true; - m_deviceShared.m_centerFrequency = settings.m_centerFrequency; // for buddies - qDebug("LimeSDROutput::applySettings: frequency set to %lu", settings.m_centerFrequency); + m_deviceShared.m_centerFrequency = deviceCenterFrequency; // for buddies + qDebug("LimeSDROutput::applySettings: frequency set to %lld", deviceCenterFrequency); } } } @@ -1084,6 +1075,9 @@ bool LimeSDROutput::applySettings(const LimeSDROutputSettings& settings, bool fo QLocale loc; qDebug().noquote() << "LimeSDROutput::applySettings: center freq: " << m_settings.m_centerFrequency << " Hz" + << " m_transverterMode: " << m_settings.m_transverterMode + << " m_transverterDeltaFrequency: " << m_settings.m_transverterDeltaFrequency + << " deviceCenterFrequency: " << deviceCenterFrequency << " device stream sample rate: " << loc.toString(m_settings.m_devSampleRate) << "S/s" << " sample rate with soft interpolation: " << loc.toString( m_settings.m_devSampleRate/(1<getNcoFrequency(); } + if (deviceSettingsKeys.contains("transverterDeltaFrequency")) { + settings.m_transverterDeltaFrequency = response.getLimeSdrOutputSettings()->getTransverterDeltaFrequency(); + } + if (deviceSettingsKeys.contains("transverterMode")) { + settings.m_transverterMode = response.getLimeSdrOutputSettings()->getTransverterMode() != 0; + } MsgConfigureLimeSDR *msg = MsgConfigureLimeSDR::create(settings, force); m_inputMessageQueue.push(msg); @@ -1177,6 +1177,15 @@ int LimeSDROutput::webapiSettingsPutPatch( return 200; } +int LimeSDROutput::webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setLimeSdrOutputReport(new SWGSDRangel::SWGLimeSdrOutputReport()); + response.getLimeSdrOutputReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} void LimeSDROutput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const LimeSDROutputSettings& settings) { response.getLimeSdrOutputSettings()->setAntennaPath((int) settings.m_antennaPath); @@ -1192,6 +1201,8 @@ void LimeSDROutput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& r response.getLimeSdrOutputSettings()->setLpfFirbw(settings.m_lpfFIRBW); response.getLimeSdrOutputSettings()->setNcoEnable(settings.m_ncoEnable ? 1 : 0); response.getLimeSdrOutputSettings()->setNcoFrequency(settings.m_ncoFrequency); + response.getLimeSdrOutputSettings()->setTransverterDeltaFrequency(settings.m_transverterDeltaFrequency); + response.getLimeSdrOutputSettings()->setTransverterMode(settings.m_transverterMode ? 1 : 0); } int LimeSDROutput::webapiRunGet( @@ -1219,3 +1230,36 @@ int LimeSDROutput::webapiRun( return 200; } + +void LimeSDROutput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + bool success = false; + double temp = 0.0; + lms_stream_status_t status; + status.active = false; + status.fifoFilledCount = 0; + status.fifoSize = 1; + status.underrun = 0; + status.overrun = 0; + status.droppedPackets = 0; + status.linkRate = 0.0; + status.timestamp = 0; + + success = (m_streamId.handle && (LMS_GetStreamStatus(&m_streamId, &status) == 0)); + + response.getLimeSdrOutputReport()->setSuccess(success ? 1 : 0); + response.getLimeSdrOutputReport()->setStreamActive(status.active ? 1 : 0); + response.getLimeSdrOutputReport()->setFifoSize(status.fifoSize); + response.getLimeSdrOutputReport()->setFifoFill(status.fifoFilledCount); + response.getLimeSdrOutputReport()->setUnderrunCount(status.underrun); + response.getLimeSdrOutputReport()->setOverrunCount(status.overrun); + response.getLimeSdrOutputReport()->setDroppedPacketsCount(status.droppedPackets); + response.getLimeSdrOutputReport()->setLinkRate(status.linkRate); + response.getLimeSdrOutputReport()->setHwTimestamp(status.timestamp); + + if (m_deviceShared.m_deviceParams->getDevice()) { + LMS_GetChipTemperature(m_deviceShared.m_deviceParams->getDevice(), 0, &temp); + } + + response.getLimeSdrOutputReport()->setTemperature(temp); +} diff --git a/plugins/samplesink/limesdroutput/limesdroutput.h b/plugins/samplesink/limesdroutput/limesdroutput.h index 7c064b924..e4979c5ca 100644 --- a/plugins/samplesink/limesdroutput/limesdroutput.h +++ b/plugins/samplesink/limesdroutput/limesdroutput.h @@ -207,6 +207,10 @@ public: SWGSDRangel::SWGDeviceSettings& response, // query + response QString& errorMessage); + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + virtual int webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage); @@ -217,9 +221,9 @@ public: QString& errorMessage); std::size_t getChannelIndex(); - void getLORange(float& minF, float& maxF, float& stepF) const; - void getSRRange(float& minF, float& maxF, float& stepF) const; - void getLPRange(float& minF, float& maxF, float& stepF) const; + void getLORange(float& minF, float& maxF) const; + void getSRRange(float& minF, float& maxF) const; + void getLPRange(float& minF, float& maxF) const; uint32_t getHWLog2Interp() const; private: @@ -244,6 +248,7 @@ private: void resumeTxBuddies(); bool applySettings(const LimeSDROutputSettings& settings, bool force = false, bool forceNCOFrequency = false); void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const LimeSDROutputSettings& settings); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); }; #endif /* PLUGINS_SAMPLESOURCE_LIMESDROUTPUT_LIMESDROUTPUT_H_ */ diff --git a/plugins/samplesink/limesdroutput/limesdroutput.pro b/plugins/samplesink/limesdroutput/limesdroutput.pro index 0ef177a0e..e296a3e6c 100644 --- a/plugins/samplesink/limesdroutput/limesdroutput.pro +++ b/plugins/samplesink/limesdroutput/limesdroutput.pro @@ -17,10 +17,11 @@ DEFINES += USE_SSE4_1=1 QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 -CONFIG(MINGW32):LIBLIMESUITESRC = "D:\softs\LimeSuite" -CONFIG(MINGW64):LIBLIMESUITESRC = "D:\softs\LimeSuite" +CONFIG(MINGW32):LIBLIMESUITESRC = "C:\softs\LimeSuite" +CONFIG(MINGW64):LIBLIMESUITESRC = "C:\softs\LimeSuite" INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client diff --git a/plugins/samplesink/limesdroutput/limesdroutputgui.cpp b/plugins/samplesink/limesdroutput/limesdroutputgui.cpp index 9cd33f6d1..f904360d9 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputgui.cpp +++ b/plugins/samplesink/limesdroutput/limesdroutputgui.cpp @@ -32,7 +32,7 @@ LimeSDROutputGUI::LimeSDROutputGUI(DeviceUISet *deviceUISet, QWidget* parent) : m_deviceUISet(deviceUISet), m_settings(), m_sampleRate(0), - m_lastEngineState((DSPDeviceSinkEngine::State)-1), + m_lastEngineState(DSPDeviceSinkEngine::StNotStarted), m_doApplySettings(true), m_forceSettings(true), m_statusCounter(0), @@ -42,17 +42,17 @@ LimeSDROutputGUI::LimeSDROutputGUI(DeviceUISet *deviceUISet, QWidget* parent) : ui->setupUi(this); - float minF, maxF, stepF; + float minF, maxF; - m_limeSDROutput->getLORange(minF, maxF, stepF); + m_limeSDROutput->getLORange(minF, maxF); ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); ui->centerFrequency->setValueRange(7, ((uint32_t) minF)/1000, ((uint32_t) maxF)/1000); // frequency dial is in kHz - m_limeSDROutput->getSRRange(minF, maxF, stepF); + m_limeSDROutput->getSRRange(minF, maxF); ui->sampleRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow)); ui->sampleRate->setValueRange(8, (uint32_t) minF, (uint32_t) maxF); - m_limeSDROutput->getLPRange(minF, maxF, stepF); + m_limeSDROutput->getLPRange(minF, maxF); ui->lpf->setColorMapper(ColorMapper(ColorMapper::GrayYellow)); ui->lpf->setValueRange(6, (minF/1000)+1, maxF/1000); @@ -109,12 +109,12 @@ void LimeSDROutputGUI::resetToDefaults() qint64 LimeSDROutputGUI::getCenterFrequency() const { - return m_settings.m_centerFrequency; + return m_settings.m_centerFrequency + (m_settings.m_ncoEnable ? m_settings.m_ncoFrequency : 0); } void LimeSDROutputGUI::setCenterFrequency(qint64 centerFrequency) { - m_settings.m_centerFrequency = centerFrequency; + m_settings.m_centerFrequency = centerFrequency - (m_settings.m_ncoEnable ? m_settings.m_ncoFrequency : 0); displaySettings(); sendSettings(); } @@ -140,6 +140,23 @@ bool LimeSDROutputGUI::deserialize(const QByteArray& data) } } +void LimeSDROutputGUI::updateFrequencyLimits() +{ + // values in kHz + float minF, maxF; + qint64 deltaFrequency = m_settings.m_transverterMode ? m_settings.m_transverterDeltaFrequency/1000 : 0; + m_limeSDROutput->getLORange(minF, maxF); + qint64 minLimit = minF/1000 + deltaFrequency; + qint64 maxLimit = maxF/1000 + deltaFrequency; + + minLimit = minLimit < 0 ? 0 : minLimit > 9999999 ? 9999999 : minLimit; + maxLimit = maxLimit < 0 ? 0 : maxLimit > 9999999 ? 9999999 : maxLimit; + + qDebug("LimeSDROutputGUI::updateFrequencyLimits: delta: %lld min: %lld max: %lld", deltaFrequency, minLimit, maxLimit); + + ui->centerFrequency->setValueRange(7, minLimit, maxLimit); +} + bool LimeSDROutputGUI::handleMessage(const Message& message) { if (LimeSDROutput::MsgConfigureLimeSDR::match(message)) @@ -298,7 +315,7 @@ void LimeSDROutputGUI::displaySettings() ui->extClock->setExternalClockFrequency(m_settings.m_extClockFreq); ui->extClock->setExternalClockActive(m_settings.m_extClock); - ui->centerFrequency->setValue(m_settings.m_centerFrequency / 1000); + setCenterFrequencyDisplay(); ui->sampleRate->setValue(m_settings.m_devSampleRate); ui->hwInterp->setCurrentIndex(m_settings.m_log2HardInterp); @@ -324,11 +341,42 @@ void LimeSDROutputGUI::displaySettings() void LimeSDROutputGUI::setNCODisplay() { int ncoHalfRange = (m_settings.m_devSampleRate * (1<<(m_settings.m_log2HardInterp)))/2; - int lowBoundary = std::max(0, (int) m_settings.m_centerFrequency - ncoHalfRange); - ui->ncoFrequency->setValueRange(7, - lowBoundary/1000, - (m_settings.m_centerFrequency + ncoHalfRange)/1000); // frequency dial is in kHz - ui->ncoFrequency->setValue((m_settings.m_centerFrequency + m_settings.m_ncoFrequency)/1000); + ui->ncoFrequency->setValueRange( + false, + 8, + -ncoHalfRange, + ncoHalfRange); + + ui->ncoFrequency->blockSignals(true); + ui->ncoFrequency->setToolTip(QString("NCO frequency shift in Hz (Range: +/- %1 kHz)").arg(ncoHalfRange/1000)); + ui->ncoFrequency->setValue(m_settings.m_ncoFrequency); + ui->ncoFrequency->blockSignals(false); +} + +void LimeSDROutputGUI::setCenterFrequencyDisplay() +{ + int64_t centerFrequency = m_settings.m_centerFrequency; + ui->centerFrequency->setToolTip(QString("Main center frequency in kHz (LO: %1 kHz)").arg(centerFrequency/1000)); + + if (m_settings.m_ncoEnable) { + centerFrequency += m_settings.m_ncoFrequency; + } + + ui->centerFrequency->blockSignals(true); + ui->centerFrequency->setValue(centerFrequency < 0 ? 0 : (uint64_t) centerFrequency/1000); // kHz + ui->centerFrequency->blockSignals(false); +} + +void LimeSDROutputGUI::setCenterFrequencySetting(uint64_t kHzValue) +{ + int64_t centerFrequency = kHzValue*1000; + + if (m_settings.m_ncoEnable) { + centerFrequency -= m_settings.m_ncoFrequency; + } + + m_settings.m_centerFrequency = centerFrequency < 0 ? 0 : (uint64_t) centerFrequency; + ui->centerFrequency->setToolTip(QString("Main center frequency in kHz (LO: %1 kHz)").arg(centerFrequency/1000)); } void LimeSDROutputGUI::sendSettings() @@ -420,28 +468,21 @@ void LimeSDROutputGUI::on_startStop_toggled(bool checked) void LimeSDROutputGUI::on_centerFrequency_changed(quint64 value) { - m_settings.m_centerFrequency = value * 1000; - setNCODisplay(); + setCenterFrequencySetting(value); sendSettings(); } -void LimeSDROutputGUI::on_ncoFrequency_changed(quint64 value) +void LimeSDROutputGUI::on_ncoFrequency_changed(qint64 value) { - m_settings.m_ncoFrequency = (int64_t) value - (int64_t) m_settings.m_centerFrequency/1000; - m_settings.m_ncoFrequency *= 1000; + m_settings.m_ncoFrequency = value; + setCenterFrequencyDisplay(); sendSettings(); } void LimeSDROutputGUI::on_ncoEnable_toggled(bool checked) { m_settings.m_ncoEnable = checked; - sendSettings(); -} - -void LimeSDROutputGUI::on_ncoReset_clicked(bool checked __attribute__((unused))) -{ - m_settings.m_ncoFrequency = 0; - ui->ncoFrequency->setValue(m_settings.m_centerFrequency/1000); + setCenterFrequencyDisplay(); sendSettings(); } @@ -485,10 +526,7 @@ void LimeSDROutputGUI::on_lpFIREnable_toggled(bool checked) void LimeSDROutputGUI::on_lpFIR_changed(quint64 value) { m_settings.m_lpfFIRBW = value * 1000; - - if (m_settings.m_lpfFIREnable) { // do not send the update if the FIR is disabled - sendSettings(); - } + sendSettings(); } void LimeSDROutputGUI::on_gain_valueChanged(int value) @@ -512,3 +550,14 @@ void LimeSDROutputGUI::on_extClock_clicked() sendSettings(); } +void LimeSDROutputGUI::on_transverter_clicked() +{ + m_settings.m_transverterMode = ui->transverter->getDeltaFrequencyAcive(); + m_settings.m_transverterDeltaFrequency = ui->transverter->getDeltaFrequency(); + qDebug("LimeSDRInputGUI::on_transverter_clicked: %lld Hz %s", m_settings.m_transverterDeltaFrequency, m_settings.m_transverterMode ? "on" : "off"); + updateFrequencyLimits(); + setCenterFrequencySetting(ui->centerFrequency->getValueNew()); + sendSettings(); +} + + diff --git a/plugins/samplesink/limesdroutput/limesdroutputgui.h b/plugins/samplesink/limesdroutput/limesdroutputgui.h index 301722131..bfa1147b6 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputgui.h +++ b/plugins/samplesink/limesdroutput/limesdroutputgui.h @@ -70,18 +70,20 @@ private: void displaySettings(); void setNCODisplay(); + void setCenterFrequencyDisplay(); + void setCenterFrequencySetting(uint64_t kHzValue); void sendSettings(); void updateSampleRateAndFrequency(); void updateDACRate(); + void updateFrequencyLimits(); void blockApplySettings(bool block); private slots: void handleInputMessages(); void on_startStop_toggled(bool checked); void on_centerFrequency_changed(quint64 value); - void on_ncoFrequency_changed(quint64 value); + void on_ncoFrequency_changed(qint64 value); void on_ncoEnable_toggled(bool checked); - void on_ncoReset_clicked(bool checked); void on_sampleRate_changed(quint64 value); void on_hwInterp_currentIndexChanged(int index); void on_swInterp_currentIndexChanged(int index); @@ -91,6 +93,7 @@ private slots: void on_gain_valueChanged(int value); void on_antenna_currentIndexChanged(int index); void on_extClock_clicked(); + void on_transverter_clicked(); void updateHardware(); void updateStatus(); diff --git a/plugins/samplesink/limesdroutput/limesdroutputgui.ui b/plugins/samplesink/limesdroutput/limesdroutputgui.ui index 487855139..19d8d00e4 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputgui.ui +++ b/plugins/samplesink/limesdroutput/limesdroutputgui.ui @@ -6,7 +6,7 @@ 0 0 - 350 + 360 290 @@ -18,18 +18,18 @@ - 350 + 360 290 - Sans Serif + Liberation Sans 9 - LimeSDR Input + LimeSDR output @@ -127,7 +127,7 @@ - DejaVu Sans Mono + Liberation Mono 20 @@ -217,23 +217,7 @@
- - - - 22 - 22 - - - - Reset the NCO to zero frequency - - - R - - - - - + 0 @@ -248,7 +232,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -256,14 +240,14 @@ PointingHandCursor - Center frequency with NCO engaged (kHz) + NCO frequency shift in Hz - kHz + Hz @@ -280,6 +264,22 @@ + + + + + 24 + 24 + + + + Transverter frequency translation dialog + + + X + + + @@ -467,7 +467,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -526,7 +526,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -584,7 +584,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -938,6 +938,17 @@ QToolTip{background-color: white; color: black;} QToolButton
gui/externalclockbutton.h
+ + ValueDialZ + QWidget +
gui/valuedialz.h
+ 1 +
+ + TransverterButton + QPushButton +
gui/transverterbutton.h
+
diff --git a/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp b/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp index a53c37602..1c4d765e9 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp +++ b/plugins/samplesink/limesdroutput/limesdroutputplugin.cpp @@ -34,7 +34,7 @@ const PluginDescriptor LimeSDROutputPlugin::m_pluginDescriptor = { QString("LimeSDR Output"), - QString("3.10.1"), + QString("4.2.4"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesink/limesdroutput/limesdroutputsettings.cpp b/plugins/samplesink/limesdroutput/limesdroutputsettings.cpp index b20991b78..96270d375 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputsettings.cpp +++ b/plugins/samplesink/limesdroutput/limesdroutputsettings.cpp @@ -38,6 +38,8 @@ void LimeSDROutputSettings::resetToDefaults() m_antennaPath = PATH_RFE_NONE; m_extClock = false; m_extClockFreq = 10000000; // 10 MHz + m_transverterMode = false; + m_transverterDeltaFrequency = 0; } QByteArray LimeSDROutputSettings::serialize() const @@ -56,6 +58,8 @@ QByteArray LimeSDROutputSettings::serialize() const s.writeS32(13, (int) m_antennaPath); s.writeBool(14, m_extClock); s.writeU32(15, m_extClockFreq); + s.writeBool(16, m_transverterMode); + s.writeS64(17, m_transverterDeltaFrequency); return s.final(); } @@ -87,6 +91,8 @@ bool LimeSDROutputSettings::deserialize(const QByteArray& data) m_antennaPath = (PathRFE) intval; d.readBool(14, &m_extClock, false); d.readU32(15, &m_extClockFreq, 10000000); + d.readBool(16, &m_transverterMode, false); + d.readS64(17, &m_transverterDeltaFrequency, 0); return true; } diff --git a/plugins/samplesink/limesdroutput/limesdroutputsettings.h b/plugins/samplesink/limesdroutput/limesdroutputsettings.h index 72e47d61f..2fe21b054 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputsettings.h +++ b/plugins/samplesink/limesdroutput/limesdroutputsettings.h @@ -54,6 +54,8 @@ struct LimeSDROutputSettings PathRFE m_antennaPath; bool m_extClock; //!< True if external clock source uint32_t m_extClockFreq; //!< Frequency (Hz) of external clock source + bool m_transverterMode; + qint64 m_transverterDeltaFrequency; LimeSDROutputSettings(); void resetToDefaults(); diff --git a/plugins/samplesink/limesdroutput/limesdroutputthread.cpp b/plugins/samplesink/limesdroutput/limesdroutputthread.cpp index 218f35523..f16eb2c7c 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputthread.cpp +++ b/plugins/samplesink/limesdroutput/limesdroutputthread.cpp @@ -15,6 +15,7 @@ /////////////////////////////////////////////////////////////////////////////////// #include +#include #include "limesdroutputthread.h" #include "limesdroutputsettings.h" @@ -24,9 +25,9 @@ LimeSDROutputThread::LimeSDROutputThread(lms_stream_t* stream, SampleSourceFifo* m_running(false), m_stream(stream), m_sampleFifo(sampleFifo), - m_log2Interp(0), - m_fcPos(LimeSDROutputSettings::FC_POS_CENTER) + m_log2Interp(0) { + std::fill(m_buf, m_buf + 2*LIMESDROUTPUT_BLOCKSIZE, 0); } LimeSDROutputThread::~LimeSDROutputThread() @@ -72,11 +73,6 @@ void LimeSDROutputThread::setLog2Interpolation(unsigned int log2_interp) m_log2Interp = log2_interp; } -void LimeSDROutputThread::setFcPos(int fcPos) -{ - m_fcPos = fcPos; -} - void LimeSDROutputThread::run() { int res; diff --git a/plugins/samplesink/limesdroutput/limesdroutputthread.h b/plugins/samplesink/limesdroutput/limesdroutputthread.h index e543d510c..2761f45d5 100644 --- a/plugins/samplesink/limesdroutput/limesdroutputthread.h +++ b/plugins/samplesink/limesdroutput/limesdroutputthread.h @@ -42,7 +42,6 @@ public: virtual void setDeviceSampleRate(int __attribute__((unused)) sampleRate) {} virtual bool isRunning() { return m_running; } void setLog2Interpolation(unsigned int log2_ioterp); - void setFcPos(int fcPos); private: QMutex m_startWaitMutex; @@ -54,7 +53,6 @@ private: SampleSourceFifo* m_sampleFifo; unsigned int m_log2Interp; // soft decimation - int m_fcPos; Interpolators m_interpolators; diff --git a/plugins/samplesink/limesdroutput/readme.md b/plugins/samplesink/limesdroutput/readme.md index c6a8c47b9..2bbf9aba9 100644 --- a/plugins/samplesink/limesdroutput/readme.md +++ b/plugins/samplesink/limesdroutput/readme.md @@ -4,7 +4,9 @@ This output sample sink plugin sends its samples to a [LimeSDR device](https://myriadrf.org/projects/limesdr/). -⚠ LimeSuite library is difficult to implement due to the lack of documentation. The plugins should work normally when running as single instances. Support of both Rx and/or both Rx running concurrently is experimental. +

⚠ Version 18.04.1 of LimeSuite is used in the buildsand corresponding gateware loaded in the LimeSDR should be is used (2.16 for LimeSDR-USB and 1.24 for LimeSDR-Mini). If you compile from source version 18.04.1 of LimeSuite must be used.

+ +

⚠ LimeSDR-Mini seems to have problems with Zadig driver therefore it is supported in Linux only.

LimeSDR is a 2x2 MIMO device so it has two transmitting channels that can run concurrently. To activate the second channel when the first is already active just open a new sink tab in the main window (Devices -> Add sink device) and select the same LimeSDR device. @@ -39,7 +41,7 @@ Device start / stop button. - Blue triangle icon: device is ready and can be started - Green square icon: device is running and can be stopped - - Magenta (or pink) square icon: an error occured. In the case the device was accidentally disconnected you may click on the icon to stop, plug back in, check the source on the sampling devices control panel and start again. + - Magenta (or pink) square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon to stop, plug back in, check the source on the sampling devices control panel and start again.

2A: DAC sample rate

@@ -73,15 +75,41 @@ The button is lit when NCO is active and dark when inactive. Use this button to activate/deactivate the TSP NCO. The LMS7002M chip has an independent NCO in each Tx channel that can span the bandwidth sent to the DAC. This effectively allows non zero digital IF. -

6: Zero (reset) NCO frequency

+

6: NCO frequency shift

-Use this push button to reset the NCO frequency to 0 and thus center on the main passband of the DAC. +This is the frequency shift applied when the NCO is engaged thus the actual LO frequency is the center frequency of transmission minus this value. Use the thumbwheels to adjust frequency as done with the LO (1.1). Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. The boundaries are dynamically calculated from the LO center frequency, sample rate and hardware interpolation factor. -

7: Center frequency with NCO engaged

+☞ In the LMS7002M TSP block the NCO sits after the interpolator (see Fig.14 of the [datasheet](http://www.limemicro.com/wp-content/uploads/2015/09/LMS7002M-Data-Sheet-v2.8.0.pdf) p.7) so it runs at the actual DAC rate. Hence the NCO limits are calculated as +/- half the device to host sample rate multiplied by the hardware interpolation factor. For example with a 4 MS/s device to host sample rate (10) and a hardware interpolation of 16 (8) you have +/- 32 MHz span around the LO for the NCO. In this example you can tune all HF frequencies with the center frequency set at its lowest (30 MHz). -This is the center frequency of the mix of LO and NCO combined and is the sink passband center frequency when the NCO is engaged. Use the thumbwheels to adjust frequency as done with the LO (1.1). Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. The boundaries are dynamically calculated from the LO center frequency, sample rate and hardware interpolation factor. +

7: Transverter mode open dialog

-☞ In the LMS7002M TSP block the NCO sits after the interpolator (see Fig.14 of the [datasheet](http://www.limemicro.com/wp-content/uploads/2015/09/LMS7002M-Data-Sheet-v2.8.0.pdf) p.7) so it runs at the actual DAC rate. Hence the NCO limits are calculated as +/- half the device to host sample rate multiplied by the hardware interpolation factor. For example with a 4 MS/s device to host sample rate (10) and a hadrware interpolation of 16 (8) you have +/- 32 MHz span around the LO for the NCO. In this example you can tune all HF frequencies with the center frequency set at its lowest (30 MHz). +This button opens a dialog to set the transverter mode frequency translation options: + +![SDR Daemon source input stream transverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) + +Note that if you mouse over the button a tooltip appears that displays the translating frequency and if translation is enabled or disabled. When the frequency translation is enabled the button is lit. + +

7.1: Translating frequency

+ +You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. + +The frequency set in the device is the frequency on the main dial (1) minus this frequency. Thus it is positive for down converters and negative for up converters. + +For example a mixer at 120 MHz for HF operation you would set the value to -120,000,000 Hz so that if the main dial frequency is set at 7,130 kHz the PlutoSDR will be set to 127.130 MHz. + +If you use a down converter to receive the 6 cm band narrowband center frequency of 5670 MHz at 432 MHz you would set the translating frequency to 5760 - 432 = 5328 MHz thus dial +5,328,000,000 Hz. + +For bands even higher in the frequency spectrum the GHz digits are not really significant so you can have them set at 1 GHz. Thus to receive the 10368 MHz frequency at 432 MHz you would set the translating frequency to 1368 - 432 = 936 MHz. Note that in this case the frequency of the LO used in the mixer of the transverter is set at 9936 MHz. + +The Hz precision allows a fine tuning of the transverter LO offset + +

7.2: Translating frequency enable/disable

+ +Use this toggle button to activate or deactivate the frequency translation + +

7.3: Confirmation buttons

+ +Use these buttons to confirm ("OK") or dismiss ("Cancel") your changes.

7A: External clock control

@@ -89,11 +117,11 @@ Use this button to open a dialog that lets you choose the external clock frequen ![LimeSDR input plugin gain GUI](../../../doc/img/LimeSDR_plugin_extclock.png) -

7A.1: Exrernal clock frequency

+

7A.1: External clock frequency

Can be varied from 5 to 300 MHz -Use the thumbwheels to adjust frequency as done with the LO (1.1). Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. The boundaries are dynamically calculated from the LO center frequency, sample rate and hardware decimation factor. +Use the thumbwheels to adjust frequency as done with the LO (1.1). Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. The boundaries are dynamically calculated from the LO center frequency, sample rate and hardware decimation factor.

7A.2: Enable/disable external clock input @@ -121,13 +149,13 @@ The I/Q stream from the baseband is upsampled by a power of two by software insi This is the LMS7002M device to/from host stream sample rate in S/s. It is the same for the Rx and Tx systems. -Use the wheels to adjust the sample rate. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. +Use the wheels to adjust the sample rate. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. The LMS7002M uses the same clock for both the ADCs and DACs therefore this sample rate affects all of the 2x2 MIMO channels.

11: Tx hardware filter bandwidth

-This is the Tx hardware filter bandwidth in kHz in the LMS7002M device for the given channel. Boundaries are updated automatically but generally are from 5 to 130 MHz in 1 kHz steps. Use the wheels to adjust the value. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +This is the Tx hardware filter bandwidth in kHz in the LMS7002M device for the given channel. Boundaries are updated automatically but generally are from 5 to 130 MHz in 1 kHz steps. Use the wheels to adjust the value. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

12: TSP FIR filter toggle

@@ -135,11 +163,11 @@ The TSP in the LMS7002M chip has a FIR filter chain per channel. Use this button

13: TSP FIR filter bandwidth

-Use the wheels to adjust the bandwidth of the hardware TSP FIR filter. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the bandwidth of the hardware TSP FIR filter. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

14: Gain

-Use this slider to adjust the global gain of the Tx chain. LimeSuite software automatically set optimal values of the amplifiers to achive this global gain. This gain can be set between 0 and 70 dB in 1 dB steps. The value in dB appears at the right of the slider. +Use this slider to adjust the global gain of the Tx chain. LimeSuite software automatically set optimal values of the amplifiers to achieve this global gain. This gain can be set between 0 and 70 dB in 1 dB steps. The value in dB appears at the right of the slider.

15: Antenna selection

@@ -159,7 +187,7 @@ This label turns green when status can be obtained from the current stream. Usua

18: Stream global (all Tx) throughput in MB/s

-This is the stream throughput in MB/s and is usually about 3 times the sample rate for a single stream and 6 times for a dual Tx stream. This is due to the fact that 12 bits samples are used and although they are represented as 16 bit values only 12 bita travel on the USB link. +This is the stream throughput in MB/s and is usually about 3 times the sample rate for a single stream and 6 times for a dual Tx stream. This is due to the fact that 12 bits samples are used and although they are represented as 16 bit values only 12 bits travel on the USB link.

19: FIFO status

@@ -167,4 +195,4 @@ This is the fill percentage of the Tx FIFO in the LimeSuite interface. In normal

20: Board temperature

-This is the board temperature in degrees Celsius updated every ~5s. Before the first probe the display marks "00C" this is normal. \ No newline at end of file +This is the board temperature in degrees Celsius updated every ~5s. Before the first probe the display marks "00C" this is normal. diff --git a/plugins/samplesink/plutosdroutput/plutosdroutput.cpp b/plugins/samplesink/plutosdroutput/plutosdroutput.cpp index 6d8d90aed..3c81a79a2 100644 --- a/plugins/samplesink/plutosdroutput/plutosdroutput.cpp +++ b/plugins/samplesink/plutosdroutput/plutosdroutput.cpp @@ -18,6 +18,8 @@ #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" +#include "SWGDeviceReport.h" +#include "SWGPlutoSdrOutputReport.h" #include "dsp/dspcommands.h" #include "dsp/dspengine.h" @@ -42,6 +44,13 @@ PlutoSDROutput::PlutoSDROutput(DeviceSinkAPI *deviceAPI) : m_plutoTxBuffer(0), m_plutoSDROutputThread(0) { + m_deviceSampleRates.m_addaConnvRate = 0; + m_deviceSampleRates.m_bbRateHz = 0; + m_deviceSampleRates.m_firRate = 0; + m_deviceSampleRates.m_hb1Rate = 0; + m_deviceSampleRates.m_hb2Rate = 0; + m_deviceSampleRates.m_hb3Rate = 0; + suspendBuddies(); openDevice(); resumeBuddies(); @@ -72,20 +81,12 @@ bool PlutoSDROutput::start() if (m_running) stop(); - applySettings(m_settings, true); - // start / stop streaming is done in the thread. - if ((m_plutoSDROutputThread = new PlutoSDROutputThread(PLUTOSDR_BLOCKSIZE_SAMPLES, m_deviceShared.m_deviceParams->getBox(), &m_sampleSourceFifo)) == 0) - { - qCritical("PlutoSDROutput::start: cannot create thread"); - stop(); - return false; - } - else - { - qDebug("PlutoSDROutput::start: thread created"); - } + m_plutoSDROutputThread = new PlutoSDROutputThread(PLUTOSDR_BLOCKSIZE_SAMPLES, m_deviceShared.m_deviceParams->getBox(), &m_sampleSourceFifo); + qDebug("PlutoSDROutput::start: thread created"); + + applySettings(m_settings, true); m_plutoSDROutputThread->setLog2Interpolation(m_settings.m_log2Interp); m_plutoSDROutputThread->startWork(); @@ -202,13 +203,11 @@ bool PlutoSDROutput::handleMessage(const Message& message) if (m_deviceAPI->initGeneration()) { m_deviceAPI->startGeneration(); - DSPEngine::instance()->startAudioInput(); } } else { m_deviceAPI->stopGeneration(); - DSPEngine::instance()->stopAudioInput(); } return true; @@ -257,11 +256,9 @@ bool PlutoSDROutput::openDevice() m_deviceAPI->setBuddySharedPtr(&m_deviceShared); // propagate common parameters to API // acquire the channel - suspendBuddies(); DevicePlutoSDRBox *plutoBox = m_deviceShared.m_deviceParams->getBox(); plutoBox->openTx(); m_plutoTxBuffer = plutoBox->createTxBuffer(PLUTOSDR_BLOCKSIZE_SAMPLES, false); // PlutoSDR buffer size is counted in number of (I,Q) samples - resumeBuddies(); return true; } @@ -317,6 +314,22 @@ bool PlutoSDROutput::applySettings(const PlutoSDROutputSettings& settings, bool bool ownThreadWasRunning = false; bool suspendAllOtherThreads = false; // All others means Rx in fact DevicePlutoSDRBox *plutoBox = m_deviceShared.m_deviceParams->getBox(); + QLocale loc; + + qDebug().noquote() << "PlutoSDROutput::applySettings: center freq: " << m_settings.m_centerFrequency << " Hz" + << " m_devSampleRate: " << loc.toString(m_settings.m_devSampleRate) << "S/s" + << " m_LOppmTenths: " << m_settings.m_LOppmTenths + << " m_lpfFIREnable: " << m_settings.m_lpfFIREnable + << " m_lpfFIRBW: " << loc.toString(m_settings.m_lpfFIRBW) + << " m_lpfFIRlog2Interp: " << m_settings.m_lpfFIRlog2Interp + << " m_lpfFIRGain: " << m_settings.m_lpfFIRGain + << " m_log2Interp: " << loc.toString(1<init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int PlutoSDROutput::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage __attribute__((unused))) +{ + PlutoSDROutputSettings settings = m_settings; + + if (deviceSettingsKeys.contains("centerFrequency")) { + settings.m_centerFrequency = response.getPlutoSdrOutputSettings()->getCenterFrequency(); + } + if (deviceSettingsKeys.contains("devSampleRate")) { + settings.m_devSampleRate = response.getPlutoSdrOutputSettings()->getDevSampleRate(); + } + if (deviceSettingsKeys.contains("LOppmTenths")) { + settings.m_LOppmTenths = response.getPlutoSdrOutputSettings()->getLOppmTenths(); + } + if (deviceSettingsKeys.contains("lpfFIREnable")) { + settings.m_lpfFIREnable = response.getPlutoSdrOutputSettings()->getLpfFirEnable() != 0; + } + if (deviceSettingsKeys.contains("lpfFIRBW")) { + settings.m_lpfFIRBW = response.getPlutoSdrOutputSettings()->getLpfFirbw(); + } + if (deviceSettingsKeys.contains("lpfFIRlog2Interp")) { + settings.m_lpfFIRlog2Interp = response.getPlutoSdrOutputSettings()->getLpfFiRlog2Interp(); + } + if (deviceSettingsKeys.contains("lpfFIRGain")) { + settings.m_lpfFIRGain = response.getPlutoSdrOutputSettings()->getLpfFirGain(); + } + if (deviceSettingsKeys.contains("log2Interp")) { + settings.m_log2Interp = response.getPlutoSdrOutputSettings()->getLog2Interp(); + } + if (deviceSettingsKeys.contains("lpfBW")) { + settings.m_lpfBW = response.getPlutoSdrOutputSettings()->getLpfBw(); + } + if (deviceSettingsKeys.contains("att")) { + settings.m_att = response.getPlutoSdrOutputSettings()->getAtt(); + } + if (deviceSettingsKeys.contains("antennaPath")) { + int antennaPath = response.getPlutoSdrOutputSettings()->getAntennaPath(); + antennaPath = antennaPath < 0 ? 0 : antennaPath >= PlutoSDROutputSettings::RFPATH_END ? PlutoSDROutputSettings::RFPATH_END-1 : antennaPath; + settings.m_antennaPath = (PlutoSDROutputSettings::RFPath) antennaPath; + } + if (deviceSettingsKeys.contains("transverterDeltaFrequency")) { + settings.m_transverterDeltaFrequency = response.getPlutoSdrOutputSettings()->getTransverterDeltaFrequency(); + } + if (deviceSettingsKeys.contains("transverterMode")) { + settings.m_transverterMode = response.getPlutoSdrOutputSettings()->getTransverterMode() != 0; + } + + MsgConfigurePlutoSDR *msg = MsgConfigurePlutoSDR::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigurePlutoSDR *msgToGUI = MsgConfigurePlutoSDR::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +int PlutoSDROutput::webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setPlutoSdrOutputReport(new SWGSDRangel::SWGPlutoSdrOutputReport()); + response.getPlutoSdrOutputReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} + +void PlutoSDROutput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const PlutoSDROutputSettings& settings) +{ + response.getPlutoSdrOutputSettings()->setCenterFrequency(settings.m_centerFrequency); + response.getPlutoSdrOutputSettings()->setDevSampleRate(settings.m_devSampleRate); + response.getPlutoSdrOutputSettings()->setLOppmTenths(settings.m_LOppmTenths); + response.getPlutoSdrOutputSettings()->setLpfFirEnable(settings.m_lpfFIREnable ? 1 : 0); + response.getPlutoSdrOutputSettings()->setLpfFirbw(settings.m_lpfFIRBW); + response.getPlutoSdrOutputSettings()->setLpfFiRlog2Interp(settings.m_lpfFIRlog2Interp); + response.getPlutoSdrOutputSettings()->setLpfFirGain(settings.m_lpfFIRGain); + response.getPlutoSdrOutputSettings()->setLog2Interp(settings.m_log2Interp); + response.getPlutoSdrOutputSettings()->setLpfBw(settings.m_lpfBW); + response.getPlutoSdrOutputSettings()->setAtt(settings.m_att); + response.getPlutoSdrOutputSettings()->setAntennaPath((int) settings.m_antennaPath); + response.getPlutoSdrOutputSettings()->setTransverterDeltaFrequency(settings.m_transverterDeltaFrequency); + response.getPlutoSdrOutputSettings()->setTransverterMode(settings.m_transverterMode ? 1 : 0); +} + +void PlutoSDROutput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + response.getPlutoSdrOutputReport()->setDacRate(getDACSampleRate()); + std::string rssiStr; + getRSSI(rssiStr); + response.getPlutoSdrOutputReport()->setRssi(new QString(rssiStr.c_str())); + fetchTemperature(); + response.getPlutoSdrOutputReport()->setTemperature(getTemperature()); +} diff --git a/plugins/samplesink/plutosdroutput/plutosdroutput.h b/plugins/samplesink/plutosdroutput/plutosdroutput.h index bffb9f276..b5512edc8 100644 --- a/plugins/samplesink/plutosdroutput/plutosdroutput.h +++ b/plugins/samplesink/plutosdroutput/plutosdroutput.h @@ -23,6 +23,7 @@ #include #include "util/message.h" #include "plutosdr/deviceplutosdrshared.h" +#include "plutosdr/deviceplutosdrbox.h" #include "plutosdroutputsettings.h" class DeviceSinkAPI; @@ -91,6 +92,20 @@ public: virtual bool handleMessage(const Message& message); + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + virtual int webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage); @@ -122,6 +137,8 @@ public: void suspendBuddies(); void resumeBuddies(); bool applySettings(const PlutoSDROutputSettings& settings, bool force = false); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const PlutoSDROutputSettings& settings); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); }; diff --git a/plugins/samplesink/plutosdroutput/plutosdroutput.pro b/plugins/samplesink/plutosdroutput/plutosdroutput.pro index 96dd9551e..2d1fd03e4 100644 --- a/plugins/samplesink/plutosdroutput/plutosdroutput.pro +++ b/plugins/samplesink/plutosdroutput/plutosdroutput.pro @@ -17,10 +17,11 @@ DEFINES += USE_SSE4_1=1 QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 -CONFIG(MINGW32):LIBIIOSRC = "D:\softs\libiio" -CONFIG(MINGW64):LIBIIOSRC = "D:\softs\libiio" +CONFIG(MINGW32):LIBIIOSRC = "C:\softs\libiio" +CONFIG(MINGW64):LIBIIOSRC = "C:\softs\libiio" INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client diff --git a/plugins/samplesink/plutosdroutput/plutosdroutputgui.cpp b/plugins/samplesink/plutosdroutput/plutosdroutputgui.cpp index 498081ad3..731240c46 100644 --- a/plugins/samplesink/plutosdroutput/plutosdroutputgui.cpp +++ b/plugins/samplesink/plutosdroutput/plutosdroutputgui.cpp @@ -37,7 +37,7 @@ PlutoSDROutputGUI::PlutoSDROutputGUI(DeviceUISet *deviceUISet, QWidget* parent) m_sampleSink(0), m_sampleRate(0), m_deviceCenterFrequency(0), - m_lastEngineState((DSPDeviceSinkEngine::State)-1), + m_lastEngineState(DSPDeviceSinkEngine::StNotStarted), m_doApplySettings(true), m_statusCounter(0) { diff --git a/plugins/samplesink/plutosdroutput/plutosdroutputgui.h b/plugins/samplesink/plutosdroutput/plutosdroutputgui.h index f9a010f4c..6ca6f2d20 100644 --- a/plugins/samplesink/plutosdroutput/plutosdroutputgui.h +++ b/plugins/samplesink/plutosdroutput/plutosdroutputgui.h @@ -17,13 +17,14 @@ #ifndef PLUGINS_SAMPLESOURCE_PLUTOSDROUTPUT_PLUTOSDROUTPUTGUI_H_ #define PLUGINS_SAMPLESOURCE_PLUTOSDROUTPUT_PLUTOSDROUTPUTGUI_H_ -#include #include #include #include #include "util/messagequeue.h" +#include "plugin/plugininstancegui.h" +#include "plutosdroutput.h" #include "plutosdroutputsettings.h" class DeviceSampleSink; diff --git a/plugins/samplesink/plutosdroutput/plutosdroutputgui.ui b/plugins/samplesink/plutosdroutput/plutosdroutputgui.ui index a996fa364..f72004af0 100644 --- a/plugins/samplesink/plutosdroutput/plutosdroutputgui.ui +++ b/plugins/samplesink/plutosdroutput/plutosdroutputgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -127,7 +127,7 @@ - DejaVu Sans Mono + Liberation Mono 20 50 false @@ -408,7 +408,7 @@ - DejaVu Sans Mono + Liberation Mono 12 50 false @@ -465,7 +465,7 @@ - DejaVu Sans Mono + Liberation Mono 12 50 false @@ -529,7 +529,7 @@ - DejaVu Sans Mono + Liberation Mono 12 diff --git a/plugins/samplesink/plutosdroutput/plutosdroutputplugin.cpp b/plugins/samplesink/plutosdroutput/plutosdroutputplugin.cpp index 188cf223c..fd5469c99 100644 --- a/plugins/samplesink/plutosdroutput/plutosdroutputplugin.cpp +++ b/plugins/samplesink/plutosdroutput/plutosdroutputplugin.cpp @@ -15,20 +15,22 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include #include "plugin/pluginapi.h" #include "plutosdr/deviceplutosdr.h" -#include "plutosdroutputgui.h" +#ifdef SERVER_MODE #include "plutosdroutput.h" +#else +#include "plutosdroutputgui.h" +#endif #include "plutosdroutputplugin.h" class DeviceSourceAPI; const PluginDescriptor PlutoSDROutputPlugin::m_pluginDescriptor = { QString("PlutoSDR Output"), - QString("3.10.1"), + QString("4.0.4"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, @@ -85,6 +87,15 @@ PluginInterface::SamplingDevices PlutoSDROutputPlugin::enumSampleSinks() return result; } +#ifdef SERVER_MODE +PluginInstanceGUI* PlutoSDROutputPlugin::createSampleSinkPluginInstanceGUI( + const QString& sinkId __attribute((unused)), + QWidget **widget __attribute((unused)), + DeviceUISet *deviceUISet __attribute((unused))) +{ + return 0; +} +#else PluginInstanceGUI* PlutoSDROutputPlugin::createSampleSinkPluginInstanceGUI( const QString& sinkId, QWidget **widget, @@ -101,6 +112,7 @@ PluginInstanceGUI* PlutoSDROutputPlugin::createSampleSinkPluginInstanceGUI( return 0; } } +#endif DeviceSampleSink *PlutoSDROutputPlugin::createSampleSinkPluginInstanceOutput(const QString& sinkId, DeviceSinkAPI *deviceAPI) { diff --git a/plugins/samplesink/plutosdroutput/plutosdroutputthread.cpp b/plugins/samplesink/plutosdroutput/plutosdroutputthread.cpp index ea1e553a2..88a10bc7a 100644 --- a/plugins/samplesink/plutosdroutput/plutosdroutputthread.cpp +++ b/plugins/samplesink/plutosdroutput/plutosdroutputthread.cpp @@ -94,7 +94,6 @@ void PlutoSDROutputThread::run() for (p_dat = m_plutoBox->txBufferFirst(), ihs = 0; p_dat < p_end; p_dat += p_inc, ihs += 2) { m_plutoBox->txChannelConvert((int16_t*) p_dat, &m_buf[ihs]); - //*((int16_t*)p_dat) = m_buf[ihs] << 4; } // Schedule TX buffer for sending @@ -102,7 +101,7 @@ void PlutoSDROutputThread::run() if (nbytes_tx != 4*m_blockSizeSamples) { - qDebug("PlutoSDROutputThread::run: error pushing buf %d / %d\n", (int) nbytes_tx, (int) 4*m_blockSizeSamples); + qDebug("PlutoSDROutputThread::run: error pushing buf %d / %d", (int) nbytes_tx, (int) 4*m_blockSizeSamples); usleep(200000); continue; } diff --git a/plugins/samplesink/plutosdroutput/plutosdroutputthread.h b/plugins/samplesink/plutosdroutput/plutosdroutputthread.h index 782c6e389..45f80e218 100644 --- a/plugins/samplesink/plutosdroutput/plutosdroutputthread.h +++ b/plugins/samplesink/plutosdroutput/plutosdroutputthread.h @@ -54,7 +54,7 @@ private: unsigned int m_log2Interp; // soft interpolation - Interpolators m_interpolators; + Interpolators m_interpolators; //!< Pluto is on 12 bit but iio_channel_convert_inverse converts from 16 to 12 bits void run(); void convert(qint16* buf, qint32 len); diff --git a/plugins/samplesink/plutosdroutput/readme.md b/plugins/samplesink/plutosdroutput/readme.md index b8724eb6a..680d0b857 100644 --- a/plugins/samplesink/plutosdroutput/readme.md +++ b/plugins/samplesink/plutosdroutput/readme.md @@ -2,7 +2,7 @@

Introduction

-This output sample sink plugin sends its samples to a [PlutoSDR device](https://wiki.analog.com/university/tools/pluto). This is also known as the ADALM-Pluto. ADALM stands for Analog Devices Active Learning Module and is targetting students in electrical engineering and digital signal processing. Of course it can be used as a radio device like any other SDR. +This output sample sink plugin sends its samples to a [PlutoSDR device](https://wiki.analog.com/university/tools/pluto). This is also known as the ADALM-Pluto. ADALM stands for Analog Devices Active Learning Module and is targeting students in electrical engineering and digital signal processing. Of course it can be used as a radio device like any other SDR. As you can see from the Wiki this is becoming a fairly popular SDR hardware platform. It does have interesting features but the library documentation and examples are poor when not misleading. Therefore while this implementation does work it should still be considered experimental. @@ -45,7 +45,7 @@ Device start / stop button. - Blue triangle icon: device is ready and can be started - Green square icon: device is running and can be stopped - - Magenta (or pink) square icon: an error occured. In the case the device was accidentally disconnected you may click on the icon to stop, plug back in, check the source on the sampling devices control panel and start again. + - Magenta (or pink) square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon to stop, plug back in, check the source on the sampling devices control panel and start again.

1.3: DAC sample rate

@@ -63,19 +63,19 @@ Use this slider to adjust LO correction in ppm. It can be varied from -20.0 to 2 This button opens a dialog to set the transverter mode frequency translation options: -![SDR Daemon source input stream trasverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) +![SDR Daemon source input stream transverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) Note that if you mouse over the button a tooltip appears that displays the translating frequency and if translation is enabled or disabled. When the frequency translation is enabled the button is lit.

2a.1: Translating frequency

-You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. The frequency set in the device is the frequency on the main dial (1) minus this frequency. Thus it is positive for up converters and negative for down converters. -For example with a mixer at 120 MHz for HF operation you would set the value to -120,000,000 Hz so that if the main dial frequency is set to 7,130 kHz the PuotSDR will be set to 127.130 MHz. +For example with a mixer at 120 MHz for HF operation you would set the value to -120,000,000 Hz so that if the main dial frequency is set to 7,130 kHz the PlutoSDR will be set to 127.130 MHz. -If you use an up converter to transmit at the 6 cm band narrowband center frequency of 5670 MHz aith the PlutoSDR set at 432 MHz you would set the translating frequency to 5760 - 432 = 5328 MHz thus dial +5,328,000,000 Hz. +If you use an up converter to transmit at the 6 cm band narrowband center frequency of 5670 MHz with the PlutoSDR set at 432 MHz you would set the translating frequency to 5760 - 432 = 5328 MHz thus dial +5,328,000,000 Hz. For bands even higher in the frequency spectrum the GHz digits are not really significant so you can have them set at 1 GHz. Thus to transmit at the 10368 MHz frequency with 432 MHz for the PlutoSDR you would set the translating frequency to 1368 - 432 = 936 MHz. Note that in this case the frequency of the LO used in the mixer of the transverter is set at 9936 MHz. @@ -101,7 +101,7 @@ The AD9363 has many port options however as only the A output is connected you s This is the AD9363 device to/from host stream sample rate in S/s. It is the same for the Rx and Tx systems. -Use the wheels to adjust the sample rate. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. +Use the wheels to adjust the sample rate. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. The minimum sample rate depends on the hardware FIR decimation factor (9) and is the following: @@ -113,7 +113,7 @@ The maximum sample rate is fixed and set to 20 MS/s

6: Tx analog filter bandwidth

-This is the Tx analog filter bandwidth in kHz in the AD9363 device. It can be varied from 625 kHz to 16 MHz in 1 kHz steps. Use the wheels to adjust the value. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +This is the Tx analog filter bandwidth in kHz in the AD9363 device. It can be varied from 625 kHz to 16 MHz in 1 kHz steps. Use the wheels to adjust the value. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

7: Hardware FIR filter toggle

@@ -123,11 +123,11 @@ The FIR filter settings are the same on Rx and Tx side therefore any change here

8: Hardware FIR filter bandwidth

-Use the wheels to adjust the bandwidth of the hardware FIR filter. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the bandwidth of the hardware FIR filter. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. The filter limits are calculated as 0.05 and 0.9 times the FIR filter input frequency for the lower and higher limit respectively. The FIR filter input frequency is the baseband sample rate (5) multiplied by the FIR interpolation factor (9) -For bandwidths greater than 0.2 times the FIR filter input frequency the filter is calculated as a windowed FIR filter with a Blackman-Harris window. This has a high out of band rejection value at the expense of a slightly smoother roll off compared to other filters. The bandwidth value sets the -6 dB point approxomately. +For bandwidths greater than 0.2 times the FIR filter input frequency the filter is calculated as a windowed FIR filter with a Blackman-Harris window. This has a high out of band rejection value at the expense of a slightly smoother roll off compared to other filters. The bandwidth value sets the -6 dB point approximately. For bandwidths between 0.05 and 0.2 times the FIR filter input frequency the window used is a Hamming window giving a sharper transition. @@ -149,4 +149,4 @@ This is the indicative RSSI of the transmitter. It works only when the Rx is in

13: Board temperature

-This is the board temperature in degrees Celsius updated every ~5s. \ No newline at end of file +This is the board temperature in degrees Celsius updated every ~5s. diff --git a/plugins/samplesink/sdrdaemonsink/CMakeLists.txt b/plugins/samplesink/sdrdaemonsink/CMakeLists.txt index a18319650..af5f6031f 100644 --- a/plugins/samplesink/sdrdaemonsink/CMakeLists.txt +++ b/plugins/samplesink/sdrdaemonsink/CMakeLists.txt @@ -18,7 +18,7 @@ set(sdrdaemonsink_SOURCES sdrdaemonsinksettings.cpp sdrdaemonsinkthread.cpp udpsinkfec.cpp - UDPSocket.cpp + udpsinkfecworker.cpp ) set(sdrdaemonsink_HEADERS @@ -28,7 +28,7 @@ set(sdrdaemonsink_HEADERS sdrdaemonsinksettings.h sdrdaemonsinkthread.h udpsinkfec.h - UDPSocket.h + udpsinkfecworker.h ) set(sdrdaemonsink_FORMS @@ -41,7 +41,6 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${LIBCM256CCSRC} - ${LIBNANOMSG_INCLUDE_DIR} ) else (BUILD_DEBIAN) include_directories( @@ -50,7 +49,6 @@ include_directories( ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${CMAKE_SOURCE_DIR}/devices ${CM256CC_INCLUDE_DIR} - ${LIBNANOMSG_INCLUDE_DIR} ) endif (BUILD_DEBIAN) @@ -73,7 +71,6 @@ target_link_libraries(outputsdrdaemonsink sdrgui swagger cm256cc - ${LIBNANOMSG_LIBRARIES} ) else (BUILD_DEBIAN) target_link_libraries(outputsdrdaemonsink @@ -82,10 +79,9 @@ target_link_libraries(outputsdrdaemonsink sdrgui swagger ${CM256CC_LIBRARIES} - ${LIBNANOMSG_LIBRARIES} ) endif (BUILD_DEBIAN) -qt5_use_modules(outputsdrdaemonsink Core Widgets) +target_link_libraries(outputsdrdaemonsink Qt5::Core Qt5::Widgets) install(TARGETS outputsdrdaemonsink DESTINATION lib/plugins/samplesink) diff --git a/plugins/samplesink/sdrdaemonsink/UDPSocket.cpp b/plugins/samplesink/sdrdaemonsink/UDPSocket.cpp index fe111cbef..dabb54e17 100644 --- a/plugins/samplesink/sdrdaemonsink/UDPSocket.cpp +++ b/plugins/samplesink/sdrdaemonsink/UDPSocket.cpp @@ -121,7 +121,6 @@ void CSocket::BindLocalAddressAndPort( const string &localAddress, unsigned shor void CSocket::FillAddr( const string & localAddress, unsigned short localPort, sockaddr_in& localAddr ) { - ////cout<<"\n Inside Fille addr:"<h_addr_list[0]); localAddr.sin_port = htons(localPort); // Assign port in network byte order - ////cout<<"\n returning from Fille addr"; } unsigned long int CSocket::GetReadBufferSize() @@ -175,19 +173,14 @@ void CSocket::SetNonBlocking( bool bBlocking ) void CSocket::ConnectToHost( const string &foreignAddress, unsigned short foreignPort ) { - //cout<<"\nstart Connect to host"; // Get the address of the requested host sockaddr_in destAddr; - //cout<<"\ninside Connect to host"; FillAddr(foreignAddress, foreignPort, destAddr); - //cout<<"trying to connect to host"; // Try to connect to the given port if (::connect(m_sockDesc, (sockaddr *) &destAddr, sizeof(destAddr)) < 0) { throw CSocketException("Connect failed (connect())", true); } - //cout<<"\n after connecting"; - } void CSocket::Send( const void *buffer, int bufferLen ) @@ -249,7 +242,6 @@ int CSocket::OnDataRead(unsigned long timeToWait) { /* master file descriptor list */ fd_set master; - //struct timeval *ptimeout = NULL; /* temp file descriptor list for select() */ fd_set read_fds; @@ -268,8 +260,8 @@ int CSocket::OnDataRead(unsigned long timeToWait) /* copy it */ read_fds = master; - //cout<<"Waiting for select"; int nRet; + if (timeToWait == ULONG_MAX) { nRet = select(fdmax+1, &read_fds, NULL, NULL, NULL); @@ -300,13 +292,6 @@ void CSocket::SetBindToDevice( const string& sInterface ) struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", sInterface.c_str()); - //Todo:SO_BINDTODEVICE not declared error comes in CygWin, need to compile in Linux. - /*int nRet = ::setsockopt(m_sockDesc, SOL_SOCKET, SO_BINDTODEVICE, (void*)&ifr, sizeof(ifr)); - - if (nRet < 0) - { - throw CSocketException("Error in binding to device ", true); - }*/ } UDPSocket::UDPSocket():CSocket(UdpSocket,IPv4Protocol) @@ -346,10 +331,8 @@ void UDPSocket::DisconnectFromHost() void UDPSocket::SendDataGram( const void *buffer, int bufferLen, const string &foreignAddress, unsigned short foreignPort ) { - //cout<<"Befor Fill addr"; sockaddr_in destAddr; FillAddr(foreignAddress, foreignPort, destAddr); - //cout<<"Befor socket send"; // Write out the whole buffer as a single message. if (sendto(m_sockDesc, (void *) buffer, bufferLen, 0,(sockaddr *) &destAddr, sizeof(destAddr)) != bufferLen) { diff --git a/plugins/samplesink/sdrdaemonsink/readme.md b/plugins/samplesink/sdrdaemonsink/readme.md index 9f1d0aea9..e8236643a 100644 --- a/plugins/samplesink/sdrdaemonsink/readme.md +++ b/plugins/samplesink/sdrdaemonsink/readme.md @@ -2,13 +2,19 @@

Introduction

-This output sample sink plugin sends its samples over tbe network to a SDRdaemon transmitter server using UDP connection. SDRdaemon refers to the SDRdaemon utility `sdrdaemontx`found in [this](https://github.com/f4exb/sdrdaemon) Github repostory. +This output sample sink plugin sends its samples over tbe network to a SDRangel instance's Daemon source channel using UDP connection. Forward Error Correction with a Cauchy MDS block erasure codec is used to prevent block loss. This can make the UDP transmission more robust particularly over WiFi links. +The distant SDRangel instance to which the data stream is sent is controlled via its REST API using a separate control software for example [SDRangelcli](https://github.com/f4exb/sdrangelcli) + +The sample size used in the I/Q stream is the Rx sample size of the local instance. Possible conversion takes place in the distant Daemon source channel plugin to match the Rx sample size of the distant instance. Best performace is obtained when both instances use the same sample size. + +It is present only in Linux binary releases. +

Build

-The plugin will be built only if `libnanomsg` and the [CM256cc library](https://github.com/f4exb/cm256cc) is installed in your system. `libnanomasg` is present in most distributions and the dev version can be installed using the package manager. For CM256cc library you will have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_INCLUDE_DIR=/opt/install/cm256cc/include/cm256cc -DCM256CC_LIBRARIES=/opt/install/cm256cc/lib/libcm256cc.so` to the cmake commands. +The plugin will be built only if the [CM256cc library](https://github.com/f4exb/cm256cc) is installed in your system. For CM256cc library you will have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_INCLUDE_DIR=/opt/install/cm256cc/include/cm256cc -DCM256CC_LIBRARIES=/opt/install/cm256cc/lib/libcm256cc.so` to the cmake commands.

Interface

@@ -23,25 +29,25 @@ Device start / stop button.

2: Stream sample rate

-Stream I/Q sample rate in kS/s over the network. +I/Q sample rate in kS/s of the stream that is sent over the network.

3: Frequency

-This is the center frequency in kHz sent to the remote device. +This is the center frequency in kHz of the remote instance device. -

4: Sample rate

+

4: Remote baseband sample rate

-![SDR Daemon sink output sample rate GUI](../../../doc/img/SDRdaemonSink_plugin_04.png) +This is the remote instance baseband sample rate. It can be a power of two multiple of the stream sample rate (2) but it will not work for other values. -

4.1: Network stream sample rate

+

5: Stream controls and API destination

+ +![SDR Daemon sink output sample rate GUI](../../../doc/img/SDRdaemonSink_plugin_05.png) + +

5.1: Network stream sample rate

This is the I/Q stream sample rate transmitted via UDP over the network -

4.2: Remote interpolation factor

- -This is the interpolation set in the remote `sdrdaemontx` server instance. - -

5: Delay between UDP blocks transmission

+

5.2: Delay between UDP blocks transmission

This sets the minimum delay between transmission of an UDP block (send datagram) and the next. This allows throttling of the UDP transmission that is otherwise uncontrolled and causes network congestion. @@ -50,9 +56,17 @@ The value is a percentage of the nominal time it takes to process a block of sam - Sample rate on the network: _SR_ - Delay percentage: _d_ - Number of FEC blocks: _F_ - - There are 127 blocks of I/Q data per frame (1 meta block for 128 blocks) and each I/Q data block of 512 bytes (128 samples) has a 4 bytes header (1 sample) thus there are 127 samples remaining effectively. This gives the constant 127*127 = 16219 samples per frame in the formula + - There are 127 blocks of I/Q data per frame (1 meta block for 128 blocks) and each I/Q data block of 512 bytes (128 samples) has a 8 bytes header (2 samples) thus there are 126 samples remaining effectively. This gives the constant 127*126 = 16002 samples per frame in the formula -Formula: ((127 ✕ 127 ✕ _d_) / _SR_) / (128 + _F_) +Formula: ((127 ✕ 126 ✕ _d_) / _SR_) / (128 + _F_) + +

5.3: remote instance device set index

+ +This is the device set index in the remote instance to which the stream is connected to. Use this value to properly address the API to get status. + +

5.4: remote instance channel index

+ +This is the channel index of the Daemon source in the remote instance to which the stream is connected to. Use this value to properly address the API to get status.

6: Forward Error Correction setting and status

@@ -62,65 +76,83 @@ Formula: ((127 ✕ 127 ✕ _d_) / _SR_) / (128 + _F_) This sets the number of FEC blocks per frame. A frame consists of 128 data blocks (1 meta data block followed by 127 I/Q data blocks) and a variable number of FEC blocks used to protect the UDP transmission with a Cauchy MDS block erasure correction. The two numbers next are the total number of blocks and the number of FEC blocks separated by a slash (/). -

6.2: Distant transmitter queue length

- -This is the samples queue length reported from the distant transmitter. This is a numnber of vectors of 127 ✕ 127 ✕ _I_ samples where _I_ is the interpolation factor. This corresponds to a block of 127 ✕ 127 samples sent over the network. This numbers serves to thottle the sample generator so that the queue length is close to 8 vectors. - -

6.3: Stream status

+

6.2: Stream status

The color of the icon indicates stream status: - Green: all original blocks have been received for all frames during the last polling timeframe (ex: 134) - No color: some original blocks were reconstructed from FEC blocks for some frames during the last polling timeframe (ex: between 128 and 133) - Red: some original blocks were definitely lost for some frames during the last polling timeframe (ex: less than 128) + - Blue: stream is idle -

6.4: Frames recovery status

+

6.3: Remote stream rate

-These are two numbers separated by a slash (/): +This is the remote stream rate calculated from the samples counter between two consecutive API polls. It is normal for it to oscillate moderately around the nominal stream rate (2). - - first: minimum total number of blocks per frame during the last polling period. If all blocks were received for all frames then this number is the nominal number of original blocks plus FEC blocks (Green lock icon). In our example this is 128+6 = 134. - - second: maximum number of FEC blocks used for original blocks recovery during the last polling timeframe. Ideally this should be 0 when no blocks are lost but the system is able to correct lost blocks up to the nominal number of FEC blocks (Neutral lock icon). - -

6.5: Reset events counters

+

6.4: Reset events counters

This push button can be used to reset the events counters (6.6 and 6.7) and reset the event counts timer (6.8) -

6.6: Unrecoverable error events counter

+

6.5: Unrecoverable error events counter

This counter counts the unrecoverable error conditions found (i.e. 6.4 lower than 128) since the last counters reset. -

6.7: Recoverable error events counter

+

6.6: Recoverable error events counter

This counter counts the unrecoverable error conditions found (i.e. 6.4 between 128 and 128 plus the number of FEC blocks) since the last counters reset. -

6.8: events counters timer

+

6.7: events counters timer

-This hh:mm:ss time display shows the time since the reset evetnts counters button (4.6) was pushed. +This HH:mm:ss time display shows the time since the reset events counters button (4.6) was pushed. -

7: Network parameters

+

7: Distant transmitter queue length gauge

-![SDR Daemon sink output network GUI](../../../doc/img/SDRdaemonSink_plugin_07.png) +This is ratio of the reported number of data frame blocks in the remote queue over the total number of blocks in the queue. -

7.1: Distant interface IP address

+

8: Distant transmitter queue length status

-Address of the network interface on the distance (server) machine where the SDRdaemon Tx server runs and receives samples. +This is the detail of the ratio shown in the gauge. Each frame block is a block of 127 ✕ 126 samples (16 bit I or Q samples) or 127 ✕ 63 samples (24 bit I or Q samples). -

7.2: Distant data port

+

9: Distant server API address and port

-UDP port on the distant (server) machine where the SDRdaemon Tx server runs and receives samples. +![SDR Daemon source input stream GUI](../../../doc/img/SDRdaemonSource_plugin_05.png) -

7.3 Distant configuration port

+

9.1: API connection indicator

-TCP port on the distant machine hosting the SDRdaemon Tx instance to send control messages to and receive status messages from. +The "API" label is lit in green when the connection is successful -

7.4: Validation button

+

9.2: API IP address

-When the return key is hit within the address (7.1), data port (7.2) or configuration port (7.3) boxes the changes are effective immediately. You can also use this button to set again these values. +IP address of the distant SDRangel instance REST API -

8: Other parameters hardware specific

+

9.3: API port

-These are the parameters that are specific to the hardware attached to the distant SDRdaemon instance. You have to know which device is attached to send the proper parameters. Please refer to the SDRdaemon documentation or its line help to get information on these parameters. +Port of the distant SDRangel instance REST API -

9: Send data to the distant SDRdaemon Rx instance

+

9.4: Validation button

-When any of the parameters change they get immediately transmitted to the distant server over the TCP link. You can however use this button to send again the complete configuration. This is handy if for some reason you are unsure of the parameters set in the distant server. +When the return key is hit within the address (9.2) or port (9.3) the changes are effective immediately. You can also use this button to set again these values. Clicking on this button will send a request to the API to get the distant SDRangel instance information that is displayed in the API message box (8) + +

10: Local data address and port

+ +![SDR Daemon source input stream GUI](../../../doc/img/SDRdaemonSource_plugin_06.png) + +

10.1: Data IP address

+ +IP address of the local network interface the distant SDRangel instance sends the data to + +

10.2: Data port

+ +Local port the distant SDRangel instance sends the data to + +

10.3: Validation button

+ +When the return key is hit within the address (10.2) or port (10.3) the changes are effective immediately. You can also use this button to set again these values. + +

11: Status message

+ +The API status is displayed in this box. It shows "API OK" when the connection is successful and reply is OK + +

12: API information

+ +This is the information returned by the API and is the distance SDRangel instance information if transaction is successful diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsink.pro b/plugins/samplesink/sdrdaemonsink/sdrdaemonsink.pro new file mode 100644 index 000000000..51a2d307a --- /dev/null +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsink.pro @@ -0,0 +1,67 @@ +#-------------------------------------------------------- +# +# Pro file for Windows builds with Qt Creator +# +#-------------------------------------------------------- + +TEMPLATE = lib +CONFIG += plugin + +QT += core gui widgets multimedia network opengl + +TARGET = outputsdrdaemonsink + +CONFIG(MINGW32):LIBCM256CCSRC = "C:\softs\cm256cc" +CONFIG(MINGW64):LIBCM256CCSRC = "C:\softs\cm256cc" +CONFIG(macx):LIBCM256CCSRC = "../../../../deps/cm256cc" + +INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports +INCLUDEPATH += ../../../sdrbase +INCLUDEPATH += ../../../sdrgui +INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client +macx:INCLUDEPATH += /opt/local/include +INCLUDEPATH += $$LIBCM256CCSRC + +DEFINES += USE_SSE2=1 +QMAKE_CXXFLAGS += -msse2 +DEFINES += USE_SSSE3=1 +QMAKE_CXXFLAGS += -mssse3 +DEFINES += USE_SSE4_1=1 +QMAKE_CXXFLAGS += -msse4.1 +QMAKE_CXXFLAGS += -std=c++11 + +CONFIG(Release):build_subdir = release +CONFIG(Debug):build_subdir = debug + +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\boost_1_66_0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\boost_1_66_0" +CONFIG(macx):INCLUDEPATH += "../../../boost_1_64_0" + +SOURCES += sdrdaemonsinkthread.cpp\ +sdrdaemonsinkgui.cpp\ +sdrdaemonsinkoutput.cpp\ +sdrdaemonsinksettings.cpp\ +sdrdaemonsinkplugin.cpp\ +udpsinkfec.cpp\ +udpsinkfecworker.cpp + +HEADERS += sdrdaemonsinkthread.h\ +sdrdaemonsinkgui.h\ +sdrdaemonsinkoutput.h\ +sdrdaemonsinksettings.h\ +sdrdaemonsinkplugin.h\ +udpsinkfec.h\ +udpsinkfecworker.h + +FORMS += sdrdaemonsinkgui.ui + +LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase +LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui +LIBS += -L../../../swagger/$${build_subdir} -lswagger +LIBS += -L../../../cm256cc/$${build_subdir} -lcm256cc + +RESOURCES = ../../../sdrgui/resources/res.qrc + +CONFIG(MINGW32):DEFINES += USE_INTERNAL_TIMER=1 + diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp index 3e844d322..c2607530f 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.cpp @@ -19,15 +19,14 @@ #include #include #include -#include #include +#include +#include +#include #include #include -#include -#include - #include "ui_sdrdaemonsinkgui.h" #include "plugin/pluginapi.h" #include "gui/colormapper.h" @@ -39,6 +38,8 @@ #include "device/devicesinkapi.h" #include "device/deviceuiset.h" +#include "channel/sdrdaemondatablock.h" +#include "udpsinkfec.h" #include "sdrdaemonsinkgui.h" SDRdaemonSinkGui::SDRdaemonSinkGui(DeviceUISet *deviceUISet, QWidget* parent) : @@ -47,25 +48,20 @@ SDRdaemonSinkGui::SDRdaemonSinkGui(DeviceUISet *deviceUISet, QWidget* parent) : m_deviceUISet(deviceUISet), m_settings(), m_deviceSampleSink(0), - m_sampleRate(0), + m_deviceCenterFrequency(0), m_samplesCount(0), m_tickCount(0), m_nbSinceLastFlowCheck(0), - m_lastEngineState((DSPDeviceSinkEngine::State)-1), + m_lastEngineState(DSPDeviceSinkEngine::StNotStarted), m_doApplySettings(true), m_forceSettings(true) { - m_nnSender = nn_socket(AF_SP, NN_PAIR); - assert(m_nnSender != -1); - int millis = 500; - int rc = nn_setsockopt(m_nnSender, NN_SOL_SOCKET, NN_SNDTIMEO, &millis, sizeof (millis)); - - if (rc != 0) { - qCritical("SDRdaemonSinkGui::SDRdaemonSinkGui: nn_setsockopt failed with rc %d", rc); - } - m_countUnrecoverable = 0; m_countRecovered = 0; + m_lastCountUnrecoverable = 0; + m_lastCountRecovered = 0; + m_lastSampleCount = 0; + m_resetCounts = true; m_paletteGreenText.setColor(QPalette::WindowText, Qt::green); m_paletteRedText.setColor(QPalette::WindowText, Qt::red); @@ -79,6 +75,8 @@ SDRdaemonSinkGui::SDRdaemonSinkGui(DeviceUISet *deviceUISet, QWidget* parent) : ui->sampleRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow)); ui->sampleRate->setValueRange(7, 32000U, 9000000U); + ui->apiAddressLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }"); + connect(&(m_deviceUISet->m_deviceSinkAPI->getMasterTimer()), SIGNAL(timeout()), this, SLOT(tick())); connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); @@ -88,17 +86,23 @@ SDRdaemonSinkGui::SDRdaemonSinkGui(DeviceUISet *deviceUISet, QWidget* parent) : connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_networkManager = new QNetworkAccessManager(); + connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); + + m_deviceUISet->getSpectrum()->setCenterFrequency(m_deviceCenterFrequency); + m_time.start(); displayEventCounts(); displayEventTimer(); displaySettings(); - sendControl(true); sendSettings(); } SDRdaemonSinkGui::~SDRdaemonSinkGui() { + disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); + delete m_networkManager; delete ui; } @@ -131,19 +135,6 @@ void SDRdaemonSinkGui::resetToDefaults() sendSettings(); } -qint64 SDRdaemonSinkGui::getCenterFrequency() const -{ - return m_settings.m_centerFrequency; -} - -void SDRdaemonSinkGui::setCenterFrequency(qint64 centerFrequency) -{ - m_settings.m_centerFrequency = centerFrequency; - displaySettings(); - sendControl(); - sendSettings(); -} - QByteArray SDRdaemonSinkGui::serialize() const { return m_settings.serialize(); @@ -157,7 +148,6 @@ bool SDRdaemonSinkGui::deserialize(const QByteArray& data) { displaySettings(); blockApplySettings(false); - sendControl(true); m_forceSettings = true; sendSettings(); return true; @@ -180,12 +170,6 @@ bool SDRdaemonSinkGui::handleMessage(const Message& message) blockApplySettings(false); return true; } - else if (SDRdaemonSinkOutput::MsgReportSDRdaemonSinkStreamTiming::match(message)) - { - m_samplesCount = ((SDRdaemonSinkOutput::MsgReportSDRdaemonSinkStreamTiming&)message).getSamplesCount(); - updateWithStreamTime(); - return true; - } else if (SDRdaemonSinkOutput::MsgStartStop::match(message)) { SDRdaemonSinkOutput::MsgStartStop& notif = (SDRdaemonSinkOutput::MsgStartStop&) message; @@ -206,15 +190,12 @@ void SDRdaemonSinkGui::handleInputMessages() while ((message = m_inputMessageQueue.pop()) != 0) { - qDebug("SDRdaemonSinkGui::handleInputMessages: message: %s", message->getIdentifier()); - if (DSPSignalNotification::match(*message)) { DSPSignalNotification* notif = (DSPSignalNotification*) message; - qDebug("SDRdaemonSinkGui::handleInputMessages: DSPSignalNotification: SampleRate:%d, CenterFrequency:%llu", notif->getSampleRate(), notif->getCenterFrequency()); m_sampleRate = notif->getSampleRate(); - m_deviceCenterFrequency = notif->getCenterFrequency(); - updateSampleRateAndFrequency(); + qDebug("SDRdaemonSinkGui::handleInputMessages: DSPSignalNotification: SampleRate:%d, CenterFrequency:%llu", notif->getSampleRate(), notif->getCenterFrequency()); + updateSampleRate(); delete message; } @@ -227,25 +208,24 @@ void SDRdaemonSinkGui::handleInputMessages() } } -void SDRdaemonSinkGui::updateSampleRateAndFrequency() +void SDRdaemonSinkGui::updateSampleRate() { m_deviceUISet->getSpectrum()->setSampleRate(m_sampleRate); - m_deviceUISet->getSpectrum()->setCenterFrequency(m_deviceCenterFrequency); - ui->deviceRateText->setText(tr("%1k").arg((float)(m_sampleRate*(1<deviceRateText->setText(tr("%1k").arg((float)(m_sampleRate) / 1000)); } void SDRdaemonSinkGui::updateTxDelayTooltip() { - double delay = ((127*127*m_settings.m_txDelay) / m_settings.m_sampleRate)/(128 + m_settings.m_nbFECBlocks); + int samplesPerBlock = SDRDaemonNbBytesPerBlock / (SDR_RX_SAMP_SZ <= 16 ? 4 : 8); + double delay = ((127*samplesPerBlock*m_settings.m_txDelay) / m_settings.m_sampleRate)/(128 + m_settings.m_nbFECBlocks); ui->txDelayText->setToolTip(tr("%1 us").arg(QString::number(delay*1e6, 'f', 0))); } void SDRdaemonSinkGui::displaySettings() { - ui->centerFrequency->setValue(m_settings.m_centerFrequency / 1000); + blockApplySettings(true); + ui->centerFrequency->setValue(m_deviceCenterFrequency / 1000); ui->sampleRate->setValue(m_settings.m_sampleRate); - ui->deviceRateText->setText(tr("%1k").arg((float)(m_sampleRate*(1<interp->setCurrentIndex(m_settings.m_log2Interp); ui->txDelay->setValue(m_settings.m_txDelay*100); ui->txDelayText->setText(tr("%1").arg(m_settings.m_txDelay*100)); ui->nbFECBlocks->setValue(m_settings.m_nbFECBlocks); @@ -254,100 +234,13 @@ void SDRdaemonSinkGui::displaySettings() QString s1 = QString::number(m_settings.m_nbFECBlocks, 'f', 0); ui->nominalNbBlocksText->setText(tr("%1/%2").arg(s0).arg(s1)); - ui->address->setText(m_settings.m_address); + ui->deviceIndex->setText(tr("%1").arg(m_settings.m_deviceIndex)); + ui->channelIndex->setText(tr("%1").arg(m_settings.m_channelIndex)); + ui->apiAddress->setText(m_settings.m_apiAddress); + ui->apiPort->setText(tr("%1").arg(m_settings.m_apiPort)); + ui->dataAddress->setText(m_settings.m_dataAddress); ui->dataPort->setText(tr("%1").arg(m_settings.m_dataPort)); - ui->controlPort->setText(tr("%1").arg(m_settings.m_controlPort)); - ui->specificParms->setText(m_settings.m_specificParameters); -} - -void SDRdaemonSinkGui::sendControl(bool force) -{ - if ((m_settings.m_address != m_controlSettings.m_address) || - (m_settings.m_controlPort != m_controlSettings.m_controlPort) || force) - { - int rc = nn_shutdown(m_nnSender, 0); - - if (rc < 0) { - qDebug() << "SDRdaemonSinkGui::sendControl: disconnection failed"; - } else { - qDebug() << "SDRdaemonSinkGui::sendControl: disconnection successful"; - } - - std::ostringstream os; - os << "tcp://" << m_settings.m_address.toStdString() << ":" << m_settings.m_controlPort; - std::string addrstrng = os.str(); - rc = nn_connect(m_nnSender, addrstrng.c_str()); - - if (rc < 0) - { - qDebug() << "SDRdaemonSinkGui::sendControl: connection to " << addrstrng.c_str() << " failed"; - QMessageBox::information(this, tr("Message"), tr("Cannot connect to remote control port")); - return; - } - else - { - qDebug() << "SDRdaemonSinkGui::sendControl: connection to " << addrstrng.c_str() << " successful"; - force = true; - } - } - - std::ostringstream os; - int nbArgs = 0; - - if ((m_settings.m_centerFrequency != m_controlSettings.m_centerFrequency) || force) - { - os << "freq=" << m_settings.m_centerFrequency; - nbArgs++; - } - - if ((m_settings.m_sampleRate != m_controlSettings.m_sampleRate) || (m_settings.m_log2Interp != m_controlSettings.m_log2Interp) || force) - { - if (nbArgs > 0) os << ","; - os << "srate=" << m_settings.m_sampleRate * (1< 0) - { - int config_size = os.str().size(); - int rc = nn_send(m_nnSender, (void *) os.str().c_str(), config_size, 0); - - if (rc != config_size) - { - //QMessageBox::information(this, tr("Message"), tr("Cannot send message to remote control port")); - qDebug() << "SDRdaemonSinkGui::sendControl: Cannot send message to remote control port." - << " remoteAddress: " << m_settings.m_address - << " remotePort: " << m_settings.m_controlPort - << " message: " << os.str().c_str(); - } - else - { - qDebug() << "SDRdaemonSinkGui::sendControl:" - << "remoteAddress:" << m_settings.m_address - << "remotePort:" << m_settings.m_controlPort - << "message:" << os.str().c_str(); - } - } - - m_controlSettings.m_address = m_settings.m_address; - m_controlSettings.m_controlPort = m_settings.m_controlPort; - m_controlSettings.m_centerFrequency = m_settings.m_centerFrequency; - m_controlSettings.m_sampleRate = m_settings.m_sampleRate; - m_controlSettings.m_log2Interp = m_settings.m_log2Interp; - m_controlSettings.m_specificParameters = m_settings.m_specificParameters; + blockApplySettings(false); } void SDRdaemonSinkGui::sendSettings() @@ -395,32 +288,13 @@ void SDRdaemonSinkGui::updateStatus() } } -void SDRdaemonSinkGui::on_centerFrequency_changed(quint64 value) -{ - m_settings.m_centerFrequency = value * 1000; - sendControl(); - sendSettings(); -} - void SDRdaemonSinkGui::on_sampleRate_changed(quint64 value) { m_settings.m_sampleRate = value; updateTxDelayTooltip(); - sendControl(); sendSettings(); } -void SDRdaemonSinkGui::on_interp_currentIndexChanged(int index) -{ - if (index < 0) { - return; - } - - m_settings.m_log2Interp = index; - updateSampleRateAndFrequency(); - sendControl(); -} - void SDRdaemonSinkGui::on_txDelay_valueChanged(int value) { m_settings.m_txDelay = value / 100.0; @@ -441,64 +315,104 @@ void SDRdaemonSinkGui::on_nbFECBlocks_valueChanged(int value) sendSettings(); } -void SDRdaemonSinkGui::on_address_returnPressed() +void SDRdaemonSinkGui::on_deviceIndex_returnPressed() { - m_settings.m_address = ui->address->text(); - sendControl(); + bool dataOk; + int deviceIndex = ui->deviceIndex->text().toInt(&dataOk); + + if ((!dataOk) || (deviceIndex < 0)) { + return; + } else { + m_settings.m_deviceIndex = deviceIndex; + } + + sendSettings(); +} + +void SDRdaemonSinkGui::on_channelIndex_returnPressed() +{ + bool dataOk; + int channelIndex = ui->channelIndex->text().toInt(&dataOk); + + if ((!dataOk) || (channelIndex < 0)) { + return; + } else { + m_settings.m_channelIndex = channelIndex; + } + + sendSettings(); +} + +void SDRdaemonSinkGui::on_apiAddress_returnPressed() +{ + m_settings.m_apiAddress = ui->apiAddress->text(); + sendSettings(); + + QString infoURL = QString("http://%1:%2/sdrangel").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort); + m_networkRequest.setUrl(QUrl(infoURL)); + m_networkManager->get(m_networkRequest); +} + +void SDRdaemonSinkGui::on_apiPort_returnPressed() +{ + bool dataOk; + int apiPort = ui->apiPort->text().toInt(&dataOk); + + if((!dataOk) || (apiPort < 1024) || (apiPort > 65535)) { + return; + } else { + m_settings.m_apiPort = apiPort; + } + + sendSettings(); + + QString infoURL = QString("http://%1:%2/sdrangel").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort); + m_networkRequest.setUrl(QUrl(infoURL)); + m_networkManager->get(m_networkRequest); +} + +void SDRdaemonSinkGui::on_dataAddress_returnPressed() +{ + m_settings.m_dataAddress = ui->dataAddress->text(); sendSettings(); } void SDRdaemonSinkGui::on_dataPort_returnPressed() { bool dataOk; - int udpDataPort = ui->dataPort->text().toInt(&dataOk); + int dataPort = ui->dataPort->text().toInt(&dataOk); - if((!dataOk) || (udpDataPort < 1024) || (udpDataPort > 65535)) - { + if((!dataOk) || (dataPort < 1024) || (dataPort > 65535)) { return; - } - else - { - m_settings.m_dataPort = udpDataPort; + } else { + m_settings.m_dataPort = dataPort; } sendSettings(); } -void SDRdaemonSinkGui::on_controlPort_returnPressed() +void SDRdaemonSinkGui::on_apiApplyButton_clicked(bool checked __attribute__((unused))) { - bool ctlOk; - int udpCtlPort = ui->controlPort->text().toInt(&ctlOk); + m_settings.m_apiAddress = ui->apiAddress->text(); - if((!ctlOk) || (udpCtlPort < 1024) || (udpCtlPort > 65535)) + bool apiOk; + int apiPort = ui->apiPort->text().toInt(&apiOk); + + if((apiOk) && (apiPort >= 1024) && (apiPort < 65535)) { - return; - } - else - { - m_settings.m_controlPort = udpCtlPort; + m_settings.m_apiPort = apiPort; } - sendControl(); + sendSettings(); + + QString infoURL = QString("http://%1:%2/sdrangel").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort); + m_networkRequest.setUrl(QUrl(infoURL)); + m_networkManager->get(m_networkRequest); } -void SDRdaemonSinkGui::on_specificParms_returnPressed() +void SDRdaemonSinkGui::on_dataApplyButton_clicked(bool checked __attribute__((unused))) { - m_settings.m_specificParameters = ui->specificParms->text(); - sendControl(); -} - -void SDRdaemonSinkGui::on_applyButton_clicked(bool checked __attribute__((unused))) -{ - m_settings.m_address = ui->address->text(); - - bool ctlOk; - int udpCtlPort = ui->controlPort->text().toInt(&ctlOk); - - if((ctlOk) && (udpCtlPort >= 1024) && (udpCtlPort < 65535)) - { - m_settings.m_controlPort = udpCtlPort; - } + m_settings.m_dataAddress = ui->dataAddress->text(); bool dataOk; int udpDataPort = ui->dataPort->text().toInt(&dataOk); @@ -507,11 +421,8 @@ void SDRdaemonSinkGui::on_applyButton_clicked(bool checked __attribute__((unused { m_settings.m_dataPort = udpDataPort; } -} -void SDRdaemonSinkGui::on_sendButton_clicked(bool checked __attribute__((unused))) -{ - sendControl(true); + sendSettings(); } void SDRdaemonSinkGui::on_startStop_toggled(bool checked) @@ -540,135 +451,173 @@ void SDRdaemonSinkGui::displayEventCounts() ui->eventRecText->setText(nstr); } +void SDRdaemonSinkGui::displayEventStatus(int recoverableCount, int unrecoverableCount) +{ + + if (unrecoverableCount == 0) + { + if (recoverableCount == 0) { + ui->allFramesDecoded->setStyleSheet("QToolButton { background-color : green; }"); + } else { + ui->allFramesDecoded->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + } + } + else + { + ui->allFramesDecoded->setStyleSheet("QToolButton { background-color : red; }"); + } +} + void SDRdaemonSinkGui::displayEventTimer() { int elapsedTimeMillis = m_time.elapsed(); QTime recordLength(0, 0, 0, 0); recordLength = recordLength.addSecs(elapsedTimeMillis/1000); - QString s_time = recordLength.toString("hh:mm:ss"); + QString s_time = recordLength.toString("HH:mm:ss"); ui->eventCountsTimeText->setText(s_time); } -void SDRdaemonSinkGui::updateWithStreamTime() -{ - int t_sec = 0; - int t_msec = 0; - - if (m_settings.m_sampleRate > 0){ - t_msec = ((m_samplesCount * 1000) / m_settings.m_sampleRate) % 1000; - t_sec = m_samplesCount / m_settings.m_sampleRate; - } - - QTime t(0, 0, 0, 0); - t = t.addSecs(t_sec); - t = t.addMSecs(t_msec); - QString s_timems = t.toString("hh:mm:ss.zzz"); - //ui->relTimeText->setText(s_timems); TODO with absolute time -} - void SDRdaemonSinkGui::tick() { - if ((++m_tickCount & 0xf) == 0) // 16*50ms ~800ms + if (++m_tickCount == 20) // once per second { - void *msgBuf = 0; + QString reportURL; - SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkStreamTiming* message = SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkStreamTiming::create(); - m_deviceSampleSink->getInputMessageQueue()->push(message); - - int len = nn_recv(m_nnSender, &msgBuf, NN_MSG, NN_DONTWAIT); - - if ((len > 0) && msgBuf) - { - std::string msg((char *) msgBuf, len); - std::vector strs; - boost::split(strs, msg, boost::is_any_of(":")); - unsigned int nbTokens = strs.size(); - unsigned int status = 0; - bool updateEventCounts = false; - - if (nbTokens > 0) // at least the queue length is given - { - try - { - int queueLength = boost::lexical_cast(strs[0]); - ui->queueLengthText->setText(QString::fromStdString(strs[0])); - m_nbSinceLastFlowCheck++; - int samplesCorr = 0; - bool quickStart = false; - - if (queueLength < 2) - { - samplesCorr = 127*8; - quickStart = true; - } - else if (queueLength < 8) - { - samplesCorr = ((8 - queueLength)*16)/m_nbSinceLastFlowCheck; - } - else if (queueLength > 8) - { - samplesCorr = ((8 - queueLength)*16)/m_nbSinceLastFlowCheck; - } - else if (queueLength > 16) - { - samplesCorr = -127*16; - quickStart = true; - } - - if (samplesCorr != 0) - { - samplesCorr = quickStart ? samplesCorr : samplesCorr <= -50 ? -50 : samplesCorr >= 50 ? 50 : samplesCorr; - SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkChunkCorrection* message = SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkChunkCorrection::create(samplesCorr); - m_deviceSampleSink->getInputMessageQueue()->push(message); - m_nbSinceLastFlowCheck = 0; - } - } - catch(const boost::bad_lexical_cast &) - { - qDebug("SDRdaemonSinkGui::tick: queue length invalid: %s", strs[0].c_str()); - } - } - - if (nbTokens > 1) // the quality status is given also - { - if (strs[1] == "2") - { - status = 2; - } - else if (strs[1] == "1") - { - status = 1; - if (m_countUnrecoverable < 999) m_countUnrecoverable++; - updateEventCounts = true; - qDebug("SDRdaemonSinkGui::tick: %s", msg.c_str()); - } - else - { - if (m_countRecovered < 999) m_countRecovered++; - updateEventCounts = true; - qDebug("SDRdaemonSinkGui::tick: %s", msg.c_str()); - } - } - - if (nbTokens > 2) // the quality indicator message is given also - { - ui->qualityStatusText->setText(QString::fromStdString(strs[2])); - } - - if (status == 2) { // all OK - ui->allFramesDecoded->setStyleSheet("QToolButton { background-color : green; }"); - } else if (status == 1) { // unrecoverable errors - ui->allFramesDecoded->setStyleSheet("QToolButton { background-color : red; }"); - } else { // recoverable errors or unknown status - ui->allFramesDecoded->setStyleSheet("QToolButton { background:rgb(56,56,56); }"); - } - - if (updateEventCounts) - { - displayEventCounts(); - } - } + reportURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/report") + .arg(m_settings.m_apiAddress) + .arg(m_settings.m_apiPort) + .arg(m_settings.m_deviceIndex) + .arg(m_settings.m_channelIndex); + m_networkRequest.setUrl(QUrl(reportURL)); + m_networkManager->get(m_networkRequest); displayEventTimer(); + + m_tickCount = 0; } } + +void SDRdaemonSinkGui::networkManagerFinished(QNetworkReply *reply) +{ + if (reply->error()) + { + ui->apiAddressLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }"); + ui->statusText->setText(reply->errorString()); + return; + } + + QString answer = reply->readAll(); + + try + { + QByteArray jsonBytes(answer.toStdString().c_str()); + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(jsonBytes, &error); + + if (error.error == QJsonParseError::NoError) + { + ui->apiAddressLabel->setStyleSheet("QLabel { background-color : green; }"); + ui->statusText->setText(QString("API OK")); + analyzeApiReply(doc.object()); + } + else + { + ui->apiAddressLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }"); + QString errorMsg = QString("Reply JSON error: ") + error.errorString() + QString(" at offset ") + QString::number(error.offset); + ui->statusText->setText(QString("JSON error. See log")); + qInfo().noquote() << "SDRdaemonSinkGui::networkManagerFinished" << errorMsg; + } + } + catch (const std::exception& ex) + { + ui->apiAddressLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }"); + QString errorMsg = QString("Error parsing request: ") + ex.what(); + ui->statusText->setText("Error parsing request. See log for details"); + qInfo().noquote() << "SDRdaemonSinkGui::networkManagerFinished" << errorMsg; + } +} + +void SDRdaemonSinkGui::analyzeApiReply(const QJsonObject& jsonObject) +{ + QString infoLine; + + if (jsonObject.contains("DaemonSourceReport")) + { + QJsonObject report = jsonObject["DaemonSourceReport"].toObject(); + m_deviceCenterFrequency = report["deviceCenterFreq"].toInt() * 1000; + m_deviceUISet->getSpectrum()->setCenterFrequency(m_deviceCenterFrequency); + ui->centerFrequency->setValue(m_deviceCenterFrequency/1000); + int remoteRate = report["deviceSampleRate"].toInt(); + ui->remoteRateText->setText(tr("%1k").arg((float)(remoteRate) / 1000)); + int queueSize = report["queueSize"].toInt(); + queueSize = queueSize == 0 ? 10 : queueSize; + int queueLength = report["queueLength"].toInt(); + QString queueLengthText = QString("%1/%2").arg(queueLength).arg(queueSize); + ui->queueLengthText->setText(queueLengthText); + int queueLengthPercent = (queueLength*100)/queueSize; + ui->queueLengthGauge->setValue(queueLengthPercent); + int unrecoverableCount = report["uncorrectableErrorsCount"].toInt(); + int recoverableCount = report["correctableErrorsCount"].toInt(); + uint64_t timestampUs = report["tvSec"].toInt()*1000000ULL + report["tvUSec"].toInt(); + + if (!m_resetCounts) + { + int recoverableCountDelta = recoverableCount - m_lastCountRecovered; + int unrecoverableCountDelta = unrecoverableCount - m_lastCountUnrecoverable; + displayEventStatus(recoverableCountDelta, unrecoverableCountDelta); + m_countRecovered += recoverableCountDelta; + m_countUnrecoverable += unrecoverableCountDelta; + displayEventCounts(); + } + + uint32_t sampleCountDelta, sampleCount; + sampleCount = report["samplesCount"].toInt(); + + if (sampleCount < m_lastSampleCount) { + sampleCountDelta = (0xFFFFFFFFU - m_lastSampleCount) + sampleCount + 1; + } else { + sampleCountDelta = sampleCount - m_lastSampleCount; + } + + if (sampleCountDelta == 0) + { + ui->allFramesDecoded->setStyleSheet("QToolButton { background-color : blue; }"); + } + + double remoteStreamRate = sampleCountDelta*1e6 / (double) (timestampUs - m_lastTimestampUs); + + if (remoteStreamRate != 0) { + ui->remoteStreamRateText->setText(QString("%1").arg(remoteStreamRate, 0, 'f', 0)); + } + + m_resetCounts = false; + m_lastCountRecovered = recoverableCount; + m_lastCountUnrecoverable = unrecoverableCount; + m_lastSampleCount = sampleCount; + m_lastTimestampUs = timestampUs; + } + + if (jsonObject.contains("version")) { + infoLine = "v" + jsonObject["version"].toString(); + } + + if (jsonObject.contains("qtVersion")) { + infoLine += " Qt" + jsonObject["qtVersion"].toString(); + } + + if (jsonObject.contains("architecture")) { + infoLine += " " + jsonObject["architecture"].toString(); + } + + if (jsonObject.contains("os")) { + infoLine += " " + jsonObject["os"].toString(); + } + + if (jsonObject.contains("dspRxBits") && jsonObject.contains("dspTxBits")) { + infoLine += QString(" %1/%2b").arg(jsonObject["dspRxBits"].toInt()).arg(jsonObject["dspTxBits"].toInt()); + } + + if (infoLine.size() > 0) { + ui->infoText->setText(infoLine); + } +} diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h index e562d3675..314be9314 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.h @@ -17,17 +17,23 @@ #ifndef INCLUDE_SDRDAEMONSINKGUI_H #define INCLUDE_SDRDAEMONSINKGUI_H -#include +#include + #include #include #include +#include +#include "plugin/plugininstancegui.h" #include "util/messagequeue.h" +#include "util/limitedcounter.h" #include "sdrdaemonsinksettings.h" #include "sdrdaemonsinkoutput.h" - +class QNetworkAccessManager; +class QNetworkReply; +class QJsonObject; class DeviceSampleSink; class DeviceUISet; @@ -35,6 +41,33 @@ namespace Ui { class SDRdaemonSinkGui; } +class SDRdaemonSinkExpAvg { +public: + SDRdaemonSinkExpAvg(float alpha) : + m_alpha(alpha), + m_start(true), + m_s(0) + {} + int put(int y) + { + if (m_start) { + m_start = false; + m_s = y; + } else { + m_s = m_alpha*y + (1.0-m_alpha)*m_s; + } + return roundf(m_s); + } + void reset() { + m_start = true; + } + +private: + float m_alpha; + bool m_start; + float m_s; +}; + class SDRdaemonSinkGui : public QWidget, public PluginInstanceGUI { Q_OBJECT @@ -47,8 +80,8 @@ public: QString getName() const; void resetToDefaults(); - virtual qint64 getCenterFrequency() const; - virtual void setCenterFrequency(qint64 centerFrequency); + virtual qint64 getCenterFrequency() const { return m_deviceCenterFrequency; } + virtual void setCenterFrequency(qint64 centerFrequency __attribute__((unused))) {} QByteArray serialize() const; bool deserialize(const QByteArray& data); virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } @@ -66,16 +99,19 @@ private: int m_sampleRate; quint64 m_deviceCenterFrequency; //!< Center frequency in device int m_samplesCount; - std::size_t m_tickCount; + uint32_t m_tickCount; std::size_t m_nbSinceLastFlowCheck; int m_lastEngineState; bool m_doApplySettings; bool m_forceSettings; - int m_nnSender; - uint32_t m_countUnrecoverable; uint32_t m_countRecovered; + uint32_t m_lastCountUnrecoverable; + uint32_t m_lastCountRecovered; + uint32_t m_lastSampleCount; + uint64_t m_lastTimestampUs; + bool m_resetCounts; QTime m_time; QPalette m_paletteGreenText; @@ -84,35 +120,40 @@ private: MessageQueue m_inputMessageQueue; + QNetworkAccessManager *m_networkManager; + QNetworkRequest m_networkRequest; + void blockApplySettings(bool block); void displaySettings(); void displayTime(); void sendControl(bool force = false); void sendSettings(); - void updateWithStreamTime(); - void updateSampleRateAndFrequency(); + void updateSampleRate(); void updateTxDelayTooltip(); void displayEventCounts(); + void displayEventStatus(int recoverableCount, int unrecoverableCount); void displayEventTimer(); + void analyzeApiReply(const QJsonObject& jsonObject); private slots: void handleInputMessages(); - void on_centerFrequency_changed(quint64 value); void on_sampleRate_changed(quint64 value); - void on_interp_currentIndexChanged(int index); void on_txDelay_valueChanged(int value); void on_nbFECBlocks_valueChanged(int value); - void on_address_returnPressed(); + void on_deviceIndex_returnPressed(); + void on_channelIndex_returnPressed(); + void on_apiAddress_returnPressed(); + void on_apiPort_returnPressed(); + void on_dataAddress_returnPressed(); void on_dataPort_returnPressed(); - void on_controlPort_returnPressed(); - void on_specificParms_returnPressed(); - void on_applyButton_clicked(bool checked); - void on_sendButton_clicked(bool checked); + void on_apiApplyButton_clicked(bool checked); + void on_dataApplyButton_clicked(bool checked); void on_startStop_toggled(bool checked); void on_eventCountsReset_clicked(bool checked); void updateHardware(); void updateStatus(); void tick(); + void networkManagerFinished(QNetworkReply *reply); }; #endif // INCLUDE_FILESINKGUI_H diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui index aa388601f..fb2586dfd 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkgui.ui @@ -2,12 +2,15 @@ SDRdaemonSinkGui + + true + 0 0 - 411 - 217 + 360 + 270 @@ -18,13 +21,13 @@ - 380 - 190 + 360 + 270 - Sans Serif + Liberation Sans 9 @@ -114,7 +117,7 @@ - true + false @@ -130,7 +133,7 @@ - DejaVu Sans Mono + Liberation Mono 20 @@ -146,24 +149,42 @@ - - - kHz - - - - - - - Qt::Horizontal - - - - 0 - 0 - - - + + + + + + + kHz + + + + + + + + + + + + 50 + 0 + + + + Remote baseband sample rate + + + 00000k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + +
@@ -202,7 +223,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -221,55 +242,6 @@ - - - - Int - - - - - - - Interpolation - - - - 1 - - - - - 2 - - - - - 4 - - - - - 8 - - - - - 16 - - - - - 32 - - - - - 64 - - - - @@ -302,7 +274,7 @@ 90 - 0 + 1 50 @@ -344,6 +316,58 @@ + + + + Dev + + + + + + + + 40 + 16777215 + + + + Device index (for SDRangel server) + + + 00 + + + 0 + + + + + + + Ch + + + + + + + + 40 + 16777215 + + + + Channel index (for SDRangel) + + + 00 + + + 0 + + + @@ -394,32 +418,6 @@ - - - - QL: - - - - - - - - 18 - 0 - - - - Current transmitter queue length in number of vectors - - - 00 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - @@ -433,7 +431,7 @@ false - Frames status: green = all original received, none = some recovered by FEC, red = some lost + Frames status: green = all original received, none = some recovered by FEC, red = some lost, blue = remote not streaming @@ -445,18 +443,34 @@ - + - 52 + 50 0 - Tx status since last poll: minimum of blocks received / maximum number of blocks used for recovery + Remote stream rate (S/s) - 100/100 + 0000000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 20 + 0 + + + + S/s @@ -485,7 +499,7 @@ - Number of uncrecoverable errors since event counts reset + Number of unrecoverable errors since event counts reset 000 @@ -534,16 +548,71 @@ - + - + - Addr: + QL - + + + + 16777215 + 14 + + + + Queue length gauge + + + 50 + + + + + + + + 50 + 0 + + + + Queue length / Queue size + + + 000/000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + 30 + 0 + + + + Green if communication OK else KO + + + API + + + + + 120 @@ -551,7 +620,7 @@ - Remote data connection IP address + Remote API IPv4 address 000.000.000.000 @@ -562,16 +631,22 @@ - + - D: + : - + + + + 50 + 16777215 + + - Remote data connection port + Remote API port 00000 @@ -579,42 +654,32 @@ 0 - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - C: + + + Qt::Horizontal - + + + 40 + 20 + + + - - - Remote control port - - - 00000 - - - 0 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - + 30 16777215 + + Set API address and port + Set @@ -623,27 +688,113 @@ - + - + + + + 30 + 0 + + - Sp: + Data - + + + + 120 + 0 + + + + Remote data connection IPv4 address + + + 000.000.000.000 + + + 0... + + - + + + : + + + + + 50 16777215 + + Remote data connection port + + + 00000 + - Send + 0 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 30 + 16777215 + + + + Set data address and port + + + Set + + + + + + + + + + + ... + + + + + + + + + + + ... diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp index 23fed01e9..0403c80c2 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.cpp @@ -14,12 +14,18 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include #include #include #include +#include +#include +#include #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" +#include "SWGDeviceReport.h" +#include "SWGSDRdaemonSinkReport.h" #include "util/simpleserializer.h" #include "dsp/dspcommands.h" @@ -28,30 +34,45 @@ #include "device/devicesinkapi.h" -#include "sdrdaemonsinkgui.h" #include "sdrdaemonsinkoutput.h" #include "sdrdaemonsinkthread.h" MESSAGE_CLASS_DEFINITION(SDRdaemonSinkOutput::MsgConfigureSDRdaemonSink, Message) MESSAGE_CLASS_DEFINITION(SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkWork, Message) MESSAGE_CLASS_DEFINITION(SDRdaemonSinkOutput::MsgStartStop, Message) -MESSAGE_CLASS_DEFINITION(SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkStreamTiming, Message) MESSAGE_CLASS_DEFINITION(SDRdaemonSinkOutput::MsgConfigureSDRdaemonSinkChunkCorrection, Message) -MESSAGE_CLASS_DEFINITION(SDRdaemonSinkOutput::MsgReportSDRdaemonSinkStreamTiming, Message) + +const uint32_t SDRdaemonSinkOutput::NbSamplesForRateCorrection = 5000000; SDRdaemonSinkOutput::SDRdaemonSinkOutput(DeviceSinkAPI *deviceAPI) : m_deviceAPI(deviceAPI), m_settings(), + m_centerFrequency(0), m_sdrDaemonSinkThread(0), m_deviceDescription("SDRdaemonSink"), m_startingTimeStamp(0), - m_masterTimer(deviceAPI->getMasterTimer()) + m_masterTimer(deviceAPI->getMasterTimer()), + m_tickCount(0), + m_tickMultiplier(20), + m_lastRemoteSampleCount(0), + m_lastSampleCount(0), + m_lastRemoteTimestampRateCorrection(0), + m_lastTimestampRateCorrection(0), + m_lastQueueLength(-2), + m_nbRemoteSamplesSinceRateCorrection(0), + m_nbSamplesSinceRateCorrection(0), + m_chunkSizeCorrection(0) { + m_networkManager = new QNetworkAccessManager(); + connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); + connect(&m_masterTimer, SIGNAL(timeout()), this, SLOT(tick())); } SDRdaemonSinkOutput::~SDRdaemonSinkOutput() { + disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); stop(); + delete m_networkManager; } void SDRdaemonSinkOutput::destroy() @@ -64,21 +85,20 @@ bool SDRdaemonSinkOutput::start() QMutexLocker mutexLocker(&m_mutex); qDebug() << "SDRdaemonSinkOutput::start"; - if((m_sdrDaemonSinkThread = new SDRdaemonSinkThread(&m_sampleSourceFifo)) == 0) - { - qCritical("out of memory"); - stop(); - return false; - } - m_sdrDaemonSinkThread->setRemoteAddress(m_settings.m_address, m_settings.m_dataPort); - m_sdrDaemonSinkThread->setCenterFrequency(m_settings.m_centerFrequency); + m_sdrDaemonSinkThread = new SDRdaemonSinkThread(&m_sampleSourceFifo); + m_sdrDaemonSinkThread->setDataAddress(m_settings.m_dataAddress, m_settings.m_dataPort); m_sdrDaemonSinkThread->setSamplerate(m_settings.m_sampleRate); m_sdrDaemonSinkThread->setNbBlocksFEC(m_settings.m_nbFECBlocks); m_sdrDaemonSinkThread->connectTimer(m_masterTimer); m_sdrDaemonSinkThread->startWork(); - double delay = ((127*127*m_settings.m_txDelay) / m_settings.m_sampleRate)/(128 + m_settings.m_nbFECBlocks); - m_sdrDaemonSinkThread->setTxDelay((int) (delay*1e6)); + // restart auto rate correction + m_lastRemoteTimestampRateCorrection = 0; + m_lastTimestampRateCorrection = 0; + m_lastQueueLength = -2; // set first value out of bounds + m_chunkSizeCorrection = 0; + + m_sdrDaemonSinkThread->setTxDelay(m_settings.m_txDelay); mutexLocker.unlock(); //applySettings(m_generalSettings, m_settings, true); @@ -144,22 +164,7 @@ int SDRdaemonSinkOutput::getSampleRate() const quint64 SDRdaemonSinkOutput::getCenterFrequency() const { - return m_settings.m_centerFrequency; -} - -void SDRdaemonSinkOutput::setCenterFrequency(qint64 centerFrequency) -{ - SDRdaemonSinkSettings settings = m_settings; - settings.m_centerFrequency = centerFrequency; - - MsgConfigureSDRdaemonSink* message = MsgConfigureSDRdaemonSink::create(settings, false); - m_inputMessageQueue.push(message); - - if (m_guiMessageQueue) - { - MsgConfigureSDRdaemonSink* messageToGUI = MsgConfigureSDRdaemonSink::create(settings, false); - m_guiMessageQueue->push(messageToGUI); - } + return m_centerFrequency; } std::time_t SDRdaemonSinkOutput::getStartingTimeStamp() const @@ -206,29 +211,15 @@ bool SDRdaemonSinkOutput::handleMessage(const Message& message) if (m_deviceAPI->initGeneration()) { m_deviceAPI->startGeneration(); - DSPEngine::instance()->startAudioInput(); } } else { m_deviceAPI->stopGeneration(); - DSPEngine::instance()->stopAudioInput(); } return true; } - else if (MsgConfigureSDRdaemonSinkStreamTiming::match(message)) - { - MsgReportSDRdaemonSinkStreamTiming *report; - - if (m_sdrDaemonSinkThread != 0 && getMessageQueueToGUI()) - { - report = MsgReportSDRdaemonSinkStreamTiming::create(m_sdrDaemonSinkThread->getSamplesCount()); - getMessageQueueToGUI()->push(report); - } - - return true; - } else if (MsgConfigureSDRdaemonSinkChunkCorrection::match(message)) { MsgConfigureSDRdaemonSinkChunkCorrection& conf = (MsgConfigureSDRdaemonSinkChunkCorrection&) message; @@ -252,55 +243,30 @@ void SDRdaemonSinkOutput::applySettings(const SDRdaemonSinkSettings& settings, b bool forwardChange = false; bool changeTxDelay = false; - if (force || (m_settings.m_address != settings.m_address) || (m_settings.m_dataPort != settings.m_dataPort)) + if (force || (m_settings.m_dataAddress != settings.m_dataAddress) || (m_settings.m_dataPort != settings.m_dataPort)) { - m_settings.m_address = settings.m_address; - m_settings.m_dataPort = settings.m_dataPort; - - if (m_sdrDaemonSinkThread != 0) - { - m_sdrDaemonSinkThread->setRemoteAddress(m_settings.m_address, m_settings.m_dataPort); + if (m_sdrDaemonSinkThread != 0) { + m_sdrDaemonSinkThread->setDataAddress(settings.m_dataAddress, settings.m_dataPort); } } - if (force || (m_settings.m_centerFrequency != settings.m_centerFrequency)) - { - m_settings.m_centerFrequency = settings.m_centerFrequency; - - if (m_sdrDaemonSinkThread != 0) - { - m_sdrDaemonSinkThread->setCenterFrequency(m_settings.m_centerFrequency); - } - - forwardChange = true; - } - if (force || (m_settings.m_sampleRate != settings.m_sampleRate)) { - m_settings.m_sampleRate = settings.m_sampleRate; - - if (m_sdrDaemonSinkThread != 0) - { - m_sdrDaemonSinkThread->setSamplerate(m_settings.m_sampleRate); + if (m_sdrDaemonSinkThread != 0) { + m_sdrDaemonSinkThread->setSamplerate(settings.m_sampleRate); } + m_tickMultiplier = (21*NbSamplesForRateCorrection) / (2*settings.m_sampleRate); // two times per sample filling period plus small extension + m_tickMultiplier = m_tickMultiplier < 20 ? 20 : m_tickMultiplier; // not below half a second + forwardChange = true; changeTxDelay = true; } - if (force || (m_settings.m_log2Interp != settings.m_log2Interp)) - { - m_settings.m_log2Interp = settings.m_log2Interp; - forwardChange = true; - } - if (force || (m_settings.m_nbFECBlocks != settings.m_nbFECBlocks)) { - m_settings.m_nbFECBlocks = settings.m_nbFECBlocks; - - if (m_sdrDaemonSinkThread != 0) - { - m_sdrDaemonSinkThread->setNbBlocksFEC(m_settings.m_nbFECBlocks); + if (m_sdrDaemonSinkThread != 0) { + m_sdrDaemonSinkThread->setNbBlocksFEC(settings.m_nbFECBlocks); } changeTxDelay = true; @@ -308,40 +274,34 @@ void SDRdaemonSinkOutput::applySettings(const SDRdaemonSinkSettings& settings, b if (force || (m_settings.m_txDelay != settings.m_txDelay)) { - m_settings.m_txDelay = settings.m_txDelay; changeTxDelay = true; } if (changeTxDelay) { - double delay = ((127*127*m_settings.m_txDelay) / m_settings.m_sampleRate)/(128 + m_settings.m_nbFECBlocks); - qDebug("SDRdaemonSinkOutput::applySettings: Tx delay: %f us", delay*1e6); - - if (m_sdrDaemonSinkThread != 0) - { - // delay is calculated as a fraction of the nominal UDP block process time - // frame size: 127 * 127 samples - // divided by sample rate gives the frame process time - // divided by the number of actual blocks including FEC blocks gives the block (i.e. UDP block) process time - m_sdrDaemonSinkThread->setTxDelay((int) (delay*1e6)); + if (m_sdrDaemonSinkThread != 0) { + m_sdrDaemonSinkThread->setTxDelay(settings.m_txDelay); } } mutexLocker.unlock(); - qDebug("SDRdaemonSinkOutput::applySettings: %s m_centerFrequency: %llu m_sampleRate: %llu m_log2Interp: %d m_txDelay: %f m_nbFECBlocks: %d", - forwardChange ? "forward change" : "", - m_settings.m_centerFrequency, - m_settings.m_sampleRate, - m_settings.m_log2Interp, - m_settings.m_txDelay, - m_settings.m_nbFECBlocks); + qDebug() << "SDRdaemonSinkOutput::applySettings:" + << " m_sampleRate: " << settings.m_sampleRate + << " m_txDelay: " << settings.m_txDelay + << " m_nbFECBlocks: " << settings.m_nbFECBlocks + << " m_apiAddress: " << settings.m_apiAddress + << " m_apiPort: " << settings.m_apiPort + << " m_dataAddress: " << settings.m_dataAddress + << " m_dataPort: " << settings.m_dataPort; if (forwardChange) { - DSPSignalNotification *notif = new DSPSignalNotification(m_settings.m_sampleRate, m_settings.m_centerFrequency); + DSPSignalNotification *notif = new DSPSignalNotification(settings.m_sampleRate, m_centerFrequency); m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif); } + + m_settings = settings; } int SDRdaemonSinkOutput::webapiRunGet( @@ -370,4 +330,230 @@ int SDRdaemonSinkOutput::webapiRun( return 200; } +int SDRdaemonSinkOutput::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setSdrDaemonSinkSettings(new SWGSDRangel::SWGSDRdaemonSinkSettings()); + response.getSdrDaemonSinkSettings()->init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} +int SDRdaemonSinkOutput::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage __attribute__((unused))) +{ + SDRdaemonSinkSettings settings = m_settings; + + if (deviceSettingsKeys.contains("sampleRate")) { + settings.m_sampleRate = response.getSdrDaemonSinkSettings()->getSampleRate(); + } + if (deviceSettingsKeys.contains("txDelay")) { + settings.m_txDelay = response.getSdrDaemonSinkSettings()->getTxDelay(); + } + if (deviceSettingsKeys.contains("nbFECBlocks")) { + settings.m_nbFECBlocks = response.getSdrDaemonSinkSettings()->getNbFecBlocks(); + } + if (deviceSettingsKeys.contains("apiAddress")) { + settings.m_apiAddress = *response.getSdrDaemonSinkSettings()->getApiAddress(); + } + if (deviceSettingsKeys.contains("apiPort")) { + settings.m_apiPort = response.getSdrDaemonSinkSettings()->getApiPort(); + } + if (deviceSettingsKeys.contains("dataAddress")) { + settings.m_dataAddress = *response.getSdrDaemonSinkSettings()->getDataAddress(); + } + if (deviceSettingsKeys.contains("dataPort")) { + settings.m_dataPort = response.getSdrDaemonSinkSettings()->getDataPort(); + } + if (deviceSettingsKeys.contains("deviceIndex")) { + settings.m_deviceIndex = response.getSdrDaemonSinkSettings()->getDeviceIndex(); + } + if (deviceSettingsKeys.contains("channelIndex")) { + settings.m_channelIndex = response.getSdrDaemonSinkSettings()->getChannelIndex(); + } + + MsgConfigureSDRdaemonSink *msg = MsgConfigureSDRdaemonSink::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureSDRdaemonSink *msgToGUI = MsgConfigureSDRdaemonSink::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +int SDRdaemonSinkOutput::webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setSdrDaemonSinkReport(new SWGSDRangel::SWGSDRdaemonSinkReport()); + response.getSdrDaemonSinkReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} + +void SDRdaemonSinkOutput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const SDRdaemonSinkSettings& settings) +{ + response.getSdrDaemonSinkSettings()->setCenterFrequency(m_centerFrequency); + response.getSdrDaemonSinkSettings()->setSampleRate(settings.m_sampleRate); + response.getSdrDaemonSinkSettings()->setTxDelay(settings.m_txDelay); + response.getSdrDaemonSinkSettings()->setNbFecBlocks(settings.m_nbFECBlocks); + response.getSdrDaemonSinkSettings()->setApiAddress(new QString(settings.m_apiAddress)); + response.getSdrDaemonSinkSettings()->setApiPort(settings.m_apiPort); + response.getSdrDaemonSinkSettings()->setDataAddress(new QString(settings.m_dataAddress)); + response.getSdrDaemonSinkSettings()->setDataPort(settings.m_dataPort); + response.getSdrDaemonSinkSettings()->setDeviceIndex(settings.m_deviceIndex); + response.getSdrDaemonSinkSettings()->setChannelIndex(settings.m_channelIndex); +} + +void SDRdaemonSinkOutput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + struct timeval tv; + response.getSdrDaemonSinkReport()->setBufferRwBalance(m_sampleSourceFifo.getRWBalance()); + response.getSdrDaemonSinkReport()->setSampleCount(m_sdrDaemonSinkThread ? (int) m_sdrDaemonSinkThread->getSamplesCount(tv) : 0); +} + +void SDRdaemonSinkOutput::tick() +{ + if (++m_tickCount == m_tickMultiplier) + { + QString reportURL; + + reportURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/report") + .arg(m_settings.m_apiAddress) + .arg(m_settings.m_apiPort) + .arg(m_settings.m_deviceIndex) + .arg(m_settings.m_channelIndex); + + m_networkRequest.setUrl(QUrl(reportURL)); + m_networkManager->get(m_networkRequest); + + m_tickCount = 0; + } +} + +void SDRdaemonSinkOutput::networkManagerFinished(QNetworkReply *reply) +{ + if (reply->error()) + { + qInfo("SDRdaemonSinkOutput::networkManagerFinished: error: %s", qPrintable(reply->errorString())); + return; + } + + QString answer = reply->readAll(); + + try + { + QByteArray jsonBytes(answer.toStdString().c_str()); + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(jsonBytes, &error); + + if (error.error == QJsonParseError::NoError) + { + analyzeApiReply(doc.object()); + } + else + { + QString errorMsg = QString("Reply JSON error: ") + error.errorString() + QString(" at offset ") + QString::number(error.offset); + qInfo().noquote() << "SDRdaemonSinkOutput::networkManagerFinished" << errorMsg; + } + } + catch (const std::exception& ex) + { + QString errorMsg = QString("Error parsing request: ") + ex.what(); + qInfo().noquote() << "SDRdaemonSinkOutput::networkManagerFinished" << errorMsg; + } +} + +void SDRdaemonSinkOutput::analyzeApiReply(const QJsonObject& jsonObject) +{ + if (jsonObject.contains("DaemonSourceReport")) + { + QJsonObject report = jsonObject["DaemonSourceReport"].toObject(); + m_centerFrequency = report["deviceCenterFreq"].toInt() * 1000; + + if (!m_sdrDaemonSinkThread) { + return; + } + + int queueSize = report["queueSize"].toInt(); + queueSize = queueSize == 0 ? 10 : queueSize; + int queueLength = report["queueLength"].toInt(); + int queueLengthPercent = (queueLength*100)/queueSize; + uint64_t remoteTimestampUs = report["tvSec"].toInt()*1000000ULL + report["tvUSec"].toInt(); + + uint32_t remoteSampleCountDelta, remoteSampleCount; + remoteSampleCount = report["samplesCount"].toInt(); + + if (remoteSampleCount < m_lastRemoteSampleCount) { + remoteSampleCountDelta = (0xFFFFFFFFU - m_lastRemoteSampleCount) + remoteSampleCount + 1; + } else { + remoteSampleCountDelta = remoteSampleCount - m_lastRemoteSampleCount; + } + + uint32_t sampleCountDelta, sampleCount; + struct timeval tv; + sampleCount = m_sdrDaemonSinkThread->getSamplesCount(tv); + + if (sampleCount < m_lastSampleCount) { + sampleCountDelta = (0xFFFFFFFFU - m_lastSampleCount) + sampleCount + 1; + } else { + sampleCountDelta = sampleCount - m_lastSampleCount; + } + + uint64_t timestampUs = tv.tv_sec*1000000ULL + tv.tv_usec; + + // on initial state wait for queue stabilization + if ((m_lastRemoteTimestampRateCorrection == 0) && (queueLength >= m_lastQueueLength-1) && (queueLength <= m_lastQueueLength+1)) + { + m_lastRemoteTimestampRateCorrection = remoteTimestampUs; + m_lastTimestampRateCorrection = timestampUs; + m_nbRemoteSamplesSinceRateCorrection = 0; + m_nbSamplesSinceRateCorrection = 0; + } + else + { + m_nbRemoteSamplesSinceRateCorrection += remoteSampleCountDelta; + m_nbSamplesSinceRateCorrection += sampleCountDelta; + + qDebug("SDRdaemonSinkOutput::analyzeApiReply: queueLengthPercent: %d m_nbSamplesSinceRateCorrection: %u", + queueLengthPercent, + m_nbRemoteSamplesSinceRateCorrection); + + if (m_nbRemoteSamplesSinceRateCorrection > NbSamplesForRateCorrection) + { + sampleRateCorrection(remoteTimestampUs - m_lastRemoteTimestampRateCorrection, + timestampUs - m_lastTimestampRateCorrection, + m_nbRemoteSamplesSinceRateCorrection, + m_nbSamplesSinceRateCorrection); + m_lastRemoteTimestampRateCorrection = remoteTimestampUs; + m_lastTimestampRateCorrection = timestampUs; + m_nbRemoteSamplesSinceRateCorrection = 0; + m_nbSamplesSinceRateCorrection = 0; + } + } + + m_lastRemoteSampleCount = remoteSampleCount; + m_lastSampleCount = sampleCount; + m_lastQueueLength = queueLength; + } +} + +void SDRdaemonSinkOutput::sampleRateCorrection(double remoteTimeDeltaUs, double timeDeltaUs, uint32_t remoteSampleCount, uint32_t sampleCount) +{ + double deltaSR = (remoteSampleCount/remoteTimeDeltaUs) - (sampleCount/timeDeltaUs); + double chunkCorr = 50000 * deltaSR; // for 50ms chunk intervals (50000us) + m_chunkSizeCorrection += roundf(chunkCorr); + + qDebug("SDRdaemonSinkOutput::sampleRateCorrection: %d (%f) samples", m_chunkSizeCorrection, chunkCorr); + + MsgConfigureSDRdaemonSinkChunkCorrection* message = MsgConfigureSDRdaemonSinkChunkCorrection::create(m_chunkSizeCorrection); + getInputMessageQueue()->push(message); +} diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h index 5e5e85551..741c6d1bd 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkoutput.h @@ -17,20 +17,27 @@ #ifndef INCLUDE_SDRDAEMONSINKOUTPUT_H #define INCLUDE_SDRDAEMONSINKOUTPUT_H -#include -#include #include #include #include +#include +#include +#include +#include + #include "dsp/devicesamplesink.h" #include "sdrdaemonsinksettings.h" class SDRdaemonSinkThread; class DeviceSinkAPI; +class QNetworkAccessManager; +class QNetworkReply; +class QJsonObject; class SDRdaemonSinkOutput : public DeviceSampleSink { + Q_OBJECT public: class MsgConfigureSDRdaemonSink : public Message { MESSAGE_CLASS_DECLARATION @@ -114,43 +121,6 @@ public: { } }; - class MsgConfigureSDRdaemonSinkStreamTiming : public Message { - MESSAGE_CLASS_DECLARATION - - public: - - static MsgConfigureSDRdaemonSinkStreamTiming* create() - { - return new MsgConfigureSDRdaemonSinkStreamTiming(); - } - - private: - - MsgConfigureSDRdaemonSinkStreamTiming() : - Message() - { } - }; - - class MsgReportSDRdaemonSinkStreamTiming : public Message { - MESSAGE_CLASS_DECLARATION - - public: - std::size_t getSamplesCount() const { return m_samplesCount; } - - static MsgReportSDRdaemonSinkStreamTiming* create(std::size_t samplesCount) - { - return new MsgReportSDRdaemonSinkStreamTiming(samplesCount); - } - - protected: - std::size_t m_samplesCount; - - MsgReportSDRdaemonSinkStreamTiming(std::size_t samplesCount) : - Message(), - m_samplesCount(samplesCount) - { } - }; - SDRdaemonSinkOutput(DeviceSinkAPI *deviceAPI); virtual ~SDRdaemonSinkOutput(); virtual void destroy(); @@ -166,11 +136,25 @@ public: virtual const QString& getDeviceDescription() const; virtual int getSampleRate() const; virtual quint64 getCenterFrequency() const; - virtual void setCenterFrequency(qint64 centerFrequency); + virtual void setCenterFrequency(qint64 centerFrequency __attribute__((unused))) {} std::time_t getStartingTimeStamp() const; virtual bool handleMessage(const Message& message); + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + virtual int webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage); @@ -184,12 +168,37 @@ private: DeviceSinkAPI *m_deviceAPI; QMutex m_mutex; SDRdaemonSinkSettings m_settings; + uint64_t m_centerFrequency; SDRdaemonSinkThread* m_sdrDaemonSinkThread; QString m_deviceDescription; std::time_t m_startingTimeStamp; const QTimer& m_masterTimer; + uint32_t m_tickCount; + uint32_t m_tickMultiplier; + + QNetworkAccessManager *m_networkManager; + QNetworkRequest m_networkRequest; + + uint32_t m_lastRemoteSampleCount; + uint32_t m_lastSampleCount; + uint64_t m_lastRemoteTimestampRateCorrection; + uint64_t m_lastTimestampRateCorrection; + int m_lastQueueLength; + uint32_t m_nbRemoteSamplesSinceRateCorrection; + uint32_t m_nbSamplesSinceRateCorrection; + int m_chunkSizeCorrection; + static const uint32_t NbSamplesForRateCorrection; void applySettings(const SDRdaemonSinkSettings& settings, bool force = false); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const SDRdaemonSinkSettings& settings); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); + + void analyzeApiReply(const QJsonObject& jsonObject); + void sampleRateCorrection(double remoteTimeDeltaUs, double timeDeltaUs, uint32_t remoteSampleCount, uint32_t sampleCount); + +private slots: + void tick(); + void networkManagerFinished(QNetworkReply *reply); }; #endif // INCLUDE_SDRDAEMONSINKOUTPUT_H diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkplugin.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkplugin.cpp index da24162f8..33c7f05c8 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkplugin.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkplugin.cpp @@ -15,18 +15,21 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include + #include "plugin/pluginapi.h" #include "util/simpleserializer.h" - #include "device/devicesinkapi.h" +#ifdef SERVER_MODE +#include "sdrdaemonsinkoutput.h" +#else #include "sdrdaemonsinkgui.h" +#endif #include "sdrdaemonsinkplugin.h" const PluginDescriptor SDRdaemonSinkPlugin::m_pluginDescriptor = { QString("SDRdaemon sink output"), - QString("3.9.0"), + QString("4.1.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, @@ -69,6 +72,15 @@ PluginInterface::SamplingDevices SDRdaemonSinkPlugin::enumSampleSinks() return result; } +#ifdef SERVER_MODE +PluginInstanceGUI* SDRdaemonSinkPlugin::createSampleSinkPluginInstanceGUI( + const QString& sinkId __attribute((unused)), + QWidget **widget __attribute((unused)), + DeviceUISet *deviceUISet __attribute((unused))) +{ + return 0; +} +#else PluginInstanceGUI* SDRdaemonSinkPlugin::createSampleSinkPluginInstanceGUI( const QString& sinkId, QWidget **widget, @@ -85,6 +97,7 @@ PluginInstanceGUI* SDRdaemonSinkPlugin::createSampleSinkPluginInstanceGUI( return 0; } } +#endif DeviceSampleSink* SDRdaemonSinkPlugin::createSampleSinkPluginInstanceOutput(const QString& sinkId, DeviceSinkAPI *deviceAPI) { diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.cpp index 415f47e2e..8467b259d 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.cpp @@ -25,28 +25,31 @@ SDRdaemonSinkSettings::SDRdaemonSinkSettings() void SDRdaemonSinkSettings::resetToDefaults() { m_centerFrequency = 435000*1000; - m_sampleRate = 192000; - m_log2Interp = 4; - m_txDelay = 0.5; + m_sampleRate = 48000; + m_txDelay = 0.35; m_nbFECBlocks = 0; - m_address = "127.0.0.1"; - m_dataPort = 9092; - m_controlPort = 9093; - m_specificParameters = ""; + m_apiAddress = "127.0.0.1"; + m_apiPort = 9091; + m_dataAddress = "127.0.0.1"; + m_dataPort = 9090; + m_deviceIndex = 0; + m_channelIndex = 0; } QByteArray SDRdaemonSinkSettings::serialize() const { SimpleSerializer s(1); - s.writeU64(1, m_sampleRate); - s.writeU32(2, m_log2Interp); + s.writeU64(1, m_centerFrequency); + s.writeU32(2, m_sampleRate); s.writeFloat(3, m_txDelay); s.writeU32(4, m_nbFECBlocks); - s.writeString(5, m_address); - s.writeU32(6, m_dataPort); - s.writeU32(7, m_controlPort); - s.writeString(8, m_specificParameters); + s.writeString(5, m_apiAddress); + s.writeU32(6, m_apiPort); + s.writeString(7, m_dataAddress); + s.writeU32(8, m_dataPort); + s.writeU32(10, m_deviceIndex); + s.writeU32(11, m_channelIndex); return s.final(); } @@ -64,16 +67,20 @@ bool SDRdaemonSinkSettings::deserialize(const QByteArray& data) if (d.getVersion() == 1) { quint32 uintval; - d.readU64(1, &m_sampleRate, 48000); - d.readU32(2, &m_log2Interp, 0); + + d.readU64(1, &m_centerFrequency, 435000*1000); + d.readU32(2, &m_sampleRate, 48000); d.readFloat(3, &m_txDelay, 0.5); d.readU32(4, &m_nbFECBlocks, 0); - d.readString(5, &m_address, "127.0.0.1"); + d.readString(5, &m_apiAddress, "127.0.0.1"); d.readU32(6, &uintval, 9090); + m_apiPort = uintval % (1<<16); + d.readString(7, &m_dataAddress, "127.0.0.1"); + d.readU32(8, &uintval, 9090); m_dataPort = uintval % (1<<16); - d.readU32(7, &uintval, 9090); - m_controlPort = uintval % (1<<16); - d.readString(8, &m_specificParameters, ""); + d.readU32(10, &m_deviceIndex, 0); + d.readU32(11, &m_channelIndex, 0); + return true; } else diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.h b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.h index 5f20970f2..88176fdec 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.h +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinksettings.h @@ -21,14 +21,15 @@ struct SDRdaemonSinkSettings { quint64 m_centerFrequency; - quint64 m_sampleRate; - quint32 m_log2Interp; + quint32 m_sampleRate; float m_txDelay; quint32 m_nbFECBlocks; - QString m_address; + QString m_apiAddress; + quint16 m_apiPort; + QString m_dataAddress; quint16 m_dataPort; - quint16 m_controlPort; - QString m_specificParameters; + quint32 m_deviceIndex; + quint32 m_channelIndex; SDRdaemonSinkSettings(); void resetToDefaults(); diff --git a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.cpp b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.cpp index 76b96d566..bbe966cb8 100644 --- a/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.cpp +++ b/plugins/samplesink/sdrdaemonsink/sdrdaemonsinkthread.cpp @@ -14,6 +14,7 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include #include #include #include @@ -47,6 +48,7 @@ SDRdaemonSinkThread::~SDRdaemonSinkThread() void SDRdaemonSinkThread::startWork() { qDebug() << "SDRdaemonSinkThread::startWork: "; + m_udpSinkFEC.start(); m_maxThrottlems = 0; m_startWaitMutex.lock(); m_elapsedTimer.start(); @@ -61,6 +63,7 @@ void SDRdaemonSinkThread::stopWork() qDebug() << "SDRdaemonSinkThread::stopWork"; m_running = false; wait(); + m_udpSinkFEC.stop(); } void SDRdaemonSinkThread::setSamplerate(int samplerate) @@ -86,6 +89,7 @@ void SDRdaemonSinkThread::setSamplerate(int samplerate) m_samplerate = samplerate; m_samplesChunkSize = (m_samplerate * m_throttlems) / 1000; + m_udpSinkFEC.setSampleRate(m_samplerate); if (wasRunning) { startWork(); @@ -126,12 +130,6 @@ void SDRdaemonSinkThread::tick() m_throttleToggle = !m_throttleToggle; } -// if (m_throttlems > m_maxThrottlems) -// { -// qDebug("FileSinkThread::tick: m_maxThrottlems: %d", m_maxThrottlems); -// m_maxThrottlems = m_throttlems; -// } - SampleVector::iterator readUntil; m_sampleFifo->readAdvance(readUntil, m_samplesChunkSize); // pull samples @@ -139,43 +137,11 @@ void SDRdaemonSinkThread::tick() m_samplesCount += m_samplesChunkSize; m_udpSinkFEC.write(beginRead, m_samplesChunkSize); -// m_ofstream->write(reinterpret_cast(&(*beginRead)), m_samplesChunkSize*sizeof(Sample)); // send samples - - // interpolation is done on the far side -// if (m_log2Interpolation == 0) -// { -// m_ofstream->write(reinterpret_cast(&(*beginRead)), m_samplesChunkSize*sizeof(Sample)); // send samples -// } -// else -// { -// int chunkSize = std::min((int) m_samplesChunkSize, m_samplerate); -// -// switch (m_log2Interpolation) -// { -// case 1: -// m_interpolators.interpolate2_cen(&beginRead, m_buf, chunkSize*(1<write(reinterpret_cast(m_buf), m_samplesChunkSize*(1< +#include +#include +#include + #include #include #include #include #include -#include -#include -#include -#include #include "dsp/inthalfbandfilter.h" #include "dsp/interpolators.h" @@ -35,6 +36,7 @@ #define SDRDAEMONSINK_THROTTLE_MS 50 class SampleSourceFifo; +struct timeval; class SDRdaemonSinkThread : public QThread { Q_OBJECT @@ -46,15 +48,14 @@ public: void startWork(); void stopWork(); - void setCenterFrequency(uint64_t centerFrequency) { m_udpSinkFEC.setCenterFrequency(centerFrequency); } void setSamplerate(int samplerate); void setNbBlocksFEC(uint32_t nbBlocksFEC) { m_udpSinkFEC.setNbBlocksFEC(nbBlocksFEC); }; - void setTxDelay(uint32_t txDelay) { m_udpSinkFEC.setTxDelay(txDelay); }; - void setRemoteAddress(const QString& address, uint16_t port) { m_udpSinkFEC.setRemoteAddress(address, port); } + void setTxDelay(float txDelay) { m_udpSinkFEC.setTxDelay(txDelay); }; + void setDataAddress(const QString& address, uint16_t port) { m_udpSinkFEC.setRemoteAddress(address, port); } bool isRunning() const { return m_running; } - std::size_t getSamplesCount() const { return m_samplesCount; } + uint32_t getSamplesCount(struct timeval& tv) const; void setSamplesCount(int samplesCount) { m_samplesCount = samplesCount; } void setChunkCorrection(int chunkCorrection) { m_chunkCorrection = chunkCorrection; } @@ -63,11 +64,11 @@ public: private: QMutex m_startWaitMutex; QWaitCondition m_startWaiter; - bool m_running; + volatile bool m_running; int m_samplesChunkSize; SampleSourceFifo* m_sampleFifo; - std::size_t m_samplesCount; + uint32_t m_samplesCount; int m_chunkCorrection; int m_samplerate; diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp b/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp index 3637c29fa..cd1b6fb0f 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfec.cpp @@ -22,70 +22,93 @@ #include #include "udpsinkfec.h" - -MESSAGE_CLASS_DEFINITION(UDPSinkFECWorker::MsgUDPFECEncodeAndSend, Message) -MESSAGE_CLASS_DEFINITION(UDPSinkFECWorker::MsgConfigureRemoteAddress, Message) +#include "udpsinkfecworker.h" UDPSinkFEC::UDPSinkFEC() : - m_centerFrequency(100000), m_sampleRate(48000), - m_sampleBytes(1), - m_sampleBits(8), m_nbSamples(0), m_nbBlocksFEC(0), + m_txDelayRatio(0.0), m_txDelay(0), m_txBlockIndex(0), m_txBlocksIndex(0), m_frameCount(0), - m_sampleIndex(0) + m_sampleIndex(0), + m_udpWorker(0), + m_remoteAddress("127.0.0.1"), + m_remotePort(9090) { + memset((char *) m_txBlocks, 0, 4*256*sizeof(SDRDaemonSuperBlock)); + memset((char *) &m_superBlock, 0, sizeof(SDRDaemonSuperBlock)); m_currentMetaFEC.init(); m_bufMeta = new uint8_t[m_udpSize]; m_buf = new uint8_t[m_udpSize]; - m_udpThread = new QThread(); - m_udpWorker = new UDPSinkFECWorker(); - - m_udpWorker->moveToThread(m_udpThread); - - connect(m_udpThread, SIGNAL(started()), m_udpWorker, SLOT(process())); - connect(m_udpWorker, SIGNAL(finished()), m_udpThread, SLOT(quit())); - - m_udpThread->start(); } UDPSinkFEC::~UDPSinkFEC() { - m_udpWorker->stop(); - m_udpThread->wait(); - delete[] m_buf; delete[] m_bufMeta; - delete m_udpWorker; - delete m_udpThread; } -void UDPSinkFEC::setTxDelay(uint32_t txDelay) +void UDPSinkFEC::start() { - qDebug() << "UDPSinkFEC::setTxDelay: txDelay: " << txDelay; - m_txDelay = txDelay; + m_udpWorker = new UDPSinkFECWorker(); + m_udpWorker->setRemoteAddress(m_remoteAddress, m_remotePort); + m_udpWorker->startStop(true); +} + +void UDPSinkFEC::stop() +{ + if (m_udpWorker) + { + m_udpWorker->startStop(false); + m_udpWorker->deleteLater(); + m_udpWorker = 0; + } +} + +void UDPSinkFEC::setTxDelay(float txDelayRatio) +{ + // delay is calculated from the fraction of the nominal UDP block process time + // frame size: 127 * (126 or 63 samples depending on I or Q sample bytes of 2 or 4 bytes respectively) + // divided by sample rate gives the frame process time + // divided by the number of actual blocks including FEC blocks gives the block (i.e. UDP block) process time + m_txDelayRatio = txDelayRatio; + int samplesPerBlock = SDRDaemonNbBytesPerBlock / (SDR_RX_SAMP_SZ <= 16 ? 4 : 8); + double delay = ((127*samplesPerBlock*txDelayRatio) / m_sampleRate)/(128 + m_nbBlocksFEC); + m_txDelay = delay * 1e6; + qDebug() << "UDPSinkFEC::setTxDelay: txDelay: " << txDelayRatio << " m_txDelay: " << m_txDelay << " us"; } void UDPSinkFEC::setNbBlocksFEC(uint32_t nbBlocksFEC) { qDebug() << "UDPSinkFEC::setNbBlocksFEC: nbBlocksFEC: " << nbBlocksFEC; m_nbBlocksFEC = nbBlocksFEC; + setTxDelay(m_txDelayRatio); +} + +void UDPSinkFEC::setSampleRate(uint32_t sampleRate) +{ + qDebug() << "UDPSinkFEC::setSampleRate: sampleRate: " << sampleRate; + m_sampleRate = sampleRate; + setTxDelay(m_txDelayRatio); } void UDPSinkFEC::setRemoteAddress(const QString& address, uint16_t port) { qDebug() << "UDPSinkFEC::setRemoteAddress: address: " << address << " port: " << port; - m_udpWorker->setRemoteAddress(address, port); + m_remoteAddress = address; + m_remotePort = port; + + if (m_udpWorker) { + m_udpWorker->setRemoteAddress(m_remoteAddress, m_remotePort); + } } void UDPSinkFEC::write(const SampleVector::iterator& begin, uint32_t sampleChunkSize) { - //qDebug("UDPSinkFEC::write(: %u samples", sampleChunkSize); const SampleVector::iterator end = begin + sampleChunkSize; SampleVector::iterator it = begin; @@ -96,15 +119,14 @@ void UDPSinkFEC::write(const SampleVector::iterator& begin, uint32_t sampleChunk if (m_txBlockIndex == 0) // Tx block index 0 is a block with only meta data { struct timeval tv; - MetaDataFEC metaData; + SDRDaemonMetaDataFEC metaData; gettimeofday(&tv, 0); - // create meta data TODO: semaphore - metaData.m_centerFrequency = m_centerFrequency; + metaData.m_centerFrequency = 0; // frequency not set by stream metaData.m_sampleRate = m_sampleRate; - metaData.m_sampleBytes = m_sampleBytes; - metaData.m_sampleBits = m_sampleBits; + metaData.m_sampleBytes = (SDR_RX_SAMP_SZ <= 16 ? 2 : 4); + metaData.m_sampleBits = SDR_RX_SAMP_SZ; metaData.m_nbOriginalBlocks = m_nbOriginalBlocks; metaData.m_nbFECBlocks = m_nbBlocksFEC; metaData.m_tv_sec = tv.tv_sec; @@ -115,11 +137,13 @@ void UDPSinkFEC::write(const SampleVector::iterator& begin, uint32_t sampleChunk metaData.m_crc32 = crc32.checksum(); - memset((void *) &m_superBlock, 0, m_udpSize); + memset((char *) &m_superBlock, 0, sizeof(m_superBlock)); - m_superBlock.header.frameIndex = m_frameCount; - m_superBlock.header.blockIndex = m_txBlockIndex; - memcpy((void *) &m_superBlock.protectedBlock, (const void *) &metaData, sizeof(MetaDataFEC)); + m_superBlock.m_header.m_frameIndex = m_frameCount; + m_superBlock.m_header.m_blockIndex = m_txBlockIndex; + m_superBlock.m_header.m_sampleBytes = (SDR_RX_SAMP_SZ <= 16 ? 2 : 4); + m_superBlock.m_header.m_sampleBits = SDR_RX_SAMP_SZ; + memcpy((char *) &m_superBlock.m_protectedBlock, (const char *) &metaData, sizeof(SDRDaemonMetaDataFEC)); if (!(metaData == m_currentMetaFEC)) { @@ -141,24 +165,28 @@ void UDPSinkFEC::write(const SampleVector::iterator& begin, uint32_t sampleChunk m_txBlockIndex = 1; // next Tx block with data } + int samplesPerBlock = SDRDaemonNbBytesPerBlock / (SDR_RX_SAMP_SZ <= 16 ? 4 : 8); // two I or Q samples + if (m_sampleIndex + inRemainingSamples < samplesPerBlock) // there is still room in the current super block { - memcpy((void *) &m_superBlock.protectedBlock.m_samples[m_sampleIndex], - (const void *) &(*it), + memcpy((char *) &m_superBlock.m_protectedBlock.buf[m_sampleIndex*sizeof(Sample)], + (const char *) &(*it), inRemainingSamples * sizeof(Sample)); m_sampleIndex += inRemainingSamples; it = end; // all input samples are consumed } else // complete super block and initiate the next if not end of frame { - memcpy((void *) &m_superBlock.protectedBlock.m_samples[m_sampleIndex], - (const void *) &(*it), + memcpy((char *) &m_superBlock.m_protectedBlock.buf[m_sampleIndex*sizeof(Sample)], + (const char *) &(*it), (samplesPerBlock - m_sampleIndex) * sizeof(Sample)); it += samplesPerBlock - m_sampleIndex; m_sampleIndex = 0; - m_superBlock.header.frameIndex = m_frameCount; - m_superBlock.header.blockIndex = m_txBlockIndex; + m_superBlock.m_header.m_frameIndex = m_frameCount; + m_superBlock.m_header.m_blockIndex = m_txBlockIndex; + m_superBlock.m_header.m_sampleBytes = (SDR_RX_SAMP_SZ <= 16 ? 2 : 4); + m_superBlock.m_header.m_sampleBits = SDR_RX_SAMP_SZ; m_txBlocks[m_txBlocksIndex][m_txBlockIndex] = m_superBlock; if (m_txBlockIndex == m_nbOriginalBlocks - 1) // frame complete @@ -166,11 +194,9 @@ void UDPSinkFEC::write(const SampleVector::iterator& begin, uint32_t sampleChunk int nbBlocksFEC = m_nbBlocksFEC; int txDelay = m_txDelay; - // TODO: send blocks - //qDebug("UDPSinkFEC::write: push frame to worker: %u", m_frameCount); - m_udpWorker->pushTxFrame(m_txBlocks[m_txBlocksIndex], nbBlocksFEC, txDelay, m_frameCount); - //m_txThread = new std::thread(transmitUDP, this, m_txBlocks[m_txBlocksIndex], m_frameCount, nbBlocksFEC, txDelay, m_cm256Valid); - //transmitUDP(this, m_txBlocks[m_txBlocksIndex], m_frameCount, m_nbBlocksFEC, m_txDelay, m_cm256Valid); + if (m_udpWorker) { + m_udpWorker->pushTxFrame(m_txBlocks[m_txBlocksIndex], nbBlocksFEC, txDelay, m_frameCount); + } m_txBlocksIndex = (m_txBlocksIndex + 1) % 4; m_txBlockIndex = 0; @@ -184,155 +210,3 @@ void UDPSinkFEC::write(const SampleVector::iterator& begin, uint32_t sampleChunk } } -UDPSinkFECWorker::UDPSinkFECWorker() : - m_running(false), - m_remotePort(9090) -{ - m_cm256Valid = m_cm256.isInitialized(); - connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::DirectConnection); -} - -UDPSinkFECWorker::~UDPSinkFECWorker() -{ - disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); - m_inputMessageQueue.clear(); -} - -void UDPSinkFECWorker::pushTxFrame(UDPSinkFEC::SuperBlock *txBlocks, - uint32_t nbBlocksFEC, - uint32_t txDelay, - uint16_t frameIndex) -{ - //qDebug("UDPSinkFECWorker::pushTxFrame. %d", m_inputMessageQueue.size()); - m_inputMessageQueue.push(MsgUDPFECEncodeAndSend::create(txBlocks, nbBlocksFEC, txDelay, frameIndex)); -} - -void UDPSinkFECWorker::setRemoteAddress(const QString& address, uint16_t port) -{ - m_inputMessageQueue.push(MsgConfigureRemoteAddress::create(address, port)); -} - -void UDPSinkFECWorker::process() -{ - m_running = true; - - qDebug("UDPSinkFECWorker::process: started"); - - while (m_running) - { - usleep(250000); - } - - qDebug("UDPSinkFECWorker::process: stopped"); - emit finished(); -} - -void UDPSinkFECWorker::stop() -{ - m_running = false; -} - -void UDPSinkFECWorker::handleInputMessages() -{ - Message* message; - - while ((message = m_inputMessageQueue.pop()) != 0) - { - if (MsgUDPFECEncodeAndSend::match(*message)) - { - MsgUDPFECEncodeAndSend *sendMsg = (MsgUDPFECEncodeAndSend *) message; - encodeAndTransmit(sendMsg->getTxBlocks(), sendMsg->getFrameIndex(), sendMsg->getNbBlocsFEC(), sendMsg->getTxDelay()); - } - else if (MsgConfigureRemoteAddress::match(*message)) - { - qDebug("UDPSinkFECWorker::handleInputMessages: %s", message->getIdentifier()); - MsgConfigureRemoteAddress *addressMsg = (MsgConfigureRemoteAddress *) message; - m_remoteAddress = addressMsg->getAddress(); - m_remotePort = addressMsg->getPort(); - } - - delete message; - } -} - -void UDPSinkFECWorker::encodeAndTransmit(UDPSinkFEC::SuperBlock *txBlockx, uint16_t frameIndex, uint32_t nbBlocksFEC, uint32_t txDelay) -{ - CM256::cm256_encoder_params cm256Params; //!< Main interface with CM256 encoder - CM256::cm256_block descriptorBlocks[256]; //!< Pointers to data for CM256 encoder - UDPSinkFEC::ProtectedBlock fecBlocks[256]; //!< FEC data - - if ((nbBlocksFEC == 0) || !m_cm256Valid) - { -// qDebug("UDPSinkFECWorker::encodeAndTransmit: transmit frame without FEC to %s:%d", m_remoteAddress.toStdString().c_str(), m_remotePort); - - for (unsigned int i = 0; i < UDPSinkFEC::m_nbOriginalBlocks; i++) - { - m_socket.SendDataGram((const void *) &txBlockx[i], (int) UDPSinkFEC::m_udpSize, m_remoteAddress.toStdString(), (uint32_t) m_remotePort); - //m_udpSocket->writeDatagram((const char *) &txBlockx[i], (int) UDPSinkFEC::m_udpSize, m_remoteAddress, m_remotePort); - usleep(txDelay); - } - } - else - { - cm256Params.BlockBytes = sizeof(UDPSinkFEC::ProtectedBlock); - cm256Params.OriginalCount = UDPSinkFEC::m_nbOriginalBlocks; - cm256Params.RecoveryCount = nbBlocksFEC; - - - // Fill pointers to data - for (int i = 0; i < cm256Params.OriginalCount + cm256Params.RecoveryCount; ++i) - { - if (i >= cm256Params.OriginalCount) { - memset((void *) &txBlockx[i].protectedBlock, 0, sizeof(UDPSinkFEC::ProtectedBlock)); - } - - txBlockx[i].header.frameIndex = frameIndex; - txBlockx[i].header.blockIndex = i; - descriptorBlocks[i].Block = (void *) &(txBlockx[i].protectedBlock); - descriptorBlocks[i].Index = txBlockx[i].header.blockIndex; - } - - // Encode FEC blocks - if (m_cm256.cm256_encode(cm256Params, descriptorBlocks, fecBlocks)) - { - qDebug("UDPSinkFECWorker::encodeAndTransmit: CM256 encode failed. No transmission."); - return; - } - - // Merge FEC with data to transmit - for (int i = 0; i < cm256Params.RecoveryCount; i++) - { - txBlockx[i + cm256Params.OriginalCount].protectedBlock = fecBlocks[i]; - } - - // Transmit all blocks - -// qDebug("UDPSinkFECWorker::encodeAndTransmit: transmit frame with FEC to %s:%d", m_remoteAddress.toStdString().c_str(), m_remotePort); - - for (int i = 0; i < cm256Params.OriginalCount + cm256Params.RecoveryCount; i++) - { -#ifdef SDRDAEMON_PUNCTURE - if (i == SDRDAEMON_PUNCTURE) { - continue; - } -#endif -// std::cerr << "UDPSinkFEC::transmitUDP:" -// << " i: " << i -// << " frameIndex: " << (int) m_txBlocks[i].header.frameIndex -// << " blockIndex: " << (int) m_txBlocks[i].header.blockIndex -// << " i.q:"; -// -// for (int j = 0; j < 10; j++) -// { -// std::cerr << " " << (int) m_txBlocks[i].protectedBlock.m_samples[j].m_real -// << "." << (int) m_txBlocks[i].protectedBlock.m_samples[j].m_imag; -// } -// -// std::cerr << std::endl; - - m_socket.SendDataGram((const void *) &txBlockx[i], (int) UDPSinkFEC::m_udpSize, m_remoteAddress.toStdString(), (uint32_t) m_remotePort); - //m_udpSocket->writeDatagram((const char *) &txBlockx[i], (int) UDPSinkFEC::m_udpSize, m_remoteAddress, m_remotePort); - usleep(txDelay); - } - } -} diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfec.h b/plugins/samplesink/sdrdaemonsink/udpsinkfec.h index a0a374e24..36055522b 100644 --- a/plugins/samplesink/sdrdaemonsink/udpsinkfec.h +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfec.h @@ -23,16 +23,10 @@ #include #include #include -#include - -#include "cm256.h" #include "dsp/dsptypes.h" #include "util/CRC64.h" -#include "util/messagequeue.h" -#include "util/message.h" - -#include "UDPSocket.h" +#include "channel/sdrdaemondatablock.h" class UDPSinkFECWorker; @@ -42,51 +36,6 @@ class UDPSinkFEC : public QObject public: static const uint32_t m_udpSize = 512; //!< Size of UDP block in number of bytes static const uint32_t m_nbOriginalBlocks = 128; //!< Number of original blocks in a protected block sequence -#pragma pack(push, 1) - struct MetaDataFEC - { - uint32_t m_centerFrequency; //!< 4 center frequency in kHz - uint32_t m_sampleRate; //!< 8 sample rate in Hz - uint8_t m_sampleBytes; //!< 9 MSB(4): indicators, LSB(4) number of bytes per sample - uint8_t m_sampleBits; //!< 10 number of effective bits per sample - uint8_t m_nbOriginalBlocks; //!< 11 number of blocks with original (protected) data - uint8_t m_nbFECBlocks; //!< 12 number of blocks carrying FEC - uint32_t m_tv_sec; //!< 16 seconds of timestamp at start time of super-frame processing - uint32_t m_tv_usec; //!< 20 microseconds of timestamp at start time of super-frame processing - uint32_t m_crc32; //!< 24 CRC32 of the above - - bool operator==(const MetaDataFEC& rhs) - { - return (memcmp((const void *) this, (const void *) &rhs, 12) == 0); // Only the 12 first bytes are relevant - } - - void init() - { - memset((void *) this, 0, sizeof(MetaDataFEC)); - m_nbFECBlocks = -1; - } - }; - - struct Header - { - uint16_t frameIndex; - uint8_t blockIndex; - uint8_t filler; - }; - - static const int samplesPerBlock = (m_udpSize - sizeof(Header)) / sizeof(Sample); - - struct ProtectedBlock - { - Sample m_samples[samplesPerBlock]; - }; - - struct SuperBlock - { - Header header; - ProtectedBlock protectedBlock; - }; -#pragma pack(pop) /** * Construct UDP sink @@ -96,6 +45,9 @@ public: /** Destroy UDP sink */ ~UDPSinkFEC(); + void start(); + void stop(); + /** * Write IQ samples */ @@ -109,17 +61,11 @@ public: return ret; } - /** Set center frequency given in Hz */ - void setCenterFrequency(uint64_t centerFrequency) { m_centerFrequency = centerFrequency / 1000; } - - /** Set sample rate given in Hz */ - void setSampleRate(uint32_t sampleRate) { m_sampleRate = sampleRate; } - - void setSampleBytes(uint8_t sampleBytes) { m_sampleBytes = (sampleBytes & 0x0F) + (m_sampleBytes & 0xF0); } - void setSampleBits(uint8_t sampleBits) { m_sampleBits = sampleBits; } + /** Set sample rate given in S/s */ + void setSampleRate(uint32_t sampleRate); void setNbBlocksFEC(uint32_t nbBlocksFEC); - void setTxDelay(uint32_t txDelay); + void setTxDelay(float txDelayRatio); void setRemoteAddress(const QString& address, uint16_t port); /** Return true if the stream is OK, return false if there is an error. */ @@ -131,10 +77,7 @@ public: private: std::string m_error; - uint32_t m_centerFrequency; //!< center frequency in kHz uint32_t m_sampleRate; //!< sample rate in Hz - uint8_t m_sampleBytes; //!< number of bytes per sample - uint8_t m_sampleBits; //!< number of effective bits per sample uint32_t m_nbSamples; //!< total number of samples sent int the last frame QHostAddress m_ownAddress; @@ -143,115 +86,20 @@ private: uint8_t* m_bufMeta; uint8_t* m_buf; - MetaDataFEC m_currentMetaFEC; //!< Meta data for current frame - uint32_t m_nbBlocksFEC; //!< Variable number of FEC blocks - uint32_t m_txDelay; //!< Delay in microseconds (usleep) between each sending of an UDP datagram - SuperBlock m_txBlocks[4][256]; //!< UDP blocks to send with original data + FEC - SuperBlock m_superBlock; //!< current super block being built - int m_txBlockIndex; //!< Current index in blocks to transmit in the Tx row - int m_txBlocksIndex; //!< Current index of Tx blocks row - uint16_t m_frameCount; //!< transmission frame count - int m_sampleIndex; //!< Current sample index in protected block data + SDRDaemonMetaDataFEC m_currentMetaFEC; //!< Meta data for current frame + uint32_t m_nbBlocksFEC; //!< Variable number of FEC blocks + float m_txDelayRatio; //!< Delay in ratio of nominal frame period + uint32_t m_txDelay; //!< Delay in microseconds (usleep) between each sending of an UDP datagram + SDRDaemonSuperBlock m_txBlocks[4][256]; //!< UDP blocks to send with original data + FEC + SDRDaemonSuperBlock m_superBlock; //!< current super block being built + int m_txBlockIndex; //!< Current index in blocks to transmit in the Tx row + int m_txBlocksIndex; //!< Current index of Tx blocks row + uint16_t m_frameCount; //!< transmission frame count + int m_sampleIndex; //!< Current sample index in protected block data - QThread *m_udpThread; UDPSinkFECWorker *m_udpWorker; + QString m_remoteAddress; + uint16_t m_remotePort; }; - -class UDPSinkFECWorker : public QObject -{ - Q_OBJECT -public: - class MsgUDPFECEncodeAndSend : public Message - { - MESSAGE_CLASS_DECLARATION - public: - UDPSinkFEC::SuperBlock *getTxBlocks() const { return m_txBlockx; } - uint32_t getNbBlocsFEC() const { return m_nbBlocksFEC; } - uint32_t getTxDelay() const { return m_txDelay; } - uint16_t getFrameIndex() const { return m_frameIndex; } - - static MsgUDPFECEncodeAndSend* create( - UDPSinkFEC::SuperBlock *txBlocks, - uint32_t nbBlocksFEC, - uint32_t txDelay, - uint16_t frameIndex) - { - return new MsgUDPFECEncodeAndSend(txBlocks, nbBlocksFEC, txDelay, frameIndex); - } - - private: - UDPSinkFEC::SuperBlock *m_txBlockx; - uint32_t m_nbBlocksFEC; - uint32_t m_txDelay; - uint16_t m_frameIndex; - - MsgUDPFECEncodeAndSend( - UDPSinkFEC::SuperBlock *txBlocks, - uint32_t nbBlocksFEC, - uint32_t txDelay, - uint16_t frameIndex) : - m_txBlockx(txBlocks), - m_nbBlocksFEC(nbBlocksFEC), - m_txDelay(txDelay), - m_frameIndex(frameIndex) - {} - }; - - class MsgConfigureRemoteAddress : public Message - { - MESSAGE_CLASS_DECLARATION - public: - const QString& getAddress() const { return m_address; } - uint16_t getPort() const { return m_port; } - - static MsgConfigureRemoteAddress* create(const QString& address, uint16_t port) - { - return new MsgConfigureRemoteAddress(address, port); - } - - private: - QString m_address; - uint16_t m_port; - - MsgConfigureRemoteAddress(const QString& address, uint16_t port) : - m_address(address), - m_port(port) - {} - }; - - UDPSinkFECWorker(); - ~UDPSinkFECWorker(); - - void pushTxFrame(UDPSinkFEC::SuperBlock *txBlocks, - uint32_t nbBlocksFEC, - uint32_t txDelay, - uint16_t frameIndex); - void setRemoteAddress(const QString& address, uint16_t port); - void stop(); - - MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication - -signals: - void finished(); - -public slots: - void process(); - -private slots: - void handleInputMessages(); - -private: - void encodeAndTransmit(UDPSinkFEC::SuperBlock *txBlockx, uint16_t frameIndex, uint32_t nbBlocksFEC, uint32_t txDelay); - - bool m_running; - CM256 m_cm256; //!< CM256 library object - bool m_cm256Valid; //!< true if CM256 library is initialized correctly - UDPSocket m_socket; - QString m_remoteAddress; - uint16_t m_remotePort; -}; - - - #endif /* PLUGINS_SAMPLESINK_SDRDAEMONSINK_UDPSINKFEC_H_ */ diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.cpp b/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.cpp new file mode 100644 index 000000000..7618c4421 --- /dev/null +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.cpp @@ -0,0 +1,202 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "udpsinkfecworker.h" + +MESSAGE_CLASS_DEFINITION(UDPSinkFECWorker::MsgUDPFECEncodeAndSend, Message) +MESSAGE_CLASS_DEFINITION(UDPSinkFECWorker::MsgConfigureRemoteAddress, Message) +MESSAGE_CLASS_DEFINITION(UDPSinkFECWorker::MsgStartStop, Message) + +UDPSinkFECWorker::UDPSinkFECWorker() : + m_running(false), + m_udpSocket(0), + m_remotePort(9090) +{ + m_cm256Valid = m_cm256.isInitialized(); + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); +} + +UDPSinkFECWorker::~UDPSinkFECWorker() +{ +} + +void UDPSinkFECWorker::startStop(bool start) +{ + MsgStartStop *msg = MsgStartStop::create(start); + m_inputMessageQueue.push(msg); +} + +void UDPSinkFECWorker::startWork() +{ + qDebug("UDPSinkFECWorker::startWork"); + m_startWaitMutex.lock(); + m_udpSocket = new QUdpSocket(this); + start(); + while(!m_running) + m_startWaiter.wait(&m_startWaitMutex, 100); + m_startWaitMutex.unlock(); +} + +void UDPSinkFECWorker::stopWork() +{ + qDebug("UDPSinkFECWorker::stopWork"); + delete m_udpSocket; + m_udpSocket = 0; + m_running = false; + wait(); +} + +void UDPSinkFECWorker::run() +{ + m_running = true; + m_startWaiter.wakeAll(); + + qDebug("UDPSinkFECWorker::process: started"); + + while (m_running) + { + sleep(1); + } + m_running = false; + + qDebug("UDPSinkFECWorker::process: stopped"); +} + +void UDPSinkFECWorker::pushTxFrame(SDRDaemonSuperBlock *txBlocks, + uint32_t nbBlocksFEC, + uint32_t txDelay, + uint16_t frameIndex) +{ + //qDebug("UDPSinkFECWorker::pushTxFrame. %d", m_inputMessageQueue.size()); + m_inputMessageQueue.push(MsgUDPFECEncodeAndSend::create(txBlocks, nbBlocksFEC, txDelay, frameIndex)); +} + +void UDPSinkFECWorker::setRemoteAddress(const QString& address, uint16_t port) +{ + m_inputMessageQueue.push(MsgConfigureRemoteAddress::create(address, port)); +} + +void UDPSinkFECWorker::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + if (MsgUDPFECEncodeAndSend::match(*message)) + { + MsgUDPFECEncodeAndSend *sendMsg = (MsgUDPFECEncodeAndSend *) message; + encodeAndTransmit(sendMsg->getTxBlocks(), sendMsg->getFrameIndex(), sendMsg->getNbBlocsFEC(), sendMsg->getTxDelay()); + } + else if (MsgConfigureRemoteAddress::match(*message)) + { + qDebug("UDPSinkFECWorker::handleInputMessages: %s", message->getIdentifier()); + MsgConfigureRemoteAddress *addressMsg = (MsgConfigureRemoteAddress *) message; + m_remoteAddress = addressMsg->getAddress(); + m_remotePort = addressMsg->getPort(); + m_remoteHostAddress.setAddress(addressMsg->getAddress()); + } + else if (MsgStartStop::match(*message)) + { + MsgStartStop* notif = (MsgStartStop*) message; + qDebug("DaemonSinkThread::handleInputMessages: MsgStartStop: %s", notif->getStartStop() ? "start" : "stop"); + + if (notif->getStartStop()) { + startWork(); + } else { + stopWork(); + } + } + + delete message; + } +} + +void UDPSinkFECWorker::encodeAndTransmit(SDRDaemonSuperBlock *txBlockx, uint16_t frameIndex, uint32_t nbBlocksFEC, uint32_t txDelay) +{ + CM256::cm256_encoder_params cm256Params; //!< Main interface with CM256 encoder + CM256::cm256_block descriptorBlocks[256]; //!< Pointers to data for CM256 encoder + SDRDaemonProtectedBlock fecBlocks[256]; //!< FEC data + + if ((nbBlocksFEC == 0) || !m_cm256Valid) + { + if (m_udpSocket) + { + for (unsigned int i = 0; i < SDRDaemonNbOrginalBlocks; i++) + { + //m_socket.SendDataGram((const void *) &txBlockx[i], SDRDaemonUdpSize, m_remoteAddress.toStdString(), (uint32_t) m_remotePort); + m_udpSocket->writeDatagram((const char *) &txBlockx[i], SDRDaemonUdpSize, m_remoteHostAddress, m_remotePort); + usleep(txDelay); + } + } + } + else + { + cm256Params.BlockBytes = sizeof(SDRDaemonProtectedBlock); + cm256Params.OriginalCount = SDRDaemonNbOrginalBlocks; + cm256Params.RecoveryCount = nbBlocksFEC; + + + // Fill pointers to data + for (int i = 0; i < cm256Params.OriginalCount + cm256Params.RecoveryCount; ++i) + { + if (i >= cm256Params.OriginalCount) { + memset((char *) &txBlockx[i].m_protectedBlock, 0, sizeof(SDRDaemonProtectedBlock)); + } + + txBlockx[i].m_header.m_frameIndex = frameIndex; + txBlockx[i].m_header.m_blockIndex = i; + txBlockx[i].m_header.m_sampleBytes = (SDR_RX_SAMP_SZ <= 16 ? 2 : 4); + txBlockx[i].m_header.m_sampleBits = SDR_RX_SAMP_SZ; + descriptorBlocks[i].Block = (void *) &(txBlockx[i].m_protectedBlock); + descriptorBlocks[i].Index = txBlockx[i].m_header.m_blockIndex; + } + + // Encode FEC blocks + if (m_cm256.cm256_encode(cm256Params, descriptorBlocks, fecBlocks)) + { + qDebug("UDPSinkFECWorker::encodeAndTransmit: CM256 encode failed. No transmission."); + return; + } + + // Merge FEC with data to transmit + for (int i = 0; i < cm256Params.RecoveryCount; i++) + { + txBlockx[i + cm256Params.OriginalCount].m_protectedBlock = fecBlocks[i]; + } + + // Transmit all blocks + if (m_udpSocket) + { + for (int i = 0; i < cm256Params.OriginalCount + cm256Params.RecoveryCount; i++) + { + #ifdef SDRDAEMON_PUNCTURE + if (i == SDRDAEMON_PUNCTURE) { + continue; + } + #endif + + m_udpSocket->writeDatagram((const char *) &txBlockx[i], SDRDaemonUdpSize, m_remoteHostAddress, m_remotePort); + usleep(txDelay); + } + } + } +} + + + + diff --git a/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.h b/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.h new file mode 100644 index 000000000..32580aa3f --- /dev/null +++ b/plugins/samplesink/sdrdaemonsink/udpsinkfecworker.h @@ -0,0 +1,147 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_SAMPLESINK_SDRDAEMONSINK_UDPSINKFECWORKER_H_ +#define PLUGINS_SAMPLESINK_SDRDAEMONSINK_UDPSINKFECWORKER_H_ + +#include +#include +#include +#include + +#include "cm256.h" + +#include "util/messagequeue.h" +#include "util/message.h" +#include "channel/sdrdaemondatablock.h" + +class QUdpSocket; + +class UDPSinkFECWorker : public QThread +{ + Q_OBJECT +public: + class MsgUDPFECEncodeAndSend : public Message + { + MESSAGE_CLASS_DECLARATION + public: + SDRDaemonSuperBlock *getTxBlocks() const { return m_txBlockx; } + uint32_t getNbBlocsFEC() const { return m_nbBlocksFEC; } + uint32_t getTxDelay() const { return m_txDelay; } + uint16_t getFrameIndex() const { return m_frameIndex; } + + static MsgUDPFECEncodeAndSend* create( + SDRDaemonSuperBlock *txBlocks, + uint32_t nbBlocksFEC, + uint32_t txDelay, + uint16_t frameIndex) + { + return new MsgUDPFECEncodeAndSend(txBlocks, nbBlocksFEC, txDelay, frameIndex); + } + + private: + SDRDaemonSuperBlock *m_txBlockx; + uint32_t m_nbBlocksFEC; + uint32_t m_txDelay; + uint16_t m_frameIndex; + + MsgUDPFECEncodeAndSend( + SDRDaemonSuperBlock *txBlocks, + uint32_t nbBlocksFEC, + uint32_t txDelay, + uint16_t frameIndex) : + m_txBlockx(txBlocks), + m_nbBlocksFEC(nbBlocksFEC), + m_txDelay(txDelay), + m_frameIndex(frameIndex) + {} + }; + + class MsgConfigureRemoteAddress : public Message + { + MESSAGE_CLASS_DECLARATION + public: + const QString& getAddress() const { return m_address; } + uint16_t getPort() const { return m_port; } + + static MsgConfigureRemoteAddress* create(const QString& address, uint16_t port) + { + return new MsgConfigureRemoteAddress(address, port); + } + + private: + QString m_address; + uint16_t m_port; + + MsgConfigureRemoteAddress(const QString& address, uint16_t port) : + m_address(address), + m_port(port) + {} + }; + + class MsgStartStop : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getStartStop() const { return m_startStop; } + + static MsgStartStop* create(bool startStop) { + return new MsgStartStop(startStop); + } + + protected: + bool m_startStop; + + MsgStartStop(bool startStop) : + Message(), + m_startStop(startStop) + { } + }; + + UDPSinkFECWorker(); + ~UDPSinkFECWorker(); + + void startStop(bool start); + + void pushTxFrame(SDRDaemonSuperBlock *txBlocks, + uint32_t nbBlocksFEC, + uint32_t txDelay, + uint16_t frameIndex); + void setRemoteAddress(const QString& address, uint16_t port); + + MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication + +private slots: + void handleInputMessages(); + +private: + void startWork(); + void stopWork(); + void run(); + void encodeAndTransmit(SDRDaemonSuperBlock *txBlockx, uint16_t frameIndex, uint32_t nbBlocksFEC, uint32_t txDelay); + + QMutex m_startWaitMutex; + QWaitCondition m_startWaiter; + bool m_running; + CM256 m_cm256; //!< CM256 library object + bool m_cm256Valid; //!< true if CM256 library is initialized correctly + QUdpSocket *m_udpSocket; + QString m_remoteAddress; + uint16_t m_remotePort; + QHostAddress m_remoteHostAddress; +}; + +#endif /* PLUGINS_SAMPLESINK_SDRDAEMONSINK_UDPSINKFECWORKER_H_ */ diff --git a/plugins/samplesink/soapysdroutput/CMakeLists.txt b/plugins/samplesink/soapysdroutput/CMakeLists.txt new file mode 100644 index 000000000..95011bb52 --- /dev/null +++ b/plugins/samplesink/soapysdroutput/CMakeLists.txt @@ -0,0 +1,78 @@ +project(soapysdroutput) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +set(soapysdroutput_SOURCES + soapysdroutputgui.cpp + soapysdroutput.cpp + soapysdroutputplugin.cpp + soapysdroutputsettings.cpp + soapysdroutputthread.cpp +) + +set(soapysdroutput_HEADERS + soapysdroutputgui.h + soapysdroutput.h + soapysdroutputplugin.h + soapysdroutputsettings.h + soapysdroutputthread.h +) + +set(soapysdroutput_FORMS + soapysdroutputgui.ui +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${SOAPYSDRSRC}/include + ${SOAPYSDRSRC}/src +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${SOAPYSDR_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +qt5_wrap_ui(soapysdroutput_FORMS_HEADERS ${soapysdroutput_FORMS}) + +add_library(outputsoapysdr SHARED + ${soapysdroutput_SOURCES} + ${soapysdroutput_HEADERS_MOC} + ${soapysdroutput_FORMS_HEADERS} +) + +if (BUILD_DEBIAN) +target_link_libraries(outputsoapysdr + ${QT_LIBRARIES} + bladerf + sdrbase + sdrgui + swagger + soapysdrdevice +) +else (BUILD_DEBIAN) +target_link_libraries(outputsoapysdr + ${QT_LIBRARIES} + ${SOAPYSDR_LIBRARY} + sdrbase + sdrgui + swagger + soapysdrdevice +) +endif (BUILD_DEBIAN) + +target_link_libraries(outputsoapysdr Qt5::Core Qt5::Widgets) + +install(TARGETS outputsoapysdr DESTINATION lib/plugins/samplesink) diff --git a/plugins/samplesink/soapysdroutput/soapysdroutput.cpp b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp new file mode 100644 index 000000000..d3642adc4 --- /dev/null +++ b/plugins/samplesink/soapysdroutput/soapysdroutput.cpp @@ -0,0 +1,1040 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "util/simpleserializer.h" +#include "dsp/dspcommands.h" +#include "dsp/dspengine.h" +#include "device/devicesinkapi.h" +#include "device/devicesourceapi.h" +#include "soapysdr/devicesoapysdr.h" + +#include "soapysdroutputthread.h" +#include "soapysdroutput.h" + +MESSAGE_CLASS_DEFINITION(SoapySDROutput::MsgConfigureSoapySDROutput, Message) +MESSAGE_CLASS_DEFINITION(SoapySDROutput::MsgStartStop, Message) +MESSAGE_CLASS_DEFINITION(SoapySDROutput::MsgReportGainChange, Message) + +SoapySDROutput::SoapySDROutput(DeviceSinkAPI *deviceAPI) : + m_deviceAPI(deviceAPI), + m_deviceDescription("SoapySDROutput"), + m_running(false), + m_thread(0) +{ + openDevice(); + initGainSettings(m_settings); +} + +SoapySDROutput::~SoapySDROutput() +{ + if (m_running) { + stop(); + } + + closeDevice(); +} + +void SoapySDROutput::destroy() +{ + delete this; +} + +bool SoapySDROutput::openDevice() +{ + m_sampleSourceFifo.resize(m_settings.m_devSampleRate/(1<<(m_settings.m_log2Interp <= 4 ? m_settings.m_log2Interp : 4))); + + // look for Tx buddies and get reference to the device object + if (m_deviceAPI->getSinkBuddies().size() > 0) // look sink sibling first + { + qDebug("SoapySDROutput::openDevice: look in Tx buddies"); + + DeviceSinkAPI *sinkBuddy = m_deviceAPI->getSinkBuddies()[0]; + DeviceSoapySDRShared *deviceSoapySDRShared = (DeviceSoapySDRShared*) sinkBuddy->getBuddySharedPtr(); + + if (deviceSoapySDRShared == 0) + { + qCritical("SoapySDROutput::openDevice: the sink buddy shared pointer is null"); + return false; + } + + SoapySDR::Device *device = deviceSoapySDRShared->m_device; + + if (device == 0) + { + qCritical("SoapySDROutput::openDevice: cannot get device pointer from Tx buddy"); + return false; + } + + m_deviceShared.m_device = device; + m_deviceShared.m_deviceParams = deviceSoapySDRShared->m_deviceParams; + } + // look for Rx buddies and get reference to the device object + else if (m_deviceAPI->getSourceBuddies().size() > 0) // then source + { + qDebug("SoapySDROutput::openDevice: look in Rx buddies"); + + DeviceSourceAPI *sourceBuddy = m_deviceAPI->getSourceBuddies()[0]; + DeviceSoapySDRShared *deviceSoapySDRShared = (DeviceSoapySDRShared*) sourceBuddy->getBuddySharedPtr(); + + if (deviceSoapySDRShared == 0) + { + qCritical("SoapySDROutput::openDevice: the source buddy shared pointer is null"); + return false; + } + + SoapySDR::Device *device = deviceSoapySDRShared->m_device; + + if (device == 0) + { + qCritical("SoapySDROutput::openDevice: cannot get device pointer from Rx buddy"); + return false; + } + + m_deviceShared.m_device = device; + m_deviceShared.m_deviceParams = deviceSoapySDRShared->m_deviceParams; + } + // There are no buddies then create the first BladeRF2 device + else + { + qDebug("SoapySDROutput::openDevice: open device here"); + DeviceSoapySDR& deviceSoapySDR = DeviceSoapySDR::instance(); + m_deviceShared.m_device = deviceSoapySDR.openSoapySDR(m_deviceAPI->getSampleSinkSequence()); + + if (!m_deviceShared.m_device) + { + qCritical("SoapySDROutput::openDevice: cannot open SoapySDR device"); + return false; + } + + m_deviceShared.m_deviceParams = new DeviceSoapySDRParams(m_deviceShared.m_device); + } + + m_deviceShared.m_channel = m_deviceAPI->getItemIndex(); // publicly allocate channel + m_deviceShared.m_sink = this; + m_deviceAPI->setBuddySharedPtr(&m_deviceShared); // propagate common parameters to API + return true; +} + +void SoapySDROutput::closeDevice() +{ + if (m_deviceShared.m_device == 0) { // was never open + return; + } + + if (m_running) { + stop(); + } + + if (m_thread) { // stills own the thread => transfer to a buddy + moveThreadToBuddy(); + } + + m_deviceShared.m_channel = -1; // publicly release channel + m_deviceShared.m_sink = 0; + + // No buddies so effectively close the device + + if ((m_deviceAPI->getSinkBuddies().size() == 0) && (m_deviceAPI->getSourceBuddies().size() == 0)) + { + DeviceSoapySDR& deviceSoapySDR = DeviceSoapySDR::instance(); + deviceSoapySDR.closeSoapySdr(m_deviceShared.m_device); + m_deviceShared.m_device = 0; + } +} + +void SoapySDROutput::getFrequencyRange(uint64_t& min, uint64_t& max) +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getTxChannelSettings(m_deviceShared.m_channel); + + if (channelSettings && (channelSettings->m_frequencySettings.size() > 0)) + { + DeviceSoapySDRParams::FrequencySetting freqSettings = channelSettings->m_frequencySettings[0]; + SoapySDR::RangeList rangeList = freqSettings.m_ranges; + + if (rangeList.size() > 0) + { + SoapySDR::Range range = rangeList[0]; + min = range.minimum(); + max = range.maximum(); + } + else + { + min = 0; + max = 0; + } + } + else + { + min = 0; + max = 0; + } +} + +void SoapySDROutput::getGlobalGainRange(int& min, int& max) +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getTxChannelSettings(m_deviceShared.m_channel); + + if (channelSettings) + { + min = channelSettings->m_gainRange.minimum(); + max = channelSettings->m_gainRange.maximum(); + } + else + { + min = 0; + max = 0; + } +} + +bool SoapySDROutput::isAGCSupported() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getTxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_hasAGC; +} + +const SoapySDR::RangeList& SoapySDROutput::getRateRanges() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getTxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_ratesRanges; +} + +const std::vector& SoapySDROutput::getAntennas() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getTxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_antennas; +} + +const SoapySDR::RangeList& SoapySDROutput::getBandwidthRanges() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getTxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_bandwidthsRanges; +} + +const std::vector& SoapySDROutput::getTunableElements() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getTxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_frequencySettings; +} + +const std::vector& SoapySDROutput::getIndividualGainsRanges() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getTxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_gainSettings; +} + +void SoapySDROutput::initGainSettings(SoapySDROutputSettings& settings) +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getTxChannelSettings(m_deviceShared.m_channel); + settings.m_individualGains.clear(); + settings.m_globalGain = 0; + + for (const auto &it : channelSettings->m_gainSettings) { + settings.m_individualGains[QString(it.m_name.c_str())] = 0.0; + } + + updateGains(m_deviceShared.m_device, m_deviceShared.m_channel, settings); +} + +bool SoapySDROutput::hasDCAutoCorrection() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getTxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_hasDCAutoCorrection; +} + +bool SoapySDROutput::hasDCCorrectionValue() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getTxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_hasDCOffsetValue; +} + +bool SoapySDROutput::hasIQCorrectionValue() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getTxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_hasIQBalanceValue; +} + +void SoapySDROutput::init() +{ + applySettings(m_settings, true); +} + +SoapySDROutputThread *SoapySDROutput::findThread() +{ + if (m_thread == 0) // this does not own the thread + { + SoapySDROutputThread *soapySDROutputThread = 0; + + // find a buddy that has allocated the thread + const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies(); + std::vector::const_iterator it = sinkBuddies.begin(); + + for (; it != sinkBuddies.end(); ++it) + { + SoapySDROutput *buddySink = ((DeviceSoapySDRShared*) (*it)->getBuddySharedPtr())->m_sink; + + if (buddySink) + { + soapySDROutputThread = buddySink->getThread(); + + if (soapySDROutputThread) { + break; + } + } + } + + return soapySDROutputThread; + } + else + { + return m_thread; // own thread + } +} + +void SoapySDROutput::moveThreadToBuddy() +{ + const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies(); + std::vector::const_iterator it = sinkBuddies.begin(); + + for (; it != sinkBuddies.end(); ++it) + { + SoapySDROutput *buddySink = ((DeviceSoapySDRShared*) (*it)->getBuddySharedPtr())->m_sink; + + if (buddySink) + { + buddySink->setThread(m_thread); + m_thread = 0; // zero for others + } + } +} + +bool SoapySDROutput::start() +{ + // There is a single thread per physical device (Tx side). This thread is unique and referenced by a unique + // buddy in the group of sink buddies associated with this physical device. + // + // This start method is responsible for managing the thread and channel enabling when the streaming of a Tx channel is started + // + // It checks the following conditions + // - the thread is allocated or not (by itself or one of its buddies). If it is it grabs the thread pointer. + // - the requested channel is the first (0) or the following + // + // There are two possible working modes: + // - Single Output (SO) with only one channel streaming. This HAS to be channel 0. + // - Multiple Output (MO) with two or more channels. It MUST be in this configuration if any channel other than 0 + // is used. For example when we will run with only channel 2 streaming from the client perspective the channels 0 and 1 + // will actually be enabled and streaming but zero samples will be sent to it. + // + // It manages the transition form SO where only one channel (the first or channel 0) should be running to the + // Multiple Output (MO) if the requested channel is 1 or more. More generally it checks if the requested channel is within the current + // channel range allocated in the thread or past it. To perform the transition it stops the thread, deletes it and creates a new one. + // It marks the thread as needing start. + // + // If the requested channel is within the thread channel range (this thread being already allocated) it simply removes its FIFO reference + // so that the samples are not taken from the FIFO anymore and leaves the thread unchanged (no stop, no delete/new) + // + // If there is no thread allocated it creates a new one with a number of channels that fits the requested channel. That is + // 1 if channel 0 is requested (SO mode) and 3 if channel 2 is requested (MO mode). It marks the thread as needing start. + // + // Eventually it registers the FIFO in the thread. If the thread has to be started it enables the channels up to the number of channels + // allocated in the thread and starts the thread. + // + // Note: this is quite similar to the BladeRF2 start handling. The main difference is that the channel allocation (enabling) process is + // done in the thread object. + + + if (!m_deviceShared.m_device) + { + qDebug("SoapySDROutput::start: no device object"); + return false; + } + + int requestedChannel = m_deviceAPI->getItemIndex(); + SoapySDROutputThread *soapySDROutputThread = findThread(); + bool needsStart = false; + + if (soapySDROutputThread) // if thread is already allocated + { + qDebug("SoapySDROutput::start: thread is already allocated"); + + int nbOriginalChannels = soapySDROutputThread->getNbChannels(); + + if (requestedChannel+1 > nbOriginalChannels) // expansion by deleting and re-creating the thread + { + qDebug("SoapySDROutput::start: expand channels. Re-allocate thread and take ownership"); + + SampleSourceFifo **fifos = new SampleSourceFifo*[nbOriginalChannels]; + unsigned int *log2Interps = new unsigned int[nbOriginalChannels]; + + for (int i = 0; i < nbOriginalChannels; i++) // save original FIFO references and data + { + fifos[i] = soapySDROutputThread->getFifo(i); + log2Interps[i] = soapySDROutputThread->getLog2Interpolation(i); + } + + soapySDROutputThread->stopWork(); + delete soapySDROutputThread; + soapySDROutputThread = new SoapySDROutputThread(m_deviceShared.m_device, requestedChannel+1); + m_thread = soapySDROutputThread; // take ownership + + for (int i = 0; i < nbOriginalChannels; i++) // restore original FIFO references + { + soapySDROutputThread->setFifo(i, fifos[i]); + soapySDROutputThread->setLog2Interpolation(i, log2Interps[i]); + } + + // remove old thread address from buddies (reset in all buddies). The address being held only in the owning sink. + const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies(); + std::vector::const_iterator it = sinkBuddies.begin(); + + for (; it != sinkBuddies.end(); ++it) { + ((DeviceSoapySDRShared*) (*it)->getBuddySharedPtr())->m_sink->setThread(0); + } + + needsStart = true; + } + else + { + qDebug("SoapySDROutput::start: keep buddy thread"); + } + } + else // first allocation + { + qDebug("SoapySDROutput::start: allocate thread and take ownership"); + soapySDROutputThread = new SoapySDROutputThread(m_deviceShared.m_device, requestedChannel+1); + m_thread = soapySDROutputThread; // take ownership + needsStart = true; + } + + soapySDROutputThread->setFifo(requestedChannel, &m_sampleSourceFifo); + soapySDROutputThread->setLog2Interpolation(requestedChannel, m_settings.m_log2Interp); + + if (needsStart) + { + qDebug("SoapySDROutput::start: (re)sart buddy thread"); + soapySDROutputThread->setSampleRate(m_settings.m_devSampleRate); + soapySDROutputThread->startWork(); + } + + qDebug("SoapySDROutput::start: started"); + m_running = true; + + return true; +} + +void SoapySDROutput::stop() +{ + // This stop method is responsible for managing the thread and channel disabling when the streaming of + // a Tx channel is stopped + // + // If the thread is currently managing only one channel (SO mode). The thread can be just stopped and deleted. + // Then the channel is closed (disabled). + // + // If the thread is currently managing many channels (MO mode) and we are removing the last channel. The transition + // from MO to SO or reduction of MO size is handled by stopping the thread, deleting it and creating a new one + // with the maximum number of channels needed if (and only if) there is still a channel active. + // + // If the thread is currently managing many channels (MO mode) but the channel being stopped is not the last + // channel then the FIFO reference is simply removed from the thread so that this FIFO will not be used anymore. + // In this case the channel is not closed (this is managed in the thread object) so that other channels can continue with the + // same configuration. The device continues streaming on this channel but the samples are set to all zeros. + + if (!m_running) { + return; + } + + int requestedChannel = m_deviceAPI->getItemIndex(); + SoapySDROutputThread *soapySDROutputThread = findThread(); + + if (soapySDROutputThread == 0) { // no thread allocated + return; + } + + int nbOriginalChannels = soapySDROutputThread->getNbChannels(); + + if (nbOriginalChannels == 1) // SO mode => just stop and delete the thread + { + qDebug("SoapySDROutput::stop: SO mode. Just stop and delete the thread"); + soapySDROutputThread->stopWork(); + delete soapySDROutputThread; + m_thread = 0; + + // remove old thread address from buddies (reset in all buddies) + const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies(); + std::vector::const_iterator it = sinkBuddies.begin(); + + for (; it != sinkBuddies.end(); ++it) { + ((DeviceSoapySDRShared*) (*it)->getBuddySharedPtr())->m_sink->setThread(0); + } + } + else if (requestedChannel == nbOriginalChannels - 1) // remove last MO channel => reduce by deleting and re-creating the thread + { + qDebug("SoapySDROutput::stop: MO mode. Reduce by deleting and re-creating the thread"); + soapySDROutputThread->stopWork(); + SampleSourceFifo **fifos = new SampleSourceFifo*[nbOriginalChannels-1]; + unsigned int *log2Interps = new unsigned int[nbOriginalChannels-1]; + int highestActiveChannelIndex = -1; + + for (int i = 0; i < nbOriginalChannels-1; i++) // save original FIFO references + { + fifos[i] = soapySDROutputThread->getFifo(i); + + if ((soapySDROutputThread->getFifo(i) != 0) && (i > highestActiveChannelIndex)) { + highestActiveChannelIndex = i; + } + + log2Interps[i] = soapySDROutputThread->getLog2Interpolation(i); + } + + delete soapySDROutputThread; + m_thread = 0; + + if (highestActiveChannelIndex >= 0) + { + soapySDROutputThread = new SoapySDROutputThread(m_deviceShared.m_device, highestActiveChannelIndex+1); + m_thread = soapySDROutputThread; // take ownership + + for (int i = 0; i < nbOriginalChannels-1; i++) // restore original FIFO references + { + soapySDROutputThread->setFifo(i, fifos[i]); + soapySDROutputThread->setLog2Interpolation(i, log2Interps[i]); + } + } + else + { + qDebug("SoapySDROutput::stop: do not re-create thread as there are no more FIFOs active"); + } + + // remove old thread address from buddies (reset in all buddies). The address being held only in the owning sink. + const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies(); + std::vector::const_iterator it = sinkBuddies.begin(); + + for (; it != sinkBuddies.end(); ++it) { + ((DeviceSoapySDRShared*) (*it)->getBuddySharedPtr())->m_sink->setThread(0); + } + + if (highestActiveChannelIndex >= 0) + { + qDebug("SoapySDROutput::stop: restarting the thread"); + soapySDROutputThread->startWork(); + } + } + else // remove channel from existing thread + { + qDebug("SoapySDROutput::stop: MO mode. Not changing MO configuration. Just remove FIFO reference"); + soapySDROutputThread->setFifo(requestedChannel, 0); // remove FIFO + } + + applySettings(m_settings, true); // re-apply forcibly to set sample rate with the new number of channels + + m_running = false; +} + +QByteArray SoapySDROutput::serialize() const +{ + return m_settings.serialize(); +} + +bool SoapySDROutput::deserialize(const QByteArray& data __attribute__((unused))) +{ + bool success = true; + + if (!m_settings.deserialize(data)) + { + m_settings.resetToDefaults(); + success = false; + } + + MsgConfigureSoapySDROutput* message = MsgConfigureSoapySDROutput::create(m_settings, true); + m_inputMessageQueue.push(message); + + if (m_guiMessageQueue) + { + MsgConfigureSoapySDROutput* messageToGUI = MsgConfigureSoapySDROutput::create(m_settings, true); + m_guiMessageQueue->push(messageToGUI); + } + + return success; +} + +const QString& SoapySDROutput::getDeviceDescription() const +{ + return m_deviceDescription; +} + +int SoapySDROutput::getSampleRate() const +{ + int rate = m_settings.m_devSampleRate; + return (rate / (1<push(messageToGUI); + } +} + +bool SoapySDROutput::setDeviceCenterFrequency(SoapySDR::Device *dev, int requestedChannel, quint64 freq_hz, int loPpmTenths) +{ + qint64 df = ((qint64)freq_hz * loPpmTenths) / 10000000LL; + freq_hz += df; + + try + { + dev->setFrequency(SOAPY_SDR_TX, + requestedChannel, + m_deviceShared.m_deviceParams->getTxChannelMainTunableElementName(requestedChannel), + freq_hz); + qDebug("SoapySDROutput::setDeviceCenterFrequency: setFrequency(%llu)", freq_hz); + return true; + } + catch (const std::exception &ex) + { + qCritical("SoapySDROutput::applySettings: could not set frequency: %llu: %s", freq_hz, ex.what()); + return false; + } +} + +void SoapySDROutput::updateGains(SoapySDR::Device *dev, int requestedChannel, SoapySDROutputSettings& settings) +{ + if (dev == 0) { + return; + } + + settings.m_globalGain = round(dev->getGain(SOAPY_SDR_TX, requestedChannel)); + + for (const auto &name : settings.m_individualGains.keys()) { + settings.m_individualGains[name] = dev->getGain(SOAPY_SDR_TX, requestedChannel, name.toStdString()); + } +} + +bool SoapySDROutput::handleMessage(const Message& message) +{ + if (MsgConfigureSoapySDROutput::match(message)) + { + MsgConfigureSoapySDROutput& conf = (MsgConfigureSoapySDROutput&) message; + qDebug() << "SoapySDROutput::handleMessage: MsgConfigureSoapySDROutput"; + + if (!applySettings(conf.getSettings(), conf.getForce())) { + qDebug("SoapySDROutput::handleMessage: MsgConfigureSoapySDROutput config error"); + } + + return true; + } + else if (MsgStartStop::match(message)) + { + MsgStartStop& cmd = (MsgStartStop&) message; + qDebug() << "SoapySDROutput::handleMessage: MsgStartStop: " << (cmd.getStartStop() ? "start" : "stop"); + + if (cmd.getStartStop()) + { + if (m_deviceAPI->initGeneration()) + { + m_deviceAPI->startGeneration(); + } + } + else + { + m_deviceAPI->stopGeneration(); + } + + return true; + } + else if (DeviceSoapySDRShared::MsgReportBuddyChange::match(message)) + { + int requestedChannel = m_deviceAPI->getItemIndex(); + //DeviceSoapySDRShared::MsgReportBuddyChange& report = (DeviceSoapySDRShared::MsgReportBuddyChange&) message; + SoapySDROutputSettings settings = m_settings; + //bool fromRxBuddy = report.getRxElseTx(); + + double centerFrequency = m_deviceShared.m_device->getFrequency( + SOAPY_SDR_TX, + requestedChannel, + m_deviceShared.m_deviceParams->getTxChannelMainTunableElementName(requestedChannel)); + + settings.m_centerFrequency = round(centerFrequency/1000.0) * 1000; + settings.m_devSampleRate = round(m_deviceShared.m_device->getSampleRate(SOAPY_SDR_TX, requestedChannel)); + settings.m_bandwidth = round(m_deviceShared.m_device->getBandwidth(SOAPY_SDR_TX, requestedChannel)); + + //SoapySDROutputThread *outputThread = findThread(); + + m_settings = settings; + + // propagate settings to GUI if any + if (getMessageQueueToGUI()) + { + MsgConfigureSoapySDROutput *reportToGUI = MsgConfigureSoapySDROutput::create(m_settings, false); + getMessageQueueToGUI()->push(reportToGUI); + } + + return true; + } + else + { + return false; + } +} + +bool SoapySDROutput::applySettings(const SoapySDROutputSettings& settings, bool force) +{ + bool forwardChangeOwnDSP = false; + bool forwardChangeToBuddies = false; + bool globalGainChanged = false; + bool individualGainsChanged = false; + + SoapySDR::Device *dev = m_deviceShared.m_device; + SoapySDROutputThread *outputThread = findThread(); + int requestedChannel = m_deviceAPI->getItemIndex(); + qint64 xlatedDeviceCenterFrequency = settings.m_centerFrequency; + xlatedDeviceCenterFrequency -= settings.m_transverterMode ? settings.m_transverterDeltaFrequency : 0; + xlatedDeviceCenterFrequency = xlatedDeviceCenterFrequency < 0 ? 0 : xlatedDeviceCenterFrequency; + + // resize FIFO + if ((m_settings.m_devSampleRate != settings.m_devSampleRate) || (m_settings.m_log2Interp != settings.m_log2Interp) || force) + { + SoapySDROutputThread *soapySDROutputThread = findThread(); + SampleSourceFifo *fifo = 0; + + if (soapySDROutputThread) + { + fifo = soapySDROutputThread->getFifo(requestedChannel); + soapySDROutputThread->setFifo(requestedChannel, 0); + } + + int fifoSize; + + if (settings.m_log2Interp >= 5) + { + fifoSize = DeviceSoapySDRShared::m_sampleFifoMinSize32; + } + else + { + fifoSize = std::max( + (int) ((settings.m_devSampleRate/(1<setFifo(requestedChannel, &m_sampleSourceFifo); + } + } + + if ((m_settings.m_devSampleRate != settings.m_devSampleRate) || force) + { + forwardChangeOwnDSP = true; + forwardChangeToBuddies = true; + + if (dev != 0) + { + try + { + dev->setSampleRate(SOAPY_SDR_TX, requestedChannel, settings.m_devSampleRate); + qDebug() << "SoapySDROutput::applySettings: setSampleRate OK: " << settings.m_devSampleRate; + + if (outputThread) + { + bool wasRunning = outputThread->isRunning(); + outputThread->stopWork(); + outputThread->setSampleRate(settings.m_devSampleRate); + + if (wasRunning) { + outputThread->startWork(); + } + } + } + catch (const std::exception &ex) + { + qCritical("SoapySDROutput::applySettings: could not set sample rate: %d: %s", + settings.m_devSampleRate, ex.what()); + } + } + } + + if ((m_settings.m_log2Interp != settings.m_log2Interp) || force) + { + forwardChangeOwnDSP = true; + + if (outputThread != 0) + { + outputThread->setLog2Interpolation(requestedChannel, settings.m_log2Interp); + qDebug() << "SoapySDROutput::applySettings: set decimation to " << (1<setAntenna(SOAPY_SDR_TX, requestedChannel, settings.m_antenna.toStdString()); + qDebug("SoapySDROutput::applySettings: set antenna to %s", settings.m_antenna.toStdString().c_str()); + } + catch (const std::exception &ex) + { + qCritical("SoapySDROutput::applySettings: cannot set antenna to %s: %s", + settings.m_antenna.toStdString().c_str(), ex.what()); + } + } + } + + if ((m_settings.m_bandwidth != settings.m_bandwidth) || force) + { + forwardChangeToBuddies = true; + + if (dev != 0) + { + try + { + dev->setBandwidth(SOAPY_SDR_TX, requestedChannel, settings.m_bandwidth); + qDebug("SoapySDROutput::applySettings: bandwidth set to %u", settings.m_bandwidth); + } + catch (const std::exception &ex) + { + qCritical("SoapySDROutput::applySettings: cannot set bandwidth to %u: %s", + settings.m_bandwidth, ex.what()); + } + } + } + + for (const auto &oname : m_settings.m_tunableElements.keys()) + { + auto nvalue = settings.m_tunableElements.find(oname); + + if (nvalue != settings.m_tunableElements.end() && (m_settings.m_tunableElements[oname] != *nvalue)) + { + if (dev != 0) + { + try + { + dev->setFrequency(SOAPY_SDR_TX, requestedChannel, oname.toStdString(), *nvalue); + qDebug("SoapySDROutput::applySettings: tunable element %s frequency set to %lf", + oname.toStdString().c_str(), *nvalue); + } + catch (const std::exception &ex) + { + qCritical("SoapySDROutput::applySettings: cannot set tunable element %s to %lf: %s", + oname.toStdString().c_str(), *nvalue, ex.what()); + } + } + + m_settings.m_tunableElements[oname] = *nvalue; + } + } + + if ((m_settings.m_globalGain != settings.m_globalGain) || force) + { + if (dev != 0) + { + try + { + dev->setGain(SOAPY_SDR_TX, requestedChannel, settings.m_globalGain); + qDebug("SoapySDROutput::applySettings: set global gain to %d", settings.m_globalGain); + globalGainChanged = true; + } + catch (const std::exception &ex) + { + qCritical("SoapySDROutput::applySettings: cannot set global gain to %d: %s", + settings.m_globalGain, ex.what()); + } + } + } + + for (const auto &oname : m_settings.m_individualGains.keys()) + { + auto nvalue = settings.m_individualGains.find(oname); + + if (nvalue != settings.m_individualGains.end() && ((m_settings.m_individualGains[oname] != *nvalue) || force)) + { + if (dev != 0) + { + try + { + dev->setGain(SOAPY_SDR_TX, requestedChannel, oname.toStdString(), *nvalue); + qDebug("SoapySDROutput::applySettings: individual gain %s set to %lf", + oname.toStdString().c_str(), *nvalue); + individualGainsChanged = true; + } + catch (const std::exception &ex) + { + qCritical("SoapySDROutput::applySettings: cannot set individual gain %s to %lf: %s", + oname.toStdString().c_str(), *nvalue, ex.what()); + } + } + + m_settings.m_individualGains[oname] = *nvalue; + } + } + + if ((m_settings.m_autoGain != settings.m_autoGain) || force) + { + if (dev != 0) + { + try + { + dev->setGainMode(SOAPY_SDR_TX, requestedChannel, settings.m_autoGain); + qDebug("SoapySDROutput::applySettings: %s AGC", settings.m_autoGain ? "set" : "unset"); + } + catch (const std::exception &ex) + { + qCritical("SoapySDROutput::applySettings: cannot %s AGC", settings.m_autoGain ? "set" : "unset"); + } + } + } + + if ((m_settings.m_autoDCCorrection != settings.m_autoDCCorrection) || force) + { + if ((dev != 0) && hasDCAutoCorrection()) + { + try + { + dev->setDCOffsetMode(SOAPY_SDR_TX, requestedChannel, settings.m_autoDCCorrection); + qDebug("SoapySDROutput::applySettings: %s DC auto correction", settings.m_autoDCCorrection ? "set" : "unset"); + } + catch (const std::exception &ex) + { + qCritical("SoapySDROutput::applySettings: cannot %s DC auto correction", settings.m_autoDCCorrection ? "set" : "unset"); + } + } + } + + if ((m_settings.m_dcCorrection != settings.m_dcCorrection) || force) + { + if ((dev != 0) && hasDCCorrectionValue()) + { + try + { + dev->setDCOffset(SOAPY_SDR_TX, requestedChannel, settings.m_dcCorrection); + qDebug("SoapySDROutput::applySettings: DC offset correction set to (%lf, %lf)", settings.m_dcCorrection.real(), settings.m_dcCorrection.imag()); + } + catch (const std::exception &ex) + { + qCritical("SoapySDROutput::applySettings: cannot set DC offset correction to (%lf, %lf)", settings.m_dcCorrection.real(), settings.m_dcCorrection.imag()); + } + } + } + + if ((m_settings.m_iqCorrection != settings.m_iqCorrection) || force) + { + if ((dev != 0) && hasIQCorrectionValue()) + { + try + { + dev->setIQBalance(SOAPY_SDR_TX, requestedChannel, settings.m_iqCorrection); + qDebug("SoapySDROutput::applySettings: IQ balance correction set to (%lf, %lf)", settings.m_iqCorrection.real(), settings.m_iqCorrection.imag()); + } + catch (const std::exception &ex) + { + qCritical("SoapySDROutput::applySettings: cannot set IQ balance correction to (%lf, %lf)", settings.m_iqCorrection.real(), settings.m_iqCorrection.imag()); + } + } + } + + if (forwardChangeOwnDSP) + { + int sampleRate = settings.m_devSampleRate/(1<getDeviceEngineInputMessageQueue()->push(notif); + } + + if (forwardChangeToBuddies) + { + // send to source buddies + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies(); + + for (const auto &itSource : sourceBuddies) + { + DeviceSoapySDRShared::MsgReportBuddyChange *report = DeviceSoapySDRShared::MsgReportBuddyChange::create( + settings.m_centerFrequency, + settings.m_LOppmTenths, + 2, + settings.m_devSampleRate, + false); + itSource->getSampleSourceInputMessageQueue()->push(report); + } + + for (const auto &itSink : sinkBuddies) + { + DeviceSoapySDRShared::MsgReportBuddyChange *report = DeviceSoapySDRShared::MsgReportBuddyChange::create( + settings.m_centerFrequency, + settings.m_LOppmTenths, + 2, + settings.m_devSampleRate, + false); + itSink->getSampleSinkInputMessageQueue()->push(report); + } + } + + m_settings = settings; + + if (globalGainChanged || individualGainsChanged) + { + if (dev) { + updateGains(dev, requestedChannel, m_settings); + } + + if (getMessageQueueToGUI()) + { + MsgReportGainChange *report = MsgReportGainChange::create(m_settings, individualGainsChanged, globalGainChanged); + getMessageQueueToGUI()->push(report); + } + } + + qDebug() << "SoapySDROutput::applySettings: " + << " m_transverterMode: " << m_settings.m_transverterMode + << " m_transverterDeltaFrequency: " << m_settings.m_transverterDeltaFrequency + << " m_centerFrequency: " << m_settings.m_centerFrequency << " Hz" + << " m_LOppmTenths: " << m_settings.m_LOppmTenths + << " m_log2Interp: " << m_settings.m_log2Interp + << " m_devSampleRate: " << m_settings.m_devSampleRate + << " m_bandwidth: " << m_settings.m_bandwidth + << " m_globalGain: " << m_settings.m_globalGain; + + return true; +} diff --git a/plugins/samplesink/soapysdroutput/soapysdroutput.h b/plugins/samplesink/soapysdroutput/soapysdroutput.h new file mode 100644 index 000000000..5fb9cc3f2 --- /dev/null +++ b/plugins/samplesink/soapysdroutput/soapysdroutput.h @@ -0,0 +1,161 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUT_H_ +#define PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUT_H_ + +#include +#include +#include + +#include "dsp/devicesamplesink.h" +#include "soapysdr/devicesoapysdrshared.h" + +#include "soapysdroutputsettings.h" + +class DeviceSinkAPI; +class SoapySDROutputThread; + +namespace SoapySDR +{ + class Device; +} + +class SoapySDROutput : public DeviceSampleSink { +public: + class MsgConfigureSoapySDROutput : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const SoapySDROutputSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureSoapySDROutput* create(const SoapySDROutputSettings& settings, bool force) + { + return new MsgConfigureSoapySDROutput(settings, force); + } + + private: + SoapySDROutputSettings m_settings; + bool m_force; + + MsgConfigureSoapySDROutput(const SoapySDROutputSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgReportGainChange : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const SoapySDROutputSettings& getSettings() const { return m_settings; } + bool getGlobalGain() const { return m_globalGain; } + bool getIndividualGains() const { return m_individualGains; } + + static MsgReportGainChange* create(const SoapySDROutputSettings& settings, bool globalGain, bool individualGains) + { + return new MsgReportGainChange(settings, globalGain, individualGains); + } + + private: + SoapySDROutputSettings m_settings; + bool m_globalGain; + bool m_individualGains; + + MsgReportGainChange(const SoapySDROutputSettings& settings, bool globalGain, bool individualGains) : + Message(), + m_settings(settings), + m_globalGain(globalGain), + m_individualGains(individualGains) + { } + }; + + class MsgStartStop : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getStartStop() const { return m_startStop; } + + static MsgStartStop* create(bool startStop) { + return new MsgStartStop(startStop); + } + + protected: + bool m_startStop; + + MsgStartStop(bool startStop) : + Message(), + m_startStop(startStop) + { } + }; + + SoapySDROutput(DeviceSinkAPI *deviceAPI); + virtual ~SoapySDROutput(); + virtual void destroy(); + + virtual void init(); + virtual bool start(); + virtual void stop(); + SoapySDROutputThread *getThread() { return m_thread; } + void setThread(SoapySDROutputThread *thread) { m_thread = thread; } + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual void setMessageQueueToGUI(MessageQueue *queue) { m_guiMessageQueue = queue; } + virtual const QString& getDeviceDescription() const; + virtual int getSampleRate() const; + virtual quint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + + virtual bool handleMessage(const Message& message); + + void getFrequencyRange(uint64_t& min, uint64_t& max); + void getGlobalGainRange(int& min, int& max); + bool isAGCSupported(); + const SoapySDR::RangeList& getRateRanges(); + const std::vector& getAntennas(); + const SoapySDR::RangeList& getBandwidthRanges(); + const std::vector& getTunableElements(); + const std::vector& getIndividualGainsRanges(); + void initGainSettings(SoapySDROutputSettings& settings); + bool hasDCAutoCorrection(); + bool hasDCCorrectionValue(); + bool hasIQAutoCorrection() { return false; } // not in SoapySDR interface + bool hasIQCorrectionValue(); + +private: + DeviceSinkAPI *m_deviceAPI; + QMutex m_mutex; + SoapySDROutputSettings m_settings; + QString m_deviceDescription; + bool m_running; + SoapySDROutputThread *m_thread; + DeviceSoapySDRShared m_deviceShared; + + bool openDevice(); + void closeDevice(); + SoapySDROutputThread *findThread(); + void moveThreadToBuddy(); + bool applySettings(const SoapySDROutputSettings& settings, bool force = false); + bool setDeviceCenterFrequency(SoapySDR::Device *dev, int requestedChannel, quint64 freq_hz, int loPpmTenths); + void updateGains(SoapySDR::Device *dev, int requestedChannel, SoapySDROutputSettings& settings); +}; + + +#endif /* PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUT_H_ */ diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp b/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp new file mode 100644 index 000000000..dbee2b4d4 --- /dev/null +++ b/plugins/samplesink/soapysdroutput/soapysdroutputgui.cpp @@ -0,0 +1,714 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "device/devicesinkapi.h" +#include "device/deviceuiset.h" +#include "util/simpleserializer.h" +#include "ui_soapysdroutputgui.h" +#include "gui/glspectrum.h" +#include "soapygui/discreterangegui.h" +#include "soapygui/intervalrangegui.h" +#include "soapygui/stringrangegui.h" +#include "soapygui/dynamicitemsettinggui.h" +#include "soapygui/intervalslidergui.h" +#include "soapygui/complexfactorgui.h" + +#include "soapysdroutputgui.h" + +SoapySDROutputGui::SoapySDROutputGui(DeviceUISet *deviceUISet, QWidget* parent) : + QWidget(parent), + ui(new Ui::SoapySDROutputGui), + m_deviceUISet(deviceUISet), + m_forceSettings(true), + m_doApplySettings(true), + m_sampleSink(0), + m_sampleRate(0), + m_lastEngineState(DSPDeviceSinkEngine::StNotStarted), + m_antennas(0), + m_sampleRateGUI(0), + m_bandwidthGUI(0), + m_gainSliderGUI(0), + m_autoGain(0), + m_dcCorrectionGUI(0), + m_iqCorrectionGUI(0), + m_autoDCCorrection(0), + m_autoIQCorrection(0) +{ + m_sampleSink = (SoapySDROutput*) m_deviceUISet->m_deviceSinkAPI->getSampleSink(); + ui->setupUi(this); + + ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); + uint64_t f_min, f_max; + m_sampleSink->getFrequencyRange(f_min, f_max); + ui->centerFrequency->setValueRange(7, f_min/1000, f_max/1000); + + createCorrectionsControl(); + createAntennasControl(m_sampleSink->getAntennas()); + createRangesControl(&m_sampleRateGUI, m_sampleSink->getRateRanges(), "SR", "S/s"); + createRangesControl(&m_bandwidthGUI, m_sampleSink->getBandwidthRanges(), "BW", "Hz"); + createTunableElementsControl(m_sampleSink->getTunableElements()); + createGlobalGainControl(); + createIndividualGainsControl(m_sampleSink->getIndividualGainsRanges()); + m_sampleSink->initGainSettings(m_settings); + + if (m_sampleRateGUI) { + connect(m_sampleRateGUI, SIGNAL(valueChanged(double)), this, SLOT(sampleRateChanged(double))); + } + if (m_bandwidthGUI) { + connect(m_bandwidthGUI, SIGNAL(valueChanged(double)), this, SLOT(bandwidthChanged(double))); + } + + connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); + connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); + m_statusTimer.start(500); + + displaySettings(); + + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSink->setMessageQueueToGUI(&m_inputMessageQueue); + + sendSettings(); +} + +SoapySDROutputGui::~SoapySDROutputGui() +{ + delete ui; +} + +void SoapySDROutputGui::destroy() +{ + delete this; +} + +void SoapySDROutputGui::createRangesControl( + ItemSettingGUI **settingGUI, + const SoapySDR::RangeList& rangeList, + const QString& text, + const QString& unit) +{ + if (rangeList.size() == 0) { // return early if the range list is empty + return; + } + + bool rangeDiscrete = true; // discretes values + bool rangeInterval = true; // intervals + + for (const auto &it : rangeList) + { + if (it.minimum() != it.maximum()) { + rangeDiscrete = false; + } else { + rangeInterval = false; + } + } + + if (rangeDiscrete) + { + DiscreteRangeGUI *rangeGUI = new DiscreteRangeGUI(this); + rangeGUI->setLabel(text); + rangeGUI->setUnits(QString("k%1").arg(unit)); + + for (const auto &it : rangeList) { + rangeGUI->addItem(QString("%1").arg(QString::number(it.minimum()/1000.0, 'f', 0)), it.minimum()); + } + + *settingGUI = rangeGUI; + QVBoxLayout *layout = (QVBoxLayout *) ui->scrollAreaWidgetContents->layout(); + layout->addWidget(rangeGUI); + } + else if (rangeInterval) + { + IntervalRangeGUI *rangeGUI = new IntervalRangeGUI(this); + rangeGUI->setLabel(text); + rangeGUI->setUnits(unit); + + for (const auto &it : rangeList) { + rangeGUI->addInterval(it.minimum(), it.maximum()); + } + + rangeGUI->reset(); + + *settingGUI = rangeGUI; + QVBoxLayout *layout = (QVBoxLayout *) ui->scrollAreaWidgetContents->layout(); + layout->addWidget(rangeGUI); + } +} + +void SoapySDROutputGui::createAntennasControl(const std::vector& antennaList) +{ + if (antennaList.size() == 0) { // return early if the antenna list is empty + return; + } + + m_antennas = new StringRangeGUI(this); + m_antennas->setLabel(QString("RF out")); + m_antennas->setUnits(QString("Port")); + + for (const auto &it : antennaList) { + m_antennas->addItem(QString(it.c_str()), it); + } + + QVBoxLayout *layout = (QVBoxLayout *) ui->scrollAreaWidgetContents->layout(); + layout->addWidget(m_antennas); + + connect(m_antennas, SIGNAL(valueChanged()), this, SLOT(antennasChanged())); +} + +void SoapySDROutputGui::createTunableElementsControl(const std::vector& tunableElementsList) +{ + if (tunableElementsList.size() <= 1) { // This list is created for other elements than the main one (RF) which is always at index 0 + return; + } + + std::vector::const_iterator it = tunableElementsList.begin() + 1; + + for (int i = 0; it != tunableElementsList.end(); ++it, i++) + { + if (it->m_ranges.size() == 0) { // skip empty ranges lists + continue; + } + + ItemSettingGUI *rangeGUI; + createRangesControl( + &rangeGUI, + it->m_ranges, + QString("%1 freq").arg(it->m_name.c_str()), + QString((it->m_name == "CORR") ? "ppm" : "Hz")); + DynamicItemSettingGUI *gui = new DynamicItemSettingGUI(rangeGUI, QString(it->m_name.c_str())); + m_tunableElementsGUIs.push_back(gui); + connect(m_tunableElementsGUIs.back(), SIGNAL(valueChanged(QString, double)), this, SLOT(tunableElementChanged(QString, double))); + } +} + +void SoapySDROutputGui::createGlobalGainControl() +{ + m_gainSliderGUI = new IntervalSliderGUI(this); + int min, max; + m_sampleSink->getGlobalGainRange(min, max); + m_gainSliderGUI->setInterval(min, max); + m_gainSliderGUI->setLabel(QString("Global gain")); + m_gainSliderGUI->setUnits(QString("")); + + QVBoxLayout *layout = (QVBoxLayout *) ui->scrollAreaWidgetContents->layout(); + + QFrame *line = new QFrame(this); + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Sunken); + layout->addWidget(line); + + if (m_sampleSink->isAGCSupported()) + { + m_autoGain = new QCheckBox(this); + m_autoGain->setText(QString("AGC")); + layout->addWidget(m_autoGain); + + connect(m_autoGain, SIGNAL(toggled(bool)), this, SLOT(autoGainChanged(bool))); + } + + layout->addWidget(m_gainSliderGUI); + + connect(m_gainSliderGUI, SIGNAL(valueChanged(double)), this, SLOT(globalGainChanged(double))); +} + +void SoapySDROutputGui::createIndividualGainsControl(const std::vector& individualGainsList) +{ + if (individualGainsList.size() == 0) { // Leave early if list of individual gains is empty + return; + } + + QVBoxLayout *layout = (QVBoxLayout *) ui->scrollAreaWidgetContents->layout(); + std::vector::const_iterator it = individualGainsList.begin(); + + for (int i = 0; it != individualGainsList.end(); ++it, i++) + { + IntervalSliderGUI *gainGUI = new IntervalSliderGUI(this); + gainGUI->setInterval(it->m_range.minimum(), it->m_range.maximum()); + gainGUI->setLabel(QString("%1 gain").arg(it->m_name.c_str())); + gainGUI->setUnits(QString("")); + DynamicItemSettingGUI *gui = new DynamicItemSettingGUI(gainGUI, QString(it->m_name.c_str())); + layout->addWidget(gainGUI); + m_individualGainsGUIs.push_back(gui); + connect(m_individualGainsGUIs.back(), SIGNAL(valueChanged(QString, double)), this, SLOT(individualGainChanged(QString, double))); + } +} + +void SoapySDROutputGui::createCorrectionsControl() +{ + QVBoxLayout *layout = (QVBoxLayout *) ui->scrollAreaWidgetContents->layout(); + + if (m_sampleSink->hasDCCorrectionValue()) // complex GUI + { + m_dcCorrectionGUI = new ComplexFactorGUI(this); + m_dcCorrectionGUI->setLabel(QString("DC")); + m_dcCorrectionGUI->setAutomaticEnable(m_sampleSink->hasDCAutoCorrection()); + layout->addWidget(m_dcCorrectionGUI); + + connect(m_dcCorrectionGUI, SIGNAL(moduleChanged(double)), this, SLOT(dcCorrectionModuleChanged(double))); + connect(m_dcCorrectionGUI, SIGNAL(argumentChanged(double)), this, SLOT(dcCorrectionArgumentChanged(double))); + + if (m_sampleSink->hasDCAutoCorrection()) { + connect(m_dcCorrectionGUI, SIGNAL(automaticChanged(bool)), this, SLOT(autoDCCorrectionChanged(bool))); + } + } + else if (m_sampleSink->hasDCAutoCorrection()) // simple checkbox + { + m_autoDCCorrection = new QCheckBox(this); + m_autoDCCorrection->setText(QString("Auto DC corr")); + m_autoDCCorrection->setToolTip(QString("Automatic hardware DC offset correction")); + layout->addWidget(m_autoDCCorrection); + + connect(m_autoDCCorrection, SIGNAL(toggled(bool)), this, SLOT(autoDCCorrectionChanged(bool))); + } + + if (m_sampleSink->hasIQCorrectionValue()) // complex GUI + { + m_iqCorrectionGUI = new ComplexFactorGUI(this); + m_iqCorrectionGUI->setLabel(QString("IQ")); + m_iqCorrectionGUI->setAutomaticEnable(m_sampleSink->hasIQAutoCorrection()); + layout->addWidget(m_iqCorrectionGUI); + + connect(m_iqCorrectionGUI, SIGNAL(moduleChanged(double)), this, SLOT(iqCorrectionModuleChanged(double))); + connect(m_iqCorrectionGUI, SIGNAL(argumentChanged(double)), this, SLOT(iqCorrectionArgumentChanged(double))); + + if (m_sampleSink->hasIQAutoCorrection()) { + connect(m_iqCorrectionGUI, SIGNAL(automaticChanged(bool)), this, SLOT(autoIQCorrectionChanged(bool))); + } + } + else if (m_sampleSink->hasIQAutoCorrection()) // simple checkbox + { + m_autoIQCorrection = new QCheckBox(this); + m_autoIQCorrection->setText(QString("Auto IQ corr")); + m_autoIQCorrection->setToolTip(QString("Automatic hardware IQ imbalance correction")); + layout->addWidget(m_autoIQCorrection); + + connect(m_autoIQCorrection, SIGNAL(toggled(bool)), this, SLOT(autoIQCorrectionChanged(bool))); + } +} + +void SoapySDROutputGui::setName(const QString& name) +{ + setObjectName(name); +} + +QString SoapySDROutputGui::getName() const +{ + return objectName(); +} + +void SoapySDROutputGui::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + sendSettings(); +} + +qint64 SoapySDROutputGui::getCenterFrequency() const +{ + return m_settings.m_centerFrequency; +} + +void SoapySDROutputGui::setCenterFrequency(qint64 centerFrequency) +{ + m_settings.m_centerFrequency = centerFrequency; + displaySettings(); + sendSettings(); +} + +QByteArray SoapySDROutputGui::serialize() const +{ + return m_settings.serialize(); +} + +bool SoapySDROutputGui::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) { + displaySettings(); + m_forceSettings = true; + sendSettings(); + return true; + } else { + resetToDefaults(); + return false; + } +} + + +bool SoapySDROutputGui::handleMessage(const Message& message) +{ + if (SoapySDROutput::MsgConfigureSoapySDROutput::match(message)) + { + const SoapySDROutput::MsgConfigureSoapySDROutput& cfg = (SoapySDROutput::MsgConfigureSoapySDROutput&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + + return true; + } + else if (SoapySDROutput::MsgReportGainChange::match(message)) + { + const SoapySDROutput::MsgReportGainChange& report = (SoapySDROutput::MsgReportGainChange&) message; + const SoapySDROutputSettings& gainSettings = report.getSettings(); + + if (report.getGlobalGain()) { + m_settings.m_globalGain = gainSettings.m_globalGain; + } + if (report.getIndividualGains()) { + m_settings.m_individualGains = gainSettings.m_individualGains; + } + + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + + return true; + } + else if (SoapySDROutput::MsgStartStop::match(message)) + { + SoapySDROutput::MsgStartStop& notif = (SoapySDROutput::MsgStartStop&) message; + blockApplySettings(true); + ui->startStop->setChecked(notif.getStartStop()); + blockApplySettings(false); + + return true; + } + else + { + return false; + } +} + +void SoapySDROutputGui::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + qDebug("SoapySDROutputGui::handleInputMessages: message: %s", message->getIdentifier()); + + if (DSPSignalNotification::match(*message)) + { + DSPSignalNotification* notif = (DSPSignalNotification*) message; + m_sampleRate = notif->getSampleRate(); + m_deviceCenterFrequency = notif->getCenterFrequency(); + qDebug("SoapySDROutputGui::handleInputMessages: DSPSignalNotification: SampleRate:%d, CenterFrequency:%llu", notif->getSampleRate(), notif->getCenterFrequency()); + updateSampleRateAndFrequency(); + + delete message; + } + else + { + if (handleMessage(*message)) + { + delete message; + } + } + } +} + +void SoapySDROutputGui::sampleRateChanged(double sampleRate) +{ + m_settings.m_devSampleRate = sampleRate; + sendSettings(); +} + +void SoapySDROutputGui::antennasChanged() +{ + const std::string& antennaStr = m_antennas->getCurrentValue(); + m_settings.m_antenna = QString(antennaStr.c_str()); + sendSettings(); +} + +void SoapySDROutputGui::bandwidthChanged(double bandwidth) +{ + m_settings.m_bandwidth = bandwidth; + sendSettings(); +} + +void SoapySDROutputGui::tunableElementChanged(QString name, double value) +{ + m_settings.m_tunableElements[name] = value; + sendSettings(); +} + +void SoapySDROutputGui::globalGainChanged(double gain) +{ + m_settings.m_globalGain = round(gain); + sendSettings(); +} + +void SoapySDROutputGui::autoGainChanged(bool set) +{ + m_settings.m_autoGain = set; + sendSettings(); +} + +void SoapySDROutputGui::individualGainChanged(QString name, double value) +{ + m_settings.m_individualGains[name] = value; + sendSettings(); +} + +void SoapySDROutputGui::autoDCCorrectionChanged(bool set) +{ + m_settings.m_autoDCCorrection = set; + sendSettings(); +} + +void SoapySDROutputGui::autoIQCorrectionChanged(bool set) +{ + m_settings.m_autoIQCorrection = set; + sendSettings(); +} + +void SoapySDROutputGui::dcCorrectionModuleChanged(double value) +{ + std::complex dcCorrection = std::polar(value, arg(m_settings.m_dcCorrection)); + m_settings.m_dcCorrection = dcCorrection; + sendSettings(); +} + +void SoapySDROutputGui::dcCorrectionArgumentChanged(double value) +{ + double angleInRadians = (value / 180.0) * M_PI; + std::complex dcCorrection = std::polar(abs(m_settings.m_dcCorrection), angleInRadians); + m_settings.m_dcCorrection = dcCorrection; + sendSettings(); +} + +void SoapySDROutputGui::iqCorrectionModuleChanged(double value) +{ + std::complex iqCorrection = std::polar(value, arg(m_settings.m_iqCorrection)); + m_settings.m_iqCorrection = iqCorrection; + sendSettings(); +} + +void SoapySDROutputGui::iqCorrectionArgumentChanged(double value) +{ + double angleInRadians = (value / 180.0) * M_PI; + std::complex iqCorrection = std::polar(abs(m_settings.m_iqCorrection), angleInRadians); + m_settings.m_iqCorrection = iqCorrection; + sendSettings(); +} + +void SoapySDROutputGui::on_centerFrequency_changed(quint64 value) +{ + m_settings.m_centerFrequency = value * 1000; + sendSettings(); +} + +void SoapySDROutputGui::on_interp_currentIndexChanged(int index) +{ + if ((index <0) || (index > 6)) + return; + m_settings.m_log2Interp = index; + sendSettings(); +} + +void SoapySDROutputGui::on_transverter_clicked() +{ + m_settings.m_transverterMode = ui->transverter->getDeltaFrequencyAcive(); + m_settings.m_transverterDeltaFrequency = ui->transverter->getDeltaFrequency(); + qDebug("SoapySDROutputGui::on_transverter_clicked: %lld Hz %s", m_settings.m_transverterDeltaFrequency, m_settings.m_transverterMode ? "on" : "off"); + updateFrequencyLimits(); + setCenterFrequencySetting(ui->centerFrequency->getValueNew()); + sendSettings(); +} + +void SoapySDROutputGui::on_LOppm_valueChanged(int value) +{ + ui->LOppmText->setText(QString("%1").arg(QString::number(value/10.0, 'f', 1))); + m_settings.m_LOppmTenths = value; + sendSettings(); +} + +void SoapySDROutputGui::on_startStop_toggled(bool checked) +{ + if (m_doApplySettings) + { + SoapySDROutput::MsgStartStop *message = SoapySDROutput::MsgStartStop::create(checked); + m_sampleSink->getInputMessageQueue()->push(message); + } +} + +void SoapySDROutputGui::displaySettings() +{ + blockApplySettings(true); + + ui->centerFrequency->setValue(m_settings.m_centerFrequency / 1000); + + if (m_antennas) { + m_antennas->setValue(m_settings.m_antenna.toStdString()); + } + if (m_sampleRateGUI) { + m_sampleRateGUI->setValue(m_settings.m_devSampleRate); + } + if (m_bandwidthGUI) { + m_bandwidthGUI->setValue(m_settings.m_bandwidth); + } + if (m_gainSliderGUI) { + m_gainSliderGUI->setValue(m_settings.m_globalGain); + } + if (m_autoGain) { + m_autoGain->setChecked(m_settings.m_autoGain); + } + + ui->interp->setCurrentIndex(m_settings.m_log2Interp); + + ui->LOppm->setValue(m_settings.m_LOppmTenths); + ui->LOppmText->setText(QString("%1").arg(QString::number(m_settings.m_LOppmTenths/10.0, 'f', 1))); + + displayTunableElementsControlSettings(); + displayIndividualGainsControlSettings(); + displayCorrectionsSettings(); + + blockApplySettings(false); +} + +void SoapySDROutputGui::displayTunableElementsControlSettings() +{ + for (const auto &it : m_tunableElementsGUIs) + { + QMap::const_iterator elIt = m_settings.m_tunableElements.find(it->getName()); + + if (elIt != m_settings.m_tunableElements.end()) { + it->setValue(*elIt); + } + } +} + +void SoapySDROutputGui::displayIndividualGainsControlSettings() +{ + for (const auto &it : m_individualGainsGUIs) + { + QMap::const_iterator elIt = m_settings.m_individualGains.find(it->getName()); + + if (elIt != m_settings.m_individualGains.end()) { + it->setValue(*elIt); + } + } +} + +void SoapySDROutputGui::displayCorrectionsSettings() +{ + if (m_dcCorrectionGUI) + { + m_dcCorrectionGUI->setAutomatic(m_settings.m_autoDCCorrection); + m_dcCorrectionGUI->setModule(abs(m_settings.m_dcCorrection)); + m_dcCorrectionGUI->setArgument(arg(m_settings.m_dcCorrection)*(180.0/M_PI)); + } + + if (m_iqCorrectionGUI) + { + m_iqCorrectionGUI->setAutomatic(m_settings.m_autoIQCorrection); + m_iqCorrectionGUI->setModule(abs(m_settings.m_iqCorrection)); + m_iqCorrectionGUI->setArgument(arg(m_settings.m_iqCorrection)*(180.0/M_PI)); + } + + if (m_autoDCCorrection) { + m_autoDCCorrection->setChecked(m_settings.m_autoDCCorrection); + } + + if (m_autoIQCorrection) { + m_autoIQCorrection->setChecked(m_settings.m_autoIQCorrection); + } +} + +void SoapySDROutputGui::sendSettings() +{ + if (!m_updateTimer.isActive()) { + m_updateTimer.start(100); + } +} + +void SoapySDROutputGui::updateSampleRateAndFrequency() +{ + m_deviceUISet->getSpectrum()->setSampleRate(m_sampleRate); + m_deviceUISet->getSpectrum()->setCenterFrequency(m_deviceCenterFrequency); + ui->deviceRateText->setText(tr("%1k").arg(QString::number(m_sampleRate / 1000.0f, 'g', 5))); +} + +void SoapySDROutputGui::updateFrequencyLimits() +{ + // values in kHz + uint64_t f_min, f_max; + qint64 deltaFrequency = m_settings.m_transverterMode ? m_settings.m_transverterDeltaFrequency/1000 : 0; + m_sampleSink->getFrequencyRange(f_min, f_max); + qint64 minLimit = f_min/1000 + deltaFrequency; + qint64 maxLimit = f_max/1000 + deltaFrequency; + + minLimit = minLimit < 0 ? 0 : minLimit > 9999999 ? 9999999 : minLimit; + maxLimit = maxLimit < 0 ? 0 : maxLimit > 9999999 ? 9999999 : maxLimit; + + qDebug("SoapySDRInputGui::updateFrequencyLimits: delta: %lld min: %lld max: %lld", deltaFrequency, minLimit, maxLimit); + + ui->centerFrequency->setValueRange(7, minLimit, maxLimit); +} + +void SoapySDROutputGui::setCenterFrequencySetting(uint64_t kHzValue) +{ + int64_t centerFrequency = kHzValue*1000; + + m_settings.m_centerFrequency = centerFrequency < 0 ? 0 : (uint64_t) centerFrequency; + ui->centerFrequency->setToolTip(QString("Main center frequency in kHz (LO: %1 kHz)").arg(centerFrequency/1000)); +} + +void SoapySDROutputGui::updateHardware() +{ + if (m_doApplySettings) + { + qDebug() << "SoapySDROutputGui::updateHardware"; + SoapySDROutput::MsgConfigureSoapySDROutput* message = SoapySDROutput::MsgConfigureSoapySDROutput::create(m_settings, m_forceSettings); + m_sampleSink->getInputMessageQueue()->push(message); + m_forceSettings = false; + m_updateTimer.stop(); + } +} + +void SoapySDROutputGui::updateStatus() +{ + int state = m_deviceUISet->m_deviceSinkAPI->state(); + + if(m_lastEngineState != state) + { + switch(state) + { + case DSPDeviceSinkEngine::StNotStarted: + ui->startStop->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + break; + case DSPDeviceSinkEngine::StIdle: + ui->startStop->setStyleSheet("QToolButton { background-color : blue; }"); + break; + case DSPDeviceSinkEngine::StRunning: + ui->startStop->setStyleSheet("QToolButton { background-color : green; }"); + break; + case DSPDeviceSinkEngine::StError: + ui->startStop->setStyleSheet("QToolButton { background-color : red; }"); + QMessageBox::information(this, tr("Message"), m_deviceUISet->m_deviceSinkAPI->errorMessage()); + break; + default: + break; + } + + m_lastEngineState = state; + } +} + diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputgui.h b/plugins/samplesink/soapysdroutput/soapysdroutputgui.h new file mode 100644 index 000000000..1ac196323 --- /dev/null +++ b/plugins/samplesink/soapysdroutput/soapysdroutputgui.h @@ -0,0 +1,135 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUTGUI_H_ +#define PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUTGUI_H_ + +#include +#include + +#include "plugin/plugininstancegui.h" +#include "util/messagequeue.h" + +#include "soapysdroutput.h" +#include "soapysdroutputsettings.h" + +class DeviceSampleSink; +class DeviceUISet; +class ItemSettingGUI; +class StringRangeGUI; +class DynamicItemSettingGUI; +class IntervalSliderGUI; +class QCheckBox; +class ComplexFactorGUI; + +namespace Ui { + class SoapySDROutputGui; +} + +class SoapySDROutputGui : public QWidget, public PluginInstanceGUI { + Q_OBJECT + +public: + explicit SoapySDROutputGui(DeviceUISet *deviceUISet, QWidget* parent = 0); + virtual ~SoapySDROutputGui(); + virtual void destroy(); + + void setName(const QString& name); + QString getName() const; + + void resetToDefaults(); + virtual qint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + virtual bool handleMessage(const Message& message); + +private: + void createRangesControl( + ItemSettingGUI **settingGUI, + const SoapySDR::RangeList& rangeList, + const QString& text, + const QString& unit); + void createAntennasControl(const std::vector& antennaList); + void createTunableElementsControl(const std::vector& tunableElementsList); + void createGlobalGainControl(); + void createIndividualGainsControl(const std::vector& individualGainsList); + void createCorrectionsControl(); + + Ui::SoapySDROutputGui* ui; + + DeviceUISet* m_deviceUISet; + bool m_forceSettings; + bool m_doApplySettings; + SoapySDROutputSettings m_settings; + QTimer m_updateTimer; + QTimer m_statusTimer; + SoapySDROutput* m_sampleSink; + int m_sampleRate; + quint64 m_deviceCenterFrequency; //!< Center frequency in device + int m_lastEngineState; + MessageQueue m_inputMessageQueue; + + StringRangeGUI *m_antennas; + ItemSettingGUI *m_sampleRateGUI; + ItemSettingGUI *m_bandwidthGUI; + std::vector m_tunableElementsGUIs; + IntervalSliderGUI *m_gainSliderGUI; + std::vector m_individualGainsGUIs; + QCheckBox *m_autoGain; + ComplexFactorGUI *m_dcCorrectionGUI; + ComplexFactorGUI *m_iqCorrectionGUI; + QCheckBox *m_autoDCCorrection; + QCheckBox *m_autoIQCorrection; + + void blockApplySettings(bool block) { m_doApplySettings = !block; } + void displaySettings(); + void displayTunableElementsControlSettings(); + void displayIndividualGainsControlSettings(); + void displayCorrectionsSettings(); + void sendSettings(); + void updateSampleRateAndFrequency(); + void updateFrequencyLimits(); + void setCenterFrequencySetting(uint64_t kHzValue); + +private slots: + void handleInputMessages(); + + void antennasChanged(); + void sampleRateChanged(double sampleRate); + void bandwidthChanged(double bandwidth); + void tunableElementChanged(QString name, double value); + void globalGainChanged(double gain); + void autoGainChanged(bool set); + void individualGainChanged(QString name, double value); + void autoDCCorrectionChanged(bool set); + void autoIQCorrectionChanged(bool set); + void dcCorrectionModuleChanged(double value); + void dcCorrectionArgumentChanged(double value); + void iqCorrectionModuleChanged(double value); + void iqCorrectionArgumentChanged(double value); + + void on_centerFrequency_changed(quint64 value); + void on_LOppm_valueChanged(int value); + void on_interp_currentIndexChanged(int index); + void on_transverter_clicked(); + void on_startStop_toggled(bool checked); + void updateHardware(); + void updateStatus(); +}; + +#endif /* PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUTGUI_H_ */ diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputgui.ui b/plugins/samplesink/soapysdroutput/soapysdroutputgui.ui new file mode 100644 index 000000000..f173efefd --- /dev/null +++ b/plugins/samplesink/soapysdroutput/soapysdroutputgui.ui @@ -0,0 +1,350 @@ + + + SoapySDROutputGui + + + + 0 + 0 + 324 + 176 + + + + + 320 + 132 + + + + + Liberation Sans + 9 + + + + SoapySDR + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + 4 + + + + + + + + + start/stop acquisition + + + + + + + :/play.png + :/stop.png:/play.png + + + + + + + + + + + I/Q sample rate kS/s + + + 00000k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 20 + + + + PointingHandCursor + + + Qt::StrongFocus + + + Tuner center frequency in kHz + + + + + + + kHz + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + + 6 + + + 6 + + + + + Interp + + + + + + + + 30 + 0 + + + + Software decimation factor + + + + 1 + + + + + 2 + + + + + 4 + + + + + 8 + + + + + 16 + + + + + 32 + + + + + 64 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 24 + 24 + + + + X + + + + + + + + + + + LO ppm + + + + + + + Local Oscillator software ppm correction + + + -1000 + + + 1000 + + + 1 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + -100.0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + true + + + + + 0 + 0 + 318 + 53 + + + + + 3 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + ValueDial + QWidget +
gui/valuedial.h
+ 1 +
+ + ButtonSwitch + QToolButton +
gui/buttonswitch.h
+
+ + TransverterButton + QPushButton +
gui/transverterbutton.h
+
+
+ + + + +
diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputplugin.cpp b/plugins/samplesink/soapysdroutput/soapysdroutputplugin.cpp new file mode 100644 index 000000000..cdb6def6d --- /dev/null +++ b/plugins/samplesink/soapysdroutput/soapysdroutputplugin.cpp @@ -0,0 +1,135 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "plugin/pluginapi.h" +#include "util/simpleserializer.h" +#include "device/devicesourceapi.h" +#include "soapysdr/devicesoapysdr.h" + +#include "soapysdroutputplugin.h" + +#ifdef SERVER_MODE +#include "soapysdroutput.h" +#else +#include "soapysdroutputgui.h" +#endif + +const PluginDescriptor SoapySDROutputPlugin::m_pluginDescriptor = { + QString("SoapySDR Output"), + QString("4.3.0"), + QString("(c) Edouard Griffiths, F4EXB"), + QString("https://github.com/f4exb/sdrangel"), + true, + QString("https://github.com/f4exb/sdrangel") +}; + +const QString SoapySDROutputPlugin::m_hardwareID = "SoapySDR"; +const QString SoapySDROutputPlugin::m_deviceTypeID = SOAPYSDROUTPUT_DEVICE_TYPE_ID; + +SoapySDROutputPlugin::SoapySDROutputPlugin(QObject* parent) : + QObject(parent) +{ +} + +const PluginDescriptor& SoapySDROutputPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void SoapySDROutputPlugin::initPlugin(PluginAPI* pluginAPI) +{ + pluginAPI->registerSampleSink(m_deviceTypeID, this); +} + +PluginInterface::SamplingDevices SoapySDROutputPlugin::enumSampleSinks() +{ + SamplingDevices result; + DeviceSoapySDR& deviceSoapySDR = DeviceSoapySDR::instance(); + const std::vector& devicesEnumeration = deviceSoapySDR.getDevicesEnumeration(); + qDebug("SoapySDROutputPlugin::enumSampleSinks: %lu SoapySDR devices. Enumerate these with Tx channel(s):", devicesEnumeration.size()); + std::vector::const_iterator it = devicesEnumeration.begin(); + + for (int idev = 0; it != devicesEnumeration.end(); ++it, idev++) + { + unsigned int nbTxChannels = it->m_nbTx; + + for (unsigned int ichan = 0; ichan < nbTxChannels; ichan++) + { + QString displayedName(QString("SoapySDR[%1:%2] %3").arg(idev).arg(ichan).arg(it->m_label)); + QString serial(QString("%1-%2").arg(it->m_driverName).arg(it->m_sequence)); + qDebug("SoapySDROutputPlugin::enumSampleSinks: device #%d (%s) serial %s channel %u", + idev, it->m_label.toStdString().c_str(), serial.toStdString().c_str(), ichan); + result.append(SamplingDevice(displayedName, + m_hardwareID, + m_deviceTypeID, + serial, + idev, + PluginInterface::SamplingDevice::PhysicalDevice, + false, + nbTxChannels, + ichan)); + } + } + + return result; +} + +#ifdef SERVER_MODE +PluginInstanceGUI* SoapySDROutputPlugin::createSampleSinkPluginInstanceGUI( + const QString& sinkId __attribute__((unused)), + QWidget **widget __attribute__((unused)), + DeviceUISet *deviceUISet __attribute__((unused))) +{ + return 0; +} +#else +PluginInstanceGUI* SoapySDROutputPlugin::createSampleSinkPluginInstanceGUI( + const QString& sinkId, + QWidget **widget, + DeviceUISet *deviceUISet) +{ + if(sinkId == m_deviceTypeID) + { + SoapySDROutputGui* gui = new SoapySDROutputGui(deviceUISet); + *widget = gui; + return gui; + } + else + { + return 0; + } +} +#endif + +DeviceSampleSink* SoapySDROutputPlugin::createSampleSinkPluginInstanceOutput(const QString& sinkId, DeviceSinkAPI *deviceAPI) +{ + if(sinkId == m_deviceTypeID) + { + SoapySDROutput* output = new SoapySDROutput(deviceAPI); + return output; + } + else + { + return 0; + } +} + + + + + diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputplugin.h b/plugins/samplesink/soapysdroutput/soapysdroutputplugin.h new file mode 100644 index 000000000..5bf655f83 --- /dev/null +++ b/plugins/samplesink/soapysdroutput/soapysdroutputplugin.h @@ -0,0 +1,54 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUTPLUGIN_H_ +#define PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUTPLUGIN_H_ + +#include +#include "plugin/plugininterface.h" + +class PluginAPI; + +#define SOAPYSDROUTPUT_DEVICE_TYPE_ID "sdrangel.samplesink.soapysdroutput" + +class SoapySDROutputPlugin : public QObject, public PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID SOAPYSDROUTPUT_DEVICE_TYPE_ID) + +public: + explicit SoapySDROutputPlugin(QObject* parent = 0); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + virtual SamplingDevices enumSampleSinks(); + + virtual PluginInstanceGUI* createSampleSinkPluginInstanceGUI( + const QString& sinkId, + QWidget **widget, + DeviceUISet *deviceUISet); + + virtual DeviceSampleSink* createSampleSinkPluginInstanceOutput(const QString& sinkId, DeviceSinkAPI *deviceAPI); + + static const QString m_hardwareID; + static const QString m_deviceTypeID; + +private: + static const PluginDescriptor m_pluginDescriptor; +}; + +#endif /* PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUTPLUGIN_H_ */ diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp new file mode 100644 index 000000000..9abc87b96 --- /dev/null +++ b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.cpp @@ -0,0 +1,133 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "util/simpleserializer.h" + +#include "soapysdroutputsettings.h" + +SoapySDROutputSettings::SoapySDROutputSettings() +{ + resetToDefaults(); +} + +void SoapySDROutputSettings::resetToDefaults() +{ + m_centerFrequency = 435000*1000; + m_LOppmTenths = 0; + m_devSampleRate = 1024000; + m_log2Interp = 0; + m_transverterMode = false; + m_transverterDeltaFrequency = 0; + m_antenna = "NONE"; + m_bandwidth = 1000000; + m_globalGain = 0; + m_autoGain = false; + m_autoDCCorrection = false; + m_autoIQCorrection = false; + m_dcCorrection = std::complex{0,0}; + m_iqCorrection = std::complex{0,0}; +} + +QByteArray SoapySDROutputSettings::serialize() const +{ + SimpleSerializer s(1); + + s.writeS32(1, m_devSampleRate); + s.writeS32(2, m_LOppmTenths); + s.writeU32(3, m_log2Interp); + s.writeBool(4, m_transverterMode); + s.writeS64(5, m_transverterDeltaFrequency); + s.writeString(6, m_antenna); + s.writeU32(7, m_bandwidth); + s.writeBlob(8, serializeNamedElementMap(m_tunableElements)); + s.writeS32(12, m_globalGain); + s.writeBlob(13, serializeNamedElementMap(m_individualGains)); + s.writeBool(14, m_autoGain); + s.writeBool(15, m_autoDCCorrection); + s.writeBool(16, m_autoIQCorrection); + s.writeDouble(17, m_dcCorrection.real()); + s.writeDouble(18, m_dcCorrection.imag()); + s.writeDouble(19, m_iqCorrection.real()); + s.writeDouble(20, m_iqCorrection.imag()); + + return s.final(); +} + +bool SoapySDROutputSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if (!d.isValid()) + { + resetToDefaults(); + return false; + } + + if (d.getVersion() == 1) + { + QByteArray blob; + double realval, imagval; + + d.readS32(1, &m_devSampleRate, 1024000); + d.readS32(2, &m_LOppmTenths, 0); + d.readU32(3, &m_log2Interp, 0); + d.readBool(4, &m_transverterMode, false); + d.readS64(5, &m_transverterDeltaFrequency, 0); + d.readString(6, &m_antenna, "NONE"); + d.readU32(7, &m_bandwidth, 1000000); + d.readBlob(8, &blob); + deserializeNamedElementMap(blob, m_tunableElements); + d.readS32(12, &m_globalGain, 0); + d.readBlob(13, &blob); + deserializeNamedElementMap(blob, m_individualGains); + d.readBool(14, &m_autoGain, false); + d.readBool(15, &m_autoDCCorrection, false); + d.readBool(16, &m_autoIQCorrection, false); + d.readDouble(17, &realval, 0); + d.readDouble(18, &imagval, 0); + m_dcCorrection = std::complex{realval, imagval}; + d.readDouble(19, &realval, 0); + d.readDouble(20, &imagval, 0); + m_iqCorrection = std::complex{realval, imagval}; + + return true; + } + else + { + resetToDefaults(); + return false; + } +} + +QByteArray SoapySDROutputSettings::serializeNamedElementMap(const QMap& map) const +{ + QByteArray data; + QDataStream *stream = new QDataStream(&data, QIODevice::WriteOnly); + (*stream) << map; + delete stream; + + return data; +} + +void SoapySDROutputSettings::deserializeNamedElementMap(const QByteArray& data, QMap& map) +{ + QDataStream *stream = new QDataStream(data); + (*stream) >> map; + delete stream; +} diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h new file mode 100644 index 000000000..d0cf8f448 --- /dev/null +++ b/plugins/samplesink/soapysdroutput/soapysdroutputsettings.h @@ -0,0 +1,51 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUTSETTINGS_H_ +#define PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUTSETTINGS_H_ + +#include +#include + +struct SoapySDROutputSettings { + quint64 m_centerFrequency; + int m_LOppmTenths; + qint32 m_devSampleRate; + quint32 m_log2Interp; + bool m_transverterMode; + qint64 m_transverterDeltaFrequency; + QString m_antenna; + quint32 m_bandwidth; + QMap m_tunableElements; + qint32 m_globalGain; + QMap m_individualGains; + bool m_autoGain; + bool m_autoDCCorrection; + bool m_autoIQCorrection; + std::complex m_dcCorrection; + std::complex m_iqCorrection; + + SoapySDROutputSettings(); + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + +private: + QByteArray serializeNamedElementMap(const QMap& map) const; + void deserializeNamedElementMap(const QByteArray& data, QMap& map); +}; + +#endif /* PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUTSETTINGS_H_ */ diff --git a/plugins/samplesink/soapysdroutput/soapysdroutputthread.cpp b/plugins/samplesink/soapysdroutput/soapysdroutputthread.cpp new file mode 100644 index 000000000..b7303d401 --- /dev/null +++ b/plugins/samplesink/soapysdroutput/soapysdroutputthread.cpp @@ -0,0 +1,432 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include + +#include "dsp/samplesourcefifo.h" + +#include "soapysdroutputthread.h" + +SoapySDROutputThread::SoapySDROutputThread(SoapySDR::Device* dev, unsigned int nbTxChannels, QObject* parent) : + QThread(parent), + m_running(false), + m_dev(dev), + m_sampleRate(0), + m_nbChannels(nbTxChannels), + m_interpolatorType(InterpolatorFloat) +{ + qDebug("SoapySDROutputThread::SoapySDROutputThread"); + m_channels = new Channel[nbTxChannels]; +} + +SoapySDROutputThread::~SoapySDROutputThread() +{ + qDebug("SoapySDROutputThread::~SoapySDROutputThread"); + + if (m_running) { + stopWork(); + } + + delete[] m_channels; +} + +void SoapySDROutputThread::startWork() +{ + if (m_running) { + return; + } + + m_startWaitMutex.lock(); + start(); + + while(!m_running) { + m_startWaiter.wait(&m_startWaitMutex, 100); + } + + m_startWaitMutex.unlock(); +} + +void SoapySDROutputThread::stopWork() +{ + if (!m_running) { + return; + } + + m_running = false; + wait(); +} + +void SoapySDROutputThread::run() +{ + m_running = true; + m_startWaiter.wakeAll(); + + unsigned int nbFifos = getNbFifos(); + + if ((m_nbChannels > 0) && (nbFifos > 0)) + { + // build channels list + std::vector channels(m_nbChannels); + std::iota(channels.begin(), channels.end(), 0); // Fill with 0, 1, ..., m_nbChannels-1. + + //initialize the sample rate for all channels + qDebug("SoapySDROutputThread::run: m_sampleRate: %u", m_sampleRate); + for (const auto &it : channels) { + m_dev->setSampleRate(SOAPY_SDR_TX, it, m_sampleRate); + } + + // Determine sample format to be used + double fullScale(0.0); + std::string format = m_dev->getNativeStreamFormat(SOAPY_SDR_TX, channels.front(), fullScale); + + qDebug("SoapySDROutputThread::run: format: %s fullScale: %f", format.c_str(), fullScale); + + if ((format == "CS8") && (fullScale == 128.0)) { // 8 bit signed - native + m_interpolatorType = Interpolator8; + } else if ((format == "CS16") && (fullScale == 2048.0)) { // 12 bit signed - native + m_interpolatorType = Interpolator12; + } else if ((format == "CS16") && (fullScale == 32768.0)) { // 16 bit signed - native + m_interpolatorType = Interpolator16; + } else { // for other types make a conversion to float + m_interpolatorType = InterpolatorFloat; + format = "CF32"; + } + + unsigned int elemSize = SoapySDR::formatToSize(format); // sample (I+Q) size in bytes + SoapySDR::Stream *stream = m_dev->setupStream(SOAPY_SDR_TX, format, channels); + + //allocate buffers for the stream read/write + const unsigned int numElems = m_dev->getStreamMTU(stream); // number of samples (I+Q) + std::vector> buffMem(m_nbChannels, std::vector(elemSize*numElems)); + std::vector buffs(m_nbChannels); + + for (std::size_t i = 0; i < m_nbChannels; i++) { + buffs[i] = buffMem[i].data(); + } + + m_dev->activateStream(stream); + int flags(0); + long long timeNs(0); + float blockTime = ((float) numElems) / (m_sampleRate <= 0 ? 1024000 : m_sampleRate); + long timeoutUs = 2000000 * blockTime; // 10 times the block time + + qDebug("SoapySDROutputThread::run: numElems: %u elemSize: %u timeoutUs: %ld", numElems, elemSize, timeoutUs); + qDebug("SoapySDROutputThread::run: start running loop"); + + while (m_running) + { + int ret = m_dev->writeStream(stream, buffs.data(), numElems, flags, timeNs, timeoutUs); + + if (ret == SOAPY_SDR_TIMEOUT) + { + qWarning("SoapySDROutputThread::run: timeout: flags: %d timeNs: %lld timeoutUs: %ld", flags, timeNs, timeoutUs); + } + else if (ret < 0) + { + qCritical("SoapySDROutputThread::run: Unexpected write stream error: %s", SoapySDR::errToStr(ret)); + break; + } + + if (m_nbChannels > 1) + { + callbackMO(buffs, numElems); // size given in number of samples (1 item per sample) + } + else + { + switch (m_interpolatorType) + { + case Interpolator8: + callbackSO8((qint8*) buffs[0], numElems); + break; + case Interpolator12: + callbackSO12((qint16*) buffs[0], numElems); + break; + case Interpolator16: + callbackSO16((qint16*) buffs[0], numElems); + break; + case InterpolatorFloat: + default: + // TODO + break; + } + } + } + + qDebug("SoapySDROutputThread::run: stop running loop"); + m_dev->deactivateStream(stream); + m_dev->closeStream(stream); + + } + else + { + qWarning("SoapySDROutputThread::run: no channels or FIFO allocated. Aborting"); + } + + m_running = false; +} + +unsigned int SoapySDROutputThread::getNbFifos() +{ + unsigned int fifoCount = 0; + + for (unsigned int i = 0; i < m_nbChannels; i++) + { + if (m_channels[i].m_sampleFifo) { + fifoCount++; + } + } + + return fifoCount; +} + +void SoapySDROutputThread::setLog2Interpolation(unsigned int channel, unsigned int log2_interp) +{ + if (channel < m_nbChannels) { + m_channels[channel].m_log2Interp = log2_interp; + } +} + +unsigned int SoapySDROutputThread::getLog2Interpolation(unsigned int channel) const +{ + if (channel < m_nbChannels) { + return m_channels[channel].m_log2Interp; + } else { + return 0; + } +} + +void SoapySDROutputThread::setFifo(unsigned int channel, SampleSourceFifo *sampleFifo) +{ + if (channel < m_nbChannels) { + m_channels[channel].m_sampleFifo = sampleFifo; + } +} + +SampleSourceFifo *SoapySDROutputThread::getFifo(unsigned int channel) +{ + if (channel < m_nbChannels) { + return m_channels[channel].m_sampleFifo; + } else { + return 0; + } +} + +void SoapySDROutputThread::callbackMO(std::vector& buffs, qint32 samplesPerChannel) +{ + for(unsigned int ichan = 0; ichan < m_nbChannels; ichan++) + { + if (m_channels[ichan].m_sampleFifo) + { + switch (m_interpolatorType) + { + case Interpolator8: + callbackSO8((qint8*) buffs[ichan], samplesPerChannel, ichan); + break; + case Interpolator12: + callbackSO12((qint16*) buffs[ichan], samplesPerChannel, ichan); + break; + case Interpolator16: + callbackSO16((qint16*) buffs[ichan], samplesPerChannel, ichan); + break; + case InterpolatorFloat: + default: + // TODO + break; + } + } + else // no FIFO for this channel means channel is unused: fill with zeros + { + switch (m_interpolatorType) + { + case Interpolator8: + std::fill((qint8*) buffs[ichan], (qint8*) buffs[ichan] + 2*samplesPerChannel, 0); + break; + case Interpolator12: + case Interpolator16: + std::fill((qint16*) buffs[ichan], (qint16*) buffs[ichan] + 2*samplesPerChannel, 0); + break; + case InterpolatorFloat: + default: + // TODO + break; + } + } + } +} + +// Interpolate according to specified log2 (ex: log2=4 => decim=16). len is a number of samples (not a number of I or Q) + +void SoapySDROutputThread::callbackSO8(qint8* buf, qint32 len, unsigned int channel) +{ + if (m_channels[channel].m_sampleFifo) + { + float bal = m_channels[channel].m_sampleFifo->getRWBalance(); + + if (bal < -0.25) { + qDebug("SoapySDROutputThread::callbackSO8: read lags: %f", bal); + } else if (bal > 0.25) { + qDebug("SoapySDROutputThread::callbackSO8: read leads: %f", bal); + } + + SampleVector::iterator beginRead; + m_channels[channel].m_sampleFifo->readAdvance(beginRead, len/(1<getRWBalance(); + + if (bal < -0.25) { + qDebug("SoapySDROutputThread::callbackSO12: read lags: %f", bal); + } else if (bal > 0.25) { + qDebug("SoapySDROutputThread::callbackSO12: read leads: %f", bal); + } + + SampleVector::iterator beginRead; + m_channels[channel].m_sampleFifo->readAdvance(beginRead, len/(1<getRWBalance(); + + if (bal < -0.25) { + qDebug("SoapySDROutputThread::callbackSO16: read lags: %f", bal); + } else if (bal > 0.25) { + qDebug("SoapySDROutputThread::callbackSO16: read leads: %f", bal); + } + + SampleVector::iterator beginRead; + m_channels[channel].m_sampleFifo->readAdvance(beginRead, len/(1<. // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUTTHREAD_H_ +#define PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUTTHREAD_H_ + + +#include +#include +#include + +#include + +#include "soapysdr/devicesoapysdrshared.h" +#include "dsp/interpolators.h" + +class SampleSourceFifo; + +class SoapySDROutputThread : public QThread { + Q_OBJECT + +public: + SoapySDROutputThread(SoapySDR::Device* dev, unsigned int nbTxChannels, QObject* parent = 0); + ~SoapySDROutputThread(); + + void startWork(); + void stopWork(); + bool isRunning() const { return m_running; } + unsigned int getNbChannels() const { return m_nbChannels; } + void setLog2Interpolation(unsigned int channel, unsigned int log2_interp); + unsigned int getLog2Interpolation(unsigned int channel) const; + void setSampleRate(unsigned int sampleRate) { m_sampleRate = sampleRate; } + unsigned int getSampleRate() const { return m_sampleRate; } + void setFifo(unsigned int channel, SampleSourceFifo *sampleFifo); + SampleSourceFifo *getFifo(unsigned int channel); + +private: + struct Channel + { + SampleSourceFifo* m_sampleFifo; + unsigned int m_log2Interp; + Interpolators m_interpolators8; + Interpolators m_interpolators12; + Interpolators m_interpolators16; + + Channel() : + m_sampleFifo(0), + m_log2Interp(0) + {} + + ~Channel() + {} + }; + + enum InterpolatorType + { + Interpolator8, + Interpolator12, + Interpolator16, + InterpolatorFloat + }; + + + QMutex m_startWaitMutex; + QWaitCondition m_startWaiter; + bool m_running; + SoapySDR::Device* m_dev; + + Channel *m_channels; //!< Array of channels dynamically allocated for the given number of Tx channels + unsigned int m_sampleRate; + unsigned int m_nbChannels; + InterpolatorType m_interpolatorType; + + void run(); + unsigned int getNbFifos(); + void callbackSO8(qint8* buf, qint32 len, unsigned int channel = 0); + void callbackSO12(qint16* buf, qint32 len, unsigned int channel = 0); + void callbackSO16(qint16* buf, qint32 len, unsigned int channel = 0); + void callbackMO(std::vector& buffs, qint32 samplesPerChannel); +}; + + +#endif /* PLUGINS_SAMPLESINK_SOAPYSDROUTPUT_SOAPYSDROUTPUTTHREAD_H_ */ diff --git a/plugins/samplesource/CMakeLists.txt b/plugins/samplesource/CMakeLists.txt index 3bfb5c675..9e442fa11 100644 --- a/plugins/samplesource/CMakeLists.txt +++ b/plugins/samplesource/CMakeLists.txt @@ -8,30 +8,11 @@ if(V4L-RTL) # add_subdirectory(v4l-rtl) endif() if(V4L-MSI) - FIND_LIBRARY (LIBV4L2 v4l2) + FIND_LIBRARY (LIBV4L2 v4l2) FIND_PATH (LIBV4L2H libv4l2.h) # add_subdirectory(v4l-msi) endif() -if(LIBUSB_FOUND AND UNIX) - FIND_PATH (ASOUNDH alsa/asoundlib.h) - FIND_LIBRARY (LIBASOUND asound) -endif() -if(LIBASOUND AND ASOUNDH) - add_subdirectory(fcdpro) - add_subdirectory(fcdproplus) -endif() - -find_package(LibRTLSDR) -if(LIBUSB_FOUND AND LIBRTLSDR_FOUND) - add_subdirectory(rtlsdr) -endif(LIBUSB_FOUND AND LIBRTLSDR_FOUND) - -find_package(LibBLADERF) -if(LIBUSB_FOUND AND LIBBLADERF_FOUND) - add_subdirectory(bladerfinput) -endif(LIBUSB_FOUND AND LIBBLADERF_FOUND) - find_package(LibAIRSPY) if(LIBUSB_FOUND AND LIBAIRSPY_FOUND) add_subdirectory(airspy) @@ -42,11 +23,52 @@ if(LIBUSB_FOUND AND LIBAIRSPYHF_FOUND) add_subdirectory(airspyhf) endif(LIBUSB_FOUND AND LIBAIRSPYHF_FOUND) +find_package(LibBLADERF) +if(LIBUSB_FOUND AND LIBBLADERF_FOUND) + add_subdirectory(bladerf1input) + add_subdirectory(bladerf2input) +endif(LIBUSB_FOUND AND LIBBLADERF_FOUND) + +if(LIBUSB_FOUND AND UNIX) + FIND_PATH (ASOUNDH alsa/asoundlib.h) + FIND_LIBRARY (LIBASOUND asound) +endif() +if(LIBASOUND AND ASOUNDH) + add_subdirectory(fcdpro) + add_subdirectory(fcdproplus) +endif() + find_package(LibHACKRF) if(LIBUSB_FOUND AND LIBHACKRF_FOUND) add_subdirectory(hackrfinput) endif(LIBUSB_FOUND AND LIBHACKRF_FOUND) +find_package(LimeSuite) +if(LIBUSB_FOUND AND LIMESUITE_FOUND) + add_subdirectory(limesdrinput) +endif(LIBUSB_FOUND AND LIMESUITE_FOUND) + +find_package(LibPerseus) +if(LIBUSB_FOUND AND RX_SAMPLE_24BIT AND LIBPERSEUS_FOUND) + message(STATUS "Add Persesus plugin") + add_subdirectory(perseus) +endif(LIBUSB_FOUND AND RX_SAMPLE_24BIT AND LIBPERSEUS_FOUND) + +find_package(LibIIO) +if(LIBUSB_FOUND AND LIBIIO_FOUND) + add_subdirectory(plutosdrinput) +endif(LIBUSB_FOUND AND LIBIIO_FOUND) + +find_package(LibRTLSDR) +if(LIBUSB_FOUND AND LIBRTLSDR_FOUND) + add_subdirectory(rtlsdr) +endif(LIBUSB_FOUND AND LIBRTLSDR_FOUND) + +find_package(CM256cc) +if(CM256CC_FOUND) + add_subdirectory(sdrdaemonsource) +endif(CM256CC_FOUND) + find_package(LibMiriSDR) if(LIBUSB_FOUND AND LIBMIRISDR_FOUND) add_subdirectory(sdrplay) @@ -55,41 +77,27 @@ else(LIBUSB_FOUND AND LIBMIRISDR_FOUND) message(STATUS "LibMiriSDR NOT found") endif(LIBUSB_FOUND AND LIBMIRISDR_FOUND) -find_package(LimeSuite) -if(LIBUSB_FOUND AND LIMESUITE_FOUND) - add_subdirectory(limesdrinput) -endif(LIBUSB_FOUND AND LIMESUITE_FOUND) - -find_package(LibIIO) -if(LIBUSB_FOUND AND LIBIIO_FOUND) - add_subdirectory(plutosdrinput) -endif(LIBUSB_FOUND AND LIBIIO_FOUND) - -find_package(CM256cc) -find_package(LibNANOMSG) -if(CM256CC_FOUND AND LIBNANOMSG_FOUND) - add_subdirectory(sdrdaemonsource) -endif(CM256CC_FOUND AND LIBNANOMSG_FOUND) - -find_package(LibPerseus) -if(LIBUSB_FOUND AND RX_SAMPLE_24BIT AND LIBPERSEUS_FOUND) - message(STATUS "Add Persesus plugin") - add_subdirectory(perseus) -endif(LIBUSB_FOUND AND RX_SAMPLE_24BIT AND LIBPERSEUS_FOUND) +find_package(SoapySDR) +if(LIBUSB_FOUND AND SOAPYSDR_FOUND) + add_subdirectory(soapysdrinput) + message(STATUS "SoapySDR found") +else() + message(STATUS "SoapySDR not found") +endif() if (BUILD_DEBIAN) - if (LIBNANOMSG_FOUND) - add_subdirectory(sdrdaemonsource) - endif (LIBNANOMSG_FOUND) add_subdirectory(airspy) add_subdirectory(airspyhf) + add_subdirectory(bladerf1input) + add_subdirectory(bladerf2input) add_subdirectory(hackrfinput) - add_subdirectory(rtlsdr) - add_subdirectory(bladerfinput) - add_subdirectory(sdrplay) add_subdirectory(limesdrinput) add_subdirectory(perseus) add_subdirectory(plutosdrinput) + add_subdirectory(rtlsdr) + add_subdirectory(sdrdaemonsource) + add_subdirectory(sdrplay) + add_subdirectory(soapysdrinput) endif (BUILD_DEBIAN) add_subdirectory(filesource) diff --git a/plugins/samplesource/airspy/CMakeLists.txt b/plugins/samplesource/airspy/CMakeLists.txt index 022530bff..9d2aa6bf4 100644 --- a/plugins/samplesource/airspy/CMakeLists.txt +++ b/plugins/samplesource/airspy/CMakeLists.txt @@ -73,6 +73,6 @@ target_link_libraries(inputairspy endif (BUILD_DEBIAN) -qt5_use_modules(inputairspy Core Widgets) +target_link_libraries(inputairspy Qt5::Core Qt5::Widgets) install(TARGETS inputairspy DESTINATION lib/plugins/samplesource) diff --git a/plugins/samplesource/airspy/airspy.pro b/plugins/samplesource/airspy/airspy.pro index 3348db739..5ba6d7050 100644 --- a/plugins/samplesource/airspy/airspy.pro +++ b/plugins/samplesource/airspy/airspy.pro @@ -11,9 +11,10 @@ QT += core gui widgets multimedia opengl TARGET = inputairspy -CONFIG(MINGW32):LIBAIRSPYSRC = "D:\softs\libairspy" -CONFIG(MINGW64):LIBAIRSPYSRC = "D:\softs\libairspy" +CONFIG(MINGW32):LIBAIRSPYSRC = "C:\softs\libairspy" +CONFIG(MINGW64):LIBAIRSPYSRC = "C:\softs\libairspy" INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client diff --git a/plugins/samplesource/airspy/airspygui.cpp b/plugins/samplesource/airspy/airspygui.cpp index 1bda2b1c8..ae59a6ca7 100644 --- a/plugins/samplesource/airspy/airspygui.cpp +++ b/plugins/samplesource/airspy/airspygui.cpp @@ -39,7 +39,7 @@ AirspyGui::AirspyGui(DeviceUISet *deviceUISet, QWidget* parent) : m_forceSettings(true), m_settings(), m_sampleSource(0), - m_lastEngineState((DSPDeviceSourceEngine::State)-1) + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted) { m_sampleSource = (AirspyInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); @@ -56,6 +56,7 @@ AirspyGui::AirspyGui(DeviceUISet *deviceUISet, QWidget* parent) : m_rates = ((AirspyInput*) m_sampleSource)->getSampleRates(); displaySampleRates(); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); sendSettings(); } diff --git a/plugins/samplesource/airspy/airspygui.ui b/plugins/samplesource/airspy/airspygui.ui index 722d960e7..b767aee82 100644 --- a/plugins/samplesource/airspy/airspygui.ui +++ b/plugins/samplesource/airspy/airspygui.ui @@ -24,7 +24,7 @@
- Sans Serif + Liberation Sans 9 @@ -135,7 +135,7 @@ - DejaVu Sans Mono + Liberation Mono 20 diff --git a/plugins/samplesource/airspy/airspyinput.cpp b/plugins/samplesource/airspy/airspyinput.cpp index ae0c53355..7ec2f68f3 100644 --- a/plugins/samplesource/airspy/airspyinput.cpp +++ b/plugins/samplesource/airspy/airspyinput.cpp @@ -17,11 +17,13 @@ #include #include #include +#include #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" +#include "SWGDeviceReport.h" +#include "SWGAirspyReport.h" -#include "airspygui.h" #include "airspyinput.h" #include "airspyplugin.h" @@ -48,10 +50,7 @@ AirspyInput::AirspyInput(DeviceSourceAPI *deviceAPI) : m_running(false) { openDevice(); - - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID()); - m_fileSink = new FileRecord(std::string(recFileNameCStr)); + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); m_deviceAPI->addSink(m_fileSink); } @@ -161,15 +160,9 @@ bool AirspyInput::start() return false; } - if (m_running) stop(); - - if((m_airspyThread = new AirspyThread(m_dev, &m_sampleFifo)) == 0) - { - qCritical("AirspyInput::start: out of memory"); - stop(); - return false; - } + if (m_running) { stop(); } + m_airspyThread = new AirspyThread(m_dev, &m_sampleFifo); m_airspyThread->setSamplerate(m_sampleRates[m_settings.m_devSampleRateIndex]); m_airspyThread->setLog2Decimation(m_settings.m_log2Decim); m_airspyThread->setFcPos((int) m_settings.m_fcPos); @@ -298,13 +291,11 @@ bool AirspyInput::handleMessage(const Message& message) if (m_deviceAPI->initAcquisition()) { m_deviceAPI->startAcquisition(); - DSPEngine::instance()->startAudioOutput(); } } else { m_deviceAPI->stopAcquisition(); - DSPEngine::instance()->stopAudioOutput(); } return true; @@ -314,9 +305,18 @@ bool AirspyInput::handleMessage(const Message& message) MsgFileRecord& conf = (MsgFileRecord&) message; qDebug() << "AirspyInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); - if (conf.getStartStop()) { + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSink->setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); + } + m_fileSink->startRecording(); - } else { + } + else + { m_fileSink->stopRecording(); } @@ -409,45 +409,22 @@ bool AirspyInput::applySettings(const AirspySettings& settings, bool force) || (m_settings.m_transverterMode != settings.m_transverterMode) || (m_settings.m_transverterDeltaFrequency != settings.m_transverterDeltaFrequency) || force) { + qint64 deviceCenterFrequency = DeviceSampleSource::calculateDeviceCenterFrequency( + settings.m_centerFrequency, + settings.m_transverterDeltaFrequency, + settings.m_log2Decim, + (DeviceSampleSource::fcPos_t) settings.m_fcPos, + m_sampleRates[m_settings.m_devSampleRateIndex], + settings.m_transverterMode); + m_settings.m_centerFrequency = settings.m_centerFrequency; m_settings.m_log2Decim = settings.m_log2Decim; m_settings.m_transverterMode = settings.m_transverterMode; m_settings.m_transverterDeltaFrequency = settings.m_transverterDeltaFrequency; m_settings.m_LOppmTenths = settings.m_LOppmTenths; - qint64 deviceCenterFrequency = m_settings.m_centerFrequency; - deviceCenterFrequency -= m_settings.m_transverterMode ? m_settings.m_transverterDeltaFrequency : 0; - deviceCenterFrequency = deviceCenterFrequency < 0 ? 0 : deviceCenterFrequency; - qint64 f_img = deviceCenterFrequency; - quint32 devSampleRate = m_sampleRates[m_settings.m_devSampleRateIndex]; - - if ((m_settings.m_log2Decim == 0) || (settings.m_fcPos == AirspySettings::FC_POS_CENTER)) - { - f_img = deviceCenterFrequency; - } - else - { - if (settings.m_fcPos == AirspySettings::FC_POS_INFRA) - { - deviceCenterFrequency += (devSampleRate / 4); - f_img = deviceCenterFrequency + devSampleRate/2; - } - else if (settings.m_fcPos == AirspySettings::FC_POS_SUPRA) - { - deviceCenterFrequency -= (devSampleRate / 4); - f_img = deviceCenterFrequency - devSampleRate/2; - } - } - - if (m_dev != 0) - { + if (m_dev != 0) { setDeviceCenterFrequency(deviceCenterFrequency); - - qDebug() << "AirspyInput::applySettings: center freq: " << m_settings.m_centerFrequency << " Hz" - << " device center freq: " << deviceCenterFrequency << " Hz" - << " device sample rate: " << devSampleRate << "Hz" - << " Actual sample rate: " << devSampleRate/(1<init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int AirspyInput::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage __attribute__((unused))) +{ + AirspySettings settings = m_settings; + + if (deviceSettingsKeys.contains("centerFrequency")) { + settings.m_centerFrequency = response.getAirspySettings()->getCenterFrequency(); + } + if (deviceSettingsKeys.contains("LOppmTenths")) { + settings.m_LOppmTenths = response.getAirspySettings()->getLOppmTenths(); + } + if (deviceSettingsKeys.contains("devSampleRateIndex")) { + settings.m_devSampleRateIndex = response.getAirspySettings()->getDevSampleRateIndex(); + } + if (deviceSettingsKeys.contains("lnaGain")) { + settings.m_lnaGain = response.getAirspySettings()->getLnaGain(); + } + if (deviceSettingsKeys.contains("mixerGain")) { + settings.m_mixerGain = response.getAirspySettings()->getMixerGain(); + } + if (deviceSettingsKeys.contains("vgaGain")) { + settings.m_vgaGain = response.getAirspySettings()->getVgaGain(); + } + if (deviceSettingsKeys.contains("lnaAGC")) { + settings.m_lnaAGC = response.getAirspySettings()->getLnaAgc() != 0; + } + if (deviceSettingsKeys.contains("mixerAGC")) { + settings.m_mixerAGC = response.getAirspySettings()->getMixerAgc() != 0; + } + if (deviceSettingsKeys.contains("log2Decim")) { + settings.m_log2Decim = response.getAirspySettings()->getLog2Decim(); + } + if (deviceSettingsKeys.contains("fcPos")) { + int fcPos = response.getAirspySettings()->getFcPos(); + fcPos = fcPos < 0 ? 0 : fcPos > 2 ? 2 : fcPos; + settings.m_fcPos = (AirspySettings::fcPos_t) fcPos; + } + if (deviceSettingsKeys.contains("biasT")) { + settings.m_biasT = response.getAirspySettings()->getBiasT() != 0; + } + if (deviceSettingsKeys.contains("dcBlock")) { + settings.m_dcBlock = response.getAirspySettings()->getDcBlock() != 0; + } + if (deviceSettingsKeys.contains("iqCorrection")) { + settings.m_iqCorrection = response.getAirspySettings()->getIqCorrection() != 0; + } + if (deviceSettingsKeys.contains("transverterDeltaFrequency")) { + settings.m_transverterDeltaFrequency = response.getAirspySettings()->getTransverterDeltaFrequency(); + } + if (deviceSettingsKeys.contains("transverterMode")) { + settings.m_transverterMode = response.getAirspySettings()->getTransverterMode() != 0; + } + if (deviceSettingsKeys.contains("fileRecordName")) { + settings.m_fileRecordName = *response.getAirspySettings()->getFileRecordName(); + } + + MsgConfigureAirspy *msg = MsgConfigureAirspy::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureAirspy *msgToGUI = MsgConfigureAirspy::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +int AirspyInput::webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setAirspyReport(new SWGSDRangel::SWGAirspyReport()); + response.getAirspyReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} + +void AirspyInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const AirspySettings& settings) +{ + response.getAirspySettings()->setCenterFrequency(settings.m_centerFrequency); + response.getAirspySettings()->setLOppmTenths(settings.m_LOppmTenths); + response.getAirspySettings()->setDevSampleRateIndex(settings.m_devSampleRateIndex); + response.getAirspySettings()->setLnaGain(settings.m_lnaGain); + response.getAirspySettings()->setMixerGain(settings.m_mixerGain); + response.getAirspySettings()->setVgaGain(settings.m_vgaGain); + response.getAirspySettings()->setLnaAgc(settings.m_lnaAGC ? 1 : 0); + response.getAirspySettings()->setMixerAgc(settings.m_mixerAGC ? 1 : 0); + response.getAirspySettings()->setLog2Decim(settings.m_log2Decim); + response.getAirspySettings()->setFcPos((int) settings.m_fcPos); + response.getAirspySettings()->setBiasT(settings.m_biasT ? 1 : 0); + response.getAirspySettings()->setDcBlock(settings.m_dcBlock ? 1 : 0); + response.getAirspySettings()->setIqCorrection(settings.m_iqCorrection ? 1 : 0); + response.getAirspySettings()->setTransverterDeltaFrequency(settings.m_transverterDeltaFrequency); + response.getAirspySettings()->setTransverterMode(settings.m_transverterMode ? 1 : 0); + + if (response.getAirspySettings()->getFileRecordName()) { + *response.getAirspySettings()->getFileRecordName() = settings.m_fileRecordName; + } else { + response.getAirspySettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + } +} + +void AirspyInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + response.getAirspyReport()->setSampleRates(new QList); + + for (std::vector::const_iterator it = getSampleRates().begin(); it != getSampleRates().end(); ++it) + { + response.getAirspyReport()->getSampleRates()->append(new SWGSDRangel::SWGSampleRate); + response.getAirspyReport()->getSampleRates()->back()->setRate(*it); + } +} diff --git a/plugins/samplesource/airspy/airspyinput.h b/plugins/samplesource/airspy/airspyinput.h index d120c155d..f6d4788f2 100644 --- a/plugins/samplesource/airspy/airspyinput.h +++ b/plugins/samplesource/airspy/airspyinput.h @@ -111,6 +111,16 @@ public: virtual bool handleMessage(const Message& message); + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + virtual int webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage); @@ -120,6 +130,10 @@ public: SWGSDRangel::SWGDeviceState& response, QString& errorMessage); + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + static const qint64 loLowLimitFreq; static const qint64 loHighLimitFreq; @@ -129,6 +143,8 @@ private: bool applySettings(const AirspySettings& settings, bool force); struct airspy_device *open_airspy_from_sequence(int sequence); void setDeviceCenterFrequency(quint64 freq); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const AirspySettings& settings); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); DeviceSourceAPI *m_deviceAPI; QMutex m_mutex; diff --git a/plugins/samplesource/airspy/airspyplugin.cpp b/plugins/samplesource/airspy/airspyplugin.cpp index 355d6df56..a3a65cf75 100644 --- a/plugins/samplesource/airspy/airspyplugin.cpp +++ b/plugins/samplesource/airspy/airspyplugin.cpp @@ -15,10 +15,13 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include #include +#ifdef SERVER_MODE +#include "airspyinput.h" +#else #include "airspygui.h" +#endif #include "airspyplugin.h" #include @@ -29,7 +32,7 @@ const int AirspyPlugin::m_maxDevices = 32; const PluginDescriptor AirspyPlugin::m_pluginDescriptor = { QString("Airspy Input"), - QString("3.11.0"), + QString("4.0.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, @@ -125,6 +128,15 @@ PluginInterface::SamplingDevices AirspyPlugin::enumSampleSources() return result; } +#ifdef SERVER_MODE +PluginInstanceGUI* AirspyPlugin::createSampleSourcePluginInstanceGUI( + const QString& sourceId __attribute__((unused)), + QWidget **widget __attribute__((unused)), + DeviceUISet *deviceUISet __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* AirspyPlugin::createSampleSourcePluginInstanceGUI( const QString& sourceId, QWidget **widget, @@ -141,6 +153,7 @@ PluginInstanceGUI* AirspyPlugin::createSampleSourcePluginInstanceGUI( return 0; } } +#endif DeviceSampleSource *AirspyPlugin::createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI) { diff --git a/plugins/samplesource/airspy/airspysettings.cpp b/plugins/samplesource/airspy/airspysettings.cpp index ef8dbb1a0..d1c7d786b 100644 --- a/plugins/samplesource/airspy/airspysettings.cpp +++ b/plugins/samplesource/airspy/airspysettings.cpp @@ -40,6 +40,7 @@ void AirspySettings::resetToDefaults() m_iqCorrection = false; m_transverterMode = false; m_transverterDeltaFrequency = 0; + m_fileRecordName = ""; } QByteArray AirspySettings::serialize() const diff --git a/plugins/samplesource/airspy/airspysettings.h b/plugins/samplesource/airspy/airspysettings.h index 4e42fe061..b8daf0ea0 100644 --- a/plugins/samplesource/airspy/airspysettings.h +++ b/plugins/samplesource/airspy/airspysettings.h @@ -17,6 +17,8 @@ #ifndef _AIRSPY_AIRSPYSETTINGS_H_ #define _AIRSPY_AIRSPYSETTINGS_H_ +#include + struct AirspySettings { typedef enum { FC_POS_INFRA = 0, @@ -39,6 +41,7 @@ struct AirspySettings { bool m_iqCorrection; bool m_transverterMode; qint64 m_transverterDeltaFrequency; + QString m_fileRecordName; AirspySettings(); void resetToDefaults(); diff --git a/plugins/samplesource/airspy/airspythread.cpp b/plugins/samplesource/airspy/airspythread.cpp index 3b2424ba5..7d03055d4 100644 --- a/plugins/samplesource/airspy/airspythread.cpp +++ b/plugins/samplesource/airspy/airspythread.cpp @@ -16,6 +16,7 @@ #include #include +#include #include "airspythread.h" @@ -34,6 +35,7 @@ AirspyThread::AirspyThread(struct airspy_device* dev, SampleSinkFifo* sampleFifo m_fcPos(0) { m_this = this; + std::fill(m_buf, m_buf + 2*AIRSPY_BLOCKSIZE, 0); } AirspyThread::~AirspyThread() diff --git a/plugins/samplesource/airspy/airspythread.h b/plugins/samplesource/airspy/airspythread.h index b21c894d3..48511b18a 100644 --- a/plugins/samplesource/airspy/airspythread.h +++ b/plugins/samplesource/airspy/airspythread.h @@ -55,11 +55,7 @@ private: int m_fcPos; static AirspyThread *m_this; -#ifdef SDR_RX_SAMPLE_24BIT - Decimators m_decimators; -#else Decimators m_decimators; -#endif void run(); void callback(const qint16* buf, qint32 len); diff --git a/plugins/samplesource/airspyhf/airspyhf.pro b/plugins/samplesource/airspyhf/airspyhf.pro index c31ae2e71..6f95b2485 100644 --- a/plugins/samplesource/airspyhf/airspyhf.pro +++ b/plugins/samplesource/airspyhf/airspyhf.pro @@ -11,9 +11,10 @@ QT += core gui widgets multimedia opengl TARGET = inputairspyhf -CONFIG(MINGW32):LIBAIRSPYHFSRC = "D:\softs\airspyhf" -CONFIG(MINGW64):LIBAIRSPYHFSRC = "D:\softs\airspyhf" +CONFIG(MINGW32):LIBAIRSPYHFSRC = "C:\softs\airspyhf" +CONFIG(MINGW64):LIBAIRSPYHFSRC = "C:\softs\airspyhf" INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client diff --git a/plugins/samplesource/airspyhf/airspyhfgui.cpp b/plugins/samplesource/airspyhf/airspyhfgui.cpp index e8e31681f..3cfb3f103 100644 --- a/plugins/samplesource/airspyhf/airspyhfgui.cpp +++ b/plugins/samplesource/airspyhf/airspyhfgui.cpp @@ -38,7 +38,7 @@ AirspyHFGui::AirspyHFGui(DeviceUISet *deviceUISet, QWidget* parent) : m_forceSettings(true), m_settings(), m_sampleSource(0), - m_lastEngineState((DSPDeviceSourceEngine::State)-1) + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted) { m_sampleSource = (AirspyHFInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); @@ -55,6 +55,7 @@ AirspyHFGui::AirspyHFGui(DeviceUISet *deviceUISet, QWidget* parent) : m_rates = ((AirspyHFInput*) m_sampleSource)->getSampleRates(); displaySampleRates(); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); sendSettings(); } @@ -284,7 +285,7 @@ void AirspyHFGui::on_sampleRate_currentIndexChanged(int index) void AirspyHFGui::on_decim_currentIndexChanged(int index) { - if ((index < 0) || (index > 5)) + if ((index < 0) || (index > 6)) return; m_settings.m_log2Decim = index; sendSettings(); diff --git a/plugins/samplesource/airspyhf/airspyhfgui.ui b/plugins/samplesource/airspyhf/airspyhfgui.ui index f08fe7d30..73334a081 100644 --- a/plugins/samplesource/airspyhf/airspyhfgui.ui +++ b/plugins/samplesource/airspyhf/airspyhfgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -135,7 +135,7 @@ - DejaVu Sans Mono + Liberation Mono 20 @@ -384,6 +384,11 @@ 32 + + + 64 + + diff --git a/plugins/samplesource/airspyhf/airspyhfinput.cpp b/plugins/samplesource/airspyhf/airspyhfinput.cpp index ac9ee6fa4..1cadfcc39 100644 --- a/plugins/samplesource/airspyhf/airspyhfinput.cpp +++ b/plugins/samplesource/airspyhf/airspyhfinput.cpp @@ -20,6 +20,8 @@ #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" +#include "SWGDeviceReport.h" +#include "SWGAirspyHFReport.h" #include #include @@ -28,7 +30,6 @@ #include "airspyhfinput.h" -#include "airspyhfgui.h" #include "airspyhfplugin.h" #include "airspyhfsettings.h" #include "airspyhfthread.h" @@ -51,10 +52,7 @@ AirspyHFInput::AirspyHFInput(DeviceSourceAPI *deviceAPI) : m_running(false) { openDevice(); - - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID()); - m_fileSink = new FileRecord(std::string(recFileNameCStr)); + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); m_deviceAPI->addSink(m_fileSink); } @@ -156,13 +154,7 @@ bool AirspyHFInput::start() if (m_running) { stop(); } - if ((m_airspyHFThread = new AirspyHFThread(m_dev, &m_sampleFifo)) == 0) - { - qCritical("AirspyHFInput::start: out of memory"); - stop(); - return false; - } - + m_airspyHFThread = new AirspyHFThread(m_dev, &m_sampleFifo); int sampleRateIndex = m_settings.m_devSampleRateIndex; if (m_settings.m_devSampleRateIndex >= m_sampleRates.size()) { @@ -310,13 +302,11 @@ bool AirspyHFInput::handleMessage(const Message& message) if (m_deviceAPI->initAcquisition()) { m_deviceAPI->startAcquisition(); - DSPEngine::instance()->startAudioOutput(); } } else { m_deviceAPI->stopAcquisition(); - DSPEngine::instance()->stopAudioOutput(); } return true; @@ -326,9 +316,18 @@ bool AirspyHFInput::handleMessage(const Message& message) MsgFileRecord& conf = (MsgFileRecord&) message; qDebug() << "AirspyHFInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); - if (conf.getStartStop()) { + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSink->setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); + } + m_fileSink->startRecording(); - } else { + } + else + { m_fileSink->stopRecording(); } @@ -485,6 +484,100 @@ airspyhf_device_t *AirspyHFInput::open_airspyhf_from_serial(const QString& seria } } +int AirspyHFInput::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setAirspyHfSettings(new SWGSDRangel::SWGAirspyHFSettings()); + response.getAirspyHfSettings()->init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int AirspyHFInput::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage __attribute__((unused))) +{ + AirspyHFSettings settings = m_settings; + + if (deviceSettingsKeys.contains("centerFrequency")) { + settings.m_centerFrequency = response.getAirspyHfSettings()->getCenterFrequency(); + } + if (deviceSettingsKeys.contains("devSampleRateIndex")) { + settings.m_devSampleRateIndex = response.getAirspyHfSettings()->getDevSampleRateIndex(); + } + if (deviceSettingsKeys.contains("LOppmTenths")) { + settings.m_LOppmTenths = response.getAirspyHfSettings()->getLOppmTenths(); + } + if (deviceSettingsKeys.contains("log2Decim")) { + settings.m_log2Decim = response.getAirspyHfSettings()->getLog2Decim(); + } + if (deviceSettingsKeys.contains("transverterDeltaFrequency")) { + settings.m_transverterDeltaFrequency = response.getAirspyHfSettings()->getTransverterDeltaFrequency(); + } + if (deviceSettingsKeys.contains("transverterMode")) { + settings.m_transverterMode = response.getAirspyHfSettings()->getTransverterMode() != 0; + } + if (deviceSettingsKeys.contains("bandIndex")) { + settings.m_bandIndex = response.getAirspyHfSettings()->getBandIndex() != 0; + } + if (deviceSettingsKeys.contains("fileRecordName")) { + settings.m_fileRecordName = *response.getAirspyHfSettings()->getFileRecordName(); + } + + MsgConfigureAirspyHF *msg = MsgConfigureAirspyHF::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureAirspyHF *msgToGUI = MsgConfigureAirspyHF::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +void AirspyHFInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const AirspyHFSettings& settings) +{ + response.getAirspyHfSettings()->setCenterFrequency(settings.m_centerFrequency); + response.getAirspyHfSettings()->setDevSampleRateIndex(settings.m_devSampleRateIndex); + response.getAirspyHfSettings()->setLOppmTenths(settings.m_LOppmTenths); + response.getAirspyHfSettings()->setLog2Decim(settings.m_log2Decim); + response.getAirspyHfSettings()->setTransverterDeltaFrequency(settings.m_transverterDeltaFrequency); + response.getAirspyHfSettings()->setTransverterMode(settings.m_transverterMode ? 1 : 0); + response.getAirspyHfSettings()->setBandIndex(settings.m_bandIndex ? 1 : 0); + + if (response.getAirspyHfSettings()->getFileRecordName()) { + *response.getAirspyHfSettings()->getFileRecordName() = settings.m_fileRecordName; + } else { + response.getAirspyHfSettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + } +} + +void AirspyHFInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + response.getAirspyHfReport()->setSampleRates(new QList); + + for (std::vector::const_iterator it = getSampleRates().begin(); it != getSampleRates().end(); ++it) + { + response.getAirspyHfReport()->getSampleRates()->append(new SWGSDRangel::SWGSampleRate); + response.getAirspyHfReport()->getSampleRates()->back()->setRate(*it); + } +} + +int AirspyHFInput::webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setAirspyHfReport(new SWGSDRangel::SWGAirspyHFReport()); + response.getAirspyHfReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} + int AirspyHFInput::webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage __attribute__((unused))) diff --git a/plugins/samplesource/airspyhf/airspyhfinput.h b/plugins/samplesource/airspyhf/airspyhfinput.h index f88e312ab..bb5e5e213 100644 --- a/plugins/samplesource/airspyhf/airspyhfinput.h +++ b/plugins/samplesource/airspyhf/airspyhfinput.h @@ -112,6 +112,20 @@ public: virtual bool handleMessage(const Message& message); + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + virtual int webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage); @@ -132,6 +146,8 @@ private: bool applySettings(const AirspyHFSettings& settings, bool force); airspyhf_device_t *open_airspyhf_from_serial(const QString& serialStr); void setDeviceCenterFrequency(quint64 freq, const AirspyHFSettings& settings); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const AirspyHFSettings& settings); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); DeviceSourceAPI *m_deviceAPI; QMutex m_mutex; diff --git a/plugins/samplesource/airspyhf/airspyhfplugin.cpp b/plugins/samplesource/airspyhf/airspyhfplugin.cpp index e5e4fc5f3..b593150df 100644 --- a/plugins/samplesource/airspyhf/airspyhfplugin.cpp +++ b/plugins/samplesource/airspyhf/airspyhfplugin.cpp @@ -15,19 +15,22 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include #include #include #include "plugin/pluginapi.h" #include "util/simpleserializer.h" #include "airspyhfplugin.h" +#ifdef SERVER_MODE +#include "airspyhfinput.h" +#else #include "airspyhfgui.h" +#endif const PluginDescriptor AirspyHFPlugin::m_pluginDescriptor = { QString("AirspyHF Input"), - QString("3.12.0"), + QString("4.0.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, @@ -95,6 +98,15 @@ PluginInterface::SamplingDevices AirspyHFPlugin::enumSampleSources() return result; } +#ifdef SERVER_MODE +PluginInstanceGUI* AirspyHFPlugin::createSampleSourcePluginInstanceGUI( + const QString& sourceId __attribute__((unused)), + QWidget **widget __attribute__((unused)), + DeviceUISet *deviceUISet __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* AirspyHFPlugin::createSampleSourcePluginInstanceGUI( const QString& sourceId, QWidget **widget, @@ -111,6 +123,7 @@ PluginInstanceGUI* AirspyHFPlugin::createSampleSourcePluginInstanceGUI( return 0; } } +#endif DeviceSampleSource *AirspyHFPlugin::createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI) { diff --git a/plugins/samplesource/airspyhf/airspyhfsettings.cpp b/plugins/samplesource/airspyhf/airspyhfsettings.cpp index 46818093f..a0971776c 100644 --- a/plugins/samplesource/airspyhf/airspyhfsettings.cpp +++ b/plugins/samplesource/airspyhf/airspyhfsettings.cpp @@ -33,6 +33,7 @@ void AirspyHFSettings::resetToDefaults() m_transverterMode = false; m_transverterDeltaFrequency = 0; m_bandIndex = 0; + m_fileRecordName = ""; } QByteArray AirspyHFSettings::serialize() const diff --git a/plugins/samplesource/airspyhf/airspyhfsettings.h b/plugins/samplesource/airspyhf/airspyhfsettings.h index b806b59eb..937355d96 100644 --- a/plugins/samplesource/airspyhf/airspyhfsettings.h +++ b/plugins/samplesource/airspyhf/airspyhfsettings.h @@ -17,6 +17,8 @@ #ifndef _AIRSPYHFF_AIRSPYHFSETTINGS_H_ #define _AIRSPYHFF_AIRSPYHFSETTINGS_H_ +#include + struct AirspyHFSettings { quint64 m_centerFrequency; @@ -26,6 +28,7 @@ struct AirspyHFSettings bool m_transverterMode; qint64 m_transverterDeltaFrequency; quint32 m_bandIndex; + QString m_fileRecordName; AirspyHFSettings(); void resetToDefaults(); diff --git a/plugins/samplesource/airspyhf/airspyhfthread.cpp b/plugins/samplesource/airspyhf/airspyhfthread.cpp index 3a1fba0d6..cde2ac7f1 100644 --- a/plugins/samplesource/airspyhf/airspyhfthread.cpp +++ b/plugins/samplesource/airspyhf/airspyhfthread.cpp @@ -1,3 +1,4 @@ + /////////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2018 Edouard Griffiths, F4EXB // // // @@ -31,6 +32,7 @@ AirspyHFThread::AirspyHFThread(airspyhf_device_t* dev, SampleSinkFifo* sampleFif m_samplerate(10), m_log2Decim(0) { + memset((char*) m_buf, 0, 2*AIRSPYHF_BLOCKSIZE*sizeof(qint16)); m_this = this; } diff --git a/plugins/samplesource/airspyhf/airspyhfthread.h b/plugins/samplesource/airspyhf/airspyhfthread.h index 9b7ab98ba..05dd414ca 100644 --- a/plugins/samplesource/airspyhf/airspyhfthread.h +++ b/plugins/samplesource/airspyhf/airspyhfthread.h @@ -17,13 +17,13 @@ #ifndef INCLUDE_AIRSPYHFTHREAD_H #define INCLUDE_AIRSPYHFTHREAD_H +#include #include #include #include #include #include "dsp/samplesinkfifo.h" -#include "dsp/decimatorsf.h" #define AIRSPYHF_BLOCKSIZE (1<<17) @@ -53,7 +53,7 @@ private: unsigned int m_log2Decim; static AirspyHFThread *m_this; - DecimatorsF m_decimators; + DecimatorsFI m_decimators; void run(); void callback(const float* buf, qint32 len); diff --git a/plugins/samplesource/airspyhf/readme.md b/plugins/samplesource/airspyhf/readme.md index 158968bba..8a7a73b7e 100644 --- a/plugins/samplesource/airspyhf/readme.md +++ b/plugins/samplesource/airspyhf/readme.md @@ -35,7 +35,7 @@ Device start / stop button. - Blue triangle icon: device is ready and can be started - Green square icon: device is running and can be stopped - - Magenta (or pink) square icon: an error occured. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again. + - Magenta (or pink) square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again.

1.3: Record

@@ -62,27 +62,27 @@ Use this combo box to select the HF or VHF range. This will set the limits of th - HF: 9 kHz to 31 MHz - VHF: 60 to 260 MHz -

5: Device to hast sample rate

+

5: Device to host sample rate

This is the device to host sample rate in kilo samples per second (kS/s). -Although the combo box is there to present a choice of sample rates at present the AirspyHF+ deals only with 768 kS/s. However the support library has provision to get a list of sample rates from the device incase of future developments. +Although the combo box is there to present a choice of sample rates at present the AirspyHF+ deals only with 768 kS/s. However the support library has provision to get a list of sample rates from the device in case of future developments.

6: Decimation factor

-The I/Q stream from the AirspyHF to host is downsampled by a power of two before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8, 16, 32, 64. When using audio channel plugins (AM, DSD, NFM, SSB...) please make sure that the sample rate is not less than 48 kHz (no decimation by 32 or 64). +The I/Q stream from the AirspyHF to host is downsampled by a power of two before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8, 16, 32, 64. When using audio channel plugins (AM, DSD, NFM, SSB...) please make sure that the sample rate is not less than the audio sample rate.

7: Transverter mode open dialog

This button opens a dialog to set the transverter mode frequency translation options: -![SDR Daemon source input stream trasverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) +![SDR Daemon source input stream transverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) Note that if you mouse over the button a tooltip appears that displays the translating frequency and if translation is enabled or disabled. When the frequency translation is enabled the button is lit.

7a.1: Translating frequency

-You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. The frequency set in the device is the frequency on the main dial (1) minus this frequency. Thus it is positive for down converters and negative for up converters. diff --git a/plugins/samplesource/airspyhfi/CMakeLists.txt b/plugins/samplesource/airspyhfi/CMakeLists.txt deleted file mode 100644 index f03cd4ff8..000000000 --- a/plugins/samplesource/airspyhfi/CMakeLists.txt +++ /dev/null @@ -1,78 +0,0 @@ -project(airspyhfi) - -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") - -set(airspyhfi_SOURCES - airspyhfigui.cpp - airspyhfiinput.cpp - airspyhfiplugin.cpp - airspyhfisettings.cpp - airspyhfithread.cpp -) - -set(airspyhfi_HEADERS - airspyhfigui.h - airspyhfiinput.h - airspyhfiplugin.h - airspyhfisettings.h - airspyhfithread.h -) - -set(airspyhfi_FORMS - airspyhfigui.ui -) - -if (BUILD_DEBIAN) -include_directories( - . - ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client - ${LIBAIRSPYHFSRC} - ${LIBAIRSPYHFSRC}/libairspyhf/src -) -else (BUILD_DEBIAN) -include_directories( - . - ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client - ${LIBAIRSPYHF_INCLUDE_DIR} -) -endif (BUILD_DEBIAN) - -#include(${QT_USE_FILE}) -#add_definitions(${QT_DEFINITIONS}) -add_definitions("${QT_DEFINITIONS} -DLIBAIRSPY_DYN_RATES") -add_definitions(-DQT_PLUGIN) -add_definitions(-DQT_SHARED) - -#qt4_wrap_cpp(airspyhf_HEADERS_MOC ${airspyhf_HEADERS}) -qt5_wrap_ui(airspyhfi_FORMS_HEADERS ${airspyhfi_FORMS}) - -add_library(inputairspyhfi SHARED - ${airspyhfi_SOURCES} - ${airspyhfi_HEADERS_MOC} - ${airspyhfi_FORMS_HEADERS} -) - -if (BUILD_DEBIAN) -target_link_libraries(inputairspyhfi - ${QT_LIBRARIES} - airspyhf - sdrbase - sdrgui - swagger -) -else (BUILD_DEBIAN) -target_link_libraries(inputairspyhfi - ${QT_LIBRARIES} - ${LIBAIRSPYHF_LIBRARIES} - sdrbase - sdrgui - swagger -) -endif (BUILD_DEBIAN) - - -qt5_use_modules(inputairspyhfi Core Widgets) - -install(TARGETS inputairspyhfi DESTINATION lib/plugins/samplesource) diff --git a/plugins/samplesource/airspyhfi/airspyhfigui.cpp b/plugins/samplesource/airspyhfi/airspyhfigui.cpp deleted file mode 100644 index 20c3d9a8c..000000000 --- a/plugins/samplesource/airspyhfi/airspyhfigui.cpp +++ /dev/null @@ -1,409 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB // -// // -// This program is free software; you can redistribute it and/or modify // -// it under the terms of the GNU General Public License as published by // -// the Free Software Foundation as version 3 of the License, or // -// // -// This program is distributed in the hope that it will be useful, // -// but WITHOUT ANY WARRANTY; without even the implied warranty of // -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // -// GNU General Public License V3 for more details. // -// // -// You should have received a copy of the GNU General Public License // -// along with this program. If not, see . // -/////////////////////////////////////////////////////////////////////////////////// - -#include -#include - -#include - -#include -#include "device/deviceuiset.h" -#include - -#include "ui_airspyhfigui.h" -#include "gui/colormapper.h" -#include "gui/glspectrum.h" -#include "dsp/dspengine.h" -#include "dsp/dspcommands.h" -#include "airspyhfigui.h" - -AirspyHFIGui::AirspyHFIGui(DeviceUISet *deviceUISet, QWidget* parent) : - QWidget(parent), - ui(new Ui::AirspyHFIGui), - m_deviceUISet(deviceUISet), - m_doApplySettings(true), - m_forceSettings(true), - m_settings(), - m_sampleSource(0), - m_lastEngineState((DSPDeviceSourceEngine::State)-1) -{ - m_sampleSource = (AirspyHFIInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); - - ui->setupUi(this); - ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); - updateFrequencyLimits(); - - connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); - connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); - m_statusTimer.start(500); - - displaySettings(); - - m_rates = ((AirspyHFIInput*) m_sampleSource)->getSampleRates(); - displaySampleRates(); - connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); - - sendSettings(); -} - -AirspyHFIGui::~AirspyHFIGui() -{ - delete ui; -} - -void AirspyHFIGui::destroy() -{ - delete this; -} - -void AirspyHFIGui::setName(const QString& name) -{ - setObjectName(name); -} - -QString AirspyHFIGui::getName() const -{ - return objectName(); -} - -void AirspyHFIGui::resetToDefaults() -{ - m_settings.resetToDefaults(); - displaySettings(); - sendSettings(); -} - -qint64 AirspyHFIGui::getCenterFrequency() const -{ - return m_settings.m_centerFrequency; -} - -void AirspyHFIGui::setCenterFrequency(qint64 centerFrequency) -{ - m_settings.m_centerFrequency = centerFrequency; - displaySettings(); - sendSettings(); -} - -QByteArray AirspyHFIGui::serialize() const -{ - return m_settings.serialize(); -} - -bool AirspyHFIGui::deserialize(const QByteArray& data) -{ - if(m_settings.deserialize(data)) { - displaySettings(); - m_forceSettings = true; - sendSettings(); - return true; - } else { - resetToDefaults(); - return false; - } -} - -bool AirspyHFIGui::handleMessage(const Message& message) -{ - if (AirspyHFIInput::MsgConfigureAirspyHFI::match(message)) - { - const AirspyHFIInput::MsgConfigureAirspyHFI& cfg = (AirspyHFIInput::MsgConfigureAirspyHFI&) message; - m_settings = cfg.getSettings(); - blockApplySettings(true); - displaySettings(); - blockApplySettings(false); - return true; - } - else if (AirspyHFIInput::MsgStartStop::match(message)) - { - AirspyHFIInput::MsgStartStop& notif = (AirspyHFIInput::MsgStartStop&) message; - blockApplySettings(true); - ui->startStop->setChecked(notif.getStartStop()); - blockApplySettings(false); - - return true; - } - else - { - return false; - } -} - -void AirspyHFIGui::handleInputMessages() -{ - Message* message; - - while ((message = m_inputMessageQueue.pop()) != 0) - { - qDebug("AirspyHFGui::handleInputMessages: message: %s", message->getIdentifier()); - - if (DSPSignalNotification::match(*message)) - { - DSPSignalNotification* notif = (DSPSignalNotification*) message; - m_sampleRate = notif->getSampleRate(); - m_deviceCenterFrequency = notif->getCenterFrequency(); - qDebug("AirspyGui::handleInputMessages: DSPSignalNotification: SampleRate:%d, CenterFrequency:%llu", notif->getSampleRate(), notif->getCenterFrequency()); - updateSampleRateAndFrequency(); - - delete message; - } - else - { - if (handleMessage(*message)) - { - delete message; - } - } - } -} - -void AirspyHFIGui::updateSampleRateAndFrequency() -{ - m_deviceUISet->getSpectrum()->setSampleRate(m_sampleRate); - m_deviceUISet->getSpectrum()->setCenterFrequency(m_deviceCenterFrequency); - ui->deviceRateText->setText(tr("%1k").arg((float)m_sampleRate / 1000)); -} - -void AirspyHFIGui::updateFrequencyLimits() -{ - // values in kHz - qint64 deltaFrequency = m_settings.m_transverterMode ? m_settings.m_transverterDeltaFrequency/1000 : 0; - - qint64 minLimit; - qint64 maxLimit; - - switch(m_settings.m_bandIndex) - { - case 1: - minLimit = AirspyHFIInput::loLowLimitFreqVHF/1000 + deltaFrequency; - maxLimit = AirspyHFIInput::loHighLimitFreqVHF/1000 + deltaFrequency; - break; - case 0: - default: - minLimit = AirspyHFIInput::loLowLimitFreqHF/1000 + deltaFrequency; - maxLimit = AirspyHFIInput::loHighLimitFreqHF/1000 + deltaFrequency; - break; - } - - minLimit = minLimit < 0 ? 0 : minLimit > 9999999 ? 9999999 : minLimit; - maxLimit = maxLimit < 0 ? 0 : maxLimit > 9999999 ? 9999999 : maxLimit; - - qDebug("AirspyHFGui::updateFrequencyLimits: delta: %lld min: %lld max: %lld", deltaFrequency, minLimit, maxLimit); - - ui->centerFrequency->setValueRange(7, minLimit, maxLimit); -} - -void AirspyHFIGui::displaySettings() -{ - blockApplySettings(true); - ui->band->blockSignals(true); - m_settings.m_bandIndex = m_settings.m_centerFrequency <= 31000000UL ? 0 : 1; // override - ui->band->setCurrentIndex(m_settings.m_bandIndex); - updateFrequencyLimits(); - ui->transverter->setDeltaFrequency(m_settings.m_transverterDeltaFrequency); - ui->transverter->setDeltaFrequencyActive(m_settings.m_transverterMode); - ui->centerFrequency->setValue(m_settings.m_centerFrequency / 1000); - ui->LOppm->setValue(m_settings.m_LOppmTenths); - ui->LOppmText->setText(QString("%1").arg(QString::number(m_settings.m_LOppmTenths/10.0, 'f', 1))); - ui->autoCorr->setCurrentIndex(m_settings.m_autoCorrOptions); - ui->sampleRate->setCurrentIndex(m_settings.m_devSampleRateIndex); - ui->decim->setCurrentIndex(m_settings.m_log2Decim); - ui->band->blockSignals(false); - blockApplySettings(false); -} - -void AirspyHFIGui::displaySampleRates() -{ - unsigned int savedIndex = m_settings.m_devSampleRateIndex; - ui->sampleRate->blockSignals(true); - - if (m_rates.size() > 0) - { - ui->sampleRate->clear(); - - for (unsigned int i = 0; i < m_rates.size(); i++) - { - int sampleRate = m_rates[i]/1000; - ui->sampleRate->addItem(QString("%1").arg(QString("%1").arg(sampleRate, 5, 10, QChar(' ')))); - } - } - - ui->sampleRate->blockSignals(false); - - if (savedIndex < m_rates.size()) - { - ui->sampleRate->setCurrentIndex(savedIndex); - } - else - { - ui->sampleRate->setCurrentIndex((int) m_rates.size()-1); - } -} - -void AirspyHFIGui::sendSettings() -{ - if(!m_updateTimer.isActive()) - m_updateTimer.start(100); -} - -void AirspyHFIGui::on_centerFrequency_changed(quint64 value) -{ - m_settings.m_centerFrequency = value * 1000; - sendSettings(); -} - -void AirspyHFIGui::on_LOppm_valueChanged(int value) -{ - m_settings.m_LOppmTenths = value; - ui->LOppmText->setText(QString("%1").arg(QString::number(m_settings.m_LOppmTenths/10.0, 'f', 1))); - sendSettings(); -} - -void AirspyHFIGui::on_resetLOppm_clicked() -{ - ui->LOppm->setValue(0); -} - -void AirspyHFIGui::on_autoCorr_currentIndexChanged(int index) -{ - if ((index < 0) || (index > AirspyHFISettings::AutoCorrLast)) { - return; - } - - m_settings.m_autoCorrOptions = (AirspyHFISettings::AutoCorrOptions) index; - sendSettings(); -} - -void AirspyHFIGui::on_sampleRate_currentIndexChanged(int index) -{ - m_settings.m_devSampleRateIndex = index; - sendSettings(); -} - -void AirspyHFIGui::on_decim_currentIndexChanged(int index) -{ - if ((index < 0) || (index > 5)) - return; - m_settings.m_log2Decim = index; - sendSettings(); -} - -void AirspyHFIGui::on_startStop_toggled(bool checked) -{ - if (m_doApplySettings) - { - AirspyHFIInput::MsgStartStop *message = AirspyHFIInput::MsgStartStop::create(checked); - m_sampleSource->getInputMessageQueue()->push(message); - } -} - -void AirspyHFIGui::on_record_toggled(bool checked) -{ - if (checked) { - ui->record->setStyleSheet("QToolButton { background-color : red; }"); - } else { - ui->record->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); - } - - AirspyHFIInput::MsgFileRecord* message = AirspyHFIInput::MsgFileRecord::create(checked); - m_sampleSource->getInputMessageQueue()->push(message); -} - -void AirspyHFIGui::on_transverter_clicked() -{ - m_settings.m_transverterMode = ui->transverter->getDeltaFrequencyAcive(); - m_settings.m_transverterDeltaFrequency = ui->transverter->getDeltaFrequency(); - qDebug("AirspyHFGui::on_transverter_clicked: %lld Hz %s", m_settings.m_transverterDeltaFrequency, m_settings.m_transverterMode ? "on" : "off"); - updateFrequencyLimits(); - m_settings.m_centerFrequency = ui->centerFrequency->getValueNew()*1000; - sendSettings(); -} - -void AirspyHFIGui::on_band_currentIndexChanged(int index) -{ - if ((index < 0) || (index > 1)) { - return; - } - - m_settings.m_bandIndex = index; - updateFrequencyLimits(); - qDebug("AirspyHFGui::on_band_currentIndexChanged: freq: %llu", ui->centerFrequency->getValueNew() * 1000); - m_settings.m_centerFrequency = ui->centerFrequency->getValueNew() * 1000; - sendSettings(); -} - -void AirspyHFIGui::updateHardware() -{ - qDebug() << "AirspyHFGui::updateHardware"; - AirspyHFIInput::MsgConfigureAirspyHFI* message = AirspyHFIInput::MsgConfigureAirspyHFI::create(m_settings, m_forceSettings); - m_sampleSource->getInputMessageQueue()->push(message); - m_forceSettings = false; - m_updateTimer.stop(); -} - -void AirspyHFIGui::updateStatus() -{ - int state = m_deviceUISet->m_deviceSourceAPI->state(); - - if(m_lastEngineState != state) - { - switch(state) - { - case DSPDeviceSourceEngine::StNotStarted: - ui->startStop->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); - break; - case DSPDeviceSourceEngine::StIdle: - ui->startStop->setStyleSheet("QToolButton { background-color : blue; }"); - break; - case DSPDeviceSourceEngine::StRunning: - ui->startStop->setStyleSheet("QToolButton { background-color : green; }"); - break; - case DSPDeviceSourceEngine::StError: - ui->startStop->setStyleSheet("QToolButton { background-color : red; }"); - QMessageBox::information(this, tr("Message"), m_deviceUISet->m_deviceSourceAPI->errorMessage()); - break; - default: - break; - } - - m_lastEngineState = state; - } -} - -uint32_t AirspyHFIGui::getDevSampleRate(unsigned int rate_index) -{ - if (rate_index < m_rates.size()) - { - return m_rates[rate_index]; - } - else - { - return m_rates[0]; - } -} - -int AirspyHFIGui::getDevSampleRateIndex(uint32_t sampeRate) -{ - for (unsigned int i=0; i < m_rates.size(); i++) - { - if (sampeRate == m_rates[i]) - { - return i; - } - } - - return -1; -} diff --git a/plugins/samplesource/airspyhfi/airspyhfiinput.cpp b/plugins/samplesource/airspyhfi/airspyhfiinput.cpp deleted file mode 100644 index a76504006..000000000 --- a/plugins/samplesource/airspyhfi/airspyhfiinput.cpp +++ /dev/null @@ -1,509 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB // -// // -// This program is free software; you can redistribute it and/or modify // -// it under the terms of the GNU General Public License as published by // -// the Free Software Foundation as version 3 of the License, or // -// // -// This program is distributed in the hope that it will be useful, // -// but WITHOUT ANY WARRANTY; without even the implied warranty of // -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // -// GNU General Public License V3 for more details. // -// // -// You should have received a copy of the GNU General Public License // -// along with this program. If not, see . // -/////////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include - -#include "SWGDeviceSettings.h" -#include "SWGDeviceState.h" - -#include -#include -#include "dsp/dspcommands.h" -#include "dsp/dspengine.h" - -#include "airspyhfisettings.h" -#include "airspyhfiinput.h" - -#include "airspyhfiplugin.h" -#include "airspyhfithread.h" -#include "airspyhfigui.h" - -MESSAGE_CLASS_DEFINITION(AirspyHFIInput::MsgConfigureAirspyHFI, Message) -MESSAGE_CLASS_DEFINITION(AirspyHFIInput::MsgStartStop, Message) -MESSAGE_CLASS_DEFINITION(AirspyHFIInput::MsgFileRecord, Message) - -const qint64 AirspyHFIInput::loLowLimitFreqHF = 9000L; -const qint64 AirspyHFIInput::loHighLimitFreqHF = 31000000L; -const qint64 AirspyHFIInput::loLowLimitFreqVHF = 60000000L; -const qint64 AirspyHFIInput::loHighLimitFreqVHF = 260000000L; - -AirspyHFIInput::AirspyHFIInput(DeviceSourceAPI *deviceAPI) : - m_deviceAPI(deviceAPI), - m_settings(), - m_dev(0), - m_airspyHFThread(0), - m_deviceDescription("AirspyHFI"), - m_running(false) -{ - openDevice(); - - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID()); - m_fileSink = new FileRecord(std::string(recFileNameCStr)); - m_deviceAPI->addSink(m_fileSink); -} - -AirspyHFIInput::~AirspyHFIInput() -{ - if (m_running) { stop(); } - m_deviceAPI->removeSink(m_fileSink); - delete m_fileSink; - closeDevice(); -} - -void AirspyHFIInput::destroy() -{ - delete this; -} - -bool AirspyHFIInput::openDevice() -{ - if (m_dev != 0) - { - closeDevice(); - } - - airspyhf_error rc; - - if (!m_sampleFifo.setSize(1<<19)) - { - qCritical("AirspyHFInput::start: could not allocate SampleFifo"); - return false; - } - - if ((m_dev = open_airspyhf_from_serial(m_deviceAPI->getSampleSourceSerial())) == 0) - { - qCritical("AirspyHFInput::start: could not open Airspy with serial %s", qPrintable(m_deviceAPI->getSampleSourceSerial())); - return false; - } - else - { - qDebug("AirspyHFInput::start: opened Airspy with serial %s", qPrintable(m_deviceAPI->getSampleSourceSerial())); - } - - uint32_t nbSampleRates; - uint32_t *sampleRates; - - rc = (airspyhf_error) airspyhf_get_samplerates(m_dev, &nbSampleRates, 0); - - if (rc == AIRSPYHF_SUCCESS) - { - qDebug("AirspyHFInput::start: %d sample rates for AirspyHF", nbSampleRates); - } - else - { - qCritical("AirspyHFInput::start: could not obtain the number of AirspyHF sample rates"); - return false; - } - - sampleRates = new uint32_t[nbSampleRates]; - - rc = (airspyhf_error) airspyhf_get_samplerates(m_dev, sampleRates, nbSampleRates); - - if (rc == AIRSPYHF_SUCCESS) - { - qDebug("AirspyHFInput::start: obtained AirspyHF sample rates"); - } - else - { - qCritical("AirspyHFInput::start: could not obtain AirspyHF sample rates"); - return false; - } - - m_sampleRates.clear(); - - for (unsigned int i = 0; i < nbSampleRates; i++) - { - m_sampleRates.push_back(sampleRates[i]); - qDebug("AirspyHFInput::start: sampleRates[%d] = %u Hz", i, sampleRates[i]); - } - - delete[] sampleRates; - - airspyhf_set_sample_type(m_dev, AIRSPYHF_SAMPLE_INT16_NDSP_IQ); - - return true; -} - -void AirspyHFIInput::init() -{ - applySettings(m_settings, true); -} - -bool AirspyHFIInput::start() -{ - QMutexLocker mutexLocker(&m_mutex); - - if (!m_dev) { - return false; - } - - if (m_running) { stop(); } - - if ((m_airspyHFThread = new AirspyHFIThread(m_dev, &m_sampleFifo)) == 0) - { - qCritical("AirspyHFInput::start: out of memory"); - stop(); - return false; - } - - m_airspyHFThread->setSamplerate(m_sampleRates[m_settings.m_devSampleRateIndex]); - m_airspyHFThread->setLog2Decimation(m_settings.m_log2Decim); - - m_airspyHFThread->startWork(); - - mutexLocker.unlock(); - - applySettings(m_settings, true); - - qDebug("AirspyHFInput::startInput: started"); - m_running = true; - - return true; -} - -void AirspyHFIInput::closeDevice() -{ - if (m_dev != 0) - { - airspyhf_stop(m_dev); - airspyhf_close(m_dev); - m_dev = 0; - } - - m_deviceDescription.clear(); -} - -void AirspyHFIInput::stop() -{ - qDebug("AirspyHFInput::stop"); - QMutexLocker mutexLocker(&m_mutex); - - if (m_airspyHFThread != 0) - { - m_airspyHFThread->stopWork(); - delete m_airspyHFThread; - m_airspyHFThread = 0; - } - - m_running = false; -} - -QByteArray AirspyHFIInput::serialize() const -{ - return m_settings.serialize(); -} - -bool AirspyHFIInput::deserialize(const QByteArray& data) -{ - bool success = true; - - if (!m_settings.deserialize(data)) - { - m_settings.resetToDefaults(); - success = false; - } - - MsgConfigureAirspyHFI* message = MsgConfigureAirspyHFI::create(m_settings, true); - m_inputMessageQueue.push(message); - - if (m_guiMessageQueue) - { - MsgConfigureAirspyHFI* messageToGUI = MsgConfigureAirspyHFI::create(m_settings, true); - m_guiMessageQueue->push(messageToGUI); - } - - return success; -} - -const QString& AirspyHFIInput::getDeviceDescription() const -{ - return m_deviceDescription; -} - -int AirspyHFIInput::getSampleRate() const -{ - int rate = m_sampleRates[m_settings.m_devSampleRateIndex]; - return (rate / (1<push(messageToGUI); - } -} - -bool AirspyHFIInput::handleMessage(const Message& message) -{ - if (MsgConfigureAirspyHFI::match(message)) - { - MsgConfigureAirspyHFI& conf = (MsgConfigureAirspyHFI&) message; - qDebug() << "MsgConfigureAirspyHF::handleMessage: MsgConfigureAirspyHF"; - - bool success = applySettings(conf.getSettings(), conf.getForce()); - - if (!success) - { - qDebug("MsgConfigureAirspyHF::handleMessage: AirspyHF config error"); - } - - return true; - } - else if (MsgStartStop::match(message)) - { - MsgStartStop& cmd = (MsgStartStop&) message; - qDebug() << "AirspyHFInput::handleMessage: MsgStartStop: " << (cmd.getStartStop() ? "start" : "stop"); - - if (cmd.getStartStop()) - { - if (m_deviceAPI->initAcquisition()) - { - m_deviceAPI->startAcquisition(); - DSPEngine::instance()->startAudioOutput(); - } - } - else - { - m_deviceAPI->stopAcquisition(); - DSPEngine::instance()->stopAudioOutput(); - } - - return true; - } - else if (MsgFileRecord::match(message)) - { - MsgFileRecord& conf = (MsgFileRecord&) message; - qDebug() << "AirspyHFInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); - - if (conf.getStartStop()) { - m_fileSink->startRecording(); - } else { - m_fileSink->stopRecording(); - } - - return true; - } - else - { - return false; - } -} - -void AirspyHFIInput::setDeviceCenterFrequency(quint64 freq_hz, const AirspyHFISettings& settings) -{ - switch(settings.m_bandIndex) - { - case 1: - freq_hz = freq_hz < loLowLimitFreqVHF ? loLowLimitFreqVHF : freq_hz > loHighLimitFreqVHF ? loHighLimitFreqVHF : freq_hz; - break; - case 0: - default: - freq_hz = freq_hz < loLowLimitFreqHF ? loLowLimitFreqHF : freq_hz > loHighLimitFreqHF ? loHighLimitFreqHF : freq_hz; - break; - } - - airspyhf_error rc = (airspyhf_error) airspyhf_set_freq(m_dev, static_cast(freq_hz)); - - if (rc == AIRSPYHF_SUCCESS) { - qDebug("AirspyHFInput::setDeviceCenterFrequency: frequency set to %llu Hz", freq_hz); - } else { - qWarning("AirspyHFInput::setDeviceCenterFrequency: could not frequency to %llu Hz", freq_hz); - } -} - -bool AirspyHFIInput::applySettings(const AirspyHFISettings& settings, bool force) -{ - QMutexLocker mutexLocker(&m_mutex); - - bool forwardChange = false; - airspyhf_error rc; - int sampleRateIndex = settings.m_devSampleRateIndex; - - qDebug() << "AirspyHFInput::applySettings"; - - if ((m_settings.m_autoCorrOptions != settings.m_autoCorrOptions) || force) - { - switch(settings.m_autoCorrOptions) - { - case AirspyHFISettings::AutoCorrDC: - m_deviceAPI->configureCorrections(true, false); - break; - case AirspyHFISettings::AutoCorrDCAndIQ: - m_deviceAPI->configureCorrections(true, true); - break; - case AirspyHFISettings::AutoCorrNone: - default: - m_deviceAPI->configureCorrections(false, false); - break; - } - } - - if ((m_settings.m_devSampleRateIndex != settings.m_devSampleRateIndex) || force) - { - forwardChange = true; - - if (settings.m_devSampleRateIndex >= m_sampleRates.size()) { - sampleRateIndex = m_sampleRates.size() - 1; - } - - if (m_dev != 0) - { - rc = (airspyhf_error) airspyhf_set_samplerate(m_dev, sampleRateIndex); - - if (rc != AIRSPYHF_SUCCESS) - { - qCritical("AirspyHFInput::applySettings: could not set sample rate index %u (%d S/s)", sampleRateIndex, m_sampleRates[sampleRateIndex]); - } - else if (m_airspyHFThread != 0) - { - qDebug("AirspyHFInput::applySettings: sample rate set to index: %u (%d S/s)", sampleRateIndex, m_sampleRates[sampleRateIndex]); - m_airspyHFThread->setSamplerate(m_sampleRates[sampleRateIndex]); - } - } - } - - if ((m_settings.m_log2Decim != settings.m_log2Decim) || force) - { - forwardChange = true; - - if (m_airspyHFThread != 0) - { - m_airspyHFThread->setLog2Decimation(settings.m_log2Decim); - qDebug() << "AirspyInput: set decimation to " << (1<handleMessage(*notif); // forward to file sink - m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif); - } - - m_settings = settings; - m_settings.m_devSampleRateIndex = sampleRateIndex; - return true; -} - -airspyhf_device_t *AirspyHFIInput::open_airspyhf_from_serial(const QString& serialStr) -{ - airspyhf_device_t *devinfo; - bool ok; - airspyhf_error rc; - - uint64_t serial = serialStr.toULongLong(&ok, 16); - - if (!ok) - { - qCritical("AirspyHFInput::open_airspyhf_from_serial: invalid serial %s", qPrintable(serialStr)); - return 0; - } - else - { - rc = (airspyhf_error) airspyhf_open_sn(&devinfo, serial); - - if (rc == AIRSPYHF_SUCCESS) { - return devinfo; - } else { - return 0; - } - } -} - -int AirspyHFIInput::webapiRunGet( - SWGSDRangel::SWGDeviceState& response, - QString& errorMessage __attribute__((unused))) -{ - m_deviceAPI->getDeviceEngineStateStr(*response.getState()); - return 200; -} - -int AirspyHFIInput::webapiRun( - bool run, - SWGSDRangel::SWGDeviceState& response, - QString& errorMessage __attribute__((unused))) -{ - m_deviceAPI->getDeviceEngineStateStr(*response.getState()); - MsgStartStop *message = MsgStartStop::create(run); - m_inputMessageQueue.push(message); - - if (m_guiMessageQueue) // forward to GUI if any - { - MsgStartStop *msgToGUI = MsgStartStop::create(run); - m_guiMessageQueue->push(msgToGUI); - } - - return 200; -} - diff --git a/plugins/samplesource/airspyhfi/airspyhfiinput.h b/plugins/samplesource/airspyhfi/airspyhfiinput.h deleted file mode 100644 index d0d0bbf99..000000000 --- a/plugins/samplesource/airspyhfi/airspyhfiinput.h +++ /dev/null @@ -1,147 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB // -// // -// This program is free software; you can redistribute it and/or modify // -// it under the terms of the GNU General Public License as published by // -// the Free Software Foundation as version 3 of the License, or // -// // -// This program is distributed in the hope that it will be useful, // -// but WITHOUT ANY WARRANTY; without even the implied warranty of // -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // -// GNU General Public License V3 for more details. // -// // -// You should have received a copy of the GNU General Public License // -// along with this program. If not, see . // -/////////////////////////////////////////////////////////////////////////////////// - -#ifndef INCLUDE_AIRSPYHFIINPUT_H -#define INCLUDE_AIRSPYHFIINPUT_H - -#include -#include - -#include -#include - -#include "airspyhfisettings.h" - -class DeviceSourceAPI; -class AirspyHFIThread; -class FileRecord; - -class AirspyHFIInput : public DeviceSampleSource { -public: - class MsgConfigureAirspyHFI : public Message { - MESSAGE_CLASS_DECLARATION - - public: - const AirspyHFISettings& getSettings() const { return m_settings; } - bool getForce() const { return m_force; } - - static MsgConfigureAirspyHFI* create(const AirspyHFISettings& settings, bool force) - { - return new MsgConfigureAirspyHFI(settings, force); - } - - private: - AirspyHFISettings m_settings; - bool m_force; - - MsgConfigureAirspyHFI(const AirspyHFISettings& settings, bool force) : - Message(), - m_settings(settings), - m_force(force) - { } - }; - - class MsgFileRecord : public Message { - MESSAGE_CLASS_DECLARATION - - public: - bool getStartStop() const { return m_startStop; } - - static MsgFileRecord* create(bool startStop) { - return new MsgFileRecord(startStop); - } - - protected: - bool m_startStop; - - MsgFileRecord(bool startStop) : - Message(), - m_startStop(startStop) - { } - }; - - class MsgStartStop : public Message { - MESSAGE_CLASS_DECLARATION - - public: - bool getStartStop() const { return m_startStop; } - - static MsgStartStop* create(bool startStop) { - return new MsgStartStop(startStop); - } - - protected: - bool m_startStop; - - MsgStartStop(bool startStop) : - Message(), - m_startStop(startStop) - { } - }; - - AirspyHFIInput(DeviceSourceAPI *deviceAPI); - virtual ~AirspyHFIInput(); - virtual void destroy(); - - virtual void init(); - virtual bool start(); - virtual void stop(); - - virtual QByteArray serialize() const; - virtual bool deserialize(const QByteArray& data); - - virtual void setMessageQueueToGUI(MessageQueue *queue) { m_guiMessageQueue = queue; } - virtual const QString& getDeviceDescription() const; - virtual int getSampleRate() const; - virtual quint64 getCenterFrequency() const; - virtual void setCenterFrequency(qint64 centerFrequency); - const std::vector& getSampleRates() const { return m_sampleRates; } - - virtual bool handleMessage(const Message& message); - - virtual int webapiRunGet( - SWGSDRangel::SWGDeviceState& response, - QString& errorMessage); - - virtual int webapiRun( - bool run, - SWGSDRangel::SWGDeviceState& response, - QString& errorMessage); - - static const qint64 loLowLimitFreqHF; - static const qint64 loHighLimitFreqHF; - static const qint64 loLowLimitFreqVHF; - static const qint64 loHighLimitFreqVHF; - -private: - bool openDevice(); - void closeDevice(); - bool applySettings(const AirspyHFISettings& settings, bool force); - airspyhf_device_t *open_airspyhf_from_serial(const QString& serialStr); - void setDeviceCenterFrequency(quint64 freq, const AirspyHFISettings& settings); - - DeviceSourceAPI *m_deviceAPI; - QMutex m_mutex; - AirspyHFISettings m_settings; - airspyhf_device_t* m_dev; - AirspyHFIThread* m_airspyHFThread; - QString m_deviceDescription; - std::vector m_sampleRates; - bool m_running; - FileRecord *m_fileSink; //!< File sink to record device I/Q output -}; - -#endif // INCLUDE_AIRSPYHFIINPUT_H diff --git a/plugins/samplesource/airspyhfi/airspyhfiplugin.cpp b/plugins/samplesource/airspyhfi/airspyhfiplugin.cpp deleted file mode 100644 index 2df26e08f..000000000 --- a/plugins/samplesource/airspyhfi/airspyhfiplugin.cpp +++ /dev/null @@ -1,127 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB // -// // -// This program is free software; you can redistribute it and/or modify // -// it under the terms of the GNU General Public License as published by // -// the Free Software Foundation as version 3 of the License, or // -// // -// This program is distributed in the hope that it will be useful, // -// but WITHOUT ANY WARRANTY; without even the implied warranty of // -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // -// GNU General Public License V3 for more details. // -// // -// You should have received a copy of the GNU General Public License // -// along with this program. If not, see . // -/////////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include - -#include -#include "plugin/pluginapi.h" -#include "util/simpleserializer.h" -#include "airspyhfiplugin.h" - -#include "airspyhfigui.h" - - -const PluginDescriptor AirspyHFIPlugin::m_pluginDescriptor = { - QString("AirspyHF Input (int)"), - QString("3.12.0"), - QString("(c) Edouard Griffiths, F4EXB"), - QString("https://github.com/f4exb/sdrangel"), - true, - QString("https://github.com/f4exb/sdrangel") -}; - -const QString AirspyHFIPlugin::m_hardwareID = "AirspyHFI"; -const QString AirspyHFIPlugin::m_deviceTypeID = AIRSPYHFI_DEVICE_TYPE_ID; -const int AirspyHFIPlugin::m_maxDevices = 32; - -AirspyHFIPlugin::AirspyHFIPlugin(QObject* parent) : - QObject(parent) -{ -} - -const PluginDescriptor& AirspyHFIPlugin::getPluginDescriptor() const -{ - return m_pluginDescriptor; -} - -void AirspyHFIPlugin::initPlugin(PluginAPI* pluginAPI) -{ - pluginAPI->registerSampleSource(m_deviceTypeID, this); -} - -PluginInterface::SamplingDevices AirspyHFIPlugin::enumSampleSources() -{ - SamplingDevices result; - int nbDevices; - uint64_t deviceSerials[m_maxDevices]; - - nbDevices = airspyhf_list_devices(deviceSerials, m_maxDevices); - - if (nbDevices < 0) - { - qCritical("AirspyHFIPlugin::enumSampleSources: failed to list Airspy HF devices"); - } - - for (int i = 0; i < nbDevices; i++) - { - if (deviceSerials[i]) - { - QString serial_str = QString::number(deviceSerials[i], 16); - QString displayedName(QString("AirspyHF(int)[%1] %2").arg(i).arg(serial_str)); - - result.append(SamplingDevice(displayedName, - m_hardwareID, - m_deviceTypeID, - serial_str, - i, - PluginInterface::SamplingDevice::PhysicalDevice, - true, - 1, - 0)); - - qDebug("AirspyHFIPlugin::enumSampleSources: enumerated Airspy device #%d", i); - } - else - { - qDebug("AirspyHFIPlugin::enumSampleSources: finished to enumerate Airspy HF. %d devices found", i); - break; // finished - } - } - - return result; -} - -PluginInstanceGUI* AirspyHFIPlugin::createSampleSourcePluginInstanceGUI( - const QString& sourceId, - QWidget **widget, - DeviceUISet *deviceUISet) -{ - if (sourceId == m_deviceTypeID) - { - AirspyHFIGui* gui = new AirspyHFIGui(deviceUISet); - *widget = gui; - return gui; - } - else - { - return 0; - } -} - -DeviceSampleSource *AirspyHFIPlugin::createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI) -{ - if (sourceId == m_deviceTypeID) - { - AirspyHFIInput* input = new AirspyHFIInput(deviceAPI); - return input; - } - else - { - return 0; - } -} diff --git a/plugins/samplesource/airspyhfi/airspyhfithread.cpp b/plugins/samplesource/airspyhfi/airspyhfithread.cpp deleted file mode 100644 index f3953c70f..000000000 --- a/plugins/samplesource/airspyhfi/airspyhfithread.cpp +++ /dev/null @@ -1,142 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB // -// // -// This program is free software; you can redistribute it and/or modify // -// it under the terms of the GNU General Public License as published by // -// the Free Software Foundation as version 3 of the License, or // -// // -// This program is distributed in the hope that it will be useful, // -// but WITHOUT ANY WARRANTY; without even the implied warranty of // -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // -// GNU General Public License V3 for more details. // -// // -// You should have received a copy of the GNU General Public License // -// along with this program. If not, see . // -/////////////////////////////////////////////////////////////////////////////////// - -#include -#include - -#include "dsp/samplesinkfifo.h" -#include "airspyhfithread.h" - -AirspyHFIThread *AirspyHFIThread::m_this = 0; - -AirspyHFIThread::AirspyHFIThread(airspyhf_device_t* dev, SampleSinkFifo* sampleFifo, QObject* parent) : - QThread(parent), - m_running(false), - m_dev(dev), - m_convertBuffer(AIRSPYHFI_BLOCKSIZE), - m_sampleFifo(sampleFifo), - m_samplerate(10), - m_log2Decim(0) -{ - m_this = this; -} - -AirspyHFIThread::~AirspyHFIThread() -{ - stopWork(); - m_this = 0; -} - -void AirspyHFIThread::startWork() -{ - m_startWaitMutex.lock(); - start(); - while(!m_running) - m_startWaiter.wait(&m_startWaitMutex, 100); - m_startWaitMutex.unlock(); -} - -void AirspyHFIThread::stopWork() -{ - qDebug("AirspyThread::stopWork"); - m_running = false; - wait(); -} - -void AirspyHFIThread::setSamplerate(uint32_t samplerate) -{ - m_samplerate = samplerate; -} - -void AirspyHFIThread::setLog2Decimation(unsigned int log2_decim) -{ - m_log2Decim = log2_decim; -} - -void AirspyHFIThread::run() -{ - airspyhf_error rc; - - m_running = true; - m_startWaiter.wakeAll(); - - rc = (airspyhf_error) airspyhf_start(m_dev, rx_callback, 0); - - if (rc != AIRSPYHF_SUCCESS) - { - qCritical("AirspyHFThread::run: failed to start Airspy Rx"); - } - else - { - while ((m_running) && (airspyhf_is_streaming(m_dev) != 0)) - { - sleep(1); - } - } - - rc = (airspyhf_error) airspyhf_stop(m_dev); - - if (rc == AIRSPYHF_SUCCESS) { - qDebug("AirspyHFThread::run: stopped Airspy Rx"); - } else { - qDebug("AirspyHFThread::run: failed to stop Airspy Rx"); - } - - m_running = false; -} - -// Decimate according to specified log2 (ex: log2=4 => decim=16) -void AirspyHFIThread::callback(const qint16* buf, qint32 len) -{ - SampleVector::iterator it = m_convertBuffer.begin(); - - switch (m_log2Decim) - { - case 0: - m_decimators.decimate1(&it, buf, len); - break; - case 1: - m_decimators.decimate2_cen(&it, buf, len); - break; - case 2: - m_decimators.decimate4_cen(&it, buf, len); - break; - case 3: - m_decimators.decimate8_cen(&it, buf, len); - break; - case 4: - m_decimators.decimate16_cen(&it, buf, len); - break; - case 5: - m_decimators.decimate32_cen(&it, buf, len); - break; - case 6: - m_decimators.decimate64_cen(&it, buf, len); - break; - default: - break; - } - - m_sampleFifo->write(m_convertBuffer.begin(), it); -} - - -int AirspyHFIThread::rx_callback(airspyhf_transfer_t* transfer) -{ - qint32 bytes_to_write = transfer->sample_count * sizeof(qint16); - m_this->callback((qint16 *) transfer->samples, bytes_to_write); - return 0; -} diff --git a/plugins/samplesource/airspyhfi/readme.md b/plugins/samplesource/airspyhfi/readme.md deleted file mode 100644 index 6af205a0d..000000000 --- a/plugins/samplesource/airspyhfi/readme.md +++ /dev/null @@ -1,105 +0,0 @@ -

AirspyHF input plugin

- -

Introduction

- -This input sample source plugin gets its samples from a [Airspy HF+ device](https://airspy.com/airspy-hf-plus/). - -

Build

- -The plugin will be built only if the [Airspy HF library](https://github.com/f4exb/airspyhf) is installed in your system. Please note that you should use my fork as it deals with integer samples. The branch to check out is `intsamples` but this is the default in Github. - -If you build it from source and install it in a custom location say: `/opt/install/libairspyhf` you will have to add `-DLIBRTLSDR_INCLUDE_DIR=/opt/install/libairspyhf/include -DLIBRTLSDR_LIBRARIES=/opt/install/libairspyhf/lib/libairspyhf.so` to the cmake command line. - -Note: if you use binary distributions this is included in the bundle. - -

Interface

- -It has very few controls compared to other source interfaces. This is because a lot of things are handled automatically with the AirspyHF+: - - - gains (hardware) - - DC and IQ correction (library software) - -![AirspyHF input plugin GUI](../../../doc/img/AirspyHFInput_plugin.png) - -

1: Common stream parameters

- -![SDR Daemon source input stream GUI](../../../doc/img/SDRdaemonSource_plugin_01.png) - -

1.1: Frequency

- -This is the center frequency of reception in kHz. - -

1.2: Start/Stop

- -Device start / stop button. - - - Blue triangle icon: device is ready and can be started - - Green square icon: device is running and can be stopped - - Magenta (or pink) square icon: an error occured. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again. - -

1.3: Record

- -Record baseband I/Q stream toggle button - -

1.4: Stream sample rate

- -Baseband I/Q sample rate in kS/s. This is the device to host sample rate (3) divided by the decimation factor (4). - -

2: Lo ppm correction

- -This is the correction factor in ppm applied to the local oscillator. The Airspy HF LO has 1 kHz increments so anything in between is obtained by mixing the signal with a Hz precision NCO. This is actually done in the AirspyHF library. - -On HF band the LO correction is not necessary because the LO is largely precise enough for the frequencies involved. You can disable the NCO in AirspyHF library by setting the value to zero. Since the LO control in SDRangel has a 1 kHz step the NCO correction will always be zero. In AirspyHF library (my fork) the NCO is not active (no extra complex multiplication) if the correction is zero. On HF band it is recommended not to use the LO correction (set it or leave it at 0). - -You can reset the ppm value anytime by pressing on button (3) - -

3: Reset LO ppm correction

- -THis resets the LO ppm correction (zero the value). By doing so the LO trimming NCO in AirspyHF libray is disabled. - -

4: Band select

- -Use this combo box to select the HF or VHF range. This will set the limits of the frequency dial (1.1) appropriately and possibly move the current frequency inside the limits. Limits are given by the AirspyHF+ specifications: - - - HF: 9 kHz to 31 MHz - - VHF: 60 to 260 MHz - -

5: Device to hast sample rate

- -This is the device to host sample rate in samples per second (S/s). - -Although the combo box is there to present a choice of sample rates at present the AirspyHF+ deals only with 768 kS/s. However the support library has provision to get a list of sample rates from the device incase of future developments. - -

6: Decimation factor

- -The I/Q stream from the AirspyHF to host is downsampled by a power of two before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8, 16, 32, 64. When using audio channel plugins (AM, DSD, NFM, SSB...) please make sure that the sample rate is not less than 48 kHz (no decimation by 32 or 64). - -

7: Transverter mode open dialog

- -This button opens a dialog to set the transverter mode frequency translation options: - -![SDR Daemon source input stream trasverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) - -Note that if you mouse over the button a tooltip appears that displays the translating frequency and if translation is enabled or disabled. When the frequency translation is enabled the button is lit. - -

7a.1: Translating frequency

- -You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. - -The frequency set in the device is the frequency on the main dial (1) minus this frequency. Thus it is positive for down converters and negative for up converters. - -For example with the DX Patrol that has a mixer at 120 MHz for HF operation you would set the value to -120,000,000 Hz so that if the main dial frequency is set at 7,130 kHz the RTLSDR of the DX Patrol will be set to 127.130 MHz. - -If you use a down converter to receive the 6 cm band narrowband center frequency of 5670 MHz at 432 MHz you would set the translating frequency to 5760 - 432 = 5328 MHz thus dial +5,328,000,000 Hz. - -For bands even higher in the frequency spectrum the GHz digits are not really significant so you can have them set at 1 GHz. Thus to receive the 10368 MHz frequency at 432 MHz you would set the translating frequency to 1368 - 432 = 936 MHz. Note that in this case the frequency of the LO used in the mixer of the transverter is set at 9936 MHz. - -The Hz precision allows a fine tuning of the transverter LO offset - -

7a.2: Translating frequency enable/disable

- -Use this toggle button to activate or deactivate the frequency translation - -

7a.3: Confirmation buttons

- -Use these buttons to confirm ("OK") or dismiss ("Cancel") your changes. diff --git a/plugins/samplesource/bladerf1input/CMakeLists.txt b/plugins/samplesource/bladerf1input/CMakeLists.txt new file mode 100644 index 000000000..7297d64a3 --- /dev/null +++ b/plugins/samplesource/bladerf1input/CMakeLists.txt @@ -0,0 +1,80 @@ +project(bladerf1input) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +set(bladerf1input_SOURCES + bladerf1inputgui.cpp + bladerf1input.cpp + bladerf1inputplugin.cpp + bladerf1inputsettings.cpp + bladerf1inputthread.cpp +) + +set(bladerf1input_HEADERS + bladerf1inputgui.h + bladerf1input.h + bladerf1inputplugin.h + bladerf1inputsettings.h + bladerf1inputthread.h +) + +set(bladerf1input_FORMS + bladerf1inputgui.ui +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERFLIBSRC}/include + ${LIBBLADERFLIBSRC}/src +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERF_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +#include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +#qt4_wrap_cpp(bladerf1input_HEADERS_MOC ${bladerf1input_HEADERS}) +qt5_wrap_ui(bladerf1input_FORMS_HEADERS ${bladerf1input_FORMS}) + +add_library(inputbladerf1 SHARED + ${bladerf1input_SOURCES} + ${bladerf1input_HEADERS_MOC} + ${bladerf1input_FORMS_HEADERS} +) + +if (BUILD_DEBIAN) +target_link_libraries(inputbladerf1 + ${QT_LIBRARIES} + bladerf + sdrbase + sdrgui + swagger + bladerf1device +) +else (BUILD_DEBIAN) +target_link_libraries(inputbladerf1 + ${QT_LIBRARIES} + ${LIBBLADERF_LIBRARIES} + sdrbase + sdrgui + swagger + bladerf1device +) +endif (BUILD_DEBIAN) + +target_link_libraries(inputbladerf1 Qt5::Core Qt5::Widgets) + +install(TARGETS inputbladerf1 DESTINATION lib/plugins/samplesource) diff --git a/plugins/samplesource/bladerfinput/bladerfinput.cpp b/plugins/samplesource/bladerf1input/bladerf1input.cpp similarity index 53% rename from plugins/samplesource/bladerfinput/bladerfinput.cpp rename to plugins/samplesource/bladerf1input/bladerf1input.cpp index 8a655c874..1791772c9 100644 --- a/plugins/samplesource/bladerfinput/bladerfinput.cpp +++ b/plugins/samplesource/bladerf1input/bladerf1input.cpp @@ -14,7 +14,7 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "bladerfinput.h" +#include "bladerf1input.h" #include #include @@ -30,14 +30,13 @@ #include "device/devicesourceapi.h" #include "device/devicesinkapi.h" -#include "bladerfinputgui.h" -#include "bladerfinputthread.h" +#include "bladerf1inputthread.h" -MESSAGE_CLASS_DEFINITION(BladerfInput::MsgConfigureBladerf, Message) -MESSAGE_CLASS_DEFINITION(BladerfInput::MsgStartStop, Message) -MESSAGE_CLASS_DEFINITION(BladerfInput::MsgFileRecord, Message) +MESSAGE_CLASS_DEFINITION(Bladerf1Input::MsgConfigureBladerf1, Message) +MESSAGE_CLASS_DEFINITION(Bladerf1Input::MsgStartStop, Message) +MESSAGE_CLASS_DEFINITION(Bladerf1Input::MsgFileRecord, Message) -BladerfInput::BladerfInput(DeviceSourceAPI *deviceAPI) : +Bladerf1Input::Bladerf1Input(DeviceSourceAPI *deviceAPI) : m_deviceAPI(deviceAPI), m_settings(), m_dev(0), @@ -46,16 +45,13 @@ BladerfInput::BladerfInput(DeviceSourceAPI *deviceAPI) : m_running(false) { openDevice(); - - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID()); - m_fileSink = new FileRecord(std::string(recFileNameCStr)); + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); m_deviceAPI->addSink(m_fileSink); m_deviceAPI->setBuddySharedPtr(&m_sharedParams); } -BladerfInput::~BladerfInput() +Bladerf1Input::~Bladerf1Input() { if (m_running) stop(); m_deviceAPI->removeSink(m_fileSink); @@ -64,12 +60,12 @@ BladerfInput::~BladerfInput() m_deviceAPI->setBuddySharedPtr(0); } -void BladerfInput::destroy() +void Bladerf1Input::destroy() { delete this; } -bool BladerfInput::openDevice() +bool Bladerf1Input::openDevice() { if (m_dev != 0) { @@ -87,7 +83,7 @@ bool BladerfInput::openDevice() if (m_deviceAPI->getSinkBuddies().size() > 0) { DeviceSinkAPI *sinkBuddy = m_deviceAPI->getSinkBuddies()[0]; - DeviceBladeRFParams *buddySharedParams = (DeviceBladeRFParams *) sinkBuddy->getBuddySharedPtr(); + DeviceBladeRF1Params *buddySharedParams = (DeviceBladeRF1Params *) sinkBuddy->getBuddySharedPtr(); if (buddySharedParams == 0) { @@ -106,7 +102,7 @@ bool BladerfInput::openDevice() } else { - if (!DeviceBladeRF::open_bladerf(&m_dev, qPrintable(m_deviceAPI->getSampleSourceSerial()))) + if (!DeviceBladeRF1::open_bladerf(&m_dev, qPrintable(m_deviceAPI->getSampleSourceSerial()))) { qCritical("BladerfInput::start: could not open BladeRF %s", qPrintable(m_deviceAPI->getSampleSourceSerial())); return false; @@ -116,7 +112,7 @@ bool BladerfInput::openDevice() } // TODO: adjust USB transfer data according to sample rate - if ((res = bladerf_sync_config(m_dev, BLADERF_MODULE_RX, BLADERF_FORMAT_SC16_Q11, 64, 8192, 32, 10000)) < 0) + if ((res = bladerf_sync_config(m_dev, BLADERF_RX_X1, BLADERF_FORMAT_SC16_Q11, 64, 8192, 32, 10000)) < 0) { qCritical("BladerfInput::start: bladerf_sync_config with return code %d", res); return false; @@ -131,27 +127,24 @@ bool BladerfInput::openDevice() return true; } -void BladerfInput::init() +void Bladerf1Input::init() { applySettings(m_settings, true); } -bool BladerfInput::start() +bool Bladerf1Input::start() { // QMutexLocker mutexLocker(&m_mutex); - if (!m_dev) { + if (!m_dev) + { + qDebug("BladerfInput::start: no device handle"); return false; } if (m_running) stop(); - if((m_bladerfThread = new BladerfInputThread(m_dev, &m_sampleFifo)) == 0) { - qCritical("BladerfInput::start: out of memory"); - stop(); - return false; - } - + m_bladerfThread = new Bladerf1InputThread(m_dev, &m_sampleFifo); m_bladerfThread->setLog2Decimation(m_settings.m_log2Decim); m_bladerfThread->setFcPos((int) m_settings.m_fcPos); @@ -166,7 +159,7 @@ bool BladerfInput::start() return true; } -void BladerfInput::closeDevice() +void Bladerf1Input::closeDevice() { int res; @@ -193,7 +186,7 @@ void BladerfInput::closeDevice() m_dev = 0; } -void BladerfInput::stop() +void Bladerf1Input::stop() { // QMutexLocker mutexLocker(&m_mutex); @@ -207,12 +200,12 @@ void BladerfInput::stop() m_running = false; } -QByteArray BladerfInput::serialize() const +QByteArray Bladerf1Input::serialize() const { return m_settings.serialize(); } -bool BladerfInput::deserialize(const QByteArray& data) +bool Bladerf1Input::deserialize(const QByteArray& data) { bool success = true; @@ -222,55 +215,55 @@ bool BladerfInput::deserialize(const QByteArray& data) success = false; } - MsgConfigureBladerf* message = MsgConfigureBladerf::create(m_settings, true); + MsgConfigureBladerf1* message = MsgConfigureBladerf1::create(m_settings, true); m_inputMessageQueue.push(message); if (m_guiMessageQueue) { - MsgConfigureBladerf* messageToGUI = MsgConfigureBladerf::create(m_settings, true); + MsgConfigureBladerf1* messageToGUI = MsgConfigureBladerf1::create(m_settings, true); m_guiMessageQueue->push(messageToGUI); } return success; } -const QString& BladerfInput::getDeviceDescription() const +const QString& Bladerf1Input::getDeviceDescription() const { return m_deviceDescription; } -int BladerfInput::getSampleRate() const +int Bladerf1Input::getSampleRate() const { int rate = m_settings.m_devSampleRate; return (rate / (1<push(messageToGUI); } } -bool BladerfInput::handleMessage(const Message& message) +bool Bladerf1Input::handleMessage(const Message& message) { - if (MsgConfigureBladerf::match(message)) + if (MsgConfigureBladerf1::match(message)) { - MsgConfigureBladerf& conf = (MsgConfigureBladerf&) message; - qDebug() << "BladerfInput::handleMessage: MsgConfigureBladerf"; + MsgConfigureBladerf1& conf = (MsgConfigureBladerf1&) message; + qDebug() << "Bladerf1Input::handleMessage: MsgConfigureBladerf1"; if (!applySettings(conf.getSettings(), conf.getForce())) { @@ -284,9 +277,18 @@ bool BladerfInput::handleMessage(const Message& message) MsgFileRecord& conf = (MsgFileRecord&) message; qDebug() << "BladerfInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); - if (conf.getStartStop()) { + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSink->setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); + } + m_fileSink->startRecording(); - } else { + } + else + { m_fileSink->stopRecording(); } @@ -302,13 +304,11 @@ bool BladerfInput::handleMessage(const Message& message) if (m_deviceAPI->initAcquisition()) { m_deviceAPI->startAcquisition(); - DSPEngine::instance()->startAudioOutput(); } } else { m_deviceAPI->stopAcquisition(); - DSPEngine::instance()->stopAudioOutput(); } return true; @@ -319,7 +319,7 @@ bool BladerfInput::handleMessage(const Message& message) } } -bool BladerfInput::applySettings(const BladeRFInputSettings& settings, bool force) +bool Bladerf1Input::applySettings(const BladeRF1InputSettings& settings, bool force) { bool forwardChange = false; // QMutexLocker mutexLocker(&m_mutex); @@ -329,65 +329,65 @@ bool BladerfInput::applySettings(const BladeRFInputSettings& settings, bool forc if ((m_settings.m_dcBlock != settings.m_dcBlock) || (m_settings.m_iqCorrection != settings.m_iqCorrection) || force) { - m_settings.m_dcBlock = settings.m_dcBlock; - m_settings.m_iqCorrection = settings.m_iqCorrection; - m_deviceAPI->configureCorrections(m_settings.m_dcBlock, m_settings.m_iqCorrection); +// m_settings.m_dcBlock = settings.m_dcBlock; +// m_settings.m_iqCorrection = settings.m_iqCorrection; + m_deviceAPI->configureCorrections(settings.m_dcBlock, settings.m_iqCorrection); } if ((m_settings.m_lnaGain != settings.m_lnaGain) || force) { - m_settings.m_lnaGain = settings.m_lnaGain; +// m_settings.m_lnaGain = settings.m_lnaGain; if (m_dev != 0) { - if(bladerf_set_lna_gain(m_dev, getLnaGain(m_settings.m_lnaGain)) != 0) + if(bladerf_set_lna_gain(m_dev, getLnaGain(settings.m_lnaGain)) != 0) { qDebug("BladerfInput::applySettings: bladerf_set_lna_gain() failed"); } else { - qDebug() << "BladerfInput::applySettings: LNA gain set to " << getLnaGain(m_settings.m_lnaGain); + qDebug() << "BladerfInput::applySettings: LNA gain set to " << getLnaGain(settings.m_lnaGain); } } } if ((m_settings.m_vga1 != settings.m_vga1) || force) { - m_settings.m_vga1 = settings.m_vga1; +// m_settings.m_vga1 = settings.m_vga1; if (m_dev != 0) { - if(bladerf_set_rxvga1(m_dev, m_settings.m_vga1) != 0) + if(bladerf_set_rxvga1(m_dev, settings.m_vga1) != 0) { qDebug("BladerfInput::applySettings: bladerf_set_rxvga1() failed"); } else { - qDebug() << "BladerfInput::applySettings: VGA1 gain set to " << m_settings.m_vga1; + qDebug() << "BladerfInput::applySettings: VGA1 gain set to " << settings.m_vga1; } } } if ((m_settings.m_vga2 != settings.m_vga2) || force) { - m_settings.m_vga2 = settings.m_vga2; +// m_settings.m_vga2 = settings.m_vga2; if(m_dev != 0) { - if(bladerf_set_rxvga2(m_dev, m_settings.m_vga2) != 0) + if(bladerf_set_rxvga2(m_dev, settings.m_vga2) != 0) { qDebug("BladerfInput::applySettings: bladerf_set_rxvga2() failed"); } else { - qDebug() << "BladerfInput::applySettings: VGA2 gain set to " << m_settings.m_vga2; + qDebug() << "BladerfInput::applySettings: VGA2 gain set to " << settings.m_vga2; } } } if ((m_settings.m_xb200 != settings.m_xb200) || force) { - m_settings.m_xb200 = settings.m_xb200; +// m_settings.m_xb200 = settings.m_xb200; if (m_dev != 0) { @@ -413,7 +413,7 @@ bool BladerfInput::applySettings(const BladeRFInputSettings& settings, bool forc if (changeSettings) { - if (m_settings.m_xb200) + if (settings.m_xb200) { if (bladerf_expansion_attach(m_dev, BLADERF_XB_200) != 0) { @@ -436,57 +436,57 @@ bool BladerfInput::applySettings(const BladeRFInputSettings& settings, bool forc } } - m_sharedParams.m_xb200Attached = m_settings.m_xb200; + m_sharedParams.m_xb200Attached = settings.m_xb200; } } } if ((m_settings.m_xb200Path != settings.m_xb200Path) || force) { - m_settings.m_xb200Path = settings.m_xb200Path; +// m_settings.m_xb200Path = settings.m_xb200Path; if (m_dev != 0) { - if(bladerf_xb200_set_path(m_dev, BLADERF_MODULE_RX, m_settings.m_xb200Path) != 0) + if(bladerf_xb200_set_path(m_dev, BLADERF_MODULE_RX, settings.m_xb200Path) != 0) { qDebug("BladerfInput::applySettings: bladerf_xb200_set_path(BLADERF_MODULE_RX) failed"); } else { - qDebug() << "BladerfInput::applySettings: set xb200 path to " << m_settings.m_xb200Path; + qDebug() << "BladerfInput::applySettings: set xb200 path to " << settings.m_xb200Path; } } } if ((m_settings.m_xb200Filter != settings.m_xb200Filter) || force) { - m_settings.m_xb200Filter = settings.m_xb200Filter; +// m_settings.m_xb200Filter = settings.m_xb200Filter; if (m_dev != 0) { - if(bladerf_xb200_set_filterbank(m_dev, BLADERF_MODULE_RX, m_settings.m_xb200Filter) != 0) + if(bladerf_xb200_set_filterbank(m_dev, BLADERF_MODULE_RX, settings.m_xb200Filter) != 0) { qDebug("BladerfInput::applySettings: bladerf_xb200_set_filterbank(BLADERF_MODULE_RX) failed"); } else { - qDebug() << "BladerfInput::applySettings: set xb200 filter to " << m_settings.m_xb200Filter; + qDebug() << "BladerfInput::applySettings: set xb200 filter to " << settings.m_xb200Filter; } } } if ((m_settings.m_devSampleRate != settings.m_devSampleRate) || force) { - m_settings.m_devSampleRate = settings.m_devSampleRate; +// m_settings.m_devSampleRate = settings.m_devSampleRate; forwardChange = true; if (m_dev != 0) { unsigned int actualSamplerate; - if (bladerf_set_sample_rate(m_dev, BLADERF_MODULE_RX, m_settings.m_devSampleRate, &actualSamplerate) < 0) + if (bladerf_set_sample_rate(m_dev, BLADERF_MODULE_RX, settings.m_devSampleRate, &actualSamplerate) < 0) { - qCritical("BladerfInput::applySettings: could not set sample rate: %d", m_settings.m_devSampleRate); + qCritical("BladerfInput::applySettings: could not set sample rate: %d", settings.m_devSampleRate); } else { @@ -497,15 +497,15 @@ bool BladerfInput::applySettings(const BladeRFInputSettings& settings, bool forc if ((m_settings.m_bandwidth != settings.m_bandwidth) || force) { - m_settings.m_bandwidth = settings.m_bandwidth; +// m_settings.m_bandwidth = settings.m_bandwidth; if(m_dev != 0) { unsigned int actualBandwidth; - if( bladerf_set_bandwidth(m_dev, BLADERF_MODULE_RX, m_settings.m_bandwidth, &actualBandwidth) < 0) + if( bladerf_set_bandwidth(m_dev, BLADERF_MODULE_RX, settings.m_bandwidth, &actualBandwidth) < 0) { - qCritical("BladerfInput::applySettings: could not set bandwidth: %d", m_settings.m_bandwidth); + qCritical("BladerfInput::applySettings: could not set bandwidth: %d", settings.m_bandwidth); } else { @@ -525,82 +525,73 @@ bool BladerfInput::applySettings(const BladeRFInputSettings& settings, bool forc if ((m_settings.m_log2Decim != settings.m_log2Decim) || force) { - m_settings.m_log2Decim = settings.m_log2Decim; +// m_settings.m_log2Decim = settings.m_log2Decim; forwardChange = true; if (m_bladerfThread != 0) { - m_bladerfThread->setLog2Decimation(m_settings.m_log2Decim); - qDebug() << "BladerfInput::applySettings: set decimation to " << (1<setLog2Decimation(settings.m_log2Decim); + qDebug() << "BladerfInput::applySettings: set decimation to " << (1<handleMessage(*notif); // forward to file sink m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif); } + m_settings = settings; + + qDebug() << "BladerfInput::applySettings: " + << " m_centerFrequency: " << m_settings.m_centerFrequency << " Hz" + << " m_bandwidth: " << m_settings.m_bandwidth + << " m_lnaGain: " << m_settings.m_lnaGain + << " m_vga1: " << m_settings.m_vga1 + << " m_vga2: " << m_settings.m_vga2 + << " m_log2Decim: " << m_settings.m_log2Decim + << " m_fcPos: " << m_settings.m_fcPos + << " m_devSampleRate: " << m_settings.m_devSampleRate + << " m_dcBlock: " << m_settings.m_dcBlock + << " m_iqCorrection: " << m_settings.m_iqCorrection + << " m_xb200Filter: " << m_settings.m_xb200Filter + << " m_xb200Path: " << m_settings.m_xb200Path + << " m_xb200: " << m_settings.m_xb200; + return true; } -bladerf_lna_gain BladerfInput::getLnaGain(int lnaGain) +bladerf_lna_gain Bladerf1Input::getLnaGain(int lnaGain) { if (lnaGain == 2) { @@ -616,7 +607,104 @@ bladerf_lna_gain BladerfInput::getLnaGain(int lnaGain) } } -int BladerfInput::webapiRunGet( +int Bladerf1Input::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setBladeRf1InputSettings(new SWGSDRangel::SWGBladeRF1InputSettings()); + response.getBladeRf1InputSettings()->init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +void Bladerf1Input::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const BladeRF1InputSettings& settings) +{ + response.getBladeRf1InputSettings()->setCenterFrequency(settings.m_centerFrequency); + response.getBladeRf1InputSettings()->setDevSampleRate(settings.m_devSampleRate); + response.getBladeRf1InputSettings()->setLnaGain(settings.m_lnaGain); + response.getBladeRf1InputSettings()->setVga1(settings.m_vga1); + response.getBladeRf1InputSettings()->setVga2(settings.m_vga2); + response.getBladeRf1InputSettings()->setBandwidth(settings.m_bandwidth); + response.getBladeRf1InputSettings()->setLog2Decim(settings.m_log2Decim); + response.getBladeRf1InputSettings()->setFcPos((int) settings.m_fcPos); + response.getBladeRf1InputSettings()->setXb200(settings.m_xb200 ? 1 : 0); + response.getBladeRf1InputSettings()->setXb200Path((int) settings.m_xb200Path); + response.getBladeRf1InputSettings()->setXb200Filter((int) settings.m_xb200Filter); + response.getBladeRf1InputSettings()->setDcBlock(settings.m_dcBlock ? 1 : 0); + response.getBladeRf1InputSettings()->setIqCorrection(settings.m_iqCorrection ? 1 : 0); + + if (response.getBladeRf1InputSettings()->getFileRecordName()) { + *response.getBladeRf1InputSettings()->getFileRecordName() = settings.m_fileRecordName; + } else { + response.getBladeRf1InputSettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + } +} + +int Bladerf1Input::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage __attribute__((unused))) +{ + BladeRF1InputSettings settings = m_settings; + + if (deviceSettingsKeys.contains("centerFrequency")) { + settings.m_centerFrequency = response.getBladeRf1InputSettings()->getCenterFrequency(); + } + if (deviceSettingsKeys.contains("devSampleRate")) { + settings.m_devSampleRate = response.getBladeRf1InputSettings()->getDevSampleRate(); + } + if (deviceSettingsKeys.contains("lnaGain")) { + settings.m_lnaGain = response.getBladeRf1InputSettings()->getLnaGain(); + } + if (deviceSettingsKeys.contains("vga1")) { + settings.m_vga1 = response.getBladeRf1InputSettings()->getVga1(); + } + if (deviceSettingsKeys.contains("vga2")) { + settings.m_vga2 = response.getBladeRf1InputSettings()->getVga2(); + } + if (deviceSettingsKeys.contains("bandwidth")) { + settings.m_bandwidth = response.getBladeRf1InputSettings()->getBandwidth(); + } + if (deviceSettingsKeys.contains("log2Decim")) { + settings.m_log2Decim = response.getBladeRf1InputSettings()->getLog2Decim(); + } + if (deviceSettingsKeys.contains("fcPos")) { + settings.m_fcPos = static_cast(response.getBladeRf1InputSettings()->getFcPos()); + } + if (deviceSettingsKeys.contains("xb200")) { + settings.m_xb200 = response.getBladeRf1InputSettings()->getXb200() == 0 ? 0 : 1; + } + if (deviceSettingsKeys.contains("xb200Path")) { + settings.m_xb200Path = static_cast(response.getBladeRf1InputSettings()->getXb200Path()); + } + if (deviceSettingsKeys.contains("xb200Filter")) { + settings.m_xb200Filter = static_cast(response.getBladeRf1InputSettings()->getXb200Filter()); + } + if (deviceSettingsKeys.contains("dcBlock")) { + settings.m_dcBlock = response.getBladeRf1InputSettings()->getDcBlock() != 0; + } + if (deviceSettingsKeys.contains("iqCorrection")) { + settings.m_iqCorrection = response.getBladeRf1InputSettings()->getIqCorrection() != 0; + } + if (deviceSettingsKeys.contains("fileRecordName")) { + settings.m_fileRecordName = *response.getBladeRf1InputSettings()->getFileRecordName(); + } + + MsgConfigureBladerf1 *msg = MsgConfigureBladerf1::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureBladerf1 *msgToGUI = MsgConfigureBladerf1::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +int Bladerf1Input::webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage __attribute__((unused))) { @@ -624,7 +712,7 @@ int BladerfInput::webapiRunGet( return 200; } -int BladerfInput::webapiRun( +int Bladerf1Input::webapiRun( bool run, SWGSDRangel::SWGDeviceState& response, QString& errorMessage __attribute__((unused))) @@ -641,42 +729,3 @@ int BladerfInput::webapiRun( return 200; } - -//struct bladerf *BladerfInput::open_bladerf_from_serial(const char *serial) -//{ -// int status; -// struct bladerf *dev; -// struct bladerf_devinfo info; -// -// /* Initialize all fields to "don't care" wildcard values. -// * -// * Immediately passing this to bladerf_open_with_devinfo() would cause -// * libbladeRF to open any device on any available backend. */ -// bladerf_init_devinfo(&info); -// -// /* Specify the desired device's serial number, while leaving all other -// * fields in the info structure wildcard values */ -// if (serial != NULL) -// { -// strncpy(info.serial, serial, BLADERF_SERIAL_LENGTH - 1); -// info.serial[BLADERF_SERIAL_LENGTH - 1] = '\0'; -// } -// -// status = bladerf_open_with_devinfo(&dev, &info); -// -// if (status == BLADERF_ERR_NODEV) -// { -// fprintf(stderr, "No devices available with serial=%s\n", serial); -// return NULL; -// } -// else if (status != 0) -// { -// fprintf(stderr, "Failed to open device with serial=%s (%s)\n", -// serial, bladerf_strerror(status)); -// return NULL; -// } -// else -// { -// return dev; -// } -//} diff --git a/plugins/samplesource/bladerfinput/bladerfinput.h b/plugins/samplesource/bladerf1input/bladerf1input.h similarity index 72% rename from plugins/samplesource/bladerfinput/bladerfinput.h rename to plugins/samplesource/bladerf1input/bladerf1input.h index 1498623eb..19b8feefb 100644 --- a/plugins/samplesource/bladerfinput/bladerfinput.h +++ b/plugins/samplesource/bladerf1input/bladerf1input.h @@ -22,33 +22,34 @@ #include #include -#include "bladerf/devicebladerf.h" -#include "bladerf/devicebladerfparam.h" -#include "bladerfinputsettings.h" + +#include "../../../devices/bladerf1/devicebladerf1.h" +#include "../../../devices/bladerf1/devicebladerf1param.h" +#include "bladerf1inputsettings.h" class DeviceSourceAPI; -class BladerfInputThread; +class Bladerf1InputThread; class FileRecord; -class BladerfInput : public DeviceSampleSource { +class Bladerf1Input : public DeviceSampleSource { public: - class MsgConfigureBladerf : public Message { + class MsgConfigureBladerf1 : public Message { MESSAGE_CLASS_DECLARATION public: - const BladeRFInputSettings& getSettings() const { return m_settings; } + const BladeRF1InputSettings& getSettings() const { return m_settings; } bool getForce() const { return m_force; } - static MsgConfigureBladerf* create(const BladeRFInputSettings& settings, bool force) + static MsgConfigureBladerf1* create(const BladeRF1InputSettings& settings, bool force) { - return new MsgConfigureBladerf(settings, force); + return new MsgConfigureBladerf1(settings, force); } private: - BladeRFInputSettings m_settings; + BladeRF1InputSettings m_settings; bool m_force; - MsgConfigureBladerf(const BladeRFInputSettings& settings, bool force) : + MsgConfigureBladerf1(const BladeRF1InputSettings& settings, bool force) : Message(), m_settings(settings), m_force(force) @@ -93,8 +94,8 @@ public: { } }; - BladerfInput(DeviceSourceAPI *deviceAPI); - virtual ~BladerfInput(); + Bladerf1Input(DeviceSourceAPI *deviceAPI); + virtual ~Bladerf1Input(); virtual void destroy(); virtual void init(); @@ -112,6 +113,16 @@ public: virtual bool handleMessage(const Message& message); + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + virtual int webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage); @@ -124,17 +135,17 @@ public: private: bool openDevice(); void closeDevice(); - bool applySettings(const BladeRFInputSettings& settings, bool force); + bool applySettings(const BladeRF1InputSettings& settings, bool force); bladerf_lna_gain getLnaGain(int lnaGain); -// struct bladerf *open_bladerf_from_serial(const char *serial); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const BladeRF1InputSettings& settings); DeviceSourceAPI *m_deviceAPI; QMutex m_mutex; - BladeRFInputSettings m_settings; + BladeRF1InputSettings m_settings; struct bladerf* m_dev; - BladerfInputThread* m_bladerfThread; + Bladerf1InputThread* m_bladerfThread; QString m_deviceDescription; - DeviceBladeRFParams m_sharedParams; + DeviceBladeRF1Params m_sharedParams; bool m_running; FileRecord *m_fileSink; //!< File sink to record device I/Q output }; diff --git a/plugins/samplesource/bladerf1input/bladerf1input.pro b/plugins/samplesource/bladerf1input/bladerf1input.pro new file mode 100644 index 000000000..035afb49f --- /dev/null +++ b/plugins/samplesource/bladerf1input/bladerf1input.pro @@ -0,0 +1,53 @@ +#-------------------------------------------- +# +# Pro file for Windows builds with Qt Creator +# +#-------------------------------------------- + +TEMPLATE = lib +CONFIG += plugin + +QT += core gui widgets multimedia opengl + +TARGET = inputbladerf1 + +DEFINES += USE_SSE2=1 +QMAKE_CXXFLAGS += -msse2 +DEFINES += USE_SSE4_1=1 +QMAKE_CXXFLAGS += -msse4.1 +QMAKE_CXXFLAGS += -std=c++11 + +CONFIG(MINGW32):LIBBLADERF = "C:\Programs\bladeRF" +CONFIG(MINGW64):LIBBLADERF = "C:\Programs\bladeRF" +INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports +INCLUDEPATH += ../../../sdrbase +INCLUDEPATH += ../../../sdrgui +INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client +INCLUDEPATH += ../../../devices +INCLUDEPATH += $$LIBBLADERF/include + +CONFIG(Release):build_subdir = release +CONFIG(Debug):build_subdir = debug + +SOURCES += bladerf1inputgui.cpp\ + bladerf1input.cpp\ + bladerf1inputplugin.cpp\ + bladerf1inputsettings.cpp\ + bladerf1inputthread.cpp + +HEADERS += bladerf1inputgui.h\ + bladerf1input.h\ + bladerf1inputplugin.h\ + bladerf1inputsettings.h\ + bladerf1inputthread.h + +FORMS += bladerf1inputgui.ui + +LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase +LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui +LIBS += -L../../../swagger/$${build_subdir} -lswagger +LIBS += -L$$LIBBLADERF/lib -lbladeRF +LIBS += -L../../../devices/$${build_subdir} -ldevices + +RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/plugins/samplesource/bladerfinput/bladerfinputgui.cpp b/plugins/samplesource/bladerf1input/bladerf1inputgui.cpp similarity index 77% rename from plugins/samplesource/bladerfinput/bladerfinputgui.cpp rename to plugins/samplesource/bladerf1input/bladerf1inputgui.cpp index 1ee1a2d54..3865fedce 100644 --- a/plugins/samplesource/bladerfinput/bladerfinputgui.cpp +++ b/plugins/samplesource/bladerf1input/bladerf1inputgui.cpp @@ -14,33 +14,33 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "../bladerfinput/bladerfinputgui.h" - #include #include #include -#include "ui_bladerfinputgui.h" +#include "ui_bladerf1inputgui.h" #include "gui/colormapper.h" #include "gui/glspectrum.h" #include "dsp/dspengine.h" #include "dsp/dspcommands.h" -#include +#include "device/devicesourceapi.h" #include "device/deviceuiset.h" -BladerfInputGui::BladerfInputGui(DeviceUISet *deviceUISet, QWidget* parent) : +#include "bladerf1inputgui.h" + +Bladerf1InputGui::Bladerf1InputGui(DeviceUISet *deviceUISet, QWidget* parent) : QWidget(parent), - ui(new Ui::BladerfInputGui), + ui(new Ui::Bladerf1InputGui), m_deviceUISet(deviceUISet), m_forceSettings(true), m_doApplySettings(true), m_settings(), m_sampleSource(NULL), m_sampleRate(0), - m_lastEngineState((DSPDeviceSourceEngine::State)-1) + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted) { - m_sampleSource = (BladerfInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); + m_sampleSource = (Bladerf1Input*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); ui->setupUi(this); ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); @@ -63,55 +63,56 @@ BladerfInputGui::BladerfInputGui(DeviceUISet *deviceUISet, QWidget* parent) : displaySettings(); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); sendSettings(); } -BladerfInputGui::~BladerfInputGui() +Bladerf1InputGui::~Bladerf1InputGui() { delete ui; } -void BladerfInputGui::destroy() +void Bladerf1InputGui::destroy() { delete this; } -void BladerfInputGui::setName(const QString& name) +void Bladerf1InputGui::setName(const QString& name) { setObjectName(name); } -QString BladerfInputGui::getName() const +QString Bladerf1InputGui::getName() const { return objectName(); } -void BladerfInputGui::resetToDefaults() +void Bladerf1InputGui::resetToDefaults() { m_settings.resetToDefaults(); displaySettings(); sendSettings(); } -qint64 BladerfInputGui::getCenterFrequency() const +qint64 Bladerf1InputGui::getCenterFrequency() const { return m_settings.m_centerFrequency; } -void BladerfInputGui::setCenterFrequency(qint64 centerFrequency) +void Bladerf1InputGui::setCenterFrequency(qint64 centerFrequency) { m_settings.m_centerFrequency = centerFrequency; displaySettings(); sendSettings(); } -QByteArray BladerfInputGui::serialize() const +QByteArray Bladerf1InputGui::serialize() const { return m_settings.serialize(); } -bool BladerfInputGui::deserialize(const QByteArray& data) +bool Bladerf1InputGui::deserialize(const QByteArray& data) { if(m_settings.deserialize(data)) { displaySettings(); @@ -124,20 +125,20 @@ bool BladerfInputGui::deserialize(const QByteArray& data) } } -bool BladerfInputGui::handleMessage(const Message& message) +bool Bladerf1InputGui::handleMessage(const Message& message) { - if (BladerfInput::MsgConfigureBladerf::match(message)) + if (Bladerf1Input::MsgConfigureBladerf1::match(message)) { - const BladerfInput::MsgConfigureBladerf& cfg = (BladerfInput::MsgConfigureBladerf&) message; + const Bladerf1Input::MsgConfigureBladerf1& cfg = (Bladerf1Input::MsgConfigureBladerf1&) message; m_settings = cfg.getSettings(); blockApplySettings(true); displaySettings(); blockApplySettings(false); return true; } - else if (BladerfInput::MsgStartStop::match(message)) + else if (Bladerf1Input::MsgStartStop::match(message)) { - BladerfInput::MsgStartStop& notif = (BladerfInput::MsgStartStop&) message; + Bladerf1Input::MsgStartStop& notif = (Bladerf1Input::MsgStartStop&) message; blockApplySettings(true); ui->startStop->setChecked(notif.getStartStop()); blockApplySettings(false); @@ -150,7 +151,7 @@ bool BladerfInputGui::handleMessage(const Message& message) } } -void BladerfInputGui::handleInputMessages() +void Bladerf1InputGui::handleInputMessages() { Message* message; @@ -178,14 +179,14 @@ void BladerfInputGui::handleInputMessages() } } -void BladerfInputGui::updateSampleRateAndFrequency() +void Bladerf1InputGui::updateSampleRateAndFrequency() { m_deviceUISet->getSpectrum()->setSampleRate(m_sampleRate); m_deviceUISet->getSpectrum()->setCenterFrequency(m_deviceCenterFrequency); ui->deviceRateLabel->setText(tr("%1k").arg(QString::number(m_sampleRate / 1000.0f, 'g', 5))); } -void BladerfInputGui::displaySettings() +void Bladerf1InputGui::displaySettings() { blockApplySettings(true); @@ -215,66 +216,66 @@ void BladerfInputGui::displaySettings() blockApplySettings(false); } -void BladerfInputGui::sendSettings() +void Bladerf1InputGui::sendSettings() { if(!m_updateTimer.isActive()) m_updateTimer.start(100); } -void BladerfInputGui::on_centerFrequency_changed(quint64 value) +void Bladerf1InputGui::on_centerFrequency_changed(quint64 value) { m_settings.m_centerFrequency = value * 1000; sendSettings(); } -void BladerfInputGui::on_sampleRate_changed(quint64 value) +void Bladerf1InputGui::on_sampleRate_changed(quint64 value) { m_settings.m_devSampleRate = value; sendSettings(); } -void BladerfInputGui::on_dcOffset_toggled(bool checked) +void Bladerf1InputGui::on_dcOffset_toggled(bool checked) { m_settings.m_dcBlock = checked; sendSettings(); } -void BladerfInputGui::on_iqImbalance_toggled(bool checked) +void Bladerf1InputGui::on_iqImbalance_toggled(bool checked) { m_settings.m_iqCorrection = checked; sendSettings(); } -void BladerfInputGui::on_bandwidth_currentIndexChanged(int index) +void Bladerf1InputGui::on_bandwidth_currentIndexChanged(int index) { int newbw = BladerfBandwidths::getBandwidth(index); m_settings.m_bandwidth = newbw * 1000; sendSettings(); } -void BladerfInputGui::on_decim_currentIndexChanged(int index) +void Bladerf1InputGui::on_decim_currentIndexChanged(int index) { - if ((index <0) || (index > 5)) + if ((index <0) || (index > 6)) return; m_settings.m_log2Decim = index; sendSettings(); } -void BladerfInputGui::on_fcPos_currentIndexChanged(int index) +void Bladerf1InputGui::on_fcPos_currentIndexChanged(int index) { if (index == 0) { - m_settings.m_fcPos = BladeRFInputSettings::FC_POS_INFRA; + m_settings.m_fcPos = BladeRF1InputSettings::FC_POS_INFRA; sendSettings(); } else if (index == 1) { - m_settings.m_fcPos = BladeRFInputSettings::FC_POS_SUPRA; + m_settings.m_fcPos = BladeRF1InputSettings::FC_POS_SUPRA; sendSettings(); } else if (index == 2) { - m_settings.m_fcPos = BladeRFInputSettings::FC_POS_CENTER; + m_settings.m_fcPos = BladeRF1InputSettings::FC_POS_CENTER; sendSettings(); } } -void BladerfInputGui::on_lna_currentIndexChanged(int index) +void Bladerf1InputGui::on_lna_currentIndexChanged(int index) { qDebug() << "BladerfGui: LNA gain = " << index * 3 << " dB"; @@ -285,7 +286,7 @@ void BladerfInputGui::on_lna_currentIndexChanged(int index) sendSettings(); } -void BladerfInputGui::on_vga1_valueChanged(int value) +void Bladerf1InputGui::on_vga1_valueChanged(int value) { if ((value < BLADERF_RXVGA1_GAIN_MIN) || (value > BLADERF_RXVGA1_GAIN_MAX)) return; @@ -295,7 +296,7 @@ void BladerfInputGui::on_vga1_valueChanged(int value) sendSettings(); } -void BladerfInputGui::on_vga2_valueChanged(int value) +void Bladerf1InputGui::on_vga2_valueChanged(int value) { if ((value < BLADERF_RXVGA2_GAIN_MIN) || (value > BLADERF_RXVGA2_GAIN_MAX)) return; @@ -305,7 +306,7 @@ void BladerfInputGui::on_vga2_valueChanged(int value) sendSettings(); } -void BladerfInputGui::on_xb200_currentIndexChanged(int index) +void Bladerf1InputGui::on_xb200_currentIndexChanged(int index) { if (index == 1) // bypass { @@ -365,16 +366,16 @@ void BladerfInputGui::on_xb200_currentIndexChanged(int index) sendSettings(); } -void BladerfInputGui::on_startStop_toggled(bool checked) +void Bladerf1InputGui::on_startStop_toggled(bool checked) { if (m_doApplySettings) { - BladerfInput::MsgStartStop *message = BladerfInput::MsgStartStop::create(checked); + Bladerf1Input::MsgStartStop *message = Bladerf1Input::MsgStartStop::create(checked); m_sampleSource->getInputMessageQueue()->push(message); } } -void BladerfInputGui::on_record_toggled(bool checked) +void Bladerf1InputGui::on_record_toggled(bool checked) { if (checked) { ui->record->setStyleSheet("QToolButton { background-color : red; }"); @@ -382,28 +383,28 @@ void BladerfInputGui::on_record_toggled(bool checked) ui->record->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); } - BladerfInput::MsgFileRecord* message = BladerfInput::MsgFileRecord::create(checked); + Bladerf1Input::MsgFileRecord* message = Bladerf1Input::MsgFileRecord::create(checked); m_sampleSource->getInputMessageQueue()->push(message); } -void BladerfInputGui::updateHardware() +void Bladerf1InputGui::updateHardware() { if (m_doApplySettings) { qDebug() << "BladerfGui::updateHardware"; - BladerfInput::MsgConfigureBladerf* message = BladerfInput::MsgConfigureBladerf::create(m_settings, m_forceSettings); + Bladerf1Input::MsgConfigureBladerf1* message = Bladerf1Input::MsgConfigureBladerf1::create(m_settings, m_forceSettings); m_sampleSource->getInputMessageQueue()->push(message); m_forceSettings = false; m_updateTimer.stop(); } } -void BladerfInputGui::blockApplySettings(bool block) +void Bladerf1InputGui::blockApplySettings(bool block) { m_doApplySettings = !block; } -void BladerfInputGui::updateStatus() +void Bladerf1InputGui::updateStatus() { int state = m_deviceUISet->m_deviceSourceAPI->state(); @@ -432,7 +433,7 @@ void BladerfInputGui::updateStatus() } } -unsigned int BladerfInputGui::getXb200Index(bool xb_200, bladerf_xb200_path xb200Path, bladerf_xb200_filter xb200Filter) +unsigned int Bladerf1InputGui::getXb200Index(bool xb_200, bladerf_xb200_path xb200Path, bladerf_xb200_filter xb200Filter) { if (xb_200) { diff --git a/plugins/samplesource/bladerfinput/bladerfinputgui.h b/plugins/samplesource/bladerf1input/bladerf1inputgui.h similarity index 91% rename from plugins/samplesource/bladerfinput/bladerfinputgui.h rename to plugins/samplesource/bladerf1input/bladerf1inputgui.h index cc7ca4da4..c27a8f8cf 100644 --- a/plugins/samplesource/bladerfinput/bladerfinputgui.h +++ b/plugins/samplesource/bladerf1input/bladerf1inputgui.h @@ -23,20 +23,20 @@ #include "util/messagequeue.h" -#include "bladerfinput.h" +#include "bladerf1input.h" class DeviceUISet; namespace Ui { - class BladerfInputGui; + class Bladerf1InputGui; } -class BladerfInputGui : public QWidget, public PluginInstanceGUI { +class Bladerf1InputGui : public QWidget, public PluginInstanceGUI { Q_OBJECT public: - explicit BladerfInputGui(DeviceUISet *deviceUISet, QWidget* parent = 0); - virtual ~BladerfInputGui(); + explicit Bladerf1InputGui(DeviceUISet *deviceUISet, QWidget* parent = 0); + virtual ~Bladerf1InputGui(); virtual void destroy(); void setName(const QString& name); @@ -51,12 +51,12 @@ public: virtual bool handleMessage(const Message& message); private: - Ui::BladerfInputGui* ui; + Ui::Bladerf1InputGui* ui; DeviceUISet* m_deviceUISet; bool m_forceSettings; bool m_doApplySettings; - BladeRFInputSettings m_settings; + BladeRF1InputSettings m_settings; QTimer m_updateTimer; QTimer m_statusTimer; std::vector m_gains; diff --git a/plugins/samplesource/bladerfinput/bladerfinputgui.ui b/plugins/samplesource/bladerf1input/bladerf1inputgui.ui similarity index 98% rename from plugins/samplesource/bladerfinput/bladerfinputgui.ui rename to plugins/samplesource/bladerf1input/bladerf1inputgui.ui index 744d3e7cf..2a231f2fd 100644 --- a/plugins/samplesource/bladerfinput/bladerfinputgui.ui +++ b/plugins/samplesource/bladerf1input/bladerf1inputgui.ui @@ -1,7 +1,7 @@ - BladerfInputGui - + Bladerf1InputGui + 0 @@ -24,12 +24,12 @@ - Sans Serif + Liberation Sans 9 - BladeRF + BladeRF1 @@ -136,7 +136,7 @@ - DejaVu Sans Mono + Liberation Mono 20 @@ -331,7 +331,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -379,7 +379,7 @@ Decimation factor - 3 + 0 @@ -411,6 +411,11 @@ 32 + + + 64 + +
diff --git a/plugins/samplesource/bladerf1input/bladerf1inputplugin.cpp b/plugins/samplesource/bladerf1input/bladerf1inputplugin.cpp new file mode 100644 index 000000000..6a826eedc --- /dev/null +++ b/plugins/samplesource/bladerf1input/bladerf1inputplugin.cpp @@ -0,0 +1,149 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "bladerf1inputplugin.h" + +#include +#include +#include "plugin/pluginapi.h" +#include "util/simpleserializer.h" +#include + +#ifdef SERVER_MODE +#include "bladerf1input.h" +#else +#include "bladerf1inputgui.h" +#endif + +const PluginDescriptor Blderf1InputPlugin::m_pluginDescriptor = { + QString("BladeRF1 Input"), + QString("4.2.0"), + QString("(c) Edouard Griffiths, F4EXB"), + QString("https://github.com/f4exb/sdrangel"), + true, + QString("https://github.com/f4exb/sdrangel") +}; + +const QString Blderf1InputPlugin::m_hardwareID = "BladeRF1"; +const QString Blderf1InputPlugin::m_deviceTypeID = BLADERF1INPUT_DEVICE_TYPE_ID; + +Blderf1InputPlugin::Blderf1InputPlugin(QObject* parent) : + QObject(parent) +{ +} + +const PluginDescriptor& Blderf1InputPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void Blderf1InputPlugin::initPlugin(PluginAPI* pluginAPI) +{ + pluginAPI->registerSampleSource(m_deviceTypeID, this); +} + +PluginInterface::SamplingDevices Blderf1InputPlugin::enumSampleSources() +{ + SamplingDevices result; + struct bladerf_devinfo *devinfo = 0; + + int count = bladerf_get_device_list(&devinfo); + + if (devinfo) + { + for(int i = 0; i < count; i++) + { + struct bladerf *dev; + + int status = bladerf_open_with_devinfo(&dev, &devinfo[i]); + + if (status == BLADERF_ERR_NODEV) + { + qCritical("BlderfInputPlugin::enumSampleSources: No device at index %d", i); + continue; + } + else if (status != 0) + { + qCritical("BlderfInputPlugin::enumSampleSources: Failed to open device at index %d", i); + continue; + } + + const char *boardName = bladerf_get_board_name(dev); + + if (strcmp(boardName, "bladerf1") == 0) + { + QString displayedName(QString("BladeRF1[%1] %2").arg(devinfo[i].instance).arg(devinfo[i].serial)); + + result.append(SamplingDevice(displayedName, + m_hardwareID, + m_deviceTypeID, + QString(devinfo[i].serial), + i, + PluginInterface::SamplingDevice::PhysicalDevice, + true, + 1, + 0)); + } + + bladerf_close(dev); + } + + bladerf_free_device_list(devinfo); // Valgrind memcheck + } + + return result; +} + +#ifdef SERVER_MODE +PluginInstanceGUI* Blderf1InputPlugin::createSampleSourcePluginInstanceGUI( + const QString& sourceId __attribute__((unused)), + QWidget **widget __attribute__((unused)), + DeviceUISet *deviceUISet __attribute__((unused))) +{ + return 0; +} +#else +PluginInstanceGUI* Blderf1InputPlugin::createSampleSourcePluginInstanceGUI( + const QString& sourceId, + QWidget **widget, + DeviceUISet *deviceUISet) +{ + if(sourceId == m_deviceTypeID) + { + Bladerf1InputGui* gui = new Bladerf1InputGui(deviceUISet); + *widget = gui; + return gui; + } + else + { + return 0; + } +} +#endif + +DeviceSampleSource *Blderf1InputPlugin::createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI) +{ + if (sourceId == m_deviceTypeID) + { + Bladerf1Input *input = new Bladerf1Input(deviceAPI); + return input; + } + else + { + return 0; + } +} + diff --git a/plugins/samplesource/bladerfinput/bladerfinputplugin.h b/plugins/samplesource/bladerf1input/bladerf1inputplugin.h similarity index 89% rename from plugins/samplesource/bladerfinput/bladerfinputplugin.h rename to plugins/samplesource/bladerf1input/bladerf1inputplugin.h index d9fcb6599..d49c28299 100644 --- a/plugins/samplesource/bladerfinput/bladerfinputplugin.h +++ b/plugins/samplesource/bladerf1input/bladerf1inputplugin.h @@ -24,15 +24,15 @@ class PluginAPI; class DeviceSourceAPI; class DeviceUISet; -#define BLADERF_DEVICE_TYPE_ID "sdrangel.samplesource.bladerf" +#define BLADERF1INPUT_DEVICE_TYPE_ID "sdrangel.samplesource.bladerf1input" -class BlderfInputPlugin : public QObject, public PluginInterface { +class Blderf1InputPlugin : public QObject, public PluginInterface { Q_OBJECT Q_INTERFACES(PluginInterface) - Q_PLUGIN_METADATA(IID BLADERF_DEVICE_TYPE_ID) + Q_PLUGIN_METADATA(IID BLADERF1INPUT_DEVICE_TYPE_ID) public: - explicit BlderfInputPlugin(QObject* parent = NULL); + explicit Blderf1InputPlugin(QObject* parent = NULL); const PluginDescriptor& getPluginDescriptor() const; void initPlugin(PluginAPI* pluginAPI); diff --git a/plugins/samplesource/bladerfinput/bladerfinputsettings.cpp b/plugins/samplesource/bladerf1input/bladerf1inputsettings.cpp similarity index 91% rename from plugins/samplesource/bladerfinput/bladerfinputsettings.cpp rename to plugins/samplesource/bladerf1input/bladerf1inputsettings.cpp index 5976dd6f8..922b93c81 100644 --- a/plugins/samplesource/bladerfinput/bladerfinputsettings.cpp +++ b/plugins/samplesource/bladerf1input/bladerf1inputsettings.cpp @@ -14,18 +14,18 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "bladerfinputsettings.h" +#include "bladerf1inputsettings.h" #include #include "util/simpleserializer.h" -BladeRFInputSettings::BladeRFInputSettings() +BladeRF1InputSettings::BladeRF1InputSettings() { resetToDefaults(); } -void BladeRFInputSettings::resetToDefaults() +void BladeRF1InputSettings::resetToDefaults() { m_centerFrequency = 435000*1000; m_devSampleRate = 3072000; @@ -40,9 +40,10 @@ void BladeRFInputSettings::resetToDefaults() m_xb200Filter = BLADERF_XB200_AUTO_1DB; m_dcBlock = false; m_iqCorrection = false; + m_fileRecordName = ""; } -QByteArray BladeRFInputSettings::serialize() const +QByteArray BladeRF1InputSettings::serialize() const { SimpleSerializer s(1); @@ -62,7 +63,7 @@ QByteArray BladeRFInputSettings::serialize() const return s.final(); } -bool BladeRFInputSettings::deserialize(const QByteArray& data) +bool BladeRF1InputSettings::deserialize(const QByteArray& data) { SimpleDeserializer d(data); diff --git a/plugins/samplesource/bladerfinput/bladerfinputsettings.h b/plugins/samplesource/bladerf1input/bladerf1inputsettings.h similarity index 94% rename from plugins/samplesource/bladerfinput/bladerfinputsettings.h rename to plugins/samplesource/bladerf1input/bladerf1inputsettings.h index 7d18fa987..ddaf11b6f 100644 --- a/plugins/samplesource/bladerfinput/bladerfinputsettings.h +++ b/plugins/samplesource/bladerf1input/bladerf1inputsettings.h @@ -18,9 +18,10 @@ #define _BLADERF_BLADERFINPUTSETTINGS_H_ #include +#include #include -struct BladeRFInputSettings { +struct BladeRF1InputSettings { typedef enum { FC_POS_INFRA = 0, FC_POS_SUPRA, @@ -40,8 +41,9 @@ struct BladeRFInputSettings { bladerf_xb200_filter m_xb200Filter; bool m_dcBlock; bool m_iqCorrection; + QString m_fileRecordName; - BladeRFInputSettings(); + BladeRF1InputSettings(); void resetToDefaults(); QByteArray serialize() const; bool deserialize(const QByteArray& data); diff --git a/plugins/samplesource/bladerfinput/bladerfinputthread.cpp b/plugins/samplesource/bladerf1input/bladerf1inputthread.cpp similarity index 80% rename from plugins/samplesource/bladerfinput/bladerfinputthread.cpp rename to plugins/samplesource/bladerf1input/bladerf1inputthread.cpp index 7805d91fa..277952ac3 100644 --- a/plugins/samplesource/bladerfinput/bladerfinputthread.cpp +++ b/plugins/samplesource/bladerf1input/bladerf1inputthread.cpp @@ -14,15 +14,16 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "../bladerfinput/bladerfinputthread.h" +#include "bladerf1inputthread.h" #include #include +#include #include "dsp/samplesinkfifo.h" -BladerfInputThread::BladerfInputThread(struct bladerf* dev, SampleSinkFifo* sampleFifo, QObject* parent) : +Bladerf1InputThread::Bladerf1InputThread(struct bladerf* dev, SampleSinkFifo* sampleFifo, QObject* parent) : QThread(parent), m_running(false), m_dev(dev), @@ -31,14 +32,15 @@ BladerfInputThread::BladerfInputThread(struct bladerf* dev, SampleSinkFifo* samp m_log2Decim(0), m_fcPos(0) { + std::fill(m_buf, m_buf + 2*BLADERF_BLOCKSIZE, 0); } -BladerfInputThread::~BladerfInputThread() +Bladerf1InputThread::~Bladerf1InputThread() { stopWork(); } -void BladerfInputThread::startWork() +void Bladerf1InputThread::startWork() { m_startWaitMutex.lock(); start(); @@ -47,23 +49,23 @@ void BladerfInputThread::startWork() m_startWaitMutex.unlock(); } -void BladerfInputThread::stopWork() +void Bladerf1InputThread::stopWork() { m_running = false; wait(); } -void BladerfInputThread::setLog2Decimation(unsigned int log2_decim) +void Bladerf1InputThread::setLog2Decimation(unsigned int log2_decim) { m_log2Decim = log2_decim; } -void BladerfInputThread::setFcPos(int fcPos) +void Bladerf1InputThread::setFcPos(int fcPos) { m_fcPos = fcPos; } -void BladerfInputThread::run() +void Bladerf1InputThread::run() { int res; @@ -83,7 +85,7 @@ void BladerfInputThread::run() } // Decimate according to specified log2 (ex: log2=4 => decim=16) -void BladerfInputThread::callback(const qint16* buf, qint32 len) +void Bladerf1InputThread::callback(const qint16* buf, qint32 len) { SampleVector::iterator it = m_convertBuffer.begin(); @@ -112,6 +114,9 @@ void BladerfInputThread::callback(const qint16* buf, qint32 len) case 5: m_decimators.decimate32_inf(&it, buf, len); break; + case 6: + m_decimators.decimate64_inf(&it, buf, len); + break; default: break; } @@ -135,6 +140,9 @@ void BladerfInputThread::callback(const qint16* buf, qint32 len) case 5: m_decimators.decimate32_sup(&it, buf, len); break; + case 6: + m_decimators.decimate64_sup(&it, buf, len); + break; default: break; } @@ -158,6 +166,9 @@ void BladerfInputThread::callback(const qint16* buf, qint32 len) case 5: m_decimators.decimate32_cen(&it, buf, len); break; + case 6: + m_decimators.decimate64_cen(&it, buf, len); + break; default: break; } diff --git a/plugins/samplesource/bladerfinput/bladerfinputthread.h b/plugins/samplesource/bladerf1input/bladerf1inputthread.h similarity index 88% rename from plugins/samplesource/bladerfinput/bladerfinputthread.h rename to plugins/samplesource/bladerf1input/bladerf1inputthread.h index 8230c5c90..4088bff04 100644 --- a/plugins/samplesource/bladerfinput/bladerfinputthread.h +++ b/plugins/samplesource/bladerf1input/bladerf1inputthread.h @@ -26,12 +26,12 @@ #define BLADERF_BLOCKSIZE (1<<14) -class BladerfInputThread : public QThread { +class Bladerf1InputThread : public QThread { Q_OBJECT public: - BladerfInputThread(struct bladerf* dev, SampleSinkFifo* sampleFifo, QObject* parent = NULL); - ~BladerfInputThread(); + Bladerf1InputThread(struct bladerf* dev, SampleSinkFifo* sampleFifo, QObject* parent = NULL); + ~Bladerf1InputThread(); void startWork(); void stopWork(); @@ -51,11 +51,7 @@ private: unsigned int m_log2Decim; int m_fcPos; -#ifdef SDR_RX_SAMPLE_24BIT - Decimators m_decimators; -#else Decimators m_decimators; -#endif void run(); void callback(const qint16* buf, qint32 len); diff --git a/plugins/samplesource/bladerfinput/readme.md b/plugins/samplesource/bladerf1input/readme.md similarity index 69% rename from plugins/samplesource/bladerfinput/readme.md rename to plugins/samplesource/bladerf1input/readme.md index 5bc853e2b..67c644bbd 100644 --- a/plugins/samplesource/bladerfinput/readme.md +++ b/plugins/samplesource/bladerf1input/readme.md @@ -1,18 +1,20 @@ -

BladeRF input plugin

+

BladeRF classic (v1) input plugin

Introduction

-This input sample source plugin gets its samples from a [BladeRF device](https://www.nuand.com/). +This input sample source plugin gets its samples from a [BladeRF1 device](https://www.nuand.com/bladerf-1) using LibbladeRF v.2. This is available in Linux distributions only.

Build

The plugin will be built only if the [BladeRF host library](https://github.com/Nuand/bladeRF) is installed in your system. If you build it from source and install it in a custom location say: `/opt/install/libbladeRF` you will have to add `-DLIBBLADERF_INCLUDE_DIR=/opt/install/libbladeRF/include -DLIBBLADERF_LIBRARIES=/opt/install/libbladeRF/lib/libbladeRF.so` to the cmake command line. -The BladeRF Host library is also provided by many Linux distributions and is built in the SDRangel binary releases. +Note that libbladeRF v2 with git tag 2018.08 should be used (official release). The FPGA image v0.7.3 should be used accordingly. The FPGA .rbf file should be copied to the folder where the `sdrangel` binary resides. You can download FPGA images from [here](https://www.nuand.com/fpga_images/) + +The BladeRF Host library is also provided by many Linux distributions (check its version) and is built in the SDRangel binary releases.

Interface

-![BladeRF input plugin GUI](../../../doc/img/BladeRFInput_plugin.png) +![BladeRF1 input plugin GUI](../../../doc/img/BladeRF1Input_plugin.png)

1: Common stream parameters

@@ -28,7 +30,7 @@ Device start / stop button. - Blue triangle icon: device is ready and can be started - Green square icon: device is running and can be stopped - - Magenta (or pink) square icon: an error occured. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again. + - Magenta (or pink) square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again.

1.3: Record

@@ -62,19 +64,24 @@ This controls the optional XB-200 add-on when it is fitted to the BladeRF main b This is the BladeRF device ADC sample rate in S/s. -Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

5: Decimation factor

-The I/Q stream from the BladeRF ADC is doensampled by a power of two before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8, 16, 32. +The I/Q stream from the BladeRF ADC is downsampled by a power of two before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8, 16, 32, 64.

6: Baseband center frequency position relative the the BladeRF Rx center frequency

Possible values are: - - **Cen**: the decimation operation takes place around the BladeRF Rx center frequency - - **Inf**: the decimation operation takes place around the center of the lower half of the BladeRF Rx passband. - - **Sup**: the decimation operation takes place around the center of the upper half of the BladeRF Rx passband. + - **Cen**: the decimation operation takes place around the BladeRF Rx center frequency Fs + - **Inf**: the decimation operation takes place around Fs - Fc. + - **Sup**: the decimation operation takes place around Fs + Fc. + +With SR as the sample rate before decimation Fc is calculated as: + + - if decimation n is 4 or lower: Fc = SR/2^(log2(n)-1). The device center frequency is on the side of the baseband. You need a RF filter bandwidth at least twice the baseband. + - if decimation n is 8 or higher: Fc = SR/n. The device center frequency is half the baseband away from the side of the baseband. You need a RF filter bandwidth at least 3 times the baseband.

7: Rx filter bandwidth

@@ -94,4 +101,4 @@ The VGA1 gain can be adjusted from 5 dB to 30 dB in 1 dB steps. The VGA1 is insi

10: Variable gain amplifier #2 gain

-The VGA2 gain can be adjusted from 0 dB to 30 dB in 3 dB steps. The VGA2 is inside the LMS6002D chip and is placed between the baseband filter and the ADC. \ No newline at end of file +The VGA2 gain can be adjusted from 0 dB to 30 dB in 3 dB steps. The VGA2 is inside the LMS6002D chip and is placed between the baseband filter and the ADC. diff --git a/plugins/samplesource/bladerf2input/CMakeLists.txt b/plugins/samplesource/bladerf2input/CMakeLists.txt new file mode 100644 index 000000000..75d330454 --- /dev/null +++ b/plugins/samplesource/bladerf2input/CMakeLists.txt @@ -0,0 +1,79 @@ +project(bladerf2input) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +set(bladerf2input_SOURCES + bladerf2inputgui.cpp + bladerf2input.cpp + bladerf2inputplugin.cpp + bladerf2inputsettings.cpp + bladerf2inputthread.cpp +) + +set(bladerf2input_HEADERS + bladerf2inputgui.h + bladerf2input.h + bladerf2inputplugin.h + bladerf2inputsettings.h + bladerf2inputthread.h +) + +set(bladerf2input_FORMS + bladerf2inputgui.ui +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERFLIBSRC}/include + ${LIBBLADERFLIBSRC}/src +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERF_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +#include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +qt5_wrap_ui(bladerf2input_FORMS_HEADERS ${bladerf2input_FORMS}) + +add_library(inputbladerf2 SHARED + ${bladerf2input_SOURCES} + ${bladerf2input_HEADERS_MOC} + ${bladerf2input_FORMS_HEADERS} +) + +if (BUILD_DEBIAN) +target_link_libraries(inputbladerf2 + ${QT_LIBRARIES} + bladerf + sdrbase + sdrgui + swagger + bladerf2device +) +else (BUILD_DEBIAN) +target_link_libraries(inputbladerf2 + ${QT_LIBRARIES} + ${LIBBLADERF_LIBRARIES} + sdrbase + sdrgui + swagger + bladerf2device +) +endif (BUILD_DEBIAN) + +target_link_libraries(inputbladerf2 Qt5::Core Qt5::Widgets) + +install(TARGETS inputbladerf2 DESTINATION lib/plugins/samplesource) diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp new file mode 100644 index 000000000..577c4a323 --- /dev/null +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -0,0 +1,1148 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "libbladeRF.h" + +#include "SWGDeviceSettings.h" +#include "SWGBladeRF2InputSettings.h" +#include "SWGDeviceState.h" +#include "SWGDeviceReport.h" +#include "SWGBladeRF2InputReport.h" + +#include "device/devicesourceapi.h" +#include "device/devicesinkapi.h" +#include "dsp/dspcommands.h" +#include "dsp/filerecord.h" +#include "dsp/dspengine.h" + +#include "bladerf2/devicebladerf2shared.h" +#include "bladerf2/devicebladerf2.h" +#include "bladerf2inputthread.h" +#include "bladerf2input.h" + + +MESSAGE_CLASS_DEFINITION(BladeRF2Input::MsgConfigureBladeRF2, Message) +MESSAGE_CLASS_DEFINITION(BladeRF2Input::MsgFileRecord, Message) +MESSAGE_CLASS_DEFINITION(BladeRF2Input::MsgStartStop, Message) +MESSAGE_CLASS_DEFINITION(BladeRF2Input::MsgReportGainRange, Message) + +BladeRF2Input::BladeRF2Input(DeviceSourceAPI *deviceAPI) : + m_deviceAPI(deviceAPI), + m_settings(), + m_deviceDescription("BladeRF2Input"), + m_running(false), + m_thread(0) +{ + openDevice(); + + if (m_deviceShared.m_dev) + { + const bladerf_gain_modes *modes = 0; + int nbModes = m_deviceShared.m_dev->getGainModesRx(&modes); + + if (modes) + { + for (int i = 0; i < nbModes; i++) { + m_gainModes.push_back(GainMode{QString(modes[i].name), modes[i].mode}); + } + } + } + + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); + m_deviceAPI->addSink(m_fileSink); +} + +BladeRF2Input::~BladeRF2Input() +{ + if (m_running) { + stop(); + } + + m_deviceAPI->removeSink(m_fileSink); + delete m_fileSink; + closeDevice(); +} + +void BladeRF2Input::destroy() +{ + delete this; +} + +bool BladeRF2Input::openDevice() +{ + if (!m_sampleFifo.setSize(96000 * 4)) + { + qCritical("BladeRF2Input::openDevice: could not allocate SampleFifo"); + return false; + } + else + { + qDebug("BladeRF2Input::openDevice: allocated SampleFifo"); + } + + // look for Rx buddies and get reference to the device object + if (m_deviceAPI->getSourceBuddies().size() > 0) // look source sibling first + { + qDebug("BladeRF2Input::openDevice: look in Rx buddies"); + + DeviceSourceAPI *sourceBuddy = m_deviceAPI->getSourceBuddies()[0]; + DeviceBladeRF2Shared *deviceBladeRF2Shared = (DeviceBladeRF2Shared*) sourceBuddy->getBuddySharedPtr(); + + if (deviceBladeRF2Shared == 0) + { + qCritical("BladeRF2Input::openDevice: the source buddy shared pointer is null"); + return false; + } + + DeviceBladeRF2 *device = deviceBladeRF2Shared->m_dev; + + if (device == 0) + { + qCritical("BladeRF2Input::openDevice: cannot get device pointer from Rx buddy"); + return false; + } + + m_deviceShared.m_dev = device; + } + // look for Tx buddies and get reference to the device object + else if (m_deviceAPI->getSinkBuddies().size() > 0) // then sink + { + qDebug("BladeRF2Input::openDevice: look in Tx buddies"); + + DeviceSinkAPI *sinkBuddy = m_deviceAPI->getSinkBuddies()[0]; + DeviceBladeRF2Shared *deviceBladeRF2Shared = (DeviceBladeRF2Shared*) sinkBuddy->getBuddySharedPtr(); + + if (deviceBladeRF2Shared == 0) + { + qCritical("BladeRF2Input::openDevice: the sink buddy shared pointer is null"); + return false; + } + + DeviceBladeRF2 *device = deviceBladeRF2Shared->m_dev; + + if (device == 0) + { + qCritical("BladeRF2Input::openDevice: cannot get device pointer from Tx buddy"); + return false; + } + + m_deviceShared.m_dev = device; + } + // There are no buddies then create the first BladeRF2 device + else + { + qDebug("BladeRF2Input::openDevice: open device here"); + + m_deviceShared.m_dev = new DeviceBladeRF2(); + char serial[256]; + strcpy(serial, qPrintable(m_deviceAPI->getSampleSourceSerial())); + + if (!m_deviceShared.m_dev->open(serial)) + { + qCritical("BladeRF2Input::openDevice: cannot open BladeRF2 device"); + return false; + } + } + + m_deviceShared.m_channel = m_deviceAPI->getItemIndex(); // publicly allocate channel + m_deviceShared.m_source = this; + m_deviceAPI->setBuddySharedPtr(&m_deviceShared); // propagate common parameters to API + return true; +} + +void BladeRF2Input::closeDevice() +{ + if (m_deviceShared.m_dev == 0) { // was never open + return; + } + + if (m_running) { + stop(); + } + + if (m_thread) { // stills own the thread => transfer to a buddy + moveThreadToBuddy(); + } + + m_deviceShared.m_channel = -1; // publicly release channel + m_deviceShared.m_source = 0; + + // No buddies so effectively close the device + + if ((m_deviceAPI->getSinkBuddies().size() == 0) && (m_deviceAPI->getSourceBuddies().size() == 0)) + { + m_deviceShared.m_dev->close(); + delete m_deviceShared.m_dev; + m_deviceShared.m_dev = 0; + } +} + +void BladeRF2Input::init() +{ + applySettings(m_settings, true); +} + +BladeRF2InputThread *BladeRF2Input::findThread() +{ + if (m_thread == 0) // this does not own the thread + { + BladeRF2InputThread *bladerf2InputThread = 0; + + // find a buddy that has allocated the thread + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + std::vector::const_iterator it = sourceBuddies.begin(); + + for (; it != sourceBuddies.end(); ++it) + { + BladeRF2Input *buddySource = ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_source; + + if (buddySource) + { + bladerf2InputThread = buddySource->getThread(); + + if (bladerf2InputThread) { + break; + } + } + } + + return bladerf2InputThread; + } + else + { + return m_thread; // own thread + } +} + +void BladeRF2Input::moveThreadToBuddy() +{ + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + std::vector::const_iterator it = sourceBuddies.begin(); + + for (; it != sourceBuddies.end(); ++it) + { + BladeRF2Input *buddySource = ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_source; + + if (buddySource) + { + buddySource->setThread(m_thread); + m_thread = 0; // zero for others + } + } +} + +bool BladeRF2Input::start() +{ + // There is a single thread per physical device (Rx side). This thread is unique and referenced by a unique + // buddy in the group of source buddies associated with this physical device. + // + // This start method is responsible for managing the thread and channel enabling when the streaming of a Rx channel is started + // + // It checks the following conditions + // - the thread is allocated or not (by itself or one of its buddies). If it is it grabs the thread pointer. + // - the requested channel is the first (0) or the following (just 1 in BladeRF 2 case) + // + // The BladeRF support library lets you work in two possible modes: + // - Single Input (SI) with only one channel streaming. This HAS to be channel 0. + // - Multiple Input (MI) with two channels streaming using interleaved samples. It MUST be in this configuration if channel 1 + // is used irrespective of what you actually do with samples coming from channel 0. When we will run with only channel 1 + // streaming from the client perspective the channel 0 will actually be enabled and streaming but its samples will + // just be disregarded. + // + // It manages the transition form SI where only one channel (the first or channel 0) should be running to the + // Multiple Input (MI) if the requested channel is 1. More generally it checks if the requested channel is within the current + // channel range allocated in the thread or past it. To perform the transition it stops the thread, deletes it and creates a new one. + // It marks the thread as needing start. + // + // If the requested channel is within the thread channel range (this thread being already allocated) it simply adds its FIFO reference + // so that the samples are fed to the FIFO and leaves the thread unchanged (no stop, no delete/new) + // + // If there is no thread allocated it creates a new one with a number of channels that fits the requested channel. That is + // 1 if channel 0 is requested (SI mode) and 2 if channel 1 is requested (MI mode). It marks the thread as needing start. + // + // Eventually it registers the FIFO in the thread. If the thread has to be started it enables the channels up to the number of channels + // allocated in the thread and starts the thread. + + if (!m_deviceShared.m_dev) + { + qDebug("BladeRF2Input::start: no device object"); + return false; + } + + int requestedChannel = m_deviceAPI->getItemIndex(); + BladeRF2InputThread *bladerf2InputThread = findThread(); + bool needsStart = false; + + if (bladerf2InputThread) // if thread is already allocated + { + qDebug("BladeRF2Input::start: thread is already allocated"); + + int nbOriginalChannels = bladerf2InputThread->getNbChannels(); + + if (requestedChannel+1 > nbOriginalChannels) // expansion by deleting and re-creating the thread + { + qDebug("BladeRF2Input::start: expand channels. Re-allocate thread and take ownership"); + + SampleSinkFifo **fifos = new SampleSinkFifo*[nbOriginalChannels]; + unsigned int *log2Decims = new unsigned int[nbOriginalChannels]; + int *fcPoss = new int[nbOriginalChannels]; + + for (int i = 0; i < nbOriginalChannels; i++) // save original FIFO references and data + { + fifos[i] = bladerf2InputThread->getFifo(i); + log2Decims[i] = bladerf2InputThread->getLog2Decimation(i); + fcPoss[i] = bladerf2InputThread->getFcPos(i); + } + + bladerf2InputThread->stopWork(); + delete bladerf2InputThread; + bladerf2InputThread = new BladeRF2InputThread(m_deviceShared.m_dev->getDev(), requestedChannel+1); + m_thread = bladerf2InputThread; // take ownership + + for (int i = 0; i < nbOriginalChannels; i++) // restore original FIFO references + { + bladerf2InputThread->setFifo(i, fifos[i]); + bladerf2InputThread->setLog2Decimation(i, log2Decims[i]); + bladerf2InputThread->setFcPos(i, fcPoss[i]); + } + + // remove old thread address from buddies (reset in all buddies). The address being held only in the owning source. + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + std::vector::const_iterator it = sourceBuddies.begin(); + + for (; it != sourceBuddies.end(); ++it) { + ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_source->setThread(0); + } + + needsStart = true; + } + else + { + qDebug("BladeRF2Input::start: keep buddy thread"); + } + } + else // first allocation + { + qDebug("BladeRF2Input::start: allocate thread and take ownership"); + bladerf2InputThread = new BladeRF2InputThread(m_deviceShared.m_dev->getDev(), requestedChannel+1); + m_thread = bladerf2InputThread; // take ownership + needsStart = true; + } + + bladerf2InputThread->setFifo(requestedChannel, &m_sampleFifo); + bladerf2InputThread->setLog2Decimation(requestedChannel, m_settings.m_log2Decim); + bladerf2InputThread->setFcPos(requestedChannel, (int) m_settings.m_fcPos); + + if (needsStart) + { + qDebug("BladeRF2Input::start: enabling channel(s) and (re)sart buddy thread"); + + int nbChannels = bladerf2InputThread->getNbChannels(); + + for (int i = 0; i < nbChannels; i++) + { + if (!m_deviceShared.m_dev->openRx(i)) { + qCritical("BladeRF2Input::start: channel %u cannot be enabled", i); + } + } + + bladerf2InputThread->startWork(); + } + + applySettings(m_settings, true); + + qDebug("BladeRF2Input::start: started"); + m_running = true; + + return true; +} + +void BladeRF2Input::stop() +{ + // This stop method is responsible for managing the thread and channel disabling when the streaming of + // a Rx channel is stopped + // + // If the thread is currently managing only one channel (SI mode). The thread can be just stopped and deleted. + // Then the channel is closed (disabled). + // + // If the thread is currently managing many channels (MI mode) and we are removing the last channel. The transition + // from MI to SI or reduction of MI size is handled by stopping the thread, deleting it and creating a new one + // with one channel less if (and only if) there is still a channel active. + // + // If the thread is currently managing many channels (MI mode) but the channel being stopped is not the last + // channel then the FIFO reference is simply removed from the thread so that it will not stream into this FIFO + // anymore. In this case the channel is not closed (disabled) so that other channels can continue with the + // same configuration. The device continues streaming on this channel but the samples are simply dropped (by + // removing FIFO reference). + + if (!m_running) { + return; + } + + int requestedChannel = m_deviceAPI->getItemIndex(); + BladeRF2InputThread *bladerf2InputThread = findThread(); + + if (bladerf2InputThread == 0) { // no thread allocated + return; + } + + int nbOriginalChannels = bladerf2InputThread->getNbChannels(); + + if (nbOriginalChannels == 1) // SI mode => just stop and delete the thread + { + qDebug("BladeRF2Input::stop: SI mode. Just stop and delete the thread"); + bladerf2InputThread->stopWork(); + delete bladerf2InputThread; + m_thread = 0; + + // remove old thread address from buddies (reset in all buddies) + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + std::vector::const_iterator it = sourceBuddies.begin(); + + for (; it != sourceBuddies.end(); ++it) { + ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_source->setThread(0); + } + + m_deviceShared.m_dev->closeRx(0); // close the unique channel + } + else if (requestedChannel == nbOriginalChannels - 1) // remove last MI channel => reduce by deleting and re-creating the thread + { + qDebug("BladeRF2Input::stop: MI mode. Reduce by deleting and re-creating the thread"); + bladerf2InputThread->stopWork(); + SampleSinkFifo **fifos = new SampleSinkFifo*[nbOriginalChannels-1]; + unsigned int *log2Decims = new unsigned int[nbOriginalChannels-1]; + int *fcPoss = new int[nbOriginalChannels-1]; + bool stillActiveFIFO = false; + + for (int i = 0; i < nbOriginalChannels-1; i++) // save original FIFO references + { + fifos[i] = bladerf2InputThread->getFifo(i); + stillActiveFIFO = stillActiveFIFO || (bladerf2InputThread->getFifo(i) != 0); + log2Decims[i] = bladerf2InputThread->getLog2Decimation(i); + fcPoss[i] = bladerf2InputThread->getFcPos(i); + } + + delete bladerf2InputThread; + m_thread = 0; + + if (stillActiveFIFO) + { + bladerf2InputThread = new BladeRF2InputThread(m_deviceShared.m_dev->getDev(), nbOriginalChannels-1); + m_thread = bladerf2InputThread; // take ownership + + for (int i = 0; i < nbOriginalChannels-1; i++) // restore original FIFO references + { + bladerf2InputThread->setFifo(i, fifos[i]); + bladerf2InputThread->setLog2Decimation(i, log2Decims[i]); + bladerf2InputThread->setFcPos(i, fcPoss[i]); + } + } + else + { + qDebug("BladeRF2Input::stop: do not re-create thread as there are no more FIFOs active"); + } + + // remove old thread address from buddies (reset in all buddies). The address being held only in the owning source. + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + std::vector::const_iterator it = sourceBuddies.begin(); + + for (; it != sourceBuddies.end(); ++it) { + ((DeviceBladeRF2Shared*) (*it)->getBuddySharedPtr())->m_source->setThread(0); + } + + m_deviceShared.m_dev->closeRx(requestedChannel); // close the last channel + + if (stillActiveFIFO) { + bladerf2InputThread->startWork(); + } + } + else // remove channel from existing thread + { + qDebug("BladeRF2Input::stop: MI mode. Not changing MI configuration. Just remove FIFO reference"); + bladerf2InputThread->setFifo(requestedChannel, 0); // remove FIFO + } + + m_running = false; +} + +QByteArray BladeRF2Input::serialize() const +{ + return m_settings.serialize(); +} + +bool BladeRF2Input::deserialize(const QByteArray& data) +{ + bool success = true; + + if (!m_settings.deserialize(data)) + { + m_settings.resetToDefaults(); + success = false; + } + + MsgConfigureBladeRF2* message = MsgConfigureBladeRF2::create(m_settings, true); + m_inputMessageQueue.push(message); + + if (m_guiMessageQueue) + { + MsgConfigureBladeRF2* messageToGUI = MsgConfigureBladeRF2::create(m_settings, true); + m_guiMessageQueue->push(messageToGUI); + } + + return success; +} + +const QString& BladeRF2Input::getDeviceDescription() const +{ + return m_deviceDescription; +} + +int BladeRF2Input::getSampleRate() const +{ + int rate = m_settings.m_devSampleRate; + return (rate / (1<push(messageToGUI); + } +} + +bool BladeRF2Input::setDeviceCenterFrequency(struct bladerf *dev, int requestedChannel, quint64 freq_hz, int loPpmTenths) +{ + qint64 df = ((qint64)freq_hz * loPpmTenths) / 10000000LL; + freq_hz += df; + + int status = bladerf_set_frequency(dev, BLADERF_CHANNEL_RX(requestedChannel), freq_hz); + + if (status < 0) { + qWarning("BladeRF2Input::setDeviceCenterFrequency: bladerf_set_frequency(%lld) failed: %s", + freq_hz, bladerf_strerror(status)); + return false; + } + else + { + qDebug("BladeRF2Input::setDeviceCenterFrequency: bladerf_set_frequency(%lld)", freq_hz); + return true; + } +} + +void BladeRF2Input::getFrequencyRange(uint64_t& min, uint64_t& max, int& step) +{ + if (m_deviceShared.m_dev) { + m_deviceShared.m_dev->getFrequencyRangeRx(min, max, step); + } +} + +void BladeRF2Input::getSampleRateRange(int& min, int& max, int& step) +{ + if (m_deviceShared.m_dev) { + m_deviceShared.m_dev->getSampleRateRangeRx(min, max, step); + } +} + +void BladeRF2Input::getBandwidthRange(int& min, int& max, int& step) +{ + if (m_deviceShared.m_dev) { + m_deviceShared.m_dev->getBandwidthRangeRx(min, max, step); + } +} + +void BladeRF2Input::getGlobalGainRange(int& min, int& max, int& step) +{ + if (m_deviceShared.m_dev) { + m_deviceShared.m_dev->getGlobalGainRangeRx(min, max, step); + } +} + +bool BladeRF2Input::handleMessage(const Message& message) +{ + if (MsgConfigureBladeRF2::match(message)) + { + MsgConfigureBladeRF2& conf = (MsgConfigureBladeRF2&) message; + qDebug() << "BladeRF2Input::handleMessage: MsgConfigureBladeRF2"; + + if (!applySettings(conf.getSettings(), conf.getForce())) + { + qDebug("BladeRF2Input::handleMessage: MsgConfigureBladeRF2 config error"); + } + + return true; + } + else if (DeviceBladeRF2Shared::MsgReportBuddyChange::match(message)) + { + DeviceBladeRF2Shared::MsgReportBuddyChange& report = (DeviceBladeRF2Shared::MsgReportBuddyChange&) message; + struct bladerf *dev = m_deviceShared.m_dev->getDev(); + BladeRF2InputSettings settings = m_settings; + int status; + unsigned int tmp_uint; + bool tmp_bool; + + // evaluate changes that may have been introduced by changes in a buddy + + if (dev) // The BladeRF device must have been open to do so + { + int requestedChannel = m_deviceAPI->getItemIndex(); + + if (report.getRxElseTx()) // Rx buddy change: check for: frequency, LO correction, gain mode and value, bias tee, sample rate, bandwidth + { + settings.m_devSampleRate = report.getDevSampleRate(); + settings.m_LOppmTenths = report.getLOppmTenths(); + settings.m_centerFrequency = report.getCenterFrequency(); + settings.m_fcPos = (BladeRF2InputSettings::fcPos_t) report.getFcPos(); + + BladeRF2InputThread *inputThread = findThread(); + + if (inputThread) { + inputThread->setFcPos(requestedChannel, (int) settings.m_fcPos); + } + + status = bladerf_get_bandwidth(dev, BLADERF_CHANNEL_RX(requestedChannel), &tmp_uint); + + if (status < 0) { + qCritical("BladeRF2Input::handleMessage: MsgReportBuddyChange: bladerf_get_bandwidth error: %s", bladerf_strerror(status)); + } else { + settings.m_bandwidth = tmp_uint; + } + + status = bladerf_get_bias_tee(dev, BLADERF_CHANNEL_RX(requestedChannel), &tmp_bool); + + if (status < 0) { + qCritical("BladeRF2Input::handleMessage: MsgReportBuddyChange: bladerf_get_bias_tee error: %s", bladerf_strerror(status)); + } else { + settings.m_biasTee = tmp_bool; + } + } + else // Tx buddy change: check for sample rate change only + { + settings.m_devSampleRate = report.getDevSampleRate(); +// status = bladerf_get_sample_rate(dev, BLADERF_CHANNEL_RX(requestedChannel), &tmp_uint); +// +// if (status < 0) { +// qCritical("BladeRF2Input::handleMessage: MsgReportBuddyChange: bladerf_get_sample_rate error: %s", bladerf_strerror(status)); +// } else { +// settings.m_devSampleRate = tmp_uint; +// } + + qint64 deviceCenterFrequency = DeviceSampleSource::calculateDeviceCenterFrequency( + settings.m_centerFrequency, + 0, + settings.m_log2Decim, + (DeviceSampleSource::fcPos_t) settings.m_fcPos, + settings.m_devSampleRate); + + if (setDeviceCenterFrequency(dev, requestedChannel, deviceCenterFrequency, settings.m_LOppmTenths)) + { + if (getMessageQueueToGUI()) + { + int min, max, step; + getGlobalGainRange(min, max, step); + MsgReportGainRange *msg = MsgReportGainRange::create(min, max, step); + getMessageQueueToGUI()->push(msg); + } + } + } + + // change DSP settings if buddy change introduced a change in center frequency or base rate + if ((settings.m_centerFrequency != m_settings.m_centerFrequency) || (settings.m_devSampleRate != m_settings.m_devSampleRate)) + { + int sampleRate = settings.m_devSampleRate/(1<handleMessage(*notif); // forward to file sink + m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif); + } + + m_settings = settings; // acknowledge the new settings + + // propagate settings to GUI if any + if (getMessageQueueToGUI()) + { + MsgConfigureBladeRF2 *reportToGUI = MsgConfigureBladeRF2::create(m_settings, false); + getMessageQueueToGUI()->push(reportToGUI); + } + } + + return true; + } + else if (MsgFileRecord::match(message)) + { + MsgFileRecord& conf = (MsgFileRecord&) message; + qDebug() << "BladeRF2Input::handleMessage: MsgFileRecord: " << conf.getStartStop(); + + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSink->setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); + } + + m_fileSink->startRecording(); + } + else + { + m_fileSink->stopRecording(); + } + + return true; + } + else if (MsgStartStop::match(message)) + { + MsgStartStop& cmd = (MsgStartStop&) message; + qDebug() << "BladeRF2Input::handleMessage: MsgStartStop: " << (cmd.getStartStop() ? "start" : "stop"); + + if (cmd.getStartStop()) + { + if (m_deviceAPI->initAcquisition()) + { + m_deviceAPI->startAcquisition(); + } + } + else + { + m_deviceAPI->stopAcquisition(); + } + + return true; + } + else + { + return false; + } +} + +bool BladeRF2Input::applySettings(const BladeRF2InputSettings& settings, bool force) +{ + bool forwardChangeOwnDSP = false; + bool forwardChangeRxBuddies = false; + bool forwardChangeTxBuddies = false; + + struct bladerf *dev = m_deviceShared.m_dev->getDev(); + int requestedChannel = m_deviceAPI->getItemIndex(); + qint64 xlatedDeviceCenterFrequency = settings.m_centerFrequency; + xlatedDeviceCenterFrequency -= settings.m_transverterMode ? settings.m_transverterDeltaFrequency : 0; + xlatedDeviceCenterFrequency = xlatedDeviceCenterFrequency < 0 ? 0 : xlatedDeviceCenterFrequency; + + if ((m_settings.m_dcBlock != settings.m_dcBlock) || + (m_settings.m_iqCorrection != settings.m_iqCorrection) || force) + { + m_deviceAPI->configureCorrections(settings.m_dcBlock, settings.m_iqCorrection); + } + + if ((m_settings.m_devSampleRate != settings.m_devSampleRate) || force) + { + forwardChangeOwnDSP = true; + forwardChangeRxBuddies = true; + forwardChangeTxBuddies = true; + + if (dev != 0) + { + unsigned int actualSamplerate; + int status = bladerf_set_sample_rate(dev, BLADERF_CHANNEL_RX(requestedChannel), settings.m_devSampleRate, &actualSamplerate); + + if (status < 0) + { + qCritical("BladeRF2Input::applySettings: could not set sample rate: %d: %s", + settings.m_devSampleRate, bladerf_strerror(status)); + } + else + { + qDebug() << "BladeRF2Input::applySettings: bladerf_set_sample_rate: actual sample rate is " << actualSamplerate; + } + } + } + + if ((m_settings.m_bandwidth != settings.m_bandwidth) || force) + { + forwardChangeRxBuddies = true; + + if (dev != 0) + { + unsigned int actualBandwidth; + int status = bladerf_set_bandwidth(dev, BLADERF_CHANNEL_RX(requestedChannel), settings.m_bandwidth, &actualBandwidth); + + if(status < 0) + { + qCritical("BladeRF2Input::applySettings: could not set bandwidth: %d: %s", + settings.m_bandwidth, bladerf_strerror(status)); + } + else + { + qDebug() << "BladeRF2Input::applySettings: bladerf_set_bandwidth: actual bandwidth is " << actualBandwidth; + } + } + } + + if ((m_settings.m_fcPos != settings.m_fcPos) || force) + { + BladeRF2InputThread *inputThread = findThread(); + + if (inputThread != 0) + { + inputThread->setFcPos(requestedChannel, (int) settings.m_fcPos); + qDebug() << "BladeRF2Input::applySettings: set fc pos (enum) to " << (int) settings.m_fcPos; + } + } + + if ((m_settings.m_log2Decim != settings.m_log2Decim) || force) + { + forwardChangeOwnDSP = true; + BladeRF2InputThread *inputThread = findThread(); + + if (inputThread != 0) + { + inputThread->setLog2Decimation(requestedChannel, settings.m_log2Decim); + qDebug() << "BladeRF2Input::applySettings: set decimation to " << (1<push(msg); + } + } + } + } + + if ((m_settings.m_biasTee != settings.m_biasTee) || force) + { + forwardChangeRxBuddies = true; + m_deviceShared.m_dev->setBiasTeeRx(settings.m_biasTee); + } + + if ((m_settings.m_gainMode != settings.m_gainMode) || force) + { + forwardChangeRxBuddies = true; + + if (dev) + { + int status = bladerf_set_gain_mode(dev, BLADERF_CHANNEL_RX(requestedChannel), (bladerf_gain_mode) settings.m_gainMode); + + if (status < 0) { + qWarning("BladeRF2Input::applySettings: bladerf_set_gain_mode(%d) failed: %s", + settings.m_gainMode, bladerf_strerror(status)); + } else { + qDebug("BladeRF2Input::applySettings: bladerf_set_gain_mode(%d)", settings.m_gainMode); + } + } + } + + if ((m_settings.m_globalGain != settings.m_globalGain) + || ((m_settings.m_gainMode != settings.m_gainMode) && (settings.m_gainMode == BLADERF_GAIN_MANUAL)) || force) + { + forwardChangeRxBuddies = true; + + if (dev) + { +// qDebug("BladeRF2Input::applySettings: channel: %d gain: %d", requestedChannel, settings.m_globalGain); + int status = bladerf_set_gain(dev, BLADERF_CHANNEL_RX(requestedChannel), settings.m_globalGain); + + if (status < 0) { + qWarning("BladeRF2Input::applySettings: bladerf_set_gain(%d) failed: %s", + settings.m_globalGain, bladerf_strerror(status)); + } else { + qDebug("BladeRF2Input::applySettings: bladerf_set_gain(%d)", settings.m_globalGain); + } + } + } + + if (forwardChangeOwnDSP) + { + int sampleRate = settings.m_devSampleRate/(1<handleMessage(*notif); // forward to file sink + m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif); + } + + if (forwardChangeRxBuddies) + { + // send to source buddies + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + std::vector::const_iterator itSource = sourceBuddies.begin(); + + for (; itSource != sourceBuddies.end(); ++itSource) + { + DeviceBladeRF2Shared::MsgReportBuddyChange *report = DeviceBladeRF2Shared::MsgReportBuddyChange::create( + settings.m_centerFrequency, + settings.m_LOppmTenths, + (int) settings.m_fcPos, + settings.m_devSampleRate, + true); + (*itSource)->getSampleSourceInputMessageQueue()->push(report); + } + } + + if (forwardChangeTxBuddies) + { + // send to sink buddies + const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies(); + std::vector::const_iterator itSink = sinkBuddies.begin(); + + for (; itSink != sinkBuddies.end(); ++itSink) + { + DeviceBladeRF2Shared::MsgReportBuddyChange *report = DeviceBladeRF2Shared::MsgReportBuddyChange::create( + settings.m_centerFrequency, + settings.m_LOppmTenths, + (int) settings.m_fcPos, + settings.m_devSampleRate, + true); + (*itSink)->getSampleSinkInputMessageQueue()->push(report); + } + } + + m_settings = settings; + + qDebug() << "BladeRF2Input::applySettings: " + << " m_transverterMode: " << m_settings.m_transverterMode + << " m_transverterDeltaFrequency: " << m_settings.m_transverterDeltaFrequency + << " m_centerFrequency: " << m_settings.m_centerFrequency << " Hz" + << " m_LOppmTenths: " << m_settings.m_LOppmTenths + << " m_bandwidth: " << m_settings.m_bandwidth + << " m_log2Decim: " << m_settings.m_log2Decim + << " m_fcPos: " << m_settings.m_fcPos + << " m_devSampleRate: " << m_settings.m_devSampleRate + << " m_globalGain: " << m_settings.m_globalGain + << " m_gainMode: " << m_settings.m_gainMode + << " m_dcBlock: " << m_settings.m_dcBlock + << " m_iqCorrection: " << m_settings.m_iqCorrection + << " m_biasTee: " << m_settings.m_biasTee; + + return true; +} + +int BladeRF2Input::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setBladeRf2InputSettings(new SWGSDRangel::SWGBladeRF2InputSettings()); + response.getBladeRf2InputSettings()->init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int BladeRF2Input::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage __attribute__((unused))) +{ + BladeRF2InputSettings settings = m_settings; + + if (deviceSettingsKeys.contains("centerFrequency")) { + settings.m_centerFrequency = response.getBladeRf2InputSettings()->getCenterFrequency(); + } + if (deviceSettingsKeys.contains("LOppmTenths")) { + settings.m_LOppmTenths = response.getBladeRf2InputSettings()->getLOppmTenths(); + } + if (deviceSettingsKeys.contains("devSampleRate")) { + settings.m_devSampleRate = response.getBladeRf2InputSettings()->getDevSampleRate(); + } + if (deviceSettingsKeys.contains("bandwidth")) { + settings.m_bandwidth = response.getBladeRf2InputSettings()->getBandwidth(); + } + if (deviceSettingsKeys.contains("log2Decim")) { + settings.m_log2Decim = response.getBladeRf2InputSettings()->getLog2Decim(); + } + if (deviceSettingsKeys.contains("fcPos")) { + settings.m_fcPos = static_cast(response.getBladeRf2InputSettings()->getFcPos()); + } + if (deviceSettingsKeys.contains("dcBlock")) { + settings.m_dcBlock = response.getBladeRf2InputSettings()->getDcBlock() != 0; + } + if (deviceSettingsKeys.contains("iqCorrection")) { + settings.m_iqCorrection = response.getBladeRf2InputSettings()->getIqCorrection() != 0; + } + if (deviceSettingsKeys.contains("biasTee")) { + settings.m_biasTee = response.getBladeRf2InputSettings()->getBiasTee() != 0; + } + if (deviceSettingsKeys.contains("gainMode")) { + settings.m_gainMode = response.getBladeRf2InputSettings()->getGainMode(); + } + if (deviceSettingsKeys.contains("globalGain")) { + settings.m_globalGain = response.getBladeRf2InputSettings()->getGlobalGain(); + } + if (deviceSettingsKeys.contains("transverterDeltaFrequency")) { + settings.m_transverterDeltaFrequency = response.getBladeRf2InputSettings()->getTransverterDeltaFrequency(); + } + if (deviceSettingsKeys.contains("transverterMode")) { + settings.m_transverterMode = response.getBladeRf2InputSettings()->getTransverterMode() != 0; + } + if (deviceSettingsKeys.contains("fileRecordName")) { + settings.m_fileRecordName = *response.getBladeRf1InputSettings()->getFileRecordName(); + } + + MsgConfigureBladeRF2 *msg = MsgConfigureBladeRF2::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureBladeRF2 *msgToGUI = MsgConfigureBladeRF2::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +int BladeRF2Input::webapiReportGet(SWGSDRangel::SWGDeviceReport& response, QString& errorMessage __attribute__((unused))) +{ + response.setBladeRf2InputReport(new SWGSDRangel::SWGBladeRF2InputReport()); + response.getBladeRf2InputReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} + +void BladeRF2Input::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const BladeRF2InputSettings& settings) +{ + response.getBladeRf2InputSettings()->setCenterFrequency(settings.m_centerFrequency); + response.getBladeRf2InputSettings()->setLOppmTenths(settings.m_LOppmTenths); + response.getBladeRf2InputSettings()->setDevSampleRate(settings.m_devSampleRate); + response.getBladeRf2InputSettings()->setBandwidth(settings.m_bandwidth); + response.getBladeRf2InputSettings()->setLog2Decim(settings.m_log2Decim); + response.getBladeRf2InputSettings()->setFcPos((int) settings.m_fcPos); + response.getBladeRf2InputSettings()->setDcBlock(settings.m_dcBlock ? 1 : 0); + response.getBladeRf2InputSettings()->setIqCorrection(settings.m_iqCorrection ? 1 : 0); + response.getBladeRf2InputSettings()->setBiasTee(settings.m_biasTee ? 1 : 0); + response.getBladeRf2InputSettings()->setGainMode(settings.m_gainMode); + response.getBladeRf2InputSettings()->setGlobalGain(settings.m_globalGain); + response.getBladeRf2InputSettings()->setTransverterDeltaFrequency(settings.m_transverterDeltaFrequency); + response.getBladeRf2InputSettings()->setTransverterMode(settings.m_transverterMode ? 1 : 0); + + if (response.getBladeRf2InputSettings()->getFileRecordName()) { + *response.getBladeRf2InputSettings()->getFileRecordName() = settings.m_fileRecordName; + } else { + response.getBladeRf2InputSettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + } +} + +void BladeRF2Input::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + DeviceBladeRF2 *device = m_deviceShared.m_dev; + + if (device) + { + int min, max, step; + uint64_t f_min, f_max; + + device->getBandwidthRangeRx(min, max, step); + + response.getBladeRf2InputReport()->setBandwidthRange(new SWGSDRangel::SWGRange); + response.getBladeRf2InputReport()->getBandwidthRange()->setMin(min); + response.getBladeRf2InputReport()->getBandwidthRange()->setMax(max); + response.getBladeRf2InputReport()->getBandwidthRange()->setStep(step); + + device->getFrequencyRangeRx(f_min, f_max, step); + + response.getBladeRf2InputReport()->setFrequencyRange(new SWGSDRangel::SWGFrequencyRange); + response.getBladeRf2InputReport()->getFrequencyRange()->setMin(f_min); + response.getBladeRf2InputReport()->getFrequencyRange()->setMax(f_max); + response.getBladeRf2InputReport()->getFrequencyRange()->setStep(step); + + device->getGlobalGainRangeRx(min, max, step); + + response.getBladeRf2InputReport()->setGlobalGainRange(new SWGSDRangel::SWGRange); + response.getBladeRf2InputReport()->getGlobalGainRange()->setMin(min); + response.getBladeRf2InputReport()->getGlobalGainRange()->setMax(max); + response.getBladeRf2InputReport()->getGlobalGainRange()->setStep(step); + + device->getSampleRateRangeRx(min, max, step); + + response.getBladeRf2InputReport()->setSampleRateRange(new SWGSDRangel::SWGRange); + response.getBladeRf2InputReport()->getSampleRateRange()->setMin(min); + response.getBladeRf2InputReport()->getSampleRateRange()->setMax(max); + response.getBladeRf2InputReport()->getSampleRateRange()->setStep(step); + + response.getBladeRf2InputReport()->setGainModes(new QList); + + const std::vector& modes = getGainModes(); + std::vector::const_iterator it = modes.begin(); + + for (; it != modes.end(); ++it) + { + response.getBladeRf2InputReport()->getGainModes()->append(new SWGSDRangel::SWGNamedEnum); + response.getBladeRf2InputReport()->getGainModes()->back()->setName(new QString(it->m_name)); + response.getBladeRf2InputReport()->getGainModes()->back()->setValue(it->m_value); + } + } +} + +int BladeRF2Input::webapiRunGet( + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage __attribute__((unused))) +{ + m_deviceAPI->getDeviceEngineStateStr(*response.getState()); + return 200; +} + +int BladeRF2Input::webapiRun( + bool run, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage __attribute__((unused))) +{ + m_deviceAPI->getDeviceEngineStateStr(*response.getState()); + MsgStartStop *message = MsgStartStop::create(run); + m_inputMessageQueue.push(message); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgStartStop *msgToGUI = MsgStartStop::create(run); + m_guiMessageQueue->push(msgToGUI); + } + + return 200; +} diff --git a/plugins/samplesource/bladerf2input/bladerf2input.h b/plugins/samplesource/bladerf2input/bladerf2input.h new file mode 100644 index 000000000..4b9a79561 --- /dev/null +++ b/plugins/samplesource/bladerf2input/bladerf2input.h @@ -0,0 +1,200 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUT_H_ +#define PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUT_H_ + +#include +#include +#include + +#include "dsp/devicesamplesource.h" +#include "bladerf2/devicebladerf2shared.h" +#include "bladerf2inputsettings.h" + +class DeviceSourceAPI; +class BladeRF2InputThread; +class FileRecord; +struct bladerf_gain_modes; +struct bladerf; + +class BladeRF2Input : public DeviceSampleSource +{ +public: + class MsgConfigureBladeRF2 : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const BladeRF2InputSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureBladeRF2* create(const BladeRF2InputSettings& settings, bool force) + { + return new MsgConfigureBladeRF2(settings, force); + } + + private: + BladeRF2InputSettings m_settings; + bool m_force; + + MsgConfigureBladeRF2(const BladeRF2InputSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgFileRecord : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getStartStop() const { return m_startStop; } + + static MsgFileRecord* create(bool startStop) { + return new MsgFileRecord(startStop); + } + + protected: + bool m_startStop; + + MsgFileRecord(bool startStop) : + Message(), + m_startStop(startStop) + { } + }; + + class MsgStartStop : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getStartStop() const { return m_startStop; } + + static MsgStartStop* create(bool startStop) { + return new MsgStartStop(startStop); + } + + protected: + bool m_startStop; + + MsgStartStop(bool startStop) : + Message(), + m_startStop(startStop) + { } + }; + + class MsgReportGainRange : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getMin() const { return m_min; } + int getMax() const { return m_max; } + int getStep() const { return m_step; } + + static MsgReportGainRange* create(int min, int max, int step) { + return new MsgReportGainRange(min, max, step); + } + + protected: + int m_min; + int m_max; + int m_step; + + MsgReportGainRange(int min, int max, int step) : + Message(), + m_min(min), + m_max(max), + m_step(step) + {} + }; + + struct GainMode + { + QString m_name; + int m_value; + }; + + BladeRF2Input(DeviceSourceAPI *deviceAPI); + virtual ~BladeRF2Input(); + virtual void destroy(); + + virtual void init(); + virtual bool start(); + virtual void stop(); + BladeRF2InputThread *getThread() { return m_thread; } + void setThread(BladeRF2InputThread *thread) { m_thread = thread; } + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual void setMessageQueueToGUI(MessageQueue *queue) { m_guiMessageQueue = queue; } + virtual const QString& getDeviceDescription() const; + virtual int getSampleRate() const; + virtual quint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + + void getFrequencyRange(uint64_t& min, uint64_t& max, int& step); + void getSampleRateRange(int& min, int& max, int& step); + void getBandwidthRange(int& min, int& max, int& step); + void getGlobalGainRange(int& min, int& max, int& step); + const std::vector& getGainModes() { return m_gainModes; } + + virtual bool handleMessage(const Message& message); + + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + + virtual int webapiRunGet( + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage); + + virtual int webapiRun( + bool run, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage); + +private: + DeviceSourceAPI *m_deviceAPI; + QMutex m_mutex; + BladeRF2InputSettings m_settings; + QString m_deviceDescription; + bool m_running; + DeviceBladeRF2Shared m_deviceShared; + BladeRF2InputThread *m_thread; + FileRecord *m_fileSink; //!< File sink to record device I/Q output + std::vector m_gainModes; + + bool openDevice(); + void closeDevice(); + BladeRF2InputThread *findThread(); + void moveThreadToBuddy(); + bool applySettings(const BladeRF2InputSettings& settings, bool force = false); + bool setDeviceCenterFrequency(struct bladerf *dev, int requestedChannel, quint64 freq_hz, int loPpmTenths); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const BladeRF2InputSettings& settings); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); +}; + +#endif /* PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUT_H_ */ diff --git a/plugins/samplesource/bladerf2input/bladerf2input.pro b/plugins/samplesource/bladerf2input/bladerf2input.pro new file mode 100644 index 000000000..e474a41ca --- /dev/null +++ b/plugins/samplesource/bladerf2input/bladerf2input.pro @@ -0,0 +1,53 @@ +#-------------------------------------------- +# +# Pro file for Windows builds with Qt Creator +# +#-------------------------------------------- + +TEMPLATE = lib +CONFIG += plugin + +QT += core gui widgets multimedia opengl + +TARGET = inputbladerf2 + +DEFINES += USE_SSE2=1 +QMAKE_CXXFLAGS += -msse2 +DEFINES += USE_SSE4_1=1 +QMAKE_CXXFLAGS += -msse4.1 +QMAKE_CXXFLAGS += -std=c++11 + +CONFIG(MINGW32):LIBBLADERF = "C:\Programs\bladeRF" +CONFIG(MINGW64):LIBBLADERF = "C:\Programs\bladeRF" +INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports +INCLUDEPATH += ../../../sdrbase +INCLUDEPATH += ../../../sdrgui +INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client +INCLUDEPATH += ../../../devices +INCLUDEPATH += $$LIBBLADERF/include + +CONFIG(Release):build_subdir = release +CONFIG(Debug):build_subdir = debug + +SOURCES += bladerf2inputgui.cpp\ + bladerf2input.cpp\ + bladerf2inputplugin.cpp\ + bladerf2inputsettings.cpp\ + bladerf2inputthread.cpp + +HEADERS += bladerf2inputgui.h\ + bladerf2input.h\ + bladerf2inputplugin.h\ + bladerf2inputsettings.h\ + bladerf2inputthread.h + +FORMS += bladerf2inputgui.ui + +LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase +LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui +LIBS += -L../../../swagger/$${build_subdir} -lswagger +LIBS += -L$$LIBBLADERF/lib -lbladeRF +LIBS += -L../../../devices/$${build_subdir} -ldevices + +RESOURCES = ../../../sdrgui/resources/res.qrc diff --git a/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp b/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp new file mode 100644 index 000000000..02e78775a --- /dev/null +++ b/plugins/samplesource/bladerf2input/bladerf2inputgui.cpp @@ -0,0 +1,459 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include + +#include "ui_bladerf2inputgui.h" +#include "gui/colormapper.h" +#include "gui/glspectrum.h" +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "device/devicesourceapi.h" +#include "device/deviceuiset.h" + +#include "bladerf2inputgui.h" + +BladeRF2InputGui::BladeRF2InputGui(DeviceUISet *deviceUISet, QWidget* parent) : + QWidget(parent), + ui(new Ui::Bladerf2InputGui), + m_deviceUISet(deviceUISet), + m_forceSettings(true), + m_doApplySettings(true), + m_settings(), + m_sampleSource(0), + m_sampleRate(0), + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted) +{ + m_sampleSource = (BladeRF2Input*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); + int max, min, step; + uint64_t f_min, f_max; + + ui->setupUi(this); + + m_sampleSource->getFrequencyRange(f_min, f_max, step); + ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); + ui->centerFrequency->setValueRange(7, f_min/1000, f_max/1000); + + m_sampleSource->getSampleRateRange(min, max, step); + ui->sampleRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow)); + ui->sampleRate->setValueRange(8, min, max); + + m_sampleSource->getBandwidthRange(min, max, step); + ui->bandwidth->setColorMapper(ColorMapper(ColorMapper::GrayYellow)); + ui->bandwidth->setValueRange(5, min/1000, max/1000); + + const std::vector& modes = m_sampleSource->getGainModes(); + std::vector::const_iterator it = modes.begin(); + + ui->gainMode->blockSignals(true); + + for (; it != modes.end(); ++it) { + ui->gainMode->addItem(it->m_name); + } + + ui->gainMode->blockSignals(false); + + m_sampleSource->getGlobalGainRange(min, max, step); + ui->gain->setMinimum(min); + ui->gain->setMaximum(max); + ui->gain->setPageStep(step); + ui->gain->setSingleStep(step); + + ui->label_decim->setText(QString::fromUtf8("D\u2193")); + + connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); + connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); + m_statusTimer.start(500); + + displaySettings(); + + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); + + sendSettings(); +} + +BladeRF2InputGui::~BladeRF2InputGui() +{ + delete ui; +} + +void BladeRF2InputGui::destroy() +{ + delete this; +} + +void BladeRF2InputGui::setName(const QString& name) +{ + setObjectName(name); +} + +QString BladeRF2InputGui::getName() const +{ + return objectName(); +} + +void BladeRF2InputGui::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + sendSettings(); +} + +qint64 BladeRF2InputGui::getCenterFrequency() const +{ + return m_settings.m_centerFrequency; +} + +void BladeRF2InputGui::setCenterFrequency(qint64 centerFrequency) +{ + m_settings.m_centerFrequency = centerFrequency; + displaySettings(); + sendSettings(); +} + +QByteArray BladeRF2InputGui::serialize() const +{ + return m_settings.serialize(); +} + +bool BladeRF2InputGui::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) { + displaySettings(); + m_forceSettings = true; + sendSettings(); + return true; + } else { + resetToDefaults(); + return false; + } +} + +void BladeRF2InputGui::updateFrequencyLimits() +{ + // values in kHz + uint64_t f_min, f_max; + int step; + qint64 deltaFrequency = m_settings.m_transverterMode ? m_settings.m_transverterDeltaFrequency/1000 : 0; + m_sampleSource->getFrequencyRange(f_min, f_max, step); + qint64 minLimit = f_min/1000 + deltaFrequency; + qint64 maxLimit = f_max/1000 + deltaFrequency; + + minLimit = minLimit < 0 ? 0 : minLimit > 9999999 ? 9999999 : minLimit; + maxLimit = maxLimit < 0 ? 0 : maxLimit > 9999999 ? 9999999 : maxLimit; + + qDebug("BladeRF2OutputGui::updateFrequencyLimits: delta: %lld min: %lld max: %lld", deltaFrequency, minLimit, maxLimit); + + ui->centerFrequency->setValueRange(7, minLimit, maxLimit); +} + +void BladeRF2InputGui::setCenterFrequencySetting(uint64_t kHzValue) +{ + int64_t centerFrequency = kHzValue*1000; + + m_settings.m_centerFrequency = centerFrequency < 0 ? 0 : (uint64_t) centerFrequency; + ui->centerFrequency->setToolTip(QString("Main center frequency in kHz (LO: %1 kHz)").arg(centerFrequency/1000)); +} + +bool BladeRF2InputGui::handleMessage(const Message& message) +{ + if (BladeRF2Input::MsgConfigureBladeRF2::match(message)) + { + const BladeRF2Input::MsgConfigureBladeRF2& cfg = (BladeRF2Input::MsgConfigureBladeRF2&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + int min, max, step; + m_sampleSource->getGlobalGainRange(min, max, step); + ui->gain->setMinimum(min); + ui->gain->setMaximum(max); + ui->gain->setPageStep(step); + ui->gain->setSingleStep(step); + displaySettings(); + blockApplySettings(false); + + return true; + } + else if (BladeRF2Input::MsgReportGainRange::match(message)) + { + const BladeRF2Input::MsgReportGainRange& cfg = (BladeRF2Input::MsgReportGainRange&) message; + ui->gain->setMinimum(cfg.getMin()); + ui->gain->setMaximum(cfg.getMax()); + ui->gain->setSingleStep(cfg.getStep()); + ui->gain->setPageStep(cfg.getStep()); + + return true; + } + else if (BladeRF2Input::MsgStartStop::match(message)) + { + BladeRF2Input::MsgStartStop& notif = (BladeRF2Input::MsgStartStop&) message; + blockApplySettings(true); + ui->startStop->setChecked(notif.getStartStop()); + blockApplySettings(false); + + return true; + } + else + { + return false; + } +} + +void BladeRF2InputGui::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + qDebug("BladeRF2InputGui::handleInputMessages: message: %s", message->getIdentifier()); + + if (DSPSignalNotification::match(*message)) + { + DSPSignalNotification* notif = (DSPSignalNotification*) message; + m_sampleRate = notif->getSampleRate(); + m_deviceCenterFrequency = notif->getCenterFrequency(); + qDebug("BladeRF2InputGui::handleInputMessages: DSPSignalNotification: SampleRate:%d, CenterFrequency:%llu", notif->getSampleRate(), notif->getCenterFrequency()); + updateSampleRateAndFrequency(); + + delete message; + } + else + { + if (handleMessage(*message)) + { + delete message; + } + } + } +} + +void BladeRF2InputGui::updateSampleRateAndFrequency() +{ + m_deviceUISet->getSpectrum()->setSampleRate(m_sampleRate); + m_deviceUISet->getSpectrum()->setCenterFrequency(m_deviceCenterFrequency); + ui->deviceRateLabel->setText(tr("%1k").arg(QString::number(m_sampleRate / 1000.0f, 'g', 5))); +} + +void BladeRF2InputGui::displaySettings() +{ + blockApplySettings(true); + + ui->centerFrequency->setValue(m_settings.m_centerFrequency / 1000); + ui->LOppm->setValue(m_settings.m_LOppmTenths); + ui->LOppmText->setText(QString("%1").arg(QString::number(m_settings.m_LOppmTenths/10.0, 'f', 1))); + ui->sampleRate->setValue(m_settings.m_devSampleRate); + ui->bandwidth->setValue(m_settings.m_bandwidth / 1000); + + ui->dcOffset->setChecked(m_settings.m_dcBlock); + ui->iqImbalance->setChecked(m_settings.m_iqCorrection); + + ui->decim->setCurrentIndex(m_settings.m_log2Decim); + ui->fcPos->setCurrentIndex((int) m_settings.m_fcPos); + + ui->gainText->setText(tr("%1 dB").arg(m_settings.m_globalGain)); + ui->gain->setValue(m_settings.m_globalGain); + + if (m_settings.m_gainMode == BLADERF_GAIN_MANUAL) { + ui->gain->setEnabled(true); + } else { + ui->gain->setEnabled(false); + } + + blockApplySettings(false); +} + +void BladeRF2InputGui::sendSettings() +{ + if(!m_updateTimer.isActive()) + m_updateTimer.start(100); +} + +void BladeRF2InputGui::on_centerFrequency_changed(quint64 value) +{ + m_settings.m_centerFrequency = value * 1000; + sendSettings(); +} + +void BladeRF2InputGui::on_LOppm_valueChanged(int value) +{ + ui->LOppmText->setText(QString("%1").arg(QString::number(value/10.0, 'f', 1))); + m_settings.m_LOppmTenths = value; + sendSettings(); +} + +void BladeRF2InputGui::on_sampleRate_changed(quint64 value) +{ + m_settings.m_devSampleRate = value; + sendSettings(); +} + +void BladeRF2InputGui::on_dcOffset_toggled(bool checked) +{ + m_settings.m_dcBlock = checked; + sendSettings(); +} + +void BladeRF2InputGui::on_iqImbalance_toggled(bool checked) +{ + m_settings.m_iqCorrection = checked; + sendSettings(); +} + +void BladeRF2InputGui::on_biasTee_toggled(bool checked) +{ + m_settings.m_biasTee = checked; + sendSettings(); +} + +void BladeRF2InputGui::on_bandwidth_changed(quint64 value) +{ + m_settings.m_bandwidth = value * 1000; + sendSettings(); +} + +void BladeRF2InputGui::on_decim_currentIndexChanged(int index) +{ + if ((index <0) || (index > 6)) + return; + m_settings.m_log2Decim = index; + sendSettings(); +} + +void BladeRF2InputGui::on_fcPos_currentIndexChanged(int index) +{ + if (index == 0) { + m_settings.m_fcPos = BladeRF2InputSettings::FC_POS_INFRA; + sendSettings(); + } else if (index == 1) { + m_settings.m_fcPos = BladeRF2InputSettings::FC_POS_SUPRA; + sendSettings(); + } else if (index == 2) { + m_settings.m_fcPos = BladeRF2InputSettings::FC_POS_CENTER; + sendSettings(); + } +} + +void BladeRF2InputGui::on_gainMode_currentIndexChanged(int index) +{ + const std::vector& modes = m_sampleSource->getGainModes(); + unsigned int uindex = index < 0 ? 0 : (unsigned int) index; + + if (uindex < modes.size()) + { + BladeRF2Input::GainMode mode = modes[index]; + + if (m_settings.m_gainMode != mode.m_value) + { + if (mode.m_value == BLADERF_GAIN_MANUAL) + { + m_settings.m_globalGain = ui->gain->value(); + ui->gain->setEnabled(true); + } else { + ui->gain->setEnabled(false); + } + } + + m_settings.m_gainMode = mode.m_value; + sendSettings(); + } +} + +void BladeRF2InputGui::on_gain_valueChanged(int value) +{ + ui->gainText->setText(tr("%1 dB").arg(value)); + m_settings.m_globalGain = value; + sendSettings(); +} + +void BladeRF2InputGui::on_transverter_clicked() +{ + m_settings.m_transverterMode = ui->transverter->getDeltaFrequencyAcive(); + m_settings.m_transverterDeltaFrequency = ui->transverter->getDeltaFrequency(); + qDebug("BladeRF2InputGui::on_transverter_clicked: %lld Hz %s", m_settings.m_transverterDeltaFrequency, m_settings.m_transverterMode ? "on" : "off"); + updateFrequencyLimits(); + setCenterFrequencySetting(ui->centerFrequency->getValueNew()); + sendSettings(); +} + +void BladeRF2InputGui::on_startStop_toggled(bool checked) +{ + if (m_doApplySettings) + { + BladeRF2Input::MsgStartStop *message = BladeRF2Input::MsgStartStop::create(checked); + m_sampleSource->getInputMessageQueue()->push(message); + } +} + +void BladeRF2InputGui::on_record_toggled(bool checked) +{ + if (checked) { + ui->record->setStyleSheet("QToolButton { background-color : red; }"); + } else { + ui->record->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + } + + BladeRF2Input::MsgFileRecord* message = BladeRF2Input::MsgFileRecord::create(checked); + m_sampleSource->getInputMessageQueue()->push(message); +} + +void BladeRF2InputGui::updateHardware() +{ + if (m_doApplySettings) + { + qDebug() << "BladeRF2InputGui::updateHardware"; + BladeRF2Input::MsgConfigureBladeRF2* message = BladeRF2Input::MsgConfigureBladeRF2::create(m_settings, m_forceSettings); + m_sampleSource->getInputMessageQueue()->push(message); + m_forceSettings = false; + m_updateTimer.stop(); + } +} + +void BladeRF2InputGui::blockApplySettings(bool block) +{ + m_doApplySettings = !block; +} + +void BladeRF2InputGui::updateStatus() +{ + int state = m_deviceUISet->m_deviceSourceAPI->state(); + + if(m_lastEngineState != state) + { + switch(state) + { + case DSPDeviceSourceEngine::StNotStarted: + ui->startStop->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + break; + case DSPDeviceSourceEngine::StIdle: + ui->startStop->setStyleSheet("QToolButton { background-color : blue; }"); + break; + case DSPDeviceSourceEngine::StRunning: + ui->startStop->setStyleSheet("QToolButton { background-color : green; }"); + break; + case DSPDeviceSourceEngine::StError: + ui->startStop->setStyleSheet("QToolButton { background-color : red; }"); + QMessageBox::information(this, tr("Message"), m_deviceUISet->m_deviceSourceAPI->errorMessage()); + break; + default: + break; + } + + m_lastEngineState = state; + } +} diff --git a/plugins/samplesource/bladerf2input/bladerf2inputgui.h b/plugins/samplesource/bladerf2input/bladerf2inputgui.h new file mode 100644 index 000000000..665670f3d --- /dev/null +++ b/plugins/samplesource/bladerf2input/bladerf2inputgui.h @@ -0,0 +1,97 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUTGUI_H_ +#define PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUTGUI_H_ + +#include +#include +#include + +#include "util/messagequeue.h" + +#include "bladerf2input.h" + +class DeviceUISet; + +namespace Ui { + class Bladerf2InputGui; +} + +class BladeRF2InputGui : public QWidget, public PluginInstanceGUI { + Q_OBJECT + +public: + explicit BladeRF2InputGui(DeviceUISet *deviceUISet, QWidget* parent = 0); + virtual ~BladeRF2InputGui(); + virtual void destroy(); + + void setName(const QString& name); + QString getName() const; + + virtual void resetToDefaults(); + virtual qint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + virtual bool handleMessage(const Message& message); + +private: + Ui::Bladerf2InputGui* ui; + + DeviceUISet* m_deviceUISet; + bool m_forceSettings; + bool m_doApplySettings; + BladeRF2InputSettings m_settings; + QTimer m_updateTimer; + QTimer m_statusTimer; + std::vector m_gains; + BladeRF2Input* m_sampleSource; + int m_sampleRate; + quint64 m_deviceCenterFrequency; //!< Center frequency in device + int m_lastEngineState; + MessageQueue m_inputMessageQueue; + + void displaySettings(); + void sendSettings(); + void updateSampleRateAndFrequency(); + void updateFrequencyLimits(); + void setCenterFrequencySetting(uint64_t kHzValue); + void blockApplySettings(bool block); + +private slots: + void handleInputMessages(); + void on_centerFrequency_changed(quint64 value); + void on_LOppm_valueChanged(int value); + void on_sampleRate_changed(quint64 value); + void on_dcOffset_toggled(bool checked); + void on_iqImbalance_toggled(bool checked); + void on_biasTee_toggled(bool checked); + void on_bandwidth_changed(quint64 value); + void on_decim_currentIndexChanged(int index); + void on_fcPos_currentIndexChanged(int index); + void on_gainMode_currentIndexChanged(int index); + void on_gain_valueChanged(int value); + void on_transverter_clicked(); + void on_startStop_toggled(bool checked); + void on_record_toggled(bool checked); + void updateHardware(); + void updateStatus(); + +}; + +#endif /* PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUTGUI_H_ */ diff --git a/plugins/samplesource/bladerf2input/bladerf2inputgui.ui b/plugins/samplesource/bladerf2input/bladerf2inputgui.ui new file mode 100644 index 000000000..aa9c8cbfa --- /dev/null +++ b/plugins/samplesource/bladerf2input/bladerf2inputgui.ui @@ -0,0 +1,600 @@ + + + Bladerf2InputGui + + + + 0 + 0 + 350 + 220 + + + + + 0 + 0 + + + + + 350 + 220 + + + + + Liberation Sans + 9 + + + + BladeRF2 + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + 4 + + + + + + + + + start/stop acquisition + + + + + + + :/play.png + :/stop.png:/play.png + + + + + + + Toggle record I/Q samples from device + + + + + + + :/record_off.png + :/record_on.png:/record_off.png + + + + + + + + + + + I/Q sample rate kS/s + + + 00000k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 20 + + + + PointingHandCursor + + + Qt::StrongFocus + + + Tuner center frequency in kHz + + + + + + + kHz + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + + + + LO ppm + + + + + + + Local Oscillator ppm correction + + + -20 + + + 20 + + + 1 + + + Qt::Horizontal + + + + + + + + 26 + 0 + + + + -0.0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Automatic IQ imbalance correction + + + IQ + + + + + + + Automatic DC offset removal + + + DC + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Auto + + + + + + + kHz + + + + + + + + 0 + 0 + + + + BW + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 12 + + + + PointingHandCursor + + + RF bandwidth + + + + + + + + 24 + 24 + + + + Transverter frequency translation dialog + + + X + + + + + + + + + Qt::Horizontal + + + + + + + 2 + + + 2 + + + + + + 0 + 0 + + + + SR + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 12 + + + + PointingHandCursor + + + Device sample rate + + + + + + + S/s + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Fp + + + + + + + Relative position of device center frequency + + + + Inf + + + + + Sup + + + + + Cen + + + + + + + + D + + + + + + + + 50 + 16777215 + + + + Decimation factor + + + 0 + + + + 1 + + + + + 2 + + + + + 4 + + + + + 8 + + + + + 16 + + + + + 32 + + + + + 64 + + + + + + + + + + 3 + + + + + Gain value + + + Qt::Horizontal + + + + + + + Gain mode + + + + + + + Gain + + + + + + + + 45 + 0 + + + + 000 dB + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Bias Tee + + + BT + + + + + + + + + Qt::Horizontal + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + ValueDial + QWidget +
gui/valuedial.h
+ 1 +
+ + ButtonSwitch + QToolButton +
gui/buttonswitch.h
+
+ + TransverterButton + QPushButton +
gui/transverterbutton.h
+
+
+ + + + +
diff --git a/plugins/samplesource/bladerf2input/bladerf2inputplugin.cpp b/plugins/samplesource/bladerf2input/bladerf2inputplugin.cpp new file mode 100644 index 000000000..2008e9bd7 --- /dev/null +++ b/plugins/samplesource/bladerf2input/bladerf2inputplugin.cpp @@ -0,0 +1,157 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "bladerf2inputplugin.h" + +#include +#include +#include "plugin/pluginapi.h" +#include "util/simpleserializer.h" +#include + +#ifdef SERVER_MODE +#include "bladerf2input.h" +#else +#include "bladerf2inputgui.h" +#endif + +const PluginDescriptor Blderf2InputPlugin::m_pluginDescriptor = { + QString("BladeRF2 Input"), + QString("4.3.0"), + QString("(c) Edouard Griffiths, F4EXB"), + QString("https://github.com/f4exb/sdrangel"), + true, + QString("https://github.com/f4exb/sdrangel") +}; + +const QString Blderf2InputPlugin::m_hardwareID = "BladeRF2"; +const QString Blderf2InputPlugin::m_deviceTypeID = BLADERF2INPUT_DEVICE_TYPE_ID; + +Blderf2InputPlugin::Blderf2InputPlugin(QObject* parent) : + QObject(parent) +{ +} + +const PluginDescriptor& Blderf2InputPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void Blderf2InputPlugin::initPlugin(PluginAPI* pluginAPI) +{ + pluginAPI->registerSampleSource(m_deviceTypeID, this); +} + +PluginInterface::SamplingDevices Blderf2InputPlugin::enumSampleSources() +{ + SamplingDevices result; + struct bladerf_devinfo *devinfo = 0; + + int count = bladerf_get_device_list(&devinfo); + + if (devinfo) + { + for(int i = 0; i < count; i++) + { + struct bladerf *dev; + + int status = bladerf_open_with_devinfo(&dev, &devinfo[i]); + + if (status == BLADERF_ERR_NODEV) + { + qCritical("Blderf2InputPlugin::enumSampleSources: No device at index %d", i); + continue; + } + else if (status != 0) + { + qCritical("Blderf2InputPlugin::enumSampleSources: Failed to open device at index %d", i); + continue; + } + + const char *boardName = bladerf_get_board_name(dev); + + if (strcmp(boardName, "bladerf2") == 0) + { + unsigned int nbRxChannels = bladerf_get_channel_count(dev, BLADERF_RX); + + for (unsigned int j = 0; j < nbRxChannels; j++) + { + qDebug("Blderf2InputPlugin::enumSampleSources: device #%d (%s) channel %u", i, devinfo[i].serial, j); + QString displayedName(QString("BladeRF2[%1:%2] %3").arg(devinfo[i].instance).arg(j).arg(devinfo[i].serial)); + result.append(SamplingDevice(displayedName, + m_hardwareID, + m_deviceTypeID, + QString(devinfo[i].serial), + i, + PluginInterface::SamplingDevice::PhysicalDevice, + true, + nbRxChannels, + j)); + } + } + + bladerf_close(dev); + } + + bladerf_free_device_list(devinfo); // Valgrind memcheck + } + + return result; +} + +#ifdef SERVER_MODE +PluginInstanceGUI* Blderf2InputPlugin::createSampleSourcePluginInstanceGUI( + const QString& sourceId __attribute__((unused)), + QWidget **widget __attribute__((unused)), + DeviceUISet *deviceUISet __attribute__((unused))) +{ + return 0; +} +#else +PluginInstanceGUI* Blderf2InputPlugin::createSampleSourcePluginInstanceGUI( + const QString& sourceId, + QWidget **widget, + DeviceUISet *deviceUISet) +{ + if(sourceId == m_deviceTypeID) + { + BladeRF2InputGui* gui = new BladeRF2InputGui(deviceUISet); + *widget = gui; + return gui; + } + else + { + return 0; + } +} +#endif + +DeviceSampleSource *Blderf2InputPlugin::createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI) +{ + if (sourceId == m_deviceTypeID) + { + BladeRF2Input *input = new BladeRF2Input(deviceAPI); + return input; + } + else + { + return 0; + } +} + + + + diff --git a/plugins/samplesource/bladerf2input/bladerf2inputplugin.h b/plugins/samplesource/bladerf2input/bladerf2inputplugin.h new file mode 100644 index 000000000..8a26150df --- /dev/null +++ b/plugins/samplesource/bladerf2input/bladerf2inputplugin.h @@ -0,0 +1,57 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUTPLUGIN_H_ +#define PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUTPLUGIN_H_ + +#include +#include "plugin/plugininterface.h" + +class PluginAPI; +class DeviceSourceAPI; +class DeviceUISet; + +#define BLADERF2INPUT_DEVICE_TYPE_ID "sdrangel.samplesource.bladerf2input" + +class Blderf2InputPlugin : public QObject, public PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID BLADERF2INPUT_DEVICE_TYPE_ID) + +public: + explicit Blderf2InputPlugin(QObject* parent = 0); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + virtual SamplingDevices enumSampleSources(); + virtual PluginInstanceGUI* createSampleSourcePluginInstanceGUI( + const QString& sourceId, + QWidget **widget, + DeviceUISet *deviceUISet); + virtual DeviceSampleSource* createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI); + + static const QString m_hardwareID; + static const QString m_deviceTypeID; + +private: + static const PluginDescriptor m_pluginDescriptor; +}; + + + + +#endif /* PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUTPLUGIN_H_ */ diff --git a/plugins/samplesource/bladerf2input/bladerf2inputsettings.cpp b/plugins/samplesource/bladerf2input/bladerf2inputsettings.cpp new file mode 100644 index 000000000..21e939ff2 --- /dev/null +++ b/plugins/samplesource/bladerf2input/bladerf2inputsettings.cpp @@ -0,0 +1,101 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "bladerf2inputsettings.h" + +#include "util/simpleserializer.h" + +BladeRF2InputSettings::BladeRF2InputSettings() +{ + resetToDefaults(); +} + +void BladeRF2InputSettings::resetToDefaults() +{ + m_centerFrequency = 435000*1000; + m_LOppmTenths = 0; + m_devSampleRate = 3072000; + m_bandwidth = 1500000; + m_gainMode = 0; + m_globalGain = 0; + m_biasTee = false; + m_log2Decim = 0; + m_fcPos = FC_POS_INFRA; + m_dcBlock = false; + m_iqCorrection = false; + m_transverterMode = false; + m_transverterDeltaFrequency = 0; + m_fileRecordName = ""; +} + +QByteArray BladeRF2InputSettings::serialize() const +{ + SimpleSerializer s(1); + + s.writeS32(1, m_devSampleRate); + s.writeS32(2, m_bandwidth); + s.writeS32(3, m_gainMode); + s.writeS32(4, m_globalGain); + s.writeBool(5, m_biasTee); + s.writeU32(6, m_log2Decim); + s.writeS32(7, (int) m_fcPos); + s.writeBool(8, m_dcBlock); + s.writeBool(9, m_iqCorrection); + s.writeS32(10, m_LOppmTenths); + s.writeBool(11, m_transverterMode); + s.writeS64(12, m_transverterDeltaFrequency); + + return s.final(); +} + +bool BladeRF2InputSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if (!d.isValid()) + { + resetToDefaults(); + return false; + } + + if (d.getVersion() == 1) + { + int intval; + + d.readS32(1, &m_devSampleRate, 3072000); + d.readS32(2, &m_bandwidth); + d.readS32(3, &m_gainMode); + d.readS32(4, &m_globalGain); + d.readBool(5, &m_biasTee); + d.readU32(6, &m_log2Decim); + d.readS32(7, &intval); + m_fcPos = (fcPos_t) intval; + d.readBool(8, &m_dcBlock); + d.readBool(9, &m_iqCorrection); + d.readS32(10, &m_LOppmTenths); + d.readBool(11, &m_transverterMode, false); + d.readS64(12, &m_transverterDeltaFrequency, 0); + + return true; + } + else + { + resetToDefaults(); + return false; + } +} + + diff --git a/plugins/samplesource/bladerf2input/bladerf2inputsettings.h b/plugins/samplesource/bladerf2input/bladerf2inputsettings.h new file mode 100644 index 000000000..722be9e6e --- /dev/null +++ b/plugins/samplesource/bladerf2input/bladerf2inputsettings.h @@ -0,0 +1,54 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUTSETTINGS_H_ +#define PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUTSETTINGS_H_ + +#include +#include + +struct BladeRF2InputSettings { + typedef enum { + FC_POS_INFRA = 0, + FC_POS_SUPRA, + FC_POS_CENTER + } fcPos_t; + + quint64 m_centerFrequency; + qint32 m_LOppmTenths; + qint32 m_devSampleRate; + qint32 m_bandwidth; + int m_gainMode; + int m_globalGain; + bool m_biasTee; + quint32 m_log2Decim; + fcPos_t m_fcPos; + bool m_dcBlock; + bool m_iqCorrection; + bool m_transverterMode; + qint64 m_transverterDeltaFrequency; + QString m_fileRecordName; + + BladeRF2InputSettings(); + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); +}; + + + + +#endif /* PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUTSETTINGS_H_ */ diff --git a/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp b/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp new file mode 100644 index 000000000..1b9ebc63f --- /dev/null +++ b/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp @@ -0,0 +1,298 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "dsp/samplesinkfifo.h" + +#include "bladerf2inputthread.h" + +BladeRF2InputThread::BladeRF2InputThread(struct bladerf* dev, unsigned int nbRxChannels, QObject* parent) : + QThread(parent), + m_running(false), + m_dev(dev), + m_nbChannels(nbRxChannels) +{ + qDebug("BladeRF2InputThread::BladeRF2InputThread"); + m_channels = new Channel[nbRxChannels]; + + for (unsigned int i = 0; i < nbRxChannels; i++) { + m_channels[i].m_convertBuffer.resize(DeviceBladeRF2::blockSize, Sample{0,0}); + } + + m_buf = new qint16[2*DeviceBladeRF2::blockSize*nbRxChannels]; +} + +BladeRF2InputThread::~BladeRF2InputThread() +{ + qDebug("BladeRF2InputThread::~BladeRF2InputThread"); + + if (m_running) { + stopWork(); + } + + delete[] m_buf; + delete[] m_channels; +} + +void BladeRF2InputThread::startWork() +{ + m_startWaitMutex.lock(); + start(); + + while(!m_running) { + m_startWaiter.wait(&m_startWaitMutex, 100); + } + + m_startWaitMutex.unlock(); +} + +void BladeRF2InputThread::stopWork() +{ + m_running = false; + wait(); +} + +void BladeRF2InputThread::run() +{ + int res; + + m_running = true; + m_startWaiter.wakeAll(); + + unsigned int nbFifos = getNbFifos(); + + if ((m_nbChannels > 0) && (nbFifos > 0)) + { + int status; + + if (m_nbChannels > 1) { + status = bladerf_sync_config(m_dev, BLADERF_RX_X2, BLADERF_FORMAT_SC16_Q11, 64, 8192, 32, 10000); + } else { + status = bladerf_sync_config(m_dev, BLADERF_RX_X1, BLADERF_FORMAT_SC16_Q11, 64, 8192, 32, 10000); + } + + if (status < 0) + { + qCritical("BladeRF2InputThread::run: cannot configure streams: %s", bladerf_strerror(status)); + } + else + { + qDebug("BladeRF2InputThread::run: start running loop"); + while (m_running) + { + if (m_nbChannels > 1) { + res = bladerf_sync_rx(m_dev, m_buf, DeviceBladeRF2::blockSize*m_nbChannels, NULL, 10000); + } else { + res = bladerf_sync_rx(m_dev, m_buf, DeviceBladeRF2::blockSize, NULL, 10000); + } + + if (res < 0) + { + qCritical("BladeRF2InputThread::run sync Rx error: %s", bladerf_strerror(res)); + break; + } + + if (m_nbChannels > 1) { + callbackMI(m_buf, DeviceBladeRF2::blockSize); + } else { + callbackSI(m_buf, 2*DeviceBladeRF2::blockSize); + } + } + qDebug("BladeRF2InputThread::run: stop running loop"); + } + } + else + { + qWarning("BladeRF2InputThread::run: no channels or FIFO allocated. Aborting"); + } + + + m_running = false; +} + +unsigned int BladeRF2InputThread::getNbFifos() +{ + unsigned int fifoCount = 0; + + for (unsigned int i = 0; i < m_nbChannels; i++) + { + if (m_channels[i].m_sampleFifo) { + fifoCount++; + } + } + + return fifoCount; +} + +void BladeRF2InputThread::setLog2Decimation(unsigned int channel, unsigned int log2_decim) +{ + if (channel < m_nbChannels) { + m_channels[channel].m_log2Decim = log2_decim; + } +} + +unsigned int BladeRF2InputThread::getLog2Decimation(unsigned int channel) const +{ + if (channel < m_nbChannels) { + return m_channels[channel].m_log2Decim; + } else { + return 0; + } +} + +void BladeRF2InputThread::setFcPos(unsigned int channel, int fcPos) +{ + if (channel < m_nbChannels) { + m_channels[channel].m_fcPos = fcPos; + } +} + +int BladeRF2InputThread::getFcPos(unsigned int channel) const +{ + if (channel < m_nbChannels) { + return m_channels[channel].m_fcPos; + } else { + return 0; + } +} + +void BladeRF2InputThread::setFifo(unsigned int channel, SampleSinkFifo *sampleFifo) +{ + if (channel < m_nbChannels) { + m_channels[channel].m_sampleFifo = sampleFifo; + } +} + +SampleSinkFifo *BladeRF2InputThread::getFifo(unsigned int channel) +{ + if (channel < m_nbChannels) { + return m_channels[channel].m_sampleFifo; + } else { + return 0; + } +} + +void BladeRF2InputThread::callbackMI(const qint16* buf, qint32 samplesPerChannel) +{ + // TODO: write a set of decimators that can take interleaved samples in input directly + int status = bladerf_deinterleave_stream_buffer(BLADERF_RX_X2, BLADERF_FORMAT_SC16_Q11 , samplesPerChannel*m_nbChannels, (void *) buf); + + if (status < 0) + { + qCritical("BladeRF2InputThread::callbackMI: cannot de-interleave buffer: %s", bladerf_strerror(status)); + return; + } + + for (unsigned int channel = 0; channel < m_nbChannels; channel++) + { + if (m_channels[channel].m_sampleFifo) { + callbackSI(&buf[2*samplesPerChannel*channel], 2*samplesPerChannel, channel); + } + } +} + +void BladeRF2InputThread::callbackSI(const qint16* buf, qint32 len, unsigned int channel) +{ + SampleVector::iterator it = m_channels[channel].m_convertBuffer.begin(); + + if (m_channels[channel].m_log2Decim == 0) + { + m_channels[channel].m_decimators.decimate1(&it, buf, len); + } + else + { + if (m_channels[channel].m_fcPos == 0) // Infra + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimators.decimate2_inf(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimators.decimate4_inf(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimators.decimate8_inf(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimators.decimate16_inf(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimators.decimate32_inf(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimators.decimate64_inf(&it, buf, len); + break; + default: + break; + } + } + else if (m_channels[channel].m_fcPos == 1) // Supra + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimators.decimate2_sup(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimators.decimate4_sup(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimators.decimate8_sup(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimators.decimate16_sup(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimators.decimate32_sup(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimators.decimate64_sup(&it, buf, len); + break; + default: + break; + } + } + else if (m_channels[channel].m_fcPos == 2) // Center + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimators.decimate2_cen(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimators.decimate4_cen(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimators.decimate8_cen(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimators.decimate16_cen(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimators.decimate32_cen(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimators.decimate64_cen(&it, buf, len); + break; + default: + break; + } + } + } + + m_channels[channel].m_sampleFifo->write(m_channels[channel].m_convertBuffer.begin(), it); +} + diff --git a/plugins/samplesource/bladerf2input/bladerf2inputthread.h b/plugins/samplesource/bladerf2input/bladerf2inputthread.h new file mode 100644 index 000000000..ad5886620 --- /dev/null +++ b/plugins/samplesource/bladerf2input/bladerf2inputthread.h @@ -0,0 +1,90 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUTTHREAD_H_ +#define PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUTTHREAD_H_ + +// BladerRF2 is a SISO/MIMO device with a single stream supporting one or two Rx +// Therefore only one thread can be allocated for the Rx side +// All FIFOs must be registered before calling startWork() else SISO/MIMO switch will not work properly +// with unpredicatable results + +#include +#include +#include + +#include + +#include "bladerf2/devicebladerf2shared.h" +#include "dsp/decimators.h" + +class SampleSinkFifo; + +class BladeRF2InputThread : public QThread { + Q_OBJECT + +public: + BladeRF2InputThread(struct bladerf* dev, unsigned int nbRxChannels, QObject* parent = NULL); + ~BladeRF2InputThread(); + + void startWork(); + void stopWork(); + bool isRunning() const { return m_running; } + unsigned int getNbChannels() const { return m_nbChannels; } + void setLog2Decimation(unsigned int channel, unsigned int log2_decim); + unsigned int getLog2Decimation(unsigned int channel) const; + void setFcPos(unsigned int channel, int fcPos); + int getFcPos(unsigned int channel) const; + void setFifo(unsigned int channel, SampleSinkFifo *sampleFifo); + SampleSinkFifo *getFifo(unsigned int channel); + +private: + struct Channel + { + SampleVector m_convertBuffer; + SampleSinkFifo* m_sampleFifo; + unsigned int m_log2Decim; + int m_fcPos; + Decimators m_decimators; + + Channel() : + m_sampleFifo(0), + m_log2Decim(0), + m_fcPos(0) + {} + + ~Channel() + {} + }; + + QMutex m_startWaitMutex; + QWaitCondition m_startWaiter; + bool m_running; + struct bladerf* m_dev; + + Channel *m_channels; //!< Array of channels dynamically allocated for the given number of Rx channels + qint16 *m_buf; //!< Full buffer for SISO or MIMO operation + unsigned int m_nbChannels; + + void run(); + unsigned int getNbFifos(); + void callbackSI(const qint16* buf, qint32 len, unsigned int channel = 0); + void callbackMI(const qint16* buf, qint32 samplesPerChannel); +}; + + + +#endif /* PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUTTHREAD_H_ */ diff --git a/plugins/samplesource/bladerf2input/readme.md b/plugins/samplesource/bladerf2input/readme.md new file mode 100644 index 000000000..be9e76c2f --- /dev/null +++ b/plugins/samplesource/bladerf2input/readme.md @@ -0,0 +1,130 @@ +

BladeRF 2.0 micro (v2) input plugin

+ +

Introduction

+ +This input sample source plugin gets its samples from a [BladeRF 2.0 micro device](https://www.nuand.com/bladerf-2) using LibbladeRF v.2. + +

Build

+ +The plugin will be built only if the [BladeRF host library](https://github.com/Nuand/bladeRF) is installed in your system. If you build it from source and install it in a custom location say: `/opt/install/libbladeRF` you will have to add `-DLIBBLADERF_INCLUDE_DIR=/opt/install/libbladeRF/include -DLIBBLADERF_LIBRARIES=/opt/install/libbladeRF/lib/libbladeRF.so` to the cmake command line. + +Note that libbladeRF v2 with git tag 2018.08 should be used (official release). The FPGA image v0.7.3 should be used accordingly. The FPGA .rbf file should be copied to the folder where the `sdrangel` binary resides. You can download FPGA images from [here](https://www.nuand.com/fpga_images/) + +The BladeRF Host library is also provided by many Linux distributions (check its version) and is built in the SDRangel binary releases. + +

Interface

+ +![BladeRF2 input plugin GUI](../../../doc/img/BladeRF2Input_plugin.png) + +

1: Common stream parameters

+ +![SDR Daemon source input stream GUI](../../../doc/img/SDRdaemonSource_plugin_01.png) + +

1.1: Frequency

+ +This is the center frequency of reception in kHz. The center frequency is the same for all Rx channels. The GUI of the sibling channel if present is adjusted automatically. + +

1.2: Start/Stop

+ +Device start / stop button. + + - Blue triangle icon: device is ready and can be started + - Green square icon: device is running and can be stopped + - Magenta (or pink) square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again. + +

1.3: Record

+ +Record baseband I/Q stream toggle button + +

1.4: Stream sample rate

+ +Baseband I/Q sample rate in kS/s. This is the device sample rate (4) divided by the decimation factor (6). + +

2: LO ppm correction

+ +Use this slider to adjust LO correction in ppm. It can be varied from -20.0 to 20.0 in 0.1 steps and is applied in software. This applies to the oscillator that controls both the Rx and Tx frequency therefore it is also changed on the related Rx and Tx plugin(s) if they are active. + +

3: Auto correction options

+ +These buttons control the local DSP auto correction options: + + - **DC**: auto remove DC component + - **IQ**: auto make I/Q balance. The DC correction must be enabled for this to be effective. + +

4: Rx filter bandwidth

+ +This is the Rx filter bandwidth in kHz. Minimum and maximum values are adjusted automatically. Normal range is from 200 kHz to 56 MHz. The Rx filter bandwidth is the same for all Rx channels. The GUI of the sibling channel if present is adjusted automatically. + +

5: Transverter mode open dialog

+ +This button opens a dialog to set the transverter mode frequency translation options: + +![Input stream transverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) + +Note that if you mouse over the button a tooltip appears that displays the translating frequency and if translation is enabled or disabled. When the frequency translation is enabled the button is lit. + +

5.1: Translating frequency

+ +You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. + +The frequency set in the device is the frequency on the main dial (1) minus this frequency. Thus it is positive for down converters and negative for up converters. + +For example a mixer at 120 MHz for HF operation you would set the value to -120,000,000 Hz so that if the main dial frequency is set at 7,130 kHz the PlutoSDR will be set to 127.130 MHz. + +If you use a down converter to receive the 6 cm band narrowband center frequency of 5670 MHz at 432 MHz you would set the translating frequency to 5760 - 432 = 5328 MHz thus dial +5,328,000,000 Hz. + +For bands even higher in the frequency spectrum the GHz digits are not really significant so you can have them set at 1 GHz. Thus to receive the 10368 MHz frequency at 432 MHz you would set the translating frequency to 1368 - 432 = 936 MHz. Note that in this case the frequency of the LO used in the mixer of the transverter is set at 9936 MHz. + +The Hz precision allows a fine tuning of the transverter LO offset + +

5.2: Translating frequency enable/disable

+ +Use this toggle button to activate or deactivate the frequency translation + +

5.3: Confirmation buttons

+ +Use these buttons to confirm ("OK") or dismiss ("Cancel") your changes. + +

6: Device sample rate

+ +This is the BladeRF device ADC sample rate in S/s. + +Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. + +The ADC sample rate is the same for all Rx channels. The GUI of the sibling channel if present is adjusted automatically. + +

7: Baseband center frequency position relative the the BladeRF Rx center frequency

+ +Possible values are: + + - **Cen**: the decimation operation takes place around the BladeRF Rx center frequency Fs + - **Inf**: the decimation operation takes place around Fs - Fc. + - **Sup**: the decimation operation takes place around Fs + Fc. + +With SR as the sample rate before decimation Fc is calculated as: + + - if decimation n is 4 or lower: Fc = SR/2^(log2(n)-1). The device center frequency is on the side of the baseband. You need a RF filter bandwidth at least twice the baseband. + - if decimation n is 8 or higher: Fc = SR/n. The device center frequency is half the baseband away from the side of the baseband. You need a RF filter bandwidth at least 3 times the baseband. + +

8: Decimation factor

+ +The I/Q stream from the BladeRF ADC is downsampled by a power of two before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8, 16, 32, 64. + +

9: Gain mode

+ +This selects the gain processing in use. Values are fetched automatically from the device. Normal values are + + - **default**: AGC with default behavior + - **Manual**: Manual. Use control (9) to adjust gain + - **fast**: fast AGC + - **slow**: slow AGC + - **hybrid**: hybrid AGC + +

10: Manual gain control

+ +Use this slider to adjust gain in manual mode. This control is disabled in non manual modes (all modes but manual). The minumum, maximum and step values are fetched automatically from the device and may vary depending on the center frequency. For frequencies around 400 MHz the gain varies from -16 to 60 dB in 1 dB steps. + +

11: Bias tee control

+ +Use this toggle button to activate or de-activate the bias tee. Note that according to BladeRF v2 specs the bias tee is simultanously present on all Rx RF ports. The GUI of the sibling channel if present is adjusted automatically. + diff --git a/plugins/samplesource/bladerfinput/CMakeLists.txt b/plugins/samplesource/bladerfinput/CMakeLists.txt deleted file mode 100644 index 2daf77264..000000000 --- a/plugins/samplesource/bladerfinput/CMakeLists.txt +++ /dev/null @@ -1,80 +0,0 @@ -project(bladerfinput) - -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") - -set(bladerfinput_SOURCES - bladerfinputgui.cpp - bladerfinput.cpp - bladerfinputplugin.cpp - bladerfinputsettings.cpp - bladerfinputthread.cpp -) - -set(bladerfinput_HEADERS - bladerfinputgui.h - bladerfinput.h - bladerfinputplugin.h - bladerfinputsettings.h - bladerfinputthread.h -) - -set(bladerfinput_FORMS - bladerfinputgui.ui -) - -if (BUILD_DEBIAN) -include_directories( - . - ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client - ${CMAKE_SOURCE_DIR}/devices - ${LIBBLADERFLIBSRC}/include - ${LIBBLADERFLIBSRC}/src -) -else (BUILD_DEBIAN) -include_directories( - . - ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client - ${CMAKE_SOURCE_DIR}/devices - ${LIBBLADERF_INCLUDE_DIR} -) -endif (BUILD_DEBIAN) - -#include(${QT_USE_FILE}) -add_definitions(${QT_DEFINITIONS}) -add_definitions(-DQT_PLUGIN) -add_definitions(-DQT_SHARED) - -#qt4_wrap_cpp(bladerfinput_HEADERS_MOC ${bladerfinput_HEADERS}) -qt5_wrap_ui(bladerfinput_FORMS_HEADERS ${bladerfinput_FORMS}) - -add_library(inputbladerf SHARED - ${bladerfinput_SOURCES} - ${bladerfinput_HEADERS_MOC} - ${bladerfinput_FORMS_HEADERS} -) - -if (BUILD_DEBIAN) -target_link_libraries(inputbladerf - ${QT_LIBRARIES} - bladerf - sdrbase - sdrgui - swagger - bladerfdevice -) -else (BUILD_DEBIAN) -target_link_libraries(inputbladerf - ${QT_LIBRARIES} - ${LIBBLADERF_LIBRARIES} - sdrbase - sdrgui - swagger - bladerfdevice -) -endif (BUILD_DEBIAN) - -qt5_use_modules(inputbladerf Core Widgets) - -install(TARGETS inputbladerf DESTINATION lib/plugins/samplesource) diff --git a/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp b/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp deleted file mode 100644 index 288d56cbb..000000000 --- a/plugins/samplesource/bladerfinput/bladerfinputplugin.cpp +++ /dev/null @@ -1,114 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2015 Edouard Griffiths, F4EXB // -// // -// This program is free software; you can redistribute it and/or modify // -// it under the terms of the GNU General Public License as published by // -// the Free Software Foundation as version 3 of the License, or // -// // -// This program is distributed in the hope that it will be useful, // -// but WITHOUT ANY WARRANTY; without even the implied warranty of // -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // -// GNU General Public License V3 for more details. // -// // -// You should have received a copy of the GNU General Public License // -// along with this program. If not, see . // -/////////////////////////////////////////////////////////////////////////////////// - -#include "../bladerfinput/bladerfinputplugin.h" - -#include -#include -#include -#include "plugin/pluginapi.h" -#include "util/simpleserializer.h" -#include - -#include "bladerfinputgui.h" - -const PluginDescriptor BlderfInputPlugin::m_pluginDescriptor = { - QString("BladeRF Input"), - QString("3.11.0"), - QString("(c) Edouard Griffiths, F4EXB"), - QString("https://github.com/f4exb/sdrangel"), - true, - QString("https://github.com/f4exb/sdrangel") -}; - -const QString BlderfInputPlugin::m_hardwareID = "BladeRF"; -const QString BlderfInputPlugin::m_deviceTypeID = BLADERF_DEVICE_TYPE_ID; - -BlderfInputPlugin::BlderfInputPlugin(QObject* parent) : - QObject(parent) -{ -} - -const PluginDescriptor& BlderfInputPlugin::getPluginDescriptor() const -{ - return m_pluginDescriptor; -} - -void BlderfInputPlugin::initPlugin(PluginAPI* pluginAPI) -{ - pluginAPI->registerSampleSource(m_deviceTypeID, this); -} - -PluginInterface::SamplingDevices BlderfInputPlugin::enumSampleSources() -{ - SamplingDevices result; - struct bladerf_devinfo *devinfo = 0; - - int count = bladerf_get_device_list(&devinfo); - - for(int i = 0; i < count; i++) - { - QString displayedName(QString("BladeRF[%1] %2").arg(devinfo[i].instance).arg(devinfo[i].serial)); - - result.append(SamplingDevice(displayedName, - m_hardwareID, - m_deviceTypeID, - QString(devinfo[i].serial), - i, - PluginInterface::SamplingDevice::PhysicalDevice, - true, - 1, - 0)); - } - - if (devinfo) - { - bladerf_free_device_list(devinfo); // Valgrind memcheck - } - - return result; -} - -PluginInstanceGUI* BlderfInputPlugin::createSampleSourcePluginInstanceGUI( - const QString& sourceId, - QWidget **widget, - DeviceUISet *deviceUISet) -{ - if(sourceId == m_deviceTypeID) - { - BladerfInputGui* gui = new BladerfInputGui(deviceUISet); - *widget = gui; - return gui; - } - else - { - return 0; - } -} - -DeviceSampleSource *BlderfInputPlugin::createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI) -{ - if (sourceId == m_deviceTypeID) - { - BladerfInput *input = new BladerfInput(deviceAPI); - return input; - } - else - { - return 0; - } -} - diff --git a/plugins/samplesource/fcdpro/CMakeLists.txt b/plugins/samplesource/fcdpro/CMakeLists.txt index 5441a76e9..a3270955f 100644 --- a/plugins/samplesource/fcdpro/CMakeLists.txt +++ b/plugins/samplesource/fcdpro/CMakeLists.txt @@ -54,6 +54,6 @@ target_link_libraries(inputfcdpro swagger ) -qt5_use_modules(inputfcdpro Core Widgets) +target_link_libraries(inputfcdpro Qt5::Core Qt5::Widgets) -install(TARGETS inputfcdpro DESTINATION lib/plugins/samplesource) \ No newline at end of file +install(TARGETS inputfcdpro DESTINATION lib/plugins/samplesource) diff --git a/plugins/samplesource/fcdpro/fcdprogui.cpp b/plugins/samplesource/fcdpro/fcdprogui.cpp index 16692ce68..20920db13 100644 --- a/plugins/samplesource/fcdpro/fcdprogui.cpp +++ b/plugins/samplesource/fcdpro/fcdprogui.cpp @@ -35,7 +35,7 @@ FCDProGui::FCDProGui(DeviceUISet *deviceUISet, QWidget* parent) : m_forceSettings(true), m_settings(), m_sampleSource(NULL), - m_lastEngineState((DSPDeviceSourceEngine::State)-1) + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted) { m_sampleSource = (FCDProInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); @@ -146,6 +146,7 @@ FCDProGui::FCDProGui(DeviceUISet *deviceUISet, QWidget* parent) : displaySettings(); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); } FCDProGui::~FCDProGui() @@ -210,9 +211,9 @@ bool FCDProGui::deserialize(const QByteArray& data) bool FCDProGui::handleMessage(const Message& message __attribute__((unused))) { - if (FCDProInput::MsgConfigureFCD::match(message)) + if (FCDProInput::MsgConfigureFCDPro::match(message)) { - const FCDProInput::MsgConfigureFCD& cfg = (FCDProInput::MsgConfigureFCD&) message; + const FCDProInput::MsgConfigureFCDPro& cfg = (FCDProInput::MsgConfigureFCDPro&) message; m_settings = cfg.getSettings(); blockApplySettings(true); displaySettings(); @@ -516,7 +517,7 @@ void FCDProGui::updateStatus() void FCDProGui::updateHardware() { - FCDProInput::MsgConfigureFCD* message = FCDProInput::MsgConfigureFCD::create(m_settings, m_forceSettings); + FCDProInput::MsgConfigureFCDPro* message = FCDProInput::MsgConfigureFCDPro::create(m_settings, m_forceSettings); m_sampleSource->getInputMessageQueue()->push(message); m_forceSettings = false; m_updateTimer.stop(); diff --git a/plugins/samplesource/fcdpro/fcdprogui.ui b/plugins/samplesource/fcdpro/fcdprogui.ui index d7a31d4c2..5e3d747d7 100644 --- a/plugins/samplesource/fcdpro/fcdprogui.ui +++ b/plugins/samplesource/fcdpro/fcdprogui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -135,7 +135,7 @@ - DejaVu Sans Mono + Liberation Mono 20 diff --git a/plugins/samplesource/fcdpro/fcdproinput.cpp b/plugins/samplesource/fcdpro/fcdproinput.cpp index 314581121..5059a79fd 100644 --- a/plugins/samplesource/fcdpro/fcdproinput.cpp +++ b/plugins/samplesource/fcdpro/fcdproinput.cpp @@ -31,12 +31,11 @@ #include -#include "fcdprogui.h" #include "fcdprothread.h" #include "fcdtraits.h" #include "fcdproconst.h" -MESSAGE_CLASS_DEFINITION(FCDProInput::MsgConfigureFCD, Message) +MESSAGE_CLASS_DEFINITION(FCDProInput::MsgConfigureFCDPro, Message) MESSAGE_CLASS_DEFINITION(FCDProInput::MsgStartStop, Message) MESSAGE_CLASS_DEFINITION(FCDProInput::MsgFileRecord, Message) @@ -49,10 +48,7 @@ FCDProInput::FCDProInput(DeviceSourceAPI *deviceAPI) : m_running(false) { openDevice(); - - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID()); - m_fileSink = new FileRecord(std::string(recFileNameCStr)); + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); m_deviceAPI->addSink(m_fileSink); } @@ -121,12 +117,7 @@ bool FCDProInput::start() return false; } - if ((m_FCDThread = new FCDProThread(&m_sampleFifo)) == NULL) - { - qCritical("out of memory"); - return false; - } - + m_FCDThread = new FCDProThread(&m_sampleFifo); m_FCDThread->startWork(); // mutexLocker.unlock(); @@ -178,12 +169,12 @@ bool FCDProInput::deserialize(const QByteArray& data) success = false; } - MsgConfigureFCD* message = MsgConfigureFCD::create(m_settings, true); + MsgConfigureFCDPro* message = MsgConfigureFCDPro::create(m_settings, true); m_inputMessageQueue.push(message); if (m_guiMessageQueue) { - MsgConfigureFCD* messageToGUI = MsgConfigureFCD::create(m_settings, true); + MsgConfigureFCDPro* messageToGUI = MsgConfigureFCDPro::create(m_settings, true); m_guiMessageQueue->push(messageToGUI); } @@ -210,22 +201,22 @@ void FCDProInput::setCenterFrequency(qint64 centerFrequency) FCDProSettings settings = m_settings; settings.m_centerFrequency = centerFrequency; - MsgConfigureFCD* message = MsgConfigureFCD::create(settings, false); + MsgConfigureFCDPro* message = MsgConfigureFCDPro::create(settings, false); m_inputMessageQueue.push(message); if (m_guiMessageQueue) { - MsgConfigureFCD* messageToGUI = MsgConfigureFCD::create(settings, false); + MsgConfigureFCDPro* messageToGUI = MsgConfigureFCDPro::create(settings, false); m_guiMessageQueue->push(messageToGUI); } } bool FCDProInput::handleMessage(const Message& message) { - if(MsgConfigureFCD::match(message)) + if(MsgConfigureFCDPro::match(message)) { qDebug() << "FCDProInput::handleMessage: MsgConfigureFCD"; - MsgConfigureFCD& conf = (MsgConfigureFCD&) message; + MsgConfigureFCDPro& conf = (MsgConfigureFCDPro&) message; applySettings(conf.getSettings(), conf.getForce()); return true; } @@ -239,13 +230,11 @@ bool FCDProInput::handleMessage(const Message& message) if (m_deviceAPI->initAcquisition()) { m_deviceAPI->startAcquisition(); - DSPEngine::instance()->startAudioOutput(); } } else { m_deviceAPI->stopAcquisition(); - DSPEngine::instance()->stopAudioOutput(); } return true; @@ -255,9 +244,18 @@ bool FCDProInput::handleMessage(const Message& message) MsgFileRecord& conf = (MsgFileRecord&) message; qDebug() << "FCDProInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); - if (conf.getStartStop()) { + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSink->setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); + } + m_fileSink->startRecording(); - } else { + } + else + { m_fileSink->stopRecording(); } @@ -774,3 +772,136 @@ int FCDProInput::webapiRun( return 200; } + +int FCDProInput::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setFcdProSettings(new SWGSDRangel::SWGFCDProSettings()); + response.getFcdProSettings()->init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int FCDProInput::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage __attribute__((unused))) +{ + FCDProSettings settings = m_settings; + + if (deviceSettingsKeys.contains("centerFrequency")) { + settings.m_centerFrequency = response.getFcdProSettings()->getCenterFrequency(); + } + if (deviceSettingsKeys.contains("LOppmTenths")) { + settings.m_LOppmTenths = response.getFcdProSettings()->getLOppmTenths(); + } + if (deviceSettingsKeys.contains("lnaGainIndex")) { + settings.m_lnaGainIndex = response.getFcdProSettings()->getLnaGainIndex(); + } + if (deviceSettingsKeys.contains("rfFilterIndex")) { + settings.m_rfFilterIndex = response.getFcdProSettings()->getRfFilterIndex(); + } + if (deviceSettingsKeys.contains("lnaEnhanceIndex")) { + settings.m_lnaEnhanceIndex = response.getFcdProSettings()->getLnaEnhanceIndex(); + } + if (deviceSettingsKeys.contains("bandIndex")) { + settings.m_bandIndex = response.getFcdProSettings()->getBandIndex(); + } + if (deviceSettingsKeys.contains("mixerGainIndex")) { + settings.m_mixerGainIndex = response.getFcdProSettings()->getMixerGainIndex(); + } + if (deviceSettingsKeys.contains("mixerFilterIndex")) { + settings.m_mixerFilterIndex = response.getFcdProSettings()->getMixerFilterIndex(); + } + if (deviceSettingsKeys.contains("biasCurrentIndex")) { + settings.m_biasCurrentIndex = response.getFcdProSettings()->getBiasCurrentIndex(); + } + if (deviceSettingsKeys.contains("modeIndex")) { + settings.m_modeIndex = response.getFcdProSettings()->getModeIndex(); + } + if (deviceSettingsKeys.contains("gain1Index")) { + settings.m_gain1Index = response.getFcdProSettings()->getGain1Index(); + } + if (deviceSettingsKeys.contains("gain2Index")) { + settings.m_gain2Index = response.getFcdProSettings()->getGain2Index(); + } + if (deviceSettingsKeys.contains("gain3Index")) { + settings.m_gain3Index = response.getFcdProSettings()->getGain3Index(); + } + if (deviceSettingsKeys.contains("gain4Index")) { + settings.m_gain4Index = response.getFcdProSettings()->getGain4Index(); + } + if (deviceSettingsKeys.contains("gain5Index")) { + settings.m_gain5Index = response.getFcdProSettings()->getGain5Index(); + } + if (deviceSettingsKeys.contains("gain6Index")) { + settings.m_gain6Index = response.getFcdProSettings()->getGain6Index(); + } + if (deviceSettingsKeys.contains("rcFilterIndex")) { + settings.m_rcFilterIndex = response.getFcdProSettings()->getRcFilterIndex(); + } + if (deviceSettingsKeys.contains("ifFilterIndex")) { + settings.m_ifFilterIndex = response.getFcdProSettings()->getIfFilterIndex(); + } + if (deviceSettingsKeys.contains("dcBlock")) { + settings.m_dcBlock = response.getFcdProSettings()->getDcBlock() != 0; + } + if (deviceSettingsKeys.contains("iqCorrection")) { + settings.m_iqCorrection = response.getFcdProSettings()->getIqCorrection() != 0; + } + if (deviceSettingsKeys.contains("transverterDeltaFrequency")) { + settings.m_transverterDeltaFrequency = response.getFcdProSettings()->getTransverterDeltaFrequency(); + } + if (deviceSettingsKeys.contains("transverterMode")) { + settings.m_transverterMode = response.getFcdProSettings()->getTransverterMode() != 0; + } + if (deviceSettingsKeys.contains("fileRecordName")) { + settings.m_fileRecordName = *response.getFcdProSettings()->getFileRecordName(); + } + + MsgConfigureFCDPro *msg = MsgConfigureFCDPro::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureFCDPro *msgToGUI = MsgConfigureFCDPro::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +void FCDProInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const FCDProSettings& settings) +{ + response.getFcdProSettings()->setCenterFrequency(settings.m_centerFrequency); + response.getFcdProSettings()->setLOppmTenths(settings.m_LOppmTenths); + response.getFcdProSettings()->setLnaGainIndex(settings.m_lnaGainIndex); + response.getFcdProSettings()->setRfFilterIndex(settings.m_rfFilterIndex); + response.getFcdProSettings()->setLnaEnhanceIndex(settings.m_lnaEnhanceIndex); + response.getFcdProSettings()->setBandIndex(settings.m_bandIndex); + response.getFcdProSettings()->setMixerGainIndex(settings.m_mixerGainIndex); + response.getFcdProSettings()->setMixerFilterIndex(settings.m_mixerFilterIndex); + response.getFcdProSettings()->setBiasCurrentIndex(settings.m_biasCurrentIndex); + response.getFcdProSettings()->setModeIndex(settings.m_modeIndex); + response.getFcdProSettings()->setGain1Index(settings.m_gain1Index); + response.getFcdProSettings()->setGain2Index(settings.m_gain2Index); + response.getFcdProSettings()->setGain3Index(settings.m_gain3Index); + response.getFcdProSettings()->setGain4Index(settings.m_gain4Index); + response.getFcdProSettings()->setGain5Index(settings.m_gain5Index); + response.getFcdProSettings()->setGain6Index(settings.m_gain6Index); + response.getFcdProSettings()->setRcFilterIndex(settings.m_rcFilterIndex); + response.getFcdProSettings()->setIfFilterIndex(settings.m_ifFilterIndex); + response.getFcdProSettings()->setDcBlock(settings.m_dcBlock ? 1 : 0); + response.getFcdProSettings()->setIqCorrection(settings.m_iqCorrection ? 1 : 0); + response.getFcdProSettings()->setTransverterDeltaFrequency(settings.m_transverterDeltaFrequency); + response.getFcdProSettings()->setTransverterMode(settings.m_transverterMode ? 1 : 0); + + if (response.getFcdProSettings()->getFileRecordName()) { + *response.getFcdProSettings()->getFileRecordName() = settings.m_fileRecordName; + } else { + response.getFcdProSettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + } +} diff --git a/plugins/samplesource/fcdpro/fcdproinput.h b/plugins/samplesource/fcdpro/fcdproinput.h index f7ed3792b..dfdbabe0d 100644 --- a/plugins/samplesource/fcdpro/fcdproinput.h +++ b/plugins/samplesource/fcdpro/fcdproinput.h @@ -37,23 +37,23 @@ class FileRecord; class FCDProInput : public DeviceSampleSource { public: - class MsgConfigureFCD : public Message { + class MsgConfigureFCDPro : public Message { MESSAGE_CLASS_DECLARATION public: const FCDProSettings& getSettings() const { return m_settings; } bool getForce() const { return m_force; } - static MsgConfigureFCD* create(const FCDProSettings& settings, bool force) + static MsgConfigureFCDPro* create(const FCDProSettings& settings, bool force) { - return new MsgConfigureFCD(settings, force); + return new MsgConfigureFCDPro(settings, force); } private: FCDProSettings m_settings; bool m_force; - MsgConfigureFCD(const FCDProSettings& settings, bool force) : + MsgConfigureFCDPro(const FCDProSettings& settings, bool force) : Message(), m_settings(settings), m_force(force) @@ -117,6 +117,16 @@ public: virtual bool handleMessage(const Message& message); + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + virtual int webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage); @@ -151,6 +161,8 @@ private: void applySettings(const FCDProSettings& settings, bool force); void set_lo_ppm(); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const FCDProSettings& settings); + DeviceSourceAPI *m_deviceAPI; hid_device *m_dev; QMutex m_mutex; diff --git a/plugins/samplesource/fcdpro/fcdproplugin.cpp b/plugins/samplesource/fcdpro/fcdproplugin.cpp index 7b81c8f87..e42f3047d 100644 --- a/plugins/samplesource/fcdpro/fcdproplugin.cpp +++ b/plugins/samplesource/fcdpro/fcdproplugin.cpp @@ -15,14 +15,17 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include #include "plugin/pluginapi.h" #include "util/simpleserializer.h" #include "fcdproplugin.h" #include +#ifdef SERVER_MODE +#include "fcdproinput.h" +#else #include "fcdprogui.h" +#endif #include "fcdtraits.h" const PluginDescriptor FCDProPlugin::m_pluginDescriptor = { @@ -78,6 +81,15 @@ PluginInterface::SamplingDevices FCDProPlugin::enumSampleSources() return result; } +#ifdef SERVER_MODE +PluginInstanceGUI* FCDProPlugin::createSampleSourcePluginInstanceGUI( + const QString& sourceId __attribute__((unused)), + QWidget **widget __attribute__((unused)), + DeviceUISet *deviceUISet __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* FCDProPlugin::createSampleSourcePluginInstanceGUI( const QString& sourceId, QWidget **widget, @@ -94,6 +106,7 @@ PluginInstanceGUI* FCDProPlugin::createSampleSourcePluginInstanceGUI( return 0; } } +#endif DeviceSampleSource *FCDProPlugin::createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI) { diff --git a/plugins/samplesource/fcdpro/fcdprosettings.cpp b/plugins/samplesource/fcdpro/fcdprosettings.cpp index 88f19bc73..65c92133a 100644 --- a/plugins/samplesource/fcdpro/fcdprosettings.cpp +++ b/plugins/samplesource/fcdpro/fcdprosettings.cpp @@ -47,6 +47,7 @@ void FCDProSettings::resetToDefaults() m_gain6Index = 0; m_transverterMode = false; m_transverterDeltaFrequency = 0; + m_fileRecordName = ""; } QByteArray FCDProSettings::serialize() const diff --git a/plugins/samplesource/fcdpro/fcdprosettings.h b/plugins/samplesource/fcdpro/fcdprosettings.h index 59f4e3860..f4972717d 100644 --- a/plugins/samplesource/fcdpro/fcdprosettings.h +++ b/plugins/samplesource/fcdpro/fcdprosettings.h @@ -17,6 +17,8 @@ #ifndef _FCDPRO_FCDPROSETTINGS_H_ #define _FCDPRO_FCDPROSETTINGS_H_ +#include + struct FCDProSettings { quint64 m_centerFrequency; qint32 m_LOppmTenths; @@ -40,6 +42,7 @@ struct FCDProSettings { bool m_iqCorrection; bool m_transverterMode; qint64 m_transverterDeltaFrequency; + QString m_fileRecordName; FCDProSettings(); void resetToDefaults(); diff --git a/plugins/samplesource/fcdproplus/CMakeLists.txt b/plugins/samplesource/fcdproplus/CMakeLists.txt index b9dd191bd..0b94f9658 100644 --- a/plugins/samplesource/fcdproplus/CMakeLists.txt +++ b/plugins/samplesource/fcdproplus/CMakeLists.txt @@ -54,6 +54,6 @@ target_link_libraries(inputfcdproplus swagger ) -qt5_use_modules(inputfcdproplus Core Widgets) +target_link_libraries(inputfcdproplus Qt5::Core Qt5::Widgets) install(TARGETS inputfcdproplus DESTINATION lib/plugins/samplesource) diff --git a/plugins/samplesource/fcdproplus/fcdproplusgui.cpp b/plugins/samplesource/fcdproplus/fcdproplusgui.cpp index 77bd5aa17..210bb4752 100644 --- a/plugins/samplesource/fcdproplus/fcdproplusgui.cpp +++ b/plugins/samplesource/fcdproplus/fcdproplusgui.cpp @@ -36,7 +36,7 @@ FCDProPlusGui::FCDProPlusGui(DeviceUISet *deviceUISet, QWidget* parent) : m_forceSettings(true), m_settings(), m_sampleSource(NULL), - m_lastEngineState((DSPDeviceSourceEngine::State)-1) + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted) { m_sampleSource = (FCDProPlusInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); @@ -64,6 +64,7 @@ FCDProPlusGui::FCDProPlusGui(DeviceUISet *deviceUISet, QWidget* parent) : displaySettings(); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); } FCDProPlusGui::~FCDProPlusGui() @@ -128,9 +129,9 @@ bool FCDProPlusGui::deserialize(const QByteArray& data) bool FCDProPlusGui::handleMessage(const Message& message __attribute__((unused))) { - if (FCDProPlusInput::MsgConfigureFCD::match(message)) + if (FCDProPlusInput::MsgConfigureFCDProPlus::match(message)) { - const FCDProPlusInput::MsgConfigureFCD& cfg = (FCDProPlusInput::MsgConfigureFCD&) message; + const FCDProPlusInput::MsgConfigureFCDProPlus& cfg = (FCDProPlusInput::MsgConfigureFCDProPlus&) message; m_settings = cfg.getSettings(); blockApplySettings(true); displaySettings(); @@ -240,7 +241,7 @@ void FCDProPlusGui::on_iqImbalance_toggled(bool checked) void FCDProPlusGui::updateHardware() { - FCDProPlusInput::MsgConfigureFCD* message = FCDProPlusInput::MsgConfigureFCD::create(m_settings, m_forceSettings); + FCDProPlusInput::MsgConfigureFCDProPlus* message = FCDProPlusInput::MsgConfigureFCDProPlus::create(m_settings, m_forceSettings); m_sampleSource->getInputMessageQueue()->push(message); m_forceSettings = false; m_updateTimer.stop(); diff --git a/plugins/samplesource/fcdproplus/fcdproplusgui.ui b/plugins/samplesource/fcdproplus/fcdproplusgui.ui index 82431d645..983ee0050 100644 --- a/plugins/samplesource/fcdproplus/fcdproplusgui.ui +++ b/plugins/samplesource/fcdproplus/fcdproplusgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -135,7 +135,7 @@ - DejaVu Sans Mono + Liberation Mono 20 diff --git a/plugins/samplesource/fcdproplus/fcdproplusinput.cpp b/plugins/samplesource/fcdproplus/fcdproplusinput.cpp index e350933f0..a15a0819e 100644 --- a/plugins/samplesource/fcdproplus/fcdproplusinput.cpp +++ b/plugins/samplesource/fcdproplus/fcdproplusinput.cpp @@ -30,12 +30,11 @@ #include -#include "fcdproplusgui.h" #include "fcdproplusthread.h" #include "fcdtraits.h" #include "fcdproplusconst.h" -MESSAGE_CLASS_DEFINITION(FCDProPlusInput::MsgConfigureFCD, Message) +MESSAGE_CLASS_DEFINITION(FCDProPlusInput::MsgConfigureFCDProPlus, Message) MESSAGE_CLASS_DEFINITION(FCDProPlusInput::MsgStartStop, Message) MESSAGE_CLASS_DEFINITION(FCDProPlusInput::MsgFileRecord, Message) @@ -48,10 +47,7 @@ FCDProPlusInput::FCDProPlusInput(DeviceSourceAPI *deviceAPI) : m_running(false) { openDevice(); - - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID()); - m_fileSink = new FileRecord(std::string(recFileNameCStr)); + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); m_deviceAPI->addSink(m_fileSink); } @@ -115,12 +111,7 @@ bool FCDProPlusInput::start() return false; } - if ((m_FCDThread = new FCDProPlusThread(&m_sampleFifo)) == NULL) - { - qCritical("out of memory"); - return false; - } - + m_FCDThread = new FCDProPlusThread(&m_sampleFifo); m_FCDThread->startWork(); // mutexLocker.unlock(); @@ -172,12 +163,12 @@ bool FCDProPlusInput::deserialize(const QByteArray& data) success = false; } - MsgConfigureFCD* message = MsgConfigureFCD::create(m_settings, true); + MsgConfigureFCDProPlus* message = MsgConfigureFCDProPlus::create(m_settings, true); m_inputMessageQueue.push(message); if (m_guiMessageQueue) { - MsgConfigureFCD* messageToGUI = MsgConfigureFCD::create(m_settings, true); + MsgConfigureFCDProPlus* messageToGUI = MsgConfigureFCDProPlus::create(m_settings, true); m_guiMessageQueue->push(messageToGUI); } @@ -204,22 +195,22 @@ void FCDProPlusInput::setCenterFrequency(qint64 centerFrequency) FCDProPlusSettings settings = m_settings; settings.m_centerFrequency = centerFrequency; - MsgConfigureFCD* message = MsgConfigureFCD::create(settings, false); + MsgConfigureFCDProPlus* message = MsgConfigureFCDProPlus::create(settings, false); m_inputMessageQueue.push(message); if (m_guiMessageQueue) { - MsgConfigureFCD* messageToGUI = MsgConfigureFCD::create(settings, false); + MsgConfigureFCDProPlus* messageToGUI = MsgConfigureFCDProPlus::create(settings, false); m_guiMessageQueue->push(messageToGUI); } } bool FCDProPlusInput::handleMessage(const Message& message) { - if(MsgConfigureFCD::match(message)) + if(MsgConfigureFCDProPlus::match(message)) { qDebug() << "FCDProPlusInput::handleMessage: MsgConfigureFCD"; - MsgConfigureFCD& conf = (MsgConfigureFCD&) message; + MsgConfigureFCDProPlus& conf = (MsgConfigureFCDProPlus&) message; applySettings(conf.getSettings(), conf.getForce()); return true; } @@ -233,13 +224,11 @@ bool FCDProPlusInput::handleMessage(const Message& message) if (m_deviceAPI->initAcquisition()) { m_deviceAPI->startAcquisition(); - DSPEngine::instance()->startAudioOutput(); } } else { m_deviceAPI->stopAcquisition(); - DSPEngine::instance()->stopAudioOutput(); } return true; @@ -249,9 +238,18 @@ bool FCDProPlusInput::handleMessage(const Message& message) MsgFileRecord& conf = (MsgFileRecord&) message; qDebug() << "FCDProPlusInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); - if (conf.getStartStop()) { + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSink->setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); + } + m_fileSink->startRecording(); - } else { + } + else + { m_fileSink->stopRecording(); } @@ -486,5 +484,101 @@ int FCDProPlusInput::webapiRun( return 200; } +int FCDProPlusInput::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setFcdProPlusSettings(new SWGSDRangel::SWGFCDProPlusSettings()); + response.getFcdProPlusSettings()->init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int FCDProPlusInput::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage __attribute__((unused))) +{ + FCDProPlusSettings settings = m_settings; + + if (deviceSettingsKeys.contains("centerFrequency")) { + settings.m_centerFrequency = response.getFcdProPlusSettings()->getCenterFrequency(); + } + if (deviceSettingsKeys.contains("rangeLow")) { + settings.m_rangeLow = response.getFcdProPlusSettings()->getRangeLow() != 0; + } + if (deviceSettingsKeys.contains("lnaGain")) { + settings.m_lnaGain = response.getFcdProPlusSettings()->getLnaGain() != 0; + } + if (deviceSettingsKeys.contains("mixGain")) { + settings.m_mixGain = response.getFcdProPlusSettings()->getMixGain() != 0; + } + if (deviceSettingsKeys.contains("biasT")) { + settings.m_biasT = response.getFcdProPlusSettings()->getBiasT() != 0; + } + if (deviceSettingsKeys.contains("ifGain")) { + settings.m_ifGain = response.getFcdProPlusSettings()->getIfGain(); + } + if (deviceSettingsKeys.contains("ifFilterIndex")) { + settings.m_ifFilterIndex = response.getFcdProPlusSettings()->getIfFilterIndex(); + } + if (deviceSettingsKeys.contains("rfFilterIndex")) { + settings.m_rfFilterIndex = response.getFcdProPlusSettings()->getRfFilterIndex(); + } + if (deviceSettingsKeys.contains("LOppmTenths")) { + settings.m_LOppmTenths = response.getFcdProPlusSettings()->getLOppmTenths(); + } + if (deviceSettingsKeys.contains("dcBlock")) { + settings.m_dcBlock = response.getFcdProPlusSettings()->getDcBlock() != 0; + } + if (deviceSettingsKeys.contains("iqImbalance")) { + settings.m_iqImbalance = response.getFcdProPlusSettings()->getIqImbalance() != 0; + } + if (deviceSettingsKeys.contains("transverterDeltaFrequency")) { + settings.m_transverterDeltaFrequency = response.getFcdProPlusSettings()->getTransverterDeltaFrequency(); + } + if (deviceSettingsKeys.contains("transverterMode")) { + settings.m_transverterMode = response.getFcdProPlusSettings()->getTransverterMode() != 0; + } + if (deviceSettingsKeys.contains("fileRecordName")) { + settings.m_fileRecordName = *response.getFcdProPlusSettings()->getFileRecordName(); + } + + MsgConfigureFCDProPlus *msg = MsgConfigureFCDProPlus::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureFCDProPlus *msgToGUI = MsgConfigureFCDProPlus::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +void FCDProPlusInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const FCDProPlusSettings& settings) +{ + response.getFcdProPlusSettings()->setCenterFrequency(settings.m_centerFrequency); + response.getFcdProPlusSettings()->setRangeLow(settings.m_rangeLow ? 1 : 0); + response.getFcdProPlusSettings()->setLnaGain(settings.m_lnaGain ? 1 : 0); + response.getFcdProPlusSettings()->setMixGain(settings.m_mixGain ? 1 : 0); + response.getFcdProPlusSettings()->setBiasT(settings.m_biasT ? 1 : 0); + response.getFcdProPlusSettings()->setIfGain(settings.m_ifGain); + response.getFcdProPlusSettings()->setIfFilterIndex(settings.m_ifFilterIndex); + response.getFcdProPlusSettings()->setRfFilterIndex(settings.m_rfFilterIndex); + response.getFcdProPlusSettings()->setLOppmTenths(settings.m_LOppmTenths); + response.getFcdProPlusSettings()->setDcBlock(settings.m_dcBlock ? 1 : 0); + response.getFcdProPlusSettings()->setIqImbalance(settings.m_iqImbalance ? 1 : 0); + response.getFcdProPlusSettings()->setTransverterDeltaFrequency(settings.m_transverterDeltaFrequency); + response.getFcdProPlusSettings()->setTransverterMode(settings.m_transverterMode ? 1 : 0); + + if (response.getFcdProPlusSettings()->getFileRecordName()) { + *response.getFcdProPlusSettings()->getFileRecordName() = settings.m_fileRecordName; + } else { + response.getFcdProPlusSettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + } +} diff --git a/plugins/samplesource/fcdproplus/fcdproplusinput.h b/plugins/samplesource/fcdproplus/fcdproplusinput.h index f73884d5e..38c4cac11 100644 --- a/plugins/samplesource/fcdproplus/fcdproplusinput.h +++ b/plugins/samplesource/fcdproplus/fcdproplusinput.h @@ -36,23 +36,23 @@ class FileRecord; class FCDProPlusInput : public DeviceSampleSource { public: - class MsgConfigureFCD : public Message { + class MsgConfigureFCDProPlus : public Message { MESSAGE_CLASS_DECLARATION public: const FCDProPlusSettings& getSettings() const { return m_settings; } bool getForce() const { return m_force; } - static MsgConfigureFCD* create(const FCDProPlusSettings& settings, bool force) + static MsgConfigureFCDProPlus* create(const FCDProPlusSettings& settings, bool force) { - return new MsgConfigureFCD(settings, force); + return new MsgConfigureFCDProPlus(settings, force); } private: FCDProPlusSettings m_settings; bool m_force; - MsgConfigureFCD(const FCDProPlusSettings& settings, bool force) : + MsgConfigureFCDProPlus(const FCDProPlusSettings& settings, bool force) : Message(), m_settings(settings), m_force(force) @@ -125,6 +125,16 @@ public: SWGSDRangel::SWGDeviceState& response, QString& errorMessage); + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + void set_center_freq(double freq); void set_bias_t(bool on); void set_lna_gain(bool on); @@ -138,6 +148,7 @@ private: bool openDevice(); void closeDevice(); void applySettings(const FCDProPlusSettings& settings, bool force); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const FCDProPlusSettings& settings); DeviceSourceAPI *m_deviceAPI; hid_device *m_dev; diff --git a/plugins/samplesource/fcdproplus/fcdproplusinputqt.cpp b/plugins/samplesource/fcdproplus/fcdproplusinputqt.cpp index 3a8f8a328..391a4f0ef 100644 --- a/plugins/samplesource/fcdproplus/fcdproplusinputqt.cpp +++ b/plugins/samplesource/fcdproplus/fcdproplusinputqt.cpp @@ -28,7 +28,7 @@ #include "fcdtraits.h" #include "fcdproplusconst.h" -MESSAGE_CLASS_DEFINITION(FCDProPlusInput::MsgConfigureFCD, Message) +MESSAGE_CLASS_DEFINITION(FCDProPlusInput::MsgConfigureFCDProPlus, Message) FCDProPlusInput::FCDProPlusInput() : m_dev(0), @@ -115,10 +115,10 @@ quint64 FCDProPlusInput::getCenterFrequency() const bool FCDProPlusInput::handleMessage(const Message& message) { - if(MsgConfigureFCD::match(message)) + if(MsgConfigureFCDProPlus::match(message)) { qDebug() << "FCDProPlusInput::handleMessage: MsgConfigureFCD"; - MsgConfigureFCD& conf = (MsgConfigureFCD&) message; + MsgConfigureFCDProPlus& conf = (MsgConfigureFCDProPlus&) message; applySettings(conf.getSettings(), false); return true; } diff --git a/plugins/samplesource/fcdproplus/fcdproplusinputqt.h b/plugins/samplesource/fcdproplus/fcdproplusinputqt.h index 215af1856..af974e582 100644 --- a/plugins/samplesource/fcdproplus/fcdproplusinputqt.h +++ b/plugins/samplesource/fcdproplus/fcdproplusinputqt.h @@ -35,15 +35,15 @@ class FCDProPlusReader; class FCDProPlusInput : public DeviceSampleSource { public: - class MsgConfigureFCD : public Message { + class MsgConfigureFCDProPlus : public Message { MESSAGE_CLASS_DECLARATION public: const FCDProPlusSettings& getSettings() const { return m_settings; } - static MsgConfigureFCD* create(const FCDProPlusSettings& settings) + static MsgConfigureFCDProPlus* create(const FCDProPlusSettings& settings) { - return new MsgConfigureFCD(settings); + return new MsgConfigureFCDProPlus(settings); } private: diff --git a/plugins/samplesource/fcdproplus/fcdproplusplugin.cpp b/plugins/samplesource/fcdproplus/fcdproplusplugin.cpp index 419563aa6..16207daa0 100644 --- a/plugins/samplesource/fcdproplus/fcdproplusplugin.cpp +++ b/plugins/samplesource/fcdproplus/fcdproplusplugin.cpp @@ -15,14 +15,17 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include #include "plugin/pluginapi.h" #include "util/simpleserializer.h" #include "fcdproplusplugin.h" #include +#ifdef SERVER_MODE +#include "fcdproplusinput.h" +#else #include "fcdproplusgui.h" +#endif #include "fcdtraits.h" const PluginDescriptor FCDProPlusPlugin::m_pluginDescriptor = { @@ -80,6 +83,15 @@ PluginInterface::SamplingDevices FCDProPlusPlugin::enumSampleSources() return result; } +#ifdef SERVER_MODE +PluginInstanceGUI* FCDProPlusPlugin::createSampleSourcePluginInstanceGUI( + const QString& sourceId __attribute__((unused)), + QWidget **widget __attribute__((unused)), + DeviceUISet *deviceUISet __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* FCDProPlusPlugin::createSampleSourcePluginInstanceGUI( const QString& sourceId, QWidget **widget, @@ -96,6 +108,7 @@ PluginInstanceGUI* FCDProPlusPlugin::createSampleSourcePluginInstanceGUI( return 0; } } +#endif DeviceSampleSource *FCDProPlusPlugin::createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI) { diff --git a/plugins/samplesource/fcdproplus/fcdproplussettings.cpp b/plugins/samplesource/fcdproplus/fcdproplussettings.cpp index 15b7abcd2..59d10c2d1 100644 --- a/plugins/samplesource/fcdproplus/fcdproplussettings.cpp +++ b/plugins/samplesource/fcdproplus/fcdproplussettings.cpp @@ -38,6 +38,7 @@ void FCDProPlusSettings::resetToDefaults() m_iqImbalance = false; m_transverterMode = false; m_transverterDeltaFrequency = 0; + m_fileRecordName = ""; } QByteArray FCDProPlusSettings::serialize() const diff --git a/plugins/samplesource/fcdproplus/fcdproplussettings.h b/plugins/samplesource/fcdproplus/fcdproplussettings.h index b0978fb17..f4fe21c3d 100644 --- a/plugins/samplesource/fcdproplus/fcdproplussettings.h +++ b/plugins/samplesource/fcdproplus/fcdproplussettings.h @@ -17,6 +17,8 @@ #ifndef _FCDPROPLUS_FCDPROPLUSSETTINGS_H_ #define _FCDPROPLUS_FCDPROPLUSSETTINGS_H_ +#include + struct FCDProPlusSettings { quint64 m_centerFrequency; bool m_rangeLow; @@ -31,6 +33,7 @@ struct FCDProPlusSettings { bool m_iqImbalance; bool m_transverterMode; qint64 m_transverterDeltaFrequency; + QString m_fileRecordName; FCDProPlusSettings(); void resetToDefaults(); diff --git a/plugins/samplesource/filesource/CMakeLists.txt b/plugins/samplesource/filesource/CMakeLists.txt index 6984dd483..c467096d2 100644 --- a/plugins/samplesource/filesource/CMakeLists.txt +++ b/plugins/samplesource/filesource/CMakeLists.txt @@ -49,6 +49,6 @@ target_link_libraries(inputfilesource swagger ) -qt5_use_modules(inputfilesource Core Widgets) +target_link_libraries(inputfilesource Qt5::Core Qt5::Widgets) install(TARGETS inputfilesource DESTINATION lib/plugins/samplesource) diff --git a/plugins/samplesource/filesource/filesource.pro b/plugins/samplesource/filesource/filesource.pro index a22dc7d9d..7a85b75b6 100644 --- a/plugins/samplesource/filesource/filesource.pro +++ b/plugins/samplesource/filesource/filesource.pro @@ -18,6 +18,7 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client diff --git a/plugins/samplesource/filesource/filesourcegui.cpp b/plugins/samplesource/filesource/filesourcegui.cpp index d679ae889..f19e500dd 100644 --- a/plugins/samplesource/filesource/filesourcegui.cpp +++ b/plugins/samplesource/filesource/filesourcegui.cpp @@ -51,26 +51,28 @@ FileSourceGui::FileSourceGui(DeviceUISet *deviceUISet, QWidget* parent) : m_samplesCount(0), m_tickCount(0), m_enableNavTime(false), - m_lastEngineState((DSPDeviceSourceEngine::State)-1) + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted) { ui->setupUi(this); ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); ui->centerFrequency->setValueRange(7, 0, pow(10,7)); ui->fileNameText->setText(m_fileName); + ui->crcLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }"); connect(&(m_deviceUISet->m_deviceSourceAPI->getMasterTimer()), SIGNAL(timeout()), this, SLOT(tick())); connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); m_statusTimer.start(500); + setAccelerationCombo(); displaySettings(); ui->navTimeSlider->setEnabled(false); - ui->playLoop->setChecked(true); // FIXME: always play in a loop - ui->playLoop->setEnabled(false); + ui->acceleration->setEnabled(false); m_sampleSource = m_deviceUISet->m_deviceSourceAPI->getSampleSource(); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); } FileSourceGui::~FileSourceGui() @@ -161,9 +163,7 @@ bool FileSourceGui::handleMessage(const Message& message) { const FileSourceInput::MsgConfigureFileSource& cfg = (FileSourceInput::MsgConfigureFileSource&) message; m_settings = cfg.getSettings(); - blockApplySettings(true); displaySettings(); - blockApplySettings(false); return true; } else if (FileSourceInput::MsgReportFileSourceAcquisition::match(message)) @@ -197,6 +197,28 @@ bool FileSourceGui::handleMessage(const Message& message) return true; } + else if (FileSourceInput::MsgPlayPause::match(message)) + { + FileSourceInput::MsgPlayPause& notif = (FileSourceInput::MsgPlayPause&) message; + bool checked = notif.getPlayPause(); + ui->play->setChecked(checked); + ui->navTimeSlider->setEnabled(!checked); + ui->acceleration->setEnabled(!checked); + m_enableNavTime = !checked; + + return true; + } + else if (FileSourceInput::MsgReportHeaderCRC::match(message)) + { + FileSourceInput::MsgReportHeaderCRC& notif = (FileSourceInput::MsgReportHeaderCRC&) message; + if (notif.isOK()) { + ui->crcLabel->setStyleSheet("QLabel { background-color : green; }"); + } else { + ui->crcLabel->setStyleSheet("QLabel { background-color : red; }"); + } + + return true; + } else { return false; @@ -212,15 +234,24 @@ void FileSourceGui::updateSampleRateAndFrequency() void FileSourceGui::displaySettings() { + blockApplySettings(true); + ui->playLoop->setChecked(m_settings.m_loop); + ui->acceleration->setCurrentIndex(FileSourceSettings::getAccelerationIndex(m_settings.m_accelerationFactor)); + blockApplySettings(false); } void FileSourceGui::sendSettings() { } -void FileSourceGui::on_playLoop_toggled(bool checked __attribute__((unused))) +void FileSourceGui::on_playLoop_toggled(bool checked) { - // TODO: do something about it! + if (m_doApplySettings) + { + m_settings.m_loop = checked; + FileSourceInput::MsgConfigureFileSource *message = FileSourceInput::MsgConfigureFileSource::create(m_settings, false); + m_sampleSource->getInputMessageQueue()->push(message); + } } void FileSourceGui::on_startStop_toggled(bool checked) @@ -266,17 +297,14 @@ void FileSourceGui::on_play_toggled(bool checked) FileSourceInput::MsgConfigureFileSourceWork* message = FileSourceInput::MsgConfigureFileSourceWork::create(checked); m_sampleSource->getInputMessageQueue()->push(message); ui->navTimeSlider->setEnabled(!checked); + ui->acceleration->setEnabled(!checked); m_enableNavTime = !checked; } void FileSourceGui::on_navTimeSlider_valueChanged(int value) { - if (m_enableNavTime && ((value >= 0) && (value <= 100))) + if (m_enableNavTime && ((value >= 0) && (value <= 1000))) { - int t_sec = (m_recordLength * value) / 100; - QTime t(0, 0, 0, 0); - t = t.addSecs(t_sec); - FileSourceInput::MsgConfigureFileSourceSeek* message = FileSourceInput::MsgConfigureFileSourceSeek::create(value); m_sampleSource->getInputMessageQueue()->push(message); } @@ -285,16 +313,27 @@ void FileSourceGui::on_navTimeSlider_valueChanged(int value) void FileSourceGui::on_showFileDialog_clicked(bool checked __attribute__((unused))) { QString fileName = QFileDialog::getOpenFileName(this, - tr("Open I/Q record file"), ".", tr("SDR I/Q Files (*.sdriq)")); + tr("Open I/Q record file"), ".", tr("SDR I/Q Files (*.sdriq)"), 0, QFileDialog::DontUseNativeDialog); if (fileName != "") { m_fileName = fileName; ui->fileNameText->setText(m_fileName); + ui->crcLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }"); configureFileName(); } } +void FileSourceGui::on_acceleration_currentIndexChanged(int index) +{ + if (m_doApplySettings) + { + m_settings.m_accelerationFactor = FileSourceSettings::getAccelerationValue(index); + FileSourceInput::MsgConfigureFileSource *message = FileSourceInput::MsgConfigureFileSource::create(m_settings, false); + m_sampleSource->getInputMessageQueue()->push(message); + } +} + void FileSourceGui::configureFileName() { qDebug() << "FileSourceGui::configureFileName: " << m_fileName.toStdString().c_str(); @@ -317,39 +356,38 @@ void FileSourceGui::updateWithStreamData() ui->play->setEnabled(m_acquisition); QTime recordLength(0, 0, 0, 0); recordLength = recordLength.addSecs(m_recordLength); - QString s_time = recordLength.toString("hh:mm:ss"); + QString s_time = recordLength.toString("HH:mm:ss"); ui->recordLengthText->setText(s_time); - updateWithStreamTime(); // TODO: remove when time data is implemented + updateWithStreamTime(); } void FileSourceGui::updateWithStreamTime() { - int t_sec = 0; - int t_msec = 0; + qint64 t_sec = 0; + qint64 t_msec = 0; if (m_sampleRate > 0){ - t_msec = ((m_samplesCount * 1000) / m_sampleRate) % 1000; t_sec = m_samplesCount / m_sampleRate; + t_msec = (m_samplesCount - (t_sec * m_sampleRate)) * 1000LL / m_sampleRate; } QTime t(0, 0, 0, 0); t = t.addSecs(t_sec); t = t.addMSecs(t_msec); - QString s_timems = t.toString("hh:mm:ss.zzz"); - QString s_time = t.toString("hh:mm:ss"); + QString s_timems = t.toString("HH:mm:ss.zzz"); ui->relTimeText->setText(s_timems); - quint64 startingTimeStampMsec = (quint64) m_startingTimeStamp * 1000LL; + qint64 startingTimeStampMsec = m_startingTimeStamp * 1000LL; QDateTime dt = QDateTime::fromMSecsSinceEpoch(startingTimeStampMsec); - dt = dt.addSecs((quint64) t_sec); - dt = dt.addMSecs((quint64) t_msec); - QString s_date = dt.toString("yyyy-MM-dd hh:mm:ss.zzz"); + dt = dt.addSecs(t_sec); + dt = dt.addMSecs(t_msec); + QString s_date = dt.toString("yyyy-MM-dd HH:mm:ss.zzz"); ui->absTimeText->setText(s_date); if (!m_enableNavTime) { float posRatio = (float) t_sec / (float) m_recordLength; - ui->navTimeSlider->setValue((int) (posRatio * 100.0)); + ui->navTimeSlider->setValue((int) (posRatio * 1000.0)); } } @@ -360,3 +398,42 @@ void FileSourceGui::tick() m_sampleSource->getInputMessageQueue()->push(message); } } + +void FileSourceGui::setAccelerationCombo() +{ + ui->acceleration->blockSignals(true); + ui->acceleration->clear(); + ui->acceleration->addItem(QString("1")); + + for (unsigned int i = 0; i <= FileSourceSettings::m_accelerationMaxScale; i++) + { + QString s; + int m = pow(10.0, i); + int x = 2*m; + setNumberStr(x, s); + ui->acceleration->addItem(s); + x = 5*m; + setNumberStr(x, s); + ui->acceleration->addItem(s); + x = 10*m; + setNumberStr(x, s); + ui->acceleration->addItem(s); + } + + ui->acceleration->blockSignals(false); +} + +void FileSourceGui::setNumberStr(int n, QString& s) +{ + if (n < 1000) { + s = tr("%1").arg(n); + } else if (n < 100000) { + s = tr("%1k").arg(n/1000); + } else if (n < 1000000) { + s = tr("%1e5").arg(n/100000); + } else if (n < 1000000000) { + s = tr("%1M").arg(n/1000000); + } else { + s = tr("%1G").arg(n/1000000000); + } +} diff --git a/plugins/samplesource/filesource/filesourcegui.h b/plugins/samplesource/filesource/filesourcegui.h index a580d528f..2e43c5b42 100644 --- a/plugins/samplesource/filesource/filesourcegui.h +++ b/plugins/samplesource/filesource/filesourcegui.h @@ -65,9 +65,9 @@ private: int m_sampleRate; quint32 m_sampleSize; quint64 m_centerFrequency; - quint32 m_recordLength; - std::time_t m_startingTimeStamp; - int m_samplesCount; + quint64 m_recordLength; + quint64 m_startingTimeStamp; + quint64 m_samplesCount; std::size_t m_tickCount; bool m_enableNavTime; int m_deviceSampleRate; @@ -84,6 +84,8 @@ private: void updateWithAcquisition(); void updateWithStreamData(); void updateWithStreamTime(); + void setAccelerationCombo(); + void setNumberStr(int n, QString& s); private slots: void handleInputMessages(); @@ -92,6 +94,7 @@ private slots: void on_play_toggled(bool checked); void on_navTimeSlider_valueChanged(int value); void on_showFileDialog_clicked(bool checked); + void on_acceleration_currentIndexChanged(int index); void updateStatus(); void tick(); }; diff --git a/plugins/samplesource/filesource/filesourcegui.ui b/plugins/samplesource/filesource/filesourcegui.ui index 20ec4bc82..458043fa2 100644 --- a/plugins/samplesource/filesource/filesourcegui.ui +++ b/plugins/samplesource/filesource/filesourcegui.ui @@ -24,8 +24,11 @@ - Sans Serif + Liberation Sans 9 + 50 + false + false @@ -130,7 +133,7 @@ - DejaVu Sans Mono + Liberation Mono 20 @@ -284,6 +287,28 @@ + + + + Qt::Vertical + + + + + + + + 8 + + + + CRC status: Green: OK Red: KO Grey: undefined + + + CRC + + + @@ -393,6 +418,80 @@ + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + + 8 + + + + Acceleration factor + + + + 1 + + + + + 2 + + + + + 5 + + + + + 10 + + + + + 20 + + + + + 50 + + + + + 100 + + + + + 200 + + + + + 500 + + + + + 1k + + + + @@ -474,7 +573,7 @@ Time navigator - 100 + 1000 1 @@ -503,20 +602,6 @@ - - - - Qt::Horizontal - - - - - - - Qt::Horizontal - - - diff --git a/plugins/samplesource/filesource/filesourceinput.cpp b/plugins/samplesource/filesource/filesourceinput.cpp index 6a6a8f946..74f0b4c26 100644 --- a/plugins/samplesource/filesource/filesourceinput.cpp +++ b/plugins/samplesource/filesource/filesourceinput.cpp @@ -1,385 +1,531 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2015 Edouard Griffiths, F4EXB // -// // -// This program is free software; you can redistribute it and/or modify // -// it under the terms of the GNU General Public License as published by // -// the Free Software Foundation as version 3 of the License, or // -// // -// This program is distributed in the hope that it will be useful, // -// but WITHOUT ANY WARRANTY; without even the implied warranty of // -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // -// GNU General Public License V3 for more details. // -// // -// You should have received a copy of the GNU General Public License // -// along with this program. If not, see . // -/////////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include - -#include "SWGDeviceSettings.h" -#include "SWGFileSourceSettings.h" -#include "SWGDeviceState.h" - -#include "util/simpleserializer.h" -#include "dsp/dspcommands.h" -#include "dsp/dspengine.h" -#include "dsp/filerecord.h" -#include "device/devicesourceapi.h" - -#include "filesourceinput.h" -#include "filesourcethread.h" - -MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgConfigureFileSource, Message) -MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgConfigureFileSourceName, Message) -MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgConfigureFileSourceWork, Message) -MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgConfigureFileSourceSeek, Message) -MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgConfigureFileSourceStreamTiming, Message) -MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgStartStop, Message) -MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgReportFileSourceAcquisition, Message) -MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgReportFileSourceStreamData, Message) -MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgReportFileSourceStreamTiming, Message) - -FileSourceInput::FileSourceInput(DeviceSourceAPI *deviceAPI) : - m_deviceAPI(deviceAPI), - m_settings(), - m_fileSourceThread(NULL), - m_deviceDescription(), - m_fileName("..."), - m_sampleRate(0), - m_sampleSize(0), - m_centerFrequency(0), - m_recordLength(0), - m_startingTimeStamp(0), - m_masterTimer(deviceAPI->getMasterTimer()) -{ - qDebug("FileSourceInput::FileSourceInput: device source engine: %p", m_deviceAPI->getDeviceSourceEngine()); - qDebug("FileSourceInput::FileSourceInput: device source engine message queue: %p", m_deviceAPI->getDeviceEngineInputMessageQueue()); - qDebug("FileSourceInput::FileSourceInput: device source: %p", m_deviceAPI->getDeviceSourceEngine()->getSource()); -} - -FileSourceInput::~FileSourceInput() -{ - stop(); -} - -void FileSourceInput::destroy() -{ - delete this; -} - -void FileSourceInput::openFileStream() -{ - //stopInput(); - - if (m_ifstream.is_open()) { - m_ifstream.close(); - } - - m_ifstream.open(m_fileName.toStdString().c_str(), std::ios::binary | std::ios::ate); - quint64 fileSize = m_ifstream.tellg(); - m_ifstream.seekg(0,std::ios_base::beg); - FileRecord::Header header; - FileRecord::readHeader(m_ifstream, header); - - m_sampleRate = header.sampleRate; - m_centerFrequency = header.centerFrequency; - m_startingTimeStamp = header.startTimeStamp; - m_sampleSize = header.sampleSize; - - if (fileSize > sizeof(FileRecord::Header)) { - m_recordLength = (fileSize - sizeof(FileRecord::Header)) / (4 * m_sampleRate); - } else { - m_recordLength = 0; - } - - qDebug() << "FileSourceInput::openFileStream: " << m_fileName.toStdString().c_str() - << " fileSize: " << fileSize << "bytes" - << " length: " << m_recordLength << " seconds"; - - if (getMessageQueueToGUI()) { - MsgReportFileSourceStreamData *report = MsgReportFileSourceStreamData::create(m_sampleRate, - m_sampleSize, - m_centerFrequency, - m_startingTimeStamp, - m_recordLength); // file stream data - getMessageQueueToGUI()->push(report); - } -} - -void FileSourceInput::seekFileStream(int seekPercentage) -{ - QMutexLocker mutexLocker(&m_mutex); - - if ((m_ifstream.is_open()) && m_fileSourceThread && !m_fileSourceThread->isRunning()) - { - int seekPoint = ((m_recordLength * seekPercentage) / 100) * m_sampleRate; - m_fileSourceThread->setSamplesCount(seekPoint); - seekPoint *= 4; // + sizeof(FileSink::Header) - m_ifstream.clear(); - m_ifstream.seekg(seekPoint + sizeof(FileRecord::Header), std::ios::beg); - } -} - -void FileSourceInput::init() -{ - DSPSignalNotification *notif = new DSPSignalNotification(m_settings.m_sampleRate, m_settings.m_centerFrequency); - m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif); -} - -bool FileSourceInput::start() -{ - QMutexLocker mutexLocker(&m_mutex); - qDebug() << "FileSourceInput::start"; - - if (m_ifstream.tellg() != 0) { - m_ifstream.clear(); - m_ifstream.seekg(sizeof(FileRecord::Header), std::ios::beg); - } - - if(!m_sampleFifo.setSize(m_sampleRate * sizeof(Sample))) { - qCritical("Could not allocate SampleFifo"); - return false; - } - - //openFileStream(); - - if((m_fileSourceThread = new FileSourceThread(&m_ifstream, &m_sampleFifo)) == NULL) { - qCritical("out of memory"); - stop(); - return false; - } - - m_fileSourceThread->setSampleRateAndSize(m_sampleRate, m_sampleSize); - m_fileSourceThread->connectTimer(m_masterTimer); - m_fileSourceThread->startWork(); - m_deviceDescription = "FileSource"; - - mutexLocker.unlock(); - //applySettings(m_generalSettings, m_settings, true); - qDebug("FileSourceInput::startInput: started"); - - if (getMessageQueueToGUI()) { - MsgReportFileSourceAcquisition *report = MsgReportFileSourceAcquisition::create(true); // acquisition on - getMessageQueueToGUI()->push(report); - } - - return true; -} - -void FileSourceInput::stop() -{ - qDebug() << "FileSourceInput::stop"; - QMutexLocker mutexLocker(&m_mutex); - - if(m_fileSourceThread != 0) - { - m_fileSourceThread->stopWork(); - delete m_fileSourceThread; - m_fileSourceThread = 0; - } - - m_deviceDescription.clear(); - - if (getMessageQueueToGUI()) { - MsgReportFileSourceAcquisition *report = MsgReportFileSourceAcquisition::create(false); // acquisition off - getMessageQueueToGUI()->push(report); - } -} - -QByteArray FileSourceInput::serialize() const -{ - return m_settings.serialize(); -} - -bool FileSourceInput::deserialize(const QByteArray& data) -{ - bool success = true; - - if (!m_settings.deserialize(data)) - { - m_settings.resetToDefaults(); - success = false; - } - - MsgConfigureFileSource* message = MsgConfigureFileSource::create(m_settings); - m_inputMessageQueue.push(message); - - if (getMessageQueueToGUI()) - { - MsgConfigureFileSource* messageToGUI = MsgConfigureFileSource::create(m_settings); - getMessageQueueToGUI()->push(messageToGUI); - } - - return success; -} - -const QString& FileSourceInput::getDeviceDescription() const -{ - return m_deviceDescription; -} - -int FileSourceInput::getSampleRate() const -{ - return m_sampleRate; -} - -quint64 FileSourceInput::getCenterFrequency() const -{ - return m_centerFrequency; -} - -void FileSourceInput::setCenterFrequency(qint64 centerFrequency) -{ - FileSourceSettings settings = m_settings; - settings.m_centerFrequency = centerFrequency; - - MsgConfigureFileSource* message = MsgConfigureFileSource::create(m_settings); - m_inputMessageQueue.push(message); - - if (getMessageQueueToGUI()) - { - MsgConfigureFileSource* messageToGUI = MsgConfigureFileSource::create(m_settings); - getMessageQueueToGUI()->push(messageToGUI); - } -} - -std::time_t FileSourceInput::getStartingTimeStamp() const -{ - return m_startingTimeStamp; -} - -bool FileSourceInput::handleMessage(const Message& message) -{ - if (MsgConfigureFileSource::match(message)) - { - MsgConfigureFileSource& conf = (MsgConfigureFileSource&) message; - FileSourceSettings settings = conf.getSettings(); - applySettings(settings); - return true; - } - else if (MsgConfigureFileSourceName::match(message)) - { - MsgConfigureFileSourceName& conf = (MsgConfigureFileSourceName&) message; - m_fileName = conf.getFileName(); - openFileStream(); - return true; - } - else if (MsgConfigureFileSourceWork::match(message)) - { - MsgConfigureFileSourceWork& conf = (MsgConfigureFileSourceWork&) message; - bool working = conf.isWorking(); - - if (m_fileSourceThread != 0) - { - if (working) - { - m_fileSourceThread->startWork(); - /* - MsgReportFileSourceStreamTiming *report = - MsgReportFileSourceStreamTiming::create(m_fileSourceThread->getSamplesCount()); - getOutputMessageQueueToGUI()->push(report);*/ - } - else - { - m_fileSourceThread->stopWork(); - } - } - - return true; - } - else if (MsgConfigureFileSourceSeek::match(message)) - { - MsgConfigureFileSourceSeek& conf = (MsgConfigureFileSourceSeek&) message; - int seekPercentage = conf.getPercentage(); - seekFileStream(seekPercentage); - - return true; - } - else if (MsgConfigureFileSourceStreamTiming::match(message)) - { - MsgReportFileSourceStreamTiming *report; - - if (m_fileSourceThread != 0) - { - if (getMessageQueueToGUI()) { - report = MsgReportFileSourceStreamTiming::create(m_fileSourceThread->getSamplesCount()); - getMessageQueueToGUI()->push(report); - } - } - - return true; - } - else if (MsgStartStop::match(message)) - { - MsgStartStop& cmd = (MsgStartStop&) message; - qDebug() << "FileSourceInput::handleMessage: MsgStartStop: " << (cmd.getStartStop() ? "start" : "stop"); - - if (cmd.getStartStop()) - { - if (m_deviceAPI->initAcquisition()) - { - m_deviceAPI->startAcquisition(); - DSPEngine::instance()->startAudioOutput(); - } - } - else - { - m_deviceAPI->stopAcquisition(); - DSPEngine::instance()->stopAudioOutput(); - } - - return true; - } - else - { - return false; - } -} - -bool FileSourceInput::applySettings(const FileSourceSettings& settings, bool force) -{ - if ((m_settings.m_centerFrequency != settings.m_centerFrequency) || force) { - m_centerFrequency = settings.m_centerFrequency; - } - - m_settings = settings; - return true; -} - -int FileSourceInput::webapiSettingsGet( - SWGSDRangel::SWGDeviceSettings& response, - QString& errorMessage __attribute__((unused))) -{ - response.setFileSourceSettings(new SWGSDRangel::SWGFileSourceSettings()); - response.getFileSourceSettings()->setFileName(new QString(m_settings.m_fileName)); - return 200; -} - -int FileSourceInput::webapiRunGet( - SWGSDRangel::SWGDeviceState& response, - QString& errorMessage __attribute__((unused))) -{ - m_deviceAPI->getDeviceEngineStateStr(*response.getState()); - return 200; -} - -int FileSourceInput::webapiRun( - bool run, - SWGSDRangel::SWGDeviceState& response, - QString& errorMessage __attribute__((unused))) -{ - m_deviceAPI->getDeviceEngineStateStr(*response.getState()); - MsgStartStop *message = MsgStartStop::create(run); - m_inputMessageQueue.push(message); - - if (getMessageQueueToGUI()) // forward to GUI if any - { - MsgStartStop *msgToGUI = MsgStartStop::create(run); - getMessageQueueToGUI()->push(msgToGUI); - } - - return 200; -} - +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include + +#include "SWGDeviceSettings.h" +#include "SWGFileSourceSettings.h" +#include "SWGDeviceState.h" +#include "SWGDeviceReport.h" +#include "SWGFileSourceSettings.h" + +#include "util/simpleserializer.h" +#include "dsp/dspcommands.h" +#include "dsp/dspengine.h" +#include "dsp/filerecord.h" +#include "device/devicesourceapi.h" + +#include "filesourceinput.h" +#include "filesourcethread.h" + +MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgConfigureFileSource, Message) +MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgConfigureFileSourceName, Message) +MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgConfigureFileSourceWork, Message) +MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgConfigureFileSourceSeek, Message) +MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgConfigureFileSourceStreamTiming, Message) +MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgStartStop, Message) +MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgPlayPause, Message) +MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgReportFileSourceAcquisition, Message) +MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgReportFileSourceStreamData, Message) +MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgReportFileSourceStreamTiming, Message) +MESSAGE_CLASS_DEFINITION(FileSourceInput::MsgReportHeaderCRC, Message) + +FileSourceInput::FileSourceInput(DeviceSourceAPI *deviceAPI) : + m_deviceAPI(deviceAPI), + m_settings(), + m_fileSourceThread(NULL), + m_deviceDescription(), + m_fileName("..."), + m_sampleRate(0), + m_sampleSize(0), + m_centerFrequency(0), + m_recordLength(0), + m_startingTimeStamp(0), + m_masterTimer(deviceAPI->getMasterTimer()) +{ + qDebug("FileSourceInput::FileSourceInput: device source engine: %p", m_deviceAPI->getDeviceSourceEngine()); + qDebug("FileSourceInput::FileSourceInput: device source engine message queue: %p", m_deviceAPI->getDeviceEngineInputMessageQueue()); + qDebug("FileSourceInput::FileSourceInput: device source: %p", m_deviceAPI->getDeviceSourceEngine()->getSource()); +} + +FileSourceInput::~FileSourceInput() +{ + stop(); +} + +void FileSourceInput::destroy() +{ + delete this; +} + +void FileSourceInput::openFileStream() +{ + //stopInput(); + + if (m_ifstream.is_open()) { + m_ifstream.close(); + } + + m_ifstream.open(m_fileName.toStdString().c_str(), std::ios::binary | std::ios::ate); + quint64 fileSize = m_ifstream.tellg(); + + if (fileSize > sizeof(FileRecord::Header)) + { + FileRecord::Header header; + m_ifstream.seekg(0,std::ios_base::beg); + bool crcOK = FileRecord::readHeader(m_ifstream, header); + m_sampleRate = header.sampleRate; + m_centerFrequency = header.centerFrequency; + m_startingTimeStamp = header.startTimeStamp; + m_sampleSize = header.sampleSize; + QString crcHex = QString("%1").arg(header.crc32 , 0, 16); + + if (crcOK) + { + qDebug("FileSourceInput::openFileStream: CRC32 OK for header: %s", qPrintable(crcHex)); + m_recordLength = (fileSize - sizeof(FileRecord::Header)) / ((m_sampleSize == 24 ? 8 : 4) * m_sampleRate); + } + else + { + qCritical("FileSourceInput::openFileStream: bad CRC32 for header: %s", qPrintable(crcHex)); + m_recordLength = 0; + } + + if (getMessageQueueToGUI()) { + MsgReportHeaderCRC *report = MsgReportHeaderCRC::create(crcOK); + getMessageQueueToGUI()->push(report); + } + } + else + { + m_recordLength = 0; + } + + qDebug() << "FileSourceInput::openFileStream: " << m_fileName.toStdString().c_str() + << " fileSize: " << fileSize << " bytes" + << " length: " << m_recordLength << " seconds" + << " sample rate: " << m_sampleRate << " S/s" + << " center frequency: " << m_centerFrequency << " Hz" + << " sample size: " << m_sampleSize << " bits"; + + if (getMessageQueueToGUI()) { + MsgReportFileSourceStreamData *report = MsgReportFileSourceStreamData::create(m_sampleRate, + m_sampleSize, + m_centerFrequency, + m_startingTimeStamp, + m_recordLength); // file stream data + getMessageQueueToGUI()->push(report); + } + + if (m_recordLength == 0) { + m_ifstream.close(); + } +} + +void FileSourceInput::seekFileStream(int seekMillis) +{ + QMutexLocker mutexLocker(&m_mutex); + + if ((m_ifstream.is_open()) && m_fileSourceThread && !m_fileSourceThread->isRunning()) + { + quint64 seekPoint = ((m_recordLength * seekMillis) / 1000) * m_sampleRate; + m_fileSourceThread->setSamplesCount(seekPoint); + seekPoint *= (m_sampleSize == 24 ? 8 : 4); // + sizeof(FileSink::Header) + m_ifstream.clear(); + m_ifstream.seekg(seekPoint + sizeof(FileRecord::Header), std::ios::beg); + } +} + +void FileSourceInput::init() +{ + DSPSignalNotification *notif = new DSPSignalNotification(m_settings.m_sampleRate, m_settings.m_centerFrequency); + m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif); +} + +bool FileSourceInput::start() +{ + if (!m_ifstream.is_open()) + { + qWarning("FileSourceInput::start: file not open. not starting"); + return false; + } + + QMutexLocker mutexLocker(&m_mutex); + qDebug() << "FileSourceInput::start"; + + if (m_ifstream.tellg() != 0) { + m_ifstream.clear(); + m_ifstream.seekg(sizeof(FileRecord::Header), std::ios::beg); + } + + if(!m_sampleFifo.setSize(m_settings.m_accelerationFactor * m_sampleRate * sizeof(Sample))) { + qCritical("Could not allocate SampleFifo"); + return false; + } + + m_fileSourceThread = new FileSourceThread(&m_ifstream, &m_sampleFifo, m_masterTimer, &m_inputMessageQueue); + m_fileSourceThread->setSampleRateAndSize(m_settings.m_accelerationFactor * m_sampleRate, m_sampleSize); // Fast Forward: 1 corresponds to live. 1/2 is half speed, 2 is double speed + m_fileSourceThread->startWork(); + m_deviceDescription = "FileSource"; + + mutexLocker.unlock(); + qDebug("FileSourceInput::startInput: started"); + + if (getMessageQueueToGUI()) { + MsgReportFileSourceAcquisition *report = MsgReportFileSourceAcquisition::create(true); // acquisition on + getMessageQueueToGUI()->push(report); + } + + return true; +} + +void FileSourceInput::stop() +{ + qDebug() << "FileSourceInput::stop"; + QMutexLocker mutexLocker(&m_mutex); + + if(m_fileSourceThread != 0) + { + m_fileSourceThread->stopWork(); + delete m_fileSourceThread; + m_fileSourceThread = 0; + } + + m_deviceDescription.clear(); + + if (getMessageQueueToGUI()) { + MsgReportFileSourceAcquisition *report = MsgReportFileSourceAcquisition::create(false); // acquisition off + getMessageQueueToGUI()->push(report); + } +} + +QByteArray FileSourceInput::serialize() const +{ + return m_settings.serialize(); +} + +bool FileSourceInput::deserialize(const QByteArray& data) +{ + bool success = true; + + if (!m_settings.deserialize(data)) + { + m_settings.resetToDefaults(); + success = false; + } + + MsgConfigureFileSource* message = MsgConfigureFileSource::create(m_settings, true); + m_inputMessageQueue.push(message); + + if (getMessageQueueToGUI()) + { + MsgConfigureFileSource* messageToGUI = MsgConfigureFileSource::create(m_settings, true); + getMessageQueueToGUI()->push(messageToGUI); + } + + return success; +} + +const QString& FileSourceInput::getDeviceDescription() const +{ + return m_deviceDescription; +} + +int FileSourceInput::getSampleRate() const +{ + return m_sampleRate; +} + +quint64 FileSourceInput::getCenterFrequency() const +{ + return m_centerFrequency; +} + +void FileSourceInput::setCenterFrequency(qint64 centerFrequency) +{ + FileSourceSettings settings = m_settings; + settings.m_centerFrequency = centerFrequency; + + MsgConfigureFileSource* message = MsgConfigureFileSource::create(m_settings, false); + m_inputMessageQueue.push(message); + + if (getMessageQueueToGUI()) + { + MsgConfigureFileSource* messageToGUI = MsgConfigureFileSource::create(m_settings, false); + getMessageQueueToGUI()->push(messageToGUI); + } +} + +quint64 FileSourceInput::getStartingTimeStamp() const +{ + return m_startingTimeStamp; +} + +bool FileSourceInput::handleMessage(const Message& message) +{ + if (MsgConfigureFileSource::match(message)) + { + MsgConfigureFileSource& conf = (MsgConfigureFileSource&) message; + FileSourceSettings settings = conf.getSettings(); + applySettings(settings); + return true; + } + else if (MsgConfigureFileSourceName::match(message)) + { + MsgConfigureFileSourceName& conf = (MsgConfigureFileSourceName&) message; + m_fileName = conf.getFileName(); + openFileStream(); + return true; + } + else if (MsgConfigureFileSourceWork::match(message)) + { + MsgConfigureFileSourceWork& conf = (MsgConfigureFileSourceWork&) message; + bool working = conf.isWorking(); + + if (m_fileSourceThread != 0) + { + if (working) { + m_fileSourceThread->startWork(); + } else { + m_fileSourceThread->stopWork(); + } + } + + return true; + } + else if (MsgConfigureFileSourceSeek::match(message)) + { + MsgConfigureFileSourceSeek& conf = (MsgConfigureFileSourceSeek&) message; + int seekMillis = conf.getMillis(); + seekFileStream(seekMillis); + + return true; + } + else if (MsgConfigureFileSourceStreamTiming::match(message)) + { + MsgReportFileSourceStreamTiming *report; + + if (m_fileSourceThread != 0) + { + if (getMessageQueueToGUI()) + { + report = MsgReportFileSourceStreamTiming::create(m_fileSourceThread->getSamplesCount()); + getMessageQueueToGUI()->push(report); + } + } + + return true; + } + else if (MsgStartStop::match(message)) + { + MsgStartStop& cmd = (MsgStartStop&) message; + qDebug() << "FileSourceInput::handleMessage: MsgStartStop: " << (cmd.getStartStop() ? "start" : "stop"); + + if (cmd.getStartStop()) + { + if (m_deviceAPI->initAcquisition()) + { + m_deviceAPI->startAcquisition(); + } + } + else + { + m_deviceAPI->stopAcquisition(); + } + + return true; + } + else if (FileSourceThread::MsgReportEOF::match(message)) + { + qDebug() << "FileSourceInput::handleMessage: MsgReportEOF"; + m_fileSourceThread->stopWork(); + + if (getMessageQueueToGUI()) + { + MsgReportFileSourceStreamTiming *report = MsgReportFileSourceStreamTiming::create(m_fileSourceThread->getSamplesCount()); + getMessageQueueToGUI()->push(report); + } + + if (m_settings.m_loop) + { + seekFileStream(0); + m_fileSourceThread->startWork(); + } + else + { + if (getMessageQueueToGUI()) + { + MsgPlayPause *report = MsgPlayPause::create(false); + getMessageQueueToGUI()->push(report); + } + } + + return true; + } + else + { + return false; + } +} + +bool FileSourceInput::applySettings(const FileSourceSettings& settings, bool force) +{ + if ((m_settings.m_centerFrequency != settings.m_centerFrequency) || force) { + m_centerFrequency = settings.m_centerFrequency; + } + + if ((m_settings.m_accelerationFactor != settings.m_accelerationFactor) || force) + { + if (m_fileSourceThread) + { + QMutexLocker mutexLocker(&m_mutex); + if (!m_sampleFifo.setSize(m_settings.m_accelerationFactor * m_sampleRate * sizeof(Sample))) { + qCritical("FileSourceInput::applySettings: could not reallocate sample FIFO size to %lu", + m_settings.m_accelerationFactor * m_sampleRate * sizeof(Sample)); + } + m_fileSourceThread->setSampleRateAndSize(settings.m_accelerationFactor * m_sampleRate, m_sampleSize); // Fast Forward: 1 corresponds to live. 1/2 is half speed, 2 is double speed + } + } + + m_settings = settings; + return true; +} + +int FileSourceInput::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setFileSourceSettings(new SWGSDRangel::SWGFileSourceSettings()); + response.getFileSourceSettings()->init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int FileSourceInput::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage __attribute__((unused))) +{ + FileSourceSettings settings = m_settings; + + if (deviceSettingsKeys.contains("fileName")) { + settings.m_fileName = *response.getFileSourceSettings()->getFileName(); + } + if (deviceSettingsKeys.contains("accelerationFactor")) { + settings.m_accelerationFactor = response.getFileSourceSettings()->getAccelerationFactor(); + } + if (deviceSettingsKeys.contains("loop")) { + settings.m_loop = response.getFileSourceSettings()->getLoop() != 0; + } + + MsgConfigureFileSource *msg = MsgConfigureFileSource::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureFileSource *msgToGUI = MsgConfigureFileSource::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +int FileSourceInput::webapiRunGet( + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage __attribute__((unused))) +{ + m_deviceAPI->getDeviceEngineStateStr(*response.getState()); + return 200; +} + +int FileSourceInput::webapiRun( + bool run, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage __attribute__((unused))) +{ + m_deviceAPI->getDeviceEngineStateStr(*response.getState()); + MsgStartStop *message = MsgStartStop::create(run); + m_inputMessageQueue.push(message); + + if (getMessageQueueToGUI()) // forward to GUI if any + { + MsgStartStop *msgToGUI = MsgStartStop::create(run); + getMessageQueueToGUI()->push(msgToGUI); + } + + return 200; +} + +int FileSourceInput::webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setFileSourceReport(new SWGSDRangel::SWGFileSourceReport()); + response.getFileSourceReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} + +void FileSourceInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const FileSourceSettings& settings) +{ + response.getFileSourceSettings()->setFileName(new QString(settings.m_fileName)); + response.getFileSourceSettings()->setAccelerationFactor(settings.m_accelerationFactor); + response.getFileSourceSettings()->setLoop(settings.m_loop ? 1 : 0); + +} + +void FileSourceInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + qint64 t_sec = 0; + qint64 t_msec = 0; + quint64 samplesCount = 0; + + if (m_fileSourceThread) { + samplesCount = m_fileSourceThread->getSamplesCount(); + } + + if (m_sampleRate > 0) + { + t_sec = samplesCount / m_sampleRate; + t_msec = (samplesCount - (t_sec * m_sampleRate)) * 1000 / m_sampleRate; + } + + QTime t(0, 0, 0, 0); + t = t.addSecs(t_sec); + t = t.addMSecs(t_msec); + response.getFileSourceReport()->setElapsedTime(new QString(t.toString("HH:mm:ss.zzz"))); + + qint64 startingTimeStampMsec = m_startingTimeStamp * 1000LL; + QDateTime dt = QDateTime::fromMSecsSinceEpoch(startingTimeStampMsec); + dt = dt.addSecs(t_sec); + dt = dt.addMSecs(t_msec); + response.getFileSourceReport()->setAbsoluteTime(new QString(dt.toString("yyyy-MM-dd HH:mm:ss.zzz"))); + + QTime recordLength(0, 0, 0, 0); + recordLength = recordLength.addSecs(m_recordLength); + response.getFileSourceReport()->setDurationTime(new QString(recordLength.toString("HH:mm:ss"))); + + response.getFileSourceReport()->setFileName(new QString(m_fileName)); + response.getFileSourceReport()->setSampleRate(m_sampleRate); + response.getFileSourceReport()->setSampleSize(m_sampleSize); +} + + diff --git a/plugins/samplesource/filesource/filesourceinput.h b/plugins/samplesource/filesource/filesourceinput.h index 075a40726..adef3b065 100644 --- a/plugins/samplesource/filesource/filesourceinput.h +++ b/plugins/samplesource/filesource/filesourceinput.h @@ -1,284 +1,337 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2015 Edouard Griffiths, F4EXB // -// // -// This program is free software; you can redistribute it and/or modify // -// it under the terms of the GNU General Public License as published by // -// the Free Software Foundation as version 3 of the License, or // -// // -// This program is distributed in the hope that it will be useful, // -// but WITHOUT ANY WARRANTY; without even the implied warranty of // -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // -// GNU General Public License V3 for more details. // -// // -// You should have received a copy of the GNU General Public License // -// along with this program. If not, see . // -/////////////////////////////////////////////////////////////////////////////////// - -#ifndef INCLUDE_FILESOURCEINPUT_H -#define INCLUDE_FILESOURCEINPUT_H - -#include -#include -#include -#include -#include -#include - -#include -#include "filesourcesettings.h" - -class FileSourceThread; -class DeviceSourceAPI; - -class FileSourceInput : public DeviceSampleSource { -public: - class MsgConfigureFileSource : public Message { - MESSAGE_CLASS_DECLARATION - - public: - const FileSourceSettings& getSettings() const { return m_settings; } - - static MsgConfigureFileSource* create(const FileSourceSettings& settings) - { - return new MsgConfigureFileSource(settings); - } - - private: - FileSourceSettings m_settings; - - MsgConfigureFileSource(const FileSourceSettings& settings) : - Message(), - m_settings(settings) - { } - }; - - class MsgConfigureFileSourceName : public Message { - MESSAGE_CLASS_DECLARATION - - public: - const QString& getFileName() const { return m_fileName; } - - static MsgConfigureFileSourceName* create(const QString& fileName) - { - return new MsgConfigureFileSourceName(fileName); - } - - private: - QString m_fileName; - - MsgConfigureFileSourceName(const QString& fileName) : - Message(), - m_fileName(fileName) - { } - }; - - class MsgConfigureFileSourceWork : public Message { - MESSAGE_CLASS_DECLARATION - - public: - bool isWorking() const { return m_working; } - - static MsgConfigureFileSourceWork* create(bool working) - { - return new MsgConfigureFileSourceWork(working); - } - - private: - bool m_working; - - MsgConfigureFileSourceWork(bool working) : - Message(), - m_working(working) - { } - }; - - class MsgConfigureFileSourceStreamTiming : public Message { - MESSAGE_CLASS_DECLARATION - - public: - - static MsgConfigureFileSourceStreamTiming* create() - { - return new MsgConfigureFileSourceStreamTiming(); - } - - private: - - MsgConfigureFileSourceStreamTiming() : - Message() - { } - }; - - class MsgConfigureFileSourceSeek : public Message { - MESSAGE_CLASS_DECLARATION - - public: - int getPercentage() const { return m_seekPercentage; } - - static MsgConfigureFileSourceSeek* create(int seekPercentage) - { - return new MsgConfigureFileSourceSeek(seekPercentage); - } - - protected: - int m_seekPercentage; //!< percentage of seek position from the beginning 0..100 - - MsgConfigureFileSourceSeek(int seekPercentage) : - Message(), - m_seekPercentage(seekPercentage) - { } - }; - - class MsgReportFileSourceAcquisition : public Message { - MESSAGE_CLASS_DECLARATION - - public: - bool getAcquisition() const { return m_acquisition; } - - static MsgReportFileSourceAcquisition* create(bool acquisition) - { - return new MsgReportFileSourceAcquisition(acquisition); - } - - protected: - bool m_acquisition; - - MsgReportFileSourceAcquisition(bool acquisition) : - Message(), - m_acquisition(acquisition) - { } - }; - - class MsgStartStop : public Message { - MESSAGE_CLASS_DECLARATION - - public: - bool getStartStop() const { return m_startStop; } - - static MsgStartStop* create(bool startStop) { - return new MsgStartStop(startStop); - } - - protected: - bool m_startStop; - - MsgStartStop(bool startStop) : - Message(), - m_startStop(startStop) - { } - }; - - class MsgReportFileSourceStreamData : public Message { - MESSAGE_CLASS_DECLARATION - - public: - int getSampleRate() const { return m_sampleRate; } - quint32 getSampleSize() const { return m_sampleSize; } - quint64 getCenterFrequency() const { return m_centerFrequency; } - std::time_t getStartingTimeStamp() const { return m_startingTimeStamp; } - quint32 getRecordLength() const { return m_recordLength; } - - static MsgReportFileSourceStreamData* create(int sampleRate, - quint32 sampleSize, - quint64 centerFrequency, - std::time_t startingTimeStamp, - quint32 recordLength) - { - return new MsgReportFileSourceStreamData(sampleRate, sampleSize, centerFrequency, startingTimeStamp, recordLength); - } - - protected: - int m_sampleRate; - quint32 m_sampleSize; - quint64 m_centerFrequency; - std::time_t m_startingTimeStamp; - quint32 m_recordLength; - - MsgReportFileSourceStreamData(int sampleRate, - quint32 sampleSize, - quint64 centerFrequency, - std::time_t startingTimeStamp, - quint32 recordLength) : - Message(), - m_sampleRate(sampleRate), - m_sampleSize(sampleSize), - m_centerFrequency(centerFrequency), - m_startingTimeStamp(startingTimeStamp), - m_recordLength(recordLength) - { } - }; - - class MsgReportFileSourceStreamTiming : public Message { - MESSAGE_CLASS_DECLARATION - - public: - std::size_t getSamplesCount() const { return m_samplesCount; } - - static MsgReportFileSourceStreamTiming* create(std::size_t samplesCount) - { - return new MsgReportFileSourceStreamTiming(samplesCount); - } - - protected: - std::size_t m_samplesCount; - - MsgReportFileSourceStreamTiming(std::size_t samplesCount) : - Message(), - m_samplesCount(samplesCount) - { } - }; - - FileSourceInput(DeviceSourceAPI *deviceAPI); - virtual ~FileSourceInput(); - virtual void destroy(); - - virtual void init(); - virtual bool start(); - virtual void stop(); - - virtual QByteArray serialize() const; - virtual bool deserialize(const QByteArray& data); - - virtual void setMessageQueueToGUI(MessageQueue *queue) { m_guiMessageQueue = queue; } - virtual const QString& getDeviceDescription() const; - virtual int getSampleRate() const; - virtual quint64 getCenterFrequency() const; - virtual void setCenterFrequency(qint64 centerFrequency); - std::time_t getStartingTimeStamp() const; - - virtual bool handleMessage(const Message& message); - - virtual int webapiSettingsGet( - SWGSDRangel::SWGDeviceSettings& response, - QString& errorMessage); - - virtual int webapiRunGet( - SWGSDRangel::SWGDeviceState& response, - QString& errorMessage); - - virtual int webapiRun( - bool run, - SWGSDRangel::SWGDeviceState& response, - QString& errorMessage); - - private: - DeviceSourceAPI *m_deviceAPI; - QMutex m_mutex; - FileSourceSettings m_settings; - std::ifstream m_ifstream; - FileSourceThread* m_fileSourceThread; - QString m_deviceDescription; - QString m_fileName; - int m_sampleRate; - quint32 m_sampleSize; - quint64 m_centerFrequency; - quint32 m_recordLength; //!< record length in seconds computed from file size - std::time_t m_startingTimeStamp; - const QTimer& m_masterTimer; - - void openFileStream(); - void seekFileStream(int seekPercentage); - bool applySettings(const FileSourceSettings& settings, bool force = false); -}; - -#endif // INCLUDE_FILESOURCEINPUT_H +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_FILESOURCEINPUT_H +#define INCLUDE_FILESOURCEINPUT_H + +#include +#include +#include +#include +#include +#include + +#include +#include "filesourcesettings.h" + +class FileSourceThread; +class DeviceSourceAPI; + +class FileSourceInput : public DeviceSampleSource { +public: + class MsgConfigureFileSource : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const FileSourceSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureFileSource* create(const FileSourceSettings& settings, bool force) + { + return new MsgConfigureFileSource(settings, force); + } + + private: + FileSourceSettings m_settings; + bool m_force; + + MsgConfigureFileSource(const FileSourceSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgConfigureFileSourceName : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const QString& getFileName() const { return m_fileName; } + + static MsgConfigureFileSourceName* create(const QString& fileName) + { + return new MsgConfigureFileSourceName(fileName); + } + + private: + QString m_fileName; + + MsgConfigureFileSourceName(const QString& fileName) : + Message(), + m_fileName(fileName) + { } + }; + + class MsgConfigureFileSourceWork : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool isWorking() const { return m_working; } + + static MsgConfigureFileSourceWork* create(bool working) + { + return new MsgConfigureFileSourceWork(working); + } + + private: + bool m_working; + + MsgConfigureFileSourceWork(bool working) : + Message(), + m_working(working) + { } + }; + + class MsgConfigureFileSourceStreamTiming : public Message { + MESSAGE_CLASS_DECLARATION + + public: + + static MsgConfigureFileSourceStreamTiming* create() + { + return new MsgConfigureFileSourceStreamTiming(); + } + + private: + + MsgConfigureFileSourceStreamTiming() : + Message() + { } + }; + + class MsgConfigureFileSourceSeek : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getMillis() const { return m_seekMillis; } + + static MsgConfigureFileSourceSeek* create(int seekMillis) + { + return new MsgConfigureFileSourceSeek(seekMillis); + } + + protected: + int m_seekMillis; //!< millis of seek position from the beginning 0..1000 + + MsgConfigureFileSourceSeek(int seekMillis) : + Message(), + m_seekMillis(seekMillis) + { } + }; + + class MsgReportFileSourceAcquisition : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getAcquisition() const { return m_acquisition; } + + static MsgReportFileSourceAcquisition* create(bool acquisition) + { + return new MsgReportFileSourceAcquisition(acquisition); + } + + protected: + bool m_acquisition; + + MsgReportFileSourceAcquisition(bool acquisition) : + Message(), + m_acquisition(acquisition) + { } + }; + + class MsgStartStop : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getStartStop() const { return m_startStop; } + + static MsgStartStop* create(bool startStop) { + return new MsgStartStop(startStop); + } + + protected: + bool m_startStop; + + MsgStartStop(bool startStop) : + Message(), + m_startStop(startStop) + { } + }; + + class MsgPlayPause : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getPlayPause() const { return m_playPause; } + + static MsgPlayPause* create(bool playPause) { + return new MsgPlayPause(playPause); + } + + protected: + bool m_playPause; + + MsgPlayPause(bool playPause) : + Message(), + m_playPause(playPause) + { } + }; + + class MsgReportFileSourceStreamData : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getSampleRate() const { return m_sampleRate; } + quint32 getSampleSize() const { return m_sampleSize; } + quint64 getCenterFrequency() const { return m_centerFrequency; } + quint64 getStartingTimeStamp() const { return m_startingTimeStamp; } + quint64 getRecordLength() const { return m_recordLength; } + + static MsgReportFileSourceStreamData* create(int sampleRate, + quint32 sampleSize, + quint64 centerFrequency, + quint64 startingTimeStamp, + quint64 recordLength) + { + return new MsgReportFileSourceStreamData(sampleRate, sampleSize, centerFrequency, startingTimeStamp, recordLength); + } + + protected: + int m_sampleRate; + quint32 m_sampleSize; + quint64 m_centerFrequency; + quint64 m_startingTimeStamp; + quint64 m_recordLength; + + MsgReportFileSourceStreamData(int sampleRate, + quint32 sampleSize, + quint64 centerFrequency, + quint64 startingTimeStamp, + quint64 recordLength) : + Message(), + m_sampleRate(sampleRate), + m_sampleSize(sampleSize), + m_centerFrequency(centerFrequency), + m_startingTimeStamp(startingTimeStamp), + m_recordLength(recordLength) + { } + }; + + class MsgReportFileSourceStreamTiming : public Message { + MESSAGE_CLASS_DECLARATION + + public: + quint64 getSamplesCount() const { return m_samplesCount; } + + static MsgReportFileSourceStreamTiming* create(quint64 samplesCount) + { + return new MsgReportFileSourceStreamTiming(samplesCount); + } + + protected: + quint64 m_samplesCount; + + MsgReportFileSourceStreamTiming(quint64 samplesCount) : + Message(), + m_samplesCount(samplesCount) + { } + }; + + class MsgReportHeaderCRC : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool isOK() const { return m_ok; } + + static MsgReportHeaderCRC* create(bool ok) { + return new MsgReportHeaderCRC(ok); + } + + protected: + bool m_ok; + + MsgReportHeaderCRC(bool ok) : + Message(), + m_ok(ok) + { } + }; + + FileSourceInput(DeviceSourceAPI *deviceAPI); + virtual ~FileSourceInput(); + virtual void destroy(); + + virtual void init(); + virtual bool start(); + virtual void stop(); + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual void setMessageQueueToGUI(MessageQueue *queue) { m_guiMessageQueue = queue; } + virtual const QString& getDeviceDescription() const; + virtual int getSampleRate() const; + virtual quint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + quint64 getStartingTimeStamp() const; + + virtual bool handleMessage(const Message& message); + + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + + virtual int webapiRunGet( + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage); + + virtual int webapiRun( + bool run, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + + private: + DeviceSourceAPI *m_deviceAPI; + QMutex m_mutex; + FileSourceSettings m_settings; + std::ifstream m_ifstream; + FileSourceThread* m_fileSourceThread; + QString m_deviceDescription; + QString m_fileName; + int m_sampleRate; + quint32 m_sampleSize; + quint64 m_centerFrequency; + quint64 m_recordLength; //!< record length in seconds computed from file size + quint64 m_startingTimeStamp; + const QTimer& m_masterTimer; + + void openFileStream(); + void seekFileStream(int seekMillis); + bool applySettings(const FileSourceSettings& settings, bool force = false); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const FileSourceSettings& settings); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); +}; + +#endif // INCLUDE_FILESOURCEINPUT_H diff --git a/plugins/samplesource/filesource/filesourceplugin.cpp b/plugins/samplesource/filesource/filesourceplugin.cpp index f4a962afd..2f4de4d84 100644 --- a/plugins/samplesource/filesource/filesourceplugin.cpp +++ b/plugins/samplesource/filesource/filesourceplugin.cpp @@ -29,7 +29,7 @@ const PluginDescriptor FileSourcePlugin::m_pluginDescriptor = { QString("File source input"), - QString("3.11.1"), + QString("4.2.2"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/filesource/filesourcesettings.cpp b/plugins/samplesource/filesource/filesourcesettings.cpp index f08701703..950f1c1af 100644 --- a/plugins/samplesource/filesource/filesourcesettings.cpp +++ b/plugins/samplesource/filesource/filesourcesettings.cpp @@ -18,6 +18,8 @@ #include "filesourcesettings.h" +const unsigned int FileSourceSettings::m_accelerationMaxScale = 2; + FileSourceSettings::FileSourceSettings() { resetToDefaults(); @@ -28,12 +30,16 @@ void FileSourceSettings::resetToDefaults() m_centerFrequency = 435000000; m_sampleRate = 48000; m_fileName = "./test.sdriq"; + m_accelerationFactor = 1; + m_loop = true; } QByteArray FileSourceSettings::serialize() const { SimpleSerializer s(1); s.writeString(1, m_fileName); + s.writeU32(2, m_accelerationFactor); + s.writeBool(3, m_loop); return s.final(); } @@ -48,6 +54,8 @@ bool FileSourceSettings::deserialize(const QByteArray& data) if(d.getVersion() == 1) { d.readString(1, &m_fileName, "./test.sdriq"); + d.readU32(2, &m_accelerationFactor, 1); + d.readBool(3, &m_loop, true); return true; } else { resetToDefaults(); @@ -55,6 +63,60 @@ bool FileSourceSettings::deserialize(const QByteArray& data) } } +int FileSourceSettings::getAccelerationIndex(int accelerationValue) +{ + if (accelerationValue <= 1) { + return 0; + } + + int v = accelerationValue; + int j = 0; + + for (int i = 0; i <= accelerationValue; i++) + { + if (v < 20) + { + if (v < 2) { + j = 0; + } else if (v < 5) { + j = 1; + } else if (v < 10) { + j = 2; + } else { + j = 3; + } + + return 3*i + j; + } + + v /= 10; + } + + return 3*m_accelerationMaxScale + 3; +} + +int FileSourceSettings::getAccelerationValue(int accelerationIndex) +{ + if (accelerationIndex <= 0) { + return 1; + } + + unsigned int v = accelerationIndex - 1; + int m = pow(10.0, v/3 > m_accelerationMaxScale ? m_accelerationMaxScale : v/3); + int x; + + if (v % 3 == 0) { + x = 2; + } else if (v % 3 == 1) { + x = 5; + } else if (v % 3 == 2) { + x = 10; + } + + return x * m; +} + + diff --git a/plugins/samplesource/filesource/filesourcesettings.h b/plugins/samplesource/filesource/filesourcesettings.h index 8bcc065f5..d355e842c 100644 --- a/plugins/samplesource/filesource/filesourcesettings.h +++ b/plugins/samplesource/filesource/filesourcesettings.h @@ -24,6 +24,9 @@ struct FileSourceSettings { quint64 m_centerFrequency; qint32 m_sampleRate; QString m_fileName; + quint32 m_accelerationFactor; + bool m_loop; + static const unsigned int m_accelerationMaxScale; //!< Max power of 10 multiplier to 2,5,10 base ex: 2 -> 2,5,10,20,50,100,200,500,1000 FileSourceSettings(); ~FileSourceSettings() {} @@ -31,6 +34,8 @@ struct FileSourceSettings { void resetToDefaults(); QByteArray serialize() const; bool deserialize(const QByteArray& data); + static int getAccelerationIndex(int averaging); + static int getAccelerationValue(int averagingIndex); }; #endif /* PLUGINS_SAMPLESOURCE_FILESOURCE_FILESOURCESETTINGS_H_ */ diff --git a/plugins/samplesource/filesource/filesourcethread.cpp b/plugins/samplesource/filesource/filesourcethread.cpp index d69099bbf..d9014f157 100644 --- a/plugins/samplesource/filesource/filesourcethread.cpp +++ b/plugins/samplesource/filesource/filesourcethread.cpp @@ -22,8 +22,15 @@ #include "dsp/filerecord.h" #include "filesourcethread.h" #include "dsp/samplesinkfifo.h" +#include "util/messagequeue.h" -FileSourceThread::FileSourceThread(std::ifstream *samplesStream, SampleSinkFifo* sampleFifo, QObject* parent) : +MESSAGE_CLASS_DEFINITION(FileSourceThread::MsgReportEOF, Message) + +FileSourceThread::FileSourceThread(std::ifstream *samplesStream, + SampleSinkFifo* sampleFifo, + const QTimer& timer, + MessageQueue *fileInputMessageQueue, + QObject* parent) : QThread(parent), m_running(false), m_ifstream(samplesStream), @@ -33,6 +40,8 @@ FileSourceThread::FileSourceThread(std::ifstream *samplesStream, SampleSinkFifo* m_chunksize(0), m_sampleFifo(sampleFifo), m_samplesCount(0), + m_timer(timer), + m_fileInputMessageQueue(fileInputMessageQueue), m_samplerate(0), m_samplesize(0), m_samplebytes(0), @@ -70,6 +79,7 @@ void FileSourceThread::startWork() while(!m_running) m_startWaiter.wait(&m_startWaitMutex, 100); m_startWaitMutex.unlock(); + connect(&m_timer, SIGNAL(timeout()), this, SLOT(tick())); } else { @@ -80,6 +90,7 @@ void FileSourceThread::startWork() void FileSourceThread::stopWork() { qDebug() << "FileSourceThread::stopWork"; + disconnect(&m_timer, SIGNAL(timeout()), this, SLOT(tick())); m_running = false; wait(); } @@ -101,7 +112,6 @@ void FileSourceThread::setSampleRateAndSize(int samplerate, quint32 samplesize) m_samplerate = samplerate; m_samplesize = samplesize; m_samplebytes = m_samplesize > 16 ? sizeof(int32_t) : sizeof(int16_t); - // TODO: implement FF and slow motion here. 2 corresponds to live. 1 is half speed, 4 is double speed m_chunksize = (m_samplerate * 2 * m_samplebytes * m_throttlems) / 1000; setBuffers(m_chunksize); @@ -161,12 +171,6 @@ void FileSourceThread::run() m_running = false; } -void FileSourceThread::connectTimer(const QTimer& timer) -{ - qDebug() << "FileSourceThread::connectTimer"; - connect(&timer, SIGNAL(timeout()), this, SLOT(tick())); -} - void FileSourceThread::tick() { if (m_running) @@ -187,18 +191,12 @@ void FileSourceThread::tick() if (m_ifstream->eof()) { writeToSampleFifo(m_fileBuf, (qint32) m_ifstream->gcount()); - //m_sampleFifo->write(m_buf, m_ifstream->gcount()); - // TODO: handle loop playback situation - m_ifstream->clear(); - m_ifstream->seekg(sizeof(FileRecord::Header), std::ios::beg); - m_samplesCount = 0; - //stopWork(); - //m_ifstream->close(); + MsgReportEOF *message = MsgReportEOF::create(); + m_fileInputMessageQueue->push(message); } else { writeToSampleFifo(m_fileBuf, (qint32) m_chunksize); - //m_sampleFifo->write(m_buf, m_chunksize); m_samplesCount += m_chunksize / (2 * m_samplebytes); } } diff --git a/plugins/samplesource/filesource/filesourcethread.h b/plugins/samplesource/filesource/filesourcethread.h index ef02de9a1..152fbd00e 100644 --- a/plugins/samplesource/filesource/filesourcethread.h +++ b/plugins/samplesource/filesource/filesourcethread.h @@ -27,16 +27,39 @@ #include #include "dsp/inthalfbandfilter.h" +#include "util/message.h" #define FILESOURCE_THROTTLE_MS 50 class SampleSinkFifo; +class MessageQueue; class FileSourceThread : public QThread { Q_OBJECT public: - FileSourceThread(std::ifstream *samplesStream, SampleSinkFifo* sampleFifo, QObject* parent = NULL); + class MsgReportEOF : public Message { + MESSAGE_CLASS_DECLARATION + + public: + + static MsgReportEOF* create() + { + return new MsgReportEOF(); + } + + private: + + MsgReportEOF() : + Message() + { } + }; + + FileSourceThread(std::ifstream *samplesStream, + SampleSinkFifo* sampleFifo, + const QTimer& timer, + MessageQueue *fileInputMessageQueue, + QObject* parent = NULL); ~FileSourceThread(); void startWork(); @@ -44,10 +67,8 @@ public: void setSampleRateAndSize(int samplerate, quint32 samplesize); void setBuffers(std::size_t chunksize); bool isRunning() const { return m_running; } - std::size_t getSamplesCount() const { return m_samplesCount; } - void setSamplesCount(int samplesCount) { m_samplesCount = samplesCount; } - - void connectTimer(const QTimer& timer); + quint64 getSamplesCount() const { return m_samplesCount; } + void setSamplesCount(quint64 samplesCount) { m_samplesCount = samplesCount; } private: QMutex m_startWaitMutex; @@ -58,14 +79,16 @@ private: quint8 *m_fileBuf; quint8 *m_convertBuf; std::size_t m_bufsize; - std::size_t m_chunksize; + qint64 m_chunksize; SampleSinkFifo* m_sampleFifo; - std::size_t m_samplesCount; + quint64 m_samplesCount; + const QTimer& m_timer; + MessageQueue *m_fileInputMessageQueue; int m_samplerate; //!< File I/Q stream original sample rate - quint32 m_samplesize; //!< File effective sample size in bits (I or Q). Ex: 16, 24. - quint32 m_samplebytes; //!< Number of bytes used to store a I or Q sample. Ex: 2. 4. - int m_throttlems; + quint64 m_samplesize; //!< File effective sample size in bits (I or Q). Ex: 16, 24. + quint64 m_samplebytes; //!< Number of bytes used to store a I or Q sample. Ex: 2. 4. + qint64 m_throttlems; QElapsedTimer m_elapsedTimer; bool m_throttleToggle; diff --git a/plugins/samplesource/filesource/readme.md b/plugins/samplesource/filesource/readme.md new file mode 100644 index 000000000..5826cab9e --- /dev/null +++ b/plugins/samplesource/filesource/readme.md @@ -0,0 +1,114 @@ +

File source input plugin

+ +

Introduction

+ +This plugin reads a file of I/Q samples that have been previously saved with the file record button of other sampling source devices. The file starts with a 32 byte header of all unsigned integer of various sizes containing meta data: + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Displ.BytesDescription
04Sample rate in S/s
48Center frequency in Hz
128Unix epoch (timestamp) of start
204Sample size (16 or 24 bits)
244Filler with zeroes
284CRC32 of the previous 28 bytes
+ +The header takes an integer number of 16 (4 bytes) or 24 (8 bytes) bits samples. To calculate CRC it is assumed that bytes are in little endian order. + +

Interface

+ +![FileSource input plugin GUI](../../../doc/img/FileSource_plugin.png) + +

1: Start/Stop

+ +Device start / stop button. + + - Blue triangle icon: ready to be started + - Green square icon: currently running and can be stopped + - Magenta (or pink) square icon: an error occurred. The file may not be found or this can be a header CRC error or the file is too small (less than the header length). You may stop and choose another file. + +

2: Stream sample rate

+ +Baseband I/Q sample rate in kS/s. This is the sample rate present in the header. + +

3: Frequency

+ +This is the center frequency of reception in kHz when the record was taken and written in the header. + +

4: Open file

+ +Opens a file dialog to select the input file. It expects a default extension of `.sdriq`. This button is disabled when the stream is running. You need to pause (button 11) to make it active and thus be able to select another file. + +

5: File path

+ +Absolute path of the file being read + +

6: File recorded sample rate

+ +Sample rate of the record in kS/s as written in the header. The reading process is based on this sample rate. + +

7: Sample size

+ +This is the sample size in bits as written in the header. The reading process is based on this sample size. + +

8: CRC indicator

+ +Indicates if the header block CRC check has succeeded (green) or failed (red) or undetermined yet (grey). If the header is corrupted you may try to reconstruct a valid header using the `rescuesdriq` utility in the folder with the same name. See the [readme](../../../rescuesdriq/readme.md) for details. + +

9: Current timestamp

+ +This is the timestamp of the current pointer in the file based on the start time, number of samples read and sample rate. + +

10: Loop

+ +Use this button to read in a loop or read only once + +

11: Play/pause

+ +This is the play/pause button + +

12: Playback acceleration

+ +Use this combo to select play back acceleration to values of 1 (no acceleration), 2, 5, 10, 20, 50, 100, 200, 500, 1k (1000) times. This is useful on long recordings used in conjunction with the spectrum "Max" averaging mode in order to see the waterfall over a long period. Thus the waterfall will be filled much faster. + +☞ Note that this control is enabled only in paused mode. + +⚠ The result when using channel plugins with acceleration is unpredictable. Use this tool to locate your signal of interest then play at normal speed to get proper demodulation or decoding. + +

13: Relative timestamp and record length

+ +Left is the relative timestamp of the current pointer from the start of the record. Right is the total record time. + +

14: Current pointer gauge

+ +This represents the position of the current pointer position in the complete recording. It can be used it paused mode to position the current pointer by moving the slider. + \ No newline at end of file diff --git a/plugins/samplesource/hackrfinput/CMakeLists.txt b/plugins/samplesource/hackrfinput/CMakeLists.txt index 98881b3d1..f8946515e 100644 --- a/plugins/samplesource/hackrfinput/CMakeLists.txt +++ b/plugins/samplesource/hackrfinput/CMakeLists.txt @@ -76,6 +76,6 @@ target_link_libraries(inputhackrf ) endif (BUILD_DEBIAN) -qt5_use_modules(inputhackrf Core Widgets) +target_link_libraries(inputhackrf Qt5::Core Qt5::Widgets) install(TARGETS inputhackrf DESTINATION lib/plugins/samplesource) diff --git a/plugins/samplesource/hackrfinput/hackrfinput.cpp b/plugins/samplesource/hackrfinput/hackrfinput.cpp index 29c3a74a1..744119e0c 100644 --- a/plugins/samplesource/hackrfinput/hackrfinput.cpp +++ b/plugins/samplesource/hackrfinput/hackrfinput.cpp @@ -49,9 +49,7 @@ HackRFInput::HackRFInput(DeviceSourceAPI *deviceAPI) : { openDevice(); - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID()); - m_fileSink = new FileRecord(std::string(recFileNameCStr)); + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); m_deviceAPI->addSink(m_fileSink); m_deviceAPI->setBuddySharedPtr(&m_sharedParams); @@ -132,12 +130,7 @@ bool HackRFInput::start() if (m_running) stop(); - if ((m_hackRFThread = new HackRFInputThread(m_dev, &m_sampleFifo)) == 0) - { - qCritical("HackRFInput::start: out of memory"); - stop(); - return false; - } + m_hackRFThread = new HackRFInputThread(m_dev, &m_sampleFifo); // mutexLocker.unlock(); @@ -265,9 +258,18 @@ bool HackRFInput::handleMessage(const Message& message) MsgFileRecord& conf = (MsgFileRecord&) message; qDebug() << "HackRFInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); - if (conf.getStartStop()) { + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSink->setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); + } + m_fileSink->startRecording(); - } else { + } + else + { m_fileSink->stopRecording(); } @@ -283,13 +285,11 @@ bool HackRFInput::handleMessage(const Message& message) if (m_deviceAPI->initAcquisition()) { m_deviceAPI->startAcquisition(); - DSPEngine::instance()->startAudioOutput(); } } else { m_deviceAPI->stopAcquisition(); - DSPEngine::instance()->stopAudioOutput(); } return true; @@ -366,10 +366,6 @@ bool HackRFInput::applySettings(const HackRFInputSettings& settings, bool force) } } - qint64 deviceCenterFrequency = settings.m_centerFrequency; - qint64 f_img = deviceCenterFrequency; - quint32 devSampleRate = settings.m_devSampleRate; - if (force || (m_settings.m_centerFrequency != settings.m_centerFrequency)) // forward delta to buddy if necessary { if (m_settings.m_linkTxFrequency && (m_deviceAPI->getSinkBuddies().size() > 0)) @@ -389,38 +385,20 @@ bool HackRFInput::applySettings(const HackRFInputSettings& settings, bool force) } if ((m_settings.m_centerFrequency != settings.m_centerFrequency) || + (m_settings.m_devSampleRate != settings.m_devSampleRate) || (m_settings.m_LOppmTenths != settings.m_LOppmTenths) || (m_settings.m_log2Decim != settings.m_log2Decim) || (m_settings.m_fcPos != settings.m_fcPos) || force) { - if ((settings.m_log2Decim == 0) || (settings.m_fcPos == HackRFInputSettings::FC_POS_CENTER)) - { - deviceCenterFrequency = settings.m_centerFrequency; - f_img = deviceCenterFrequency; - } - else - { - if (settings.m_fcPos == HackRFInputSettings::FC_POS_INFRA) - { - deviceCenterFrequency = settings.m_centerFrequency + (devSampleRate / 4); - f_img = deviceCenterFrequency + devSampleRate/2; - } - else if (settings.m_fcPos == HackRFInputSettings::FC_POS_SUPRA) - { - deviceCenterFrequency = settings.m_centerFrequency - (devSampleRate / 4); - f_img = deviceCenterFrequency - devSampleRate/2; - } - } + qint64 deviceCenterFrequency = DeviceSampleSource::calculateDeviceCenterFrequency( + settings.m_centerFrequency, + 0, + settings.m_log2Decim, + (DeviceSampleSource::fcPos_t) settings.m_fcPos, + settings.m_devSampleRate); - if (m_dev != 0) - { + if (m_dev != 0) { setDeviceCenterFrequency(deviceCenterFrequency); - - qDebug() << "HackRFInput::applySettings: center freq: " << settings.m_centerFrequency << " Hz" - << " device center freq: " << deviceCenterFrequency << " Hz" - << " device sample rate: " << devSampleRate << "Hz" - << " Actual sample rate: " << devSampleRate/(1<handleMessage(*notif); // forward to file sink m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif); @@ -584,6 +562,12 @@ int HackRFInput::webapiSettingsPutPatch( if (deviceSettingsKeys.contains("log2Decim")) { settings.m_log2Decim = response.getHackRfInputSettings()->getLog2Decim(); } + if (deviceSettingsKeys.contains("fcPos")) + { + int fcPos = response.getHackRfInputSettings()->getFcPos(); + fcPos = fcPos < 0 ? 0 : fcPos > 2 ? 2 : fcPos; + settings.m_fcPos = (HackRFInputSettings::fcPos_t) fcPos; + } if (deviceSettingsKeys.contains("devSampleRate")) { settings.m_devSampleRate = response.getHackRfInputSettings()->getDevSampleRate(); } @@ -602,6 +586,9 @@ int HackRFInput::webapiSettingsPutPatch( if (deviceSettingsKeys.contains("linkTxFrequency")) { settings.m_linkTxFrequency = response.getHackRfInputSettings()->getLinkTxFrequency() != 0; } + if (deviceSettingsKeys.contains("fileRecordName")) { + settings.m_fileRecordName = *response.getHackRfInputSettings()->getFileRecordName(); + } MsgConfigureHackRF *msg = MsgConfigureHackRF::create(settings, force); m_inputMessageQueue.push(msg); @@ -631,6 +618,12 @@ void HackRFInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& res response.getHackRfInputSettings()->setDcBlock(settings.m_dcBlock ? 1 : 0); response.getHackRfInputSettings()->setIqCorrection(settings.m_iqCorrection ? 1 : 0); response.getHackRfInputSettings()->setLinkTxFrequency(settings.m_linkTxFrequency ? 1 : 0); + + if (response.getHackRfInputSettings()->getFileRecordName()) { + *response.getHackRfInputSettings()->getFileRecordName() = settings.m_fileRecordName; + } else { + response.getHackRfInputSettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + } } int HackRFInput::webapiRunGet( diff --git a/plugins/samplesource/hackrfinput/hackrfinput.pro b/plugins/samplesource/hackrfinput/hackrfinput.pro index a6f151a9c..ea5511dc7 100644 --- a/plugins/samplesource/hackrfinput/hackrfinput.pro +++ b/plugins/samplesource/hackrfinput/hackrfinput.pro @@ -17,9 +17,10 @@ DEFINES += USE_SSE4_1=1 QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 -CONFIG(MINGW32):LIBHACKRFSRC = "D:\softs\hackrf\host" -CONFIG(MINGW64):LIBHACKRFSRC = "D:\softs\hackrf\host" +CONFIG(MINGW32):LIBHACKRFSRC = "C:\softs\hackrf\host" +CONFIG(MINGW64):LIBHACKRFSRC = "C:\softs\hackrf\host" INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client diff --git a/plugins/samplesource/hackrfinput/hackrfinputgui.cpp b/plugins/samplesource/hackrfinput/hackrfinputgui.cpp index f45962dd6..6de6ca1b0 100644 --- a/plugins/samplesource/hackrfinput/hackrfinputgui.cpp +++ b/plugins/samplesource/hackrfinput/hackrfinputgui.cpp @@ -40,7 +40,7 @@ HackRFInputGui::HackRFInputGui(DeviceUISet *deviceUISet, QWidget* parent) : m_forceSettings(true), m_doApplySettings(true), m_sampleSource(NULL), - m_lastEngineState((DSPDeviceSourceEngine::State)-1) + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted) { m_sampleSource = (HackRFInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); @@ -59,6 +59,7 @@ HackRFInputGui::HackRFInputGui(DeviceUISet *deviceUISet, QWidget* parent) : displayBandwidths(); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); sendSettings(); } diff --git a/plugins/samplesource/hackrfinput/hackrfinputgui.ui b/plugins/samplesource/hackrfinput/hackrfinputgui.ui index dc2d7d917..7a075d010 100644 --- a/plugins/samplesource/hackrfinput/hackrfinputgui.ui +++ b/plugins/samplesource/hackrfinput/hackrfinputgui.ui @@ -24,7 +24,7 @@
- Sans Serif + Liberation Sans 9 @@ -132,7 +132,7 @@ - DejaVu Sans Mono + Liberation Mono 20 @@ -193,7 +193,7 @@ - Local Oscullator ppm correction + Local Oscillator ppm correction -300 @@ -355,7 +355,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -461,7 +461,7 @@ - RF bandpas filter + RF bandpass filter diff --git a/plugins/samplesource/hackrfinput/hackrfinputplugin.cpp b/plugins/samplesource/hackrfinput/hackrfinputplugin.cpp index 19bdbb9db..3ac127c62 100644 --- a/plugins/samplesource/hackrfinput/hackrfinputplugin.cpp +++ b/plugins/samplesource/hackrfinput/hackrfinputplugin.cpp @@ -32,7 +32,7 @@ const PluginDescriptor HackRFInputPlugin::m_pluginDescriptor = { QString("HackRF Input"), - QString("3.11.0"), + QString("3.14.6"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/hackrfinput/hackrfinputsettings.cpp b/plugins/samplesource/hackrfinput/hackrfinputsettings.cpp index 67c667cdc..d2831bcc1 100644 --- a/plugins/samplesource/hackrfinput/hackrfinputsettings.cpp +++ b/plugins/samplesource/hackrfinput/hackrfinputsettings.cpp @@ -40,6 +40,7 @@ void HackRFInputSettings::resetToDefaults() m_iqCorrection = false; m_devSampleRate = 2400000; m_linkTxFrequency = false; + m_fileRecordName = ""; } QByteArray HackRFInputSettings::serialize() const diff --git a/plugins/samplesource/hackrfinput/hackrfinputsettings.h b/plugins/samplesource/hackrfinput/hackrfinputsettings.h index 2a5f4ed22..b2f0b3056 100644 --- a/plugins/samplesource/hackrfinput/hackrfinputsettings.h +++ b/plugins/samplesource/hackrfinput/hackrfinputsettings.h @@ -18,6 +18,7 @@ #define _HACKRF_HACKRFINPUTSETTINGS_H_ #include +#include struct HackRFInputSettings { typedef enum { @@ -39,6 +40,7 @@ struct HackRFInputSettings { bool m_dcBlock; bool m_iqCorrection; bool m_linkTxFrequency; + QString m_fileRecordName; HackRFInputSettings(); void resetToDefaults(); diff --git a/plugins/samplesource/hackrfinput/hackrfinputthread.cpp b/plugins/samplesource/hackrfinput/hackrfinputthread.cpp index db002490a..c0a9dfc57 100644 --- a/plugins/samplesource/hackrfinput/hackrfinputthread.cpp +++ b/plugins/samplesource/hackrfinput/hackrfinputthread.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include "dsp/samplesinkfifo.h" @@ -32,6 +33,7 @@ HackRFInputThread::HackRFInputThread(hackrf_device* dev, SampleSinkFifo* sampleF m_log2Decim(0), m_fcPos(0) { + std::fill(m_buf, m_buf + 2*HACKRF_BLOCKSIZE, 0); } HackRFInputThread::~HackRFInputThread() diff --git a/plugins/samplesource/hackrfinput/hackrfinputthread.h b/plugins/samplesource/hackrfinput/hackrfinputthread.h index 32df49077..cf93c4e92 100644 --- a/plugins/samplesource/hackrfinput/hackrfinputthread.h +++ b/plugins/samplesource/hackrfinput/hackrfinputthread.h @@ -54,11 +54,7 @@ private: unsigned int m_log2Decim; int m_fcPos; -#ifdef SDR_RX_SAMPLE_24BIT - Decimators m_decimators; -#else Decimators m_decimators; -#endif void run(); void callback(const qint8* buf, qint32 len); diff --git a/plugins/samplesource/hackrfinput/readme.md b/plugins/samplesource/hackrfinput/readme.md index 6c78948fa..8b9df7c7e 100644 --- a/plugins/samplesource/hackrfinput/readme.md +++ b/plugins/samplesource/hackrfinput/readme.md @@ -2,7 +2,7 @@

Introduction

-This intput sample source plugin gets its samples from a [HackRF device](https://greatscottgadgets.com/hackrf/). +This input sample source plugin gets its samples from a [HackRF device](https://greatscottgadgets.com/hackrf/).

Build

@@ -28,7 +28,7 @@ Device start / stop button. - Blue triangle icon: device is ready and can be started - Green square icon: device is running and can be stopped - - Red square icon: an error occured. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again. + - Red square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again. If you have the Tx open in another tab and it is running then it will be stopped automatically before the Rx starts. In a similar manner the Rx will be stopped before the Tx is started from the Tx tab. @@ -73,22 +73,33 @@ Use this checkbox to toggle the extra low noise amplifier (LNA). This gives an a This is the HackRF device ADC sample rate in S/s. -Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. -

7: Rx filter bandwidth

- -This is the Rx filter bandwidth in kHz. Possible values are: 1750, 2500, 3500, 5000, 5500, 6000, 7000, 8000, 9000, 10000, 12000, 14000, 15000, 20000, 24000, 28000 kHz. - -

8: Decimation factor

+

7: Decimation factor

The device stream from the HackRF is decimated to obtain the baseband stream. Possible values are: - **1**: no decimation - - **2**: divide devcie stream sample rate by 2 - - **4**: divide devcie stream sample rate by 4 - - **8**: divide devcie stream sample rate by 8 - - **16**: divide devcie stream sample rate by 16 - - **32**: divide devcie stream sample rate by 32 + - **2**: divide device stream sample rate by 2 + - **4**: divide device stream sample rate by 4 + - **8**: divide device stream sample rate by 8 + - **16**: divide device stream sample rate by 16 + - **32**: divide device stream sample rate by 32 + +

8: Baseband center frequency position relative the the HackRF Rx center frequency

+ + - **Cen**: the decimation operation takes place around the HackRF Rx center frequency Fs + - **Inf**: the decimation operation takes place around Fs - Fc. + - **Sup**: the decimation operation takes place around Fs + Fc. + +With SR as the sample rate before decimation Fc is calculated as: + + - if decimation n is 4 or lower: Fc = SR/2^(log2(n)-1). The device center frequency is on the side of the baseband. You need a RF filter bandwidth at least twice the baseband. + - if decimation n is 8 or higher: Fc = SR/n. The device center frequency is half the baseband away from the side of the baseband. You need a RF filter bandwidth at least 3 times the baseband. + +

9: Rx filter bandwidth

+ +This is the Rx filter bandwidth in kHz. Possible values are: 1750, 2500, 3500, 5000, 5500, 6000, 7000, 8000, 9000, 10000, 12000, 14000, 15000, 20000, 24000, 28000 kHz.

10: Internal LNA gain

diff --git a/plugins/samplesource/limesdrinput/CMakeLists.txt b/plugins/samplesource/limesdrinput/CMakeLists.txt index b5d9fd215..6bf0d5602 100644 --- a/plugins/samplesource/limesdrinput/CMakeLists.txt +++ b/plugins/samplesource/limesdrinput/CMakeLists.txt @@ -82,6 +82,6 @@ target_link_libraries(inputlimesdr ) endif (BUILD_DEBIAN) -qt5_use_modules(inputlimesdr Core Widgets) +target_link_libraries(inputlimesdr Qt5::Core Qt5::Widgets) install(TARGETS inputlimesdr DESTINATION lib/plugins/samplesource) diff --git a/plugins/samplesource/limesdrinput/limesdrinput.cpp b/plugins/samplesource/limesdrinput/limesdrinput.cpp index f30b3270a..45add7cdd 100644 --- a/plugins/samplesource/limesdrinput/limesdrinput.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinput.cpp @@ -23,6 +23,8 @@ #include "SWGDeviceSettings.h" #include "SWGLimeSdrInputSettings.h" #include "SWGDeviceState.h" +#include "SWGDeviceReport.h" +#include "SWGLimeSdrInputReport.h" #include "device/devicesourceapi.h" #include "device/devicesinkapi.h" @@ -57,9 +59,7 @@ LimeSDRInput::LimeSDRInput(DeviceSourceAPI *deviceAPI) : resumeTxBuddies(); resumeRxBuddies(); - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID()); - m_fileSink = new FileRecord(std::string(recFileNameCStr)); + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); m_deviceAPI->addSink(m_fileSink); } @@ -103,6 +103,13 @@ bool LimeSDRInput::openDevice() DeviceSourceAPI *sourceBuddy = m_deviceAPI->getSourceBuddies()[0]; //m_deviceShared = *((DeviceLimeSDRShared *) sourceBuddy->getBuddySharedPtr()); // copy shared data DeviceLimeSDRShared *deviceLimeSDRShared = (DeviceLimeSDRShared*) sourceBuddy->getBuddySharedPtr(); + + if (deviceLimeSDRShared == 0) + { + qCritical("LimeSDRInput::openDevice: the source buddy shared pointer is null"); + return false; + } + m_deviceShared.m_deviceParams = deviceLimeSDRShared->m_deviceParams; DeviceLimeSDRParams *deviceParams = m_deviceShared.m_deviceParams; // get device parameters @@ -129,9 +136,6 @@ bool LimeSDRInput::openDevice() // check if the requested channel is busy and abort if so (should not happen if device management is working correctly) - char *busyChannels = new char[deviceParams->m_nbRxChannels]; - memset(busyChannels, 0, deviceParams->m_nbRxChannels); - for (unsigned int i = 0; i < m_deviceAPI->getSourceBuddies().size(); i++) { DeviceSourceAPI *buddy = m_deviceAPI->getSourceBuddies()[i]; @@ -140,13 +144,11 @@ bool LimeSDRInput::openDevice() if (buddyShared->m_channel == requestedChannel) { qCritical("LimeSDRInput::openDevice: cannot open busy channel %u", requestedChannel); - delete[] busyChannels; return false; } } m_deviceShared.m_channel = requestedChannel; // acknowledge the requested channel - delete[] busyChannels; } // look for Tx buddies and get reference to common parameters // take the first Rx channel @@ -157,6 +159,13 @@ bool LimeSDRInput::openDevice() DeviceSinkAPI *sinkBuddy = m_deviceAPI->getSinkBuddies()[0]; //m_deviceShared = *((DeviceLimeSDRShared *) sinkBuddy->getBuddySharedPtr()); // copy parameters DeviceLimeSDRShared *deviceLimeSDRShared = (DeviceLimeSDRShared*) sinkBuddy->getBuddySharedPtr(); + + if (deviceLimeSDRShared == 0) + { + qCritical("LimeSDRInput::openDevice: the sink buddy shared pointer is null"); + return false; + } + m_deviceShared.m_deviceParams = deviceLimeSDRShared->m_deviceParams; if (m_deviceShared.m_deviceParams == 0) @@ -389,20 +398,12 @@ bool LimeSDRInput::start() return false; } - applySettings(m_settings, true); - // start / stop streaming is done in the thread. - if ((m_limeSDRInputThread = new LimeSDRInputThread(&m_streamId, &m_sampleFifo)) == 0) - { - qCritical("LimeSDRInput::start: cannot create thread"); - stop(); - return false; - } - else - { - qDebug("LimeSDRInput::start: thread created"); - } + m_limeSDRInputThread = new LimeSDRInputThread(&m_streamId, &m_sampleFifo); + qDebug("LimeSDRInput::start: thread created"); + + applySettings(m_settings, true); m_limeSDRInputThread->setLog2Decimation(m_settings.m_log2SoftDecim); @@ -471,13 +472,13 @@ int LimeSDRInput::getSampleRate() const quint64 LimeSDRInput::getCenterFrequency() const { - return m_settings.m_centerFrequency; + return m_settings.m_centerFrequency + (m_settings.m_ncoEnable ? m_settings.m_ncoFrequency : 0); } void LimeSDRInput::setCenterFrequency(qint64 centerFrequency) { LimeSDRInputSettings settings = m_settings; - settings.m_centerFrequency = centerFrequency; + settings.m_centerFrequency = centerFrequency - (m_settings.m_ncoEnable ? m_settings.m_ncoFrequency : 0); MsgConfigureLimeSDR* message = MsgConfigureLimeSDR::create(settings, false); m_inputMessageQueue.push(message); @@ -494,31 +495,28 @@ std::size_t LimeSDRInput::getChannelIndex() return m_deviceShared.m_channel; } -void LimeSDRInput::getLORange(float& minF, float& maxF, float& stepF) const +void LimeSDRInput::getLORange(float& minF, float& maxF) const { lms_range_t range = m_deviceShared.m_deviceParams->m_loRangeRx; minF = range.min; maxF = range.max; - stepF = range.step; - qDebug("LimeSDRInput::getLORange: min: %f max: %f step: %f", range.min, range.max, range.step); + qDebug("LimeSDRInput::getLORange: min: %f max: %f", range.min, range.max); } -void LimeSDRInput::getSRRange(float& minF, float& maxF, float& stepF) const +void LimeSDRInput::getSRRange(float& minF, float& maxF) const { lms_range_t range = m_deviceShared.m_deviceParams->m_srRangeRx; minF = range.min; maxF = range.max; - stepF = range.step; - qDebug("LimeSDRInput::getSRRange: min: %f max: %f step: %f", range.min, range.max, range.step); + qDebug("LimeSDRInput::getSRRange: min: %f max: %f", range.min, range.max); } -void LimeSDRInput::getLPRange(float& minF, float& maxF, float& stepF) const +void LimeSDRInput::getLPRange(float& minF, float& maxF) const { lms_range_t range = m_deviceShared.m_deviceParams->m_lpfRangeRx; minF = range.min; maxF = range.max; - stepF = range.step; - qDebug("LimeSDRInput::getLPRange: min: %f max: %f step: %f", range.min, range.max, range.step); + qDebug("LimeSDRInput::getLPRange: min: %f max: %f", range.min, range.max); } uint32_t LimeSDRInput::getHWLog2Decim() const @@ -592,9 +590,12 @@ bool LimeSDRInput::handleMessage(const Message& message) m_settings.m_centerFrequency + ncoShift); m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif); - DeviceLimeSDRShared::MsgReportBuddyChange *reportToGUI = DeviceLimeSDRShared::MsgReportBuddyChange::create( - m_settings.m_devSampleRate, m_settings.m_log2HardDecim, m_settings.m_centerFrequency, true); - getMessageQueueToGUI()->push(reportToGUI); + if (getMessageQueueToGUI()) + { + DeviceLimeSDRShared::MsgReportBuddyChange *reportToGUI = DeviceLimeSDRShared::MsgReportBuddyChange::create( + m_settings.m_devSampleRate, m_settings.m_log2HardDecim, m_settings.m_centerFrequency, true); + getMessageQueueToGUI()->push(reportToGUI); + } return true; } @@ -605,9 +606,12 @@ bool LimeSDRInput::handleMessage(const Message& message) m_settings.m_extClock = report.getExtClock(); m_settings.m_extClockFreq = report.getExtClockFeq(); - DeviceLimeSDRShared::MsgReportClockSourceChange *reportToGUI = DeviceLimeSDRShared::MsgReportClockSourceChange::create( - m_settings.m_extClock, m_settings.m_extClockFreq); - getMessageQueueToGUI()->push(reportToGUI); + if (getMessageQueueToGUI()) + { + DeviceLimeSDRShared::MsgReportClockSourceChange *reportToGUI = DeviceLimeSDRShared::MsgReportClockSourceChange::create( + m_settings.m_extClock, m_settings.m_extClockFreq); + getMessageQueueToGUI()->push(reportToGUI); + } return true; } @@ -705,9 +709,18 @@ bool LimeSDRInput::handleMessage(const Message& message) MsgFileRecord& conf = (MsgFileRecord&) message; qDebug() << "LimeSDRInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); - if (conf.getStartStop()) { + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSink->setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); + } + m_fileSink->startRecording(); - } else { + } + else + { m_fileSink->stopRecording(); } @@ -723,13 +736,11 @@ bool LimeSDRInput::handleMessage(const Message& message) if (m_deviceAPI->initAcquisition()) { m_deviceAPI->startAcquisition(); - DSPEngine::instance()->startAudioOutput(); } } else { m_deviceAPI->stopAcquisition(); - DSPEngine::instance()->stopAudioOutput(); } return true; @@ -753,6 +764,10 @@ bool LimeSDRInput::applySettings(const LimeSDRInputSettings& settings, bool forc double clockGenFreq = 0.0; // QMutexLocker mutexLocker(&m_mutex); + qint64 deviceCenterFrequency = settings.m_centerFrequency; + deviceCenterFrequency -= settings.m_transverterMode ? settings.m_transverterDeltaFrequency : 0; + deviceCenterFrequency = deviceCenterFrequency < 0 ? 0 : deviceCenterFrequency; + if (LMS_GetClockFreq(m_deviceShared.m_deviceParams->getDevice(), LMS_CLOCK_CGEN, &clockGenFreq) != 0) { qCritical("LimeSDRInput::applySettings: could not get clock gen frequency"); @@ -1034,24 +1049,24 @@ bool LimeSDRInput::applySettings(const LimeSDRInputSettings& settings, bool forc } } - if ((m_settings.m_centerFrequency != settings.m_centerFrequency) || setAntennaAuto || force) + if ((m_settings.m_centerFrequency != settings.m_centerFrequency) + || (m_settings.m_transverterMode != settings.m_transverterMode) + || (m_settings.m_transverterDeltaFrequency != settings.m_transverterDeltaFrequency) + || setAntennaAuto || force) { forwardChangeRxDSP = true; if (m_deviceShared.m_deviceParams->getDevice() != 0 && m_channelAcquired) { - if (LMS_SetLOFrequency(m_deviceShared.m_deviceParams->getDevice(), - LMS_CH_RX, - m_deviceShared.m_channel, // same for both channels anyway but switches antenna port automatically - settings.m_centerFrequency) < 0) + if (LMS_SetClockFreq(m_deviceShared.m_deviceParams->getDevice(), LMS_CLOCK_SXR, deviceCenterFrequency) < 0) { - qCritical("LimeSDRInput::applySettings: could not set frequency to %lu", settings.m_centerFrequency); + qCritical("LimeSDRInput::applySettings: could not set frequency to %lld", deviceCenterFrequency); } else { doCalibration = true; - m_deviceShared.m_centerFrequency = settings.m_centerFrequency; // for buddies - qDebug("LimeSDRInput::applySettings: frequency set to %lu", settings.m_centerFrequency); + m_deviceShared.m_centerFrequency = deviceCenterFrequency; // for buddies + qDebug("LimeSDRInput::applySettings: frequency set to %lld", deviceCenterFrequency); } } } @@ -1238,6 +1253,9 @@ bool LimeSDRInput::applySettings(const LimeSDRInputSettings& settings, bool forc QLocale loc; qDebug().noquote() << "LimeSDRInput::applySettings: center freq: " << m_settings.m_centerFrequency << " Hz" + << " m_transverterMode: " << m_settings.m_transverterMode + << " m_transverterDeltaFrequency: " << m_settings.m_transverterDeltaFrequency + << " deviceCenterFrequency: " << deviceCenterFrequency << " device stream sample rate: " << loc.toString(m_settings.m_devSampleRate) << "S/s" << " sample rate with soft decimation: " << loc.toString( m_settings.m_devSampleRate/(1<getTiaGain(); } + if (deviceSettingsKeys.contains("transverterDeltaFrequency")) { + settings.m_transverterDeltaFrequency = response.getLimeSdrInputSettings()->getTransverterDeltaFrequency(); + } + if (deviceSettingsKeys.contains("transverterMode")) { + settings.m_transverterMode = response.getLimeSdrInputSettings()->getTransverterMode() != 0; + } + if (deviceSettingsKeys.contains("fileRecordName")) { + settings.m_fileRecordName = *response.getLimeSdrInputSettings()->getFileRecordName(); + } MsgConfigureLimeSDR *msg = MsgConfigureLimeSDR::create(settings, force); m_inputMessageQueue.push(msg); @@ -1370,6 +1397,24 @@ void LimeSDRInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& re response.getLimeSdrInputSettings()->setNcoFrequency(settings.m_ncoFrequency); response.getLimeSdrInputSettings()->setPgaGain(settings.m_pgaGain); response.getLimeSdrInputSettings()->setTiaGain(settings.m_tiaGain); + response.getLimeSdrInputSettings()->setTransverterDeltaFrequency(settings.m_transverterDeltaFrequency); + response.getLimeSdrInputSettings()->setTransverterMode(settings.m_transverterMode ? 1 : 0); + + if (response.getLimeSdrInputSettings()->getFileRecordName()) { + *response.getLimeSdrInputSettings()->getFileRecordName() = settings.m_fileRecordName; + } else { + response.getLimeSdrInputSettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + } +} + +int LimeSDRInput::webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setLimeSdrInputReport(new SWGSDRangel::SWGLimeSdrInputReport()); + response.getLimeSdrInputReport()->init(); + webapiFormatDeviceReport(response); + return 200; } int LimeSDRInput::webapiRunGet( @@ -1398,3 +1443,35 @@ int LimeSDRInput::webapiRun( return 200; } +void LimeSDRInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + bool success = false; + double temp = 0.0; + lms_stream_status_t status; + status.active = false; + status.fifoFilledCount = 0; + status.fifoSize = 1; + status.underrun = 0; + status.overrun = 0; + status.droppedPackets = 0; + status.linkRate = 0.0; + status.timestamp = 0; + + success = (m_streamId.handle && (LMS_GetStreamStatus(&m_streamId, &status) == 0)); + + response.getLimeSdrInputReport()->setSuccess(success ? 1 : 0); + response.getLimeSdrInputReport()->setStreamActive(status.active ? 1 : 0); + response.getLimeSdrInputReport()->setFifoSize(status.fifoSize); + response.getLimeSdrInputReport()->setFifoFill(status.fifoFilledCount); + response.getLimeSdrInputReport()->setUnderrunCount(status.underrun); + response.getLimeSdrInputReport()->setOverrunCount(status.overrun); + response.getLimeSdrInputReport()->setDroppedPacketsCount(status.droppedPackets); + response.getLimeSdrInputReport()->setLinkRate(status.linkRate); + response.getLimeSdrInputReport()->setHwTimestamp(status.timestamp); + + if (m_deviceShared.m_deviceParams->getDevice()) { + LMS_GetChipTemperature(m_deviceShared.m_deviceParams->getDevice(), 0, &temp); + } + + response.getLimeSdrInputReport()->setTemperature(temp); +} diff --git a/plugins/samplesource/limesdrinput/limesdrinput.h b/plugins/samplesource/limesdrinput/limesdrinput.h index 398e8496f..532f2579a 100644 --- a/plugins/samplesource/limesdrinput/limesdrinput.h +++ b/plugins/samplesource/limesdrinput/limesdrinput.h @@ -27,7 +27,6 @@ class DeviceSourceAPI; class LimeSDRInputThread; -struct DeviceLimeSDRParams; class FileRecord; class LimeSDRInput : public DeviceSampleSource @@ -228,6 +227,10 @@ public: SWGSDRangel::SWGDeviceSettings& response, // query + response QString& errorMessage); + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + virtual int webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage); @@ -238,9 +241,9 @@ public: QString& errorMessage); std::size_t getChannelIndex(); - void getLORange(float& minF, float& maxF, float& stepF) const; - void getSRRange(float& minF, float& maxF, float& stepF) const; - void getLPRange(float& minF, float& maxF, float& stepF) const; + void getLORange(float& minF, float& maxF) const; + void getSRRange(float& minF, float& maxF) const; + void getLPRange(float& minF, float& maxF) const; uint32_t getHWLog2Decim() const; private: @@ -265,6 +268,7 @@ private: void resumeTxBuddies(); bool applySettings(const LimeSDRInputSettings& settings, bool force = false, bool forceNCOFrequency = false); void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const LimeSDRInputSettings& settings); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); }; #endif /* PLUGINS_SAMPLESOURCE_LIMESDRINPUT_LIMESDRINPUT_H_ */ diff --git a/plugins/samplesource/limesdrinput/limesdrinput.pro b/plugins/samplesource/limesdrinput/limesdrinput.pro index 901fc788b..f8fc5db5b 100644 --- a/plugins/samplesource/limesdrinput/limesdrinput.pro +++ b/plugins/samplesource/limesdrinput/limesdrinput.pro @@ -19,10 +19,11 @@ QMAKE_CXXFLAGS += -std=c++11 CONFIG(MINGW32):QMAKE_CXXFLAGS += -std=c++11 -CONFIG(MINGW32):LIBLIMESUITESRC = "D:\softs\LimeSuite" -CONFIG(MINGW64):LIBLIMESUITESRC = "D:\softs\LimeSuite" +CONFIG(MINGW32):LIBLIMESUITESRC = "C:\softs\LimeSuite" +CONFIG(MINGW64):LIBLIMESUITESRC = "C:\softs\LimeSuite" INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client diff --git a/plugins/samplesource/limesdrinput/limesdrinputgui.cpp b/plugins/samplesource/limesdrinput/limesdrinputgui.cpp index b440ab69b..8693eb7e1 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputgui.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinputgui.cpp @@ -35,7 +35,7 @@ LimeSDRInputGUI::LimeSDRInputGUI(DeviceUISet *deviceUISet, QWidget* parent) : m_deviceUISet(deviceUISet), m_settings(), m_sampleRate(0), - m_lastEngineState((DSPDeviceSourceEngine::State)-1), + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted), m_doApplySettings(true), m_forceSettings(true), m_statusCounter(0), @@ -45,17 +45,17 @@ LimeSDRInputGUI::LimeSDRInputGUI(DeviceUISet *deviceUISet, QWidget* parent) : ui->setupUi(this); - float minF, maxF, stepF; + float minF, maxF; - m_limeSDRInput->getLORange(minF, maxF, stepF); + m_limeSDRInput->getLORange(minF, maxF); ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); ui->centerFrequency->setValueRange(7, ((uint32_t) minF)/1000, ((uint32_t) maxF)/1000); // frequency dial is in kHz - m_limeSDRInput->getSRRange(minF, maxF, stepF); + m_limeSDRInput->getSRRange(minF, maxF); ui->sampleRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow)); ui->sampleRate->setValueRange(8, (uint32_t) minF, (uint32_t) maxF); - m_limeSDRInput->getLPRange(minF, maxF, stepF); + m_limeSDRInput->getLPRange(minF, maxF); ui->lpf->setColorMapper(ColorMapper(ColorMapper::GrayYellow)); ui->lpf->setValueRange(6, (minF/1000)+1, maxF/1000); @@ -76,6 +76,7 @@ LimeSDRInputGUI::LimeSDRInputGUI(DeviceUISet *deviceUISet, QWidget* parent) : displaySettings(); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_limeSDRInput->setMessageQueueToGUI(&m_inputMessageQueue); } LimeSDRInputGUI::~LimeSDRInputGUI() @@ -107,12 +108,12 @@ void LimeSDRInputGUI::resetToDefaults() qint64 LimeSDRInputGUI::getCenterFrequency() const { - return m_settings.m_centerFrequency; + return m_settings.m_centerFrequency + (m_settings.m_ncoEnable ? m_settings.m_ncoFrequency : 0); } void LimeSDRInputGUI::setCenterFrequency(qint64 centerFrequency) { - m_settings.m_centerFrequency = centerFrequency; + m_settings.m_centerFrequency = centerFrequency - (m_settings.m_ncoEnable ? m_settings.m_ncoFrequency : 0); displaySettings(); sendSettings(); } @@ -242,6 +243,23 @@ bool LimeSDRInputGUI::handleMessage(const Message& message) } } +void LimeSDRInputGUI::updateFrequencyLimits() +{ + // values in kHz + float minF, maxF; + qint64 deltaFrequency = m_settings.m_transverterMode ? m_settings.m_transverterDeltaFrequency/1000 : 0; + m_limeSDRInput->getLORange(minF, maxF); + qint64 minLimit = minF/1000 + deltaFrequency; + qint64 maxLimit = maxF/1000 + deltaFrequency; + + minLimit = minLimit < 0 ? 0 : minLimit > 9999999 ? 9999999 : minLimit; + maxLimit = maxLimit < 0 ? 0 : maxLimit > 9999999 ? 9999999 : maxLimit; + + qDebug("LimeSDRInputGUI::updateFrequencyLimits: delta: %lld min: %lld max: %lld", deltaFrequency, minLimit, maxLimit); + + ui->centerFrequency->setValueRange(7, minLimit, maxLimit); +} + void LimeSDRInputGUI::handleInputMessages() { Message* message; @@ -298,7 +316,7 @@ void LimeSDRInputGUI::displaySettings() ui->extClock->setExternalClockFrequency(m_settings.m_extClockFreq); ui->extClock->setExternalClockActive(m_settings.m_extClock); - ui->centerFrequency->setValue(m_settings.m_centerFrequency / 1000); + setCenterFrequencyDisplay(); ui->sampleRate->setValue(m_settings.m_devSampleRate); ui->dcOffset->setChecked(m_settings.m_dcBlock); @@ -347,11 +365,42 @@ void LimeSDRInputGUI::displaySettings() void LimeSDRInputGUI::setNCODisplay() { int ncoHalfRange = (m_settings.m_devSampleRate * (1<<(m_settings.m_log2HardDecim)))/2; - int lowBoundary = std::max(0, (int) m_settings.m_centerFrequency - ncoHalfRange); - ui->ncoFrequency->setValueRange(7, - lowBoundary/1000, - (m_settings.m_centerFrequency + ncoHalfRange)/1000); // frequency dial is in kHz - ui->ncoFrequency->setValue((m_settings.m_centerFrequency + m_settings.m_ncoFrequency)/1000); + ui->ncoFrequency->setValueRange( + false, + 8, + -ncoHalfRange, + ncoHalfRange); + + ui->ncoFrequency->blockSignals(true); + ui->ncoFrequency->setToolTip(QString("NCO frequency shift in Hz (Range: +/- %1 kHz)").arg(ncoHalfRange/1000)); + ui->ncoFrequency->setValue(m_settings.m_ncoFrequency); + ui->ncoFrequency->blockSignals(false); +} + +void LimeSDRInputGUI::setCenterFrequencyDisplay() +{ + int64_t centerFrequency = m_settings.m_centerFrequency; + ui->centerFrequency->setToolTip(QString("Main center frequency in kHz (LO: %1 kHz)").arg(centerFrequency/1000)); + + if (m_settings.m_ncoEnable) { + centerFrequency += m_settings.m_ncoFrequency; + } + + ui->centerFrequency->blockSignals(true); + ui->centerFrequency->setValue(centerFrequency < 0 ? 0 : (uint64_t) centerFrequency/1000); // kHz + ui->centerFrequency->blockSignals(false); +} + +void LimeSDRInputGUI::setCenterFrequencySetting(uint64_t kHzValue) +{ + int64_t centerFrequency = kHzValue*1000; + + if (m_settings.m_ncoEnable) { + centerFrequency -= m_settings.m_ncoFrequency; + } + + m_settings.m_centerFrequency = centerFrequency < 0 ? 0 : (uint64_t) centerFrequency; + ui->centerFrequency->setToolTip(QString("Main center frequency in kHz (LO: %1 kHz)").arg(centerFrequency/1000)); } void LimeSDRInputGUI::sendSettings() @@ -455,28 +504,21 @@ void LimeSDRInputGUI::on_record_toggled(bool checked) void LimeSDRInputGUI::on_centerFrequency_changed(quint64 value) { - m_settings.m_centerFrequency = value * 1000; - setNCODisplay(); + setCenterFrequencySetting(value); sendSettings(); } -void LimeSDRInputGUI::on_ncoFrequency_changed(quint64 value) +void LimeSDRInputGUI::on_ncoFrequency_changed(qint64 value) { - m_settings.m_ncoFrequency = (int64_t) value - (int64_t) m_settings.m_centerFrequency/1000; - m_settings.m_ncoFrequency *= 1000; + m_settings.m_ncoFrequency = value; + setCenterFrequencyDisplay(); sendSettings(); } void LimeSDRInputGUI::on_ncoEnable_toggled(bool checked) { m_settings.m_ncoEnable = checked; - sendSettings(); -} - -void LimeSDRInputGUI::on_ncoReset_clicked(bool checked __attribute__((unused))) -{ - m_settings.m_ncoFrequency = 0; - ui->ncoFrequency->setValue(m_settings.m_centerFrequency/1000); + setCenterFrequencyDisplay(); sendSettings(); } @@ -532,10 +574,7 @@ void LimeSDRInputGUI::on_lpFIREnable_toggled(bool checked) void LimeSDRInputGUI::on_lpFIR_changed(quint64 value) { m_settings.m_lpfFIRBW = value * 1000; - - if (m_settings.m_lpfFIREnable) { // do not send the update if the FIR is disabled - sendSettings(); - } + sendSettings(); } void LimeSDRInputGUI::on_gainMode_currentIndexChanged(int index) @@ -601,3 +640,14 @@ void LimeSDRInputGUI::on_extClock_clicked() sendSettings(); } +void LimeSDRInputGUI::on_transverter_clicked() +{ + m_settings.m_transverterMode = ui->transverter->getDeltaFrequencyAcive(); + m_settings.m_transverterDeltaFrequency = ui->transverter->getDeltaFrequency(); + qDebug("LimeSDRInputGUI::on_transverter_clicked: %lld Hz %s", m_settings.m_transverterDeltaFrequency, m_settings.m_transverterMode ? "on" : "off"); + updateFrequencyLimits(); + setCenterFrequencySetting(ui->centerFrequency->getValueNew()); + sendSettings(); +} + + diff --git a/plugins/samplesource/limesdrinput/limesdrinputgui.h b/plugins/samplesource/limesdrinput/limesdrinputgui.h index 2bc328dfe..3ded84e9f 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputgui.h +++ b/plugins/samplesource/limesdrinput/limesdrinputgui.h @@ -69,9 +69,12 @@ private: void displaySettings(); void setNCODisplay(); + void setCenterFrequencyDisplay(); + void setCenterFrequencySetting(uint64_t kHzValue); void sendSettings(); void updateSampleRateAndFrequency(); void updateADCRate(); + void updateFrequencyLimits(); void blockApplySettings(bool block); private slots: @@ -79,9 +82,8 @@ private slots: void on_startStop_toggled(bool checked); void on_record_toggled(bool checked); void on_centerFrequency_changed(quint64 value); - void on_ncoFrequency_changed(quint64 value); + void on_ncoFrequency_changed(qint64 value); void on_ncoEnable_toggled(bool checked); - void on_ncoReset_clicked(bool checked); void on_dcOffset_toggled(bool checked); void on_iqImbalance_toggled(bool checked); void on_sampleRate_changed(quint64 value); @@ -97,6 +99,7 @@ private slots: void on_pgaGain_valueChanged(int value); void on_antenna_currentIndexChanged(int index); void on_extClock_clicked(); + void on_transverter_clicked(); void updateHardware(); void updateStatus(); diff --git a/plugins/samplesource/limesdrinput/limesdrinputgui.ui b/plugins/samplesource/limesdrinput/limesdrinputgui.ui index 80c5b34ef..a41f0c50e 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputgui.ui +++ b/plugins/samplesource/limesdrinput/limesdrinputgui.ui @@ -6,7 +6,7 @@ 0 0 - 374 + 360 290 @@ -18,13 +18,13 @@ - 350 + 360 290 - Sans Serif + Liberation Sans 9 @@ -142,7 +142,7 @@ - DejaVu Sans Mono + Liberation Mono 20 50 false @@ -240,23 +240,7 @@
- - - - 22 - 22 - - - - Reset the NCO to zero frequency - - - R - - - - - + 0 @@ -271,7 +255,7 @@ - DejaVu Sans Mono + Liberation Mono 12 50 false @@ -281,14 +265,14 @@ PointingHandCursor - Center frequency with NCO engaged (kHz) + NCO frequency shift in Hz - kHz + Hz @@ -325,6 +309,22 @@
+ + + + + 24 + 24 + + + + Transverter frequency translation dialog + + + X + + + @@ -512,7 +512,7 @@ - DejaVu Sans Mono + Liberation Mono 12 50 false @@ -573,7 +573,7 @@ - DejaVu Sans Mono + Liberation Mono 12 50 false @@ -594,6 +594,19 @@ + + + + Qt::Horizontal + + + + 40 + 20 + + + + @@ -620,7 +633,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -1166,6 +1179,17 @@ QToolTip{background-color: white; color: black;}
QToolButton
gui/externalclockbutton.h
+ + ValueDialZ + QWidget +
gui/valuedialz.h
+ 1 +
+ + TransverterButton + QPushButton +
gui/transverterbutton.h
+
diff --git a/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp b/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp index 21c043c48..a61ed225c 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinputplugin.cpp @@ -33,7 +33,7 @@ const PluginDescriptor LimeSDRInputPlugin::m_pluginDescriptor = { QString("LimeSDR Input"), - QString("3.10.1"), + QString("4.2.4"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/limesdrinput/limesdrinputsettings.cpp b/plugins/samplesource/limesdrinput/limesdrinputsettings.cpp index 025ce48f5..88383e328 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputsettings.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinputsettings.cpp @@ -43,6 +43,9 @@ void LimeSDRInputSettings::resetToDefaults() m_pgaGain = 16; m_extClock = false; m_extClockFreq = 10000000; // 10 MHz + m_transverterMode = false; + m_transverterDeltaFrequency = 0; + m_fileRecordName = ""; } QByteArray LimeSDRInputSettings::serialize() const @@ -67,6 +70,8 @@ QByteArray LimeSDRInputSettings::serialize() const s.writeU32(17, m_pgaGain); s.writeBool(18, m_extClock); s.writeU32(19, m_extClockFreq); + s.writeBool(20, m_transverterMode); + s.writeS64(21, m_transverterDeltaFrequency); return s.final(); } @@ -105,6 +110,8 @@ bool LimeSDRInputSettings::deserialize(const QByteArray& data) d.readU32(17, &m_pgaGain, 16); d.readBool(18, &m_extClock, false); d.readU32(19, &m_extClockFreq, 10000000); + d.readBool(20, &m_transverterMode, false); + d.readS64(21, &m_transverterDeltaFrequency, 0); return true; } diff --git a/plugins/samplesource/limesdrinput/limesdrinputsettings.h b/plugins/samplesource/limesdrinput/limesdrinputsettings.h index e129af9c9..9287589a2 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputsettings.h +++ b/plugins/samplesource/limesdrinput/limesdrinputsettings.h @@ -18,6 +18,7 @@ #define PLUGINS_SAMPLESOURCE_LIMESDRINPUT_LIMESDRINPUTSETTINGS_H_ #include +#include #include /** @@ -62,6 +63,9 @@ struct LimeSDRInputSettings uint32_t m_pgaGain; //!< Manual PGA gain bool m_extClock; //!< True if external clock source uint32_t m_extClockFreq; //!< Frequency (Hz) of external clock source + bool m_transverterMode; + qint64 m_transverterDeltaFrequency; + QString m_fileRecordName; LimeSDRInputSettings(); void resetToDefaults(); diff --git a/plugins/samplesource/limesdrinput/limesdrinputthread.cpp b/plugins/samplesource/limesdrinput/limesdrinputthread.cpp index 5d32b4060..208455b30 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputthread.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinputthread.cpp @@ -15,6 +15,7 @@ /////////////////////////////////////////////////////////////////////////////////// #include +#include #include "limesdrinputsettings.h" #include "limesdrinputthread.h" @@ -27,6 +28,7 @@ LimeSDRInputThread::LimeSDRInputThread(lms_stream_t* stream, SampleSinkFifo* sam m_sampleFifo(sampleFifo), m_log2Decim(0) { + std::fill(m_buf, m_buf + 2*LIMESDR_BLOCKSIZE, 0); } LimeSDRInputThread::~LimeSDRInputThread() diff --git a/plugins/samplesource/limesdrinput/limesdrinputthread.h b/plugins/samplesource/limesdrinput/limesdrinputthread.h index 096083ac8..ddf671047 100644 --- a/plugins/samplesource/limesdrinput/limesdrinputthread.h +++ b/plugins/samplesource/limesdrinput/limesdrinputthread.h @@ -55,11 +55,7 @@ private: unsigned int m_log2Decim; // soft decimation -#ifdef SDR_RX_SAMPLE_24BIT - Decimators m_decimators; -#else Decimators m_decimators; -#endif void run(); void callback(const qint16* buf, qint32 len); diff --git a/plugins/samplesource/limesdrinput/readme.md b/plugins/samplesource/limesdrinput/readme.md index ad2632985..758fbd190 100644 --- a/plugins/samplesource/limesdrinput/readme.md +++ b/plugins/samplesource/limesdrinput/readme.md @@ -4,7 +4,9 @@ This input sample source plugin gets its samples from a [LimeSDR device](https://myriadrf.org/projects/limesdr/). -⚠ LimeSuite library is difficult to implement due to the lack of documentation. The plugins should work normally when running as single instances. Support of both Rx and/or both Rx running concurrently is experimental. +

⚠ Version 18.04.1 of LimeSuite is used in the builds and corresponding gateware loaded in the LimeSDR should be is used (2.16 for LimeSDR-USB and 1.24 for LimeSDR-Mini). If you compile from source version 18.04.1 of LimeSuite must be used.

+ +

⚠ LimeSDR-Mini seems to have problems with Zadig driver therefore it is supported in Linux only.

LimeSDR is a 2x2 MIMO device so it has two receiving channels that can run concurrently. To activate the second channel when the first is already active just open a new source tab in the main window (Devices -> Add source device) and select the same LimeSDR device. @@ -47,7 +49,7 @@ Device start / stop button. - Blue triangle icon: device is ready and can be started - Green square icon: device is running and can be stopped - - Magenta (or pink) square icon: an error occured. In the case the device was accidentally disconnected you may click on the icon to stop, plug back in, check the source on the sampling devices control panel and start again. + - Magenta (or pink) square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon to stop, plug back in, check the source on the sampling devices control panel and start again.

1.3: Record

@@ -77,35 +79,61 @@ The button is lit when NCO is active and dark when inactive. Use this button to activate/deactivate the TSP NCO. The LMS7002M chip has an independent NCO in each Rx channel that can span the bandwidth received by the ADC. This effectively allows non zero digital IF. -

2.2: Zero (reset) NCO frequency

+

2.2: NCO frequency shift

-USe this push button to reset the NCO frequency to 0 and thus center on the main passband of the ADC. +This is the frequency shift applied when the NCO is engaged thus the actual LO frequency is the center frequency of reception minus this value. Use the thumbwheels to adjust frequency as done with the LO (1.1). Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. The boundaries are dynamically calculated from the LO center frequency, sample rate and hardware decimation factor. -

2.3: Center frequency with NCO engaged

+☞ In the LMS7002M TSP block the NCO sits before the decimator (see Fig.14 of the [datasheet](http://www.limemicro.com/wp-content/uploads/2015/09/LMS7002M-Data-Sheet-v2.8.0.pdf) p.7) so it runs at the actual ADC rate. Hence the NCO limits are calculated as +/- half the device to host sample rate multiplied by the hardware decimation factor. For example with a 4 MS/s device to host sample rate (5) and a hardware decimation of 16 (3) you have +/- 32 MHz span around the LO for the NCO. In this example you can tune all HF frequencies with the center frequency set at its lowest (30 MHz). -This is the center frequency of the mix of LO and NCO combined and is the source passband center frequency when the NCO is engaged. Use the thumbwheels to adjust frequency as done with the LO (1.1). Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. The boundaries are dynamically calculated from the LO center frequency, sample rate and hardware decimation factor. - -☞ In the LMS7002M TSP block the NCO sits before the decimator (see Fig.14 of the [datasheet](http://www.limemicro.com/wp-content/uploads/2015/09/LMS7002M-Data-Sheet-v2.8.0.pdf) p.7) so it runs at the actual ADC rate. Hence the NCO limits are calculated as +/- half the device to host sample rate multiplied by the hardware decimation factor. For example with a 4 MS/s device to host sample rate (5) and a hadrware decimation of 16 (3) you have +/- 32 MHz span around the LO for the NCO. In this example you can tune all HF frequencies with the center frequency set at its lowest (30 MHz). - -

2.4: DC component auto correction

+

2.3: DC component auto correction

Enables or disables the auto remove DC component -

2.5: I/Q balance auto correction

+

2.4: I/Q balance auto correction

Enables or disables the auto I/Q balance correction. The DC correction must be enabled for this to be effective. +

2.5: Transverter mode open dialog

+ +This button opens a dialog to set the transverter mode frequency translation options: + +![SDR Daemon source input stream transverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) + +Note that if you mouse over the button a tooltip appears that displays the translating frequency and if translation is enabled or disabled. When the frequency translation is enabled the button is lit. + +
2.5.1: Translating frequency
+ +You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. + +The frequency set in the device is the frequency on the main dial (1) minus this frequency. Thus it is positive for down converters and negative for up converters. + +For example a mixer at 120 MHz for HF operation you would set the value to -120,000,000 Hz so that if the main dial frequency is set at 7,130 kHz the PlutoSDR will be set to 127.130 MHz. + +If you use a down converter to receive the 6 cm band narrowband center frequency of 5670 MHz at 432 MHz you would set the translating frequency to 5760 - 432 = 5328 MHz thus dial +5,328,000,000 Hz. + +For bands even higher in the frequency spectrum the GHz digits are not really significant so you can have them set at 1 GHz. Thus to receive the 10368 MHz frequency at 432 MHz you would set the translating frequency to 1368 - 432 = 936 MHz. Note that in this case the frequency of the LO used in the mixer of the transverter is set at 9936 MHz. + +The Hz precision allows a fine tuning of the transverter LO offset + +
2.5.2: Translating frequency enable/disable
+ +Use this toggle button to activate or deactivate the frequency translation + +
2.5.3: Confirmation buttons
+ +Use these buttons to confirm ("OK") or dismiss ("Cancel") your changes. +

2.6: External clock control

Use this button to open a dialog that lets you choose the external clock frequency and enable or disable it. When disabled the internal 30.72 MHz VCTCXO is used. ![LimeSDR input plugin gain GUI](../../../doc/img/LimeSDR_plugin_extclock.png) -
2.6.1: Exrernal clock frequency
+
2.6.1: External clock frequency
Can be varied from 5 to 300 MHz -Use the thumbwheels to adjust frequency as done with the LO (1.1). Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. The boundaries are dynamically calculated from the LO center frequency, sample rate and hardware decimation factor. +Use the thumbwheels to adjust frequency as done with the LO (1.1). Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. The boundaries are dynamically calculated from the LO center frequency, sample rate and hardware decimation factor.
2.6.2: Enable/disable external clock input
@@ -127,19 +155,19 @@ Thus the actual sample rate of the ADC is the stream sample rate (5) multiplied

4: Software decimation factor

-The I/Q stream from the LimeSDR is doensampled by a power of two by software inside the plugin before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8, 16, 32. +The I/Q stream from the LimeSDR is downsampled by a power of two by software inside the plugin before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8, 16, 32.

5: Device to host stream sample rate

This is the LMS7002M device to/from host stream sample rate in S/s. It is the same for the Rx and Tx systems. -Use the wheels to adjust the sample rate. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. +Use the wheels to adjust the sample rate. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. The LMS7002M uses the same clock for both the ADCs and DACs therefore this sample rate affects all of the 2x2 MIMO channels.

6: Rx hardware filter bandwidth

-This is the Rx hardware filter bandwidth in kHz in the LMS7002M device for the given channel. Boundaries are updated automatically but generally are from 1.4 to 130 MHz in 1 kHz steps. Use the wheels to adjust the value. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +This is the Rx hardware filter bandwidth in kHz in the LMS7002M device for the given channel. Boundaries are updated automatically but generally are from 1.4 to 130 MHz in 1 kHz steps. Use the wheels to adjust the value. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

7: TSP FIR filter toggle

@@ -147,7 +175,7 @@ The TSP in the LMS7002M chip has a FIR filter chain per channel. Use this button

8: TSP FIR filter bandwidth

-USe the wheels to adjust the bandwidth of the hardware TSP FIR filter. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the bandwidth of the hardware TSP FIR filter. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

9: Gain settings

@@ -155,17 +183,17 @@ USe the wheels to adjust the bandwidth of the hardware TSP FIR filter. Pressing

9.1: Gain mode

-Use this combo to select either the automatic gain (Aut) or the manual (Man) gain setting. Autonatic gain sets the global gain using a predefined table for LNA, TIA and PGA gain blocks. This global gain is set with button 9.2. When manual gain is engaged the LNA, TIA and PGA gains can be set independently with the 9.3, 9.4 and 9.5 buttons respectively. +Use this combo to select either the automatic gain (Aut) or the manual (Man) gain setting. Automatic gain sets the global gain using a predefined table for LNA, TIA and PGA gain blocks. This global gain is set with button 9.2. When manual gain is engaged the LNA, TIA and PGA gains can be set independently with the 9.3, 9.4 and 9.5 buttons respectively. Please refer to [LMS7002M documentation](http://www.limemicro.com/wp-content/uploads/2015/09/LMS7002M-Data-Sheet-v2.8.0.pdf) for a precise description of LNA, TIA and PGA and their location in the Rx chain. To summarize these blocks are placed in this order from antenna to ADC.

9.2: Global automatic gain

-Use this button to adjust the global gain of the LNA, TIA and PGA. LimeSuite software automatically set optimal values of the amplifiers to achive this global gain. This gain can be set between 0 and 70 dB in 1 dB steps. The value in dB appears at the right of the button. +Use this button to adjust the global gain of the LNA, TIA and PGA. LimeSuite software automatically set optimal values of the amplifiers to achieve this global gain. This gain can be set between 0 and 70 dB in 1 dB steps. The value in dB appears at the right of the button.

9.3: LNA manual gain

-Use this button to adjust the gain of tha LNA when manual gain mode is set (9.1). Gain can be set between 1 and 30 dB in 1 dB steps. However the hardware has 3 dB steps for the lower gain values so increasing or decerasing by one step does not always produce a change. The value in dB appears at the right of the button. +Use this button to adjust the gain of tha LNA when manual gain mode is set (9.1). Gain can be set between 1 and 30 dB in 1 dB steps. However the hardware has 3 dB steps for the lower gain values so increasing or decreasing by one step does not always produce a change. The value in dB appears at the right of the button.

9.4: TIA manual gain

@@ -181,7 +209,7 @@ Use this combo box to select the antenna input: - **No**: None - **Lo**: Selects the low frequency input (700 to 900 MHz nominally) - - **Hi**: Selects the high frequncy input (2 to 2.6 GHz) + - **Hi**: Selects the high frequency input (2 to 2.6 GHz) - **Wo**: Selects the wideband input - **T1**: Selects loopback from TX #1 (experimental) - **T1**: Selects loopback from TX #2 (experimental) @@ -206,4 +234,4 @@ This is the fill percentage of the Rx FIFO in the LimeSuite interface. It should

16: Board temperature

-This is the board temperature in degrees Celsius updated every ~5s. Before the first probe the display marks "00C" this is normal. \ No newline at end of file +This is the board temperature in degrees Celsius updated every ~5s. Before the first probe the display marks "00C" this is normal. diff --git a/plugins/samplesource/perseus/perseusgui.cpp b/plugins/samplesource/perseus/perseusgui.cpp index f25f30afd..7ca36aa33 100644 --- a/plugins/samplesource/perseus/perseusgui.cpp +++ b/plugins/samplesource/perseus/perseusgui.cpp @@ -36,7 +36,7 @@ PerseusGui::PerseusGui(DeviceUISet *deviceUISet, QWidget* parent) : m_forceSettings(true), m_settings(), m_sampleSource(0), - m_lastEngineState((DSPDeviceSourceEngine::State)-1) + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted) { m_sampleSource = (PerseusInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); @@ -53,6 +53,7 @@ PerseusGui::PerseusGui(DeviceUISet *deviceUISet, QWidget* parent) : m_rates = m_sampleSource->getSampleRates(); displaySampleRates(); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); sendSettings(); } diff --git a/plugins/samplesource/perseus/perseusgui.ui b/plugins/samplesource/perseus/perseusgui.ui index c448f4904..b6d1fbd00 100644 --- a/plugins/samplesource/perseus/perseusgui.ui +++ b/plugins/samplesource/perseus/perseusgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -135,7 +135,7 @@ - DejaVu Sans Mono + Liberation Mono 20 diff --git a/plugins/samplesource/perseus/perseusinput.cpp b/plugins/samplesource/perseus/perseusinput.cpp index 893accd0a..aaf431566 100644 --- a/plugins/samplesource/perseus/perseusinput.cpp +++ b/plugins/samplesource/perseus/perseusinput.cpp @@ -18,6 +18,8 @@ #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" +#include "SWGDeviceReport.h" +#include "SWGPerseusReport.h" #include "dsp/filerecord.h" #include "dsp/dspcommands.h" @@ -41,9 +43,7 @@ PerseusInput::PerseusInput(DeviceSourceAPI *deviceAPI) : m_perseusDescriptor(0) { openDevice(); - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID()); - m_fileSink = new FileRecord(std::string(recFileNameCStr)); + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); m_deviceAPI->addSink(m_fileSink); } @@ -68,20 +68,12 @@ bool PerseusInput::start() { if (m_running) stop(); - applySettings(m_settings, true); - // start / stop streaming is done in the thread. - if ((m_perseusThread = new PerseusThread(m_perseusDescriptor, &m_sampleFifo)) == 0) - { - qCritical("PerseusInput::start: cannot create thread"); - stop(); - return false; - } - else - { - qDebug("PerseusInput::start: thread created"); - } + m_perseusThread = new PerseusThread(m_perseusDescriptor, &m_sampleFifo); + qDebug("PerseusInput::start: thread created"); + + applySettings(m_settings, true); m_perseusThread->setLog2Decimation(m_settings.m_log2Decim); m_perseusThread->startWork(); @@ -188,13 +180,11 @@ bool PerseusInput::handleMessage(const Message& message) if (m_deviceAPI->initAcquisition()) { m_deviceAPI->startAcquisition(); - DSPEngine::instance()->startAudioOutput(); } } else { m_deviceAPI->stopAcquisition(); - DSPEngine::instance()->stopAudioOutput(); } return true; @@ -204,9 +194,18 @@ bool PerseusInput::handleMessage(const Message& message) MsgFileRecord& conf = (MsgFileRecord&) message; qDebug() << "PerseusInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); - if (conf.getStartStop()) { + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSink->setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); + } + m_fileSink->startRecording(); - } else { + } + else + { m_fileSink->stopRecording(); } @@ -426,3 +425,112 @@ int PerseusInput::webapiRun( return 200; } + +int PerseusInput::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setPerseusSettings(new SWGSDRangel::SWGPerseusSettings()); + response.getPerseusSettings()->init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int PerseusInput::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage __attribute__((unused))) +{ + PerseusSettings settings = m_settings; + + if (deviceSettingsKeys.contains("centerFrequency")) { + settings.m_centerFrequency = response.getPerseusSettings()->getCenterFrequency(); + } + if (deviceSettingsKeys.contains("LOppmTenths")) { + settings.m_LOppmTenths = response.getPerseusSettings()->getLOppmTenths(); + } + if (deviceSettingsKeys.contains("devSampleRateIndex")) { + settings.m_devSampleRateIndex = response.getPerseusSettings()->getDevSampleRateIndex(); + } + if (deviceSettingsKeys.contains("log2Decim")) { + settings.m_log2Decim = response.getPerseusSettings()->getLog2Decim(); + } + if (deviceSettingsKeys.contains("adcDither")) { + settings.m_adcDither = response.getPerseusSettings()->getAdcDither() != 0; + } + if (deviceSettingsKeys.contains("adcPreamp")) { + settings.m_adcPreamp = response.getPerseusSettings()->getAdcPreamp() != 0; + } + if (deviceSettingsKeys.contains("wideBand")) { + settings.m_wideBand = response.getPerseusSettings()->getWideBand() != 0; + } + if (deviceSettingsKeys.contains("attenuator")) { + int attenuator = response.getPerseusSettings()->getAttenuator(); + attenuator = attenuator < 0 ? 0 : attenuator > 3 ? 3 : attenuator; + settings.m_attenuator = (PerseusSettings::Attenuator) attenuator; + } + if (deviceSettingsKeys.contains("transverterDeltaFrequency")) { + settings.m_transverterDeltaFrequency = response.getPerseusSettings()->getTransverterDeltaFrequency(); + } + if (deviceSettingsKeys.contains("transverterMode")) { + settings.m_transverterMode = response.getPerseusSettings()->getTransverterMode() != 0; + } + if (deviceSettingsKeys.contains("fileRecordName")) { + settings.m_fileRecordName = *response.getPerseusSettings()->getFileRecordName(); + } + + MsgConfigurePerseus *msg = MsgConfigurePerseus::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigurePerseus *msgToGUI = MsgConfigurePerseus::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +int PerseusInput::webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setPerseusReport(new SWGSDRangel::SWGPerseusReport()); + response.getPerseusReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} + +void PerseusInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const PerseusSettings& settings) +{ + response.getPerseusSettings()->setCenterFrequency(settings.m_centerFrequency); + response.getPerseusSettings()->setLOppmTenths(settings.m_LOppmTenths); + response.getPerseusSettings()->setDevSampleRateIndex(settings.m_devSampleRateIndex); + response.getPerseusSettings()->setLog2Decim(settings.m_log2Decim); + response.getPerseusSettings()->setAdcDither(settings.m_adcDither ? 1 : 0); + response.getPerseusSettings()->setAdcPreamp(settings.m_adcPreamp ? 1 : 0); + response.getPerseusSettings()->setWideBand(settings.m_wideBand ? 1 : 0); + response.getPerseusSettings()->setAttenuator((int) settings.m_attenuator); + response.getPerseusSettings()->setTransverterDeltaFrequency(settings.m_transverterDeltaFrequency); + response.getPerseusSettings()->setTransverterMode(settings.m_transverterMode ? 1 : 0); + + if (response.getPerseusSettings()->getFileRecordName()) { + *response.getPerseusSettings()->getFileRecordName() = settings.m_fileRecordName; + } else { + response.getPerseusSettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + } +} + +void PerseusInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + response.getPerseusReport()->setSampleRates(new QList); + + for (std::vector::const_iterator it = getSampleRates().begin(); it != getSampleRates().end(); ++it) + { + response.getPerseusReport()->getSampleRates()->append(new SWGSDRangel::SWGSampleRate); + response.getPerseusReport()->getSampleRates()->back()->setRate(*it); + } +} + diff --git a/plugins/samplesource/perseus/perseusinput.h b/plugins/samplesource/perseus/perseusinput.h index 9973921c7..f30ed9877 100644 --- a/plugins/samplesource/perseus/perseusinput.h +++ b/plugins/samplesource/perseus/perseusinput.h @@ -110,6 +110,20 @@ public: virtual bool handleMessage(const Message& message); + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + virtual int webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage); @@ -135,6 +149,8 @@ private: void closeDevice(); void setDeviceCenterFrequency(quint64 freq, const PerseusSettings& settings); bool applySettings(const PerseusSettings& settings, bool force = false); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const PerseusSettings& settings); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); }; #endif /* PLUGINS_SAMPLESOURCE_PERSEUS_PERSEUSINPUT_H_ */ diff --git a/plugins/samplesource/perseus/perseusplugin.cpp b/plugins/samplesource/perseus/perseusplugin.cpp index 1cfb595bb..f4e5e6276 100644 --- a/plugins/samplesource/perseus/perseusplugin.cpp +++ b/plugins/samplesource/perseus/perseusplugin.cpp @@ -15,7 +15,6 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include #include "perseus-sdr.h" #include @@ -23,11 +22,15 @@ #include "util/simpleserializer.h" #include "perseus/deviceperseus.h" #include "perseusplugin.h" +#ifdef SERVER_MODE +#include "perseusinput.h" +#else #include "perseusgui.h" +#endif const PluginDescriptor PerseusPlugin::m_pluginDescriptor = { QString("Perseus Input"), - QString("3.12.0"), + QString("4.0.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, @@ -78,12 +81,21 @@ PluginInterface::SamplingDevices PerseusPlugin::enumSampleSources() 1, 0)); - qDebug("PerseusPlugin::enumSampleSources: enumerated PlutoSDR device #%d", i); + qDebug("PerseusPlugin::enumSampleSources: enumerated Perseus device #%d", i); } return result; } +#ifdef SERVER_MODE +PluginInstanceGUI* PerseusPlugin::createSampleSourcePluginInstanceGUI( + const QString& sourceId __attribute__((unused)), + QWidget **widget __attribute__((unused)), + DeviceUISet *deviceUISet __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* PerseusPlugin::createSampleSourcePluginInstanceGUI( const QString& sourceId, QWidget **widget, @@ -100,6 +112,7 @@ PluginInstanceGUI* PerseusPlugin::createSampleSourcePluginInstanceGUI( return 0; } } +#endif DeviceSampleSource *PerseusPlugin::createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI) { diff --git a/plugins/samplesource/perseus/perseussettings.cpp b/plugins/samplesource/perseus/perseussettings.cpp index 77a57a83f..5ae31d97d 100644 --- a/plugins/samplesource/perseus/perseussettings.cpp +++ b/plugins/samplesource/perseus/perseussettings.cpp @@ -35,6 +35,7 @@ void PerseusSettings::resetToDefaults() m_adcPreamp = false; m_wideBand = false; m_attenuator = Attenuator_None; + m_fileRecordName = ""; } QByteArray PerseusSettings::serialize() const diff --git a/plugins/samplesource/perseus/perseussettings.h b/plugins/samplesource/perseus/perseussettings.h index 864bb0cec..1330e2747 100644 --- a/plugins/samplesource/perseus/perseussettings.h +++ b/plugins/samplesource/perseus/perseussettings.h @@ -18,6 +18,7 @@ #define PLUGINS_SAMPLESOURCE_PERSEUS_PERSEUSSETTINGS_H_ #include +#include struct PerseusSettings { @@ -40,6 +41,7 @@ struct PerseusSettings bool m_adcPreamp; bool m_wideBand; Attenuator m_attenuator; + QString m_fileRecordName; PerseusSettings(); void resetToDefaults(); diff --git a/plugins/samplesource/perseus/perseusthread.cpp b/plugins/samplesource/perseus/perseusthread.cpp index a64b84e47..ec0441a78 100644 --- a/plugins/samplesource/perseus/perseusthread.cpp +++ b/plugins/samplesource/perseus/perseusthread.cpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2018 Edouard Griffiths, F4EXB // + // Copyright (C) 2018 Edouard Griffiths, F4EXB // // // // This program is free software; you can redistribute it and/or modify // // it under the terms of the GNU General Public License as published by // @@ -15,6 +15,7 @@ /////////////////////////////////////////////////////////////////////////////////// #include +#include #include "perseusthread.h" PerseusThread *PerseusThread::m_this = 0; @@ -28,6 +29,7 @@ PerseusThread::PerseusThread(perseus_descr* dev, SampleSinkFifo* sampleFifo, QOb m_log2Decim(0) { m_this = this; + std::fill(m_buf, m_buf + 2*PERSEUS_NBSAMPLES, 0); } PerseusThread::~PerseusThread() diff --git a/plugins/samplesource/perseus/perseusthread.h b/plugins/samplesource/perseus/perseusthread.h index 6fe21e93d..0adfda1e8 100644 --- a/plugins/samplesource/perseus/perseusthread.h +++ b/plugins/samplesource/perseus/perseusthread.h @@ -42,7 +42,7 @@ public: private: QMutex m_startWaitMutex; QWaitCondition m_startWaiter; - bool m_running; + volatile bool m_running; perseus_descr* m_dev; qint32 m_buf[2*PERSEUS_NBSAMPLES]; @@ -52,8 +52,8 @@ private: unsigned int m_log2Decim; static PerseusThread *m_this; - Decimators, SDR_RX_SAMP_SZ, 24> m_decimators32; // for no decimation (accumulator is int32) - Decimators, SDR_RX_SAMP_SZ, 24> m_decimators64; // for actual decimation (accumulator is int64) + Decimators, SDR_RX_SAMP_SZ, 24> m_decimators32; // for no decimation (accumulator is int32) + Decimators, SDR_RX_SAMP_SZ, 24> m_decimators64; // for actual decimation (accumulator is int64) void run(); void callback(const uint8_t* buf, qint32 len); // inner call back diff --git a/plugins/samplesource/perseus/readme.md b/plugins/samplesource/perseus/readme.md index 18a961ba7..1b24c42ab 100644 --- a/plugins/samplesource/perseus/readme.md +++ b/plugins/samplesource/perseus/readme.md @@ -39,7 +39,7 @@ Device start / stop button. - Blue triangle icon: device is ready and can be started - Green square icon: device is running and can be stopped - - Magenta (or pink) square icon: an error occured. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again. + - Magenta (or pink) square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again.

1.3: Record

@@ -59,7 +59,7 @@ This resets the LO ppm correction (zero the value).

4: Device to hast sample rate

-This is the device to host sample rate in kilo samples per second (kS/s). The sample rate can be as low as 48 kS/s so there is no need for software decimation. Note that at 48 kS/s some slight rate mismatch can appear with the audio that has the same nominal rate. This may cause some occasinal audio samples drops however hardly noticeable. +This is the device to host sample rate in kilo samples per second (kS/s). The sample rate can be as low as 48 kS/s so there is no need for software decimation. Note that at 48 kS/s some slight rate mismatch can appear with the audio that has the same nominal rate. This may cause some occasional audio samples drops however hardly noticeable.

5: Wideband mode

@@ -67,19 +67,19 @@ Switch on this button to disable the preselection filters. The corresponding LED

6: Decimation factor

-The I/Q stream from the Perseus to host is downsampled by a power of two before being sent to the passband. This is normally not needed for most use cases as the Perseus can go as low as 48 kS/s which is the lower limit for audio channel plugins (AM, FM, SSB, Digital voice). So it can be left to `1` most of the time. A software decimation by 2 or 4 is still provided for easier anaysis of very narrowband or slow varying signals. Note that there is no dynamic gain with this decimation as the precision is already limited to 24 significant bits either for integer or floating point (float) processing. +The I/Q stream from the Perseus to host is downsampled by a power of two before being sent to the passband. This is normally not needed for most use cases as the Perseus can go as low as 48 kS/s which is the lower limit for audio channel plugins (AM, FM, SSB, Digital voice). So it can be left to `1` most of the time. A software decimation by 2 or 4 is still provided for easier analysis of very narrowband or slow varying signals. Note that there is no dynamic gain with this decimation as the precision is already limited to 24 significant bits either for integer or floating point (float) processing.

7: Transverter mode open dialog

This button opens a dialog to set the transverter mode frequency translation options: -![SDR Daemon source input stream trasverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) +![SDR Daemon source input stream transverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) Note that if you mouse over the button a tooltip appears that displays the translating frequency and if translation is enabled or disabled. When the frequency translation is enabled the button is lit.

7a.1: Translating frequency

-You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. The frequency set in the device is the frequency on the main dial (1) minus this frequency. Thus it is positive for down converters and negative for up converters. @@ -101,7 +101,7 @@ Use these buttons to confirm ("OK") or dismiss ("Cancel") your changes.

8: Attenuators control

-Use this combo box to cotrol the attenuators inside the Perseus: +Use this combo box to control the attenuators inside the Perseus: - 0 dB: no attenuation - 10 dB: 10 dB attenuator engaged @@ -117,4 +117,4 @@ Use this button to turn on or off the Perseus ADC dithering

10: ADC preamplifier

Use this button to turn on or off the Perseus ADC preamplifier - \ No newline at end of file + diff --git a/plugins/samplesource/plutosdrinput/plutosdrinput.cpp b/plugins/samplesource/plutosdrinput/plutosdrinput.cpp index 4025c3c5f..09c2317fc 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinput.cpp +++ b/plugins/samplesource/plutosdrinput/plutosdrinput.cpp @@ -18,6 +18,8 @@ #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" +#include "SWGDeviceReport.h" +#include "SWGPlutoSdrInputReport.h" #include "dsp/filerecord.h" #include "dsp/dspcommands.h" @@ -44,13 +46,18 @@ PlutoSDRInput::PlutoSDRInput(DeviceSourceAPI *deviceAPI) : m_plutoRxBuffer(0), m_plutoSDRInputThread(0) { + m_deviceSampleRates.m_addaConnvRate = 0; + m_deviceSampleRates.m_bbRateHz = 0; + m_deviceSampleRates.m_firRate = 0; + m_deviceSampleRates.m_hb1Rate = 0; + m_deviceSampleRates.m_hb2Rate = 0; + m_deviceSampleRates.m_hb3Rate = 0; + suspendBuddies(); openDevice(); resumeBuddies(); - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID()); - m_fileSink = new FileRecord(std::string(recFileNameCStr)); + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); m_deviceAPI->addSink(m_fileSink); } @@ -81,20 +88,12 @@ bool PlutoSDRInput::start() if (m_running) stop(); - applySettings(m_settings, true); - // start / stop streaming is done in the thread. - if ((m_plutoSDRInputThread = new PlutoSDRInputThread(PLUTOSDR_BLOCKSIZE_SAMPLES, m_deviceShared.m_deviceParams->getBox(), &m_sampleFifo)) == 0) - { - qCritical("PlutoSDRInput::start: cannot create thread"); - stop(); - return false; - } - else - { - qDebug("PlutoSDRInput::start: thread created"); - } + m_plutoSDRInputThread = new PlutoSDRInputThread(PLUTOSDR_BLOCKSIZE_SAMPLES, m_deviceShared.m_deviceParams->getBox(), &m_sampleFifo); + qDebug("PlutoSDRInput::start: thread created"); + + applySettings(m_settings, true); m_plutoSDRInputThread->setLog2Decimation(m_settings.m_log2Decim); m_plutoSDRInputThread->startWork(); @@ -193,9 +192,18 @@ bool PlutoSDRInput::handleMessage(const Message& message) MsgFileRecord& conf = (MsgFileRecord&) message; qDebug() << "PlutoSDRInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); - if (conf.getStartStop()) { + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSink->setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); + } + m_fileSink->startRecording(); - } else { + } + else + { m_fileSink->stopRecording(); } @@ -211,13 +219,11 @@ bool PlutoSDRInput::handleMessage(const Message& message) if (m_deviceAPI->initAcquisition()) { m_deviceAPI->startAcquisition(); - DSPEngine::instance()->startAudioOutput(); } } else { m_deviceAPI->stopAcquisition(); - DSPEngine::instance()->stopAudioOutput(); } return true; @@ -287,11 +293,9 @@ bool PlutoSDRInput::openDevice() m_deviceAPI->setBuddySharedPtr(&m_deviceShared); // propagate common parameters to API // acquire the channel - suspendBuddies(); DevicePlutoSDRBox *plutoBox = m_deviceShared.m_deviceParams->getBox(); plutoBox->openRx(); m_plutoRxBuffer = plutoBox->createRxBuffer(PLUTOSDR_BLOCKSIZE_SAMPLES, false); - resumeBuddies(); return true; } @@ -347,6 +351,23 @@ bool PlutoSDRInput::applySettings(const PlutoSDRInputSettings& settings, bool fo bool ownThreadWasRunning = false; bool suspendAllOtherThreads = false; // All others means Tx in fact DevicePlutoSDRBox *plutoBox = m_deviceShared.m_deviceParams->getBox(); + QLocale loc; + + qDebug().noquote() << "PlutoSDRInput::applySettings: center freq: " << m_settings.m_centerFrequency << " Hz" + << " m_devSampleRate: " << loc.toString(m_settings.m_devSampleRate) << "S/s" + << " m_LOppmTenths: " << m_settings.m_LOppmTenths + << " m_lpfFIREnable: " << m_settings.m_lpfFIREnable + << " m_lpfFIRBW: " << loc.toString(m_settings.m_lpfFIRBW) + << " m_lpfFIRlog2Decim: " << m_settings.m_lpfFIRlog2Decim + << " m_lpfFIRGain: " << m_settings.m_lpfFIRGain + << " m_log2Decim: " << loc.toString(1<init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int PlutoSDRInput::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage __attribute__((unused))) +{ + PlutoSDRInputSettings settings = m_settings; + + if (deviceSettingsKeys.contains("centerFrequency")) { + settings.m_centerFrequency = response.getPlutoSdrInputSettings()->getCenterFrequency(); + } + if (deviceSettingsKeys.contains("devSampleRate")) { + settings.m_devSampleRate = response.getPlutoSdrInputSettings()->getDevSampleRate(); + } + if (deviceSettingsKeys.contains("LOppmTenths")) { + settings.m_LOppmTenths = response.getPlutoSdrInputSettings()->getLOppmTenths(); + } + if (deviceSettingsKeys.contains("lpfFIREnable")) { + settings.m_lpfFIREnable = response.getPlutoSdrInputSettings()->getLpfFirEnable() != 0; + } + if (deviceSettingsKeys.contains("lpfFIRBW")) { + settings.m_lpfFIRBW = response.getPlutoSdrInputSettings()->getLpfFirbw(); + } + if (deviceSettingsKeys.contains("lpfFIRlog2Decim")) { + settings.m_lpfFIRlog2Decim = response.getPlutoSdrInputSettings()->getLpfFiRlog2Decim(); + } + if (deviceSettingsKeys.contains("lpfFIRGain")) { + settings.m_lpfFIRGain = response.getPlutoSdrInputSettings()->getLpfFirGain(); + } + if (deviceSettingsKeys.contains("fcPos")) { + int fcPos = response.getPlutoSdrInputSettings()->getFcPos(); + fcPos = fcPos < 0 ? 0 : fcPos > 2 ? 2 : fcPos; + settings.m_fcPos = (PlutoSDRInputSettings::fcPos_t) fcPos; + } + if (deviceSettingsKeys.contains("dcBlock")) { + settings.m_dcBlock = response.getPlutoSdrInputSettings()->getDcBlock() != 0; + } + if (deviceSettingsKeys.contains("iqCorrection")) { + settings.m_iqCorrection = response.getPlutoSdrInputSettings()->getIqCorrection() != 0; + } + if (deviceSettingsKeys.contains("log2Decim")) { + settings.m_log2Decim = response.getPlutoSdrInputSettings()->getLog2Decim(); + } + if (deviceSettingsKeys.contains("lpfBW")) { + settings.m_lpfBW = response.getPlutoSdrInputSettings()->getLpfBw(); + } + if (deviceSettingsKeys.contains("gain")) { + settings.m_gain = response.getPlutoSdrInputSettings()->getGain(); + } + if (deviceSettingsKeys.contains("antennaPath")) { + int antennaPath = response.getPlutoSdrInputSettings()->getAntennaPath(); + antennaPath = antennaPath < 0 ? 0 : antennaPath >= PlutoSDRInputSettings::RFPATH_END ? PlutoSDRInputSettings::RFPATH_END-1 : antennaPath; + settings.m_antennaPath = (PlutoSDRInputSettings::RFPath) antennaPath; + } + if (deviceSettingsKeys.contains("gainMode")) { + int gainMode = response.getPlutoSdrInputSettings()->getGainMode(); + gainMode = gainMode < 0 ? 0 : gainMode >= PlutoSDRInputSettings::GAIN_END ? PlutoSDRInputSettings::GAIN_END-1 : gainMode; + settings.m_gainMode = (PlutoSDRInputSettings::GainMode) gainMode; + } + if (deviceSettingsKeys.contains("transverterDeltaFrequency")) { + settings.m_transverterDeltaFrequency = response.getPlutoSdrInputSettings()->getTransverterDeltaFrequency(); + } + if (deviceSettingsKeys.contains("transverterMode")) { + settings.m_transverterMode = response.getPlutoSdrInputSettings()->getTransverterMode() != 0; + } + if (deviceSettingsKeys.contains("fileRecordName")) { + settings.m_fileRecordName = *response.getPlutoSdrInputSettings()->getFileRecordName(); + } + + MsgConfigurePlutoSDR *msg = MsgConfigurePlutoSDR::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigurePlutoSDR *msgToGUI = MsgConfigurePlutoSDR::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +int PlutoSDRInput::webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setPlutoSdrInputReport(new SWGSDRangel::SWGPlutoSdrInputReport()); + response.getPlutoSdrInputReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} + +void PlutoSDRInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const PlutoSDRInputSettings& settings) +{ + response.getPlutoSdrInputSettings()->setCenterFrequency(settings.m_centerFrequency); + response.getPlutoSdrInputSettings()->setDevSampleRate(settings.m_devSampleRate); + response.getPlutoSdrInputSettings()->setLOppmTenths(settings.m_LOppmTenths); + response.getPlutoSdrInputSettings()->setLpfFirEnable(settings.m_lpfFIREnable ? 1 : 0); + response.getPlutoSdrInputSettings()->setLpfFirbw(settings.m_lpfFIRBW); + response.getPlutoSdrInputSettings()->setLpfFiRlog2Decim(settings.m_lpfFIRlog2Decim); + response.getPlutoSdrInputSettings()->setLpfFirGain(settings.m_lpfFIRGain); + response.getPlutoSdrInputSettings()->setFcPos((int) settings.m_fcPos); + response.getPlutoSdrInputSettings()->setDcBlock(settings.m_dcBlock ? 1 : 0); + response.getPlutoSdrInputSettings()->setIqCorrection(settings.m_iqCorrection ? 1 : 0); + response.getPlutoSdrInputSettings()->setLog2Decim(settings.m_log2Decim); + response.getPlutoSdrInputSettings()->setLpfBw(settings.m_lpfBW); + response.getPlutoSdrInputSettings()->setGain(settings.m_gain); + response.getPlutoSdrInputSettings()->setAntennaPath((int) m_settings.m_antennaPath); + response.getPlutoSdrInputSettings()->setGainMode((int) settings.m_gainMode); + response.getPlutoSdrInputSettings()->setTransverterDeltaFrequency(settings.m_transverterDeltaFrequency); + response.getPlutoSdrInputSettings()->setTransverterMode(settings.m_transverterMode ? 1 : 0); + + if (response.getPlutoSdrInputSettings()->getFileRecordName()) { + *response.getPlutoSdrInputSettings()->getFileRecordName() = settings.m_fileRecordName; + } else { + response.getPlutoSdrInputSettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + } +} + +void PlutoSDRInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + response.getPlutoSdrInputReport()->setAdcRate(getADCSampleRate()); + std::string rssiStr; + getRSSI(rssiStr); + response.getPlutoSdrInputReport()->setRssi(new QString(rssiStr.c_str())); + int gainDB; + getGain(gainDB); + response.getPlutoSdrInputReport()->setGainDb(gainDB); + fetchTemperature(); + response.getPlutoSdrInputReport()->setTemperature(getTemperature()); +} diff --git a/plugins/samplesource/plutosdrinput/plutosdrinput.h b/plugins/samplesource/plutosdrinput/plutosdrinput.h index daefbdda7..a2191f755 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinput.h +++ b/plugins/samplesource/plutosdrinput/plutosdrinput.h @@ -24,6 +24,7 @@ #include #include "util/message.h" #include "plutosdr/deviceplutosdrshared.h" +#include "plutosdr/deviceplutosdrbox.h" #include "plutosdrinputsettings.h" class DeviceSourceAPI; @@ -121,6 +122,20 @@ public: SWGSDRangel::SWGDeviceState& response, QString& errorMessage); + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + uint32_t getADCSampleRate() const { return m_deviceSampleRates.m_addaConnvRate; } uint32_t getFIRSampleRate() const { return m_deviceSampleRates.m_hb1Rate; } void getRSSI(std::string& rssiStr); @@ -145,6 +160,8 @@ public: void suspendBuddies(); void resumeBuddies(); bool applySettings(const PlutoSDRInputSettings& settings, bool force = false); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const PlutoSDRInputSettings& settings); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); }; diff --git a/plugins/samplesource/plutosdrinput/plutosdrinput.pro b/plugins/samplesource/plutosdrinput/plutosdrinput.pro index dab6ad546..f18fe1bb4 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinput.pro +++ b/plugins/samplesource/plutosdrinput/plutosdrinput.pro @@ -17,10 +17,11 @@ DEFINES += USE_SSE4_1=1 QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 -CONFIG(MINGW32):LIBIIOSRC = "D:\softs\libiio" -CONFIG(MINGW64):LIBIIOSRC = "D:\softs\libiio" +CONFIG(MINGW32):LIBIIOSRC = "C:\softs\libiio" +CONFIG(MINGW64):LIBIIOSRC = "C:\softs\libiio" INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client diff --git a/plugins/samplesource/plutosdrinput/plutosdrinputgui.cpp b/plugins/samplesource/plutosdrinput/plutosdrinputgui.cpp index 302f2a882..441413b0d 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinputgui.cpp +++ b/plugins/samplesource/plutosdrinput/plutosdrinputgui.cpp @@ -37,7 +37,7 @@ PlutoSDRInputGui::PlutoSDRInputGui(DeviceUISet *deviceUISet, QWidget* parent) : m_sampleSource(NULL), m_sampleRate(0), m_deviceCenterFrequency(0), - m_lastEngineState((DSPDeviceSourceEngine::State)-1), + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted), m_doApplySettings(true), m_statusCounter(0) { @@ -68,6 +68,7 @@ PlutoSDRInputGui::PlutoSDRInputGui(DeviceUISet *deviceUISet, QWidget* parent) : m_statusTimer.start(500); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); } PlutoSDRInputGui::~PlutoSDRInputGui() @@ -310,6 +311,7 @@ void PlutoSDRInputGui::displaySettings() ui->loPPMText->setText(QString("%1").arg(QString::number(m_settings.m_LOppmTenths/10.0, 'f', 1))); ui->swDecim->setCurrentIndex(m_settings.m_log2Decim); + ui->fcPos->setCurrentIndex((int) m_settings.m_fcPos); ui->lpf->setValue(m_settings.m_lpfBW / 1000); diff --git a/plugins/samplesource/plutosdrinput/plutosdrinputgui.h b/plugins/samplesource/plutosdrinput/plutosdrinputgui.h index 3e2c622db..0c5cba146 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinputgui.h +++ b/plugins/samplesource/plutosdrinput/plutosdrinputgui.h @@ -17,13 +17,14 @@ #ifndef PLUGINS_SAMPLESOURCE_PLUTOSDRINPUT_PLUTOSDRINPUTGUI_H_ #define PLUGINS_SAMPLESOURCE_PLUTOSDRINPUT_PLUTOSDRINPUTGUI_H_ -#include #include #include #include #include "util/messagequeue.h" +#include "plugin/plugininstancegui.h" +#include "plutosdrinput.h" #include "plutosdrinputsettings.h" class DeviceSampleSource; diff --git a/plugins/samplesource/plutosdrinput/plutosdrinputgui.ui b/plugins/samplesource/plutosdrinput/plutosdrinputgui.ui index 853cbad14..4f89275e7 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinputgui.ui +++ b/plugins/samplesource/plutosdrinput/plutosdrinputgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -142,10 +142,8 @@ - DejaVu Sans Mono + Liberation Mono 20 - 50 - false @@ -533,7 +531,7 @@ - DejaVu Sans Mono + Liberation Mono 12 50 false @@ -590,7 +588,7 @@ - DejaVu Sans Mono + Liberation Mono 12 50 false @@ -654,7 +652,7 @@ - DejaVu Sans Mono + Liberation Mono 12 diff --git a/plugins/samplesource/plutosdrinput/plutosdrinputplugin.cpp b/plugins/samplesource/plutosdrinput/plutosdrinputplugin.cpp index b8acbeb77..1fb15bfc6 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinputplugin.cpp +++ b/plugins/samplesource/plutosdrinput/plutosdrinputplugin.cpp @@ -15,20 +15,22 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include #include "plugin/pluginapi.h" #include "plutosdr/deviceplutosdr.h" -#include "plutosdrinputgui.h" +#ifdef SERVER_MODE #include "plutosdrinput.h" +#else +#include "plutosdrinputgui.h" +#endif #include "plutosdrinputplugin.h" class DeviceSourceAPI; const PluginDescriptor PlutoSDRInputPlugin::m_pluginDescriptor = { QString("PlutoSDR Input"), - QString("3.11.1"), + QString("4.0.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, @@ -85,6 +87,15 @@ PluginInterface::SamplingDevices PlutoSDRInputPlugin::enumSampleSources() return result; } +#ifdef SERVER_MODE +PluginInstanceGUI* PlutoSDRInputPlugin::createSampleSourcePluginInstanceGUI( + const QString& sourceId __attribute__((unused)), + QWidget **widget __attribute__((unused)), + DeviceUISet *deviceUISet __attribute__((unused))) +{ + return 0; +} +#else PluginInstanceGUI* PlutoSDRInputPlugin::createSampleSourcePluginInstanceGUI( const QString& sourceId, QWidget **widget, @@ -101,6 +112,7 @@ PluginInstanceGUI* PlutoSDRInputPlugin::createSampleSourcePluginInstanceGUI( return 0; } } +#endif DeviceSampleSource *PlutoSDRInputPlugin::createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI) { diff --git a/plugins/samplesource/plutosdrinput/plutosdrinputsettings.cpp b/plugins/samplesource/plutosdrinput/plutosdrinputsettings.cpp index c90e7a630..556977d75 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinputsettings.cpp +++ b/plugins/samplesource/plutosdrinput/plutosdrinputsettings.cpp @@ -44,6 +44,7 @@ void PlutoSDRInputSettings::resetToDefaults() m_gainMode = GAIN_MANUAL; m_transverterMode = false; m_transverterDeltaFrequency = 0; + m_fileRecordName = ""; } QByteArray PlutoSDRInputSettings::serialize() const diff --git a/plugins/samplesource/plutosdrinput/plutosdrinputsettings.h b/plugins/samplesource/plutosdrinput/plutosdrinputsettings.h index 19d4793b2..691161181 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinputsettings.h +++ b/plugins/samplesource/plutosdrinput/plutosdrinputsettings.h @@ -18,6 +18,7 @@ #define _PLUTOSDR_PLUTOSDRINPUTSETTINGS_H_ #include +#include #include struct PlutoSDRInputSettings { @@ -73,7 +74,7 @@ struct PlutoSDRInputSettings { GainMode m_gainMode; bool m_transverterMode; qint64 m_transverterDeltaFrequency; - + QString m_fileRecordName; PlutoSDRInputSettings(); void resetToDefaults(); diff --git a/plugins/samplesource/plutosdrinput/plutosdrinputthread.cpp b/plugins/samplesource/plutosdrinput/plutosdrinputthread.cpp index 2a6ee9f36..a85a078a3 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinputthread.cpp +++ b/plugins/samplesource/plutosdrinput/plutosdrinputthread.cpp @@ -96,7 +96,7 @@ void PlutoSDRInputThread::run() if (nbytes_rx != m_blockSizeSamples*2) { - qWarning("PlutoSDRInputThread::run: error refilling buf (1) %d / %d\n",(int) nbytes_rx, (int) m_blockSizeSamples*2); + qWarning("PlutoSDRInputThread::run: error refilling buf (1) %d / %d",(int) nbytes_rx, (int) m_blockSizeSamples*2); usleep(200000); continue; } @@ -121,7 +121,7 @@ void PlutoSDRInputThread::run() if (nbytes_rx != m_blockSizeSamples*2) { - qWarning("PlutoSDRInputThread::run: error refilling buf (2) %d / %d\n",(int) nbytes_rx, (int) m_blockSizeSamples*2); + qWarning("PlutoSDRInputThread::run: error refilling buf (2) %d / %d",(int) nbytes_rx, (int) m_blockSizeSamples*2); usleep(200000); continue; } diff --git a/plugins/samplesource/plutosdrinput/plutosdrinputthread.h b/plugins/samplesource/plutosdrinput/plutosdrinputthread.h index e79630334..4bd285aee 100644 --- a/plugins/samplesource/plutosdrinput/plutosdrinputthread.h +++ b/plugins/samplesource/plutosdrinput/plutosdrinputthread.h @@ -59,11 +59,7 @@ private: int m_fcPos; float m_phasor; -#ifdef SDR_RX_SAMPLE_24BIT - Decimators m_decimators; -#else Decimators m_decimators; -#endif void run(); void convert(const qint16* buf, qint32 len); diff --git a/plugins/samplesource/plutosdrinput/readme.md b/plugins/samplesource/plutosdrinput/readme.md index 409370b37..11614c512 100644 --- a/plugins/samplesource/plutosdrinput/readme.md +++ b/plugins/samplesource/plutosdrinput/readme.md @@ -2,7 +2,7 @@

Introduction

-This input sample source plugin gets its samples from a [PlutoSDR device](https://wiki.analog.com/university/tools/pluto). This is also known as the ADALM-Pluto. ADALM stands for Analog Devices Active Learning Module and is targetting students in electrical engineering and digital signal processing. Of course it can be used as a radio device like any other SDR. +This input sample source plugin gets its samples from a [PlutoSDR device](https://wiki.analog.com/university/tools/pluto). This is also known as the ADALM-Pluto. ADALM stands for Analog Devices Active Learning Module and is targeting students in electrical engineering and digital signal processing. Of course it can be used as a radio device like any other SDR. As you can see from the Wiki this is becoming a fairly popular SDR hardware platform. It does have interesting features but the library documentation and examples are poor when not misleading. Therefore while this implementation does work it should still be considered experimental. @@ -45,7 +45,7 @@ Device start / stop button. - Blue triangle icon: device is ready and can be started - Green square icon: device is running and can be stopped - - Magenta (or pink) square icon: an error occured. In the case the device was accidentally disconnected you may click on the icon to stop, plug back in, check the source on the sampling devices control panel and start again. + - Magenta (or pink) square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon to stop, plug back in, check the source on the sampling devices control panel and start again.

1.3: Record

@@ -76,13 +76,13 @@ These buttons control the software DSP auto correction options: This button opens a dialog to set the transverter mode frequency translation options: -![SDR Daemon source input stream trasverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) +![SDR Daemon source input stream transverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) Note that if you mouse over the button a tooltip appears that displays the translating frequency and if translation is enabled or disabled. When the frequency translation is enabled the button is lit.

4a.1: Translating frequency

-You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. The frequency set in the device is the frequency on the main dial (1) minus this frequency. Thus it is positive for down converters and negative for up converters. @@ -104,13 +104,18 @@ Use these buttons to confirm ("OK") or dismiss ("Cancel") your changes.

5: Software decimation factor

-The I/Q stream from the PlutoSDR is doensampled by a power of two by software inside the plugin before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8, 16, 32, 64. +The I/Q stream from the PlutoSDR is downsampled by a power of two by software inside the plugin before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8, 16, 32, 64. -

6: Decimated bandpass center frequency placement

+

6: Decimated bandpass center frequency position relative the the PlutoSDR Rx center frequency

- - **Inf**: Infradyne: the decimation takes place in the lower sideband - - **Sup**: Supradyne: the decimation takes place in the lower sideband - - **Cen**: Centered: the decimation takes place around the center + - **Cen**: the decimation operation takes place around the PlutoSDR Rx center frequency Fs + - **Inf**: the decimation operation takes place around Fs - Fc. + - **Sup**: the decimation operation takes place around Fs + Fc. + +With SR as the sample rate before decimation Fc is calculated as: + + - if decimation n is 4 or lower: Fc = SR/2^(log2(n)-1). The device center frequency is on the side of the baseband. You need a RF filter bandwidth at least twice the baseband. + - if decimation n is 8 or higher: Fc = SR/n. The device center frequency is half the baseband away from the side of the baseband. You need a RF filter bandwidth at least 3 times the baseband.

7: Antenna (input) connection

@@ -120,7 +125,7 @@ The AD9363 has many port options however as only the A balanced input is connect This is the AD9363 device to/from host stream sample rate in S/s. It is the same for the Rx and Tx systems. -Use the wheels to adjust the sample rate. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. +Use the wheels to adjust the sample rate. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. The minimum sample rate depends on the hardware FIR decimation factor (12) and is the following: @@ -132,7 +137,7 @@ The maximum sample rate is fixed and set to 20 MS/s

9: Rx analog filter bandwidth

-This is the Rx analog filter bandwidth in kHz in the AD9363 device. It can be varied from 200 kHz to 14 MHz in 1 kHz steps. Use the wheels to adjust the value. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +This is the Rx analog filter bandwidth in kHz in the AD9363 device. It can be varied from 200 kHz to 14 MHz in 1 kHz steps. Use the wheels to adjust the value. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

10: Hardware FIR filter toggle

@@ -142,11 +147,11 @@ The FIR filter settings are the same on Rx and Tx side therefore any change here

11: Hardware FIR filter bandwidth

-Use the wheels to adjust the bandwidth of the hardware FIR filter. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the bandwidth of the hardware FIR filter. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. The filter limits are calculated as 0.05 and 0.9 times the FIR filter input frequency for the lower and higher limit respectively. The FIR filter input frequency is the baseband sample rate (5) multiplied by the FIR interpolation factor (9) -For bandwidths greater than 0.2 times the FIR filter input frequency the filter is calculated as a windowed FIR filter with a Blackman-Harris window. This has a high out of band rejection value at the expense of a slightly smoother roll off compared to other filters. The bandwidth value sets the -6 dB point approxomately. +For bandwidths greater than 0.2 times the FIR filter input frequency the filter is calculated as a windowed FIR filter with a Blackman-Harris window. This has a high out of band rejection value at the expense of a slightly smoother roll off compared to other filters. The bandwidth value sets the -6 dB point approximately. For bandwidths between 0.05 and 0.2 times the FIR filter input frequency the window used is a Hamming window giving a sharper transition. @@ -183,4 +188,4 @@ This is the indicative RSSI of the receiver.

17: Board temperature

-This is the board temperature in degrees Celsius updated every ~5s. \ No newline at end of file +This is the board temperature in degrees Celsius updated every ~5s. diff --git a/plugins/samplesource/rtlsdr/CMakeLists.txt b/plugins/samplesource/rtlsdr/CMakeLists.txt index 5ce6daa93..918d5641b 100644 --- a/plugins/samplesource/rtlsdr/CMakeLists.txt +++ b/plugins/samplesource/rtlsdr/CMakeLists.txt @@ -72,6 +72,6 @@ target_link_libraries(inputrtlsdr endif (BUILD_DEBIAN) -qt5_use_modules(inputrtlsdr Core Widgets) +target_link_libraries(inputrtlsdr Qt5::Core Qt5::Widgets) install(TARGETS inputrtlsdr DESTINATION lib/plugins/samplesource) diff --git a/plugins/samplesource/rtlsdr/readme.md b/plugins/samplesource/rtlsdr/readme.md index 0e28d4efd..ebe304077 100644 --- a/plugins/samplesource/rtlsdr/readme.md +++ b/plugins/samplesource/rtlsdr/readme.md @@ -28,7 +28,7 @@ Device start / stop button. - Blue triangle icon: device is ready and can be started - Green square icon: device is running and can be stopped - - Magenta (or pink) square icon: an error occured. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again. + - Magenta (or pink) square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again.

1.3: Record

@@ -49,25 +49,28 @@ These buttons control the local DSP auto correction options: - **DC**: auto remove DC component - **IQ**: auto make I/Q balance. The DC correction must be enabled for this to be effective. -

4: Baseband center frequency position relative the center frequency

+

4: Decimated bandpass center frequency position relative the RTL-SDR center frequency

-Possible values are: + - **Cen**: the decimation operation takes place around the RTL-SDR center frequency Fs + - **Inf**: the decimation operation takes place around Fs - Fc. + - **Sup**: the decimation operation takes place around Fs + Fc. + +With SR as the sample rate before decimation Fc is calculated as: - - **Cen**: the decimation operation takes place around the BladeRF Rx center frequency - - **Inf**: the decimation operation takes place around the center of the lower half of the BladeRF Rx passband. - - **Sup**: the decimation operation takes place around the center of the upper half of the BladeRF Rx passband. + - if decimation n is 4 or lower: Fc = SR/2^(log2(n)-1). The device center frequency is on the side of the baseband. You need a RF filter bandwidth at least twice the baseband. + - if decimation n is 8 or higher: Fc = SR/n. The device center frequency is half the baseband away from the side of the baseband. You need a RF filter bandwidth at least 3 times the baseband.

4a: Transverter mode open dialog

This button opens a dialog to set the transverter mode frequency translation options: -![SDR Daemon source input stream trasverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) +![SDR Daemon source input stream transverter dialog](../../../doc/img/RTLSDR_plugin_xvrt.png) Note that if you mouse over the button a tooltip appears that displays the translating frequency and if translation is enabled or disabled. When the frequency translation is enabled the button is lit.

4a.1: Translating frequency

-You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +You can set the translating frequency in Hz with this dial. Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. The frequency set in the device is the frequency on the main dial (1) minus this frequency. Thus it is positive for down converters and negative for up converters. @@ -96,11 +99,11 @@ When button is off the sample rate can vary from 950 kS/s to 2400 kS/s This is the device sample rate in samples per second (S/s). -Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.

7: Decimation factor

-The I/Q stream from the RTLSDR ADC is doensampled by a power of two before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8, 16, 32. +The I/Q stream from the RTLSDR ADC is downsampled by a power of two before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8, 16, 32, 64.

8: Direct sampling mode

@@ -114,4 +117,4 @@ This controls the tuner filter bandwidth and can be varied from 350 kHz to 8 MHz The slider sets RF gain in dB. The values are defined in the RTLSDR device and generally are: 0.0, 0.9, 1.4, 2.7, 3.7, 7.7, 8.7, 12.5, 14.4, 15.7, 16.6, 19.7, 20.7, 22.9, 25.4, 28.0, 29.7, 32.8, 33.8, 36.4, 37.2, 38.6, 40.2, 42.1, 43.4, 43.9, 44.5, 48.0, 49.6 -The AGC checkbox can be used to switch on or off the RTL2838 AGC. This is independent of the gain setting as this AGC acts after the gain block. \ No newline at end of file +The AGC checkbox can be used to switch on or off the RTL2838 AGC. This is independent of the gain setting as this AGC acts after the gain block. diff --git a/plugins/samplesource/rtlsdr/rtlsdr.pro b/plugins/samplesource/rtlsdr/rtlsdr.pro index a2e403e3d..f88833f4a 100644 --- a/plugins/samplesource/rtlsdr/rtlsdr.pro +++ b/plugins/samplesource/rtlsdr/rtlsdr.pro @@ -17,12 +17,16 @@ DEFINES += USE_SSE4_1=1 QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 -CONFIG(MINGW32):LIBRTLSDRSRC = "D:\softs\librtlsdr" -CONFIG(MINGW64):LIBRTLSDRSRC = "D:\softs\librtlsdr" +CONFIG(MINGW32):LIBRTLSDRSRC = "C:\softs\librtlsdr" +CONFIG(MINGW64):LIBRTLSDRSRC = "C:\softs\librtlsdr" +CONFIG(MSVC):LIBRTLSDRSRC = "C:\softs\librtlsdr" + INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client + !macx:INCLUDEPATH += $$LIBRTLSDRSRC/include macx:INCLUDEPATH += /opt/local/include diff --git a/plugins/samplesource/rtlsdr/rtlsdrgui.cpp b/plugins/samplesource/rtlsdr/rtlsdrgui.cpp index 7b97cc9c8..381a2b053 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrgui.cpp +++ b/plugins/samplesource/rtlsdr/rtlsdrgui.cpp @@ -38,7 +38,7 @@ RTLSDRGui::RTLSDRGui(DeviceUISet *deviceUISet, QWidget* parent) : m_forceSettings(true), m_settings(), m_sampleSource(0), - m_lastEngineState((DSPDeviceSourceEngine::State)-1) + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted) { m_sampleSource = (RTLSDRInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); @@ -62,6 +62,7 @@ RTLSDRGui::RTLSDRGui(DeviceUISet *deviceUISet, QWidget* parent) : displayGains(); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); } RTLSDRGui::~RTLSDRGui() @@ -259,6 +260,7 @@ void RTLSDRGui::displaySettings() ui->fcPos->setCurrentIndex((int) m_settings.m_fcPos); ui->checkBox->setChecked(m_settings.m_noModMode); ui->agc->setChecked(m_settings.m_agc); + ui->lowSampleRate->setChecked(m_settings.m_lowSampleRate); } void RTLSDRGui::sendSettings() @@ -277,7 +279,7 @@ void RTLSDRGui::on_centerFrequency_changed(quint64 value) void RTLSDRGui::on_decim_currentIndexChanged(int index) { - if ((index <0) || (index > 5)) + if ((index <0) || (index > 6)) { return; } @@ -447,7 +449,9 @@ void RTLSDRGui::on_rfBW_changed(quint64 value) void RTLSDRGui::on_lowSampleRate_toggled(bool checked) { - if (checked) { + m_settings.m_lowSampleRate = checked; + + if (m_settings.m_lowSampleRate) { ui->sampleRate->setValueRange(7, RTLSDRInput::sampleRateLowRangeMin, RTLSDRInput::sampleRateLowRangeMax); } else { ui->sampleRate->setValueRange(7, RTLSDRInput::sampleRateHighRangeMin, RTLSDRInput::sampleRateHighRangeMax); diff --git a/plugins/samplesource/rtlsdr/rtlsdrgui.ui b/plugins/samplesource/rtlsdr/rtlsdrgui.ui index 5cfd01cb9..79b0dd127 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrgui.ui +++ b/plugins/samplesource/rtlsdr/rtlsdrgui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -135,7 +135,7 @@ - DejaVu Sans Mono + Liberation Mono 20 @@ -269,7 +269,7 @@ - Relative postion of device center frequency + Relative position of device center frequency 2 @@ -376,7 +376,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -459,6 +459,11 @@ 32 + + + 64 + + @@ -511,7 +516,7 @@ - DejaVu Sans Mono + Liberation Mono 12 diff --git a/plugins/samplesource/rtlsdr/rtlsdrinput.cpp b/plugins/samplesource/rtlsdr/rtlsdrinput.cpp index 5a0b711dc..406f104ba 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrinput.cpp +++ b/plugins/samplesource/rtlsdr/rtlsdrinput.cpp @@ -22,6 +22,8 @@ #include "SWGDeviceSettings.h" #include "SWGRtlSdrSettings.h" #include "SWGDeviceState.h" +#include "SWGDeviceReport.h" +#include "SWGRtlSdrReport.h" #include "rtlsdrinput.h" #include "device/devicesourceapi.h" @@ -34,7 +36,7 @@ MESSAGE_CLASS_DEFINITION(RTLSDRInput::MsgConfigureRTLSDR, Message) MESSAGE_CLASS_DEFINITION(RTLSDRInput::MsgFileRecord, Message) MESSAGE_CLASS_DEFINITION(RTLSDRInput::MsgStartStop, Message) -const quint64 RTLSDRInput::frequencyLowRangeMin = 1000UL; +const quint64 RTLSDRInput::frequencyLowRangeMin = 0UL; const quint64 RTLSDRInput::frequencyLowRangeMax = 275000UL; const quint64 RTLSDRInput::frequencyHighRangeMin = 24000UL; const quint64 RTLSDRInput::frequencyHighRangeMax = 1900000UL; @@ -53,9 +55,7 @@ RTLSDRInput::RTLSDRInput(DeviceSourceAPI *deviceAPI) : { openDevice(); - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID()); - m_fileSink = new FileRecord(std::string(recFileNameCStr)); + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); m_deviceAPI->addSink(m_fileSink); } @@ -187,13 +187,7 @@ bool RTLSDRInput::start() if (m_running) stop(); - if ((m_rtlSDRThread = new RTLSDRThread(m_dev, &m_sampleFifo)) == NULL) - { - qCritical("RTLSDRInput::start: out of memory"); - stop(); - return false; - } - + m_rtlSDRThread = new RTLSDRThread(m_dev, &m_sampleFifo); m_rtlSDRThread->setSamplerate(m_settings.m_devSampleRate); m_rtlSDRThread->setLog2Decimation(m_settings.m_log2Decim); m_rtlSDRThread->setFcPos((int) m_settings.m_fcPos); @@ -312,9 +306,18 @@ bool RTLSDRInput::handleMessage(const Message& message) MsgFileRecord& conf = (MsgFileRecord&) message; qDebug() << "RTLSDRInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); - if (conf.getStartStop()) { + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSink->setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); + } + m_fileSink->startRecording(); - } else { + } + else + { m_fileSink->stopRecording(); } @@ -330,13 +333,11 @@ bool RTLSDRInput::handleMessage(const Message& message) if (m_deviceAPI->initAcquisition()) { m_deviceAPI->startAcquisition(); - DSPEngine::instance()->startAudioOutput(); } } else { m_deviceAPI->stopAcquisition(); - DSPEngine::instance()->stopAudioOutput(); } return true; @@ -441,72 +442,51 @@ bool RTLSDRInput::applySettings(const RTLSDRSettings& settings, bool force) if ((m_settings.m_centerFrequency != settings.m_centerFrequency) || (m_settings.m_fcPos != settings.m_fcPos) || (m_settings.m_log2Decim != settings.m_log2Decim) + || (m_settings.m_devSampleRate != settings.m_devSampleRate) || (m_settings.m_transverterMode != settings.m_transverterMode) || (m_settings.m_transverterDeltaFrequency != settings.m_transverterDeltaFrequency) || force) { + qint64 deviceCenterFrequency = DeviceSampleSource::calculateDeviceCenterFrequency( + settings.m_centerFrequency, + settings.m_transverterDeltaFrequency, + settings.m_log2Decim, + (DeviceSampleSource::fcPos_t) settings.m_fcPos, + settings.m_devSampleRate, + settings.m_transverterMode); + m_settings.m_centerFrequency = settings.m_centerFrequency; + m_settings.m_log2Decim = settings.m_log2Decim; + m_settings.m_devSampleRate = settings.m_devSampleRate; m_settings.m_transverterMode = settings.m_transverterMode; m_settings.m_transverterDeltaFrequency = settings.m_transverterDeltaFrequency; - m_settings.m_log2Decim = settings.m_log2Decim; - qint64 deviceCenterFrequency = m_settings.m_centerFrequency; - deviceCenterFrequency -= m_settings.m_transverterMode ? m_settings.m_transverterDeltaFrequency : 0; - deviceCenterFrequency = deviceCenterFrequency < 0 ? 0 : deviceCenterFrequency; - qint64 f_img = deviceCenterFrequency; - quint32 devSampleRate = m_settings.m_devSampleRate; forwardChange = true; - if ((m_settings.m_log2Decim == 0) || (settings.m_fcPos == RTLSDRSettings::FC_POS_CENTER)) + if ((m_settings.m_fcPos != settings.m_fcPos) || force) { - f_img = deviceCenterFrequency; - } - else - { - if (settings.m_fcPos == RTLSDRSettings::FC_POS_INFRA) - { - deviceCenterFrequency += (devSampleRate / 4); - f_img = deviceCenterFrequency + devSampleRate/2; - } - else if (settings.m_fcPos == RTLSDRSettings::FC_POS_SUPRA) - { - deviceCenterFrequency -= (devSampleRate / 4); - f_img = deviceCenterFrequency - devSampleRate/2; + m_settings.m_fcPos = settings.m_fcPos; + + if (m_rtlSDRThread != 0) { + m_rtlSDRThread->setFcPos((int) m_settings.m_fcPos); } + + qDebug() << "RTLSDRInput::applySettings: set fc pos (enum) to " << (int) m_settings.m_fcPos; } if (m_dev != 0) { - if (rtlsdr_set_center_freq( m_dev, deviceCenterFrequency ) != 0) - { - qDebug("rtlsdr_set_center_freq(%lld) failed", deviceCenterFrequency); - } - else - { - qDebug() << "RTLSDRInput::applySettings: center freq: " << m_settings.m_centerFrequency << " Hz" - << " device center freq: " << deviceCenterFrequency << " Hz" - << " device sample rate: " << devSampleRate << "S/s" - << " Actual sample rate: " << devSampleRate/(1<setFcPos((int) m_settings.m_fcPos); - } - - qDebug() << "RTLSDRInput: set fc pos (enum) to " << (int) m_settings.m_fcPos; - } - if ((m_settings.m_noModMode != settings.m_noModMode) || force) { m_settings.m_noModMode = settings.m_noModMode; - qDebug() << "RTLSDRInput: set noModMode to " << m_settings.m_noModMode; + qDebug() << "RTLSDRInput::applySettings: set noModMode to " << m_settings.m_noModMode; // Direct Modes: 0: off, 1: I, 2: Q, 3: NoMod. if (m_settings.m_noModMode) { @@ -516,6 +496,11 @@ bool RTLSDRInput::applySettings(const RTLSDRSettings& settings, bool force) } } + if ((m_settings.m_lowSampleRate != settings.m_lowSampleRate) || force) + { + m_settings.m_lowSampleRate = settings.m_lowSampleRate; + } + if ((m_settings.m_rfBandwidth != settings.m_rfBandwidth) || force) { m_settings.m_rfBandwidth = settings.m_rfBandwidth; @@ -607,7 +592,10 @@ int RTLSDRInput::webapiSettingsPutPatch( settings.m_transverterMode = response.getRtlSdrSettings()->getTransverterMode() != 0; } if (deviceSettingsKeys.contains("rfBandwidth")) { - settings.m_rfBandwidth = response.getRtlSdrSettings()->getRfBandwidth() != 0; + settings.m_rfBandwidth = response.getRtlSdrSettings()->getRfBandwidth(); + } + if (deviceSettingsKeys.contains("fileRecordName")) { + settings.m_fileRecordName = *response.getRtlSdrSettings()->getFileRecordName(); } MsgConfigureRTLSDR *msg = MsgConfigureRTLSDR::create(settings, force); @@ -625,6 +613,7 @@ int RTLSDRInput::webapiSettingsPutPatch( void RTLSDRInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const RTLSDRSettings& settings) { + qDebug("RTLSDRInput::webapiFormatDeviceSettings: m_lowSampleRate: %s", settings.m_lowSampleRate ? "true" : "false"); response.getRtlSdrSettings()->setAgc(settings.m_agc ? 1 : 0); response.getRtlSdrSettings()->setCenterFrequency(settings.m_centerFrequency); response.getRtlSdrSettings()->setDcBlock(settings.m_dcBlock ? 1 : 0); @@ -639,6 +628,12 @@ void RTLSDRInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& res response.getRtlSdrSettings()->setTransverterDeltaFrequency(settings.m_transverterDeltaFrequency); response.getRtlSdrSettings()->setTransverterMode(settings.m_transverterMode ? 1 : 0); response.getRtlSdrSettings()->setRfBandwidth(settings.m_rfBandwidth); + + if (response.getRtlSdrSettings()->getFileRecordName()) { + *response.getRtlSdrSettings()->getFileRecordName() = settings.m_fileRecordName; + } else { + response.getRtlSdrSettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + } } int RTLSDRInput::webapiRunGet( @@ -666,3 +661,26 @@ int RTLSDRInput::webapiRun( return 200; } + +int RTLSDRInput::webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setRtlSdrReport(new SWGSDRangel::SWGRtlSdrReport()); + response.getRtlSdrReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} + +void RTLSDRInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + response.getRtlSdrReport()->setGains(new QList); + + for (std::vector::const_iterator it = getGains().begin(); it != getGains().end(); ++it) + { + response.getRtlSdrReport()->getGains()->append(new SWGSDRangel::SWGGain); + response.getRtlSdrReport()->getGains()->back()->setGainCb(*it); + } +} + + diff --git a/plugins/samplesource/rtlsdr/rtlsdrinput.h b/plugins/samplesource/rtlsdr/rtlsdrinput.h index 2ec558f5f..c67b6815d 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrinput.h +++ b/plugins/samplesource/rtlsdr/rtlsdrinput.h @@ -121,6 +121,10 @@ public: SWGSDRangel::SWGDeviceSettings& response, // query + response QString& errorMessage); + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + virtual int webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage); @@ -157,6 +161,7 @@ private: void closeDevice(); bool applySettings(const RTLSDRSettings& settings, bool force); void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const RTLSDRSettings& settings); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); }; #endif // INCLUDE_RTLSDRINPUT_H diff --git a/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp b/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp index 98034f378..e4d4c5959 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp +++ b/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp @@ -14,7 +14,7 @@ const PluginDescriptor RTLSDRPlugin::m_pluginDescriptor = { QString("RTL-SDR Input"), - QString("3.11.0"), + QString("4.0.6"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/rtlsdr/rtlsdrsettings.cpp b/plugins/samplesource/rtlsdr/rtlsdrsettings.cpp index 479fa6c96..6b5fd6ead 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrsettings.cpp +++ b/plugins/samplesource/rtlsdr/rtlsdrsettings.cpp @@ -39,6 +39,7 @@ void RTLSDRSettings::resetToDefaults() m_transverterMode = false; m_transverterDeltaFrequency = 0; m_rfBandwidth = 2500 * 1000; // Hz + m_fileRecordName = ""; } QByteArray RTLSDRSettings::serialize() const diff --git a/plugins/samplesource/rtlsdr/rtlsdrsettings.h b/plugins/samplesource/rtlsdr/rtlsdrsettings.h index 32303eff5..bf2ad2c60 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrsettings.h +++ b/plugins/samplesource/rtlsdr/rtlsdrsettings.h @@ -17,6 +17,8 @@ #ifndef _RTLSDR_RTLSDRSETTINGS_H_ #define _RTLSDR_RTLSDRSETTINGS_H_ +#include + struct RTLSDRSettings { typedef enum { FC_POS_INFRA = 0, @@ -38,6 +40,7 @@ struct RTLSDRSettings { bool m_transverterMode; qint64 m_transverterDeltaFrequency; quint32 m_rfBandwidth; //!< RF filter bandwidth in Hz + QString m_fileRecordName; RTLSDRSettings(); void resetToDefaults(); diff --git a/plugins/samplesource/rtlsdr/rtlsdrthread.cpp b/plugins/samplesource/rtlsdr/rtlsdrthread.cpp index 6c1ee8a17..7c00695a9 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrthread.cpp +++ b/plugins/samplesource/rtlsdr/rtlsdrthread.cpp @@ -116,6 +116,9 @@ void RTLSDRThread::callback(const quint8* buf, qint32 len) break; case 5: m_decimators.decimate32_inf(&it, buf, len); + break; + case 6: + m_decimators.decimate64_inf(&it, buf, len); break; default: break; @@ -139,6 +142,9 @@ void RTLSDRThread::callback(const quint8* buf, qint32 len) break; case 5: m_decimators.decimate32_sup(&it, buf, len); + break; + case 6: + m_decimators.decimate64_sup(&it, buf, len); break; default: break; @@ -162,6 +168,9 @@ void RTLSDRThread::callback(const quint8* buf, qint32 len) break; case 5: m_decimators.decimate32_cen(&it, buf, len); + break; + case 6: + m_decimators.decimate64_cen(&it, buf, len); break; default: break; diff --git a/plugins/samplesource/rtlsdr/rtlsdrthread.h b/plugins/samplesource/rtlsdr/rtlsdrthread.h index 97f3ed56d..081cba920 100644 --- a/plugins/samplesource/rtlsdr/rtlsdrthread.h +++ b/plugins/samplesource/rtlsdr/rtlsdrthread.h @@ -52,11 +52,7 @@ private: unsigned int m_log2Decim; int m_fcPos; -#ifdef SDR_RX_SAMPLE_24BIT - DecimatorsU m_decimators; -#else DecimatorsU m_decimators; -#endif void run(); void callback(const quint8* buf, qint32 len); diff --git a/plugins/samplesource/sdrdaemonsource/CMakeLists.txt b/plugins/samplesource/sdrdaemonsource/CMakeLists.txt index 477561e9b..387ad4ed9 100644 --- a/plugins/samplesource/sdrdaemonsource/CMakeLists.txt +++ b/plugins/samplesource/sdrdaemonsource/CMakeLists.txt @@ -53,7 +53,6 @@ target_include_directories(inputsdrdaemonsource PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${LIBCM256CCSRC} - ${LIBNANOMSG_INCLUDE_DIR} ) else (BUILD_DEBIAN) target_include_directories(inputsdrdaemonsource PUBLIC @@ -61,7 +60,6 @@ target_include_directories(inputsdrdaemonsource PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${CM256CC_INCLUDE_DIR} - ${LIBNANOMSG_INCLUDE_DIR} ) endif (BUILD_DEBIAN) @@ -69,7 +67,6 @@ if (BUILD_DEBIAN) target_link_libraries(inputsdrdaemonsource ${QT_LIBRARIES} cm256cc - ${LIBNANOMSG_LIBRARIES} sdrbase sdrgui swagger @@ -78,13 +75,12 @@ else (BUILD_DEBIAN) target_link_libraries(inputsdrdaemonsource ${QT_LIBRARIES} ${CM256CC_LIBRARIES} - ${LIBNANOMSG_LIBRARIES} sdrbase sdrgui swagger ) endif (BUILD_DEBIAN) -qt5_use_modules(inputsdrdaemonsource Core Widgets) +target_link_libraries(inputsdrdaemonsource Qt5::Core Qt5::Widgets) install(TARGETS inputsdrdaemonsource DESTINATION lib/plugins/samplesource) diff --git a/plugins/samplesource/sdrdaemonsource/readme.md b/plugins/samplesource/sdrdaemonsource/readme.md index 36d72f053..50313c04c 100644 --- a/plugins/samplesource/sdrdaemonsource/readme.md +++ b/plugins/samplesource/sdrdaemonsource/readme.md @@ -2,15 +2,21 @@

Introduction

-This input sample source plugin gets its samples over tbe network from a SDRdaemon receiver server using UDP connection. SDRdaemon refers to the SDRdaemon utility `sdrdaemonrx`found in [this](https://github.com/f4exb/sdrdaemon) Github repostory. +This input sample source plugin gets its samples over tbe network from a SDRangel instance's Daemon channel sink using UDP connection. Forward Error Correction with a Cauchy MDS block erasure codec is used to prevent block loss. This can make the UDP transmission more robust particularly over WiFi links. Please note that there is no provision for handling out of sync UDP blocks. It is assumed that frames and block numbers always increase with possible blocks missing. Such out of sync situation has never been encountered in practice. +The distant SDRangel instance that sends the data stream is controlled via its REST API using a separate control software for example [SDRangelcli](https://github.com/f4exb/sdrangelcli) + +A sample size conversion takes place if the stream sample size sent by the distant instance and the Rx sample size of the local instance do not match (i.e. 16 to 24 bits or 24 to 16 bits). Best performace is obtained when both instances use the same sample size. + +It is present only in Linux binary releases. +

Build

-The plugin will be built only if `libnanomsg` and the [CM256cc library](https://github.com/f4exb/cm256cc) is installed in your system. `libnanomasg` is present in most distributions and the dev version can be installed using the package manager. For CM256cc library you will have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_INCLUDE_DIR=/opt/install/cm256cc/include/cm256cc -DCM256CC_LIBRARIES=/opt/install/cm256cc/lib/libcm256cc.so` to the cmake commands. +The plugin will be built only if the [CM256cc library](https://github.com/f4exb/cm256cc) is installed in your system. For CM256cc library you will have to specify the include and library paths on the cmake command line. Say if you install cm256cc in `/opt/install/cm256cc` you will have to add `-DCM256CC_INCLUDE_DIR=/opt/install/cm256cc/include/cm256cc -DCM256CC_LIBRARIES=/opt/install/cm256cc/lib/libcm256cc.so` to the cmake commands.

Interface

@@ -33,7 +39,7 @@ Device start / stop button.

1.3: Record

-Record I/Q stresm toggle button +Record I/Q stream toggle button

1.4: Stream sample rate

@@ -41,7 +47,7 @@ Stream I/Q sample rate in kS/s

2: Auto correction options and stream status

-![SDR Daemon source input AC and stream1 GUI](../../../doc/img/SDRdaemonSource_plugin_02.png) +![SDR Daemon source input stream GUI](../../../doc/img/SDRdaemonSource_plugin_02.png)

2.1: Auto correction options

@@ -74,22 +80,24 @@ There are two gauges separated by a dot in the center. Ideally these gauges shou - The left gauge is the negative gauge. It is the value in percent of buffer size from the write pointer position to the read pointer position when this difference is less than half of a buffer distance. It means that the writes are leading or reads are lagging. - The right gauge is the positive gauge. It is the value in percent of buffer size of the difference from the read pointer position to the write pointer position when this difference is less than half of a buffer distance. It menas that the writes are lagging or reads are leading. -The system tries to compensate read / write unbalance however at start or when a large stream distruption has occured a delay of a few tens of seconds is necessary before read / write reaches equilibrium. +The system tries to compensate read / write unbalance however at start or when a large stream disruption has occurred a delay of a few tens of seconds is necessary before read / write reaches equilibrium. -

4: Forward Error Correction setting and status

+

4: Data stream status

-![SDR Daemon source input FEC GUI](../../../doc/img/SDRdaemonSource_plugin_04.png) +![SDR Daemon source input stream GUI](../../../doc/img/SDRdaemonSource_plugin_04.png) -

4.1: Desired number of FEC blocks per frame

+

4.1: Sample size

-This is the number of FEC blocks per frame set by the user. A frame consists of 128 data blocks (1 meta data block followed by 127 I/Q data blocks) and a variable number of FEC blocks used to protect the UDP transmission with a Cauchy MDS block erasure correction. - -Using the Cauchy MDS block erasure correction ensures that if at least the number of data blocks (128) is received per complete frame then all lost blocks in any position can be restored. For example if 8 FEC blocks are used then 136 blocks are transmitted per frame. If only 130 blocks (128 or greater) are received then data can be recovered. If only 127 blocks (or less) are received then none of the lost blocks can be recovered. +This is the size in bits of a I or Q sample sent in the stream by the distant server.

4.2: Total number of frames and number of FEC blocks

This is the total number of frames and number of FEC blocks separated by a slash '/' as sent in the meta data block thus acknowledged by the distant server. When you set the number of FEC blocks with (4.1) the effect may not be immediate and this information can be used to monitor when it gets effectively set in the distant server. +A frame consists of 128 data blocks (1 meta data block followed by 127 I/Q data blocks) and a variable number of FEC blocks used to protect the UDP transmission with a Cauchy MDS block erasure correction. + +Using the Cauchy MDS block erasure correction ensures that if at least the number of data blocks (128) is received per complete frame then all lost blocks in any position can be restored. For example if 8 FEC blocks are used then 136 blocks are transmitted per frame. If only 130 blocks (128 or greater) are received then data can be recovered. If only 127 blocks (or less) are received then none of the lost blocks can be recovered. +

4.3: Stream status

The color of the icon indicates stream status: @@ -102,7 +110,7 @@ The color of the icon indicates stream status: This is the minimum total number of blocks per frame during the last polling period. If all blocks were received for all frames then this number is the nominal number of original blocks plus FEC blocks (Green lock icon). In our example this is 128+8 = 136. -If this number falls below 128 then some blocks are definitely lost and the lock lits in red. +If this number falls below 128 then some blocks are definitely lost and the lock lights in red.

4.5: Maximum number of FEC blocks used by frame

@@ -122,69 +130,48 @@ This counter counts the unrecoverable error conditions found (i.e. 4.4 between 1

4.9: events counters timer

-This hh:mm:ss time display shows the time since the reset evetnts counters button (4.6) was pushed. +This HH:mm:ss time display shows the time since the reset events counters button (4.6) was pushed. -

5: Network parameters

+

5: Distant server API address and port

-![SDR Daemon status3 GUI](../../../doc/img/SDRdaemonSource_plugin_05.png) +![SDR Daemon source input stream GUI](../../../doc/img/SDRdaemonSource_plugin_05.png) -

5.1: Local interface IP address

+

5.1: API connection indicator

-Address of the network interface on the local (your) machine to which the SDRdaemon Rx server sends samples to. +The "API" label is lit in green when the connection is successful -

5.2: Local data port

+

5.2: API IP address

-UDP port on the local (your) machine to which the SDRdaemon Rx server sends samples to. +IP address of the distant SDRangel instance REST API -

5.3 Distant configuration port

+

5.3: API port

-TCP port on the distant machine hosting the SDRdaemon Rx instance to send control messages to. The IP address of the host where the SDRdaemon instance runs is guessed from the address sending the data blocks hence the distant address does not need to be specified. +Port of the distant SDRangel instance REST API

5.4: Validation button

-When the return key is hit within the address (5.1), data port (5.2) or configuration port (5.3) boxes the changes are effective immediately. You can also use this button to set again these values. +When the return key is hit within the address (5.2) or port (5.3) the changes are effective immediately. You can also use this button to set again these values. Clicking on this button will send a request to the API to get the distant SDRangel instance information that is displayed in the API message box (8) -

6: Desired center frequency

+

6: Local data address and port

-This is the center frequency sent to the distant device. This becomes reflected in the main frequency dial (1.1) only when it gets acknowledged by the distant server and this frequency is sent back in the frames meta data. +![SDR Daemon source input stream GUI](../../../doc/img/SDRdaemonSource_plugin_06.png) -Use the wheels to adjust the frequency. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. The minimum value is 0 Hz and the maximum value is 9.9 GHz. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +

6.1: Data IP address

-

7: Delay between UDP blocks transmission

+IP address of the local network interface the distant SDRangel instance sends the data to -This sets the minimum delay between transmission of an UDP block (send datagram) and the next. This allows throttling of the UDP transmission that is otherwise uncontrolled and causes network congestion. +

6.2: Data port

-The value is a percentage of the nominal time it takes to process a block of samples corresponding to one UDP block (512 bytes). This is calculated as follows: +Local port the distant SDRangel instance sends the data to - - Sample rate on the network: _SR_ - - Delay percentage: _d_ - - Number of FEC blocks: _F_ - - There are 127 blocks of I/Q data per frame (1 meta block for 128 blocks) and each I/Q data block of 512 bytes (128 samples) has a 4 bytes header (1 sample) thus there are 127 samples remaining effectively. This gives the constant 127*127 = 16219 samples per frame in the formula - -Formula: ((127 ✕ 127 ✕ _d_) / _SR_) / (128 + _F_) +

6.3: Validation button

-

8: Desired distant device sample rate

+When the return key is hit within the address (5.2) or port (5.3) the changes are effective immediately. You can also use this button to set again these values. -This is the device sample rate sent to the distant device. It will be divided in the distant server by the decimation factor set with (9) to give the actual sample rate over the network. This becomes effective and displayed in (1.4) only when it gets acknowledged by the distant server and this sample rate is sent back in the frames meta data. +

7: Status message

-Use the wheels to adjust the sample rate. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. The minimum value is 32 kS/s and the maximum value is 9.9 MS/s. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arroews. Pressing shift simultanoeusly moves digit by 5 and pressing control moves it by 2. +The API status is displayed in this box. It shows "API OK" when the connection is successful and reply is OK -

9: Desired distant decimation factor

+

8: API information

-This is the decimation factor to be set in the distant server downsampler. The hardware device sample rate is divided by this factor before the I/Q blocks are sent over the network. The actual network sample rate becomes effective and displayed in (1.4) only when it gets acknowledged by the distant server and this sample rate is sent back in the frames meta data. - -

10: Center frequency position

- -The center frequency in the passband wil be set either: - - - below the local oscillator (NCO) or infradyne. Actually -1/4th the bandwidth. - - above the local oscillator (NCO) or supradyne. Actually +1/4th the bandwidth. - - centered on the local oscillator or zero IF. - -

11: Other parameters hardware specific

- -These are the parameters that are specific to the hardware attached to the distant SDRdaemon instance. You have to know which device is attached to send the proper parameters. Please refer to the SDRdaemon documentation or its line help to get information on these parameters. - -

12: Send data to the distant SDRdaemon Rx instance

- -When any of the parameters change they get immediately transmitted to the distant server over the TCP link. You can however use this button to send again the complete configuration. This is handy if for some reason you are unsure of the parameters set in the distant server. +This is the information returned by the API and is the distance SDRangel instance information if transaction is successful diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsource.pro b/plugins/samplesource/sdrdaemonsource/sdrdaemonsource.pro index d33662bdc..27270562d 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsource.pro +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsource.pro @@ -11,18 +11,15 @@ QT += core gui widgets multimedia network opengl TARGET = inputsdrdaemonsource -CONFIG(MINGW32):LIBNANOMSGSRC = "D:\softs\nanomsg-0.8-beta" -CONFIG(MINGW64):LIBNANOMSGSRC = "D:\softs\nanomsg-0.8-beta" - -CONFIG(MINGW32):LIBCM256CCSRC = "D:\softs\cm256cc" -CONFIG(MINGW64):LIBCM256CCSRC = "D:\softs\cm256cc" +CONFIG(MINGW32):LIBCM256CCSRC = "C:\softs\cm256cc" +CONFIG(MINGW64):LIBCM256CCSRC = "C:\softs\cm256cc" CONFIG(macx):LIBCM256CCSRC = "../../../../deps/cm256cc" INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client -!macx:INCLUDEPATH += $$LIBNANOMSGSRC/src macx:INCLUDEPATH += /opt/local/include INCLUDEPATH += $$LIBCM256CCSRC @@ -37,8 +34,8 @@ QMAKE_CXXFLAGS += -std=c++11 CONFIG(Release):build_subdir = release CONFIG(Debug):build_subdir = debug -CONFIG(MINGW32):INCLUDEPATH += "D:\boost_1_58_0" -CONFIG(MINGW64):INCLUDEPATH += "D:\boost_1_58_0" +CONFIG(MINGW32):INCLUDEPATH += "C:\softs\boost_1_66_0" +CONFIG(MINGW64):INCLUDEPATH += "C:\softs\boost_1_66_0" CONFIG(macx):INCLUDEPATH += "../../../boost_1_64_0" SOURCES += sdrdaemonsourcebuffer.cpp\ @@ -60,6 +57,7 @@ FORMS += sdrdaemonsourcegui.ui LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui LIBS += -L../../../swagger/$${build_subdir} -lswagger + !macx:LIBS += -L../../../nanomsg/$${build_subdir} -lnanomsg macx:LIBS += -L/usr/local/lib -lnanomsg LIBS += -L../../../cm256cc/$${build_subdir} -lcm256cc diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.cpp index 1ab67e08c..c1cfe6476 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.cpp @@ -18,16 +18,14 @@ #include #include #include +#include #include #include #include "sdrdaemonsourcebuffer.h" -const int SDRdaemonSourceBuffer::m_sampleSize = 2; -const int SDRdaemonSourceBuffer::m_iqSampleSize = 2 * m_sampleSize; - -SDRdaemonSourceBuffer::SDRdaemonSourceBuffer(uint32_t throttlems) : +SDRdaemonSourceBuffer::SDRdaemonSourceBuffer() : m_decoderIndexHead(nbDecoderSlots/2), m_frameHead(0), m_curNbBlocks(0), @@ -38,7 +36,6 @@ SDRdaemonSourceBuffer::SDRdaemonSourceBuffer(uint32_t throttlems) : m_maxNbRecovery(0), m_framesDecoded(true), m_readIndex(0), - m_throttlemsNominal(throttlems), m_readBuffer(0), m_readSize(0), m_bufferLenSec(0.0f), @@ -53,8 +50,8 @@ SDRdaemonSourceBuffer::SDRdaemonSourceBuffer(uint32_t throttlems) : m_tvOut_sec = 0; m_tvOut_usec = 0; m_readNbBytes = 1; - m_paramsCM256.BlockBytes = sizeof(ProtectedBlock); // never changes - m_paramsCM256.OriginalCount = m_nbOriginalBlocks; // never changes + m_paramsCM256.BlockBytes = sizeof(SDRDaemonProtectedBlock); // never changes + m_paramsCM256.OriginalCount = SDRDaemonNbOrginalBlocks; // never changes if (!m_cm256.isInitialized()) { m_cm256_OK = false; @@ -62,6 +59,9 @@ SDRdaemonSourceBuffer::SDRdaemonSourceBuffer(uint32_t throttlems) : } else { m_cm256_OK = true; } + + std::fill(m_decoderSlots, m_decoderSlots + nbDecoderSlots, DecoderSlot()); + std::fill(m_frames, m_frames + nbDecoderSlots, BufferFrame()); } SDRdaemonSourceBuffer::~SDRdaemonSourceBuffer() @@ -81,7 +81,7 @@ void SDRdaemonSourceBuffer::initDecodeAllSlots() m_decoderSlots[i].m_decoded = false; m_decoderSlots[i].m_metaRetrieved = false; resetOriginalBlocks(i); - memset((void *) m_decoderSlots[i].m_recoveryBlocks, 0, m_nbOriginalBlocks * sizeof(ProtectedBlock)); + memset((void *) m_decoderSlots[i].m_recoveryBlocks, 0, SDRDaemonNbOrginalBlocks * sizeof(SDRDaemonProtectedBlock)); } } @@ -118,7 +118,7 @@ void SDRdaemonSourceBuffer::initDecodeSlot(int slotIndex) m_decoderSlots[slotIndex].m_metaRetrieved = false; resetOriginalBlocks(slotIndex); - memset((void *) m_decoderSlots[slotIndex].m_recoveryBlocks, 0, m_nbOriginalBlocks * sizeof(ProtectedBlock)); + memset((void *) m_decoderSlots[slotIndex].m_recoveryBlocks, 0, SDRDaemonNbOrginalBlocks * sizeof(SDRDaemonProtectedBlock)); } void SDRdaemonSourceBuffer::initReadIndex() @@ -149,7 +149,7 @@ void SDRdaemonSourceBuffer::rwCorrectionEstimate(int slotIndex) dBytes = (nbDecoderSlots * sizeof(BufferFrame)) - normalizedReadIndex - rwDelta; } - m_balCorrection = (m_balCorrection / 4) + (dBytes / (int) (m_iqSampleSize * m_nbReads)); // correction is in number of samples. Alpha = 0.25 + m_balCorrection = (m_balCorrection / 4) + (dBytes / (int) (m_currentMeta.m_sampleBytes * 2 * m_nbReads)); // correction is in number of samples. Alpha = 0.25 if (m_balCorrection < -m_balCorrLimit) { m_balCorrection = -m_balCorrLimit; @@ -174,7 +174,7 @@ void SDRdaemonSourceBuffer::checkSlotData(int slotIndex) if (sampleRate > 0) { int64_t ts = m_currentMeta.m_tv_sec * 1000000LL + m_currentMeta.m_tv_usec; - ts -= (rwDelayBytes * 1000000LL) / (sampleRate * sizeof(SDRdaemonSample)); + ts -= (rwDelayBytes * 1000000LL) / (sampleRate * 2 * m_currentMeta.m_sampleBytes); m_tvOut_sec = ts / 1000000LL; m_tvOut_usec = ts - (m_tvOut_sec * 1000000LL); } @@ -182,6 +182,7 @@ void SDRdaemonSourceBuffer::checkSlotData(int slotIndex) if (!m_decoderSlots[slotIndex].m_decoded) { qDebug() << "SDRdaemonSourceBuffer::checkSlotData: incomplete frame:" + << " slotIndex: " << slotIndex << " m_blockCount: " << m_decoderSlots[slotIndex].m_blockCount << " m_recoveryCount: " << m_decoderSlots[slotIndex].m_recoveryCount; } @@ -189,8 +190,8 @@ void SDRdaemonSourceBuffer::checkSlotData(int slotIndex) void SDRdaemonSourceBuffer::writeData(char *array) { - SuperBlock *superBlock = (SuperBlock *) array; - int frameIndex = superBlock->header.frameIndex; + SDRDaemonSuperBlock *superBlock = (SDRDaemonSuperBlock *) array; + int frameIndex = superBlock->m_header.m_frameIndex; int decoderIndex = frameIndex % nbDecoderSlots; // frame break @@ -213,9 +214,9 @@ void SDRdaemonSourceBuffer::writeData(char *array) // Block processing - if (m_decoderSlots[decoderIndex].m_blockCount < m_nbOriginalBlocks) // not enough blocks to decode -> store data + if (m_decoderSlots[decoderIndex].m_blockCount < SDRDaemonNbOrginalBlocks) // not enough blocks to decode -> store data { - int blockIndex = superBlock->header.blockIndex; + int blockIndex = superBlock->m_header.m_blockIndex; int blockCount = m_decoderSlots[decoderIndex].m_blockCount; int recoveryCount = m_decoderSlots[decoderIndex].m_recoveryCount; m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks[blockCount].Index = blockIndex; @@ -225,14 +226,14 @@ void SDRdaemonSourceBuffer::writeData(char *array) m_decoderSlots[decoderIndex].m_metaRetrieved = true; } - if (blockIndex < m_nbOriginalBlocks) // original data + if (blockIndex < SDRDaemonNbOrginalBlocks) // original data { - m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks[blockCount].Block = (void *) storeOriginalBlock(decoderIndex, blockIndex, superBlock->protectedBlock); + m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks[blockCount].Block = (void *) storeOriginalBlock(decoderIndex, blockIndex, superBlock->m_protectedBlock); m_decoderSlots[decoderIndex].m_originalCount++; } else // recovery data { - m_decoderSlots[decoderIndex].m_recoveryBlocks[recoveryCount] = superBlock->protectedBlock; + m_decoderSlots[decoderIndex].m_recoveryBlocks[recoveryCount] = superBlock->m_protectedBlock; m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks[blockCount].Block = (void *) &m_decoderSlots[decoderIndex].m_recoveryBlocks[recoveryCount]; m_decoderSlots[decoderIndex].m_recoveryCount++; } @@ -240,14 +241,14 @@ void SDRdaemonSourceBuffer::writeData(char *array) m_decoderSlots[decoderIndex].m_blockCount++; - if (m_decoderSlots[decoderIndex].m_blockCount == m_nbOriginalBlocks) // ready to decode + if (m_decoderSlots[decoderIndex].m_blockCount == SDRDaemonNbOrginalBlocks) // ready to decode { m_decoderSlots[decoderIndex].m_decoded = true; if (m_cm256_OK && (m_decoderSlots[decoderIndex].m_recoveryCount > 0)) // recovery data used => need to decode FEC { - m_paramsCM256.BlockBytes = sizeof(ProtectedBlock); // never changes - m_paramsCM256.OriginalCount = m_nbOriginalBlocks; // never changes + m_paramsCM256.BlockBytes = sizeof(SDRDaemonProtectedBlock); // never changes + m_paramsCM256.OriginalCount = SDRDaemonNbOrginalBlocks; // never changes if (m_decoderSlots[decoderIndex].m_metaRetrieved) { m_paramsCM256.RecoveryCount = m_currentMeta.m_nbFECBlocks; @@ -258,24 +259,28 @@ void SDRdaemonSourceBuffer::writeData(char *array) if (m_cm256.cm256_decode(m_paramsCM256, m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks)) // CM256 decode { qDebug() << "SDRdaemonSourceBuffer::writeData: decode CM256 error:" + << " decoderIndex: " << decoderIndex + << " m_blockCount: " << m_decoderSlots[decoderIndex].m_blockCount << " m_originalCount: " << m_decoderSlots[decoderIndex].m_originalCount << " m_recoveryCount: " << m_decoderSlots[decoderIndex].m_recoveryCount; } else { qDebug() << "SDRdaemonSourceBuffer::writeData: decode CM256 success:" + << " decoderIndex: " << decoderIndex + << " m_blockCount: " << m_decoderSlots[decoderIndex].m_blockCount << " m_originalCount: " << m_decoderSlots[decoderIndex].m_originalCount << " m_recoveryCount: " << m_decoderSlots[decoderIndex].m_recoveryCount; for (int ir = 0; ir < m_decoderSlots[decoderIndex].m_recoveryCount; ir++) // restore missing blocks { - int recoveryIndex = m_nbOriginalBlocks - m_decoderSlots[decoderIndex].m_recoveryCount + ir; + int recoveryIndex = SDRDaemonNbOrginalBlocks - m_decoderSlots[decoderIndex].m_recoveryCount + ir; int blockIndex = m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks[recoveryIndex].Index; - ProtectedBlock *recoveredBlock = (ProtectedBlock *) m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks[recoveryIndex].Block; + SDRDaemonProtectedBlock *recoveredBlock = (SDRDaemonProtectedBlock *) m_decoderSlots[decoderIndex].m_cm256DescriptorBlocks[recoveryIndex].Block; if (blockIndex == 0) // first block with meta { - MetaDataFEC *metaData = (MetaDataFEC *) recoveredBlock; + SDRDaemonMetaDataFEC *metaData = (SDRDaemonMetaDataFEC *) recoveredBlock; boost::crc_32_type crc32; crc32.process_bytes(metaData, 20); @@ -296,20 +301,21 @@ void SDRdaemonSourceBuffer::writeData(char *array) qDebug() << "SDRdaemonSourceBuffer::writeData: recovered block #" << blockIndex; } // restore missing blocks } // CM256 decode - } // revovery + } // recovery if (m_decoderSlots[decoderIndex].m_metaRetrieved) // block zero with its meta data has been received { - MetaDataFEC *metaData = getMetaData(decoderIndex); + SDRDaemonMetaDataFEC *metaData = getMetaData(decoderIndex); if (!(*metaData == m_currentMeta)) { - int sampleRate = metaData->m_sampleRate; + uint32_t sampleRate = metaData->m_sampleRate; - if (sampleRate > 0) { - m_bufferLenSec = (float) m_framesNbBytes / (float) (sampleRate * m_iqSampleSize); + if (sampleRate != 0) + { + m_bufferLenSec = (float) m_framesNbBytes / (float) (sampleRate * metaData->m_sampleBytes * 2); m_balCorrLimit = sampleRate / 1000; // +/- 1 ms correction max per read - m_readNbBytes = (sampleRate * m_iqSampleSize) / 20; + m_readNbBytes = (sampleRate * metaData->m_sampleBytes * 2) / 20; } printMeta("SDRdaemonSourceBuffer::writeData: new meta", metaData); // print for change other than timestamp @@ -320,79 +326,6 @@ void SDRdaemonSourceBuffer::writeData(char *array) } // decode } -void SDRdaemonSourceBuffer::writeData0(char *array __attribute__((unused)), uint32_t length __attribute__((unused))) -{ -// Kept as comments for the out of sync blocks algorithms -// assert(length == m_udpPayloadSize); -// -// bool dataAvailable = false; -// SuperBlock *superBlock = (SuperBlock *) array; -// int frameIndex = superBlock->header.frameIndex; -// int decoderIndex = frameIndex % nbDecoderSlots; -// int blockIndex = superBlock->header.blockIndex; -// -//// qDebug() << "SDRdaemonSourceBuffer::writeData:" -//// << " frameIndex: " << frameIndex -//// << " decoderIndex: " << decoderIndex -//// << " blockIndex: " << blockIndex; -// -// if (m_frameHead == -1) // initial state -// { -// m_decoderIndexHead = decoderIndex; // new decoder slot head -// m_frameHead = frameIndex; -// initReadIndex(); // reset read index -// initDecodeAllSlots(); // initialize all slots -// } -// else -// { -// int frameDelta = m_frameHead - frameIndex; -// -// if (frameDelta < 0) -// { -// if (-frameDelta < nbDecoderSlots) // new frame head not too new -// { -// //qDebug() << "SDRdaemonSourceBuffer::writeData: new frame head (1): " << frameIndex << ":" << frameDelta << ":" << decoderIndex; -// m_decoderIndexHead = decoderIndex; // new decoder slot head -// m_frameHead = frameIndex; -// checkSlotData(decoderIndex); -// dataAvailable = true; -// initDecodeSlot(decoderIndex); // collect stats and re-initialize current slot -// } -// else if (-frameDelta <= 65536 - nbDecoderSlots) // loss of sync start over -// { -// //qDebug() << "SDRdaemonSourceBuffer::writeData: loss of sync start over (1)" << frameIndex << ":" << frameDelta << ":" << decoderIndex; -// m_decoderIndexHead = decoderIndex; // new decoder slot head -// m_frameHead = frameIndex; -// initReadIndex(); // reset read index -// initDecodeAllSlots(); // re-initialize all slots -// } -// } -// else -// { -// if (frameDelta > 65536 - nbDecoderSlots) // new frame head not too new -// { -// //qDebug() << "SDRdaemonSourceBuffer::writeData: new frame head (2): " << frameIndex << ":" << frameDelta << ":" << decoderIndex; -// m_decoderIndexHead = decoderIndex; // new decoder slot head -// m_frameHead = frameIndex; -// checkSlotData(decoderIndex); -// dataAvailable = true; -// initDecodeSlot(decoderIndex); // collect stats and re-initialize current slot -// } -// else if (frameDelta >= nbDecoderSlots) // loss of sync start over -// { -// //qDebug() << "SDRdaemonSourceBuffer::writeData: loss of sync start over (2)" << frameIndex << ":" << frameDelta << ":" << decoderIndex; -// m_decoderIndexHead = decoderIndex; // new decoder slot head -// m_frameHead = frameIndex; -// initReadIndex(); // reset read index -// initDecodeAllSlots(); // re-initialize all slots -// } -// } -// } -// -// // decoderIndex should now be correctly set -// -} - uint8_t *SDRdaemonSourceBuffer::readData(int32_t length) { uint8_t *buffer = (uint8_t *) m_frames; @@ -435,7 +368,7 @@ uint8_t *SDRdaemonSourceBuffer::readData(int32_t length) } } -void SDRdaemonSourceBuffer::printMeta(const QString& header, MetaDataFEC *metaData) +void SDRdaemonSourceBuffer::printMeta(const QString& header, SDRDaemonMetaDataFEC *metaData) { qDebug() << header << ": " << "|" << metaData->m_centerFrequency diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h index 5613ecac0..405ad38ce 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcebuffer.h @@ -22,6 +22,7 @@ #include #include "cm256.h" #include "util/movingaverage.h" +#include "channel/sdrdaemondatablock.h" #define SDRDAEMONSOURCE_UDPSIZE 512 // UDP payload size @@ -31,68 +32,15 @@ class SDRdaemonSourceBuffer { public: -#pragma pack(push, 1) - struct MetaDataFEC - { - uint32_t m_centerFrequency; //!< 4 center frequency in kHz - uint32_t m_sampleRate; //!< 8 sample rate in Hz - uint8_t m_sampleBytes; //!< 9 MSB(4): indicators, LSB(4) number of bytes per sample - uint8_t m_sampleBits; //!< 10 number of effective bits per sample - uint8_t m_nbOriginalBlocks; //!< 11 number of blocks with original (protected) data - uint8_t m_nbFECBlocks; //!< 12 number of blocks carrying FEC - uint32_t m_tv_sec; //!< 16 seconds of timestamp at start time of super-frame processing - uint32_t m_tv_usec; //!< 20 microseconds of timestamp at start time of super-frame processing - uint32_t m_crc32; //!< 24 CRC32 of the above - - bool operator==(const MetaDataFEC& rhs) - { - return (memcmp((const void *) this, (const void *) &rhs, 12) == 0); // Only the 12 first bytes are relevant - } - - void init() - { - memset((void *) this, 0, sizeof(MetaDataFEC)); - } - }; - - struct SDRdaemonSample - { - int16_t i; - int16_t q; - }; - - struct Header - { - uint16_t frameIndex; - uint8_t blockIndex; - uint8_t filler; - }; - - static const int samplesPerBlock = (SDRDAEMONSOURCE_UDPSIZE - sizeof(Header)) / sizeof(SDRdaemonSample); - static const int framesSize = SDRDAEMONSOURCE_NBDECODERSLOTS * (SDRDAEMONSOURCE_NBORIGINALBLOCKS - 1) * (SDRDAEMONSOURCE_UDPSIZE - sizeof(Header)); - - struct ProtectedBlock - { - SDRdaemonSample samples[samplesPerBlock]; - }; - - struct SuperBlock - { - Header header; - ProtectedBlock protectedBlock; - }; -#pragma pack(pop) - - SDRdaemonSourceBuffer(uint32_t throttlems); + SDRdaemonSourceBuffer(); ~SDRdaemonSourceBuffer(); // R/W operations void writeData(char *array); //!< Write data into buffer. - void writeData0(char *array, uint32_t length); //!< Write data into buffer. uint8_t *readData(int32_t length); //!< Read data from buffer // meta data - const MetaDataFEC& getCurrentMeta() const { return m_currentMeta; } + const SDRDaemonMetaDataFEC& getCurrentMeta() const { return m_currentMeta; } // samples timestamp uint32_t getTVOutSec() const { return m_tvOut_sec; } @@ -156,10 +104,7 @@ public: } } - static const int m_udpPayloadSize = SDRDAEMONSOURCE_UDPSIZE; - static const int m_nbOriginalBlocks = SDRDAEMONSOURCE_NBORIGINALBLOCKS; - static const int m_sampleSize; - static const int m_iqSampleSize; + static const int framesSize = SDRDAEMONSOURCE_NBDECODERSLOTS * (SDRDaemonNbOrginalBlocks - 1) * SDRDaemonNbBytesPerBlock; private: static const int nbDecoderSlots = SDRDAEMONSOURCE_NBDECODERSLOTS; @@ -167,24 +112,24 @@ private: #pragma pack(push, 1) struct BufferFrame { - ProtectedBlock m_blocks[m_nbOriginalBlocks - 1]; + SDRDaemonProtectedBlock m_blocks[SDRDaemonNbOrginalBlocks - 1]; }; #pragma pack(pop) struct DecoderSlot { - ProtectedBlock m_blockZero; //!< First block of a frame. Has meta data. - ProtectedBlock m_originalBlocks[m_nbOriginalBlocks]; //!< Original blocks retrieved directly or by later FEC - ProtectedBlock m_recoveryBlocks[m_nbOriginalBlocks]; //!< Recovery blocks (FEC blocks) with max size - CM256::cm256_block m_cm256DescriptorBlocks[m_nbOriginalBlocks]; //!< CM256 decoder descriptors (block addresses and block indexes) - int m_blockCount; //!< number of blocks received for this frame - int m_originalCount; //!< number of original blocks received - int m_recoveryCount; //!< number of recovery blocks received - bool m_decoded; //!< true if decoded - bool m_metaRetrieved; //!< true if meta data (block zero) was retrieved + SDRDaemonProtectedBlock m_blockZero; //!< First block of a frame. Has meta data. + SDRDaemonProtectedBlock m_originalBlocks[SDRDaemonNbOrginalBlocks]; //!< Original blocks retrieved directly or by later FEC + SDRDaemonProtectedBlock m_recoveryBlocks[SDRDaemonNbOrginalBlocks]; //!< Recovery blocks (FEC blocks) with max size + CM256::cm256_block m_cm256DescriptorBlocks[SDRDaemonNbOrginalBlocks]; //!< CM256 decoder descriptors (block addresses and block indexes) + int m_blockCount; //!< number of blocks received for this frame + int m_originalCount; //!< number of original blocks received + int m_recoveryCount; //!< number of recovery blocks received + bool m_decoded; //!< true if decoded + bool m_metaRetrieved; //!< true if meta data (block zero) was retrieved }; - MetaDataFEC m_currentMeta; //!< Stored current meta data + SDRDaemonMetaDataFEC m_currentMeta; //!< Stored current meta data CM256::cm256_encoder_params m_paramsCM256; //!< CM256 decoder parameters block DecoderSlot m_decoderSlots[nbDecoderSlots]; //!< CM256 decoding control/buffer slots BufferFrame m_frames[nbDecoderSlots]; //!< Samples buffer @@ -207,7 +152,6 @@ private: uint32_t m_tvOut_usec; //!< Estimated returned samples timestamp (microseconds) int m_readNbBytes; //!< Nominal number of bytes per read (50ms) - uint32_t m_throttlemsNominal; //!< Initial throttle in ms uint8_t* m_readBuffer; //!< Read buffer to hold samples when looping back to beginning of raw buffer int m_readSize; //!< Read buffer size @@ -220,7 +164,7 @@ private: CM256 m_cm256; //!< CM256 library bool m_cm256_OK; //!< CM256 library initialized OK - inline ProtectedBlock* storeOriginalBlock(int slotIndex, int blockIndex, const ProtectedBlock& protectedBlock) + inline SDRDaemonProtectedBlock* storeOriginalBlock(int slotIndex, int blockIndex, const SDRDaemonProtectedBlock& protectedBlock) { if (blockIndex == 0) { // m_decoderSlots[slotIndex].m_originalBlocks[0] = protectedBlock; @@ -235,7 +179,7 @@ private: } } - inline ProtectedBlock& getOriginalBlock(int slotIndex, int blockIndex) + inline SDRDaemonProtectedBlock& getOriginalBlock(int slotIndex, int blockIndex) { if (blockIndex == 0) { // return m_decoderSlots[slotIndex].m_originalBlocks[0]; @@ -246,17 +190,17 @@ private: } } - inline MetaDataFEC *getMetaData(int slotIndex) + inline SDRDaemonMetaDataFEC *getMetaData(int slotIndex) { // return (MetaDataFEC *) &m_decoderSlots[slotIndex].m_originalBlocks[0]; - return (MetaDataFEC *) &m_decoderSlots[slotIndex].m_blockZero; + return (SDRDaemonMetaDataFEC *) &m_decoderSlots[slotIndex].m_blockZero; } inline void resetOriginalBlocks(int slotIndex) { // memset((void *) m_decoderSlots[slotIndex].m_originalBlocks, 0, m_nbOriginalBlocks * sizeof(ProtectedBlock)); - memset((void *) &m_decoderSlots[slotIndex].m_blockZero, 0, sizeof(ProtectedBlock)); - memset((void *) m_frames[slotIndex].m_blocks, 0, (m_nbOriginalBlocks - 1) * sizeof(ProtectedBlock)); + memset((void *) &m_decoderSlots[slotIndex].m_blockZero, 0, sizeof(SDRDaemonProtectedBlock)); + memset((void *) m_frames[slotIndex].m_blocks, 0, (SDRDaemonNbOrginalBlocks - 1) * sizeof(SDRDaemonProtectedBlock)); } void initDecodeAllSlots(); @@ -265,7 +209,7 @@ private: void checkSlotData(int slotIndex); void initDecodeSlot(int slotIndex); - static void printMeta(const QString& header, MetaDataFEC *metaData); + static void printMeta(const QString& header, SDRDaemonMetaDataFEC *metaData); }; diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp index 9c07e33b8..2607ff590 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.cpp @@ -24,15 +24,10 @@ #include #include #include -#include - -#ifdef _WIN32 -#include -#include -#else -#include -#include -#endif +#include +#include +#include +#include #include "ui_sdrdaemonsourcegui.h" #include "gui/colormapper.h" @@ -41,9 +36,9 @@ #include "dsp/dspcommands.h" #include "mainwindow.h" #include "util/simpleserializer.h" - -#include +#include "device/devicesourceapi.h" #include "device/deviceuiset.h" + #include "sdrdaemonsourcegui.h" SDRdaemonSourceGui::SDRdaemonSourceGui(DeviceUISet *deviceUISet, QWidget* parent) : @@ -55,12 +50,14 @@ SDRdaemonSourceGui::SDRdaemonSourceGui(DeviceUISet *deviceUISet, QWidget* parent m_acquisition(false), m_streamSampleRate(0), m_streamCenterFrequency(0), - m_lastEngineState((DSPDeviceSourceEngine::State)-1), + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted), m_framesDecodingStatus(0), m_bufferLengthInSecs(0.0), m_bufferGauge(-50), m_nbOriginalBlocks(128), m_nbFECBlocks(0), + m_sampleBits(16), // assume 16 bits to start with + m_sampleBytes(2), m_samplesCount(0), m_tickCount(0), m_addressEdited(false), @@ -81,22 +78,19 @@ SDRdaemonSourceGui::SDRdaemonSourceGui(DeviceUISet *deviceUISet, QWidget* parent ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); ui->centerFrequency->setValueRange(7, 0, 9999999U); - ui->freq->setColorMapper(ColorMapper(ColorMapper::GrayGold)); - ui->freq->setValueRange(7, 0, 9999999U); - - ui->sampleRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow)); - ui->sampleRate->setValueRange(7, 32000U, 9999999U); - displaySettings(); connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); m_statusTimer.start(500); - connect(&(m_deviceUISet->m_deviceSourceAPI->getMasterTimer()), SIGNAL(timeout()), this, SLOT(tick())); connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); m_sampleSource = (SDRdaemonSourceInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); + + m_networkManager = new QNetworkAccessManager(); + connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); m_eventsTime.start(); displayEventCounts(); @@ -108,6 +102,8 @@ SDRdaemonSourceGui::SDRdaemonSourceGui(DeviceUISet *deviceUISet, QWidget* parent SDRdaemonSourceGui::~SDRdaemonSourceGui() { + disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); + delete m_networkManager; delete ui; } @@ -150,7 +146,6 @@ bool SDRdaemonSourceGui::deserialize(const QByteArray& data) if (m_settings.deserialize(data)) { - updateTxDelay(); displaySettings(); m_forceSettings = true; sendSettings(); @@ -168,10 +163,8 @@ qint64 SDRdaemonSourceGui::getCenterFrequency() const return m_streamCenterFrequency; } -void SDRdaemonSourceGui::setCenterFrequency(qint64 centerFrequency) +void SDRdaemonSourceGui::setCenterFrequency(qint64 centerFrequency __attribute__((unused))) { - m_settings.m_centerFrequency = centerFrequency; - sendSettings(); } bool SDRdaemonSourceGui::handleMessage(const Message& message) @@ -218,13 +211,13 @@ bool SDRdaemonSourceGui::handleMessage(const Message& message) m_avgNbOriginalBlocks = ((SDRdaemonSourceInput::MsgReportSDRdaemonSourceStreamTiming&)message).getAvgNbOriginalBlocks(); m_avgNbRecovery = ((SDRdaemonSourceInput::MsgReportSDRdaemonSourceStreamTiming&)message).getAvgNbRecovery(); m_nbOriginalBlocks = ((SDRdaemonSourceInput::MsgReportSDRdaemonSourceStreamTiming&)message).getNbOriginalBlocksPerFrame(); + m_sampleBits = ((SDRdaemonSourceInput::MsgReportSDRdaemonSourceStreamTiming&)message).getSampleBits(); + m_sampleBytes = ((SDRdaemonSourceInput::MsgReportSDRdaemonSourceStreamTiming&)message).getSampleBytes(); int nbFECBlocks = ((SDRdaemonSourceInput::MsgReportSDRdaemonSourceStreamTiming&)message).getNbFECBlocksPerFrame(); - if (m_nbFECBlocks != nbFECBlocks) - { + if (m_nbFECBlocks != nbFECBlocks) { m_nbFECBlocks = nbFECBlocks; - updateTxDelay(); } updateWithStreamTime(); @@ -257,10 +250,8 @@ void SDRdaemonSourceGui::handleInputMessages() { DSPSignalNotification* notif = (DSPSignalNotification*) message; - if (notif->getSampleRate() != m_streamSampleRate) - { + if (notif->getSampleRate() != m_streamSampleRate) { m_streamSampleRate = notif->getSampleRate(); - updateTxDelay(); } m_streamCenterFrequency = notif->getCenterFrequency(); @@ -290,21 +281,9 @@ void SDRdaemonSourceGui::updateSampleRateAndFrequency() ui->deviceRateText->setText(tr("%1k").arg((float)m_streamSampleRate / 1000)); blockApplySettings(true); ui->centerFrequency->setValue(m_streamCenterFrequency / 1000); - ui->freq->setValue(m_streamCenterFrequency / 1000); blockApplySettings(false); } -void SDRdaemonSourceGui::updateTxDelay() -{ - if (m_streamSampleRate == 0) { - m_txDelay = 0.0; // 0 value will not set the Tx delay - } else { - m_txDelay = ((127*127*m_settings.m_txDelay) / m_streamSampleRate)/(128 + m_nbFECBlocks); - } - - ui->txDelayText->setToolTip(tr("%1 us").arg(QString::number(m_txDelay*1e6, 'f', 0))); -} - void SDRdaemonSourceGui::displaySettings() { blockApplySettings(true); @@ -312,24 +291,10 @@ void SDRdaemonSourceGui::displaySettings() ui->centerFrequency->setValue(m_streamCenterFrequency / 1000); ui->deviceRateText->setText(tr("%1k").arg(m_streamSampleRate / 1000.0)); - ui->freq->setValue(m_streamCenterFrequency / 1000); - ui->decim->setCurrentIndex(m_settings.m_log2Decim); - ui->fcPos->setCurrentIndex(m_settings.m_fcPos); - ui->sampleRate->setValue(m_settings.m_sampleRate); - ui->specificParms->setText(m_settings.m_specificParameters); - ui->specificParms->setCursorPosition(0); - ui->txDelayText->setText(tr("%1").arg(m_settings.m_txDelay*100)); - ui->nbFECBlocks->setValue(m_settings.m_nbFECBlocks); - QString nstr = QString("%1").arg(m_settings.m_nbFECBlocks, 2, 10, QChar('0')); - ui->nbFECBlocksText->setText(nstr); - - QString s0 = QString::number(128 + m_settings.m_nbFECBlocks, 'f', 0); - ui->nominalNbBlocksText->setText(tr("%1/%2").arg(s0).arg(nstr)); - - ui->address->setText(m_settings.m_address); + ui->apiAddress->setText(m_settings.m_apiAddress); + ui->apiPort->setText(tr("%1").arg(m_settings.m_apiPort)); ui->dataPort->setText(tr("%1").arg(m_settings.m_dataPort)); - ui->controlPort->setText(tr("%1").arg(m_settings.m_controlPort)); - ui->specificParms->setText(m_settings.m_specificParameters); + ui->dataAddress->setText(m_settings.m_dataAddress); ui->dcOffset->setChecked(m_settings.m_dcBlock); ui->iqImbalance->setChecked(m_settings.m_iqCorrection); @@ -343,45 +308,52 @@ void SDRdaemonSourceGui::sendSettings() m_updateTimer.start(100); } -void SDRdaemonSourceGui::on_applyButton_clicked(bool checked __attribute__((unused))) +void SDRdaemonSourceGui::on_apiApplyButton_clicked(bool checked __attribute__((unused))) { - m_settings.m_address = ui->address->text(); + m_settings.m_apiAddress = ui->apiAddress->text(); - bool send = false; bool ctlOk; - int udpCtlPort = ui->controlPort->text().toInt(&ctlOk); + int udpApiPort = ui->apiPort->text().toInt(&ctlOk); - if((ctlOk) && (udpCtlPort >= 1024) && (udpCtlPort < 65535)) - { - m_settings.m_controlPort = udpCtlPort; - send = true; + if((ctlOk) && (udpApiPort >= 1024) && (udpApiPort < 65535)) { + m_settings.m_apiPort = udpApiPort; } + sendSettings(); + + QString infoURL = QString("http://%1:%2/sdrangel").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort); + m_networkRequest.setUrl(QUrl(infoURL)); + m_networkManager->get(m_networkRequest); +} + +void SDRdaemonSourceGui::on_dataApplyButton_clicked(bool checked __attribute__((unused))) +{ + m_settings.m_dataAddress = ui->dataAddress->text(); + bool dataOk; int udpDataPort = ui->dataPort->text().toInt(&dataOk); - if((dataOk) && (udpDataPort >= 1024) && (udpDataPort < 65535)) - { + if((dataOk) && (udpDataPort >= 1024) && (udpDataPort < 65535)) { m_settings.m_dataPort = udpDataPort; - send = true; } - if (send) { - sendSettings(); - } -} - -void SDRdaemonSourceGui::on_sendButton_clicked(bool checked __attribute__((unused))) -{ - updateTxDelay(); - m_forceSettings = true; sendSettings(); - ui->specificParms->setCursorPosition(0); } -void SDRdaemonSourceGui::on_address_returnPressed() +void SDRdaemonSourceGui::on_apiAddress_returnPressed() { - m_settings.m_address = ui->address->text(); + m_settings.m_apiAddress = ui->apiAddress->text(); + + QString infoURL = QString("http://%1:%2/sdrangel").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort); + m_networkRequest.setUrl(QUrl(infoURL)); + m_networkManager->get(m_networkRequest); + + sendSettings(); +} + +void SDRdaemonSourceGui::on_dataAddress_returnPressed() +{ + m_settings.m_dataAddress = ui->dataAddress->text(); sendSettings(); } @@ -401,18 +373,23 @@ void SDRdaemonSourceGui::on_dataPort_returnPressed() } } -void SDRdaemonSourceGui::on_controlPort_returnPressed() +void SDRdaemonSourceGui::on_apiPort_returnPressed() { bool ctlOk; - int udpCtlPort = ui->controlPort->text().toInt(&ctlOk); + int udpApiPort = ui->apiPort->text().toInt(&ctlOk); - if((!ctlOk) || (udpCtlPort < 1024) || (udpCtlPort > 65535)) + if((!ctlOk) || (udpApiPort < 1024) || (udpApiPort > 65535)) { return; } else { - m_settings.m_controlPort = udpCtlPort; + m_settings.m_apiPort = udpApiPort; + + QString infoURL = QString("http://%1:%2/sdrangel").arg(m_settings.m_apiAddress).arg(m_settings.m_apiPort); + m_networkRequest.setUrl(QUrl(infoURL)); + m_networkManager->get(m_networkRequest); + sendSettings(); } } @@ -429,54 +406,6 @@ void SDRdaemonSourceGui::on_iqImbalance_toggled(bool checked) sendSettings(); } -void SDRdaemonSourceGui::on_freq_changed(quint64 value) -{ - m_settings.m_centerFrequency = value * 1000; - sendSettings(); -} - -void SDRdaemonSourceGui::on_sampleRate_changed(quint64 value) -{ - m_settings.m_sampleRate = value; - sendSettings(); -} - -void SDRdaemonSourceGui::on_specificParms_returnPressed() -{ - if ((ui->specificParms->text()).size() > 0) { - m_settings.m_specificParameters = ui->specificParms->text(); - sendSettings(); - } -} - -void SDRdaemonSourceGui::on_decim_currentIndexChanged(int index __attribute__((unused))) -{ - m_settings.m_log2Decim = ui->decim->currentIndex(); - sendSettings(); -} - -void SDRdaemonSourceGui::on_fcPos_currentIndexChanged(int index __attribute__((unused))) -{ - m_settings.m_fcPos = ui->fcPos->currentIndex(); - sendSettings(); -} - -void SDRdaemonSourceGui::on_txDelay_valueChanged(int value) -{ - m_settings.m_txDelay = value / 100.0; - ui->txDelayText->setText(tr("%1").arg(value)); - updateTxDelay(); - sendSettings(); -} - -void SDRdaemonSourceGui::on_nbFECBlocks_valueChanged(int value) -{ - m_settings.m_nbFECBlocks = value; - QString nstr = QString("%1").arg(m_settings.m_nbFECBlocks, 2, 10, QChar('0')); - ui->nbFECBlocksText->setText(nstr); - sendSettings(); -} - void SDRdaemonSourceGui::on_startStop_toggled(bool checked) { if (m_doApplySettings) @@ -520,7 +449,7 @@ void SDRdaemonSourceGui::displayEventTimer() int elapsedTimeMillis = m_eventsTime.elapsed(); QTime recordLength(0, 0, 0, 0); recordLength = recordLength.addSecs(elapsedTimeMillis/1000); - QString s_time = recordLength.toString("hh:mm:ss"); + QString s_time = recordLength.toString("HH:mm:ss"); ui->eventCountsTimeText->setText(s_time); } @@ -533,7 +462,7 @@ void SDRdaemonSourceGui::updateWithStreamTime() bool updateEventCounts = false; quint64 startingTimeStampMsec = ((quint64) m_startingTimeStamp.tv_sec * 1000LL) + ((quint64) m_startingTimeStamp.tv_usec / 1000LL); QDateTime dt = QDateTime::fromMSecsSinceEpoch(startingTimeStampMsec); - QString s_date = dt.toString("yyyy-MM-dd hh:mm:ss.zzz"); + QString s_date = dt.toString("yyyy-MM-dd HH:mm:ss.zzz"); ui->absTimeText->setText(s_date); if (m_framesDecodingStatus == 2) @@ -572,6 +501,8 @@ void SDRdaemonSourceGui::updateWithStreamTime() QString s1 = QString("%1").arg(m_nbFECBlocks, 2, 10, QChar('0')); ui->nominalNbBlocksText->setText(tr("%1/%2").arg(s).arg(s1)); + ui->sampleBitsText->setText(tr("%1b").arg(m_sampleBits)); + if (updateEventCounts) { displayEventCounts(); @@ -633,10 +564,71 @@ void SDRdaemonSourceGui::updateStatus() } } -void SDRdaemonSourceGui::tick() +void SDRdaemonSourceGui::networkManagerFinished(QNetworkReply *reply) { - if ((++m_tickCount & 0xf) == 0) { - SDRdaemonSourceInput::MsgConfigureSDRdaemonStreamTiming* message = SDRdaemonSourceInput::MsgConfigureSDRdaemonStreamTiming::create(); - m_sampleSource->getInputMessageQueue()->push(message); - } + if (reply->error()) + { + ui->apiAddressLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }"); + ui->statusText->setText(reply->errorString()); + return; + } + + QString answer = reply->readAll(); + + try + { + QByteArray jsonBytes(answer.toStdString().c_str()); + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(jsonBytes, &error); + + if (error.error == QJsonParseError::NoError) + { + ui->apiAddressLabel->setStyleSheet("QLabel { background-color : green; }"); + ui->statusText->setText(QString("API OK")); + analyzeApiReply(doc.object()); + } + else + { + ui->apiAddressLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }"); + QString errorMsg = QString("Reply JSON error: ") + error.errorString() + QString(" at offset ") + QString::number(error.offset); + ui->statusText->setText(QString("JSON error. See log")); + qInfo().noquote() << "SDRdaemonSinkGui::networkManagerFinished" << errorMsg; + } + } + catch (const std::exception& ex) + { + ui->apiAddressLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }"); + QString errorMsg = QString("Error parsing request: ") + ex.what(); + ui->statusText->setText("Error parsing request. See log for details"); + qInfo().noquote() << "SDRdaemonSinkGui::networkManagerFinished" << errorMsg; + } +} + +void SDRdaemonSourceGui::analyzeApiReply(const QJsonObject& jsonObject) +{ + QString infoLine; + + if (jsonObject.contains("version")) { + infoLine = "v" + jsonObject["version"].toString(); + } + + if (jsonObject.contains("qtVersion")) { + infoLine += " Qt" + jsonObject["qtVersion"].toString(); + } + + if (jsonObject.contains("architecture")) { + infoLine += " " + jsonObject["architecture"].toString(); + } + + if (jsonObject.contains("os")) { + infoLine += " " + jsonObject["os"].toString(); + } + + if (jsonObject.contains("dspRxBits") && jsonObject.contains("dspTxBits")) { + infoLine += QString(" %1/%2b").arg(jsonObject["dspRxBits"].toInt()).arg(jsonObject["dspTxBits"].toInt()); + } + + if (infoLine.size() > 0) { + ui->infoText->setText(infoLine); + } } diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.h b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.h index 3c04ff99f..457705f5a 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.h +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.h @@ -17,16 +17,21 @@ #ifndef INCLUDE_SDRDAEMONSOURCEGUI_H #define INCLUDE_SDRDAEMONSOURCEGUI_H -#include -#include -#include #include +#include +#include +#include + +#include "plugin/plugininstancegui.h" #include "util/messagequeue.h" #include "sdrdaemonsourceinput.h" class DeviceUISet; +class QNetworkAccessManager; +class QNetworkReply; +class QJsonObject; namespace Ui { class SDRdaemonSourceGui; @@ -80,6 +85,8 @@ private: float m_avgNbRecovery; int m_nbOriginalBlocks; int m_nbFECBlocks; + int m_sampleBits; + int m_sampleBytes; int m_samplesCount; std::size_t m_tickCount; @@ -98,39 +105,36 @@ private: QPalette m_paletteGreenText; QPalette m_paletteWhiteText; + QNetworkAccessManager *m_networkManager; + QNetworkRequest m_networkRequest; + void blockApplySettings(bool block); void displaySettings(); void displayTime(); void sendSettings(); void updateWithAcquisition(); void updateWithStreamTime(); - void updateSampleRateAndFrequency(); - void updateTxDelay(); + void updateSampleRateAndFrequency(); void displayEventCounts(); void displayEventTimer(); + void analyzeApiReply(const QJsonObject& jsonObject); private slots: void handleInputMessages(); - void on_applyButton_clicked(bool checked); + void on_apiApplyButton_clicked(bool checked); + void on_dataApplyButton_clicked(bool checked); void on_dcOffset_toggled(bool checked); void on_iqImbalance_toggled(bool checked); - void on_address_returnPressed(); + void on_apiAddress_returnPressed(); + void on_apiPort_returnPressed(); + void on_dataAddress_returnPressed(); void on_dataPort_returnPressed(); - void on_controlPort_returnPressed(); - void on_sendButton_clicked(bool checked); - void on_freq_changed(quint64 value); - void on_sampleRate_changed(quint64 value); - void on_specificParms_returnPressed(); - void on_decim_currentIndexChanged(int index); - void on_fcPos_currentIndexChanged(int index); void on_startStop_toggled(bool checked); void on_record_toggled(bool checked); void on_eventCountsReset_clicked(bool checked); - void on_txDelay_valueChanged(int value); - void on_nbFECBlocks_valueChanged(int value); void updateHardware(); void updateStatus(); - void tick(); + void networkManagerFinished(QNetworkReply *reply); }; #endif // INCLUDE_SDRDAEMONSOURCEGUI_H diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.ui b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.ui index 645408a19..118f06235 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.ui +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcegui.ui @@ -6,19 +6,19 @@ 0 0 - 372 - 261 + 360 + 270 - 372 - 261 + 360 + 270 - Sans Serif + Liberation Sans 9 @@ -132,7 +132,7 @@ - DejaVu Sans Mono + Liberation Mono 20 @@ -374,37 +374,12 @@ - - - - 24 - 24 - - + - Desired number of FEC blocks per frame - - - 64 - - - 1 - - - - - - - - 18 - 0 - - - - Desired number of FEC blocks per frame + Sample size (bits) - 00 + 16b @@ -527,7 +502,7 @@ - Number of uncrecoverable errors since event counts reset + Number of unrecoverable errors since event counts reset 000 @@ -577,33 +552,39 @@ - + - + + + + 30 + 0 + + - Addr: + API - + true - 110 + 120 0 - 110 + 120 16777215 - Local data connection IP address + Remote API IPv4 address 000.000.000.000 @@ -617,9 +598,115 @@ - + - D: + : + + + + + + + + 60 + 0 + + + + + 60 + 16777215 + + + + Remote API port + + + 00000 + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + true + + + + 30 + 16777215 + + + + Set + + + + + + + + + + + + 30 + 0 + + + + Data + + + + + + + + 120 + 0 + + + + + 120 + 16777215 + + + + Local data connection IPv4 address + + + 000.000.000.000 + + + 0.0.0.0 + + + + + + + : @@ -655,42 +742,7 @@ - - - C: - - - - - - - - 60 - 0 - - - - - 60 - 16777215 - - - - Remote control port - - - 00000 - - - 0 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - + Qt::Horizontal @@ -703,10 +755,7 @@ - - - true - + 30 @@ -721,321 +770,22 @@ - - - Qt::Horizontal - - - - - + - + - Fc: - - - - - - - - 0 - 0 - - - - - 32 - 16 - - - - - DejaVu Sans Mono - 12 - false - - - - PointingHandCursor - - - Desired device center frequency - - - - - - - kHz - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Vertical - - - - - - - UDly - - - - - - - - 24 - 24 - - - - Delay between consecutive UDP packets in percentage of nominal UDP packet process time - - - 10 - - - 90 - - - 1 - - - 50 - - - - - - - - 20 - 0 - - - - - 20 - 16777215 - - - - 90 + ... - + - + - SR: - - - - - - - - 0 - 0 - - - - - 32 - 16 - - - - - DejaVu Sans Mono - 12 - false - - - - PointingHandCursor - - - Desired remote device sample rate - - - - - - - S/s - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Dec: - - - - - - - - 45 - 0 - - - - - 16777215 - 16777215 - - - - Decimation - - - 3 - - - - 1 - - - - - 2 - - - - - 4 - - - - - 8 - - - - - 16 - - - - - 32 - - - - - 64 - - - - - - - - Fp: - - - - - - - - 50 - 16777215 - - - - Center frequency position (Infradyne, Supradyne, Centered) - - - 2 - - - - Inf - - - - - Sup - - - - - Cen - - - - - - - - - - - - Sp: - - - - - - - Other parameters that are hardware specific - - - - - - - - 50 - 16777215 - - - - Send commands to remote SDRdaemonRx instance - - - Send + ... diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp index 5013a3f38..2718bed46 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.cpp @@ -16,18 +16,13 @@ #include #include +#include #include -#ifdef _WIN32 -#include -#include -#else -#include -#include -#endif - #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" +#include "SWGDeviceReport.h" +#include "SWGSDRdaemonSourceReport.h" #include "util/simpleserializer.h" #include "dsp/dspcommands.h" @@ -35,7 +30,6 @@ #include #include -#include "sdrdaemonsourcegui.h" #include "sdrdaemonsourceinput.h" #include "sdrdaemonsourceudphandler.h" @@ -52,22 +46,12 @@ SDRdaemonSourceInput::SDRdaemonSourceInput(DeviceSourceAPI *deviceAPI) : m_settings(), m_SDRdaemonUDPHandler(0), m_deviceDescription(), - m_startingTimeStamp(0), - m_autoFollowRate(false), - m_autoCorrBuffer(false) + m_startingTimeStamp(0) { - m_sender = nn_socket(AF_SP, NN_PAIR); - assert(m_sender != -1); - int millis = 500; - int rc __attribute__((unused)) = nn_setsockopt (m_sender, NN_SOL_SOCKET, NN_SNDTIMEO, &millis, sizeof (millis)); - assert (rc == 0); - m_sampleFifo.setSize(96000 * 4); m_SDRdaemonUDPHandler = new SDRdaemonSourceUDPHandler(&m_sampleFifo, m_deviceAPI); - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID()); - m_fileSink = new FileRecord(std::string(recFileNameCStr)); + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); m_deviceAPI->addSink(m_fileSink); } @@ -142,29 +126,16 @@ const QString& SDRdaemonSourceInput::getDeviceDescription() const int SDRdaemonSourceInput::getSampleRate() const { - if (m_SDRdaemonUDPHandler->getSampleRate()) { - return m_SDRdaemonUDPHandler->getSampleRate(); - } else { - return m_settings.m_sampleRate / (1<getSampleRate(); } quint64 SDRdaemonSourceInput::getCenterFrequency() const { - if (m_SDRdaemonUDPHandler->getCenterFrequency()) { - return m_SDRdaemonUDPHandler->getCenterFrequency(); - } else { - return m_settings.m_centerFrequency; - } + return m_SDRdaemonUDPHandler->getCenterFrequency(); } -void SDRdaemonSourceInput::setCenterFrequency(qint64 centerFrequency) +void SDRdaemonSourceInput::setCenterFrequency(qint64 centerFrequency __attribute__((unused))) { - SDRdaemonSourceSettings settings = m_settings; - settings.m_centerFrequency = centerFrequency; - - MsgConfigureSDRdaemonSource* message = MsgConfigureSDRdaemonSource::create(m_settings, false); - m_inputMessageQueue.push(message); } std::time_t SDRdaemonSourceInput::getStartingTimeStamp() const @@ -189,9 +160,18 @@ bool SDRdaemonSourceInput::handleMessage(const Message& message) MsgFileRecord& conf = (MsgFileRecord&) message; qDebug() << "SDRdaemonSourceInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); - if (conf.getStartStop()) { + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSink->setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); + } + m_fileSink->startRecording(); - } else { + } + else + { m_fileSink->stopRecording(); } @@ -207,13 +187,11 @@ bool SDRdaemonSourceInput::handleMessage(const Message& message) if (m_deviceAPI->initAcquisition()) { m_deviceAPI->startAcquisition(); - DSPEngine::instance()->startAudioOutput(); } } else { m_deviceAPI->stopAcquisition(); - DSPEngine::instance()->stopAudioOutput(); } return true; @@ -225,30 +203,6 @@ bool SDRdaemonSourceInput::handleMessage(const Message& message) applySettings(conf.getSettings(), conf.getForce()); return true; } - else if (MsgConfigureSDRdaemonStreamTiming::match(message)) - { - return true; - } - else if (MsgReportSDRdaemonSourceStreamData::match(message)) - { - // Forward message to the GUI if it is present - if (getMessageQueueToGUI()) { - getMessageQueueToGUI()->push(const_cast(&message)); - return false; // deletion of message is handled by the GUI - } else { - return true; // delete the unused message - } - } - else if (MsgReportSDRdaemonSourceStreamTiming::match(message)) - { - // Forward message to the GUI if it is present - if (getMessageQueueToGUI()) { - getMessageQueueToGUI()->push(const_cast(&message)); - return false; // deletion of message is handled by the GUI - } else { - return true; // delete the unused message - } - } else { return false; @@ -258,9 +212,7 @@ bool SDRdaemonSourceInput::handleMessage(const Message& message) void SDRdaemonSourceInput::applySettings(const SDRdaemonSourceSettings& settings, bool force) { QMutexLocker mutexLocker(&m_mutex); - bool changeTxDelay = false; std::ostringstream os; - int nbArgs = 0; QString remoteAddress; m_SDRdaemonUDPHandler->getRemoteAddress(remoteAddress); @@ -268,139 +220,23 @@ void SDRdaemonSourceInput::applySettings(const SDRdaemonSourceSettings& settings { m_deviceAPI->configureCorrections(settings.m_dcBlock, settings.m_iqCorrection); qDebug("SDRdaemonSourceInput::applySettings: corrections: DC block: %s IQ imbalance: %s", - m_settings.m_dcBlock ? "true" : "false", - m_settings.m_iqCorrection ? "true" : "false"); + settings.m_dcBlock ? "true" : "false", + settings.m_iqCorrection ? "true" : "false"); } - if (force || (m_settings.m_address != settings.m_address) || (m_settings.m_dataPort != settings.m_dataPort)) - { - m_SDRdaemonUDPHandler->configureUDPLink(settings.m_address, settings.m_dataPort); - m_SDRdaemonUDPHandler->getRemoteAddress(remoteAddress); - } - - if (force || (remoteAddress != m_remoteAddress) || (m_settings.m_controlPort != settings.m_controlPort)) - { - int rc = nn_shutdown(m_sender, 0); - - if (rc < 0) { - qDebug() << "SDRdaemonSourceInput::applySettings: nn disconnection failed"; - } else { - qDebug() << "SDRdaemonSourceInput::applySettings: nn disconnection successful"; - } - - std::ostringstream os; - os << "tcp://" << remoteAddress.toStdString() << ":" << m_settings.m_controlPort; - std::string addrstrng = os.str(); - rc = nn_connect(m_sender, addrstrng.c_str()); - - if (rc < 0) { - qDebug() << "SDRdaemonSourceInput::applySettings: nn connexion to " << addrstrng.c_str() << " failed"; - } else { - qDebug() << "SDRdaemonSourceInput::applySettings: nn connexion to " << addrstrng.c_str() << " successful"; - } - } - - if (force || (m_settings.m_centerFrequency != settings.m_centerFrequency)) - { - os << "freq=" << settings.m_centerFrequency; - nbArgs++; - } - - if (force || (m_settings.m_sampleRate != settings.m_sampleRate) || (m_settings.m_log2Decim != settings.m_log2Decim)) - { - if (nbArgs > 0) os << ","; - os << "srate=" << m_settings.m_sampleRate; - nbArgs++; - changeTxDelay = m_settings.m_sampleRate != settings.m_sampleRate; - } - - if (force || (m_settings.m_log2Decim != settings.m_log2Decim)) - { - if (nbArgs > 0) os << ","; - os << "decim=" << m_settings.m_log2Decim; - nbArgs++; - } - - if ((m_settings.m_fcPos != settings.m_fcPos) || force) - { - if (nbArgs > 0) os << ","; - os << "fcpos=" << m_settings.m_fcPos; - nbArgs++; - } - - if (force || (m_settings.m_nbFECBlocks != settings.m_nbFECBlocks)) - { - if (nbArgs > 0) os << ","; - os << "fecblk=" << m_settings.m_nbFECBlocks; - nbArgs++; - changeTxDelay = true; - } - - if (force || (m_settings.m_txDelay != settings.m_txDelay)) - { - changeTxDelay = true; - } - - if (changeTxDelay) - { - double delay = ((127*127*settings.m_txDelay) / settings.m_sampleRate)/(128 + settings.m_nbFECBlocks); - qDebug("SDRdaemonSourceInput::applySettings: Tx delay: %f us", delay*1e6); - - if (delay != 0.0) - { - if (nbArgs > 0) os << ","; - os << "txdelay=" << (int) (delay*1e6); - nbArgs++; - } - } - - if ((m_settings.m_specificParameters != settings.m_specificParameters) || force) - { - if (settings.m_specificParameters.size() > 0) - { - if (nbArgs > 0) os << ","; - os << settings.m_specificParameters.toStdString(); - nbArgs++; - } - } - - if (nbArgs > 0) - { - int config_size = os.str().size(); - int rc = nn_send(m_sender, (void *) os.str().c_str(), config_size, 0); - - if (rc != config_size) - { - qDebug() << "SDRdaemonSourceInput::applySettings: Cannot nn send to " - << " remoteAddress: " << remoteAddress - << " remotePort: " << settings.m_controlPort - << " message: " << os.str().c_str(); - } - else - { - qDebug() << "SDRdaemonSourceInput::applySettings: nn send to " - << "remoteAddress:" << remoteAddress - << "remotePort:" << settings.m_controlPort - << "message:" << os.str().c_str(); - } - } + m_SDRdaemonUDPHandler->configureUDPLink(settings.m_dataAddress, settings.m_dataPort); + m_SDRdaemonUDPHandler->getRemoteAddress(remoteAddress); mutexLocker.unlock(); m_settings = settings; m_remoteAddress = remoteAddress; qDebug() << "SDRdaemonSourceInput::applySettings: " - << " m_address: " << m_settings.m_address - << " m_remoteAddress: " << m_remoteAddress + << " m_dataAddress: " << m_settings.m_dataAddress << " m_dataPort: " << m_settings.m_dataPort - << " m_controlPort: " << m_settings.m_controlPort - << " m_centerFrequency: " << m_settings.m_centerFrequency - << " m_sampleRate: " << m_settings.m_sampleRate - << " m_log2Decim: " << m_settings.m_log2Decim - << " m_fcPos: " << m_settings.m_fcPos - << " m_txDelay: " << m_settings.m_txDelay - << " m_nbFECBlocks: " << m_settings.m_nbFECBlocks - << " m_specificParameters: " << m_settings.m_specificParameters; + << " m_apiAddress: " << m_settings.m_apiAddress + << " m_apiPort: " << m_settings.m_apiPort + << " m_remoteAddress: " << m_remoteAddress; } int SDRdaemonSourceInput::webapiRunGet( @@ -429,3 +265,95 @@ int SDRdaemonSourceInput::webapiRun( return 200; } +int SDRdaemonSourceInput::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setSdrDaemonSourceSettings(new SWGSDRangel::SWGSDRdaemonSourceSettings()); + response.getSdrDaemonSourceSettings()->init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int SDRdaemonSourceInput::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage __attribute__((unused))) +{ + SDRdaemonSourceSettings settings = m_settings; + + if (deviceSettingsKeys.contains("apiAddress")) { + settings.m_apiAddress = *response.getSdrDaemonSourceSettings()->getApiAddress(); + } + if (deviceSettingsKeys.contains("apiPort")) { + settings.m_apiPort = response.getSdrDaemonSourceSettings()->getApiPort(); + } + if (deviceSettingsKeys.contains("dataAddress")) { + settings.m_dataAddress = *response.getSdrDaemonSourceSettings()->getDataAddress(); + } + if (deviceSettingsKeys.contains("dataPort")) { + settings.m_dataPort = response.getSdrDaemonSourceSettings()->getDataPort(); + } + if (deviceSettingsKeys.contains("dcBlock")) { + settings.m_dcBlock = response.getSdrDaemonSourceSettings()->getDcBlock() != 0; + } + if (deviceSettingsKeys.contains("iqCorrection")) { + settings.m_iqCorrection = response.getSdrDaemonSourceSettings()->getIqCorrection() != 0; + } + if (deviceSettingsKeys.contains("fileRecordName")) { + settings.m_fileRecordName = *response.getSdrDaemonSourceSettings()->getFileRecordName(); + } + + MsgConfigureSDRdaemonSource *msg = MsgConfigureSDRdaemonSource::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureSDRdaemonSource *msgToGUI = MsgConfigureSDRdaemonSource::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +void SDRdaemonSourceInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const SDRdaemonSourceSettings& settings) +{ + response.getSdrDaemonSourceSettings()->setApiAddress(new QString(settings.m_apiAddress)); + response.getSdrDaemonSourceSettings()->setApiPort(settings.m_apiPort); + response.getSdrDaemonSourceSettings()->setDataAddress(new QString(settings.m_dataAddress)); + response.getSdrDaemonSourceSettings()->setDataPort(settings.m_dataPort); + response.getSdrDaemonSourceSettings()->setDcBlock(settings.m_dcBlock ? 1 : 0); + response.getSdrDaemonSourceSettings()->setIqCorrection(settings.m_iqCorrection); + + if (response.getSdrDaemonSourceSettings()->getFileRecordName()) { + *response.getSdrDaemonSourceSettings()->getFileRecordName() = settings.m_fileRecordName; + } else { + response.getSdrDaemonSourceSettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + } +} + +int SDRdaemonSourceInput::webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setSdrDaemonSourceReport(new SWGSDRangel::SWGSDRdaemonSourceReport()); + response.getSdrDaemonSourceReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} + +void SDRdaemonSourceInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + response.getSdrDaemonSourceReport()->setCenterFrequency(m_SDRdaemonUDPHandler->getCenterFrequency()); + response.getSdrDaemonSourceReport()->setSampleRate(m_SDRdaemonUDPHandler->getSampleRate()); + response.getSdrDaemonSourceReport()->setBufferRwBalance(m_SDRdaemonUDPHandler->getBufferGauge()); + + quint64 startingTimeStampMsec = ((quint64) m_SDRdaemonUDPHandler->getTVSec() * 1000LL) + ((quint64) m_SDRdaemonUDPHandler->getTVuSec() / 1000LL); + QDateTime dt = QDateTime::fromMSecsSinceEpoch(startingTimeStampMsec); + response.getSdrDaemonSourceReport()->setDaemonTimestamp(new QString(dt.toString("yyyy-MM-dd HH:mm:ss.zzz"))); + + response.getSdrDaemonSourceReport()->setMinNbBlocks(m_SDRdaemonUDPHandler->getMinNbBlocks()); + response.getSdrDaemonSourceReport()->setMaxNbRecovery(m_SDRdaemonUDPHandler->getMaxNbRecovery()); +} diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.h b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.h index 8e640428c..077a2b1ac 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.h +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceinput.h @@ -140,6 +140,8 @@ public: float getAvgNbRecovery() const { return m_avgNbRecovery; } int getNbOriginalBlocksPerFrame() const { return m_nbOriginalBlocksPerFrame; } int getNbFECBlocksPerFrame() const { return m_nbFECBlocksPerFrame; } + int getSampleBits() const { return m_sampleBits; } + int getSampleBytes() const { return m_sampleBytes; } static MsgReportSDRdaemonSourceStreamTiming* create(uint32_t tv_sec, uint32_t tv_usec, @@ -154,7 +156,9 @@ public: float avgNbOriginalBlocks, float avgNbRecovery, int nbOriginalBlocksPerFrame, - int nbFECBlocksPerFrame) + int nbFECBlocksPerFrame, + int sampleBits, + int sampleBytes) { return new MsgReportSDRdaemonSourceStreamTiming(tv_sec, tv_usec, @@ -169,7 +173,9 @@ public: avgNbOriginalBlocks, avgNbRecovery, nbOriginalBlocksPerFrame, - nbFECBlocksPerFrame); + nbFECBlocksPerFrame, + sampleBits, + sampleBytes); } protected: @@ -187,6 +193,8 @@ public: float m_avgNbRecovery; int m_nbOriginalBlocksPerFrame; int m_nbFECBlocksPerFrame; + int m_sampleBits; + int m_sampleBytes; MsgReportSDRdaemonSourceStreamTiming(uint32_t tv_sec, uint32_t tv_usec, @@ -201,7 +209,9 @@ public: float avgNbOriginalBlocks, float avgNbRecovery, int nbOriginalBlocksPerFrame, - int nbFECBlocksPerFrame) : + int nbFECBlocksPerFrame, + int sampleBits, + int sampleBytes) : Message(), m_tv_sec(tv_sec), m_tv_usec(tv_usec), @@ -216,7 +226,9 @@ public: m_avgNbOriginalBlocks(avgNbOriginalBlocks), m_avgNbRecovery(avgNbRecovery), m_nbOriginalBlocksPerFrame(nbOriginalBlocksPerFrame), - m_nbFECBlocksPerFrame(nbFECBlocksPerFrame) + m_nbFECBlocksPerFrame(nbFECBlocksPerFrame), + m_sampleBits(sampleBits), + m_sampleBytes(sampleBytes) { } }; @@ -279,6 +291,20 @@ public: virtual bool handleMessage(const Message& message); + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + virtual int webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage); @@ -294,14 +320,13 @@ private: SDRdaemonSourceSettings m_settings; SDRdaemonSourceUDPHandler* m_SDRdaemonUDPHandler; QString m_remoteAddress; - int m_sender; QString m_deviceDescription; std::time_t m_startingTimeStamp; - bool m_autoFollowRate; - bool m_autoCorrBuffer; FileRecord *m_fileSink; //!< File sink to record device I/Q output void applySettings(const SDRdaemonSourceSettings& settings, bool force = false); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const SDRdaemonSourceSettings& settings); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); }; #endif // INCLUDE_SDRDAEMONSOURCEINPUT_H diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp index 8f5b33517..2a11bdcb9 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceplugin.cpp @@ -15,18 +15,21 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include + #include "plugin/pluginapi.h" #include "util/simpleserializer.h" +#include "device/devicesourceapi.h" -#include - +#ifdef SERVER_MODE +#include "sdrdaemonsourceinput.h" +#else #include "sdrdaemonsourcegui.h" +#endif #include "sdrdaemonsourceplugin.h" const PluginDescriptor SDRdaemonSourcePlugin::m_pluginDescriptor = { QString("SDRdaemon source input"), - QString("3.11.1"), + QString("4.1.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, @@ -69,6 +72,15 @@ PluginInterface::SamplingDevices SDRdaemonSourcePlugin::enumSampleSources() return result; } +#ifdef SERVER_MODE +PluginInstanceGUI* SDRdaemonSourcePlugin::createSampleSourcePluginInstanceGUI( + const QString& sourceId __attribute((unused)), + QWidget **widget __attribute((unused)), + DeviceUISet *deviceUISet __attribute((unused))) +{ + return 0; +} +#else PluginInstanceGUI* SDRdaemonSourcePlugin::createSampleSourcePluginInstanceGUI( const QString& sourceId, QWidget **widget, @@ -85,6 +97,7 @@ PluginInstanceGUI* SDRdaemonSourcePlugin::createSampleSourcePluginInstanceGUI( return 0; } } +#endif DeviceSampleSource *SDRdaemonSourcePlugin::createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI) { diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcesettings.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcesettings.cpp index 81da4ca8a..de6bfc2fd 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcesettings.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcesettings.cpp @@ -24,35 +24,25 @@ SDRdaemonSourceSettings::SDRdaemonSourceSettings() void SDRdaemonSourceSettings::resetToDefaults() { - m_centerFrequency = 435000*1000; - m_sampleRate = 256000; - m_log2Decim = 1; - m_txDelay = 0.5; - m_nbFECBlocks = 0; - m_address = "127.0.0.1"; - m_dataPort = 9092; - m_controlPort = 9093; - m_specificParameters = ""; + m_apiAddress = "127.0.0.1"; + m_apiPort = 9091; + m_dataAddress = "127.0.0.1"; + m_dataPort = 9090; m_dcBlock = false; m_iqCorrection = false; - m_fcPos = 2; // center + m_fileRecordName = ""; } QByteArray SDRdaemonSourceSettings::serialize() const { SimpleSerializer s(1); - s.writeU64(1, m_sampleRate); - s.writeU32(2, m_log2Decim); - s.writeFloat(3, m_txDelay); - s.writeU32(4, m_nbFECBlocks); - s.writeString(5, m_address); - s.writeU32(6, m_dataPort); - s.writeU32(7, m_controlPort); - s.writeString(8, m_specificParameters); + s.writeString(5, m_apiAddress); + s.writeU32(6, m_apiPort); + s.writeU32(7, m_dataPort); + s.writeString(8, m_dataAddress); s.writeBool(9, m_dcBlock); s.writeBool(10, m_iqCorrection); - s.writeU32(11, m_fcPos); return s.final(); } @@ -70,19 +60,14 @@ bool SDRdaemonSourceSettings::deserialize(const QByteArray& data) if (d.getVersion() == 1) { quint32 uintval; - d.readU64(1, &m_sampleRate, 48000); - d.readU32(2, &m_log2Decim, 0); - d.readFloat(3, &m_txDelay, 0.5); - d.readU32(4, &m_nbFECBlocks, 0); - d.readString(5, &m_address, "127.0.0.1"); + d.readString(5, &m_apiAddress, "127.0.0.1"); d.readU32(6, &uintval, 9090); + m_apiPort = uintval % (1<<16); + d.readU32(7, &uintval, 9091); m_dataPort = uintval % (1<<16); - d.readU32(7, &uintval, 9090); - m_controlPort = uintval % (1<<16); - d.readString(8, &m_specificParameters, ""); + d.readString(8, &m_dataAddress, "127.0.0.1"); d.readBool(9, &m_dcBlock, false); d.readBool(10, &m_iqCorrection, false); - d.readU32(11, &m_fcPos, 2); return true; } else diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcesettings.h b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcesettings.h index 53a2a2bb0..e1b010585 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcesettings.h +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourcesettings.h @@ -21,18 +21,13 @@ #include struct SDRdaemonSourceSettings { - quint64 m_centerFrequency; - quint64 m_sampleRate; - quint32 m_log2Decim; - float m_txDelay; - quint32 m_nbFECBlocks; - QString m_address; + QString m_apiAddress; + quint16 m_apiPort; + QString m_dataAddress; quint16 m_dataPort; - quint16 m_controlPort; - QString m_specificParameters; bool m_dcBlock; bool m_iqCorrection; - quint32 m_fcPos; + QString m_fileRecordName; SDRdaemonSourceSettings(); void resetToDefaults(); diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp index a60062a85..399ca525e 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.cpp @@ -31,7 +31,7 @@ SDRdaemonSourceUDPHandler::SDRdaemonSourceUDPHandler(SampleSinkFifo *sampleFifo, m_masterTimer(deviceAPI->getMasterTimer()), m_masterTimerConnected(false), m_running(false), - m_sdrDaemonBuffer(m_rateDivider), + m_rateDivider(1000/SDRDAEMONSOURCE_THROTTLE_MS), m_dataSocket(0), m_dataAddress(QHostAddress::LocalHost), m_remoteAddress(QHostAddress::LocalHost), @@ -54,10 +54,9 @@ SDRdaemonSourceUDPHandler::SDRdaemonSourceUDPHandler(SampleSinkFifo *sampleFifo, m_converterBuffer(0), m_converterBufferNbSamples(0), m_throttleToggle(false), - m_rateDivider(1000/SDRDAEMONSOURCE_THROTTLE_MS), m_autoCorrBuffer(true) { - m_udpBuf = new char[SDRdaemonSourceBuffer::m_udpPayloadSize]; + m_udpBuf = new char[SDRDaemonUdpSize]; #ifdef USE_INTERNAL_TIMER #warning "Uses internal timer" @@ -97,7 +96,7 @@ void SDRdaemonSourceUDPHandler::start() if (!m_dataConnected) { - connect(m_dataSocket, SIGNAL(readyRead()), this, SLOT(dataReadyRead()), Qt::QueuedConnection); // , Qt::QueuedConnection + connect(m_dataSocket, SIGNAL(readyRead()), this, SLOT(dataReadyRead())); //, Qt::QueuedConnection); if (m_dataSocket->bind(m_dataAddress, m_dataPort)) { @@ -168,7 +167,7 @@ void SDRdaemonSourceUDPHandler::dataReadyRead() qint64 pendingDataSize = m_dataSocket->pendingDatagramSize(); m_udpReadBytes += m_dataSocket->readDatagram(&m_udpBuf[m_udpReadBytes], pendingDataSize, &m_remoteAddress, 0); - if (m_udpReadBytes == SDRdaemonSourceBuffer::m_udpPayloadSize) { + if (m_udpReadBytes == SDRDaemonUdpSize) { processData(); m_udpReadBytes = 0; } @@ -178,7 +177,7 @@ void SDRdaemonSourceUDPHandler::dataReadyRead() void SDRdaemonSourceUDPHandler::processData() { m_sdrDaemonBuffer.writeData(m_udpBuf); - const SDRdaemonSourceBuffer::MetaDataFEC& metaData = m_sdrDaemonBuffer.getCurrentMeta(); + const SDRDaemonMetaDataFEC& metaData = m_sdrDaemonBuffer.getCurrentMeta(); bool change = false; m_tv_sec = m_sdrDaemonBuffer.getTVOutSec(); @@ -196,7 +195,7 @@ void SDRdaemonSourceUDPHandler::processData() change = true; } - if (change && (m_samplerate != 0) && (m_centerFrequency != 0)) + if (change && (m_samplerate != 0)) { qDebug("SDRdaemonSourceUDPHandler::processData: m_samplerate: %u m_centerFrequency: %u kHz", m_samplerate, m_centerFrequency); @@ -264,15 +263,16 @@ void SDRdaemonSourceUDPHandler::tick() m_readLengthSamples += m_sdrDaemonBuffer.getRWBalanceCorrection(); } - m_readLength = m_readLengthSamples * SDRdaemonSourceBuffer::m_iqSampleSize; + const SDRDaemonMetaDataFEC& metaData = m_sdrDaemonBuffer.getCurrentMeta(); + m_readLength = m_readLengthSamples * (metaData.m_sampleBytes & 0xF) * 2; - if (SDR_RX_SAMP_SZ == 16) + if (SDR_RX_SAMP_SZ == metaData.m_sampleBits) // same sample size { // read samples directly feeding the SampleFifo (no callback) m_sampleFifo->write(reinterpret_cast(m_sdrDaemonBuffer.readData(m_readLength)), m_readLength); m_samplesCount += m_readLengthSamples; } - else if (SDR_RX_SAMP_SZ == 24) + else if (metaData.m_sampleBits == 16) // 16 -> 24 bits { if (m_readLengthSamples > m_converterBufferNbSamples) { @@ -284,14 +284,37 @@ void SDRdaemonSourceUDPHandler::tick() for (unsigned int is = 0; is < m_readLengthSamples; is++) { - m_converterBuffer[2*is] = ((int16_t*)buf)[2*is]; + m_converterBuffer[2*is] = ((int16_t*)buf)[2*is]; // I m_converterBuffer[2*is]<<=8; - m_converterBuffer[2*is+1] = ((int16_t*)buf)[2*is+1]; + m_converterBuffer[2*is+1] = ((int16_t*)buf)[2*is+1]; // Q m_converterBuffer[2*is+1]<<=8; } m_sampleFifo->write(reinterpret_cast(m_converterBuffer), m_readLengthSamples*sizeof(Sample)); } + else if (metaData.m_sampleBits == 24) // 24 -> 16 bits + { + if (m_readLengthSamples > m_converterBufferNbSamples) + { + if (m_converterBuffer) { delete[] m_converterBuffer; } + m_converterBuffer = new int32_t[m_readLengthSamples]; + } + + uint8_t *buf = m_sdrDaemonBuffer.readData(m_readLength); + + for (unsigned int is = 0; is < m_readLengthSamples; is++) + { + m_converterBuffer[is] = ((int32_t *)buf)[2*is+1]>>8; // Q -> MSB + m_converterBuffer[is] <<=16; + m_converterBuffer[is] += ((int32_t *)buf)[2*is]>>8; // I -> LSB + } + + m_sampleFifo->write(reinterpret_cast(m_converterBuffer), m_readLengthSamples*sizeof(Sample)); + } + else + { + qWarning("SDRdaemonSourceUDPHandler::tick: unexpected sample size in stream: %d bits", (int) metaData.m_sampleBits); + } if (m_tickCount < m_rateDivider) { @@ -308,6 +331,8 @@ void SDRdaemonSourceUDPHandler::tick() int minNbOriginalBlocks = m_sdrDaemonBuffer.getMinOriginalBlocks(); int nbOriginalBlocks = m_sdrDaemonBuffer.getCurrentMeta().m_nbOriginalBlocks; int nbFECblocks = m_sdrDaemonBuffer.getCurrentMeta().m_nbFECBlocks; + int sampleBits = m_sdrDaemonBuffer.getCurrentMeta().m_sampleBits; + int sampleBytes = m_sdrDaemonBuffer.getCurrentMeta().m_sampleBytes; //framesDecodingStatus = (minNbOriginalBlocks == nbOriginalBlocks ? 2 : (minNbOriginalBlocks < nbOriginalBlocks - nbFECblocks ? 0 : 1)); if (minNbBlocks < nbOriginalBlocks) { @@ -332,7 +357,9 @@ void SDRdaemonSourceUDPHandler::tick() m_sdrDaemonBuffer.getAvgOriginalBlocks(), m_sdrDaemonBuffer.getAvgNbRecovery(), nbOriginalBlocks, - nbFECblocks); + nbFECblocks, + sampleBits, + sampleBytes); m_outputMessageQueueToGUI->push(report); } diff --git a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.h b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.h index 0f686329a..6abc6b03f 100644 --- a/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.h +++ b/plugins/samplesource/sdrdaemonsource/sdrdaemonsourceudphandler.h @@ -43,10 +43,15 @@ public: void stop(); void configureUDPLink(const QString& address, quint16 port); void getRemoteAddress(QString& s) const { s = m_remoteAddress.toString(); } - int getNbOriginalBlocks() const { return SDRdaemonSourceBuffer::m_nbOriginalBlocks; } + int getNbOriginalBlocks() const { return SDRDaemonNbOrginalBlocks; } bool isStreaming() const { return m_masterTimerConnected; } int getSampleRate() const { return m_samplerate; } int getCenterFrequency() const { return m_centerFrequency * 1000; } + int getBufferGauge() const { return m_sdrDaemonBuffer.getBufferGauge(); } + uint32_t getTVSec() const { return m_tv_sec; } + uint32_t getTVuSec() const { return m_tv_usec; } + int getMinNbBlocks() { return m_sdrDaemonBuffer.getMinNbBlocks(); } + int getMaxNbRecovery() { return m_sdrDaemonBuffer.getMaxNbRecovery(); } public slots: void dataReadyRead(); @@ -55,6 +60,7 @@ private: const QTimer& m_masterTimer; bool m_masterTimerConnected; bool m_running; + uint32_t m_rateDivider; SDRdaemonSourceBuffer m_sdrDaemonBuffer; QUdpSocket *m_dataSocket; QHostAddress m_dataAddress; @@ -80,7 +86,6 @@ private: int32_t *m_converterBuffer; uint32_t m_converterBufferNbSamples; bool m_throttleToggle; - uint32_t m_rateDivider; bool m_autoCorrBuffer; void connectTimer(); diff --git a/plugins/samplesource/sdrplay/CMakeLists.txt b/plugins/samplesource/sdrplay/CMakeLists.txt index aa9464e53..13d79ee72 100644 --- a/plugins/samplesource/sdrplay/CMakeLists.txt +++ b/plugins/samplesource/sdrplay/CMakeLists.txt @@ -68,6 +68,6 @@ target_link_libraries(inputsdrplay ) endif (BUILD_DEBIAN) -qt5_use_modules(inputsdrplay Core Widgets) +target_link_libraries(inputsdrplay Qt5::Core Qt5::Widgets) install(TARGETS inputsdrplay DESTINATION lib/plugins/samplesource) diff --git a/plugins/samplesource/sdrplay/readme.md b/plugins/samplesource/sdrplay/readme.md index cc2128d28..8c99f42aa 100644 --- a/plugins/samplesource/sdrplay/readme.md +++ b/plugins/samplesource/sdrplay/readme.md @@ -6,7 +6,7 @@ This plugin supports input from SDRplay RSP1 devices. SDRplay is based on the MS No Windows support -Driver is too unstable in Windows randomly stopping the appication and causing BSOD. +Driver is too unstable in Windows randomly stopping the application and causing BSOD.

Build

@@ -75,13 +75,16 @@ You have the choice between various sample rates from 1536 to 8192 kHz. Some val Decimation in powers of two from 1 (no decimation) to 64. -

9. Center frequency position

+

9: Decimated bandpass center frequency position relative the SDRplay center frequency

-Relative position of center frequency of decimated baseband relative to the original: + - **Cen**: the decimation operation takes place around the SDRplay center frequency Fs + - **Inf**: the decimation operation takes place around Fs - Fc. + - **Sup**: the decimation operation takes place around Fs + Fc. + +With SR as the sample rate before decimation Fc is calculated as: - - Inf: infradyne i.e. in the lower half of original baseband: to be used with non zero IFs - - Cen: Centered i.e. around the center of the original baseband - - Sup: Supradyne i.e. in the upper half of original baseband + - if decimation n is 4 or lower: Fc = SR/2^(log2(n)-1). The device center frequency is on the side of the baseband. You need a RF filter bandwidth at least twice the baseband. + - if decimation n is 8 or higher: Fc = SR/n. The device center frequency is half the baseband away from the side of the baseband. You need a RF filter bandwidth at least 3 times the baseband.

10. Tuner gain mode

diff --git a/plugins/samplesource/sdrplay/sdrplaygui.cpp b/plugins/samplesource/sdrplay/sdrplaygui.cpp index 21c0392c2..34e4a3d13 100644 --- a/plugins/samplesource/sdrplay/sdrplaygui.cpp +++ b/plugins/samplesource/sdrplay/sdrplaygui.cpp @@ -73,6 +73,7 @@ SDRPlayGui::SDRPlayGui(DeviceUISet *deviceUISet, QWidget* parent) : displaySettings(); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); } SDRPlayGui::~SDRPlayGui() @@ -471,216 +472,3 @@ void SDRPlayGui::on_record_toggled(bool checked) m_sampleSource->getInputMessageQueue()->push(message); } -// ==================================================================== - -unsigned int SDRPlaySampleRates::m_rates[m_nb_rates] = { - 1536000, // 0 - 1792000, // 1 - 2000000, // 2 - 2048000, // 3 - 2304000, // 4 - 2400000, // 5 - 3072000, // 6 - 3200000, // 7 - 4000000, // 8 - 4096000, // 9 - 4608000, // 10 - 4800000, // 11 - 5000000, // 12 - 6000000, // 13 - 6144000, // 14 - 6400000, // 15 - 8000000, // 16 - 8192000, // 17 -}; - -unsigned int SDRPlaySampleRates::getRate(unsigned int rate_index) -{ - if (rate_index < m_nb_rates) - { - return m_rates[rate_index]; - } - else - { - return m_rates[0]; - } -} - -unsigned int SDRPlaySampleRates::getRateIndex(unsigned int rate) -{ - for (unsigned int i=0; i < m_nb_rates; i++) - { - if (rate == m_rates[i]) - { - return i; - } - } - - return 0; -} - -unsigned int SDRPlaySampleRates::getNbRates() -{ - return SDRPlaySampleRates::m_nb_rates; -} - -// ==================================================================== - -unsigned int SDRPlayBandwidths::m_bw[m_nb_bw] = { - 200000, // 0 - 300000, // 1 - 600000, // 2 - 1536000, // 3 - 5000000, // 4 - 6000000, // 5 - 7000000, // 6 - 8000000, // 7 -}; - -unsigned int SDRPlayBandwidths::getBandwidth(unsigned int bandwidth_index) -{ - if (bandwidth_index < m_nb_bw) - { - return m_bw[bandwidth_index]; - } - else - { - return m_bw[0]; - } -} - -unsigned int SDRPlayBandwidths::getBandwidthIndex(unsigned int bandwidth) -{ - for (unsigned int i=0; i < m_nb_bw; i++) - { - if (bandwidth == m_bw[i]) - { - return i; - } - } - - return 0; -} - -unsigned int SDRPlayBandwidths::getNbBandwidths() -{ - return SDRPlayBandwidths::m_nb_bw; -} - -// ==================================================================== - -unsigned int SDRPlayIF::m_if[m_nb_if] = { - 0, // 0 - 450000, // 1 - 1620000, // 2 - 2048000, // 3 -}; - -unsigned int SDRPlayIF::getIF(unsigned int if_index) -{ - if (if_index < m_nb_if) - { - return m_if[if_index]; - } - else - { - return m_if[0]; - } -} - -unsigned int SDRPlayIF::getIFIndex(unsigned int iff) -{ - for (unsigned int i=0; i < m_nb_if; i++) - { - if (iff == m_if[i]) - { - return i; - } - } - - return 0; -} - -unsigned int SDRPlayIF::getNbIFs() -{ - return SDRPlayIF::m_nb_if; -} - -// ==================================================================== - -/** Lower frequency bound in kHz inclusive */ -unsigned int SDRPlayBands::m_bandLow[m_nb_bands] = { - 10, // 0 - 12000, // 1 - 30000, // 2 - 50000, // 3 - 120000, // 4 - 250000, // 5 - 380000, // 6 - 1000000, // 7 -}; - -/** Lower frequency bound in kHz exclusive */ -unsigned int SDRPlayBands::m_bandHigh[m_nb_bands] = { - 12000, // 0 - 30000, // 1 - 50000, // 2 - 120000, // 3 - 250000, // 4 - 380000, // 5 - 1000000, // 6 - 2000000, // 7 -}; - -const char* SDRPlayBands::m_bandName[m_nb_bands] = { - "10k-12M", // 0 - "12-30M", // 1 - "30-50M", // 2 - "50-120M", // 3 - "120-250M", // 4 - "250-380M", // 5 - "380M-1G", // 6 - "1-2G", // 7 -}; - -QString SDRPlayBands::getBandName(unsigned int band_index) -{ - if (band_index < m_nb_bands) - { - return QString(m_bandName[band_index]); - } - else - { - return QString(m_bandName[0]); - } -} - -unsigned int SDRPlayBands::getBandLow(unsigned int band_index) -{ - if (band_index < m_nb_bands) - { - return m_bandLow[band_index]; - } - else - { - return m_bandLow[0]; - } -} - -unsigned int SDRPlayBands::getBandHigh(unsigned int band_index) -{ - if (band_index < m_nb_bands) - { - return m_bandHigh[band_index]; - } - else - { - return m_bandHigh[0]; - } -} - - -unsigned int SDRPlayBands::getNbBands() -{ - return SDRPlayBands::m_nb_bands; -} diff --git a/plugins/samplesource/sdrplay/sdrplaygui.h b/plugins/samplesource/sdrplay/sdrplaygui.h index cab175969..12616b2c1 100644 --- a/plugins/samplesource/sdrplay/sdrplaygui.h +++ b/plugins/samplesource/sdrplay/sdrplaygui.h @@ -97,50 +97,4 @@ private slots: void on_record_toggled(bool checked); }; - -// ==================================================================== - -class SDRPlaySampleRates { -public: - static unsigned int getRate(unsigned int rate_index); - static unsigned int getRateIndex(unsigned int rate); - static unsigned int getNbRates(); -private: - static const unsigned int m_nb_rates = 18; - static unsigned int m_rates[m_nb_rates]; -}; - -class SDRPlayBandwidths { -public: - static unsigned int getBandwidth(unsigned int bandwidth_index); - static unsigned int getBandwidthIndex(unsigned int bandwidth); - static unsigned int getNbBandwidths(); -private: - static const unsigned int m_nb_bw = 8; - static unsigned int m_bw[m_nb_bw]; -}; - -class SDRPlayIF { -public: - static unsigned int getIF(unsigned int if_index); - static unsigned int getIFIndex(unsigned int iff); - static unsigned int getNbIFs(); -private: - static const unsigned int m_nb_if = 4; - static unsigned int m_if[m_nb_if]; -}; - -class SDRPlayBands { -public: - static QString getBandName(unsigned int band_index); - static unsigned int getBandLow(unsigned int band_index); - static unsigned int getBandHigh(unsigned int band_index); - static unsigned int getNbBands(); -private: - static const unsigned int m_nb_bands = 8; - static unsigned int m_bandLow[m_nb_bands]; - static unsigned int m_bandHigh[m_nb_bands]; - static const char* m_bandName[m_nb_bands]; -}; - #endif /* PLUGINS_SAMPLESOURCE_SDRPLAY_SDRPLAYGUI_H_ */ diff --git a/plugins/samplesource/sdrplay/sdrplaygui.ui b/plugins/samplesource/sdrplay/sdrplaygui.ui index 95d7ecb82..22ce537d1 100644 --- a/plugins/samplesource/sdrplay/sdrplaygui.ui +++ b/plugins/samplesource/sdrplay/sdrplaygui.ui @@ -24,7 +24,7 @@ - Sans Serif + Liberation Sans 9 @@ -136,7 +136,7 @@ - DejaVu Sans Mono + Liberation Mono 20 diff --git a/plugins/samplesource/sdrplay/sdrplayinput.cpp b/plugins/samplesource/sdrplay/sdrplayinput.cpp index ce013c784..cc970b956 100644 --- a/plugins/samplesource/sdrplay/sdrplayinput.cpp +++ b/plugins/samplesource/sdrplay/sdrplayinput.cpp @@ -20,12 +20,13 @@ #include "SWGDeviceSettings.h" #include "SWGDeviceState.h" +#include "SWGDeviceReport.h" +#include "SWGSDRPlayReport.h" #include "util/simpleserializer.h" #include "dsp/dspcommands.h" #include "dsp/dspengine.h" #include -#include "sdrplaygui.h" #include "sdrplayinput.h" #include @@ -48,10 +49,7 @@ SDRPlayInput::SDRPlayInput(DeviceSourceAPI *deviceAPI) : m_running(false) { openDevice(); - - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID()); - m_fileSink = new FileRecord(std::string(recFileNameCStr)); + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); m_deviceAPI->addSink(m_fileSink); } @@ -168,12 +166,7 @@ bool SDRPlayInput::start() return false; } - if((m_sdrPlayThread = new SDRPlayThread(m_dev, &m_sampleFifo)) == 0) - { - qCritical("SDRPlayInput::start: failed to create thread"); - return false; - } - + m_sdrPlayThread = new SDRPlayThread(m_dev, &m_sampleFifo); m_sdrPlayThread->setLog2Decimation(m_settings.m_log2Decim); m_sdrPlayThread->setFcPos((int) m_settings.m_fcPos); @@ -307,9 +300,18 @@ bool SDRPlayInput::handleMessage(const Message& message) MsgFileRecord& conf = (MsgFileRecord&) message; qDebug() << "SDRPlayInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); - if (conf.getStartStop()) { + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSink->setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); + } + m_fileSink->startRecording(); - } else { + } + else + { m_fileSink->stopRecording(); } @@ -325,13 +327,11 @@ bool SDRPlayInput::handleMessage(const Message& message) if (m_deviceAPI->initAcquisition()) { m_deviceAPI->startAcquisition(); - DSPEngine::instance()->startAudioOutput(); } } else { m_deviceAPI->stopAcquisition(); - DSPEngine::instance()->stopAudioOutput(); } return true; @@ -533,44 +533,24 @@ bool SDRPlayInput::applySettings(const SDRPlaySettings& settings, bool forwardCh || (m_settings.m_fcPos != settings.m_fcPos) || (m_settings.m_log2Decim != settings.m_log2Decim) || force) { + qint64 deviceCenterFrequency = DeviceSampleSource::calculateDeviceCenterFrequency( + settings.m_centerFrequency, + 0, + settings.m_log2Decim, + (DeviceSampleSource::fcPos_t) settings.m_fcPos, + SDRPlaySampleRates::getRate(m_settings.m_devSampleRateIndex)); + m_settings.m_centerFrequency = settings.m_centerFrequency; m_settings.m_LOppmTenths = settings.m_LOppmTenths; m_settings.m_fcPos = settings.m_fcPos; m_settings.m_log2Decim = settings.m_log2Decim; - qint64 deviceCenterFrequency = m_settings.m_centerFrequency; - qint64 f_img = deviceCenterFrequency; - quint32 devSampleRate = SDRPlaySampleRates::getRate(m_settings.m_devSampleRateIndex); forwardChange = true; - if ((m_settings.m_log2Decim == 0) || (settings.m_fcPos == SDRPlaySettings::FC_POS_CENTER)) - { - deviceCenterFrequency = m_settings.m_centerFrequency; - f_img = deviceCenterFrequency; - } - else - { - if (settings.m_fcPos == SDRPlaySettings::FC_POS_INFRA) - { - deviceCenterFrequency = m_settings.m_centerFrequency + (devSampleRate / 4); - f_img = deviceCenterFrequency + devSampleRate/2; - } - else if (settings.m_fcPos == SDRPlaySettings::FC_POS_SUPRA) - { - deviceCenterFrequency = m_settings.m_centerFrequency - (devSampleRate / 4); - f_img = deviceCenterFrequency - devSampleRate/2; - } - } - if(m_dev != 0) { - if (setDeviceCenterFrequency(deviceCenterFrequency)) - { - qDebug() << "SDRPlayInput::applySettings: center freq: " << m_settings.m_centerFrequency << " Hz" - << " device center freq: " << deviceCenterFrequency << " Hz" - << " device sample rate: " << devSampleRate << "Hz" - << " Actual sample rate: " << devSampleRate/(1<init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int SDRPlayInput::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage __attribute__((unused))) +{ + SDRPlaySettings settings = m_settings; + + if (deviceSettingsKeys.contains("centerFrequency")) { + settings.m_centerFrequency = response.getSdrPlaySettings()->getCenterFrequency(); + } + if (deviceSettingsKeys.contains("tunerGain")) { + settings.m_tunerGain = response.getSdrPlaySettings()->getTunerGain(); + } + if (deviceSettingsKeys.contains("LOppmTenths")) { + settings.m_LOppmTenths = response.getSdrPlaySettings()->getLOppmTenths(); + } + if (deviceSettingsKeys.contains("frequencyBandIndex")) { + settings.m_frequencyBandIndex = response.getSdrPlaySettings()->getFrequencyBandIndex(); + } + if (deviceSettingsKeys.contains("ifFrequencyIndex")) { + settings.m_ifFrequencyIndex = response.getSdrPlaySettings()->getIfFrequencyIndex(); + } + if (deviceSettingsKeys.contains("bandwidthIndex")) { + settings.m_bandwidthIndex = response.getSdrPlaySettings()->getBandwidthIndex(); + } + if (deviceSettingsKeys.contains("devSampleRateIndex")) { + settings.m_devSampleRateIndex = response.getSdrPlaySettings()->getDevSampleRateIndex(); + } + if (deviceSettingsKeys.contains("log2Decim")) { + settings.m_log2Decim = response.getSdrPlaySettings()->getLog2Decim(); + } + if (deviceSettingsKeys.contains("fcPos")) + { + int fcPos = response.getSdrPlaySettings()->getFcPos(); + fcPos = fcPos < 0 ? 0 : fcPos > 2 ? 2 : fcPos; + settings.m_fcPos = (SDRPlaySettings::fcPos_t) fcPos; + } + if (deviceSettingsKeys.contains("dcBlock")) { + settings.m_dcBlock = response.getSdrPlaySettings()->getDcBlock() != 0; + } + if (deviceSettingsKeys.contains("iqCorrection")) { + settings.m_iqCorrection = response.getSdrPlaySettings()->getIqCorrection() != 0; + } + if (deviceSettingsKeys.contains("tunerGainMode")) { + settings.m_tunerGainMode = response.getSdrPlaySettings()->getTunerGainMode() != 0; + } + if (deviceSettingsKeys.contains("lnaOn")) { + settings.m_lnaOn = response.getSdrPlaySettings()->getLnaOn() != 0; + } + if (deviceSettingsKeys.contains("mixerAmpOn")) { + settings.m_mixerAmpOn = response.getSdrPlaySettings()->getMixerAmpOn() != 0; + } + if (deviceSettingsKeys.contains("basebandGain")) { + settings.m_basebandGain = response.getSdrPlaySettings()->getBasebandGain(); + } + if (deviceSettingsKeys.contains("fileRecordName")) { + settings.m_fileRecordName = *response.getRtlSdrSettings()->getFileRecordName(); + } + + MsgConfigureSDRPlay *msg = MsgConfigureSDRPlay::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureSDRPlay *msgToGUI = MsgConfigureSDRPlay::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +void SDRPlayInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const SDRPlaySettings& settings) +{ + response.getSdrPlaySettings()->setCenterFrequency(settings.m_centerFrequency); + response.getSdrPlaySettings()->setTunerGain(settings.m_tunerGain); + response.getSdrPlaySettings()->setLOppmTenths(settings.m_LOppmTenths); + response.getSdrPlaySettings()->setFrequencyBandIndex(settings.m_frequencyBandIndex); + response.getSdrPlaySettings()->setIfFrequencyIndex(settings.m_ifFrequencyIndex); + response.getSdrPlaySettings()->setBandwidthIndex(settings.m_bandwidthIndex); + response.getSdrPlaySettings()->setDevSampleRateIndex(settings.m_devSampleRateIndex); + response.getSdrPlaySettings()->setLog2Decim(settings.m_log2Decim); + response.getSdrPlaySettings()->setFcPos((int) settings.m_fcPos); + response.getSdrPlaySettings()->setDcBlock(settings.m_dcBlock ? 1 : 0); + response.getSdrPlaySettings()->setIqCorrection(settings.m_iqCorrection ? 1 : 0); + response.getSdrPlaySettings()->setTunerGainMode((int) settings.m_tunerGainMode); + response.getSdrPlaySettings()->setLnaOn(settings.m_lnaOn ? 1 : 0); + response.getSdrPlaySettings()->setMixerAmpOn(settings.m_mixerAmpOn ? 1 : 0); + response.getSdrPlaySettings()->setBasebandGain(settings.m_basebandGain); + + if (response.getSdrPlaySettings()->getFileRecordName()) { + *response.getSdrPlaySettings()->getFileRecordName() = settings.m_fileRecordName; + } else { + response.getSdrPlaySettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + } +} + +int SDRPlayInput::webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage __attribute__((unused))) +{ + response.setSdrPlayReport(new SWGSDRangel::SWGSDRPlayReport()); + response.getSdrPlayReport()->init(); + webapiFormatDeviceReport(response); + return 200; +} + +void SDRPlayInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response) +{ + response.getSdrPlayReport()->setSampleRates(new QList); + + for (unsigned int i = 0; i < SDRPlaySampleRates::getNbRates(); i++) + { + response.getSdrPlayReport()->getSampleRates()->append(new SWGSDRangel::SWGSampleRate); + response.getSdrPlayReport()->getSampleRates()->back()->setRate(SDRPlaySampleRates::getRate(i)); + } + + response.getSdrPlayReport()->setIntermediateFrequencies(new QList); + + for (unsigned int i = 0; i < SDRPlayIF::getNbIFs(); i++) + { + response.getSdrPlayReport()->getIntermediateFrequencies()->append(new SWGSDRangel::SWGFrequency); + response.getSdrPlayReport()->getIntermediateFrequencies()->back()->setFrequency(SDRPlayIF::getIF(i)); + } + + response.getSdrPlayReport()->setBandwidths(new QList); + + for (unsigned int i = 0; i < SDRPlayBandwidths::getNbBandwidths(); i++) + { + response.getSdrPlayReport()->getBandwidths()->append(new SWGSDRangel::SWGBandwidth); + response.getSdrPlayReport()->getBandwidths()->back()->setBandwidth(SDRPlayBandwidths::getBandwidth(i)); + } + + response.getSdrPlayReport()->setFrequencyBands(new QList); + + for (unsigned int i = 0; i < SDRPlayBands::getNbBands(); i++) + { + response.getSdrPlayReport()->getFrequencyBands()->append(new SWGSDRangel::SWGFrequencyBand); + response.getSdrPlayReport()->getFrequencyBands()->back()->setName(new QString(SDRPlayBands::getBandName(i))); + response.getSdrPlayReport()->getFrequencyBands()->back()->setLowerBound(SDRPlayBands::getBandLow(i)); + response.getSdrPlayReport()->getFrequencyBands()->back()->setHigherBound(SDRPlayBands::getBandHigh(i)); + } +} + +// ==================================================================== + +unsigned int SDRPlaySampleRates::m_rates[m_nb_rates] = { + 1536000, // 0 + 1792000, // 1 + 2000000, // 2 + 2048000, // 3 + 2304000, // 4 + 2400000, // 5 + 3072000, // 6 + 3200000, // 7 + 4000000, // 8 + 4096000, // 9 + 4608000, // 10 + 4800000, // 11 + 5000000, // 12 + 6000000, // 13 + 6144000, // 14 + 6400000, // 15 + 8000000, // 16 + 8192000, // 17 +}; + +unsigned int SDRPlaySampleRates::getRate(unsigned int rate_index) +{ + if (rate_index < m_nb_rates) + { + return m_rates[rate_index]; + } + else + { + return m_rates[0]; + } +} + +unsigned int SDRPlaySampleRates::getRateIndex(unsigned int rate) +{ + for (unsigned int i=0; i < m_nb_rates; i++) + { + if (rate == m_rates[i]) + { + return i; + } + } + + return 0; +} + +unsigned int SDRPlaySampleRates::getNbRates() +{ + return SDRPlaySampleRates::m_nb_rates; +} + +// ==================================================================== + +unsigned int SDRPlayBandwidths::m_bw[m_nb_bw] = { + 200000, // 0 + 300000, // 1 + 600000, // 2 + 1536000, // 3 + 5000000, // 4 + 6000000, // 5 + 7000000, // 6 + 8000000, // 7 +}; + +unsigned int SDRPlayBandwidths::getBandwidth(unsigned int bandwidth_index) +{ + if (bandwidth_index < m_nb_bw) + { + return m_bw[bandwidth_index]; + } + else + { + return m_bw[0]; + } +} + +unsigned int SDRPlayBandwidths::getBandwidthIndex(unsigned int bandwidth) +{ + for (unsigned int i=0; i < m_nb_bw; i++) + { + if (bandwidth == m_bw[i]) + { + return i; + } + } + + return 0; +} + +unsigned int SDRPlayBandwidths::getNbBandwidths() +{ + return SDRPlayBandwidths::m_nb_bw; +} + +// ==================================================================== + +unsigned int SDRPlayIF::m_if[m_nb_if] = { + 0, // 0 + 450000, // 1 + 1620000, // 2 + 2048000, // 3 +}; + +unsigned int SDRPlayIF::getIF(unsigned int if_index) +{ + if (if_index < m_nb_if) + { + return m_if[if_index]; + } + else + { + return m_if[0]; + } +} + +unsigned int SDRPlayIF::getIFIndex(unsigned int iff) +{ + for (unsigned int i=0; i < m_nb_if; i++) + { + if (iff == m_if[i]) + { + return i; + } + } + + return 0; +} + +unsigned int SDRPlayIF::getNbIFs() +{ + return SDRPlayIF::m_nb_if; +} + +// ==================================================================== + +/** Lower frequency bound in kHz inclusive */ +unsigned int SDRPlayBands::m_bandLow[m_nb_bands] = { + 10, // 0 + 12000, // 1 + 30000, // 2 + 50000, // 3 + 120000, // 4 + 250000, // 5 + 380000, // 6 + 1000000, // 7 +}; + +/** Lower frequency bound in kHz exclusive */ +unsigned int SDRPlayBands::m_bandHigh[m_nb_bands] = { + 12000, // 0 + 30000, // 1 + 50000, // 2 + 120000, // 3 + 250000, // 4 + 380000, // 5 + 1000000, // 6 + 2000000, // 7 +}; + +const char* SDRPlayBands::m_bandName[m_nb_bands] = { + "10k-12M", // 0 + "12-30M", // 1 + "30-50M", // 2 + "50-120M", // 3 + "120-250M", // 4 + "250-380M", // 5 + "380M-1G", // 6 + "1-2G", // 7 +}; + +QString SDRPlayBands::getBandName(unsigned int band_index) +{ + if (band_index < m_nb_bands) + { + return QString(m_bandName[band_index]); + } + else + { + return QString(m_bandName[0]); + } +} + +unsigned int SDRPlayBands::getBandLow(unsigned int band_index) +{ + if (band_index < m_nb_bands) + { + return m_bandLow[band_index]; + } + else + { + return m_bandLow[0]; + } +} + +unsigned int SDRPlayBands::getBandHigh(unsigned int band_index) +{ + if (band_index < m_nb_bands) + { + return m_bandHigh[band_index]; + } + else + { + return m_bandHigh[0]; + } +} + + +unsigned int SDRPlayBands::getNbBands() +{ + return SDRPlayBands::m_nb_bands; +} + diff --git a/plugins/samplesource/sdrplay/sdrplayinput.h b/plugins/samplesource/sdrplay/sdrplayinput.h index 3ee347188..3d5b52d6a 100644 --- a/plugins/samplesource/sdrplay/sdrplayinput.h +++ b/plugins/samplesource/sdrplay/sdrplayinput.h @@ -148,6 +148,20 @@ public: virtual bool handleMessage(const Message& message); + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + virtual int webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage); @@ -164,6 +178,8 @@ private: void closeDevice(); bool applySettings(const SDRPlaySettings& settings, bool forwardChange, bool force); bool setDeviceCenterFrequency(quint64 freq); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const SDRPlaySettings& settings); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); DeviceSourceAPI *m_deviceAPI; QMutex m_mutex; @@ -177,4 +193,49 @@ private: FileRecord *m_fileSink; //!< File sink to record device I/Q output }; +// ==================================================================== + +class SDRPlaySampleRates { +public: + static unsigned int getRate(unsigned int rate_index); + static unsigned int getRateIndex(unsigned int rate); + static unsigned int getNbRates(); +private: + static const unsigned int m_nb_rates = 18; + static unsigned int m_rates[m_nb_rates]; +}; + +class SDRPlayBandwidths { +public: + static unsigned int getBandwidth(unsigned int bandwidth_index); + static unsigned int getBandwidthIndex(unsigned int bandwidth); + static unsigned int getNbBandwidths(); +private: + static const unsigned int m_nb_bw = 8; + static unsigned int m_bw[m_nb_bw]; +}; + +class SDRPlayIF { +public: + static unsigned int getIF(unsigned int if_index); + static unsigned int getIFIndex(unsigned int iff); + static unsigned int getNbIFs(); +private: + static const unsigned int m_nb_if = 4; + static unsigned int m_if[m_nb_if]; +}; + +class SDRPlayBands { +public: + static QString getBandName(unsigned int band_index); + static unsigned int getBandLow(unsigned int band_index); + static unsigned int getBandHigh(unsigned int band_index); + static unsigned int getNbBands(); +private: + static const unsigned int m_nb_bands = 8; + static unsigned int m_bandLow[m_nb_bands]; + static unsigned int m_bandHigh[m_nb_bands]; + static const char* m_bandName[m_nb_bands]; +}; + #endif /* PLUGINS_SAMPLESOURCE_SDRPLAY_SDRPLAYINPUT_H_ */ diff --git a/plugins/samplesource/sdrplay/sdrplayplugin.cpp b/plugins/samplesource/sdrplay/sdrplayplugin.cpp index ee861911e..ebb5698ce 100644 --- a/plugins/samplesource/sdrplay/sdrplayplugin.cpp +++ b/plugins/samplesource/sdrplay/sdrplayplugin.cpp @@ -15,17 +15,20 @@ /////////////////////////////////////////////////////////////////////////////////// #include -#include #include #include "plugin/pluginapi.h" #include "util/simpleserializer.h" +#ifdef SERVER_MODE +#include "sdrplayinput.h" +#else #include "sdrplaygui.h" +#endif #include "sdrplayplugin.h" #include const PluginDescriptor SDRPlayPlugin::m_pluginDescriptor = { QString("SDRPlay RSP1 Input"), - QString("3.11.0"), + QString("4.0.0"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, @@ -87,6 +90,15 @@ PluginInterface::SamplingDevices SDRPlayPlugin::enumSampleSources() return result; } +#ifdef SERVER_MODE +PluginInstanceGUI* SDRPlayPlugin::createSampleSourcePluginInstanceGUI( + const QString& sourceId __attribute((unused)), + QWidget **widget __attribute((unused)), + DeviceUISet *deviceUISet __attribute((unused))) +{ + return 0; +} +#else PluginInstanceGUI* SDRPlayPlugin::createSampleSourcePluginInstanceGUI( const QString& sourceId, QWidget **widget, @@ -103,6 +115,7 @@ PluginInstanceGUI* SDRPlayPlugin::createSampleSourcePluginInstanceGUI( return 0; } } +#endif DeviceSampleSource *SDRPlayPlugin::createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI) { diff --git a/plugins/samplesource/sdrplay/sdrplaysettings.cpp b/plugins/samplesource/sdrplay/sdrplaysettings.cpp index 201cd314c..8585f54c5 100644 --- a/plugins/samplesource/sdrplay/sdrplaysettings.cpp +++ b/plugins/samplesource/sdrplay/sdrplaysettings.cpp @@ -41,6 +41,7 @@ void SDRPlaySettings::resetToDefaults() m_lnaOn = false; m_mixerAmpOn = false; m_basebandGain = 29; + m_fileRecordName = ""; } QByteArray SDRPlaySettings::serialize() const diff --git a/plugins/samplesource/sdrplay/sdrplaysettings.h b/plugins/samplesource/sdrplay/sdrplaysettings.h index 8c7b06fd1..36b048928 100644 --- a/plugins/samplesource/sdrplay/sdrplaysettings.h +++ b/plugins/samplesource/sdrplay/sdrplaysettings.h @@ -19,6 +19,7 @@ #include #include +#include #include struct SDRPlaySettings { @@ -43,6 +44,7 @@ struct SDRPlaySettings { bool m_lnaOn; bool m_mixerAmpOn; int m_basebandGain; + QString m_fileRecordName; SDRPlaySettings(); void resetToDefaults(); diff --git a/plugins/samplesource/sdrplay/sdrplaythread.h b/plugins/samplesource/sdrplay/sdrplaythread.h index 807db699c..428cd00c4 100644 --- a/plugins/samplesource/sdrplay/sdrplaythread.h +++ b/plugins/samplesource/sdrplay/sdrplaythread.h @@ -52,11 +52,7 @@ private: unsigned int m_log2Decim; int m_fcPos; -#ifdef SDR_RX_SAMPLE_24BIT - Decimators m_decimators; -#else Decimators m_decimators; -#endif void run(); void callback(const qint16* buf, qint32 len); diff --git a/plugins/samplesource/soapysdrinput/CMakeLists.txt b/plugins/samplesource/soapysdrinput/CMakeLists.txt new file mode 100644 index 000000000..b87a6567f --- /dev/null +++ b/plugins/samplesource/soapysdrinput/CMakeLists.txt @@ -0,0 +1,78 @@ +project(soapysdrinput) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +set(soapysdrinput_SOURCES + soapysdrinputgui.cpp + soapysdrinput.cpp + soapysdrinputplugin.cpp + soapysdrinputsettings.cpp + soapysdrinputthread.cpp +) + +set(soapysdrinput_HEADERS + soapysdrinputgui.h + soapysdrinput.h + soapysdrinputplugin.h + soapysdrinputsettings.h + soapysdrinputthread.h +) + +set(soapysdrinput_FORMS + soapysdrinputgui.ui +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${SOAPYSDRSRC}/include + ${SOAPYSDRSRC}/src +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${SOAPYSDR_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +qt5_wrap_ui(soapysdrinput_FORMS_HEADERS ${soapysdrinput_FORMS}) + +add_library(inputsoapysdr SHARED + ${soapysdrinput_SOURCES} + ${soapysdrinput_HEADERS_MOC} + ${soapysdrinput_FORMS_HEADERS} +) + +if (BUILD_DEBIAN) +target_link_libraries(inputsoapysdr + ${QT_LIBRARIES} + bladerf + sdrbase + sdrgui + swagger + soapysdrdevice +) +else (BUILD_DEBIAN) +target_link_libraries(inputsoapysdr + ${QT_LIBRARIES} + ${SOAPYSDR_LIBRARY} + sdrbase + sdrgui + swagger + soapysdrdevice +) +endif (BUILD_DEBIAN) + +target_link_libraries(inputsoapysdr Qt5::Core Qt5::Widgets) + +install(TARGETS inputsoapysdr DESTINATION lib/plugins/samplesource) diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp new file mode 100644 index 000000000..198c9d822 --- /dev/null +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp @@ -0,0 +1,1091 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "util/simpleserializer.h" + +#include "device/devicesourceapi.h" +#include "device/devicesinkapi.h" +#include "dsp/dspcommands.h" +#include "dsp/filerecord.h" +#include "dsp/dspengine.h" +#include "soapysdr/devicesoapysdr.h" + +#include "soapysdrinputthread.h" +#include "soapysdrinput.h" + +MESSAGE_CLASS_DEFINITION(SoapySDRInput::MsgConfigureSoapySDRInput, Message) +MESSAGE_CLASS_DEFINITION(SoapySDRInput::MsgFileRecord, Message) +MESSAGE_CLASS_DEFINITION(SoapySDRInput::MsgStartStop, Message) +MESSAGE_CLASS_DEFINITION(SoapySDRInput::MsgReportGainChange, Message) + +SoapySDRInput::SoapySDRInput(DeviceSourceAPI *deviceAPI) : + m_deviceAPI(deviceAPI), + m_settings(), + m_deviceDescription("SoapySDRInput"), + m_running(false), + m_thread(0) +{ + openDevice(); + initGainSettings(m_settings); + + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); + m_deviceAPI->addSink(m_fileSink); +} + +SoapySDRInput::~SoapySDRInput() +{ + if (m_running) { + stop(); + } + + m_deviceAPI->removeSink(m_fileSink); + delete m_fileSink; + + closeDevice(); +} + +void SoapySDRInput::destroy() +{ + delete this; +} + +bool SoapySDRInput::openDevice() +{ + if (!m_sampleFifo.setSize(96000 * 4)) + { + qCritical("SoapySDRInput::openDevice: could not allocate SampleFifo"); + return false; + } + else + { + qDebug("SoapySDRInput::openDevice: allocated SampleFifo"); + } + + // look for Rx buddies and get reference to the device object + if (m_deviceAPI->getSourceBuddies().size() > 0) // look source sibling first + { + qDebug("SoapySDRInput::openDevice: look in Rx buddies"); + + DeviceSourceAPI *sourceBuddy = m_deviceAPI->getSourceBuddies()[0]; + DeviceSoapySDRShared *deviceSoapySDRShared = (DeviceSoapySDRShared*) sourceBuddy->getBuddySharedPtr(); + + if (deviceSoapySDRShared == 0) + { + qCritical("SoapySDRInput::openDevice: the source buddy shared pointer is null"); + return false; + } + + SoapySDR::Device *device = deviceSoapySDRShared->m_device; + + if (device == 0) + { + qCritical("SoapySDRInput::openDevice: cannot get device pointer from Rx buddy"); + return false; + } + + m_deviceShared.m_device = device; + m_deviceShared.m_deviceParams = deviceSoapySDRShared->m_deviceParams; + } + // look for Tx buddies and get reference to the device object + else if (m_deviceAPI->getSinkBuddies().size() > 0) // then sink + { + qDebug("SoapySDRInput::openDevice: look in Tx buddies"); + + DeviceSinkAPI *sinkBuddy = m_deviceAPI->getSinkBuddies()[0]; + DeviceSoapySDRShared *deviceSoapySDRShared = (DeviceSoapySDRShared*) sinkBuddy->getBuddySharedPtr(); + + if (deviceSoapySDRShared == 0) + { + qCritical("SoapySDRInput::openDevice: the sink buddy shared pointer is null"); + return false; + } + + SoapySDR::Device *device = deviceSoapySDRShared->m_device; + + if (device == 0) + { + qCritical("SoapySDRInput::openDevice: cannot get device pointer from Tx buddy"); + return false; + } + + m_deviceShared.m_device = device; + m_deviceShared.m_deviceParams = deviceSoapySDRShared->m_deviceParams; + } + // There are no buddies then create the first SoapySDR device + else + { + qDebug("SoapySDRInput::openDevice: open device here"); + DeviceSoapySDR& deviceSoapySDR = DeviceSoapySDR::instance(); + m_deviceShared.m_device = deviceSoapySDR.openSoapySDR(m_deviceAPI->getSampleSourceSequence()); + + if (!m_deviceShared.m_device) + { + qCritical("BladeRF2Input::openDevice: cannot open BladeRF2 device"); + return false; + } + + m_deviceShared.m_deviceParams = new DeviceSoapySDRParams(m_deviceShared.m_device); + } + + m_deviceShared.m_channel = m_deviceAPI->getItemIndex(); // publicly allocate channel + m_deviceShared.m_source = this; + m_deviceAPI->setBuddySharedPtr(&m_deviceShared); // propagate common parameters to API + return true; +} + +void SoapySDRInput::closeDevice() +{ + if (m_deviceShared.m_device == 0) { // was never open + return; + } + + if (m_running) { + stop(); + } + + if (m_thread) { // stills own the thread => transfer to a buddy + moveThreadToBuddy(); + } + + m_deviceShared.m_channel = -1; // publicly release channel + m_deviceShared.m_source = 0; + + // No buddies so effectively close the device and delete parameters + + if ((m_deviceAPI->getSinkBuddies().size() == 0) && (m_deviceAPI->getSourceBuddies().size() == 0)) + { + delete m_deviceShared.m_deviceParams; + m_deviceShared.m_deviceParams = 0; + DeviceSoapySDR& deviceSoapySDR = DeviceSoapySDR::instance(); + deviceSoapySDR.closeSoapySdr(m_deviceShared.m_device); + m_deviceShared.m_device = 0; + } +} + +void SoapySDRInput::getFrequencyRange(uint64_t& min, uint64_t& max) +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getRxChannelSettings(m_deviceShared.m_channel); + + if (channelSettings && (channelSettings->m_frequencySettings.size() > 0)) + { + DeviceSoapySDRParams::FrequencySetting freqSettings = channelSettings->m_frequencySettings[0]; + SoapySDR::RangeList rangeList = freqSettings.m_ranges; + + if (rangeList.size() > 0) + { + SoapySDR::Range range = rangeList[0]; + min = range.minimum(); + max = range.maximum(); + } + else + { + min = 0; + max = 0; + } + } + else + { + min = 0; + max = 0; + } +} + +void SoapySDRInput::getGlobalGainRange(int& min, int& max) +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getRxChannelSettings(m_deviceShared.m_channel); + + if (channelSettings) + { + min = channelSettings->m_gainRange.minimum(); + max = channelSettings->m_gainRange.maximum(); + } + else + { + min = 0; + max = 0; + } +} + +bool SoapySDRInput::isAGCSupported() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getRxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_hasAGC; +} + +const std::vector& SoapySDRInput::getAntennas() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getRxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_antennas; +} + +const SoapySDR::RangeList& SoapySDRInput::getRateRanges() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getRxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_ratesRanges; +} + +const SoapySDR::RangeList& SoapySDRInput::getBandwidthRanges() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getRxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_bandwidthsRanges; +} + +int SoapySDRInput::getAntennaIndex(const std::string& antenna) +{ + const std::vector& antennaList = getAntennas(); + std::vector::const_iterator it = std::find(antennaList.begin(), antennaList.end(), antenna); + + if (it == antennaList.end()) { + return -1; + } else { + return it - antennaList.begin(); + } +} + +const std::vector& SoapySDRInput::getTunableElements() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getRxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_frequencySettings; +} + +const std::vector& SoapySDRInput::getIndividualGainsRanges() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getRxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_gainSettings; +} + +void SoapySDRInput::initGainSettings(SoapySDRInputSettings& settings) +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getRxChannelSettings(m_deviceShared.m_channel); + settings.m_individualGains.clear(); + settings.m_globalGain = 0; + + for (const auto &it : channelSettings->m_gainSettings) { + settings.m_individualGains[QString(it.m_name.c_str())] = 0.0; + } + + updateGains(m_deviceShared.m_device, m_deviceShared.m_channel, settings); +} + +bool SoapySDRInput::hasDCAutoCorrection() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getRxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_hasDCAutoCorrection; +} + +bool SoapySDRInput::hasDCCorrectionValue() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getRxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_hasDCOffsetValue; +} + +bool SoapySDRInput::hasIQCorrectionValue() +{ + const DeviceSoapySDRParams::ChannelSettings* channelSettings = m_deviceShared.m_deviceParams->getRxChannelSettings(m_deviceShared.m_channel); + return channelSettings->m_hasIQBalanceValue; +} + +void SoapySDRInput::init() +{ + applySettings(m_settings, true); +} + +SoapySDRInputThread *SoapySDRInput::findThread() +{ + if (m_thread == 0) // this does not own the thread + { + SoapySDRInputThread *soapySDRInputThread = 0; + + // find a buddy that has allocated the thread + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + std::vector::const_iterator it = sourceBuddies.begin(); + + for (; it != sourceBuddies.end(); ++it) + { + SoapySDRInput *buddySource = ((DeviceSoapySDRShared*) (*it)->getBuddySharedPtr())->m_source; + + if (buddySource) + { + soapySDRInputThread = buddySource->getThread(); + + if (soapySDRInputThread) { + break; + } + } + } + + return soapySDRInputThread; + } + else + { + return m_thread; // own thread + } +} + +void SoapySDRInput::moveThreadToBuddy() +{ + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + std::vector::const_iterator it = sourceBuddies.begin(); + + for (; it != sourceBuddies.end(); ++it) + { + SoapySDRInput *buddySource = ((DeviceSoapySDRShared*) (*it)->getBuddySharedPtr())->m_source; + + if (buddySource) + { + buddySource->setThread(m_thread); + m_thread = 0; // zero for others + } + } +} + +bool SoapySDRInput::start() +{ + // There is a single thread per physical device (Rx side). This thread is unique and referenced by a unique + // buddy in the group of source buddies associated with this physical device. + // + // This start method is responsible for managing the thread and number of channels when the streaming of a Rx channel is started + // + // It checks the following conditions + // - the thread is allocated or not (by itself or one of its buddies). If it is it grabs the thread pointer. + // - the requested channel is the first (0) or the following + // + // There are two possible working modes: + // - Single Input (SI) with only one channel streaming. This HAS to be channel 0. + // - Multiple Input (MI) with two or more channels. It MUST be in this configuration if any channel other than 0 + // is used irrespective of what you actually do with samples coming from ignored channels. + // For example When we will run with only channel 2 streaming from the client perspective the channels 0 and 1 will actually + // be enabled and streaming but its samples will just be disregarded. + // This means that all channels up to the highest in index being used are activated. + // + // It manages the transition form SI where only one channel (the first or channel 0) should be running to the + // Multiple Input (MI) if the requested channel is 1 or more. More generally it checks if the requested channel is within the current + // channel range allocated in the thread or past it. To perform the transition it stops the thread, deletes it and creates a new one. + // It marks the thread as needing start. + // + // If the requested channel is within the thread channel range (this thread being already allocated) it simply adds its FIFO reference + // so that the samples are fed to the FIFO and leaves the thread unchanged (no stop, no delete/new) + // + // If there is no thread allocated it creates a new one with a number of channels that fits the requested channel. That is + // 1 if channel 0 is requested (SI mode) and 3 if channel 2 is requested (MI mode). It marks the thread as needing start. + // + // Eventually it registers the FIFO in the thread. If the thread has to be started it enables the channels up to the number of channels + // allocated in the thread and starts the thread. + // + // Note: this is quite similar to the BladeRF2 start handling. The main difference is that the channel allocation (enabling) process is + // done in the thread object. + + if (!m_deviceShared.m_device) + { + qDebug("SoapySDRInput::start: no device object"); + return false; + } + + int requestedChannel = m_deviceAPI->getItemIndex(); + SoapySDRInputThread *soapySDRInputThread = findThread(); + bool needsStart = false; + + if (soapySDRInputThread) // if thread is already allocated + { + qDebug("SoapySDRInput::start: thread is already allocated"); + + int nbOriginalChannels = soapySDRInputThread->getNbChannels(); + + if (requestedChannel+1 > nbOriginalChannels) // expansion by deleting and re-creating the thread + { + qDebug("SoapySDRInput::start: expand channels. Re-allocate thread and take ownership"); + + SampleSinkFifo **fifos = new SampleSinkFifo*[nbOriginalChannels]; + unsigned int *log2Decims = new unsigned int[nbOriginalChannels]; + int *fcPoss = new int[nbOriginalChannels]; + + for (int i = 0; i < nbOriginalChannels; i++) // save original FIFO references and data + { + fifos[i] = soapySDRInputThread->getFifo(i); + log2Decims[i] = soapySDRInputThread->getLog2Decimation(i); + fcPoss[i] = soapySDRInputThread->getFcPos(i); + } + + soapySDRInputThread->stopWork(); + delete soapySDRInputThread; + soapySDRInputThread = new SoapySDRInputThread(m_deviceShared.m_device, requestedChannel+1); + m_thread = soapySDRInputThread; // take ownership + + for (int i = 0; i < nbOriginalChannels; i++) // restore original FIFO references + { + soapySDRInputThread->setFifo(i, fifos[i]); + soapySDRInputThread->setLog2Decimation(i, log2Decims[i]); + soapySDRInputThread->setFcPos(i, fcPoss[i]); + } + + // remove old thread address from buddies (reset in all buddies). The address being held only in the owning source. + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + std::vector::const_iterator it = sourceBuddies.begin(); + + for (; it != sourceBuddies.end(); ++it) { + ((DeviceSoapySDRShared*) (*it)->getBuddySharedPtr())->m_source->setThread(0); + } + + needsStart = true; + } + else + { + qDebug("SoapySDRInput::start: keep buddy thread"); + } + } + else // first allocation + { + qDebug("SoapySDRInput::start: allocate thread and take ownership"); + soapySDRInputThread = new SoapySDRInputThread(m_deviceShared.m_device, requestedChannel+1); + m_thread = soapySDRInputThread; // take ownership + needsStart = true; + } + + soapySDRInputThread->setFifo(requestedChannel, &m_sampleFifo); + soapySDRInputThread->setLog2Decimation(requestedChannel, m_settings.m_log2Decim); + soapySDRInputThread->setFcPos(requestedChannel, (int) m_settings.m_fcPos); + + if (needsStart) + { + qDebug("SoapySDRInput::start: (re)sart buddy thread"); + soapySDRInputThread->setSampleRate(m_settings.m_devSampleRate); + soapySDRInputThread->startWork(); + } + + qDebug("SoapySDRInput::start: started"); + m_running = true; + + return true; +} + +void SoapySDRInput::stop() +{ + // This stop method is responsible for managing the thread and channel disabling when the streaming of + // a Rx channel is stopped + // + // If the thread is currently managing only one channel (SI mode). The thread can be just stopped and deleted. + // Then the channel is closed (disabled). + // + // If the thread is currently managing many channels (MI mode) and we are removing the last channel. The transition + // or reduction of MI size is handled by stopping the thread, deleting it and creating a new one + // with the maximum number of channels needed if (and only if) there is still a channel active. + // + // If the thread is currently managing many channels (MI mode) but the channel being stopped is not the last + // channel then the FIFO reference is simply removed from the thread so that it will not stream into this FIFO + // anymore. In this case the channel is not closed (this is managed in the thread object) so that other channels + // can continue with the same configuration. The device continues streaming on this channel but the samples are simply + // dropped (by removing FIFO reference). + // + // Note: this is quite similar to the BladeRF2 stop handling. The main difference is that the channel allocation (enabling) process is + // done in the thread object. + + if (!m_running) { + return; + } + + int requestedChannel = m_deviceAPI->getItemIndex(); + SoapySDRInputThread *soapySDRInputThread = findThread(); + + if (soapySDRInputThread == 0) { // no thread allocated + return; + } + + int nbOriginalChannels = soapySDRInputThread->getNbChannels(); + + if (nbOriginalChannels == 1) // SI mode => just stop and delete the thread + { + qDebug("SoapySDRInput::stop: SI mode. Just stop and delete the thread"); + soapySDRInputThread->stopWork(); + delete soapySDRInputThread; + m_thread = 0; + + // remove old thread address from buddies (reset in all buddies) + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + std::vector::const_iterator it = sourceBuddies.begin(); + + for (; it != sourceBuddies.end(); ++it) { + ((DeviceSoapySDRShared*) (*it)->getBuddySharedPtr())->m_source->setThread(0); + } + } + else if (requestedChannel == nbOriginalChannels - 1) // remove last MI channel => reduce by deleting and re-creating the thread + { + qDebug("SoapySDRInput::stop: MI mode. Reduce by deleting and re-creating the thread"); + soapySDRInputThread->stopWork(); + SampleSinkFifo **fifos = new SampleSinkFifo*[nbOriginalChannels-1]; + unsigned int *log2Decims = new unsigned int[nbOriginalChannels-1]; + int *fcPoss = new int[nbOriginalChannels-1]; + int highestActiveChannelIndex = -1; + + for (int i = 0; i < nbOriginalChannels-1; i++) // save original FIFO references and get the channel with highest index + { + fifos[i] = soapySDRInputThread->getFifo(i); + + if ((soapySDRInputThread->getFifo(i) != 0) && (i > highestActiveChannelIndex)) { + highestActiveChannelIndex = i; + } + + log2Decims[i] = soapySDRInputThread->getLog2Decimation(i); + fcPoss[i] = soapySDRInputThread->getFcPos(i); + } + + delete soapySDRInputThread; + m_thread = 0; + + if (highestActiveChannelIndex >= 0) // there is at least one channel still active + { + soapySDRInputThread = new SoapySDRInputThread(m_deviceShared.m_device, highestActiveChannelIndex+1); + m_thread = soapySDRInputThread; // take ownership + + for (int i = 0; i < highestActiveChannelIndex; i++) // restore original FIFO references + { + soapySDRInputThread->setFifo(i, fifos[i]); + soapySDRInputThread->setLog2Decimation(i, log2Decims[i]); + soapySDRInputThread->setFcPos(i, fcPoss[i]); + } + } + else + { + qDebug("SoapySDRInput::stop: do not re-create thread as there are no more FIFOs active"); + } + + // remove old thread address from buddies (reset in all buddies). The address being held only in the owning source. + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + std::vector::const_iterator it = sourceBuddies.begin(); + + for (; it != sourceBuddies.end(); ++it) { + ((DeviceSoapySDRShared*) (*it)->getBuddySharedPtr())->m_source->setThread(0); + } + + if (highestActiveChannelIndex >= 0) + { + qDebug("SoapySDRInput::stop: restarting the thread"); + soapySDRInputThread->startWork(); + } + } + else // remove channel from existing thread + { + qDebug("SoapySDRInput::stop: MI mode. Not changing MI configuration. Just remove FIFO reference"); + soapySDRInputThread->setFifo(requestedChannel, 0); // remove FIFO + } + + m_running = false; +} + +QByteArray SoapySDRInput::serialize() const +{ + SimpleSerializer s(1); + return s.final(); +} + +bool SoapySDRInput::deserialize(const QByteArray& data __attribute__((unused))) +{ + return false; +} + +const QString& SoapySDRInput::getDeviceDescription() const +{ + return m_deviceDescription; +} + +int SoapySDRInput::getSampleRate() const +{ + int rate = m_settings.m_devSampleRate; + return (rate / (1<push(messageToGUI); + } +} + +bool SoapySDRInput::setDeviceCenterFrequency(SoapySDR::Device *dev, int requestedChannel, quint64 freq_hz, int loPpmTenths) +{ + qint64 df = ((qint64)freq_hz * loPpmTenths) / 10000000LL; + freq_hz += df; + + try + { + dev->setFrequency(SOAPY_SDR_RX, + requestedChannel, + m_deviceShared.m_deviceParams->getRxChannelMainTunableElementName(requestedChannel), + freq_hz); + qDebug("SoapySDRInput::setDeviceCenterFrequency: setFrequency(%llu)", freq_hz); + return true; + } + catch (const std::exception &ex) + { + qCritical("SoapySDRInput::applySettings: could not set frequency: %llu: %s", freq_hz, ex.what()); + return false; + } +} + +void SoapySDRInput::updateGains(SoapySDR::Device *dev, int requestedChannel, SoapySDRInputSettings& settings) +{ + if (dev == 0) { + return; + } + + settings.m_globalGain = round(dev->getGain(SOAPY_SDR_RX, requestedChannel)); + + for (const auto &name : settings.m_individualGains.keys()) { + settings.m_individualGains[name] = dev->getGain(SOAPY_SDR_RX, requestedChannel, name.toStdString()); + } +} + +bool SoapySDRInput::handleMessage(const Message& message __attribute__((unused))) +{ + if (MsgConfigureSoapySDRInput::match(message)) + { + MsgConfigureSoapySDRInput& conf = (MsgConfigureSoapySDRInput&) message; + qDebug() << "SoapySDRInput::handleMessage: MsgConfigureSoapySDRInput"; + + if (!applySettings(conf.getSettings(), conf.getForce())) { + qDebug("SoapySDRInput::handleMessage: MsgConfigureSoapySDRInput config error"); + } + + return true; + } + else if (MsgFileRecord::match(message)) + { + MsgFileRecord& conf = (MsgFileRecord&) message; + qDebug() << "SoapySDRInput::handleMessage: MsgFileRecord: " << conf.getStartStop(); + + if (conf.getStartStop()) + { + if (m_settings.m_fileRecordName.size() != 0) { + m_fileSink->setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); + } + + m_fileSink->startRecording(); + } + else + { + m_fileSink->stopRecording(); + } + + return true; + } + else if (MsgStartStop::match(message)) + { + MsgStartStop& cmd = (MsgStartStop&) message; + qDebug() << "SoapySDRInput::handleMessage: MsgStartStop: " << (cmd.getStartStop() ? "start" : "stop"); + + if (cmd.getStartStop()) + { + if (m_deviceAPI->initAcquisition()) + { + m_deviceAPI->startAcquisition(); + } + } + else + { + m_deviceAPI->stopAcquisition(); + } + + return true; + } + else if (DeviceSoapySDRShared::MsgReportBuddyChange::match(message)) + { + int requestedChannel = m_deviceAPI->getItemIndex(); + DeviceSoapySDRShared::MsgReportBuddyChange& report = (DeviceSoapySDRShared::MsgReportBuddyChange&) message; + SoapySDRInputSettings settings = m_settings; + settings.m_fcPos = (SoapySDRInputSettings::fcPos_t) report.getFcPos(); + //bool fromRxBuddy = report.getRxElseTx(); + + double centerFrequency = m_deviceShared.m_device->getFrequency( + SOAPY_SDR_RX, + requestedChannel, + m_deviceShared.m_deviceParams->getRxChannelMainTunableElementName(requestedChannel)); + + settings.m_centerFrequency = round(centerFrequency/1000.0) * 1000; + settings.m_devSampleRate = round(m_deviceShared.m_device->getSampleRate(SOAPY_SDR_RX, requestedChannel)); + settings.m_bandwidth = round(m_deviceShared.m_device->getBandwidth(SOAPY_SDR_RX, requestedChannel)); + + SoapySDRInputThread *inputThread = findThread(); + + if (inputThread) + { + inputThread->setFcPos(requestedChannel, (int) settings.m_fcPos); + } + + m_settings = settings; + + // propagate settings to GUI if any + if (getMessageQueueToGUI()) + { + MsgConfigureSoapySDRInput *reportToGUI = MsgConfigureSoapySDRInput::create(m_settings, false); + getMessageQueueToGUI()->push(reportToGUI); + } + + return true; + } + else + { + return false; + } +} + +bool SoapySDRInput::applySettings(const SoapySDRInputSettings& settings, bool force) +{ + bool forwardChangeOwnDSP = false; + bool forwardChangeToBuddies = false; + bool globalGainChanged = false; + bool individualGainsChanged = false; + + SoapySDR::Device *dev = m_deviceShared.m_device; + SoapySDRInputThread *inputThread = findThread(); + int requestedChannel = m_deviceAPI->getItemIndex(); + qint64 xlatedDeviceCenterFrequency = settings.m_centerFrequency; + xlatedDeviceCenterFrequency -= settings.m_transverterMode ? settings.m_transverterDeltaFrequency : 0; + xlatedDeviceCenterFrequency = xlatedDeviceCenterFrequency < 0 ? 0 : xlatedDeviceCenterFrequency; + + if ((m_settings.m_softDCCorrection != settings.m_softDCCorrection) || + (m_settings.m_softIQCorrection != settings.m_softIQCorrection) || force) + { + m_deviceAPI->configureCorrections(settings.m_softDCCorrection, settings.m_softIQCorrection); + } + + if ((m_settings.m_devSampleRate != settings.m_devSampleRate) || force) + { + forwardChangeOwnDSP = true; + forwardChangeToBuddies = true; + + if (dev != 0) + { + try + { + dev->setSampleRate(SOAPY_SDR_RX, requestedChannel, settings.m_devSampleRate); + qDebug() << "SoapySDRInput::applySettings: setSampleRate OK: " << settings.m_devSampleRate; + + if (inputThread) + { + bool wasRunning = inputThread->isRunning(); + inputThread->stopWork(); + inputThread->setSampleRate(settings.m_devSampleRate); + + if (wasRunning) { + inputThread->startWork(); + } + } + } + catch (const std::exception &ex) + { + qCritical("SoapySDRInput::applySettings: could not set sample rate: %d: %s", + settings.m_devSampleRate, ex.what()); + } + } + } + + if ((m_settings.m_fcPos != settings.m_fcPos) || force) + { + if (inputThread != 0) + { + inputThread->setFcPos(requestedChannel, (int) settings.m_fcPos); + qDebug() << "SoapySDRInput::applySettings: set fc pos (enum) to " << (int) settings.m_fcPos; + } + } + + if ((m_settings.m_log2Decim != settings.m_log2Decim) || force) + { + forwardChangeOwnDSP = true; + SoapySDRInputThread *inputThread = findThread(); + + if (inputThread != 0) + { + inputThread->setLog2Decimation(requestedChannel, settings.m_log2Decim); + qDebug() << "SoapySDRInput::applySettings: set decimation to " << (1<setAntenna(SOAPY_SDR_RX, requestedChannel, settings.m_antenna.toStdString()); + qDebug("SoapySDRInput::applySettings: set antenna to %s", settings.m_antenna.toStdString().c_str()); + } + catch (const std::exception &ex) + { + qCritical("SoapySDRInput::applySettings: cannot set antenna to %s: %s", + settings.m_antenna.toStdString().c_str(), ex.what()); + } + } + } + + if ((m_settings.m_bandwidth != settings.m_bandwidth) || force) + { + forwardChangeToBuddies = true; + + if (dev != 0) + { + try + { + dev->setBandwidth(SOAPY_SDR_RX, requestedChannel, settings.m_bandwidth); + qDebug("SoapySDRInput::applySettings: bandwidth set to %u", settings.m_bandwidth); + } + catch (const std::exception &ex) + { + qCritical("SoapySDRInput::applySettings: cannot set bandwidth to %u: %s", + settings.m_bandwidth, ex.what()); + } + } + } + + for (const auto &oname : m_settings.m_tunableElements.keys()) + { + auto nvalue = settings.m_tunableElements.find(oname); + + if (nvalue != settings.m_tunableElements.end() && (m_settings.m_tunableElements[oname] != *nvalue)) + { + if (dev != 0) + { + try + { + dev->setFrequency(SOAPY_SDR_RX, requestedChannel, oname.toStdString(), *nvalue); + qDebug("SoapySDRInput::applySettings: tunable element %s frequency set to %lf", + oname.toStdString().c_str(), *nvalue); + } + catch (const std::exception &ex) + { + qCritical("SoapySDRInput::applySettings: cannot set tunable element %s to %lf: %s", + oname.toStdString().c_str(), *nvalue, ex.what()); + } + } + + m_settings.m_tunableElements[oname] = *nvalue; + } + } + + if ((m_settings.m_globalGain != settings.m_globalGain) || force) + { + if (dev != 0) + { + try + { + dev->setGain(SOAPY_SDR_RX, requestedChannel, settings.m_globalGain); + qDebug("SoapySDRInput::applySettings: set global gain to %d", settings.m_globalGain); + globalGainChanged = true; + } + catch (const std::exception &ex) + { + qCritical("SoapySDRInput::applySettings: cannot set global gain to %d: %s", + settings.m_globalGain, ex.what()); + } + } + } + + for (const auto &oname : m_settings.m_individualGains.keys()) + { + auto nvalue = settings.m_individualGains.find(oname); + + if (nvalue != settings.m_individualGains.end() && ((m_settings.m_individualGains[oname] != *nvalue) || force)) + { + if (dev != 0) + { + try + { + dev->setGain(SOAPY_SDR_RX, requestedChannel, oname.toStdString(), *nvalue); + qDebug("SoapySDRInput::applySettings: individual gain %s set to %lf", + oname.toStdString().c_str(), *nvalue); + individualGainsChanged = true; + } + catch (const std::exception &ex) + { + qCritical("SoapySDRInput::applySettings: cannot set individual gain %s to %lf: %s", + oname.toStdString().c_str(), *nvalue, ex.what()); + } + } + + m_settings.m_individualGains[oname] = *nvalue; + } + } + + if ((m_settings.m_autoGain != settings.m_autoGain) || force) + { + if (dev != 0) + { + try + { + dev->setGainMode(SOAPY_SDR_RX, requestedChannel, settings.m_autoGain); + qDebug("SoapySDRInput::applySettings: %s AGC", settings.m_autoGain ? "set" : "unset"); + } + catch (const std::exception &ex) + { + qCritical("SoapySDRInput::applySettings: cannot %s AGC", settings.m_autoGain ? "set" : "unset"); + } + } + } + + if ((m_settings.m_autoDCCorrection != settings.m_autoDCCorrection) || force) + { + if ((dev != 0) && hasDCAutoCorrection()) + { + try + { + dev->setDCOffsetMode(SOAPY_SDR_RX, requestedChannel, settings.m_autoDCCorrection); + qDebug("SoapySDRInput::applySettings: %s DC auto correction", settings.m_autoDCCorrection ? "set" : "unset"); + } + catch (const std::exception &ex) + { + qCritical("SoapySDRInput::applySettings: cannot %s DC auto correction", settings.m_autoDCCorrection ? "set" : "unset"); + } + } + } + + if ((m_settings.m_dcCorrection != settings.m_dcCorrection) || force) + { + if ((dev != 0) && hasDCCorrectionValue()) + { + try + { + dev->setDCOffset(SOAPY_SDR_RX, requestedChannel, settings.m_dcCorrection); + qDebug("SoapySDRInput::applySettings: DC offset correction set to (%lf, %lf)", settings.m_dcCorrection.real(), settings.m_dcCorrection.imag()); + } + catch (const std::exception &ex) + { + qCritical("SoapySDRInput::applySettings: cannot set DC offset correction to (%lf, %lf)", settings.m_dcCorrection.real(), settings.m_dcCorrection.imag()); + } + } + } + + if ((m_settings.m_iqCorrection != settings.m_iqCorrection) || force) + { + if ((dev != 0) && hasIQCorrectionValue()) + { + try + { + dev->setIQBalance(SOAPY_SDR_RX, requestedChannel, settings.m_iqCorrection); + qDebug("SoapySDRInput::applySettings: IQ balance correction set to (%lf, %lf)", settings.m_iqCorrection.real(), settings.m_iqCorrection.imag()); + } + catch (const std::exception &ex) + { + qCritical("SoapySDRInput::applySettings: cannot set IQ balance correction to (%lf, %lf)", settings.m_iqCorrection.real(), settings.m_iqCorrection.imag()); + } + } + } + + if (forwardChangeOwnDSP) + { + int sampleRate = settings.m_devSampleRate/(1<handleMessage(*notif); // forward to file sink + m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif); + } + + if (forwardChangeToBuddies) + { + // send to source buddies + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + const std::vector& sinkBuddies = m_deviceAPI->getSinkBuddies(); + + for (const auto &itSource : sourceBuddies) + { + DeviceSoapySDRShared::MsgReportBuddyChange *report = DeviceSoapySDRShared::MsgReportBuddyChange::create( + settings.m_centerFrequency, + settings.m_LOppmTenths, + (int) settings.m_fcPos, + settings.m_devSampleRate, + true); + itSource->getSampleSourceInputMessageQueue()->push(report); + } + + for (const auto &itSink : sinkBuddies) + { + DeviceSoapySDRShared::MsgReportBuddyChange *report = DeviceSoapySDRShared::MsgReportBuddyChange::create( + settings.m_centerFrequency, + settings.m_LOppmTenths, + (int) settings.m_fcPos, + settings.m_devSampleRate, + true); + itSink->getSampleSinkInputMessageQueue()->push(report); + } + } + + m_settings = settings; + + if (globalGainChanged || individualGainsChanged) + { + if (dev) { + updateGains(dev, requestedChannel, m_settings); + } + + if (getMessageQueueToGUI()) + { + MsgReportGainChange *report = MsgReportGainChange::create(m_settings, individualGainsChanged, globalGainChanged); + getMessageQueueToGUI()->push(report); + } + } + + qDebug() << "SoapySDRInput::applySettings: " + << " m_transverterMode: " << m_settings.m_transverterMode + << " m_transverterDeltaFrequency: " << m_settings.m_transverterDeltaFrequency + << " m_centerFrequency: " << m_settings.m_centerFrequency << " Hz" + << " m_LOppmTenths: " << m_settings.m_LOppmTenths + << " m_log2Decim: " << m_settings.m_log2Decim + << " m_fcPos: " << m_settings.m_fcPos + << " m_devSampleRate: " << m_settings.m_devSampleRate + << " m_softDCCorrection: " << m_settings.m_softDCCorrection + << " m_softIQCorrection: " << m_settings.m_softIQCorrection + << " m_antenna: " << m_settings.m_antenna + << " m_bandwidth: " << m_settings.m_bandwidth + << " m_globalGain: " << m_settings.m_globalGain; + + return true; +} diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.h b/plugins/samplesource/soapysdrinput/soapysdrinput.h new file mode 100644 index 000000000..d18c5194e --- /dev/null +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.h @@ -0,0 +1,185 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUT_H_ +#define PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUT_H_ + +#include +#include +#include + +#include "soapysdr/devicesoapysdrshared.h" +#include "dsp/devicesamplesource.h" + +#include "soapysdrinputsettings.h" + +class DeviceSourceAPI; +class SoapySDRInputThread; +class FileRecord; + +namespace SoapySDR +{ + class Device; +} + +class SoapySDRInput : public DeviceSampleSource +{ +public: + class MsgConfigureSoapySDRInput : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const SoapySDRInputSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureSoapySDRInput* create(const SoapySDRInputSettings& settings, bool force) + { + return new MsgConfigureSoapySDRInput(settings, force); + } + + private: + SoapySDRInputSettings m_settings; + bool m_force; + + MsgConfigureSoapySDRInput(const SoapySDRInputSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgFileRecord : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getStartStop() const { return m_startStop; } + + static MsgFileRecord* create(bool startStop) { + return new MsgFileRecord(startStop); + } + + protected: + bool m_startStop; + + MsgFileRecord(bool startStop) : + Message(), + m_startStop(startStop) + { } + }; + + class MsgStartStop : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getStartStop() const { return m_startStop; } + + static MsgStartStop* create(bool startStop) { + return new MsgStartStop(startStop); + } + + protected: + bool m_startStop; + + MsgStartStop(bool startStop) : + Message(), + m_startStop(startStop) + { } + }; + + class MsgReportGainChange : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const SoapySDRInputSettings& getSettings() const { return m_settings; } + bool getGlobalGain() const { return m_globalGain; } + bool getIndividualGains() const { return m_individualGains; } + + static MsgReportGainChange* create(const SoapySDRInputSettings& settings, bool globalGain, bool individualGains) + { + return new MsgReportGainChange(settings, globalGain, individualGains); + } + + private: + SoapySDRInputSettings m_settings; + bool m_globalGain; + bool m_individualGains; + + MsgReportGainChange(const SoapySDRInputSettings& settings, bool globalGain, bool individualGains) : + Message(), + m_settings(settings), + m_globalGain(globalGain), + m_individualGains(individualGains) + { } + }; + + SoapySDRInput(DeviceSourceAPI *deviceAPI); + virtual ~SoapySDRInput(); + virtual void destroy(); + + virtual void init(); + virtual bool start(); + virtual void stop(); + SoapySDRInputThread *getThread() { return m_thread; } + void setThread(SoapySDRInputThread *thread) { m_thread = thread; } + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual void setMessageQueueToGUI(MessageQueue *queue) { m_guiMessageQueue = queue; } + virtual const QString& getDeviceDescription() const; + virtual int getSampleRate() const; + virtual quint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + + virtual bool handleMessage(const Message& message); + + void getFrequencyRange(uint64_t& min, uint64_t& max); + void getGlobalGainRange(int& min, int& max); + bool isAGCSupported(); + const std::vector& getAntennas(); + const SoapySDR::RangeList& getRateRanges(); + const SoapySDR::RangeList& getBandwidthRanges(); + int getAntennaIndex(const std::string& antenna); + const std::vector& getTunableElements(); + const std::vector& getIndividualGainsRanges(); + void initGainSettings(SoapySDRInputSettings& settings); + bool hasDCAutoCorrection(); + bool hasDCCorrectionValue(); + bool hasIQAutoCorrection() { return false; } // not in SoapySDR interface + bool hasIQCorrectionValue(); + +private: + DeviceSourceAPI *m_deviceAPI; + QMutex m_mutex; + SoapySDRInputSettings m_settings; + QString m_deviceDescription; + bool m_running; + SoapySDRInputThread *m_thread; + DeviceSoapySDRShared m_deviceShared; + FileRecord *m_fileSink; //!< File sink to record device I/Q output + + bool openDevice(); + void closeDevice(); + SoapySDRInputThread *findThread(); + void moveThreadToBuddy(); + bool applySettings(const SoapySDRInputSettings& settings, bool force = false); + bool setDeviceCenterFrequency(SoapySDR::Device *dev, int requestedChannel, quint64 freq_hz, int loPpmTenths); + void updateGains(SoapySDR::Device *dev, int requestedChannel, SoapySDRInputSettings& settings); +}; + + + +#endif /* PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUT_H_ */ diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp new file mode 100644 index 000000000..b8c387d76 --- /dev/null +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.cpp @@ -0,0 +1,765 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "device/devicesourceapi.h" +#include "device/deviceuiset.h" +#include "util/simpleserializer.h" +#include "gui/glspectrum.h" +#include "soapygui/discreterangegui.h" +#include "soapygui/intervalrangegui.h" +#include "soapygui/stringrangegui.h" +#include "soapygui/dynamicitemsettinggui.h" +#include "soapygui/intervalslidergui.h" +#include "soapygui/complexfactorgui.h" + +#include "ui_soapysdrinputgui.h" +#include "soapysdrinputgui.h" + +SoapySDRInputGui::SoapySDRInputGui(DeviceUISet *deviceUISet, QWidget* parent) : + QWidget(parent), + ui(new Ui::SoapySDRInputGui), + m_deviceUISet(deviceUISet), + m_forceSettings(true), + m_doApplySettings(true), + m_sampleSource(0), + m_sampleRate(0), + m_deviceCenterFrequency(0), + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted), + m_antennas(0), + m_sampleRateGUI(0), + m_bandwidthGUI(0), + m_gainSliderGUI(0), + m_autoGain(0), + m_dcCorrectionGUI(0), + m_iqCorrectionGUI(0), + m_autoDCCorrection(0), + m_autoIQCorrection(0) +{ + m_sampleSource = (SoapySDRInput*) m_deviceUISet->m_deviceSourceAPI->getSampleSource(); + ui->setupUi(this); + + ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); + uint64_t f_min, f_max; + m_sampleSource->getFrequencyRange(f_min, f_max); + ui->centerFrequency->setValueRange(7, f_min/1000, f_max/1000); + + createCorrectionsControl(); + createAntennasControl(m_sampleSource->getAntennas()); + createRangesControl(&m_sampleRateGUI, m_sampleSource->getRateRanges(), "SR", "S/s"); + createRangesControl(&m_bandwidthGUI, m_sampleSource->getBandwidthRanges(), "BW", "Hz"); + createTunableElementsControl(m_sampleSource->getTunableElements()); + createGlobalGainControl(); + createIndividualGainsControl(m_sampleSource->getIndividualGainsRanges()); + m_sampleSource->initGainSettings(m_settings); + + if (m_sampleRateGUI) { + connect(m_sampleRateGUI, SIGNAL(valueChanged(double)), this, SLOT(sampleRateChanged(double))); + } + if (m_bandwidthGUI) { + connect(m_bandwidthGUI, SIGNAL(valueChanged(double)), this, SLOT(bandwidthChanged(double))); + } + + connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); + connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); + m_statusTimer.start(500); + + displaySettings(); + + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); + + sendSettings(); +} + +SoapySDRInputGui::~SoapySDRInputGui() +{ + delete ui; +} + +void SoapySDRInputGui::destroy() +{ + delete this; +} + +void SoapySDRInputGui::createRangesControl( + ItemSettingGUI **settingGUI, + const SoapySDR::RangeList& rangeList, + const QString& text, + const QString& unit) +{ + if (rangeList.size() == 0) { // return early if the range list is empty + return; + } + + bool rangeDiscrete = true; // discretes values + bool rangeInterval = true; // intervals + + for (const auto &it : rangeList) + { + if (it.minimum() != it.maximum()) { + rangeDiscrete = false; + } else { + rangeInterval = false; + } + } + + if (rangeDiscrete) + { + DiscreteRangeGUI *rangeGUI = new DiscreteRangeGUI(this); + rangeGUI->setLabel(text); + rangeGUI->setUnits(QString("k%1").arg(unit)); + + for (const auto &it : rangeList) { + rangeGUI->addItem(QString("%1").arg(QString::number(it.minimum()/1000.0, 'f', 0)), it.minimum()); + } + + *settingGUI = rangeGUI; + QVBoxLayout *layout = (QVBoxLayout *) ui->scrollAreaWidgetContents->layout(); + layout->addWidget(rangeGUI); + } + else if (rangeInterval) + { + IntervalRangeGUI *rangeGUI = new IntervalRangeGUI(ui->scrollAreaWidgetContents); + rangeGUI->setLabel(text); + rangeGUI->setUnits(unit); + + for (const auto &it : rangeList) { + rangeGUI->addInterval(it.minimum(), it.maximum()); + } + + rangeGUI->reset(); + + *settingGUI = rangeGUI; + QVBoxLayout *layout = (QVBoxLayout *) ui->scrollAreaWidgetContents->layout(); + layout->addWidget(rangeGUI); + } +} + +void SoapySDRInputGui::createAntennasControl(const std::vector& antennaList) +{ + if (antennaList.size() == 0) { // return early if the antenna list is empty + return; + } + + m_antennas = new StringRangeGUI(this); + m_antennas->setLabel(QString("RF in")); + m_antennas->setUnits(QString("Port")); + + for (const auto &it : antennaList) { + m_antennas->addItem(QString(it.c_str()), it); + } + + QVBoxLayout *layout = (QVBoxLayout *) ui->scrollAreaWidgetContents->layout(); + layout->addWidget(m_antennas); + + connect(m_antennas, SIGNAL(valueChanged()), this, SLOT(antennasChanged())); +} + +void SoapySDRInputGui::createTunableElementsControl(const std::vector& tunableElementsList) +{ + if (tunableElementsList.size() <= 1) { // This list is created for other elements than the main one (RF) which is always at index 0 + return; + } + + std::vector::const_iterator it = tunableElementsList.begin() + 1; + + for (int i = 0; it != tunableElementsList.end(); ++it, i++) + { + if (it->m_ranges.size() == 0) { // skip empty ranges lists + continue; + } + + ItemSettingGUI *rangeGUI; + createRangesControl( + &rangeGUI, + it->m_ranges, + QString("%1 freq").arg(it->m_name.c_str()), + QString((it->m_name == "CORR") ? "ppm" : "Hz")); + DynamicItemSettingGUI *gui = new DynamicItemSettingGUI(rangeGUI, QString(it->m_name.c_str())); + m_tunableElementsGUIs.push_back(gui); + connect(m_tunableElementsGUIs.back(), SIGNAL(valueChanged(QString, double)), this, SLOT(tunableElementChanged(QString, double))); + } +} + +void SoapySDRInputGui::createGlobalGainControl() +{ + m_gainSliderGUI = new IntervalSliderGUI(this); + int min, max; + m_sampleSource->getGlobalGainRange(min, max); + m_gainSliderGUI->setInterval(min, max); + m_gainSliderGUI->setLabel(QString("Global gain")); + m_gainSliderGUI->setUnits(QString("")); + + QVBoxLayout *layout = (QVBoxLayout *) ui->scrollAreaWidgetContents->layout(); + + QFrame *line = new QFrame(this); + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Sunken); + layout->addWidget(line); + + if (m_sampleSource->isAGCSupported()) + { + m_autoGain = new QCheckBox(this); + m_autoGain->setText(QString("AGC")); + layout->addWidget(m_autoGain); + + connect(m_autoGain, SIGNAL(toggled(bool)), this, SLOT(autoGainChanged(bool))); + } + + layout->addWidget(m_gainSliderGUI); + + connect(m_gainSliderGUI, SIGNAL(valueChanged(double)), this, SLOT(globalGainChanged(double))); +} + +void SoapySDRInputGui::createIndividualGainsControl(const std::vector& individualGainsList) +{ + if (individualGainsList.size() == 0) { // Leave early if list of individual gains is empty + return; + } + + QVBoxLayout *layout = (QVBoxLayout *) ui->scrollAreaWidgetContents->layout(); + std::vector::const_iterator it = individualGainsList.begin(); + + for (int i = 0; it != individualGainsList.end(); ++it, i++) + { + IntervalSliderGUI *gainGUI = new IntervalSliderGUI(this); + gainGUI->setInterval(it->m_range.minimum(), it->m_range.maximum()); + gainGUI->setLabel(QString("%1 gain").arg(it->m_name.c_str())); + gainGUI->setUnits(QString("")); + DynamicItemSettingGUI *gui = new DynamicItemSettingGUI(gainGUI, QString(it->m_name.c_str())); + layout->addWidget(gainGUI); + m_individualGainsGUIs.push_back(gui); + connect(m_individualGainsGUIs.back(), SIGNAL(valueChanged(QString, double)), this, SLOT(individualGainChanged(QString, double))); + } +} + +void SoapySDRInputGui::createCorrectionsControl() +{ + QVBoxLayout *layout = (QVBoxLayout *) ui->scrollAreaWidgetContents->layout(); + + if (m_sampleSource->hasDCCorrectionValue()) // complex GUI + { + m_dcCorrectionGUI = new ComplexFactorGUI(this); + m_dcCorrectionGUI->setLabel(QString("DC corr")); + m_dcCorrectionGUI->setToolTip(QString("Hardware DC offset correction")); + m_dcCorrectionGUI->setAutomaticEnable(m_sampleSource->hasDCAutoCorrection()); + layout->addWidget(m_dcCorrectionGUI); + + connect(m_dcCorrectionGUI, SIGNAL(moduleChanged(double)), this, SLOT(dcCorrectionModuleChanged(double))); + connect(m_dcCorrectionGUI, SIGNAL(argumentChanged(double)), this, SLOT(dcCorrectionArgumentChanged(double))); + + if (m_sampleSource->hasDCAutoCorrection()) { + connect(m_dcCorrectionGUI, SIGNAL(automaticChanged(bool)), this, SLOT(autoDCCorrectionChanged(bool))); + } + } + else if (m_sampleSource->hasDCAutoCorrection()) // simple checkbox + { + m_autoDCCorrection = new QCheckBox(this); + m_autoDCCorrection->setText(QString("Auto DC corr")); + m_autoDCCorrection->setToolTip(QString("Automatic hardware DC offset correction")); + layout->addWidget(m_autoDCCorrection); + + connect(m_autoDCCorrection, SIGNAL(toggled(bool)), this, SLOT(autoDCCorrectionChanged(bool))); + } + + if (m_sampleSource->hasIQCorrectionValue()) // complex GUI + { + m_iqCorrectionGUI = new ComplexFactorGUI(this); + m_iqCorrectionGUI->setLabel(QString("IQ corr")); + m_iqCorrectionGUI->setToolTip(QString("Hardware IQ imbalance correction")); + m_iqCorrectionGUI->setAutomaticEnable(m_sampleSource->hasIQAutoCorrection()); + layout->addWidget(m_iqCorrectionGUI); + + connect(m_iqCorrectionGUI, SIGNAL(moduleChanged(double)), this, SLOT(iqCorrectionModuleChanged(double))); + connect(m_iqCorrectionGUI, SIGNAL(argumentChanged(double)), this, SLOT(iqCorrectionArgumentChanged(double))); + + if (m_sampleSource->hasIQAutoCorrection()) { + connect(m_iqCorrectionGUI, SIGNAL(automaticChanged(bool)), this, SLOT(autoIQCorrectionChanged(bool))); + } + } + else if (m_sampleSource->hasIQAutoCorrection()) // simple checkbox + { + m_autoIQCorrection = new QCheckBox(this); + m_autoIQCorrection->setText(QString("Auto IQ corr")); + m_autoIQCorrection->setToolTip(QString("Automatic hardware IQ imbalance correction")); + layout->addWidget(m_autoIQCorrection); + + connect(m_autoIQCorrection, SIGNAL(toggled(bool)), this, SLOT(autoIQCorrectionChanged(bool))); + } +} + +void SoapySDRInputGui::setName(const QString& name) +{ + setObjectName(name); +} + +QString SoapySDRInputGui::getName() const +{ + return objectName(); +} + +void SoapySDRInputGui::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + sendSettings(); +} + +qint64 SoapySDRInputGui::getCenterFrequency() const +{ + return m_settings.m_centerFrequency; +} + +void SoapySDRInputGui::setCenterFrequency(qint64 centerFrequency) +{ + m_settings.m_centerFrequency = centerFrequency; + displaySettings(); + sendSettings(); +} + +QByteArray SoapySDRInputGui::serialize() const +{ + return m_settings.serialize(); +} + +bool SoapySDRInputGui::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) { + displaySettings(); + m_forceSettings = true; + sendSettings(); + return true; + } else { + resetToDefaults(); + return false; + } +} + +bool SoapySDRInputGui::handleMessage(const Message& message) +{ + if (SoapySDRInput::MsgConfigureSoapySDRInput::match(message)) + { + const SoapySDRInput::MsgConfigureSoapySDRInput& cfg = (SoapySDRInput::MsgConfigureSoapySDRInput&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + + return true; + } + else if (SoapySDRInput::MsgReportGainChange::match(message)) + { + const SoapySDRInput::MsgReportGainChange& report = (SoapySDRInput::MsgReportGainChange&) message; + const SoapySDRInputSettings& gainSettings = report.getSettings(); + + if (report.getGlobalGain()) { + m_settings.m_globalGain = gainSettings.m_globalGain; + } + if (report.getIndividualGains()) { + m_settings.m_individualGains = gainSettings.m_individualGains; + } + + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + + return true; + } + else if (SoapySDRInput::MsgStartStop::match(message)) + { + SoapySDRInput::MsgStartStop& notif = (SoapySDRInput::MsgStartStop&) message; + blockApplySettings(true); + ui->startStop->setChecked(notif.getStartStop()); + blockApplySettings(false); + + return true; + } + else + { + return false; + } +} + +void SoapySDRInputGui::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + qDebug("SoapySDRInputGui::handleInputMessages: message: %s", message->getIdentifier()); + + if (DSPSignalNotification::match(*message)) + { + DSPSignalNotification* notif = (DSPSignalNotification*) message; + m_sampleRate = notif->getSampleRate(); + m_deviceCenterFrequency = notif->getCenterFrequency(); + qDebug("SoapySDRInputGui::handleInputMessages: DSPSignalNotification: SampleRate:%d, CenterFrequency:%llu", notif->getSampleRate(), notif->getCenterFrequency()); + updateSampleRateAndFrequency(); + + delete message; + } + else + { + if (handleMessage(*message)) + { + delete message; + } + } + } +} + +void SoapySDRInputGui::antennasChanged() +{ + const std::string& antennaStr = m_antennas->getCurrentValue(); + m_settings.m_antenna = QString(antennaStr.c_str()); + + sendSettings(); +} + +void SoapySDRInputGui::sampleRateChanged(double sampleRate) +{ + m_settings.m_devSampleRate = sampleRate; + sendSettings(); +} + +void SoapySDRInputGui::bandwidthChanged(double bandwidth) +{ + m_settings.m_bandwidth = bandwidth; + sendSettings(); +} + +void SoapySDRInputGui::tunableElementChanged(QString name, double value) +{ + m_settings.m_tunableElements[name] = value; + sendSettings(); +} + +void SoapySDRInputGui::globalGainChanged(double gain) +{ + m_settings.m_globalGain = round(gain); + sendSettings(); +} + +void SoapySDRInputGui::autoGainChanged(bool set) +{ + m_settings.m_autoGain = set; + sendSettings(); +} + +void SoapySDRInputGui::individualGainChanged(QString name, double value) +{ + m_settings.m_individualGains[name] = value; + sendSettings(); +} + +void SoapySDRInputGui::autoDCCorrectionChanged(bool set) +{ + m_settings.m_autoDCCorrection = set; + sendSettings(); +} + +void SoapySDRInputGui::autoIQCorrectionChanged(bool set) +{ + m_settings.m_autoIQCorrection = set; + sendSettings(); +} + +void SoapySDRInputGui::dcCorrectionModuleChanged(double value) +{ + std::complex dcCorrection = std::polar(value, arg(m_settings.m_dcCorrection)); + m_settings.m_dcCorrection = dcCorrection; + sendSettings(); +} + +void SoapySDRInputGui::dcCorrectionArgumentChanged(double value) +{ + double angleInRadians = (value / 180.0) * M_PI; + std::complex dcCorrection = std::polar(abs(m_settings.m_dcCorrection), angleInRadians); + m_settings.m_dcCorrection = dcCorrection; + sendSettings(); +} + +void SoapySDRInputGui::iqCorrectionModuleChanged(double value) +{ + std::complex iqCorrection = std::polar(value, arg(m_settings.m_iqCorrection)); + m_settings.m_iqCorrection = iqCorrection; + sendSettings(); +} + +void SoapySDRInputGui::iqCorrectionArgumentChanged(double value) +{ + double angleInRadians = (value / 180.0) * M_PI; + std::complex iqCorrection = std::polar(abs(m_settings.m_iqCorrection), angleInRadians); + m_settings.m_iqCorrection = iqCorrection; + sendSettings(); +} + +void SoapySDRInputGui::on_centerFrequency_changed(quint64 value) +{ + m_settings.m_centerFrequency = value * 1000; + sendSettings(); +} + +void SoapySDRInputGui::on_dcOffset_toggled(bool checked) +{ + m_settings.m_softDCCorrection = checked; + sendSettings(); +} + +void SoapySDRInputGui::on_iqImbalance_toggled(bool checked) +{ + m_settings.m_softIQCorrection = checked; + sendSettings(); +} + +void SoapySDRInputGui::on_decim_currentIndexChanged(int index) +{ + if ((index <0) || (index > 6)) + return; + m_settings.m_log2Decim = index; + sendSettings(); +} + +void SoapySDRInputGui::on_fcPos_currentIndexChanged(int index) +{ + if (index == 0) { + m_settings.m_fcPos = SoapySDRInputSettings::FC_POS_INFRA; + sendSettings(); + } else if (index == 1) { + m_settings.m_fcPos = SoapySDRInputSettings::FC_POS_SUPRA; + sendSettings(); + } else if (index == 2) { + m_settings.m_fcPos = SoapySDRInputSettings::FC_POS_CENTER; + sendSettings(); + } +} + +void SoapySDRInputGui::on_transverter_clicked() +{ + m_settings.m_transverterMode = ui->transverter->getDeltaFrequencyAcive(); + m_settings.m_transverterDeltaFrequency = ui->transverter->getDeltaFrequency(); + qDebug("SoapySDRInputGui::on_transverter_clicked: %lld Hz %s", m_settings.m_transverterDeltaFrequency, m_settings.m_transverterMode ? "on" : "off"); + updateFrequencyLimits(); + setCenterFrequencySetting(ui->centerFrequency->getValueNew()); + sendSettings(); +} + +void SoapySDRInputGui::on_LOppm_valueChanged(int value) +{ + ui->LOppmText->setText(QString("%1").arg(QString::number(value/10.0, 'f', 1))); + m_settings.m_LOppmTenths = value; + sendSettings(); +} + +void SoapySDRInputGui::on_startStop_toggled(bool checked) +{ + if (m_doApplySettings) + { + SoapySDRInput::MsgStartStop *message = SoapySDRInput::MsgStartStop::create(checked); + m_sampleSource->getInputMessageQueue()->push(message); + } +} + +void SoapySDRInputGui::on_record_toggled(bool checked) +{ + if (checked) { + ui->record->setStyleSheet("QToolButton { background-color : red; }"); + } else { + ui->record->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + } + + SoapySDRInput::MsgFileRecord* message = SoapySDRInput::MsgFileRecord::create(checked); + m_sampleSource->getInputMessageQueue()->push(message); +} + +void SoapySDRInputGui::displaySettings() +{ + blockApplySettings(true); + + ui->centerFrequency->setValue(m_settings.m_centerFrequency / 1000); + + if (m_antennas) { + qDebug("SoapySDRInputGui::displaySettings: m_antenna: %s", m_settings.m_antenna.toStdString().c_str()); + m_antennas->setValue(m_settings.m_antenna.toStdString()); + } + if (m_sampleRateGUI) { + m_sampleRateGUI->setValue(m_settings.m_devSampleRate); + } + if (m_bandwidthGUI) { + m_bandwidthGUI->setValue(m_settings.m_bandwidth); + } + if (m_gainSliderGUI) { + m_gainSliderGUI->setValue(m_settings.m_globalGain); + } + if (m_autoGain) { + m_autoGain->setChecked(m_settings.m_autoGain); + } + + ui->dcOffset->setChecked(m_settings.m_softDCCorrection); + ui->iqImbalance->setChecked(m_settings.m_softIQCorrection); + + ui->decim->setCurrentIndex(m_settings.m_log2Decim); + ui->fcPos->setCurrentIndex((int) m_settings.m_fcPos); + + ui->LOppm->setValue(m_settings.m_LOppmTenths); + ui->LOppmText->setText(QString("%1").arg(QString::number(m_settings.m_LOppmTenths/10.0, 'f', 1))); + + displayTunableElementsControlSettings(); + displayIndividualGainsControlSettings(); + displayCorrectionsSettings(); + + blockApplySettings(false); +} + +void SoapySDRInputGui::displayTunableElementsControlSettings() +{ + for (const auto &it : m_tunableElementsGUIs) + { + QMap::const_iterator elIt = m_settings.m_tunableElements.find(it->getName()); + + if (elIt != m_settings.m_tunableElements.end()) { + it->setValue(*elIt); + } + } +} + +void SoapySDRInputGui::displayIndividualGainsControlSettings() +{ + for (const auto &it : m_individualGainsGUIs) + { + QMap::const_iterator elIt = m_settings.m_individualGains.find(it->getName()); + + if (elIt != m_settings.m_individualGains.end()) { + it->setValue(*elIt); + } + } +} + +void SoapySDRInputGui::displayCorrectionsSettings() +{ + if (m_dcCorrectionGUI) + { + m_dcCorrectionGUI->setAutomatic(m_settings.m_autoDCCorrection); + m_dcCorrectionGUI->setModule(abs(m_settings.m_dcCorrection)); + m_dcCorrectionGUI->setArgument(arg(m_settings.m_dcCorrection)*(180.0/M_PI)); + } + + if (m_iqCorrectionGUI) + { + m_iqCorrectionGUI->setAutomatic(m_settings.m_autoIQCorrection); + m_iqCorrectionGUI->setModule(abs(m_settings.m_iqCorrection)); + m_iqCorrectionGUI->setArgument(arg(m_settings.m_iqCorrection)*(180.0/M_PI)); + } + + if (m_autoDCCorrection) { + m_autoDCCorrection->setChecked(m_settings.m_autoDCCorrection); + } + + if (m_autoIQCorrection) { + m_autoIQCorrection->setChecked(m_settings.m_autoIQCorrection); + } +} + +void SoapySDRInputGui::sendSettings() +{ + if (!m_updateTimer.isActive()) { + m_updateTimer.start(100); + } +} + +void SoapySDRInputGui::updateSampleRateAndFrequency() +{ + m_deviceUISet->getSpectrum()->setSampleRate(m_sampleRate); + m_deviceUISet->getSpectrum()->setCenterFrequency(m_deviceCenterFrequency); + ui->deviceRateText->setText(tr("%1k").arg(QString::number(m_sampleRate / 1000.0f, 'g', 5))); +} + +void SoapySDRInputGui::updateFrequencyLimits() +{ + // values in kHz + uint64_t f_min, f_max; + qint64 deltaFrequency = m_settings.m_transverterMode ? m_settings.m_transverterDeltaFrequency/1000 : 0; + m_sampleSource->getFrequencyRange(f_min, f_max); + qint64 minLimit = f_min/1000 + deltaFrequency; + qint64 maxLimit = f_max/1000 + deltaFrequency; + + minLimit = minLimit < 0 ? 0 : minLimit > 9999999 ? 9999999 : minLimit; + maxLimit = maxLimit < 0 ? 0 : maxLimit > 9999999 ? 9999999 : maxLimit; + + qDebug("SoapySDRInputGui::updateFrequencyLimits: delta: %lld min: %lld max: %lld", deltaFrequency, minLimit, maxLimit); + + ui->centerFrequency->setValueRange(7, minLimit, maxLimit); +} + +void SoapySDRInputGui::setCenterFrequencySetting(uint64_t kHzValue) +{ + int64_t centerFrequency = kHzValue*1000; + + m_settings.m_centerFrequency = centerFrequency < 0 ? 0 : (uint64_t) centerFrequency; + ui->centerFrequency->setToolTip(QString("Main center frequency in kHz (LO: %1 kHz)").arg(centerFrequency/1000)); +} + +void SoapySDRInputGui::blockApplySettings(bool block) +{ + m_doApplySettings = !block; +} + +void SoapySDRInputGui::updateHardware() +{ + if (m_doApplySettings) + { + qDebug() << "SoapySDRInputGui::updateHardware"; + SoapySDRInput::MsgConfigureSoapySDRInput* message = SoapySDRInput::MsgConfigureSoapySDRInput::create(m_settings, m_forceSettings); + m_sampleSource->getInputMessageQueue()->push(message); + m_forceSettings = false; + m_updateTimer.stop(); + } +} + +void SoapySDRInputGui::updateStatus() +{ + int state = m_deviceUISet->m_deviceSourceAPI->state(); + + if(m_lastEngineState != state) + { + switch(state) + { + case DSPDeviceSourceEngine::StNotStarted: + ui->startStop->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + break; + case DSPDeviceSourceEngine::StIdle: + ui->startStop->setStyleSheet("QToolButton { background-color : blue; }"); + break; + case DSPDeviceSourceEngine::StRunning: + ui->startStop->setStyleSheet("QToolButton { background-color : green; }"); + break; + case DSPDeviceSourceEngine::StError: + ui->startStop->setStyleSheet("QToolButton { background-color : red; }"); + QMessageBox::information(this, tr("Message"), m_deviceUISet->m_deviceSourceAPI->errorMessage()); + break; + default: + break; + } + + m_lastEngineState = state; + } +} + diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputgui.h b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h new file mode 100644 index 000000000..7f65de91a --- /dev/null +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.h @@ -0,0 +1,138 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUTGUI_H_ +#define PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUTGUI_H_ + +#include +#include + +#include "plugin/plugininstancegui.h" +#include "util/messagequeue.h" + +#include "soapysdrinput.h" + +class DeviceUISet; +class ItemSettingGUI; +class StringRangeGUI; +class DynamicItemSettingGUI; +class IntervalSliderGUI; +class QCheckBox; +class ComplexFactorGUI; + +namespace Ui { + class SoapySDRInputGui; +} + +class SoapySDRInputGui : public QWidget, public PluginInstanceGUI { + Q_OBJECT + +public: + explicit SoapySDRInputGui(DeviceUISet *deviceUISet, QWidget* parent = 0); + virtual ~SoapySDRInputGui(); + virtual void destroy(); + + void setName(const QString& name); + QString getName() const; + + virtual void resetToDefaults(); + virtual qint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + virtual bool handleMessage(const Message& message); + +private: + void createRangesControl( + ItemSettingGUI **settingGUI, + const SoapySDR::RangeList& rangeList, + const QString& text, + const QString& unit); + void createAntennasControl(const std::vector& antennaList); + void createTunableElementsControl(const std::vector& tunableElementsList); + void createGlobalGainControl(); + void createIndividualGainsControl(const std::vector& individualGainsList); + void createCorrectionsControl(); + + Ui::SoapySDRInputGui* ui; + + DeviceUISet* m_deviceUISet; + bool m_forceSettings; + bool m_doApplySettings; + SoapySDRInputSettings m_settings; + QTimer m_updateTimer; + QTimer m_statusTimer; + SoapySDRInput* m_sampleSource; + int m_sampleRate; + quint64 m_deviceCenterFrequency; //!< Center frequency in device + int m_lastEngineState; + MessageQueue m_inputMessageQueue; + + StringRangeGUI *m_antennas; + ItemSettingGUI *m_sampleRateGUI; + ItemSettingGUI *m_bandwidthGUI; + std::vector m_tunableElementsGUIs; + IntervalSliderGUI *m_gainSliderGUI; + std::vector m_individualGainsGUIs; + QCheckBox *m_autoGain; + ComplexFactorGUI *m_dcCorrectionGUI; + ComplexFactorGUI *m_iqCorrectionGUI; + QCheckBox *m_autoDCCorrection; + QCheckBox *m_autoIQCorrection; + + void displaySettings(); + void displayTunableElementsControlSettings(); + void displayIndividualGainsControlSettings(); + void displayCorrectionsSettings(); + void sendSettings(); + void updateSampleRateAndFrequency(); + void updateFrequencyLimits(); + void setCenterFrequencySetting(uint64_t kHzValue); + void blockApplySettings(bool block); + +private slots: + void handleInputMessages(); + void antennasChanged(); + void sampleRateChanged(double sampleRate); + void bandwidthChanged(double bandwidth); + void tunableElementChanged(QString name, double value); + void globalGainChanged(double gain); + void autoGainChanged(bool set); + void individualGainChanged(QString name, double value); + void autoDCCorrectionChanged(bool set); + void autoIQCorrectionChanged(bool set); + void dcCorrectionModuleChanged(double value); + void dcCorrectionArgumentChanged(double value); + void iqCorrectionModuleChanged(double value); + void iqCorrectionArgumentChanged(double value); + + void on_centerFrequency_changed(quint64 value); + void on_LOppm_valueChanged(int value); + void on_dcOffset_toggled(bool checked); + void on_iqImbalance_toggled(bool checked); + void on_decim_currentIndexChanged(int index); + void on_fcPos_currentIndexChanged(int index); + void on_transverter_clicked(); + void on_startStop_toggled(bool checked); + void on_record_toggled(bool checked); + void updateHardware(); + void updateStatus(); +}; + + + +#endif /* PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUTGUI_H_ */ diff --git a/plugins/samplesource/airspyhfi/airspyhfigui.ui b/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui similarity index 69% rename from plugins/samplesource/airspyhfi/airspyhfigui.ui rename to plugins/samplesource/soapysdrinput/soapysdrinputgui.ui index 18a142606..0bb9c3aec 100644 --- a/plugins/samplesource/airspyhfi/airspyhfigui.ui +++ b/plugins/samplesource/soapysdrinput/soapysdrinputgui.ui @@ -1,21 +1,15 @@ - AirspyHFIGui - + SoapySDRInputGui + 0 0 324 - 174 + 176 - - - 0 - 0 - - 320 @@ -24,12 +18,12 @@ - Sans Serif + Liberation Sans 9 - AirspyHF + SoapySDR @@ -135,7 +129,7 @@ - DejaVu Sans Mono + Liberation Mono 20 @@ -173,214 +167,77 @@
- - - - - LO ppm - - - - - - - Local Oscillator ppm correction - - - -100 - - - 100 - - - 1 - - - Qt::Horizontal - - - - - - - - 36 - 0 - - - - LO correction value (ppm) - - - -00.0 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 24 - 16777215 - - - - Rest LO ppm correction - - - R - - - - - - - - - - - Corr - - - - - - - DC offset and IQ correction options - - - - None - - - - - DC - - - - - DC+IQ - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Qt::Horizontal + + + 6 + + + 6 - - - - - + - Band + Auto - + + + Software DC block + + + DC + + + + + + + Software IQ correction + + + IQ + + + + + + + Fp + + + + + - 56 + 50 0 - Band select + Relative position of device center frequency - HF + Inf - VHF + Sup + + + + + Cen - - - - 0 - 0 - - - - S/R - - - - - - - - 60 - 0 - - - - - 60 - 16777215 - - - - Device sample rate in MS/s - - - - 0000 - - - - - - - - k - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - + Dec @@ -388,17 +245,14 @@ - + - 50 - 16777215 + 30 + 0 - Decimation factor - - - 0 + Software decimation factor @@ -430,8 +284,26 @@ 32 + + + 64 + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + @@ -440,9 +312,6 @@ 24 - - Transverter frequency translation dialog - X @@ -451,22 +320,88 @@ - + - - - Qt::Vertical + + + LO ppm - + + + + + + Local Oscillator software ppm correction + + + -1000 + + + 1000 + + + 1 + + + Qt::Horizontal + + + + + + - 20 - 40 + 40 + 0 - + + -100.0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + Qt::ScrollBarAsNeeded + + + true + + + + + 0 + 0 + 318 + 53 + + + + + 3 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputplugin.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputplugin.cpp new file mode 100644 index 000000000..f0e6ed867 --- /dev/null +++ b/plugins/samplesource/soapysdrinput/soapysdrinputplugin.cpp @@ -0,0 +1,134 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include "plugin/pluginapi.h" +#include "util/simpleserializer.h" +#include "device/devicesourceapi.h" +#include "soapysdr/devicesoapysdr.h" + +#include "soapysdrinputplugin.h" + +#ifdef SERVER_MODE +#include "soapysdrinput.h" +#else +#include "soapysdrinputgui.h" +#endif + +const PluginDescriptor SoapySDRInputPlugin::m_pluginDescriptor = { + QString("SoapySDR Input"), + QString("4.3.0"), + QString("(c) Edouard Griffiths, F4EXB"), + QString("https://github.com/f4exb/sdrangel"), + true, + QString("https://github.com/f4exb/sdrangel") +}; + +const QString SoapySDRInputPlugin::m_hardwareID = "SoapySDR"; +const QString SoapySDRInputPlugin::m_deviceTypeID = SOAPYSDRINPUT_DEVICE_TYPE_ID; + +SoapySDRInputPlugin::SoapySDRInputPlugin(QObject* parent) : + QObject(parent) +{ +} + +const PluginDescriptor& SoapySDRInputPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void SoapySDRInputPlugin::initPlugin(PluginAPI* pluginAPI) +{ + pluginAPI->registerSampleSource(m_deviceTypeID, this); +} + +PluginInterface::SamplingDevices SoapySDRInputPlugin::enumSampleSources() +{ + SamplingDevices result; + DeviceSoapySDR& deviceSoapySDR = DeviceSoapySDR::instance(); + const std::vector& devicesEnumeration = deviceSoapySDR.getDevicesEnumeration(); + qDebug("SoapySDRInputPlugin::enumSampleSources: %lu SoapySDR devices. Enumerate these with Rx channel(s):", devicesEnumeration.size()); + std::vector::const_iterator it = devicesEnumeration.begin(); + + for (int idev = 0; it != devicesEnumeration.end(); ++it, idev++) + { + unsigned int nbRxChannels = it->m_nbRx; + + for (unsigned int ichan = 0; ichan < nbRxChannels; ichan++) + { + QString displayedName(QString("SoapySDR[%1:%2] %3").arg(idev).arg(ichan).arg(it->m_label)); + QString serial(QString("%1-%2").arg(it->m_driverName).arg(it->m_sequence)); + qDebug("SoapySDRInputPlugin::enumSampleSources: device #%d (%s) serial %s channel %u", + idev, it->m_label.toStdString().c_str(), serial.toStdString().c_str(), ichan); + result.append(SamplingDevice(displayedName, + m_hardwareID, + m_deviceTypeID, + serial, + idev, + PluginInterface::SamplingDevice::PhysicalDevice, + true, + nbRxChannels, + ichan)); + } + } + + return result; +} + +#ifdef SERVER_MODE +PluginInstanceGUI* SoapySDRInputPlugin::createSampleSourcePluginInstanceGUI( + const QString& sourceId __attribute__((unused)), + QWidget **widget __attribute__((unused)), + DeviceUISet *deviceUISet __attribute__((unused))) +{ + return 0; +} +#else +PluginInstanceGUI* SoapySDRInputPlugin::createSampleSourcePluginInstanceGUI( + const QString& sourceId, + QWidget **widget, + DeviceUISet *deviceUISet) +{ + if(sourceId == m_deviceTypeID) + { + SoapySDRInputGui* gui = new SoapySDRInputGui(deviceUISet); + *widget = gui; + return gui; + } + else + { + return 0; + } +} +#endif + +DeviceSampleSource *SoapySDRInputPlugin::createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI) +{ + if (sourceId == m_deviceTypeID) + { + SoapySDRInput *input = new SoapySDRInput(deviceAPI); + return input; + } + else + { + return 0; + } +} + + + + + diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputplugin.h b/plugins/samplesource/soapysdrinput/soapysdrinputplugin.h new file mode 100644 index 000000000..c53acf12a --- /dev/null +++ b/plugins/samplesource/soapysdrinput/soapysdrinputplugin.h @@ -0,0 +1,56 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUTPLUGIN_H_ +#define PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUTPLUGIN_H_ + +#include +#include "plugin/plugininterface.h" + +class PluginAPI; +class DeviceSourceAPI; +class DeviceUISet; + +#define SOAPYSDRINPUT_DEVICE_TYPE_ID "sdrangel.samplesource.soapysdrinput" + +class SoapySDRInputPlugin : public QObject, public PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID SOAPYSDRINPUT_DEVICE_TYPE_ID) + +public: + explicit SoapySDRInputPlugin(QObject* parent = 0); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + virtual SamplingDevices enumSampleSources(); + virtual PluginInstanceGUI* createSampleSourcePluginInstanceGUI( + const QString& sourceId, + QWidget **widget, + DeviceUISet *deviceUISet); + virtual DeviceSampleSource* createSampleSourcePluginInstanceInput(const QString& sourceId, DeviceSourceAPI *deviceAPI); + + static const QString m_hardwareID; + static const QString m_deviceTypeID; + +private: + static const PluginDescriptor m_pluginDescriptor; +}; + + + +#endif /* PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUTPLUGIN_H_ */ diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp new file mode 100644 index 000000000..7cceae2c4 --- /dev/null +++ b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.cpp @@ -0,0 +1,144 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "util/simpleserializer.h" + +#include "soapysdrinputsettings.h" + +SoapySDRInputSettings::SoapySDRInputSettings() +{ + resetToDefaults(); +} + +void SoapySDRInputSettings::resetToDefaults() +{ + m_centerFrequency = 435000*1000; + m_LOppmTenths = 0; + m_devSampleRate = 1024000; + m_log2Decim = 0; + m_fcPos = FC_POS_CENTER; + m_softDCCorrection = false; + m_softIQCorrection = false; + m_transverterMode = false; + m_transverterDeltaFrequency = 0; + m_fileRecordName = ""; + m_antenna = "NONE"; + m_bandwidth = 1000000; + m_globalGain = 0; + m_autoGain = false; + m_autoDCCorrection = false; + m_autoIQCorrection = false; + m_dcCorrection = std::complex{0,0}; + m_iqCorrection = std::complex{0,0}; +} + +QByteArray SoapySDRInputSettings::serialize() const +{ + SimpleSerializer s(1); + + s.writeS32(1, m_devSampleRate); + s.writeU32(2, m_log2Decim); + s.writeS32(3, (int) m_fcPos); + s.writeBool(4, m_softDCCorrection); + s.writeBool(5, m_softIQCorrection); + s.writeS32(6, m_LOppmTenths); + s.writeBool(7, m_transverterMode); + s.writeS64(8, m_transverterDeltaFrequency); + s.writeString(9, m_antenna); + s.writeU32(10, m_bandwidth); + s.writeBlob(11, serializeNamedElementMap(m_tunableElements)); + s.writeS32(12, m_globalGain); + s.writeBlob(13, serializeNamedElementMap(m_individualGains)); + s.writeBool(14, m_autoGain); + s.writeBool(15, m_autoDCCorrection); + s.writeBool(16, m_autoIQCorrection); + s.writeDouble(17, m_dcCorrection.real()); + s.writeDouble(18, m_dcCorrection.imag()); + s.writeDouble(19, m_iqCorrection.real()); + s.writeDouble(20, m_iqCorrection.imag()); + + return s.final(); +} + +bool SoapySDRInputSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if (!d.isValid()) + { + resetToDefaults(); + return false; + } + + if (d.getVersion() == 1) + { + int intval; + QByteArray blob; + double realval, imagval; + + d.readS32(1, &m_devSampleRate, 1024000); + d.readU32(2, &m_log2Decim, 0); + d.readS32(3, &intval, (int) FC_POS_CENTER); + m_fcPos = (fcPos_t) intval; + d.readBool(4, &m_softDCCorrection, false); + d.readBool(5, &m_softIQCorrection, false); + d.readS32(6, &m_LOppmTenths, 0); + d.readBool(7, &m_transverterMode, false); + d.readS64(8, &m_transverterDeltaFrequency, 0); + d.readString(9, &m_antenna, "NONE"); + d.readU32(10, &m_bandwidth, 1000000); + d.readBlob(11, &blob); + deserializeNamedElementMap(blob, m_tunableElements); + d.readS32(12, &m_globalGain, 0); + d.readBlob(13, &blob); + deserializeNamedElementMap(blob, m_individualGains); + d.readBool(14, &m_autoGain, false); + d.readBool(15, &m_autoDCCorrection, false); + d.readBool(16, &m_autoIQCorrection, false); + d.readDouble(17, &realval, 0); + d.readDouble(18, &imagval, 0); + m_dcCorrection = std::complex{realval, imagval}; + d.readDouble(19, &realval, 0); + d.readDouble(20, &imagval, 0); + m_iqCorrection = std::complex{realval, imagval}; + + return true; + } + else + { + resetToDefaults(); + return false; + } +} + +QByteArray SoapySDRInputSettings::serializeNamedElementMap(const QMap& map) const +{ + QByteArray data; + QDataStream *stream = new QDataStream(&data, QIODevice::WriteOnly); + (*stream) << map; + delete stream; + + return data; +} + +void SoapySDRInputSettings::deserializeNamedElementMap(const QByteArray& data, QMap& map) +{ + QDataStream *stream = new QDataStream(data); + (*stream) >> map; + delete stream; +} diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h new file mode 100644 index 000000000..4bbb2db47 --- /dev/null +++ b/plugins/samplesource/soapysdrinput/soapysdrinputsettings.h @@ -0,0 +1,62 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUTSETTINGS_H_ +#define PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUTSETTINGS_H_ + +#include +#include +#include + +struct SoapySDRInputSettings { + typedef enum { + FC_POS_INFRA = 0, + FC_POS_SUPRA, + FC_POS_CENTER + } fcPos_t; + + quint64 m_centerFrequency; + qint32 m_LOppmTenths; + qint32 m_devSampleRate; + quint32 m_log2Decim; + fcPos_t m_fcPos; + bool m_softDCCorrection; + bool m_softIQCorrection; + bool m_transverterMode; + qint64 m_transverterDeltaFrequency; + QString m_fileRecordName; + QString m_antenna; + quint32 m_bandwidth; + QMap m_tunableElements; + qint32 m_globalGain; + QMap m_individualGains; + bool m_autoGain; + bool m_autoDCCorrection; + bool m_autoIQCorrection; + std::complex m_dcCorrection; + std::complex m_iqCorrection; + + SoapySDRInputSettings(); + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + +private: + QByteArray serializeNamedElementMap(const QMap& map) const; + void deserializeNamedElementMap(const QByteArray& data, QMap& map); +}; + +#endif /* PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUTSETTINGS_H_ */ diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputthread.cpp b/plugins/samplesource/soapysdrinput/soapysdrinputthread.cpp new file mode 100644 index 000000000..dd969a8f6 --- /dev/null +++ b/plugins/samplesource/soapysdrinput/soapysdrinputthread.cpp @@ -0,0 +1,641 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include +#include + +#include "dsp/samplesinkfifo.h" +#include "soapysdr/devicesoapysdr.h" + +#include "soapysdrinputthread.h" + +SoapySDRInputThread::SoapySDRInputThread(SoapySDR::Device* dev, unsigned int nbRxChannels, QObject* parent) : + QThread(parent), + m_running(false), + m_dev(dev), + m_sampleRate(0), + m_nbChannels(nbRxChannels), + m_decimatorType(DecimatorFloat) +{ + qDebug("SoapySDRInputThread::SoapySDRInputThread"); + m_channels = new Channel[nbRxChannels]; +} + +SoapySDRInputThread::~SoapySDRInputThread() +{ + qDebug("SoapySDRInputThread::~SoapySDRInputThread"); + + if (m_running) { + stopWork(); + } + + delete[] m_channels; +} + +void SoapySDRInputThread::startWork() +{ + if (m_running) { + return; + } + + m_startWaitMutex.lock(); + start(); + + while(!m_running) { + m_startWaiter.wait(&m_startWaitMutex, 100); + } + + m_startWaitMutex.unlock(); +} + +void SoapySDRInputThread::stopWork() +{ + if (!m_running) { + return; + } + + m_running = false; + wait(); +} + +void SoapySDRInputThread::run() +{ + m_running = true; + m_startWaiter.wakeAll(); + + unsigned int nbFifos = getNbFifos(); + + if ((m_nbChannels > 0) && (nbFifos > 0)) + { + // build channels list + std::vector channels(m_nbChannels); + std::iota(channels.begin(), channels.end(), 0); // Fill with 0, 1, ..., m_nbChannels-1. + + //initialize the sample rate for all channels + for (const auto &it : channels) { + m_dev->setSampleRate(SOAPY_SDR_RX, it, m_sampleRate); + } + + // Determine sample format to be used + double fullScale(0.0); + std::string format = m_dev->getNativeStreamFormat(SOAPY_SDR_RX, channels.front(), fullScale); + + qDebug("SoapySDRInputThread::run: format: %s fullScale: %f", format.c_str(), fullScale); + + if ((format == "CS8") && (fullScale == 128.0)) { // 8 bit signed - native + m_decimatorType = Decimator8; + } else if ((format == "CS16") && (fullScale == 2048.0)) { // 12 bit signed - native + m_decimatorType = Decimator12; + } else if ((format == "CS16") && (fullScale == 32768.0)) { // 16 bit signed - native + m_decimatorType = Decimator16; + } else { // for other types make a conversion to float + m_decimatorType = DecimatorFloat; + format = "CF32"; + } + + unsigned int elemSize = SoapySDR::formatToSize(format); // sample (I+Q) size in bytes + SoapySDR::Stream *stream = m_dev->setupStream(SOAPY_SDR_RX, format, channels); + + //allocate buffers for the stream read/write + const unsigned int numElems = m_dev->getStreamMTU(stream); // number of samples (I+Q) + std::vector> buffMem(m_nbChannels, std::vector(elemSize*numElems)); + std::vector buffs(m_nbChannels); + + for (std::size_t i = 0; i < m_nbChannels; i++) { + buffs[i] = buffMem[i].data(); + } + + for (unsigned int i = 0; i < m_nbChannels; i++) { + m_channels[i].m_convertBuffer.resize(numElems, Sample{0,0}); + } + + m_dev->activateStream(stream); + int flags(0); + long long timeNs(0); + float blockTime = ((float) numElems) / (m_sampleRate <= 0 ? 1024000 : m_sampleRate); + long timeoutUs = 2000000 * blockTime; // 10 times the block time + + qDebug("SoapySDRInputThread::run: numElems: %u elemSize: %u timeoutUs: %ld", numElems, elemSize, timeoutUs); + qDebug("SoapySDRInputThread::run: start running loop"); + + while (m_running) + { + int ret = m_dev->readStream(stream, buffs.data(), numElems, flags, timeNs, timeoutUs); + + if (ret == SOAPY_SDR_TIMEOUT) + { + qWarning("SoapySDRInputThread::run: timeout: flags: %d timeNs: %lld timeoutUs: %ld", flags, timeNs, timeoutUs); + } + else if (ret < 0) + { + qCritical("SoapySDRInputThread::run: Unexpected read stream error: %s", SoapySDR::errToStr(ret)); + break; + } + + if (m_nbChannels > 1) + { + callbackMI(buffs, numElems*2); // size given in number of I or Q samples (2 items per sample) + } + else + { + switch (m_decimatorType) + { + case Decimator8: + callbackSI8((const qint8*) buffs[0], numElems*2); + break; + case Decimator12: + callbackSI12((const qint16*) buffs[0], numElems*2); + break; + case Decimator16: + callbackSI16((const qint16*) buffs[0], numElems*2); + break; + case DecimatorFloat: + default: + callbackSIF((const float*) buffs[0], numElems*2); + } + } + } + + qDebug("SoapySDRInputThread::run: stop running loop"); + m_dev->deactivateStream(stream); + m_dev->closeStream(stream); + } + else + { + qWarning("SoapySDRInputThread::run: no channels or FIFO allocated. Aborting"); + } + + m_running = false; +} + +unsigned int SoapySDRInputThread::getNbFifos() +{ + unsigned int fifoCount = 0; + + for (unsigned int i = 0; i < m_nbChannels; i++) + { + if (m_channels[i].m_sampleFifo) { + fifoCount++; + } + } + + return fifoCount; +} + +void SoapySDRInputThread::setLog2Decimation(unsigned int channel, unsigned int log2_decim) +{ + if (channel < m_nbChannels) { + m_channels[channel].m_log2Decim = log2_decim; + } +} + +unsigned int SoapySDRInputThread::getLog2Decimation(unsigned int channel) const +{ + if (channel < m_nbChannels) { + return m_channels[channel].m_log2Decim; + } else { + return 0; + } +} + +void SoapySDRInputThread::setFcPos(unsigned int channel, int fcPos) +{ + if (channel < m_nbChannels) { + m_channels[channel].m_fcPos = fcPos; + } +} + +int SoapySDRInputThread::getFcPos(unsigned int channel) const +{ + if (channel < m_nbChannels) { + return m_channels[channel].m_fcPos; + } else { + return 0; + } +} + +void SoapySDRInputThread::setFifo(unsigned int channel, SampleSinkFifo *sampleFifo) +{ + if (channel < m_nbChannels) { + m_channels[channel].m_sampleFifo = sampleFifo; + } +} + +SampleSinkFifo *SoapySDRInputThread::getFifo(unsigned int channel) +{ + if (channel < m_nbChannels) { + return m_channels[channel].m_sampleFifo; + } else { + return 0; + } +} + +void SoapySDRInputThread::callbackMI(std::vector& buffs, qint32 samplesPerChannel) +{ + for(unsigned int ichan = 0; ichan < m_nbChannels; ichan++) + { + switch (m_decimatorType) + { + case Decimator8: + callbackSI8((const qint8*) buffs[ichan], samplesPerChannel, ichan); + break; + case Decimator12: + callbackSI12((const qint16*) buffs[ichan], samplesPerChannel, ichan); + break; + case Decimator16: + callbackSI16((const qint16*) buffs[ichan], samplesPerChannel, ichan); + break; + case DecimatorFloat: + default: + callbackSIF((const float*) buffs[ichan], samplesPerChannel, ichan); + } + } +} + +void SoapySDRInputThread::callbackSI8(const qint8* buf, qint32 len, unsigned int channel) +{ + SampleVector::iterator it = m_channels[channel].m_convertBuffer.begin(); + + if (m_channels[channel].m_log2Decim == 0) + { + m_channels[channel].m_decimators8.decimate1(&it, buf, len); + } + else + { + if (m_channels[channel].m_fcPos == 0) // Infra + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimators8.decimate2_inf(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimators8.decimate4_inf(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimators8.decimate8_inf(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimators8.decimate16_inf(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimators8.decimate32_inf(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimators8.decimate64_inf(&it, buf, len); + break; + default: + break; + } + } + else if (m_channels[channel].m_fcPos == 1) // Supra + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimators8.decimate2_sup(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimators8.decimate4_sup(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimators8.decimate8_sup(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimators8.decimate16_sup(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimators8.decimate32_sup(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimators8.decimate64_sup(&it, buf, len); + break; + default: + break; + } + } + else if (m_channels[channel].m_fcPos == 2) // Center + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimators8.decimate2_cen(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimators8.decimate4_cen(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimators8.decimate8_cen(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimators8.decimate16_cen(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimators8.decimate32_cen(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimators8.decimate64_cen(&it, buf, len); + break; + default: + break; + } + } + } + + m_channels[channel].m_sampleFifo->write(m_channels[channel].m_convertBuffer.begin(), it); +} + +void SoapySDRInputThread::callbackSI12(const qint16* buf, qint32 len, unsigned int channel) +{ + SampleVector::iterator it = m_channels[channel].m_convertBuffer.begin(); + + if (m_channels[channel].m_log2Decim == 0) + { + m_channels[channel].m_decimators12.decimate1(&it, buf, len); + } + else + { + if (m_channels[channel].m_fcPos == 0) // Infra + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimators12.decimate2_inf(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimators12.decimate4_inf(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimators12.decimate8_inf(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimators12.decimate16_inf(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimators12.decimate32_inf(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimators12.decimate64_inf(&it, buf, len); + break; + default: + break; + } + } + else if (m_channels[channel].m_fcPos == 1) // Supra + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimators12.decimate2_sup(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimators12.decimate4_sup(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimators12.decimate8_sup(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimators12.decimate16_sup(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimators12.decimate32_sup(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimators12.decimate64_sup(&it, buf, len); + break; + default: + break; + } + } + else if (m_channels[channel].m_fcPos == 2) // Center + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimators12.decimate2_cen(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimators12.decimate4_cen(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimators12.decimate8_cen(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimators12.decimate16_cen(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimators12.decimate32_cen(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimators12.decimate64_cen(&it, buf, len); + break; + default: + break; + } + } + } + + m_channels[channel].m_sampleFifo->write(m_channels[channel].m_convertBuffer.begin(), it); +} + +void SoapySDRInputThread::callbackSI16(const qint16* buf, qint32 len, unsigned int channel) +{ + SampleVector::iterator it = m_channels[channel].m_convertBuffer.begin(); + + if (m_channels[channel].m_log2Decim == 0) + { + m_channels[channel].m_decimators16.decimate1(&it, buf, len); + } + else + { + if (m_channels[channel].m_fcPos == 0) // Infra + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimators16.decimate2_inf(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimators16.decimate4_inf(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimators16.decimate8_inf(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimators16.decimate16_inf(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimators16.decimate32_inf(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimators16.decimate64_inf(&it, buf, len); + break; + default: + break; + } + } + else if (m_channels[channel].m_fcPos == 1) // Supra + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimators16.decimate2_sup(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimators16.decimate4_sup(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimators16.decimate8_sup(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimators16.decimate16_sup(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimators16.decimate32_sup(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimators16.decimate64_sup(&it, buf, len); + break; + default: + break; + } + } + else if (m_channels[channel].m_fcPos == 2) // Center + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimators16.decimate2_cen(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimators16.decimate4_cen(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimators16.decimate8_cen(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimators16.decimate16_cen(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimators16.decimate32_cen(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimators16.decimate64_cen(&it, buf, len); + break; + default: + break; + } + } + } + + m_channels[channel].m_sampleFifo->write(m_channels[channel].m_convertBuffer.begin(), it); +} + +void SoapySDRInputThread::callbackSIF(const float* buf, qint32 len, unsigned int channel) +{ + SampleVector::iterator it = m_channels[channel].m_convertBuffer.begin(); + + if (m_channels[channel].m_log2Decim == 0) + { + m_channels[channel].m_decimatorsFloat.decimate1(&it, buf, len); + } + else + { + if (m_channels[channel].m_fcPos == 0) // Infra + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimatorsFloat.decimate2_inf(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimatorsFloat.decimate4_inf(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimatorsFloat.decimate8_inf(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimatorsFloat.decimate16_inf(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimatorsFloat.decimate32_inf(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimatorsFloat.decimate64_inf(&it, buf, len); + break; + default: + break; + } + } + else if (m_channels[channel].m_fcPos == 1) // Supra + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimatorsFloat.decimate2_sup(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimatorsFloat.decimate4_sup(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimatorsFloat.decimate8_sup(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimatorsFloat.decimate16_sup(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimatorsFloat.decimate32_sup(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimatorsFloat.decimate64_sup(&it, buf, len); + break; + default: + break; + } + } + else if (m_channels[channel].m_fcPos == 2) // Center + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimatorsFloat.decimate2_cen(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimatorsFloat.decimate4_cen(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimatorsFloat.decimate8_cen(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimatorsFloat.decimate16_cen(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimatorsFloat.decimate32_cen(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimatorsFloat.decimate64_cen(&it, buf, len); + break; + default: + break; + } + } + } + + m_channels[channel].m_sampleFifo->write(m_channels[channel].m_convertBuffer.begin(), it); +} diff --git a/plugins/samplesource/soapysdrinput/soapysdrinputthread.h b/plugins/samplesource/soapysdrinput/soapysdrinputthread.h new file mode 100644 index 000000000..96aeca95b --- /dev/null +++ b/plugins/samplesource/soapysdrinput/soapysdrinputthread.h @@ -0,0 +1,107 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUTTHREAD_H_ +#define PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUTTHREAD_H_ + +// SoapySDR is a device wrapper with a single stream supporting one or many Rx +// Therefore only one thread can be allocated for the Rx side +// All FIFOs must be registered before calling startWork() + +#include +#include +#include + +#include + +#include "soapysdr/devicesoapysdrshared.h" +#include "dsp/decimators.h" +#include "dsp/decimatorsfi.h" + +class SampleSinkFifo; + +class SoapySDRInputThread : public QThread { + Q_OBJECT + +public: + SoapySDRInputThread(SoapySDR::Device* dev, unsigned int nbRxChannels, QObject* parent = 0); + ~SoapySDRInputThread(); + + void startWork(); + void stopWork(); + bool isRunning() const { return m_running; } + unsigned int getNbChannels() const { return m_nbChannels; } + void setLog2Decimation(unsigned int channel, unsigned int log2_decim); + unsigned int getLog2Decimation(unsigned int channel) const; + void setSampleRate(unsigned int sampleRate) { m_sampleRate = sampleRate; } + unsigned int getSampleRate() const { return m_sampleRate; } + void setFcPos(unsigned int channel, int fcPos); + int getFcPos(unsigned int channel) const; + void setFifo(unsigned int channel, SampleSinkFifo *sampleFifo); + SampleSinkFifo *getFifo(unsigned int channel); + +private: + struct Channel + { + SampleVector m_convertBuffer; + SampleSinkFifo* m_sampleFifo; + unsigned int m_log2Decim; + int m_fcPos; + Decimators m_decimators8; + Decimators m_decimators12; + Decimators m_decimators16; + DecimatorsFI m_decimatorsFloat; + + Channel() : + m_sampleFifo(0), + m_log2Decim(0), + m_fcPos(0) + {} + + ~Channel() + {} + }; + + enum DecimatorType + { + Decimator8, + Decimator12, + Decimator16, + DecimatorFloat + }; + + QMutex m_startWaitMutex; + QWaitCondition m_startWaiter; + bool m_running; + SoapySDR::Device* m_dev; + + Channel *m_channels; //!< Array of channels dynamically allocated for the given number of Rx channels + unsigned int m_sampleRate; + unsigned int m_nbChannels; + DecimatorType m_decimatorType; + + void run(); + unsigned int getNbFifos(); + void callbackSI8(const qint8* buf, qint32 len, unsigned int channel = 0); + void callbackSI12(const qint16* buf, qint32 len, unsigned int channel = 0); + void callbackSI16(const qint16* buf, qint32 len, unsigned int channel = 0); + void callbackSIF(const float* buf, qint32 len, unsigned int channel = 0); + void callbackMI(std::vector& buffs, qint32 samplesPerChannel); +}; + + + +#endif /* PLUGINS_SAMPLESOURCE_SOAPYSDRINPUT_SOAPYSDRINPUTTHREAD_H_ */ diff --git a/plugins/samplesource/testsource/CMakeLists.txt b/plugins/samplesource/testsource/CMakeLists.txt index 24fd797fc..110b29e7f 100644 --- a/plugins/samplesource/testsource/CMakeLists.txt +++ b/plugins/samplesource/testsource/CMakeLists.txt @@ -49,6 +49,6 @@ target_link_libraries(inputtestsource swagger ) -qt5_use_modules(inputtestsource Core Widgets) +target_link_libraries(inputtestsource Qt5::Core Qt5::Widgets) install(TARGETS inputtestsource DESTINATION lib/plugins/samplesource) diff --git a/plugins/samplesource/testsource/readme.md b/plugins/samplesource/testsource/readme.md index b357c6f16..6b198eaa1 100644 --- a/plugins/samplesource/testsource/readme.md +++ b/plugins/samplesource/testsource/readme.md @@ -2,7 +2,7 @@

Introduction

-This input sample source plugin is an internal continuous wave generator that can be used to carry out test of software internals. +This input sample source plugin is an internal continuous wave generator that can be used to carry out test of software internals.

Build

@@ -22,19 +22,19 @@ This is the center frequency of reception in kHz.

1.2: Start/Stop

-Device start / stop button. +Device start / stop button. - Blue triangle icon: device is ready and can be started - Green square icon: device is running and can be stopped - - Magenta (or pink) square icon: an error occured. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again. - + - Magenta (or pink) square icon: an error occurred. In the case the device was accidentally disconnected you may click on the icon, plug back in and start again. +

1.3: Record

Record baseband I/Q stream toggle button

1.4: Stream sample rate

-Baseband I/Q sample rate in kS/s. This is the device to host sample rate (3) divided by the decimation factor (4). +Baseband I/Q sample rate in kS/s. This is the device to host sample rate (3) divided by the decimation factor (4).

2: Various options

@@ -50,17 +50,20 @@ This combo box control the local DSP auto correction options:

2.2: Decimation factor

-The I/Q stream from the generator is doensampled by a power of two before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8, 16, 32. This exercises the decimation chain. +The I/Q stream from the generator is downsampled by a power of two before being sent to the passband. Possible values are increasing powers of two: 1 (no decimation), 2, 4, 8, 16, 32. This exercises the decimation chain. This exercises the decimation chain.

2.3: Baseband center frequency position relative the center frequency

-Possible values are: + - **Cen**: the decimation operation takes place around the center frequency Fs + - **Inf**: the decimation operation takes place around Fs - Fc. + - **Sup**: the decimation operation takes place around Fs + Fc. - - **Cen**: the decimation operation takes place around the BladeRF Rx center frequency - - **Inf**: the decimation operation takes place around the center of the lower half of the BladeRF Rx passband. - - **Sup**: the decimation operation takes place around the center of the upper half of the BladeRF Rx passband. +With SR as the sample rate before decimation Fc is calculated as: + + - if decimation n is 4 or lower: Fc = SR/2^(log2(n)-1). The device center frequency is on the side of the baseband. You need a RF filter bandwidth at least twice the baseband. + - if decimation n is 8 or higher: Fc = SR/n. The device center frequency is half the baseband away from the side of the baseband. You need a RF filter bandwidth at least 3 times the baseband.

2.4: Sample size

@@ -79,7 +82,17 @@ This controls the generator sample rate in samples per second. - **No**: No modulation - **AM**: Amplitude modulation (AM) - **FM**: Frequency modulation (FM) - + - **P0**: Pattern 0 is a binary pattern + - Pulse width: 150 samples + - Sync pattern: 010 at full amplitude + - Binary pattern LSB first on 3 bits from 0 to 7 at 0.3 amplitude + - **P1**: Pattern 1 is a sawtooth pattern + - Pulse width: 1000 samples + - Starts at full amplitude then amplitude decreases linearly down to zero + - **P2**: Pattern 2 is a 50% duty cycle square pattern + - Pulse width: 1000 samples + - Starts with a full amplitude pulse then down to zero for the duration of one pulse +

5: Modulating tone frequency

This controls the modulating tone frequency in kHz in 10 Hz steps. @@ -87,23 +100,23 @@ This controls the modulating tone frequency in kHz in 10 Hz steps.

6: Carrier shift from center frequency

Use this control to set the offset of the carrier from the center frequency of reception. - +

7: AM modulation factor

This controls the AM modulation factor from 0 to 99%

8: FM deviation

-This controls the frequency modulation deviation in kHz in 100 Hz steps. It cannot exceed the sample rate. - +This controls the frequency modulation deviation in kHz in 100 Hz steps. It cannot exceed the sample rate. +

9: Amplitude coarse control

This slider controls the number of amplitude bits by steps of 100 bits. The total number of amplitude bits appear on the right. - +

10: Amplitude fine control

This slider controls the number of amplitude bits by steps of 1 bit. The signal power in dB relative to the maximum power (full bit range) appear on the right. - +

11: DC bias

Use this slider to give a DC component in percentage of maximum amplitude. diff --git a/plugins/samplesource/testsource/testsource.pro b/plugins/samplesource/testsource/testsource.pro index 547baba25..04b7389dc 100644 --- a/plugins/samplesource/testsource/testsource.pro +++ b/plugins/samplesource/testsource/testsource.pro @@ -18,6 +18,7 @@ QMAKE_CXXFLAGS += -msse4.1 QMAKE_CXXFLAGS += -std=c++11 INCLUDEPATH += $$PWD +INCLUDEPATH += ../../../exports INCLUDEPATH += ../../../sdrbase INCLUDEPATH += ../../../sdrgui INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client diff --git a/plugins/samplesource/testsource/testsourcegui.cpp b/plugins/samplesource/testsource/testsourcegui.cpp index e9a0e912d..e91b49dec 100644 --- a/plugins/samplesource/testsource/testsourcegui.cpp +++ b/plugins/samplesource/testsource/testsourcegui.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include #include "ui_testsourcegui.h" @@ -45,7 +44,7 @@ TestSourceGui::TestSourceGui(DeviceUISet *deviceUISet, QWidget* parent) : m_forceSettings(true), m_sampleSource(0), m_tickCount(0), - m_lastEngineState((DSPDeviceSourceEngine::State)-1) + m_lastEngineState(DSPDeviceSourceEngine::StNotStarted) { qDebug("TestSourceGui::TestSourceGui"); m_sampleSource = m_deviceUISet->m_deviceSourceAPI->getSampleSource(); @@ -66,6 +65,7 @@ TestSourceGui::TestSourceGui(DeviceUISet *deviceUISet, QWidget* parent) : m_statusTimer.start(500); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleSource->setMessageQueueToGUI(&m_inputMessageQueue); } TestSourceGui::~TestSourceGui() @@ -379,11 +379,17 @@ void TestSourceGui::displaySettings() ui->amplitudeFine->setValue(amplitudeBits%100); displayAmplitude(); int dcBiasPercent = roundf(m_settings.m_dcFactor * 100.0f); + ui->dcBias->setValue((int) dcBiasPercent); ui->dcBiasText->setText(QString(tr("%1 %").arg(dcBiasPercent))); int iBiasPercent = roundf(m_settings.m_iFactor * 100.0f); + ui->iBias->setValue((int) iBiasPercent); ui->iBiasText->setText(QString(tr("%1 %").arg(iBiasPercent))); int qBiasPercent = roundf(m_settings.m_qFactor * 100.0f); + ui->qBias->setValue((int) qBiasPercent); ui->qBiasText->setText(QString(tr("%1 %").arg(qBiasPercent))); + int phaseImbalancePercent = roundf(m_settings.m_phaseImbalance * 100.0f); + ui->phaseImbalance->setValue((int) phaseImbalancePercent); + ui->phaseImbalanceText->setText(QString(tr("%1 %").arg(phaseImbalancePercent))); ui->autoCorr->setCurrentIndex(m_settings.m_autoCorrOptions); ui->sampleSize->blockSignals(false); ui->modulation->setCurrentIndex((int) m_settings.m_modulation); diff --git a/plugins/samplesource/testsource/testsourcegui.ui b/plugins/samplesource/testsource/testsourcegui.ui index 13e13e02d..0706fc67e 100644 --- a/plugins/samplesource/testsource/testsourcegui.ui +++ b/plugins/samplesource/testsource/testsourcegui.ui @@ -24,8 +24,11 @@ - Sans Serif + Liberation Sans 9 + 50 + false + false @@ -141,7 +144,7 @@ - DejaVu Sans Mono + Liberation Mono 20 @@ -287,7 +290,7 @@ - Relative postion of generator center frequency + Relative position of generator center frequency 2 @@ -412,7 +415,7 @@ - DejaVu Sans Mono + Liberation Mono 12 @@ -477,6 +480,21 @@ FM + + + P0 + + + + + P1 + + + + + P2 + + @@ -553,9 +571,8 @@ - DejaVu Sans Mono + Liberation Mono 12 - false diff --git a/plugins/samplesource/testsource/testsourceinput.cpp b/plugins/samplesource/testsource/testsourceinput.cpp index d1aa8d40a..3d230b2d6 100644 --- a/plugins/samplesource/testsource/testsourceinput.cpp +++ b/plugins/samplesource/testsource/testsourceinput.cpp @@ -41,9 +41,7 @@ TestSourceInput::TestSourceInput(DeviceSourceAPI *deviceAPI) : m_running(false), m_masterTimer(deviceAPI->getMasterTimer()) { - char recFileNameCStr[30]; - sprintf(recFileNameCStr, "test_%d.sdriq", m_deviceAPI->getDeviceUID()); - m_fileSink = new FileRecord(std::string(recFileNameCStr)); + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); m_deviceAPI->addSink(m_fileSink); if (!m_sampleFifo.setSize(96000 * 4)) { @@ -74,16 +72,9 @@ bool TestSourceInput::start() if (m_running) stop(); - if ((m_testSourceThread = new TestSourceThread(&m_sampleFifo)) == 0) - { - qCritical("TestSourceInput::start: out of memory"); - stop(); - return false; - } - + m_testSourceThread = new TestSourceThread(&m_sampleFifo); m_testSourceThread->setSamplerate(m_settings.m_sampleRate); - m_testSourceThread->connectTimer(m_masterTimer); - m_testSourceThread->startWork(); + m_testSourceThread->startStop(true); mutexLocker.unlock(); @@ -99,8 +90,8 @@ void TestSourceInput::stop() if (m_testSourceThread != 0) { - m_testSourceThread->stopWork(); - delete m_testSourceThread; + m_testSourceThread->startStop(false); + m_testSourceThread->deleteLater(); m_testSourceThread = 0; } @@ -141,7 +132,7 @@ const QString& TestSourceInput::getDeviceDescription() const int TestSourceInput::getSampleRate() const { - return m_settings.m_sampleRate; + return m_settings.m_sampleRate/(1<setFileName(m_settings.m_fileRecordName); + } else { + m_fileSink->genUniqueFileName(m_deviceAPI->getDeviceUID()); + } - if (conf.getStartStop()) { m_fileSink->startRecording(); - } else { + } + else + { m_fileSink->stopRecording(); } @@ -196,20 +196,18 @@ bool TestSourceInput::handleMessage(const Message& message) else if (MsgStartStop::match(message)) { MsgStartStop& cmd = (MsgStartStop&) message; - qDebug() << "RTLSDRInput::handleMessage: MsgStartStop: " << (cmd.getStartStop() ? "start" : "stop"); + qDebug() << "TestSourceInput::handleMessage: MsgStartStop: " << (cmd.getStartStop() ? "start" : "stop"); if (cmd.getStartStop()) { if (m_deviceAPI->initAcquisition()) { m_deviceAPI->startAcquisition(); - DSPEngine::instance()->startAudioOutput(); } } else { m_deviceAPI->stopAcquisition(); - DSPEngine::instance()->stopAudioOutput(); } return true; @@ -260,27 +258,25 @@ bool TestSourceInput::applySettings(const TestSourceSettings& settings, bool for if ((m_settings.m_centerFrequency != settings.m_centerFrequency) || (m_settings.m_fcPos != settings.m_fcPos) || (m_settings.m_frequencyShift != settings.m_frequencyShift) + || (m_settings.m_sampleRate != settings.m_sampleRate) || (m_settings.m_log2Decim != settings.m_log2Decim) || force) { - qint64 deviceCenterFrequency = settings.m_centerFrequency; + qint64 deviceCenterFrequency = DeviceSampleSource::calculateDeviceCenterFrequency( + settings.m_centerFrequency, + 0, // no transverter mode + settings.m_log2Decim, + (DeviceSampleSource::fcPos_t) settings.m_fcPos, + settings.m_sampleRate); + int frequencyShift = settings.m_frequencyShift; - qint64 f_img = deviceCenterFrequency; quint32 devSampleRate = settings.m_sampleRate; if (settings.m_log2Decim != 0) { - if (settings.m_fcPos == TestSourceSettings::FC_POS_INFRA) - { - deviceCenterFrequency += (devSampleRate / 4); - frequencyShift -= (devSampleRate / 4); - f_img = deviceCenterFrequency + devSampleRate/2; - } - else if (settings.m_fcPos == TestSourceSettings::FC_POS_SUPRA) - { - deviceCenterFrequency -= (devSampleRate / 4); - frequencyShift += (devSampleRate / 4); - f_img = deviceCenterFrequency - devSampleRate/2; - } + frequencyShift += DeviceSampleSource::calculateFrequencyShift( + settings.m_log2Decim, + (DeviceSampleSource::fcPos_t) settings.m_fcPos, + settings.m_sampleRate); } if (m_testSourceThread != 0) @@ -292,7 +288,6 @@ bool TestSourceInput::applySettings(const TestSourceSettings& settings, bool for << " device center freq: " << deviceCenterFrequency << " Hz" << " device sample rate: " << devSampleRate << "Hz" << " Actual sample rate: " << devSampleRate/(1<setModulation(settings.m_modulation); + + if (settings.m_modulation == TestSourceSettings::ModulationPattern0) { + m_testSourceThread->setPattern0(); + } else if (settings.m_modulation == TestSourceSettings::ModulationPattern1) { + m_testSourceThread->setPattern1(); + } else if (settings.m_modulation == TestSourceSettings::ModulationPattern2) { + m_testSourceThread->setPattern2(); + } } } @@ -407,3 +411,121 @@ int TestSourceInput::webapiRun( return 200; } + +int TestSourceInput::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage __attribute__((unused))) +{ + response.setTestSourceSettings(new SWGSDRangel::SWGTestSourceSettings()); + response.getTestSourceSettings()->init(); + webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int TestSourceInput::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage __attribute__((unused))) +{ + TestSourceSettings settings = m_settings; + + if (deviceSettingsKeys.contains("centerFrequency")) { + settings.m_centerFrequency = response.getTestSourceSettings()->getCenterFrequency(); + } + if (deviceSettingsKeys.contains("frequencyShift")) { + settings.m_frequencyShift = response.getTestSourceSettings()->getFrequencyShift(); + } + if (deviceSettingsKeys.contains("sampleRate")) { + settings.m_sampleRate = response.getTestSourceSettings()->getSampleRate(); + } + if (deviceSettingsKeys.contains("log2Decim")) { + settings.m_log2Decim = response.getTestSourceSettings()->getLog2Decim(); + } + if (deviceSettingsKeys.contains("fcPos")) { + int fcPos = response.getTestSourceSettings()->getFcPos(); + fcPos = fcPos < 0 ? 0 : fcPos > 2 ? 2 : fcPos; + settings.m_fcPos = (TestSourceSettings::fcPos_t) fcPos; + } + if (deviceSettingsKeys.contains("sampleSizeIndex")) { + int sampleSizeIndex = response.getTestSourceSettings()->getSampleSizeIndex(); + sampleSizeIndex = sampleSizeIndex < 0 ? 0 : sampleSizeIndex > 1 ? 2 : sampleSizeIndex; + settings.m_sampleSizeIndex = sampleSizeIndex; + } + if (deviceSettingsKeys.contains("amplitudeBits")) { + settings.m_amplitudeBits = response.getTestSourceSettings()->getAmplitudeBits(); + } + if (deviceSettingsKeys.contains("autoCorrOptions")) { + int autoCorrOptions = response.getTestSourceSettings()->getAutoCorrOptions(); + autoCorrOptions = autoCorrOptions < 0 ? 0 : autoCorrOptions >= TestSourceSettings::AutoCorrLast ? TestSourceSettings::AutoCorrLast-1 : autoCorrOptions; + settings.m_sampleSizeIndex = (TestSourceSettings::AutoCorrOptions) autoCorrOptions; + } + if (deviceSettingsKeys.contains("modulation")) { + int modulation = response.getTestSourceSettings()->getModulation(); + modulation = modulation < 0 ? 0 : modulation >= TestSourceSettings::ModulationLast ? TestSourceSettings::ModulationLast-1 : modulation; + settings.m_modulation = (TestSourceSettings::Modulation) modulation; + } + if (deviceSettingsKeys.contains("modulationTone")) { + settings.m_modulationTone = response.getTestSourceSettings()->getModulationTone(); + } + if (deviceSettingsKeys.contains("amModulation")) { + settings.m_amModulation = response.getTestSourceSettings()->getAmModulation(); + }; + if (deviceSettingsKeys.contains("fmDeviation")) { + settings.m_fmDeviation = response.getTestSourceSettings()->getFmDeviation(); + }; + if (deviceSettingsKeys.contains("dcFactor")) { + settings.m_dcFactor = response.getTestSourceSettings()->getDcFactor(); + }; + if (deviceSettingsKeys.contains("iFactor")) { + settings.m_iFactor = response.getTestSourceSettings()->getIFactor(); + }; + if (deviceSettingsKeys.contains("qFactor")) { + settings.m_qFactor = response.getTestSourceSettings()->getQFactor(); + }; + if (deviceSettingsKeys.contains("phaseImbalance")) { + settings.m_phaseImbalance = response.getTestSourceSettings()->getPhaseImbalance(); + }; + if (deviceSettingsKeys.contains("fileRecordName")) { + settings.m_fileRecordName = *response.getTestSourceSettings()->getFileRecordName(); + } + + MsgConfigureTestSource *msg = MsgConfigureTestSource::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureTestSource *msgToGUI = MsgConfigureTestSource::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +void TestSourceInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const TestSourceSettings& settings) +{ + response.getTestSourceSettings()->setCenterFrequency(settings.m_centerFrequency); + response.getTestSourceSettings()->setFrequencyShift(settings.m_frequencyShift); + response.getTestSourceSettings()->setSampleRate(settings.m_sampleRate); + response.getTestSourceSettings()->setLog2Decim(settings.m_log2Decim); + response.getTestSourceSettings()->setFcPos((int) settings.m_fcPos); + response.getTestSourceSettings()->setSampleSizeIndex((int) settings.m_sampleSizeIndex); + response.getTestSourceSettings()->setAmplitudeBits(settings.m_amplitudeBits); + response.getTestSourceSettings()->setAutoCorrOptions((int) settings.m_autoCorrOptions); + response.getTestSourceSettings()->setModulation((int) settings.m_modulation); + response.getTestSourceSettings()->setModulationTone(settings.m_modulationTone); + response.getTestSourceSettings()->setAmModulation(settings.m_amModulation); + response.getTestSourceSettings()->setFmDeviation(settings.m_fmDeviation); + response.getTestSourceSettings()->setDcFactor(settings.m_dcFactor); + response.getTestSourceSettings()->setIFactor(settings.m_iFactor); + response.getTestSourceSettings()->setQFactor(settings.m_qFactor); + response.getTestSourceSettings()->setPhaseImbalance(settings.m_phaseImbalance); + + if (response.getTestSourceSettings()->getFileRecordName()) { + *response.getTestSourceSettings()->getFileRecordName() = settings.m_fileRecordName; + } else { + response.getTestSourceSettings()->setFileRecordName(new QString(settings.m_fileRecordName)); + } +} + diff --git a/plugins/samplesource/testsource/testsourceinput.h b/plugins/samplesource/testsource/testsourceinput.h index e69624604..6938f7b52 100644 --- a/plugins/samplesource/testsource/testsourceinput.h +++ b/plugins/samplesource/testsource/testsourceinput.h @@ -110,6 +110,16 @@ public: virtual bool handleMessage(const Message& message); + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + virtual int webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage); @@ -130,6 +140,7 @@ private: const QTimer& m_masterTimer; bool applySettings(const TestSourceSettings& settings, bool force); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const TestSourceSettings& settings); }; #endif // _TESTSOURCE_TESTSOURCEINPUT_H_ diff --git a/plugins/samplesource/testsource/testsourceplugin.cpp b/plugins/samplesource/testsource/testsourceplugin.cpp index 618fb408d..ad19169ad 100644 --- a/plugins/samplesource/testsource/testsourceplugin.cpp +++ b/plugins/samplesource/testsource/testsourceplugin.cpp @@ -29,7 +29,7 @@ const PluginDescriptor TestSourcePlugin::m_pluginDescriptor = { QString("Test Source input"), - QString("3.12.0"), + QString("4.2.4"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/samplesource/testsource/testsourcesettings.cpp b/plugins/samplesource/testsource/testsourcesettings.cpp index a78d81f92..65e1b10b2 100644 --- a/plugins/samplesource/testsource/testsourcesettings.cpp +++ b/plugins/samplesource/testsource/testsourcesettings.cpp @@ -41,6 +41,7 @@ void TestSourceSettings::resetToDefaults() m_iFactor = 0.0f; m_qFactor = 0.0f; m_phaseImbalance = 0.0f; + m_fileRecordName = ""; } QByteArray TestSourceSettings::serialize() const diff --git a/plugins/samplesource/testsource/testsourcesettings.h b/plugins/samplesource/testsource/testsourcesettings.h index d4c476703..60c7d055f 100644 --- a/plugins/samplesource/testsource/testsourcesettings.h +++ b/plugins/samplesource/testsource/testsourcesettings.h @@ -17,6 +17,8 @@ #ifndef _TESTSOURCE_TESTSOURCESETTINGS_H_ #define _TESTSOURCE_TESTSOURCESETTINGS_H_ +#include + struct TestSourceSettings { typedef enum { FC_POS_INFRA = 0, @@ -35,6 +37,9 @@ struct TestSourceSettings { ModulationNone, ModulationAM, ModulationFM, + ModulationPattern0, + ModulationPattern1, + ModulationPattern2, ModulationLast } Modulation; @@ -54,6 +59,7 @@ struct TestSourceSettings { float m_iFactor; //!< -1.0 < x < 1.0 float m_qFactor; //!< -1.0 < x < 1.0 float m_phaseImbalance; //!< -1.0 < x < 1.0 + QString m_fileRecordName; TestSourceSettings(); void resetToDefaults(); diff --git a/plugins/samplesource/testsource/testsourcethread.cpp b/plugins/samplesource/testsource/testsourcethread.cpp index 70fd0d980..0e972b776 100644 --- a/plugins/samplesource/testsource/testsourcethread.cpp +++ b/plugins/samplesource/testsource/testsourcethread.cpp @@ -22,6 +22,8 @@ #define TESTSOURCE_BLOCKSIZE 16384 +MESSAGE_CLASS_DEFINITION(TestSourceThread::MsgStartStop, Message) + TestSourceThread::TestSourceThread(SampleSinkFifo* sampleFifo, QObject* parent) : QThread(parent), m_running(false), @@ -36,6 +38,11 @@ TestSourceThread::TestSourceThread(SampleSinkFifo* sampleFifo, QObject* parent) m_amModulation(0.5f), m_fmDeviationUnit(0.0f), m_fmPhasor(0.0f), + m_pulseWidth(150), + m_pulseSampleCount(0), + m_pulsePatternCount(0), + m_pulsePatternCycle(8), + m_pulsePatternPlaces(3), m_samplerate(48000), m_log2Decim(4), m_fcPos(0), @@ -55,15 +62,17 @@ TestSourceThread::TestSourceThread(SampleSinkFifo* sampleFifo, QObject* parent) m_throttleToggle(false), m_mutex(QMutex::Recursive) { + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); } TestSourceThread::~TestSourceThread() { - stopWork(); } void TestSourceThread::startWork() { + connect(&m_timer, SIGNAL(timeout()), this, SLOT(tick())); + m_timer.start(50); m_startWaitMutex.lock(); m_elapsedTimer.start(); start(); @@ -76,6 +85,8 @@ void TestSourceThread::stopWork() { m_running = false; wait(); + m_timer.stop(); + disconnect(&m_timer, SIGNAL(timeout()), this, SLOT(tick())); } void TestSourceThread::setSamplerate(int samplerate) @@ -177,6 +188,12 @@ void TestSourceThread::setFMDeviation(float deviation) qDebug("TestSourceThread::setFMDeviation: m_fmDeviationUnit: %f", m_fmDeviationUnit); } +void TestSourceThread::startStop(bool start) +{ + MsgStartStop *msg = MsgStartStop::create(start); + m_inputMessageQueue.push(msg); +} + void TestSourceThread::run() { m_running = true; @@ -250,6 +267,81 @@ void TestSourceThread::generate(quint32 chunksize) m_buf[i++] = (int16_t) (im * (float) m_amplitudeBitsQ); } break; + case TestSourceSettings::ModulationPattern0: // binary pattern + { + if (m_pulseSampleCount < m_pulseWidth) // sync pattern: 0 + { + m_buf[i++] = m_amplitudeBitsDC; + m_buf[i++] = 0; + } + else if (m_pulseSampleCount < 2*m_pulseWidth) // sync pattern: 1 + { + m_buf[i++] = (int16_t) (m_amplitudeBitsI + m_amplitudeBitsDC); + m_buf[i++] = (int16_t) (m_phaseImbalance * (float) m_amplitudeBitsQ); + } + else if (m_pulseSampleCount < 3*m_pulseWidth) // sync pattern: 0 + { + m_buf[i++] = m_amplitudeBitsDC; + m_buf[i++] = 0; + } + else if (m_pulseSampleCount < (3+m_pulsePatternPlaces)*m_pulseWidth) // binary pattern + { + uint32_t patPulseSampleCount = m_pulseSampleCount - 3*m_pulseWidth; + uint32_t patPulseIndex = patPulseSampleCount / m_pulseWidth; + float patFigure = (m_pulsePatternCount & (1<write(m_convertBuffer.begin(), it); } -void TestSourceThread::connectTimer(const QTimer& timer) -{ - qDebug() << "TestSourceThread::connectTimer"; - connect(&timer, SIGNAL(timeout()), this, SLOT(tick())); -} - void TestSourceThread::tick() { if (m_running) { qint64 throttlems = m_elapsedTimer.restart(); - if (throttlems != m_throttlems) + if ((throttlems > 45) && (throttlems < 55) && (throttlems != m_throttlems)) { QMutexLocker mutexLocker(&m_mutex); m_throttlems = throttlems; @@ -315,3 +401,45 @@ void TestSourceThread::tick() } } +void TestSourceThread::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + if (MsgStartStop::match(*message)) + { + MsgStartStop* notif = (MsgStartStop*) message; + qDebug("TestSourceThread::handleInputMessages: MsgStartStop: %s", notif->getStartStop() ? "start" : "stop"); + + if (notif->getStartStop()) { + startWork(); + } else { + stopWork(); + } + + delete message; + } + } +} + +void TestSourceThread::setPattern0() +{ + m_pulseWidth = 150; + m_pulseSampleCount = 0; + m_pulsePatternCount = 0; + m_pulsePatternCycle = 8; + m_pulsePatternPlaces = 3; +} + +void TestSourceThread::setPattern1() +{ + m_pulseWidth = 1000; + m_pulseSampleCount = 0; +} + +void TestSourceThread::setPattern2() +{ + m_pulseWidth = 1000; + m_pulseSampleCount = 0; +} diff --git a/plugins/samplesource/testsource/testsourcethread.h b/plugins/samplesource/testsource/testsourcethread.h index dbfd508a2..0944c603e 100644 --- a/plugins/samplesource/testsource/testsourcethread.h +++ b/plugins/samplesource/testsource/testsourcethread.h @@ -27,6 +27,8 @@ #include "dsp/samplesinkfifo.h" #include "dsp/decimators.h" #include "dsp/ncof.h" +#include "util/message.h" +#include "util/messagequeue.h" #include "testsourcesettings.h" @@ -36,11 +38,29 @@ class TestSourceThread : public QThread { Q_OBJECT public: + class MsgStartStop : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getStartStop() const { return m_startStop; } + + static MsgStartStop* create(bool startStop) { + return new MsgStartStop(startStop); + } + + protected: + bool m_startStop; + + MsgStartStop(bool startStop) : + Message(), + m_startStop(startStop) + { } + }; + TestSourceThread(SampleSinkFifo* sampleFifo, QObject* parent = 0); ~TestSourceThread(); - void startWork(); - void stopWork(); + void startStop(bool start); void setSamplerate(int samplerate); void setLog2Decimation(unsigned int log2_decim); void setFcPos(int fcPos); @@ -55,8 +75,9 @@ public: void setModulation(TestSourceSettings::Modulation modulation); void setAMModulation(float amModulation); void setFMDeviation(float deviation); - - void connectTimer(const QTimer& timer); + void setPattern0(); + void setPattern1(); + void setPattern2(); private: QMutex m_startWaitMutex; @@ -76,6 +97,11 @@ private: float m_amModulation; float m_fmDeviationUnit; float m_fmPhasor; + uint32_t m_pulseWidth; //!< pulse width in number of samples + uint32_t m_pulseSampleCount; + uint32_t m_pulsePatternCount; + uint32_t m_pulsePatternCycle; + uint32_t m_pulsePatternPlaces; int m_samplerate; unsigned int m_log2Decim; @@ -95,20 +121,19 @@ private: int m_fcPosShift; int m_throttlems; + QTimer m_timer; QElapsedTimer m_elapsedTimer; bool m_throttleToggle; QMutex m_mutex; -#ifdef SDR_RX_SAMPLE_24BIT - Decimators m_decimators_8; - Decimators m_decimators_12; - Decimators m_decimators_16; -#else + MessageQueue m_inputMessageQueue; + Decimators m_decimators_8; Decimators m_decimators_12; Decimators m_decimators_16; -#endif + void startWork(); + void stopWork(); void run(); void callback(const qint16* buf, qint32 len); void setBuffers(quint32 chunksize); @@ -353,6 +378,7 @@ private: private slots: void tick(); + void handleInputMessages(); }; #endif // _TESTSOURCE_TESTSOURCETHREAD_H_ diff --git a/pluginssrv/channelrx/CMakeLists.txt b/pluginssrv/channelrx/CMakeLists.txt index c581fe7db..d34907866 100644 --- a/pluginssrv/channelrx/CMakeLists.txt +++ b/pluginssrv/channelrx/CMakeLists.txt @@ -1,3 +1,18 @@ project(demod) +add_subdirectory(demodam) +add_subdirectory(demodbfm) + +if((LIBDSDCC_FOUND AND LIBMBE_FOUND) OR BUILD_DEBIAN) + add_subdirectory(demoddsd) +endif() + +find_package(CM256cc) +if(CM256CC_FOUND) + add_subdirectory(daemonsink) +endif(CM256CC_FOUND) + add_subdirectory(demodnfm) +add_subdirectory(demodssb) +add_subdirectory(demodwfm) +add_subdirectory(udpsink) diff --git a/pluginssrv/channelrx/daemonsink/CMakeLists.txt b/pluginssrv/channelrx/daemonsink/CMakeLists.txt new file mode 100644 index 000000000..2ff2295da --- /dev/null +++ b/pluginssrv/channelrx/daemonsink/CMakeLists.txt @@ -0,0 +1,46 @@ +project(daemonsink) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/channelrx/daemonsink") + +set(daemonsink_SOURCES + ${PLUGIN_PREFIX}/daemonsink.cpp + ${PLUGIN_PREFIX}/daemonsinksettings.cpp + ${PLUGIN_PREFIX}/daemonsinkthread.cpp + ${PLUGIN_PREFIX}/daemonsinkplugin.cpp +) + +set(daemonsink_HEADERS + ${PLUGIN_PREFIX}/daemonsink.h + ${PLUGIN_PREFIX}/daemonsinksettings.h + ${PLUGIN_PREFIX}/daemonsinkthread.h + ${PLUGIN_PREFIX}/daemonsinkplugin.h +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CM256CC_INCLUDE_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +#include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(daemonsinksrv SHARED + ${daemonsink_SOURCES} + ${daemonsink_HEADERS_MOC} +) + +target_link_libraries(daemonsinksrv + ${QT_LIBRARIES} + ${CM256CC_LIBRARIES} + sdrbase + swagger +) + +target_link_libraries(daemonsinksrv Qt5::Core) + +install(TARGETS daemonsinksrv DESTINATION lib/pluginssrv/channelrx) diff --git a/pluginssrv/channelrx/demodam/CMakeLists.txt b/pluginssrv/channelrx/demodam/CMakeLists.txt new file mode 100644 index 000000000..b5ba1b65e --- /dev/null +++ b/pluginssrv/channelrx/demodam/CMakeLists.txt @@ -0,0 +1,42 @@ +project(am) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/channelrx/demodam") + +set(am_SOURCES + ${PLUGIN_PREFIX}/amdemod.cpp + ${PLUGIN_PREFIX}/amdemodsettings.cpp + ${PLUGIN_PREFIX}/amdemodplugin.cpp +) + +set(am_HEADERS + ${PLUGIN_PREFIX}/amdemod.h + ${PLUGIN_PREFIX}/amdemodsettings.h + ${PLUGIN_PREFIX}/amdemodplugin.h +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +#include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(demodamsrv SHARED + ${am_SOURCES} + ${am_HEADERS_MOC} +) + +target_link_libraries(demodamsrv + ${QT_LIBRARIES} + sdrbase + swagger +) + +target_link_libraries(demodamsrv Qt5::Core) + +install(TARGETS demodamsrv DESTINATION lib/pluginssrv/channelrx) \ No newline at end of file diff --git a/pluginssrv/channelrx/demodbfm/CMakeLists.txt b/pluginssrv/channelrx/demodbfm/CMakeLists.txt new file mode 100644 index 000000000..f1862946b --- /dev/null +++ b/pluginssrv/channelrx/demodbfm/CMakeLists.txt @@ -0,0 +1,55 @@ +project(bfm) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/channelrx/demodbfm") + +set(bfm_SOURCES + ${PLUGIN_PREFIX}/bfmdemod.cpp + ${PLUGIN_PREFIX}/bfmdemodsettings.cpp + ${PLUGIN_PREFIX}/bfmplugin.cpp + ${PLUGIN_PREFIX}/rdsdemod.cpp + ${PLUGIN_PREFIX}/rdsdecoder.cpp + ${PLUGIN_PREFIX}/rdsparser.cpp + ${PLUGIN_PREFIX}/rdstmc.cpp +) + +set(bfm_HEADERS + ${PLUGIN_PREFIX}/bfmdemod.h + ${PLUGIN_PREFIX}/bfmdemodsettings.h + ${PLUGIN_PREFIX}/bfmplugin.h + ${PLUGIN_PREFIX}/rdsdemod.h + ${PLUGIN_PREFIX}/rdsdecoder.h + ${PLUGIN_PREFIX}/rdsparser.h + ${PLUGIN_PREFIX}/rdstmc.h +) + +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set_source_files_properties(rdstmc.cpp PROPERTIES COMPILE_FLAGS -fno-var-tracking-assignments) +endif() + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +#include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(demodbfmsrv SHARED + ${bfm_SOURCES} + ${bfm_HEADERS_MOC} + ${bfm_FORMS_HEADERS} +) + +target_link_libraries(demodbfmsrv + ${QT_LIBRARIES} + sdrbase + swagger +) + +target_link_libraries(demodbfmsrv Qt5::Core) + +install(TARGETS demodbfmsrv DESTINATION lib/pluginssrv/channelrx) diff --git a/pluginssrv/channelrx/demoddsd/CMakeLists.txt b/pluginssrv/channelrx/demoddsd/CMakeLists.txt new file mode 100644 index 000000000..c0a5d2571 --- /dev/null +++ b/pluginssrv/channelrx/demoddsd/CMakeLists.txt @@ -0,0 +1,69 @@ +project(dsddemod) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/channelrx/demoddsd") + +set(dsddemod_SOURCES + ${PLUGIN_PREFIX}/dsddemod.cpp + ${PLUGIN_PREFIX}/dsddemodplugin.cpp + ${PLUGIN_PREFIX}/dsddemodbaudrates.cpp + ${PLUGIN_PREFIX}/dsddemodsettings.cpp + ${PLUGIN_PREFIX}/dsddecoder.cpp +) + +set(dsddemod_HEADERS + ${PLUGIN_PREFIX}/dsddemod.h + ${PLUGIN_PREFIX}/dsddemodplugin.h + ${PLUGIN_PREFIX}/dsddemodbaudrates.h + ${PLUGIN_PREFIX}/dsddemodsettings.h + ${PLUGIN_PREFIX}/dsddecoder.h +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${LIBDSDCCSRC} + ${LIBMBELIBSRC} +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${LIBDSDCC_INCLUDE_DIR} + ${LIBMBE_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +#include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(demoddsdsrv SHARED + ${dsddemod_SOURCES} + ${dsddemod_HEADERS_MOC} +) + +if (BUILD_DEBIAN) +target_link_libraries(demoddsdsrv + ${QT_LIBRARIES} + sdrbase + dsdcc + mbelib +) +else (BUILD_DEBIAN) +target_link_libraries(demoddsdsrv + ${QT_LIBRARIES} + sdrbase + ${LIBDSDCC_LIBRARIES} + ${LIBMBE_LIBRARY} +) +endif (BUILD_DEBIAN) + + +target_link_libraries(demoddsdsrv Qt5::Core) + +install(TARGETS demoddsdsrv DESTINATION lib/pluginssrv/channelrx) diff --git a/pluginssrv/channelrx/demodnfm/CMakeLists.txt b/pluginssrv/channelrx/demodnfm/CMakeLists.txt index b54567bb6..1c6f98132 100644 --- a/pluginssrv/channelrx/demodnfm/CMakeLists.txt +++ b/pluginssrv/channelrx/demodnfm/CMakeLists.txt @@ -29,16 +29,14 @@ add_definitions(-DQT_SHARED) add_library(demodnfmsrv SHARED ${nfm_SOURCES} ${nfm_HEADERS_MOC} - ${nfm_FORMS_HEADERS} ) target_link_libraries(demodnfmsrv ${QT_LIBRARIES} sdrbase - sdrgui swagger ) -qt5_use_modules(demodnfmsrv Core) +target_link_libraries(demodnfmsrv Qt5::Core) install(TARGETS demodnfmsrv DESTINATION lib/pluginssrv/channelrx) \ No newline at end of file diff --git a/pluginssrv/channelrx/demodssb/CMakeLists.txt b/pluginssrv/channelrx/demodssb/CMakeLists.txt new file mode 100644 index 000000000..a2c9e8c41 --- /dev/null +++ b/pluginssrv/channelrx/demodssb/CMakeLists.txt @@ -0,0 +1,43 @@ +project(ssb) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/channelrx/demodssb") + +set(ssb_SOURCES + ${PLUGIN_PREFIX}/ssbdemod.cpp + ${PLUGIN_PREFIX}/ssbdemodsettings.cpp + ${PLUGIN_PREFIX}/ssbplugin.cpp +) + +set(ssb_HEADERS + ${PLUGIN_PREFIX}/ssbdemod.h + ${PLUGIN_PREFIX}/ssbdemodsettings.h + ${PLUGIN_PREFIX}/ssbplugin.h +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +#include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +#qt5_wrap_cpp(ssb_HEADERS_MOC ${ssb_HEADERS}) + +add_library(demodssbsrv SHARED + ${ssb_SOURCES} + ${ssb_HEADERS_MOC} +) + +target_link_libraries(demodssbsrv + ${QT_LIBRARIES} + sdrbase +) + +target_link_libraries(demodssbsrv Qt5::Core) + +install(TARGETS demodssbsrv DESTINATION lib/pluginssrv/channelrx) diff --git a/pluginssrv/channelrx/demodwfm/CMakeLists.txt b/pluginssrv/channelrx/demodwfm/CMakeLists.txt new file mode 100644 index 000000000..6a1c3a73f --- /dev/null +++ b/pluginssrv/channelrx/demodwfm/CMakeLists.txt @@ -0,0 +1,43 @@ +project(wfm) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/channelrx/demodwfm") + +set(wfm_SOURCES + ${PLUGIN_PREFIX}/wfmdemod.cpp + ${PLUGIN_PREFIX}/wfmdemodsettings.cpp + ${PLUGIN_PREFIX}/wfmplugin.cpp +) + +set(wfm_HEADERS + ${PLUGIN_PREFIX}/wfmdemod.h + ${PLUGIN_PREFIX}/wfmdemodsettings.h + ${PLUGIN_PREFIX}/wfmplugin.h +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +#include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +#qt5_wrap_cpp(nfm_HEADERS_MOC ${nfm_HEADERS}) + +add_library(demodwfmsrv SHARED + ${wfm_SOURCES} + ${wfm_HEADERS_MOC} +) + +target_link_libraries(demodwfmsrv + ${QT_LIBRARIES} + sdrbase +) + +target_link_libraries(demodwfmsrv Qt5::Core) + +install(TARGETS demodwfmsrv DESTINATION lib/pluginssrv/channelrx) \ No newline at end of file diff --git a/pluginssrv/channelrx/udpsink/CMakeLists.txt b/pluginssrv/channelrx/udpsink/CMakeLists.txt new file mode 100644 index 000000000..2f1db0788 --- /dev/null +++ b/pluginssrv/channelrx/udpsink/CMakeLists.txt @@ -0,0 +1,41 @@ +project(udpsink) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/channelrx/udpsink") + +set(udpsink_SOURCES + ${PLUGIN_PREFIX}/udpsink.cpp + ${PLUGIN_PREFIX}/udpsinkplugin.cpp + ${PLUGIN_PREFIX}/udpsinksettings.cpp +) + +set(udpsink_HEADERS + ${PLUGIN_PREFIX}/udpsink.h + ${PLUGIN_PREFIX}/udpsinkplugin.h + ${PLUGIN_PREFIX}/udpsinksettings.h +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +#include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(udpsinksrv SHARED + ${udpsink_SOURCES} + ${udpsink_HEADERS_MOC} +) + +target_link_libraries(udpsinksrv + ${QT_LIBRARIES} + sdrbase +) + +target_link_libraries(udpsinksrv Qt5::Core Qt5::Network) + +install(TARGETS udpsinksrv DESTINATION lib/pluginssrv/channelrx) diff --git a/pluginssrv/channeltx/CMakeLists.txt b/pluginssrv/channeltx/CMakeLists.txt index 2bccaebd3..b4541996d 100644 --- a/pluginssrv/channeltx/CMakeLists.txt +++ b/pluginssrv/channeltx/CMakeLists.txt @@ -1,3 +1,17 @@ project(mod) +add_subdirectory(modam) add_subdirectory(modnfm) +add_subdirectory(modssb) +add_subdirectory(modwfm) +add_subdirectory(udpsource) + +find_package(CM256cc) +if(CM256CC_FOUND) + add_subdirectory(daemonsource) +endif() + +find_package(OpenCV) +if (OpenCV_FOUND) + add_subdirectory(modatv) +endif() diff --git a/pluginssrv/channeltx/daemonsource/CMakeLists.txt b/pluginssrv/channeltx/daemonsource/CMakeLists.txt new file mode 100644 index 000000000..27eb29836 --- /dev/null +++ b/pluginssrv/channeltx/daemonsource/CMakeLists.txt @@ -0,0 +1,46 @@ +project(daemonsource) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/channeltx/daemonsource") + +set(daemonsource_SOURCES + ${PLUGIN_PREFIX}/daemonsource.cpp + ${PLUGIN_PREFIX}/daemonsourcethread.cpp + ${PLUGIN_PREFIX}/daemonsourceplugin.cpp + ${PLUGIN_PREFIX}/daemonsourcesettings.cpp +) + +set(daemonsource_HEADERS + ${PLUGIN_PREFIX}/daemonsource.h + ${PLUGIN_PREFIX}/daemonsourcethread.h + ${PLUGIN_PREFIX}/daemonsourceplugin.h + ${PLUGIN_PREFIX}/daemonsourcesettings.h +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CM256CC_INCLUDE_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +#include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(daemonsourcesrv SHARED + ${daemonsource_SOURCES} + ${daemonsource_HEADERS_MOC} +) + +target_link_libraries(daemonsourcesrv + ${QT_LIBRARIES} + ${CM256CC_LIBRARIES} + sdrbase + swagger +) + +target_link_libraries(daemonsourcesrv Qt5::Core Qt5::Network) + +install(TARGETS daemonsourcesrv DESTINATION lib/pluginssrv/channeltx) diff --git a/pluginssrv/channeltx/modam/CMakeLists.txt b/pluginssrv/channeltx/modam/CMakeLists.txt new file mode 100644 index 000000000..51f6493b2 --- /dev/null +++ b/pluginssrv/channeltx/modam/CMakeLists.txt @@ -0,0 +1,41 @@ +project(modam) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/channeltx/modam") + +set(modam_SOURCES + ${PLUGIN_PREFIX}/ammod.cpp + ${PLUGIN_PREFIX}/ammodplugin.cpp + ${PLUGIN_PREFIX}/ammodsettings.cpp +) + +set(modam_HEADERS + ${PLUGIN_PREFIX}/ammod.h + ${PLUGIN_PREFIX}/ammodplugin.h + ${PLUGIN_PREFIX}/ammodsettings.h +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(modamsrv SHARED + ${modam_SOURCES} + ${modam_HEADERS_MOC} +) + +target_link_libraries(modamsrv + ${QT_LIBRARIES} + sdrbase + swagger +) + +target_link_libraries(modamsrv Qt5::Core) + +install(TARGETS modamsrv DESTINATION lib/pluginssrv/channeltx) \ No newline at end of file diff --git a/pluginssrv/channeltx/modatv/CMakeLists.txt b/pluginssrv/channeltx/modatv/CMakeLists.txt new file mode 100644 index 000000000..d67f453fa --- /dev/null +++ b/pluginssrv/channeltx/modatv/CMakeLists.txt @@ -0,0 +1,45 @@ +project(modatv) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/channeltx/modatv") + +set(modatv_SOURCES + ${PLUGIN_PREFIX}/atvmod.cpp + ${PLUGIN_PREFIX}/atvmodplugin.cpp + ${PLUGIN_PREFIX}/atvmodsettings.cpp +) + +set(modatv_HEADERS + ${PLUGIN_PREFIX}/atvmod.h + ${PLUGIN_PREFIX}/atvmodplugin.h + ${PLUGIN_PREFIX}/atvmodsettings.h +) + +# OpenCV variables defined in /usr/share/OpenCV/OpenCVConfig.cmake (Ubuntu) + +include_directories( + . + ${OpenCV_INCLUDE_DIRS} + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(modatvsrv SHARED + ${modatv_SOURCES} + ${modatv_HEADERS_MOC} +) + +target_link_libraries(modatvsrv + ${OpenCV_LIBS} + ${QT_LIBRARIES} + sdrbase + swagger +) + +target_link_libraries(modatvsrv Qt5::Core) + +install(TARGETS modatvsrv DESTINATION lib/pluginssrv/channeltx) \ No newline at end of file diff --git a/pluginssrv/channeltx/modnfm/CMakeLists.txt b/pluginssrv/channeltx/modnfm/CMakeLists.txt index ef8114298..21af13ac5 100644 --- a/pluginssrv/channeltx/modnfm/CMakeLists.txt +++ b/pluginssrv/channeltx/modnfm/CMakeLists.txt @@ -28,16 +28,14 @@ add_definitions(-DQT_SHARED) add_library(modnfmsrv SHARED ${modnfm_SOURCES} ${modnfm_HEADERS_MOC} - ${modnfm_FORMS_HEADERS} ) target_link_libraries(modnfmsrv ${QT_LIBRARIES} sdrbase - sdrgui swagger ) -qt5_use_modules(modnfmsrv Core) +target_link_libraries(modnfmsrv Qt5::Core) install(TARGETS modnfmsrv DESTINATION lib/pluginssrv/channeltx) \ No newline at end of file diff --git a/pluginssrv/channeltx/modssb/CMakeLists.txt b/pluginssrv/channeltx/modssb/CMakeLists.txt new file mode 100644 index 000000000..c65b75bac --- /dev/null +++ b/pluginssrv/channeltx/modssb/CMakeLists.txt @@ -0,0 +1,41 @@ +project(modssb) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/channeltx/modssb") + +set(modssb_SOURCES + ${PLUGIN_PREFIX}/ssbmod.cpp + ${PLUGIN_PREFIX}/ssbmodplugin.cpp + ${PLUGIN_PREFIX}/ssbmodsettings.cpp +) + +set(modssb_HEADERS + ${PLUGIN_PREFIX}/ssbmod.h + ${PLUGIN_PREFIX}/ssbmodplugin.h + ${PLUGIN_PREFIX}/ssbmodsettings.h +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(modssbsrv SHARED + ${modssb_SOURCES} + ${modssb_HEADERS_MOC} +) + +target_link_libraries(modssbsrv + ${QT_LIBRARIES} + sdrbase + swagger +) + +target_link_libraries(modssbsrv Qt5::Core) + +install(TARGETS modssbsrv DESTINATION lib/pluginssrv/channeltx) \ No newline at end of file diff --git a/pluginssrv/channeltx/modwfm/CMakeLists.txt b/pluginssrv/channeltx/modwfm/CMakeLists.txt new file mode 100644 index 000000000..e8a8e145c --- /dev/null +++ b/pluginssrv/channeltx/modwfm/CMakeLists.txt @@ -0,0 +1,41 @@ +project(modwfm) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/channeltx/modwfm") + +set(modwfm_SOURCES + ${PLUGIN_PREFIX}/wfmmod.cpp + ${PLUGIN_PREFIX}/wfmmodplugin.cpp + ${PLUGIN_PREFIX}/wfmmodsettings.cpp +) + +set(modwfm_HEADERS + ${PLUGIN_PREFIX}/wfmmod.h + ${PLUGIN_PREFIX}/wfmmodplugin.h + ${PLUGIN_PREFIX}/wfmmodsettings.h +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(modwfmsrv SHARED + ${modwfm_SOURCES} + ${modwfm_HEADERS_MOC} +) + +target_link_libraries(modwfmsrv + ${QT_LIBRARIES} + sdrbase + swagger +) + +target_link_libraries(modwfmsrv Qt5::Core) + +install(TARGETS modwfmsrv DESTINATION lib/pluginssrv/channeltx) \ No newline at end of file diff --git a/pluginssrv/channeltx/udpsource/CMakeLists.txt b/pluginssrv/channeltx/udpsource/CMakeLists.txt new file mode 100644 index 000000000..767104379 --- /dev/null +++ b/pluginssrv/channeltx/udpsource/CMakeLists.txt @@ -0,0 +1,45 @@ +project(udpsource) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/channeltx/udpsource") + +set(udpsource_SOURCES + ${PLUGIN_PREFIX}/udpsource.cpp + ${PLUGIN_PREFIX}/udpsourceplugin.cpp + ${PLUGIN_PREFIX}/udpsourceudphandler.cpp + ${PLUGIN_PREFIX}/udpsourcemsg.cpp + ${PLUGIN_PREFIX}/udpsourcesettings.cpp +) + +set(udpsource_HEADERS + ${PLUGIN_PREFIX}/udpsource.h + ${PLUGIN_PREFIX}/udpsourceplugin.h + ${PLUGIN_PREFIX}/udpsourceudphandler.h + ${PLUGIN_PREFIX}/udpsourcemsg.h + ${PLUGIN_PREFIX}/udpsourcesettings.h +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(udpsourcesrv SHARED + ${udpsource_SOURCES} + ${udpsource_HEADERS_MOC} +) + +target_link_libraries(udpsourcesrv + ${QT_LIBRARIES} + sdrbase + swagger +) + +target_link_libraries(udpsourcesrv Qt5::Core Qt5::Network) + +install(TARGETS udpsourcesrv DESTINATION lib/pluginssrv/channeltx) diff --git a/pluginssrv/samplesink/CMakeLists.txt b/pluginssrv/samplesink/CMakeLists.txt index fc12191ee..ec27eee6c 100644 --- a/pluginssrv/samplesink/CMakeLists.txt +++ b/pluginssrv/samplesink/CMakeLists.txt @@ -2,6 +2,12 @@ project(samplesink) find_package(LibUSB) +find_package(LibBLADERF) +if(LIBUSB_FOUND AND LIBBLADERF_FOUND) + add_subdirectory(bladerf1output) + add_subdirectory(bladerf2output) +endif(LIBUSB_FOUND AND LIBBLADERF_FOUND) + find_package(LibHACKRF) if(LIBUSB_FOUND AND LIBHACKRF_FOUND) add_subdirectory(hackrfoutput) @@ -12,9 +18,23 @@ if(LIBUSB_FOUND AND LIMESUITE_FOUND) add_subdirectory(limesdroutput) endif(LIBUSB_FOUND AND LIMESUITE_FOUND) +find_package(LibIIO) +if(LIBUSB_FOUND AND LIBIIO_FOUND) + add_subdirectory(plutosdroutput) +endif(LIBUSB_FOUND AND LIBIIO_FOUND) + +find_package(CM256cc) +if(CM256CC_FOUND) + add_subdirectory(sdrdaemonsink) +endif(CM256CC_FOUND) + if (BUILD_DEBIAN) - add_subdirectory(limesdroutput) + add_subdirectory(bladerf1output) + add_subdirectory(bladerf2output) add_subdirectory(hackrfoutput) + add_subdirectory(limesdroutput) + add_subdirectory(plutosdroutput) + add_subdirectory(sdrdaemonsink) endif (BUILD_DEBIAN) add_subdirectory(filesink) diff --git a/pluginssrv/samplesink/bladerf1output/CMakeLists.txt b/pluginssrv/samplesink/bladerf1output/CMakeLists.txt new file mode 100644 index 000000000..f71592a84 --- /dev/null +++ b/pluginssrv/samplesink/bladerf1output/CMakeLists.txt @@ -0,0 +1,68 @@ +project(bladerf1output) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesink/bladerf1output") + +set(bladerf1output_SOURCES + ${PLUGIN_PREFIX}/bladerf1output.cpp + ${PLUGIN_PREFIX}/bladerf1outputplugin.cpp + ${PLUGIN_PREFIX}/bladerf1outputsettings.cpp + ${PLUGIN_PREFIX}/bladerf1outputthread.cpp +) + +set(bladerf1output_HEADERS + ${PLUGIN_PREFIX}/bladerf1output.h + ${PLUGIN_PREFIX}/bladerf1outputplugin.h + ${PLUGIN_PREFIX}/bladerf1outputsettings.h + ${PLUGIN_PREFIX}/bladerf1outputthread.h +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERFLIBSRC}/include + ${LIBBLADERFLIBSRC}/src +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERF_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(outputbladerf1srv SHARED + ${bladerf1output_SOURCES} + ${bladerf1output_HEADERS_MOC} +) + +if (BUILD_DEBIAN) +target_link_libraries(outputbladerf1srv + ${QT_LIBRARIES} + bladerf + sdrbase + swagger + bladerf1device +) +else (BUILD_DEBIAN) +target_link_libraries(outputbladerf1srv + ${QT_LIBRARIES} + ${LIBBLADERF_LIBRARIES} + sdrbase + swagger + bladerf1device +) +endif (BUILD_DEBIAN) + +target_link_libraries(outputbladerf1srv Qt5::Core) + +install(TARGETS outputbladerf1srv DESTINATION lib/pluginssrv/samplesink) diff --git a/pluginssrv/samplesink/bladerf2output/CMakeLists.txt b/pluginssrv/samplesink/bladerf2output/CMakeLists.txt new file mode 100644 index 000000000..69bc590cf --- /dev/null +++ b/pluginssrv/samplesink/bladerf2output/CMakeLists.txt @@ -0,0 +1,68 @@ +project(bladerf2output) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesink/bladerf2output") + +set(bladerf2output_SOURCES + ${PLUGIN_PREFIX}/bladerf2output.cpp + ${PLUGIN_PREFIX}/bladerf2outputplugin.cpp + ${PLUGIN_PREFIX}/bladerf2outputsettings.cpp + ${PLUGIN_PREFIX}/bladerf2outputthread.cpp +) + +set(bladerf2output_HEADERS + ${PLUGIN_PREFIX}/bladerf2output.h + ${PLUGIN_PREFIX}/bladerf2outputplugin.h + ${PLUGIN_PREFIX}/bladerf2outputsettings.h + ${PLUGIN_PREFIX}/bladerf2outputthread.h +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERFLIBSRC}/include + ${LIBBLADERFLIBSRC}/src +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERF_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(outputbladerf2srv SHARED + ${bladerf2output_SOURCES} + ${bladerf2output_HEADERS_MOC} +) + +if (BUILD_DEBIAN) +target_link_libraries(outputbladerf2srv + ${QT_LIBRARIES} + bladerf + sdrbase + swagger + bladerf2device +) +else (BUILD_DEBIAN) +target_link_libraries(outputbladerf2srv + ${QT_LIBRARIES} + ${LIBBLADERF_LIBRARIES} + sdrbase + swagger + bladerf2device +) +endif (BUILD_DEBIAN) + +target_link_libraries(outputbladerf2srv Qt5::Core) + +install(TARGETS outputbladerf2srv DESTINATION lib/pluginssrv/samplesink) diff --git a/pluginssrv/samplesink/filesink/CMakeLists.txt b/pluginssrv/samplesink/filesink/CMakeLists.txt index cfaf7ddfb..3efdee02c 100644 --- a/pluginssrv/samplesink/filesink/CMakeLists.txt +++ b/pluginssrv/samplesink/filesink/CMakeLists.txt @@ -20,6 +20,7 @@ set(filesink_HEADERS include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) @@ -38,6 +39,6 @@ target_link_libraries(outputfilesinksrv swagger ) -qt5_use_modules(outputfilesinksrv Core) +target_link_libraries(outputfilesinksrv Qt5::Core) install(TARGETS outputfilesinksrv DESTINATION lib/pluginssrv/samplesink) diff --git a/pluginssrv/samplesink/hackrfoutput/CMakeLists.txt b/pluginssrv/samplesink/hackrfoutput/CMakeLists.txt index e96887267..fcb5af765 100644 --- a/pluginssrv/samplesink/hackrfoutput/CMakeLists.txt +++ b/pluginssrv/samplesink/hackrfoutput/CMakeLists.txt @@ -21,6 +21,7 @@ if (BUILD_DEBIAN) include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${CMAKE_SOURCE_DIR}/devices ${LIBHACKRFSRC} @@ -30,6 +31,7 @@ else (BUILD_DEBIAN) include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${CMAKE_SOURCE_DIR}/devices ${LIBHACKRF_INCLUDE_DIR} @@ -63,6 +65,6 @@ target_link_libraries(outputhackrfsrv ) endif (BUILD_DEBIAN) -qt5_use_modules(outputhackrfsrv Core) +target_link_libraries(outputhackrfsrv Qt5::Core) install(TARGETS outputhackrfsrv DESTINATION lib/pluginssrv/samplesink) diff --git a/pluginssrv/samplesink/limesdroutput/CMakeLists.txt b/pluginssrv/samplesink/limesdroutput/CMakeLists.txt index 1fdde5e2a..4bda2a737 100644 --- a/pluginssrv/samplesink/limesdroutput/CMakeLists.txt +++ b/pluginssrv/samplesink/limesdroutput/CMakeLists.txt @@ -21,6 +21,7 @@ if (BUILD_DEBIAN) include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${CMAKE_SOURCE_DIR}/devices ${LIBLIMESUITESRC}/src @@ -37,6 +38,7 @@ else (BUILD_DEBIAN) include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${CMAKE_SOURCE_DIR}/devices ${LIMESUITE_INCLUDE_DIR} @@ -71,6 +73,6 @@ target_link_libraries(outputlimesdrsrv ) endif (BUILD_DEBIAN) -qt5_use_modules(outputlimesdrsrv Core) +target_link_libraries(outputlimesdrsrv Qt5::Core) install(TARGETS outputlimesdrsrv DESTINATION lib/pluginssrv/samplesink) diff --git a/pluginssrv/samplesink/plutosdroutput/CMakeLists.txt b/pluginssrv/samplesink/plutosdroutput/CMakeLists.txt new file mode 100644 index 000000000..cf668f5f4 --- /dev/null +++ b/pluginssrv/samplesink/plutosdroutput/CMakeLists.txt @@ -0,0 +1,66 @@ +project(plutosdroutput) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesink/plutosdroutput") + +set(plutosdroutput_SOURCES + ${PLUGIN_PREFIX}/plutosdroutput.cpp + ${PLUGIN_PREFIX}/plutosdroutputplugin.cpp + ${PLUGIN_PREFIX}/plutosdroutputsettings.cpp + ${PLUGIN_PREFIX}/plutosdroutputthread.cpp +) + +set(plutosdroutput_HEADERS + ${PLUGIN_PREFIX}/plutosdroutput.h + ${PLUGIN_PREFIX}/plutosdroutputplugin.h + ${PLUGIN_PREFIX}/plutosdroutputsettings.h + ${PLUGIN_PREFIX}/plutosdroutputthread.h +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBIIOSRC} +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBIIO_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(outputplutosdrsrv SHARED + ${plutosdroutput_SOURCES} + ${plutosdroutput_HEADERS_MOC} +) + +if (BUILD_DEBIAN) +target_link_libraries(outputplutosdrsrv + ${QT_LIBRARIES} + iio + sdrbase + swagger + plutosdrdevice +) +else (BUILD_DEBIAN) +target_link_libraries(outputplutosdrsrv + ${QT_LIBRARIES} + ${LIBIIO_LIBRARIES} + sdrbase + swagger + plutosdrdevice +) +endif (BUILD_DEBIAN) + +qt5_use_modules(outputplutosdrsrv Core) + +install(TARGETS outputplutosdrsrv DESTINATION lib/pluginssrv/samplesink) diff --git a/pluginssrv/samplesink/sdrdaemonsink/CMakeLists.txt b/pluginssrv/samplesink/sdrdaemonsink/CMakeLists.txt new file mode 100644 index 000000000..ace22f7d2 --- /dev/null +++ b/pluginssrv/samplesink/sdrdaemonsink/CMakeLists.txt @@ -0,0 +1,77 @@ +project(sdrdaemonsink) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesink/sdrdaemonsink") + +if (HAS_SSSE3) + message(STATUS "SDRdaemonFEC: use SSSE3 SIMD" ) +elseif (HAS_NEON) + message(STATUS "SDRdaemonFEC: use Neon SIMD" ) +else() + message(STATUS "SDRdaemonFEC: Unsupported architecture") + return() +endif() + +set(sdrdaemonsink_SOURCES + ${PLUGIN_PREFIX}/sdrdaemonsinkoutput.cpp + ${PLUGIN_PREFIX}/sdrdaemonsinkplugin.cpp + ${PLUGIN_PREFIX}/sdrdaemonsinksettings.cpp + ${PLUGIN_PREFIX}/sdrdaemonsinkthread.cpp + ${PLUGIN_PREFIX}/udpsinkfec.cpp + ${PLUGIN_PREFIX}/udpsinkfecworker.cpp +) + +set(sdrdaemonsink_HEADERS + ${PLUGIN_PREFIX}/sdrdaemonsinkoutput.h + ${PLUGIN_PREFIX}/sdrdaemonsinkplugin.h + ${PLUGIN_PREFIX}/sdrdaemonsinksettings.h + ${PLUGIN_PREFIX}/sdrdaemonsinkthread.h + ${PLUGIN_PREFIX}/udpsinkfec.h + ${PLUGIN_PREFIX}/udpsinkfecworker.h +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${LIBCM256CCSRC} +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${CM256CC_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(outputsdrdaemonsinksrv SHARED + ${sdrdaemonsink_SOURCES} + ${sdrdaemonsink_HEADERS_MOC} +) + +if (BUILD_DEBIAN) +target_link_libraries(outputsdrdaemonsinksrv + ${QT_LIBRARIES} + sdrbase + swagger + cm256cc +) +else (BUILD_DEBIAN) +target_link_libraries(outputsdrdaemonsinksrv + ${QT_LIBRARIES} + sdrbase + swagger + ${CM256CC_LIBRARIES} +) +endif (BUILD_DEBIAN) + +target_link_libraries(outputsdrdaemonsinksrv Qt5::Core) + +install(TARGETS outputsdrdaemonsinksrv DESTINATION lib/pluginssrv/samplesink) diff --git a/pluginssrv/samplesource/CMakeLists.txt b/pluginssrv/samplesource/CMakeLists.txt index 3cc921e76..8e23ea759 100644 --- a/pluginssrv/samplesource/CMakeLists.txt +++ b/pluginssrv/samplesource/CMakeLists.txt @@ -8,11 +8,36 @@ if(V4L-RTL) # add_subdirectory(v4l-rtl) endif() if(V4L-MSI) - FIND_LIBRARY (LIBV4L2 v4l2) + FIND_LIBRARY (LIBV4L2 v4l2) FIND_PATH (LIBV4L2H libv4l2.h) # add_subdirectory(v4l-msi) endif() +find_package(LibAIRSPY) +if(LIBUSB_FOUND AND LIBAIRSPY_FOUND) + add_subdirectory(airspy) +endif(LIBUSB_FOUND AND LIBAIRSPY_FOUND) + +find_package(LibAIRSPYHF) +if(LIBUSB_FOUND AND LIBAIRSPYHF_FOUND) + add_subdirectory(airspyhf) +endif(LIBUSB_FOUND AND LIBAIRSPYHF_FOUND) + +find_package(LibBLADERF) +if(LIBUSB_FOUND AND LIBBLADERF_FOUND) + add_subdirectory(bladerf1input) + add_subdirectory(bladerf2input) +endif(LIBUSB_FOUND AND LIBBLADERF_FOUND) + +if(LIBUSB_FOUND AND UNIX) + FIND_PATH (ASOUNDH alsa/asoundlib.h) + FIND_LIBRARY (LIBASOUND asound) +endif() +if(LIBASOUND AND ASOUNDH) + add_subdirectory(fcdpro) + add_subdirectory(fcdproplus) +endif() + find_package(LibHACKRF) if(LIBUSB_FOUND AND LIBHACKRF_FOUND) add_subdirectory(hackrfinput) @@ -23,15 +48,48 @@ if(LIBUSB_FOUND AND LIMESUITE_FOUND) add_subdirectory(limesdrinput) endif(LIBUSB_FOUND AND LIMESUITE_FOUND) +find_package(LibPerseus) +if(LIBUSB_FOUND AND RX_SAMPLE_24BIT AND LIBPERSEUS_FOUND) + message(STATUS "Server: add Persesus plugin") + add_subdirectory(perseus) +endif(LIBUSB_FOUND AND RX_SAMPLE_24BIT AND LIBPERSEUS_FOUND) + +find_package(LibIIO) +if(LIBUSB_FOUND AND LIBIIO_FOUND) + add_subdirectory(plutosdrinput) +endif(LIBUSB_FOUND AND LIBIIO_FOUND) + find_package(LibRTLSDR) if(LIBUSB_FOUND AND LIBRTLSDR_FOUND) add_subdirectory(rtlsdr) endif(LIBUSB_FOUND AND LIBRTLSDR_FOUND) +find_package(CM256cc) +if(CM256CC_FOUND) + add_subdirectory(sdrdaemonsource) +endif(CM256CC_FOUND) + +find_package(LibMiriSDR) +if(LIBUSB_FOUND AND LIBMIRISDR_FOUND) + add_subdirectory(sdrplay) + message(STATUS "LibMiriSDR found") +else(LIBUSB_FOUND AND LIBMIRISDR_FOUND) + message(STATUS "LibMiriSDR NOT found") +endif(LIBUSB_FOUND AND LIBMIRISDR_FOUND) + if (BUILD_DEBIAN) + add_subdirectory(airspy) + add_subdirectory(airspyhf) + add_subdirectory(bladerf1input) + add_subdirectory(bladerf2input) add_subdirectory(hackrfinput) add_subdirectory(limesdrinput) + add_subdirectory(perseus) + add_subdirectory(plutosdrinput) add_subdirectory(rtlsdr) + add_subdirectory(sdrdaemonsource) + add_subdirectory(sdrplay) endif (BUILD_DEBIAN) add_subdirectory(filesource) +add_subdirectory(testsource) diff --git a/pluginssrv/samplesource/airspy/CMakeLists.txt b/pluginssrv/samplesource/airspy/CMakeLists.txt new file mode 100644 index 000000000..cbc40b880 --- /dev/null +++ b/pluginssrv/samplesource/airspy/CMakeLists.txt @@ -0,0 +1,65 @@ +project(airspy) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesource/airspy") + +set(airspy_SOURCES + ${PLUGIN_PREFIX}/airspyinput.cpp + ${PLUGIN_PREFIX}/airspyplugin.cpp + ${PLUGIN_PREFIX}/airspysettings.cpp + ${PLUGIN_PREFIX}/airspythread.cpp +) + +set(airspy_HEADERS + ${PLUGIN_PREFIX}/airspyinput.h + ${PLUGIN_PREFIX}/airspyplugin.h + ${PLUGIN_PREFIX}/airspysettings.h + ${PLUGIN_PREFIX}/airspythread.h +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${LIBAIRSPYSRC} + ${LIBAIRSPYSRC}/libairspy/src +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${LIBAIRSPY_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +add_definitions("${QT_DEFINITIONS} -DLIBAIRSPY_DYN_RATES") +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(inputairspysrv SHARED + ${airspy_SOURCES} + ${airspy_HEADERS_MOC} +) + +if (BUILD_DEBIAN) +target_link_libraries(inputairspysrv + ${QT_LIBRARIES} + airspy + sdrbase + swagger +) +else (BUILD_DEBIAN) +target_link_libraries(inputairspysrv + ${QT_LIBRARIES} + ${LIBAIRSPY_LIBRARIES} + sdrbase + swagger +) +endif (BUILD_DEBIAN) + + +target_link_libraries(inputairspysrv Qt5::Core) + +install(TARGETS inputairspysrv DESTINATION lib/pluginssrv/samplesource) diff --git a/pluginssrv/samplesource/airspyhf/CMakeLists.txt b/pluginssrv/samplesource/airspyhf/CMakeLists.txt new file mode 100644 index 000000000..17a3ab6a9 --- /dev/null +++ b/pluginssrv/samplesource/airspyhf/CMakeLists.txt @@ -0,0 +1,64 @@ +project(airspyhf) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesource/airspyhf") + +set(airspyhf_SOURCES + ${PLUGIN_PREFIX}/airspyhfinput.cpp + ${PLUGIN_PREFIX}/airspyhfplugin.cpp + ${PLUGIN_PREFIX}/airspyhfsettings.cpp + ${PLUGIN_PREFIX}/airspyhfthread.cpp +) + +set(airspyhf_HEADERS + ${PLUGIN_PREFIX}/airspyhfinput.h + ${PLUGIN_PREFIX}/airspyhfplugin.h + ${PLUGIN_PREFIX}/airspyhfsettings.h + ${PLUGIN_PREFIX}/airspyhfthread.h +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${LIBAIRSPYHFSRC} + ${LIBAIRSPYHFSRC}/libairspyhf/src +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${LIBAIRSPYHF_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +add_definitions("${QT_DEFINITIONS} -DLIBAIRSPY_DYN_RATES") +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(inputairspyhfsrv SHARED + ${airspyhf_SOURCES} + ${airspyhf_HEADERS_MOC} +) + +if (BUILD_DEBIAN) +target_link_libraries(inputairspyhfsrv + ${QT_LIBRARIES} + airspyhf + sdrbase + swagger +) +else (BUILD_DEBIAN) +target_link_libraries(inputairspyhfsrv + ${QT_LIBRARIES} + ${LIBAIRSPYHF_LIBRARIES} + sdrbase + swagger +) +endif (BUILD_DEBIAN) + +qt5_use_modules(inputairspyhfsrv Core) + +install(TARGETS inputairspyhfsrv DESTINATION lib/pluginssrv/samplesource) diff --git a/pluginssrv/samplesource/bladerf1input/CMakeLists.txt b/pluginssrv/samplesource/bladerf1input/CMakeLists.txt new file mode 100644 index 000000000..eb73f4553 --- /dev/null +++ b/pluginssrv/samplesource/bladerf1input/CMakeLists.txt @@ -0,0 +1,68 @@ +project(bladerf1input) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesource/bladerf1input") + +set(bladerf1input_SOURCES + ${PLUGIN_PREFIX}/bladerf1input.cpp + ${PLUGIN_PREFIX}/bladerf1inputplugin.cpp + ${PLUGIN_PREFIX}/bladerf1inputsettings.cpp + ${PLUGIN_PREFIX}/bladerf1inputthread.cpp +) + +set(bladerf1input_HEADERS + ${PLUGIN_PREFIX}/bladerf1input.h + ${PLUGIN_PREFIX}/bladerf1inputplugin.h + ${PLUGIN_PREFIX}/bladerf1inputsettings.h + ${PLUGIN_PREFIX}/bladerf1inputthread.h +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERFLIBSRC}/include + ${LIBBLADERFLIBSRC}/src +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERF_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(inputbladerf1srv SHARED + ${bladerf1input_SOURCES} + ${bladerf1input_HEADERS_MOC} +) + +if (BUILD_DEBIAN) +target_link_libraries(inputbladerf1srv + ${QT_LIBRARIES} + bladerf + sdrbase + swagger + bladerf1device +) +else (BUILD_DEBIAN) +target_link_libraries(inputbladerf1srv + ${QT_LIBRARIES} + ${LIBBLADERF_LIBRARIES} + sdrbase + swagger + bladerf1device +) +endif (BUILD_DEBIAN) + +target_link_libraries(inputbladerf1srv Qt5::Core) + +install(TARGETS inputbladerf1srv DESTINATION lib/pluginssrv/samplesource) diff --git a/pluginssrv/samplesource/bladerf2input/CMakeLists.txt b/pluginssrv/samplesource/bladerf2input/CMakeLists.txt new file mode 100644 index 000000000..8341b6008 --- /dev/null +++ b/pluginssrv/samplesource/bladerf2input/CMakeLists.txt @@ -0,0 +1,68 @@ +project(bladerf2input) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesource/bladerf2input") + +set(bladerf2input_SOURCES + ${PLUGIN_PREFIX}/bladerf2input.cpp + ${PLUGIN_PREFIX}/bladerf2inputplugin.cpp + ${PLUGIN_PREFIX}/bladerf2inputsettings.cpp + ${PLUGIN_PREFIX}/bladerf2inputthread.cpp +) + +set(bladerf2input_HEADERS + ${PLUGIN_PREFIX}/bladerf2input.h + ${PLUGIN_PREFIX}/bladerf2inputplugin.h + ${PLUGIN_PREFIX}/bladerf2inputsettings.h + ${PLUGIN_PREFIX}/bladerf2inputthread.h +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERFLIBSRC}/include + ${LIBBLADERFLIBSRC}/src +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERF_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(inputbladerf2srv SHARED + ${bladerf2input_SOURCES} + ${bladerf2input_HEADERS_MOC} +) + +if (BUILD_DEBIAN) +target_link_libraries(inputbladerf2srv + ${QT_LIBRARIES} + bladerf + sdrbase + swagger + bladerf2device +) +else (BUILD_DEBIAN) +target_link_libraries(inputbladerf2srv + ${QT_LIBRARIES} + ${LIBBLADERF_LIBRARIES} + sdrbase + swagger + bladerf2device +) +endif (BUILD_DEBIAN) + +target_link_libraries(inputbladerf2srv Qt5::Core) + +install(TARGETS inputbladerf2srv DESTINATION lib/pluginssrv/samplesource) diff --git a/pluginssrv/samplesource/fcdpro/CMakeLists.txt b/pluginssrv/samplesource/fcdpro/CMakeLists.txt new file mode 100644 index 000000000..44ad593ad --- /dev/null +++ b/pluginssrv/samplesource/fcdpro/CMakeLists.txt @@ -0,0 +1,48 @@ +project(fcdpro) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesource/fcdpro") + +set(fcdpro_SOURCES + ${PLUGIN_PREFIX}/fcdproinput.cpp + ${PLUGIN_PREFIX}/fcdproplugin.cpp + ${PLUGIN_PREFIX}/fcdprosettings.cpp + ${PLUGIN_PREFIX}/fcdprothread.cpp +) + +set(fcdpro_HEADERS + ${PLUGIN_PREFIX}/fcdproinput.h + ${PLUGIN_PREFIX}/fcdproplugin.h + ${PLUGIN_PREFIX}/fcdprosettings.h + ${PLUGIN_PREFIX}/fcdprothread.h +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/fcdhid + ${CMAKE_SOURCE_DIR}/fcdlib +) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(inputfcdprosrv SHARED + ${fcdpro_SOURCES} + ${fcdpro_HEADERS_MOC} +) + +target_link_libraries(inputfcdprosrv + ${QT_LIBRARIES} + asound + fcdhid + fcdlib + sdrbase + swagger +) + +target_link_libraries(inputfcdprosrv Qt5::Core) + +install(TARGETS inputfcdprosrv DESTINATION lib/pluginssrv/samplesource) \ No newline at end of file diff --git a/pluginssrv/samplesource/fcdproplus/CMakeLists.txt b/pluginssrv/samplesource/fcdproplus/CMakeLists.txt new file mode 100644 index 000000000..623d259e8 --- /dev/null +++ b/pluginssrv/samplesource/fcdproplus/CMakeLists.txt @@ -0,0 +1,48 @@ +project(fcdproplus) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesource/fcdproplus") + +set(fcdproplus_SOURCES + ${PLUGIN_PREFIX}/fcdproplusinput.cpp + ${PLUGIN_PREFIX}/fcdproplusplugin.cpp + ${PLUGIN_PREFIX}/fcdproplussettings.cpp + ${PLUGIN_PREFIX}/fcdproplusthread.cpp +) + +set(fcdproplus_HEADERS + ${PLUGIN_PREFIX}/fcdproplusinput.h + ${PLUGIN_PREFIX}/fcdproplusplugin.h + ${PLUGIN_PREFIX}/fcdproplussettings.h + ${PLUGIN_PREFIX}/fcdproplusthread.h +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/fcdhid + ${CMAKE_SOURCE_DIR}/fcdlib +) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(inputfcdproplussrv SHARED + ${fcdproplus_SOURCES} + ${fcdproplus_HEADERS_MOC} +) + +target_link_libraries(inputfcdproplussrv + ${QT_LIBRARIES} + asound + fcdhid + fcdlib + sdrbase + swagger +) + +target_link_libraries(inputfcdproplussrv Qt5::Core) + +install(TARGETS inputfcdproplussrv DESTINATION lib/pluginssrv/samplesource) diff --git a/pluginssrv/samplesource/filesource/CMakeLists.txt b/pluginssrv/samplesource/filesource/CMakeLists.txt index 72c4f4250..edf9458a5 100644 --- a/pluginssrv/samplesource/filesource/CMakeLists.txt +++ b/pluginssrv/samplesource/filesource/CMakeLists.txt @@ -19,6 +19,7 @@ set(filesource_HEADERS include_directories( . + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) @@ -39,6 +40,6 @@ target_link_libraries(inputfilesourcesrv swagger ) -qt5_use_modules(inputfilesourcesrv Core) +target_link_libraries(inputfilesourcesrv Qt5::Core) install(TARGETS inputfilesourcesrv DESTINATION lib/pluginssrv/samplesource) diff --git a/pluginssrv/samplesource/hackrfinput/CMakeLists.txt b/pluginssrv/samplesource/hackrfinput/CMakeLists.txt index d1c67c8c5..61a8760ed 100644 --- a/pluginssrv/samplesource/hackrfinput/CMakeLists.txt +++ b/pluginssrv/samplesource/hackrfinput/CMakeLists.txt @@ -20,6 +20,7 @@ set(hackrfinput_HEADERS if (BUILD_DEBIAN) include_directories( . + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${CMAKE_SOURCE_DIR}/devices @@ -29,6 +30,7 @@ include_directories( else (BUILD_DEBIAN) include_directories( . + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${CMAKE_SOURCE_DIR}/devices @@ -63,6 +65,6 @@ target_link_libraries(inputhackrfsrv ) endif (BUILD_DEBIAN) -qt5_use_modules(inputhackrfsrv Core) +target_link_libraries(inputhackrfsrv Qt5::Core) install(TARGETS inputhackrfsrv DESTINATION lib/pluginssrv/samplesource) diff --git a/pluginssrv/samplesource/limesdrinput/CMakeLists.txt b/pluginssrv/samplesource/limesdrinput/CMakeLists.txt index b24dc0d4c..85c619849 100644 --- a/pluginssrv/samplesource/limesdrinput/CMakeLists.txt +++ b/pluginssrv/samplesource/limesdrinput/CMakeLists.txt @@ -21,6 +21,7 @@ if (BUILD_DEBIAN) include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${CMAKE_SOURCE_DIR}/devices ${LIBLIMESUITESRC}/src @@ -36,7 +37,8 @@ include_directories( else (BUILD_DEBIAN) include_directories( . - ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${CMAKE_SOURCE_DIR}/devices ${LIMESUITE_INCLUDE_DIR} @@ -71,6 +73,6 @@ target_link_libraries(inputlimesdrsrv ) endif (BUILD_DEBIAN) -qt5_use_modules(inputlimesdrsrv Core) +target_link_libraries(inputlimesdrsrv Qt5::Core) install(TARGETS inputlimesdrsrv DESTINATION lib/pluginssrv/samplesource) diff --git a/pluginssrv/samplesource/perseus/CMakeLists.txt b/pluginssrv/samplesource/perseus/CMakeLists.txt new file mode 100644 index 000000000..732db4f69 --- /dev/null +++ b/pluginssrv/samplesource/perseus/CMakeLists.txt @@ -0,0 +1,69 @@ +project(perseus) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesource/perseus") + +set(perseus_SOURCES + ${PLUGIN_PREFIX}/perseusinput.cpp + ${PLUGIN_PREFIX}/perseusplugin.cpp + ${PLUGIN_PREFIX}/perseussettings.cpp + ${PLUGIN_PREFIX}/perseusthread.cpp +) + +set(perseus_HEADERS + ${PLUGIN_PREFIX}/perseusinput.h + ${PLUGIN_PREFIX}/perseusplugin.h + ${PLUGIN_PREFIX}/perseussettings.h + ${PLUGIN_PREFIX}/perseusthread.h +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBPERSEUSSRC} + ${LIBPERSEUSSRC}/libperseus/src +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBPERSEUS_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +add_definitions("${QT_DEFINITIONS} -DLIBAIRSPY_DYN_RATES") +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(inputperseussrv SHARED + ${perseus_SOURCES} + ${perseus_HEADERS_MOC} +) + +if (BUILD_DEBIAN) +target_link_libraries(inputperseussrv + ${QT_LIBRARIES} + perseus + sdrbase + swagger + perseusdevice +) +else (BUILD_DEBIAN) +target_link_libraries(inputperseussrv + ${QT_LIBRARIES} + ${LIBPERSEUS_LIBRARIES} + sdrbase + swagger + perseusdevice +) +endif (BUILD_DEBIAN) + + +qt5_use_modules(inputperseussrv Core) + +install(TARGETS inputperseussrv DESTINATION lib/pluginssrv/samplesource) diff --git a/pluginssrv/samplesource/plutosdrinput/CMakeLists.txt b/pluginssrv/samplesource/plutosdrinput/CMakeLists.txt new file mode 100644 index 000000000..c66dccf4e --- /dev/null +++ b/pluginssrv/samplesource/plutosdrinput/CMakeLists.txt @@ -0,0 +1,66 @@ +project(plutosdrinput) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesource/plutosdrinput") + +set(plutosdrinput_SOURCES + ${PLUGIN_PREFIX}/plutosdrinput.cpp + ${PLUGIN_PREFIX}/plutosdrinputplugin.cpp + ${PLUGIN_PREFIX}/plutosdrinputsettings.cpp + ${PLUGIN_PREFIX}/plutosdrinputthread.cpp +) + +set(plutosdrinput_HEADERS + ${PLUGIN_PREFIX}/plutosdrinput.h + ${PLUGIN_PREFIX}/plutosdrinputplugin.h + ${PLUGIN_PREFIX}/plutosdrinputsettings.h + ${PLUGIN_PREFIX}/plutosdrinputthread.h +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBIIOSRC} +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBIIO_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(inputplutosdrsrv SHARED + ${plutosdrinput_SOURCES} + ${plutosdrinput_HEADERS_MOC} +) + +if (BUILD_DEBIAN) +target_link_libraries(inputplutosdrsrv + ${QT_LIBRARIES} + iio + sdrbase + swagger + plutosdrdevice +) +else (BUILD_DEBIAN) +target_link_libraries(inputplutosdrsrv + ${QT_LIBRARIES} + ${LIBIIO_LIBRARIES} + sdrbase + swagger + plutosdrdevice +) +endif (BUILD_DEBIAN) + +qt5_use_modules(inputplutosdrsrv Core) + +install(TARGETS inputplutosdrsrv DESTINATION lib/pluginssrv/samplesource) diff --git a/pluginssrv/samplesource/rtlsdr/CMakeLists.txt b/pluginssrv/samplesource/rtlsdr/CMakeLists.txt index 13462859b..705afd1e0 100644 --- a/pluginssrv/samplesource/rtlsdr/CMakeLists.txt +++ b/pluginssrv/samplesource/rtlsdr/CMakeLists.txt @@ -21,6 +21,7 @@ if (BUILD_DEBIAN) include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${LIBRTLSDRSRC}/include ${LIBRTLSDRSRC}/src @@ -29,6 +30,7 @@ else (BUILD_DEBIAN) include_directories( . ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${LIBRTLSDR_INCLUDE_DIR} ) @@ -61,6 +63,6 @@ target_link_libraries(inputrtlsdrsrv endif (BUILD_DEBIAN) -qt5_use_modules(inputrtlsdrsrv Core) +target_link_libraries(inputrtlsdrsrv Qt5::Core) install(TARGETS inputrtlsdrsrv DESTINATION lib/pluginssrv/samplesource) diff --git a/pluginssrv/samplesource/sdrdaemonsource/CMakeLists.txt b/pluginssrv/samplesource/sdrdaemonsource/CMakeLists.txt new file mode 100644 index 000000000..98de7ca6d --- /dev/null +++ b/pluginssrv/samplesource/sdrdaemonsource/CMakeLists.txt @@ -0,0 +1,75 @@ +project(sdrdaemonsource) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesource/sdrdaemonsource") + +if (HAS_SSSE3) + message(STATUS "SDRdaemonSource: use SSSE3 SIMD" ) +elseif (HAS_NEON) + message(STATUS "SDRdaemonSource: use Neon SIMD" ) +else() + message(STATUS "SDRdaemonSource: Unsupported architecture") + return() +endif() + +set(sdrdaemonsource_SOURCES + ${PLUGIN_PREFIX}/sdrdaemonsourcebuffer.cpp + ${PLUGIN_PREFIX}/sdrdaemonsourceinput.cpp + ${PLUGIN_PREFIX}/sdrdaemonsourcesettings.cpp + ${PLUGIN_PREFIX}/sdrdaemonsourceplugin.cpp + ${PLUGIN_PREFIX}/sdrdaemonsourceudphandler.cpp +) + +set(sdrdaemonsource_HEADERS + ${PLUGIN_PREFIX}/sdrdaemonsourcebuffer.h + ${PLUGIN_PREFIX}/sdrdaemonsourceinput.h + ${PLUGIN_PREFIX}/sdrdaemonsourcesettings.h + ${PLUGIN_PREFIX}/sdrdaemonsourceplugin.h + ${PLUGIN_PREFIX}/sdrdaemonsourceudphandler.h +) + + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(inputsdrdaemonsourcesrv SHARED + ${sdrdaemonsource_SOURCES} + ${sdrdaemonsource_HEADERS_MOC} +) + +if (BUILD_DEBIAN) +target_include_directories(inputsdrdaemonsourcesrv PUBLIC + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${LIBCM256CCSRC} +) +else (BUILD_DEBIAN) +target_include_directories(inputsdrdaemonsourcesrv PUBLIC + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CM256CC_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +if (BUILD_DEBIAN) +target_link_libraries(inputsdrdaemonsourcesrv + ${QT_LIBRARIES} + cm256cc + sdrbase + swagger +) +else (BUILD_DEBIAN) +target_link_libraries(inputsdrdaemonsourcesrv + ${QT_LIBRARIES} + ${CM256CC_LIBRARIES} + sdrbase + swagger +) +endif (BUILD_DEBIAN) + +target_link_libraries(inputsdrdaemonsourcesrv Qt5::Core) + +install(TARGETS inputsdrdaemonsourcesrv DESTINATION lib/pluginssrv/samplesource) diff --git a/pluginssrv/samplesource/sdrplay/CMakeLists.txt b/pluginssrv/samplesource/sdrplay/CMakeLists.txt new file mode 100644 index 000000000..67b3939ac --- /dev/null +++ b/pluginssrv/samplesource/sdrplay/CMakeLists.txt @@ -0,0 +1,63 @@ +project(sdrplay) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesource/sdrplay") + +set(sdrplay_SOURCES + ${PLUGIN_PREFIX}/sdrplayinput.cpp + ${PLUGIN_PREFIX}/sdrplayplugin.cpp + ${PLUGIN_PREFIX}/sdrplaysettings.cpp + ${PLUGIN_PREFIX}/sdrplaythread.cpp +) + +set(sdrplay_HEADERS + ${PLUGIN_PREFIX}/sdrplayinput.h + ${PLUGIN_PREFIX}/sdrplayplugin.h + ${PLUGIN_PREFIX}/sdrplaysettings.h + ${PLUGIN_PREFIX}/sdrplaythread.h +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${LIBMIRISDRSRC}/include + ${LIBMIRISDRSRC}/src +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${LIBMIRISDR_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(inputsdrplaysrv SHARED + ${sdrplay_SOURCES} + ${sdrplay_HEADERS_MOC} +) + +if (BUILD_DEBIAN) +target_link_libraries(inputsdrplaysrv + ${QT_LIBRARIES} + mirisdr + sdrbase + swagger +) +else (BUILD_DEBIAN) +target_link_libraries(inputsdrplaysrv + ${QT_LIBRARIES} + ${LIBMIRISDR_LIBRARIES} + sdrbase + swagger +) +endif (BUILD_DEBIAN) + +target_link_libraries(inputsdrplaysrv Qt5::Core) + +install(TARGETS inputsdrplaysrv DESTINATION lib/pluginssrv/samplesource) diff --git a/pluginssrv/samplesource/testsource/CMakeLists.txt b/pluginssrv/samplesource/testsource/CMakeLists.txt new file mode 100644 index 000000000..8b1dc76c2 --- /dev/null +++ b/pluginssrv/samplesource/testsource/CMakeLists.txt @@ -0,0 +1,43 @@ +project(testsource) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(PLUGIN_PREFIX "../../../plugins/samplesource/testsource") + +set(testsource_SOURCES + ${PLUGIN_PREFIX}/testsourceinput.cpp + ${PLUGIN_PREFIX}/testsourceplugin.cpp + ${PLUGIN_PREFIX}/testsourcethread.cpp + ${PLUGIN_PREFIX}/testsourcesettings.cpp +) + +set(testsource_HEADERS + ${PLUGIN_PREFIX}/testsourceinput.h + ${PLUGIN_PREFIX}/testsourceplugin.h + ${PLUGIN_PREFIX}/testsourcethread.h + ${PLUGIN_PREFIX}/testsourcesettings.h +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +add_library(inputtestsourcesrv SHARED + ${testsource_SOURCES} + ${testsource_HEADERS_MOC} +) + +target_link_libraries(inputtestsourcesrv + ${QT_LIBRARIES} + sdrbase + swagger +) + +target_link_libraries(inputtestsourcesrv Qt5::Core) + +install(TARGETS inputtestsourcesrv DESTINATION lib/pluginssrv/samplesource) diff --git a/qrtplib/CMakeLists.txt b/qrtplib/CMakeLists.txt index 65dcc3bb7..784e7f504 100644 --- a/qrtplib/CMakeLists.txt +++ b/qrtplib/CMakeLists.txt @@ -1,41 +1,95 @@ project(qrtplib) -set(qrtplib_SOURCES - rtptimeutilities.cpp - rtprandomurandom.cpp - rtprandomrand48.cpp - rtprandom.cpp - rtperrors.cpp -) +set (qrtplib_HEADERS + rtcpapppacket.h + rtcpbyepacket.h + rtcpcompoundpacket.h + rtcpcompoundpacketbuilder.h + rtcppacket.h + rtcppacketbuilder.h + rtcprrpacket.h + rtcpscheduler.h + rtcpsdesinfo.h + rtcpsdespacket.h + rtcpsrpacket.h + rtcpunknownpacket.h + rtpaddress.h + rtpcollisionlist.h + rtpconfig.h + rtpdefines.h + rtpendian.h + rtperrors.h + rtpinternalsourcedata.h + rtpkeyhashtable.h + rtppacket.h + rtppacketbuilder.h + rtprandom.h + rtprandomrand48.h + rtprandomrands.h + rtprandomurandom.h + rtprawpacket.h + rtpsession.h + rtpsessionparams.h + rtpsessionsources.h + rtpsourcedata.h + rtpsources.h + rtpstructs.h + rtptimeutilities.h + rtptransmitter.h + rtptypes_win.h + rtptypes.h + rtpudptransmitter.h + rtpsocketutil.h + ) -set(qrtplib_HEADERS - rtptimeutilities.h - rtprandom.h - rtprandomurandom.h - rtprandomrand48.h - rtprandom.h - rtpinternalutils.h - rtperrors.h - rtpdefines.h -) +set(qrtplib_SOURCES + rtcpapppacket.cpp + rtcpbyepacket.cpp + rtcpcompoundpacket.cpp + rtcpcompoundpacketbuilder.cpp + rtcppacketbuilder.cpp + rtcprrpacket.cpp + rtcpscheduler.cpp + rtcpsdesinfo.cpp + rtcpsdespacket.cpp + rtcpsrpacket.cpp + rtpaddress.cpp + rtpcollisionlist.cpp + rtperrors.cpp + rtpinternalsourcedata.cpp + rtppacket.cpp + rtppacketbuilder.cpp + rtprandom.cpp + rtprandomrand48.cpp + rtprandomrands.cpp + rtprandomurandom.cpp + rtpsession.cpp + rtpsessionparams.cpp + rtpsessionsources.cpp + rtpsourcedata.cpp + rtpsources.cpp + rtptimeutilities.cpp + rtpudptransmitter.cpp + ) include_directories( - . - ${CMAKE_CURRENT_BINARY_DIR} + . + ${CMAKE_SOURCE_DIR}/exports + ${CMAKE_CURRENT_BINARY_DIR} ) add_definitions(${QT_DEFINITIONS}) add_definitions(-DQT_SHARED) add_library(qrtplib SHARED - ${qrtplib_SOURCES} - ${qrtplib_HEADERS_MOC} + ${qrtplib_SOURCES} + ${qrtplib_HEADERS_MOC} ) target_link_libraries(qrtplib - ${QT_LIBRARIES} + ${QT_LIBRARIES} ) -qt5_use_modules(qrtplib Core Network) +target_link_libraries(qrtplib Qt5::Core Qt5::Network) install(TARGETS qrtplib DESTINATION lib) diff --git a/qrtplib/qrtplib.pro b/qrtplib/qrtplib.pro new file mode 100644 index 000000000..adfffceca --- /dev/null +++ b/qrtplib/qrtplib.pro @@ -0,0 +1,93 @@ +#-------------------------------------------------------- +# +# Pro file for Windows builds with Qt Creator +# +#-------------------------------------------------------- + +QT += core network + +TEMPLATE = lib +TARGET = qrtplib + +INCLUDEPATH += $$PWD +INCLUDEPATH += ../exports + +QMAKE_CXXFLAGS += -std=c++11 + +CONFIG(MSVC):DEFINES += qrtplib_EXPORTS + +CONFIG(Release):build_subdir = release +CONFIG(Debug):build_subdir = debug + +# Enable very detailed debug messages when compiling the debug version +CONFIG(debug, debug|release) { + DEFINES += SUPERVERBOSE +} + +HEADERS += $$PWD/rtcpapppacket.h \ + $$PWD/rtcpbyepacket.h \ + $$PWD/rtcpcompoundpacket.h \ + $$PWD/rtcpcompoundpacketbuilder.h \ + $$PWD/rtcppacket.h \ + $$PWD/rtcppacketbuilder.h \ + $$PWD/rtcprrpacket.h \ + $$PWD/rtcpscheduler.h \ + $$PWD/rtcpsdesinfo.h \ + $$PWD/rtcpsdespacket.h \ + $$PWD/rtcpsrpacket.h \ + $$PWD/rtcpunknownpacket.h \ + $$PWD/rtpaddress.h \ + $$PWD/rtpcollisionlist.h \ + $$PWD/rtpconfig.h \ + $$PWD/rtpdefines.h \ + $$PWD/rtpendian.h \ + $$PWD/rtperrors.h \ + $$PWD/rtpinternalsourcedata.h \ + $$PWD/rtppacket.h \ + $$PWD/rtppacketbuilder.h \ + $$PWD/rtprandom.h \ + $$PWD/rtprandomrand48.h \ + $$PWD/rtprandomrands.h \ + $$PWD/rtprandomurandom.h \ + $$PWD/rtprawpacket.h \ + $$PWD/rtpsession.h \ + $$PWD/rtpsessionparams.h \ + $$PWD/rtpsessionsources.h \ + $$PWD/rtpsourcedata.h \ + $$PWD/rtpsources.h \ + $$PWD/rtpstructs.h \ + $$PWD/rtptimeutilities.h \ + $$PWD/rtptransmitter.h \ + $$PWD/rtptypes_win.h \ + $$PWD/rtptypes.h \ + $$PWD/rtpudptransmitter.h \ + $$PWD/rtpsocketutil.h + + +SOURCES += $$PWD/rtcpapppacket.cpp \ + $$PWD/rtcpbyepacket.cpp \ + $$PWD/rtcpcompoundpacket.cpp \ + $$PWD/rtcpcompoundpacketbuilder.cpp \ + $$PWD/rtcppacketbuilder.cpp \ + $$PWD/rtcprrpacket.cpp \ + $$PWD/rtcpscheduler.cpp \ + $$PWD/rtcpsdesinfo.cpp \ + $$PWD/rtcpsdespacket.cpp \ + $$PWD/rtcpsrpacket.cpp \ + $$PWD/rtpaddress.cpp \ + $$PWD/rtpcollisionlist.cpp \ + $$PWD/rtperrors.cpp \ + $$PWD/rtpinternalsourcedata.cpp \ + $$PWD/rtppacket.cpp \ + $$PWD/rtppacketbuilder.cpp \ + $$PWD/rtprandom.cpp \ + $$PWD/rtprandomrand48.cpp \ + $$PWD/rtprandomrands.cpp \ + $$PWD/rtprandomurandom.cpp \ + $$PWD/rtpsession.cpp \ + $$PWD/rtpsessionparams.cpp \ + $$PWD/rtpsessionsources.cpp \ + $$PWD/rtpsourcedata.cpp \ + $$PWD/rtpsources.cpp \ + $$PWD/rtptimeutilities.cpp \ + $$PWD/rtpudptransmitter.cpp diff --git a/qrtplib/rtcpapppacket.cpp b/qrtplib/rtcpapppacket.cpp new file mode 100644 index 000000000..07737797e --- /dev/null +++ b/qrtplib/rtcpapppacket.cpp @@ -0,0 +1,65 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +#include "rtcpapppacket.h" + +namespace qrtplib +{ + +RTCPAPPPacket::RTCPAPPPacket(uint8_t *data, std::size_t datalength) : + RTCPPacket(APP, data, datalength) +{ + knownformat = false; + + RTCPCommonHeader *hdr; + std::size_t len = datalength; + + hdr = (RTCPCommonHeader *) data; + if (hdr->padding) + { + uint8_t padcount = data[datalength - 1]; + if ((padcount & 0x03) != 0) // not a multiple of four! (see rfc 3550 p 37) + return; + if (((std::size_t) padcount) >= len) + return; + len -= (std::size_t) padcount; + } + + if (len < (sizeof(RTCPCommonHeader) + sizeof(uint32_t) * 2)) + return; + len -= (sizeof(RTCPCommonHeader) + sizeof(uint32_t) * 2); + appdatalen = len; + knownformat = true; +} + +} // end namespace + diff --git a/qrtplib/rtcpapppacket.h b/qrtplib/rtcpapppacket.h new file mode 100644 index 000000000..6a94597e4 --- /dev/null +++ b/qrtplib/rtcpapppacket.h @@ -0,0 +1,132 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +/** + * \file rtcpapppacket.h + */ + +#ifndef RTCPAPPPACKET_H + +#define RTCPAPPPACKET_H + +#include "rtpconfig.h" +#include "rtcppacket.h" +#include "rtpstructs.h" +#include "rtpendian.h" + +#include "export.h" + +namespace qrtplib +{ + +class RTCPCompoundPacket; + +/** Describes an RTCP APP packet. */ +class QRTPLIB_API RTCPAPPPacket: public RTCPPacket +{ +public: + /** Creates an instance based on the data in \c data with length \c datalen. + * Creates an instance based on the data in \c data with length \c datalen. Since the \c data pointer + * is referenced inside the class (no copy of the data is made) one must make sure that the memory it + * points to is valid as long as the class instance exists. + */ + RTCPAPPPacket(uint8_t *data, std::size_t datalen); + ~RTCPAPPPacket() + { + } + + /** Returns the subtype contained in the APP packet. */ + uint8_t GetSubType() const; + + /** Returns the SSRC of the source which sent this packet. */ + uint32_t GetSSRC() const; + + /** Returns the name contained in the APP packet. + * Returns the name contained in the APP packet. This alway consists of four bytes and is not NULL-terminated. + */ + uint8_t *GetName(); + + /** Returns a pointer to the actual data. */ + uint8_t *GetAPPData(); + + /** Returns the length of the actual data. */ + std::size_t GetAPPDataLength() const; +private: + RTPEndian m_endian; + std::size_t appdatalen; +}; + +inline uint8_t RTCPAPPPacket::GetSubType() const +{ + if (!knownformat) + return 0; + RTCPCommonHeader *hdr = (RTCPCommonHeader *) data; + return hdr->count; +} + +inline uint32_t RTCPAPPPacket::GetSSRC() const +{ + if (!knownformat) + return 0; + + uint32_t *ssrc = (uint32_t *) (data + sizeof(RTCPCommonHeader)); + return m_endian.qToHost(*ssrc); +} + +inline uint8_t *RTCPAPPPacket::GetName() +{ + if (!knownformat) + return 0; + + return (data + sizeof(RTCPCommonHeader) + sizeof(uint32_t)); +} + +inline uint8_t *RTCPAPPPacket::GetAPPData() +{ + if (!knownformat) + return 0; + if (appdatalen == 0) + return 0; + return (data + sizeof(RTCPCommonHeader) + sizeof(uint32_t) * 2); +} + +inline std::size_t RTCPAPPPacket::GetAPPDataLength() const +{ + if (!knownformat) + return 0; + return appdatalen; +} + +} // end namespace + +#endif // RTCPAPPPACKET_H + diff --git a/qrtplib/rtcpbyepacket.cpp b/qrtplib/rtcpbyepacket.cpp new file mode 100644 index 000000000..23d1f5eec --- /dev/null +++ b/qrtplib/rtcpbyepacket.cpp @@ -0,0 +1,73 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +#include "rtcpbyepacket.h" + +namespace qrtplib +{ + +RTCPBYEPacket::RTCPBYEPacket(uint8_t *data, std::size_t datalength) : + RTCPPacket(BYE, data, datalength) +{ + knownformat = false; + reasonoffset = 0; + + RTCPCommonHeader *hdr; + std::size_t len = datalength; + + hdr = (RTCPCommonHeader *) data; + if (hdr->padding) + { + uint8_t padcount = data[datalength - 1]; + if ((padcount & 0x03) != 0) // not a multiple of four! (see rfc 3550 p 37) + return; + if (((std::size_t) padcount) >= len) + return; + len -= (std::size_t) padcount; + } + + std::size_t ssrclen = ((std::size_t)(hdr->count)) * sizeof(uint32_t) + sizeof(RTCPCommonHeader); + if (ssrclen > len) + return; + if (ssrclen < len) // there's probably a reason for leaving + { + uint8_t *reasonlength = (data + ssrclen); + std::size_t reaslen = (std::size_t)(*reasonlength); + if (reaslen > (len - ssrclen - 1)) + return; + reasonoffset = ssrclen; + } + knownformat = true; +} + +} // end namespace + diff --git a/qrtplib/rtcpbyepacket.h b/qrtplib/rtcpbyepacket.h new file mode 100644 index 000000000..16947943d --- /dev/null +++ b/qrtplib/rtcpbyepacket.h @@ -0,0 +1,140 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +/** + * \file rtcpbyepacket.h + */ + +#ifndef RTCPBYEPACKET_H + +#define RTCPBYEPACKET_H + +#include "rtpconfig.h" +#include "rtcppacket.h" +#include "rtpstructs.h" +#include "rtpendian.h" + +#include "export.h" + +namespace qrtplib +{ + +class RTCPCompoundPacket; + +/** Describes an RTCP BYE packet. */ +class QRTPLIB_API RTCPBYEPacket: public RTCPPacket +{ +public: + /** Creates an instance based on the data in \c data with length \c datalen. + * Creates an instance based on the data in \c data with length \c datalen. Since the \c data pointer + * is referenced inside the class (no copy of the data is made) one must make sure that the memory it + * points to is valid as long as the class instance exists. + */ + RTCPBYEPacket(uint8_t *data, std::size_t datalen); + ~RTCPBYEPacket() + { + } + + /** Returns the number of SSRC identifiers present in this BYE packet. */ + int GetSSRCCount() const; + + /** Returns the SSRC described by \c index which may have a value from 0 to GetSSRCCount()-1 + * (note that no check is performed to see if \c index is valid). + */ + uint32_t GetSSRC(int index) const; // note: no check is performed to see if index is valid! + + /** Returns true if the BYE packet contains a reason for leaving. */ + bool HasReasonForLeaving() const; + + /** Returns the length of the string which describes why the source(s) left. */ + std::size_t GetReasonLength() const; + + /** Returns the actual reason for leaving data. */ + uint8_t *GetReasonData(); + +private: + RTPEndian m_endian; + std::size_t reasonoffset; +}; + +inline int RTCPBYEPacket::GetSSRCCount() const +{ + if (!knownformat) + return 0; + + RTCPCommonHeader *hdr = (RTCPCommonHeader *) data; + return (int) (hdr->count); +} + +inline uint32_t RTCPBYEPacket::GetSSRC(int index) const +{ + if (!knownformat) + return 0; + uint32_t *ssrc = (uint32_t *) (data + sizeof(RTCPCommonHeader) + sizeof(uint32_t) * index); + return m_endian.qToHost(*ssrc); +} + +inline bool RTCPBYEPacket::HasReasonForLeaving() const +{ + if (!knownformat) + return false; + if (reasonoffset == 0) + return false; + return true; +} + +inline std::size_t RTCPBYEPacket::GetReasonLength() const +{ + if (!knownformat) + return 0; + if (reasonoffset == 0) + return 0; + uint8_t *reasonlen = (data + reasonoffset); + return (std::size_t)(*reasonlen); +} + +inline uint8_t *RTCPBYEPacket::GetReasonData() +{ + if (!knownformat) + return 0; + if (reasonoffset == 0) + return 0; + uint8_t *reasonlen = (data + reasonoffset); + if ((*reasonlen) == 0) + return 0; + return (data + reasonoffset + 1); +} + +} // end namespace + +#endif // RTCPBYEPACKET_H + diff --git a/qrtplib/rtcpcompoundpacket.cpp b/qrtplib/rtcpcompoundpacket.cpp new file mode 100644 index 000000000..c0e12e472 --- /dev/null +++ b/qrtplib/rtcpcompoundpacket.cpp @@ -0,0 +1,206 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +#include "rtcpcompoundpacket.h" +#include "rtprawpacket.h" +#include "rtperrors.h" +#include "rtpstructs.h" +#include "rtpdefines.h" +#include "rtcpsrpacket.h" +#include "rtcprrpacket.h" +#include "rtcpsdespacket.h" +#include "rtcpbyepacket.h" +#include "rtcpapppacket.h" +#include "rtcpunknownpacket.h" + +namespace qrtplib +{ + +RTCPCompoundPacket::RTCPCompoundPacket(RTPRawPacket &rawpack) +{ + compoundpacket = 0; + compoundpacketlength = 0; + error = 0; + + if (rawpack.IsRTP()) + { + error = ERR_RTP_RTCPCOMPOUND_INVALIDPACKET; + return; + } + + uint8_t *data = rawpack.GetData(); + std::size_t datalen = rawpack.GetDataLength(); + + error = ParseData(data, datalen); + + if (error < 0) { + return; + } + + compoundpacket = rawpack.GetData(); + compoundpacketlength = rawpack.GetDataLength(); + + rtcppackit = rtcppacklist.begin(); +} + +RTCPCompoundPacket::RTCPCompoundPacket(uint8_t *packet, std::size_t packetlen) +{ + compoundpacket = 0; + compoundpacketlength = 0; + + error = ParseData(packet, packetlen); + + if (error < 0) { + return; + } + + compoundpacket = packet; + compoundpacketlength = packetlen; + + rtcppackit = rtcppacklist.begin(); +} + +RTCPCompoundPacket::RTCPCompoundPacket() +{ + compoundpacket = 0; + compoundpacketlength = 0; + error = 0; +} + +int RTCPCompoundPacket::ParseData(uint8_t *data, std::size_t datalen) +{ + bool first; + + if (datalen < sizeof(RTCPCommonHeader)) + return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET; + + first = true; + + do + { + RTCPCommonHeader *rtcphdr; + std::size_t length; + + rtcphdr = (RTCPCommonHeader *) data; + if (rtcphdr->version != RTP_VERSION) // check version + { + ClearPacketList(); + return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET; + } + if (first) + { + // Check if first packet is SR or RR + + first = false; + if (!(rtcphdr->packettype == RTP_RTCPTYPE_SR || rtcphdr->packettype == RTP_RTCPTYPE_RR)) + { + ClearPacketList(); + return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET; + } + } + + length = (std::size_t) m_endian.qToHost(rtcphdr->length); + length++; + length *= sizeof(uint32_t); + + if (length > datalen) // invalid length field + { + ClearPacketList(); + return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET; + } + + if (rtcphdr->padding) + { + // check if it's the last packet + if (length != datalen) + { + ClearPacketList(); + return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET; + } + } + + RTCPPacket *p; + + switch (rtcphdr->packettype) + { + case RTP_RTCPTYPE_SR: + p = new RTCPSRPacket(data, length); + break; + case RTP_RTCPTYPE_RR: + p = new RTCPRRPacket(data, length); + break; + case RTP_RTCPTYPE_SDES: + p = new RTCPSDESPacket(data, length); + break; + case RTP_RTCPTYPE_BYE: + p = new RTCPBYEPacket(data, length); + break; + case RTP_RTCPTYPE_APP: + p = new RTCPAPPPacket(data, length); + break; + default: + p = new RTCPUnknownPacket(data, length); + } + + rtcppacklist.push_back(p); + + datalen -= length; + data += length; + } while (datalen >= (std::size_t) sizeof(RTCPCommonHeader)); + + if (datalen != 0) // some remaining bytes + { + ClearPacketList(); + return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET; + } + return 0; +} + +RTCPCompoundPacket::~RTCPCompoundPacket() +{ + ClearPacketList(); +} + +void RTCPCompoundPacket::ClearPacketList() +{ + std::list::const_iterator it; + + for (it = rtcppacklist.begin(); it != rtcppacklist.end(); it++) { + delete *it; + } + + rtcppacklist.clear(); + rtcppackit = rtcppacklist.begin(); +} + +} // end namespace + diff --git a/qrtplib/rtcpcompoundpacket.h b/qrtplib/rtcpcompoundpacket.h new file mode 100644 index 000000000..461a263b8 --- /dev/null +++ b/qrtplib/rtcpcompoundpacket.h @@ -0,0 +1,125 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +/** + * \file rtcpcompoundpacket.h + */ + +#ifndef RTCPCOMPOUNDPACKET_H + +#define RTCPCOMPOUNDPACKET_H + +#include "rtpconfig.h" +#include "rtptypes.h" +#include "rtpendian.h" +#include + +#include "export.h" + +namespace qrtplib +{ + +class RTPRawPacket; +class RTCPPacket; + +/** Represents an RTCP compound packet. */ +class QRTPLIB_API RTCPCompoundPacket +{ +public: + /** Creates an RTCPCompoundPacket instance from the data in \c rawpack, installing a memory manager if specified. */ + RTCPCompoundPacket(RTPRawPacket &rawpack); + + /** Creates an RTCPCompoundPacket instance from the data in \c packet}, with size \c len. */ + RTCPCompoundPacket(uint8_t *packet, std::size_t len); +protected: + RTCPCompoundPacket(); // this is for the compoundpacket builder +public: + virtual ~RTCPCompoundPacket(); + + /** Checks if the RTCP compound packet was created successfully. + * If the raw packet data in the constructor could not be parsed, this function returns the error code of + * what went wrong. If the packet had an invalid format, the return value is \c ERR_RTP_RTCPCOMPOUND_INVALIDPACKET. + */ + int GetCreationError() + { + return error; + } + + /** Returns a pointer to the data of the entire RTCP compound packet. */ + uint8_t *GetCompoundPacketData() + { + return compoundpacket; + } + + /** Returns the size of the entire RTCP compound packet. */ + std::size_t GetCompoundPacketLength() + { + return compoundpacketlength; + } + + /** Starts the iteration over the individual RTCP packets in the RTCP compound packet. */ + void GotoFirstPacket() + { + rtcppackit = rtcppacklist.begin(); + } + + /** Returns a pointer to the next individual RTCP packet. + * Returns a pointer to the next individual RTCP packet. Note that no \c delete call may be done + * on the RTCPPacket instance which is returned. + */ + RTCPPacket *GetNextPacket() + { + if (rtcppackit == rtcppacklist.end()) + return 0; + RTCPPacket *p = *rtcppackit; + rtcppackit++; + return p; + } + +protected: + void ClearPacketList(); + int ParseData(uint8_t *packet, std::size_t len); + + RTPEndian m_endian; + int error; + + uint8_t *compoundpacket; + std::size_t compoundpacketlength; + + std::list rtcppacklist; + std::list::const_iterator rtcppackit; +}; + +} // end namespace + +#endif // RTCPCOMPOUNDPACKET_H + diff --git a/qrtplib/rtcpcompoundpacketbuilder.cpp b/qrtplib/rtcpcompoundpacketbuilder.cpp new file mode 100644 index 000000000..bfb4b5ee2 --- /dev/null +++ b/qrtplib/rtcpcompoundpacketbuilder.cpp @@ -0,0 +1,632 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +#include "rtcpcompoundpacketbuilder.h" +#include "rtcpsrpacket.h" +#include "rtcprrpacket.h" +#include "rtcpsdespacket.h" +#include "rtcpbyepacket.h" +#include "rtcpapppacket.h" +#include + +namespace qrtplib +{ + +RTCPCompoundPacketBuilder::RTCPCompoundPacketBuilder() +{ + byesize = 0; + appsize = 0; + maximumpacketsize = 0; + buffer = 0; + external = false; + arebuilding = false; +} + +RTCPCompoundPacketBuilder::~RTCPCompoundPacketBuilder() +{ + if (external) + compoundpacket = 0; // make sure RTCPCompoundPacket doesn't delete the external buffer + ClearBuildBuffers(); +} + +void RTCPCompoundPacketBuilder::ClearBuildBuffers() +{ + report.Clear(); + sdes.Clear(); + + std::list::const_iterator it; + for (it = byepackets.begin(); it != byepackets.end(); it++) + { + if ((*it).packetdata) + delete[] (*it).packetdata; + } + for (it = apppackets.begin(); it != apppackets.end(); it++) + { + if ((*it).packetdata) + delete[] (*it).packetdata; + } + byepackets.clear(); + apppackets.clear(); + byesize = 0; + appsize = 0; +} + +int RTCPCompoundPacketBuilder::InitBuild(std::size_t maxpacketsize) +{ + if (arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILDING; + if (compoundpacket) + return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILT; + + if (maxpacketsize < RTP_MINPACKETSIZE) + return ERR_RTP_RTCPCOMPPACKBUILDER_MAXPACKETSIZETOOSMALL; + + maximumpacketsize = maxpacketsize; + buffer = 0; + external = false; + byesize = 0; + appsize = 0; + + arebuilding = true; + return 0; +} + +int RTCPCompoundPacketBuilder::InitBuild(void *externalbuffer, std::size_t buffersize) +{ + if (arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILDING; + if (compoundpacket) + return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILT; + + if (buffersize < RTP_MINPACKETSIZE) + return ERR_RTP_RTCPCOMPPACKBUILDER_BUFFERSIZETOOSMALL; + + maximumpacketsize = buffersize; + buffer = (uint8_t *) externalbuffer; + external = true; + byesize = 0; + appsize = 0; + + arebuilding = true; + return 0; +} + +int RTCPCompoundPacketBuilder::StartSenderReport(uint32_t senderssrc, const RTPNTPTime &ntptimestamp, uint32_t rtptimestamp, uint32_t packetcount, uint32_t octetcount) +{ + if (!arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; + + if (report.headerlength != 0) + return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYGOTREPORT; + + std::size_t totalsize = byesize + appsize + sdes.NeededBytes(); + std::size_t sizeleft = maximumpacketsize - totalsize; + std::size_t neededsize = sizeof(RTCPCommonHeader) + sizeof(uint32_t) + sizeof(RTCPSenderReport); + + if (neededsize > sizeleft) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; + + // fill in some things + + report.headerlength = sizeof(uint32_t) + sizeof(RTCPSenderReport); + report.isSR = true; + + uint32_t *ssrc = (uint32_t *) report.headerdata; + *ssrc = qToBigEndian(senderssrc); + + RTCPSenderReport *sr = (RTCPSenderReport *) (report.headerdata + sizeof(uint32_t)); + sr->ntptime_msw = qToBigEndian(ntptimestamp.GetMSW()); + sr->ntptime_lsw = qToBigEndian(ntptimestamp.GetLSW()); + sr->rtptimestamp = qToBigEndian(rtptimestamp); + sr->packetcount = qToBigEndian(packetcount); + sr->octetcount = qToBigEndian(octetcount); + + return 0; +} + +int RTCPCompoundPacketBuilder::StartReceiverReport(uint32_t senderssrc) +{ + if (!arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; + if (report.headerlength != 0) + return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYGOTREPORT; + + std::size_t totalsize = byesize + appsize + sdes.NeededBytes(); + std::size_t sizeleft = maximumpacketsize - totalsize; + std::size_t neededsize = sizeof(RTCPCommonHeader) + sizeof(uint32_t); + + if (neededsize > sizeleft) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; + + // fill in some things + + report.headerlength = sizeof(uint32_t); + report.isSR = false; + + uint32_t *ssrc = (uint32_t *) report.headerdata; + *ssrc = qToBigEndian(senderssrc); + + return 0; +} + +int RTCPCompoundPacketBuilder::AddReportBlock(uint32_t ssrc, uint8_t fractionlost, int32_t packetslost, uint32_t exthighestseq, uint32_t jitter, uint32_t lsr, uint32_t dlsr) +{ + if (!arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; + if (report.headerlength == 0) + return ERR_RTP_RTCPCOMPPACKBUILDER_REPORTNOTSTARTED; + + std::size_t totalothersize = byesize + appsize + sdes.NeededBytes(); + std::size_t reportsizewithextrablock = report.NeededBytesWithExtraReportBlock(); + + if ((totalothersize + reportsizewithextrablock) > maximumpacketsize) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; + + uint8_t *buf = new uint8_t[sizeof(RTCPReceiverReport)]; + + RTCPReceiverReport *rr = (RTCPReceiverReport *) buf; + uint32_t *packlost = (uint32_t *) &packetslost; + uint32_t packlost2 = (*packlost); + + rr->ssrc = qToBigEndian(ssrc); + rr->fractionlost = fractionlost; + rr->packetslost[2] = (uint8_t) (packlost2 & 0xFF); + rr->packetslost[1] = (uint8_t) ((packlost2 >> 8) & 0xFF); + rr->packetslost[0] = (uint8_t) ((packlost2 >> 16) & 0xFF); + rr->exthighseqnr = qToBigEndian(exthighestseq); + rr->jitter = qToBigEndian(jitter); + rr->lsr = qToBigEndian(lsr); + rr->dlsr = qToBigEndian(dlsr); + + report.reportblocks.push_back(Buffer(buf, sizeof(RTCPReceiverReport))); + return 0; +} + +int RTCPCompoundPacketBuilder::AddSDESSource(uint32_t ssrc) +{ + if (!arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; + + std::size_t totalotherbytes = byesize + appsize + report.NeededBytes(); + std::size_t sdessizewithextrasource = sdes.NeededBytesWithExtraSource(); + + if ((totalotherbytes + sdessizewithextrasource) > maximumpacketsize) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; + + int status; + + if ((status = sdes.AddSSRC(ssrc)) < 0) + return status; + return 0; +} + +int RTCPCompoundPacketBuilder::AddSDESNormalItem(RTCPSDESPacket::ItemType t, const void *itemdata, uint8_t itemlength) +{ + if (!arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; + if (sdes.sdessources.empty()) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOCURRENTSOURCE; + + uint8_t itemid; + + switch (t) + { + case RTCPSDESPacket::CNAME: + itemid = RTCP_SDES_ID_CNAME; + break; + case RTCPSDESPacket::NAME: + itemid = RTCP_SDES_ID_NAME; + break; + case RTCPSDESPacket::EMAIL: + itemid = RTCP_SDES_ID_EMAIL; + break; + case RTCPSDESPacket::PHONE: + itemid = RTCP_SDES_ID_PHONE; + break; + case RTCPSDESPacket::LOC: + itemid = RTCP_SDES_ID_LOCATION; + break; + case RTCPSDESPacket::TOOL: + itemid = RTCP_SDES_ID_TOOL; + break; + case RTCPSDESPacket::NOTE: + itemid = RTCP_SDES_ID_NOTE; + break; + default: + return ERR_RTP_RTCPCOMPPACKBUILDER_INVALIDITEMTYPE; + } + + std::size_t totalotherbytes = byesize + appsize + report.NeededBytes(); + std::size_t sdessizewithextraitem = sdes.NeededBytesWithExtraItem(itemlength); + + if ((sdessizewithextraitem + totalotherbytes) > maximumpacketsize) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; + + uint8_t *buf; + std::size_t len; + + buf = new uint8_t[sizeof(RTCPSDESHeader) + (std::size_t) itemlength]; + len = sizeof(RTCPSDESHeader) + (std::size_t) itemlength; + + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (buf); + + sdeshdr->sdesid = itemid; + sdeshdr->length = itemlength; + if (itemlength != 0) + memcpy((buf + sizeof(RTCPSDESHeader)), itemdata, (std::size_t) itemlength); + + sdes.AddItem(buf, len); + return 0; +} + +#ifdef RTP_SUPPORT_SDESPRIV +int RTCPCompoundPacketBuilder::AddSDESPrivateItem(const void *prefixdata, uint8_t prefixlength, const void *valuedata, uint8_t valuelength) +{ + if (!arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; + if (sdes.sdessources.empty()) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOCURRENTSOURCE; + + std::size_t itemlength = ((std::size_t) prefixlength) + 1 + ((std::size_t) valuelength); + if (itemlength > 255) + return ERR_RTP_RTCPCOMPPACKBUILDER_TOTALITEMLENGTHTOOBIG; + + std::size_t totalotherbytes = byesize + appsize + report.NeededBytes(); + std::size_t sdessizewithextraitem = sdes.NeededBytesWithExtraItem(itemlength); + + if ((sdessizewithextraitem + totalotherbytes) > maximumpacketsize) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; + + uint8_t *buf; + std::size_t len; + + buf = new uint8_t[sizeof(RTCPSDESHeader) + itemlength]; + len = sizeof(RTCPSDESHeader) + (std::size_t) itemlength; + + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (buf); + + sdeshdr->sdesid = RTCP_SDES_ID_PRIVATE; + sdeshdr->length = itemlength; + + buf[sizeof(RTCPSDESHeader)] = prefixlength; + if (prefixlength != 0) + memcpy((buf + sizeof(RTCPSDESHeader) + 1), prefixdata, (std::size_t) prefixlength); + if (valuelength != 0) + memcpy((buf + sizeof(RTCPSDESHeader) + 1 + (std::size_t) prefixlength), valuedata, (std::size_t) valuelength); + + sdes.AddItem(buf, len); + return 0; +} +#endif // RTP_SUPPORT_SDESPRIV + +int RTCPCompoundPacketBuilder::AddBYEPacket(uint32_t *ssrcs, uint8_t numssrcs, const void *reasondata, uint8_t reasonlength) +{ + if (!arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; + + if (numssrcs > 31) + return ERR_RTP_RTCPCOMPPACKBUILDER_TOOMANYSSRCS; + + std::size_t packsize = sizeof(RTCPCommonHeader) + sizeof(uint32_t) * ((std::size_t) numssrcs); + std::size_t zerobytes = 0; + + if (reasonlength > 0) + { + packsize += 1; // 1 byte for the length; + packsize += (std::size_t) reasonlength; + + std::size_t r = (packsize & 0x03); + if (r != 0) + { + zerobytes = 4 - r; + packsize += zerobytes; + } + } + + std::size_t totalotherbytes = appsize + byesize + sdes.NeededBytes() + report.NeededBytes(); + + if ((totalotherbytes + packsize) > maximumpacketsize) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; + + uint8_t *buf; + std::size_t numwords; + + buf = new uint8_t[packsize]; + + RTCPCommonHeader *hdr = (RTCPCommonHeader *) buf; + + hdr->version = 2; + hdr->padding = 0; + hdr->count = numssrcs; + + numwords = packsize / sizeof(uint32_t); + hdr->length = qToBigEndian((uint16_t) (numwords - 1)); + hdr->packettype = RTP_RTCPTYPE_BYE; + + uint32_t *sources = (uint32_t *) (buf + sizeof(RTCPCommonHeader)); + uint8_t srcindex; + + for (srcindex = 0; srcindex < numssrcs; srcindex++) + sources[srcindex] = qToBigEndian(ssrcs[srcindex]); + + if (reasonlength != 0) + { + std::size_t offset = sizeof(RTCPCommonHeader) + ((std::size_t) numssrcs) * sizeof(uint32_t); + + buf[offset] = reasonlength; + memcpy((buf + offset + 1), reasondata, (std::size_t) reasonlength); + for (std::size_t i = 0; i < zerobytes; i++) + buf[packsize - 1 - i] = 0; + } + + byepackets.push_back(Buffer(buf, packsize)); + byesize += packsize; + + return 0; +} + +int RTCPCompoundPacketBuilder::AddAPPPacket(uint8_t subtype, uint32_t ssrc, const uint8_t name[4], const void *appdata, std::size_t appdatalen) +{ + if (!arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; + if (subtype > 31) + return ERR_RTP_RTCPCOMPPACKBUILDER_ILLEGALSUBTYPE; + if ((appdatalen % 4) != 0) + return ERR_RTP_RTCPCOMPPACKBUILDER_ILLEGALAPPDATALENGTH; + + std::size_t appdatawords = appdatalen / 4; + + if ((appdatawords + 2) > 65535) + return ERR_RTP_RTCPCOMPPACKBUILDER_APPDATALENTOOBIG; + + std::size_t packsize = sizeof(RTCPCommonHeader) + sizeof(uint32_t) * 2 + appdatalen; + std::size_t totalotherbytes = appsize + byesize + sdes.NeededBytes() + report.NeededBytes(); + + if ((totalotherbytes + packsize) > maximumpacketsize) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT; + + uint8_t *buf; + + buf = new uint8_t[packsize]; + + RTCPCommonHeader *hdr = (RTCPCommonHeader *) buf; + + hdr->version = 2; + hdr->padding = 0; + hdr->count = subtype; + + hdr->length = qToBigEndian((uint16_t) (appdatawords + 2)); + hdr->packettype = RTP_RTCPTYPE_APP; + + uint32_t *source = (uint32_t *) (buf + sizeof(RTCPCommonHeader)); + *source = qToBigEndian(ssrc); + + buf[sizeof(RTCPCommonHeader) + sizeof(uint32_t) + 0] = name[0]; + buf[sizeof(RTCPCommonHeader) + sizeof(uint32_t) + 1] = name[1]; + buf[sizeof(RTCPCommonHeader) + sizeof(uint32_t) + 2] = name[2]; + buf[sizeof(RTCPCommonHeader) + sizeof(uint32_t) + 3] = name[3]; + + if (appdatalen > 0) + memcpy((buf + sizeof(RTCPCommonHeader) + sizeof(uint32_t) * 2), appdata, appdatalen); + + apppackets.push_back(Buffer(buf, packsize)); + appsize += packsize; + + return 0; +} + +int RTCPCompoundPacketBuilder::EndBuild() +{ + if (!arebuilding) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING; + if (report.headerlength == 0) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOREPORTPRESENT; + + uint8_t *buf; + std::size_t len; + + len = appsize + byesize + report.NeededBytes() + sdes.NeededBytes(); + + if (!external) + { + buf = new uint8_t[len]; + } + else + buf = buffer; + + uint8_t *curbuf = buf; + RTCPPacket *p; + + // first, we'll add all report info + + { + bool firstpacket = true; + bool done = false; + std::list::const_iterator it = report.reportblocks.begin(); + do + { + RTCPCommonHeader *hdr = (RTCPCommonHeader *) curbuf; + std::size_t offset; + + hdr->version = 2; + hdr->padding = 0; + + if (firstpacket && report.isSR) + { + hdr->packettype = RTP_RTCPTYPE_SR; + memcpy((curbuf + sizeof(RTCPCommonHeader)), report.headerdata, report.headerlength); + offset = sizeof(RTCPCommonHeader) + report.headerlength; + } + else + { + hdr->packettype = RTP_RTCPTYPE_RR; + memcpy((curbuf + sizeof(RTCPCommonHeader)), report.headerdata, sizeof(uint32_t)); + offset = sizeof(RTCPCommonHeader) + sizeof(uint32_t); + } + firstpacket = false; + + uint8_t count = 0; + + while (it != report.reportblocks.end() && count < 31) + { + memcpy(curbuf + offset, (*it).packetdata, (*it).packetlength); + offset += (*it).packetlength; + count++; + it++; + } + + std::size_t numwords = offset / sizeof(uint32_t); + + hdr->length = qToBigEndian((uint16_t) (numwords - 1)); + hdr->count = count; + + // add entry in parent's list + if (hdr->packettype == RTP_RTCPTYPE_SR) + p = new RTCPSRPacket(curbuf, offset); + else + p = new RTCPRRPacket(curbuf, offset); + rtcppacklist.push_back(p); + + curbuf += offset; + if (it == report.reportblocks.end()) + done = true; + } while (!done); + } + + // then, we'll add the sdes info + + if (!sdes.sdessources.empty()) + { + bool done = false; + std::list::const_iterator sourceit = sdes.sdessources.begin(); + + do + { + RTCPCommonHeader *hdr = (RTCPCommonHeader *) curbuf; + std::size_t offset = sizeof(RTCPCommonHeader); + + hdr->version = 2; + hdr->padding = 0; + hdr->packettype = RTP_RTCPTYPE_SDES; + + uint8_t sourcecount = 0; + + while (sourceit != sdes.sdessources.end() && sourcecount < 31) + { + uint32_t *ssrc = (uint32_t *) (curbuf + offset); + *ssrc = qToBigEndian((*sourceit)->ssrc); + offset += sizeof(uint32_t); + + std::list::const_iterator itemit, itemend; + + itemit = (*sourceit)->items.begin(); + itemend = (*sourceit)->items.end(); + while (itemit != itemend) + { + memcpy(curbuf + offset, (*itemit).packetdata, (*itemit).packetlength); + offset += (*itemit).packetlength; + itemit++; + } + + curbuf[offset] = 0; // end of item list; + offset++; + + std::size_t r = offset & 0x03; + if (r != 0) // align to 32 bit boundary + { + std::size_t num = 4 - r; + std::size_t i; + + for (i = 0; i < num; i++) + curbuf[offset + i] = 0; + offset += num; + } + + sourceit++; + sourcecount++; + } + + std::size_t numwords = offset / 4; + + hdr->count = sourcecount; + hdr->length = qToBigEndian((uint16_t) (numwords - 1)); + + p = new RTCPSDESPacket(curbuf, offset); + rtcppacklist.push_back(p); + + curbuf += offset; + if (sourceit == sdes.sdessources.end()) + done = true; + } while (!done); + } + + // adding the app data + + { + std::list::const_iterator it; + + for (it = apppackets.begin(); it != apppackets.end(); it++) + { + memcpy(curbuf, (*it).packetdata, (*it).packetlength); + + p = new RTCPAPPPacket(curbuf, (*it).packetlength); + rtcppacklist.push_back(p); + + curbuf += (*it).packetlength; + } + } + + // adding bye packets + + { + std::list::const_iterator it; + + for (it = byepackets.begin(); it != byepackets.end(); it++) + { + memcpy(curbuf, (*it).packetdata, (*it).packetlength); + + p = new RTCPBYEPacket(curbuf, (*it).packetlength); + rtcppacklist.push_back(p); + + curbuf += (*it).packetlength; + } + } + + compoundpacket = buf; + compoundpacketlength = len; + arebuilding = false; + ClearBuildBuffers(); + return 0; +} + +} // end namespace + diff --git a/qrtplib/rtcpcompoundpacketbuilder.h b/qrtplib/rtcpcompoundpacketbuilder.h new file mode 100644 index 000000000..7daff9dc1 --- /dev/null +++ b/qrtplib/rtcpcompoundpacketbuilder.h @@ -0,0 +1,404 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +/** + * \file rtcpcompoundpacketbuilder.h + */ + +#ifndef RTCPCOMPOUNDPACKETBUILDER_H + +#define RTCPCOMPOUNDPACKETBUILDER_H + +#include "rtpconfig.h" +#include "rtcpcompoundpacket.h" +#include "rtptimeutilities.h" +#include "rtcpsdespacket.h" +#include "rtperrors.h" +#include "rtpendian.h" +#include + +#include "export.h" + +namespace qrtplib +{ + +/** This class can be used to construct an RTCP compound packet. + * The RTCPCompoundPacketBuilder class can be used to construct an RTCP compound packet. It inherits the member + * functions of RTCPCompoundPacket which can be used to access the information in the compound packet once it has + * been built successfully. The member functions described below return \c ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT + * if the action would cause the maximum allowed size to be exceeded. + */ +class QRTPLIB_API RTCPCompoundPacketBuilder: public RTCPCompoundPacket +{ +public: + /** Constructs an RTCPCompoundPacketBuilder instance, optionally installing a memory manager. */ + RTCPCompoundPacketBuilder(); + ~RTCPCompoundPacketBuilder(); + + /** Starts building an RTCP compound packet with maximum size \c maxpacketsize. + * Starts building an RTCP compound packet with maximum size \c maxpacketsize. New memory will be allocated + * to store the packet. + */ + int InitBuild(std::size_t maxpacketsize); + + /** Starts building a RTCP compound packet. + * Starts building a RTCP compound packet. Data will be stored in \c externalbuffer which + * can contain \c buffersize bytes. + */ + int InitBuild(void *externalbuffer, std::size_t buffersize); + + /** Adds a sender report to the compound packet. + * Tells the packet builder that the packet should start with a sender report which will contain + * the sender information specified by this function's arguments. Once the sender report is started, + * report blocks can be added using the AddReportBlock function. + */ + int StartSenderReport(uint32_t senderssrc, const RTPNTPTime &ntptimestamp, uint32_t rtptimestamp, uint32_t packetcount, uint32_t octetcount); + + /** Adds a receiver report to the compound packet. + * Tells the packet builder that the packet should start with a receiver report which will contain + * he sender SSRC \c senderssrc. Once the sender report is started, report blocks can be added using the + * AddReportBlock function. + */ + int StartReceiverReport(uint32_t senderssrc); + + /** Adds the report block information specified by the function's arguments. + * Adds the report block information specified by the function's arguments. If more than 31 report blocks + * are added, the builder will automatically use a new RTCP receiver report packet. + */ + int AddReportBlock(uint32_t ssrc, uint8_t fractionlost, int32_t packetslost, uint32_t exthighestseq, uint32_t jitter, uint32_t lsr, uint32_t dlsr); + + /** Starts an SDES chunk for participant \c ssrc. */ + int AddSDESSource(uint32_t ssrc); + + /** Adds a normal (non-private) SDES item of type \c t to the current SDES chunk. + * Adds a normal (non-private) SDES item of type \c t to the current SDES chunk. The item's value + * will have length \c itemlength and will contain the data \c itemdata. + */ + int AddSDESNormalItem(RTCPSDESPacket::ItemType t, const void *itemdata, uint8_t itemlength); +#ifdef RTP_SUPPORT_SDESPRIV + /** Adds an SDES PRIV item described by the function's arguments to the current SDES chunk. */ + int AddSDESPrivateItem(const void *prefixdata, uint8_t prefixlength, const void *valuedata, uint8_t valuelength); +#endif // RTP_SUPPORT_SDESPRIV + + /** Adds a BYE packet to the compound packet. + * Adds a BYE packet to the compound packet. It will contain \c numssrcs source identifiers specified in + * \c ssrcs and will indicate as reason for leaving the string of length \c reasonlength + * containing data \c reasondata. + */ + int AddBYEPacket(uint32_t *ssrcs, uint8_t numssrcs, const void *reasondata, uint8_t reasonlength); + + /** Adds the APP packet specified by the arguments to the compound packet. + * Adds the APP packet specified by the arguments to the compound packet. Note that \c appdatalen has to be + * a multiple of four. + */ + int AddAPPPacket(uint8_t subtype, uint32_t ssrc, const uint8_t name[4], const void *appdata, std::size_t appdatalen); + + /** Finishes building the compound packet. + * Finishes building the compound packet. If successful, the RTCPCompoundPacket member functions + * can be used to access the RTCP packet data. + */ + int EndBuild(); + +private: + class Buffer + { + public: + Buffer() : + packetdata(0), packetlength(0) + { + } + Buffer(uint8_t *data, std::size_t len) : + packetdata(data), packetlength(len) + { + } + + uint8_t *packetdata; + std::size_t packetlength; + }; + + class Report + { + public: + Report() + { + headerdata = (uint8_t *) headerdata32; + isSR = false; + headerlength = 0; + } + ~Report() + { + Clear(); + } + + void Clear() + { + std::list::const_iterator it; + for (it = reportblocks.begin(); it != reportblocks.end(); it++) + { + if ((*it).packetdata) + delete[] (*it).packetdata; + } + reportblocks.clear(); + isSR = false; + headerlength = 0; + } + + std::size_t NeededBytes() + { + std::size_t x, n, d, r; + n = reportblocks.size(); + if (n == 0) + { + if (headerlength == 0) + return 0; + x = sizeof(RTCPCommonHeader) + headerlength; + } + else + { + x = n * sizeof(RTCPReceiverReport); + d = n / 31; // max 31 reportblocks per report + r = n % 31; + if (r != 0) + d++; + x += d * (sizeof(RTCPCommonHeader) + sizeof(uint32_t)); /* header and SSRC */ + if (isSR) + x += sizeof(RTCPSenderReport); + } + return x; + } + + std::size_t NeededBytesWithExtraReportBlock() + { + std::size_t x, n, d, r; + n = reportblocks.size() + 1; // +1 for the extra block + x = n * sizeof(RTCPReceiverReport); + d = n / 31; // max 31 reportblocks per report + r = n % 31; + if (r != 0) + d++; + x += d * (sizeof(RTCPCommonHeader) + sizeof(uint32_t)); /* header and SSRC */ + if (isSR) + x += sizeof(RTCPSenderReport); + return x; + } + + bool isSR; + + uint8_t *headerdata; + uint32_t headerdata32[(sizeof(uint32_t) + sizeof(RTCPSenderReport)) / sizeof(uint32_t)]; // either for ssrc and sender info or just ssrc + std::size_t headerlength; + std::list reportblocks; + }; + + class SDESSource + { + public: + SDESSource(uint32_t s) : + ssrc(s), totalitemsize(0) + { + } + ~SDESSource() + { + std::list::const_iterator it; + for (it = items.begin(); it != items.end(); it++) + { + if ((*it).packetdata) + delete[] (*it).packetdata; + } + items.clear(); + } + + std::size_t NeededBytes() + { + std::size_t x, r; + x = totalitemsize + 1; // +1 for the 0 byte which terminates the item list + r = x % sizeof(uint32_t); + if (r != 0) + x += (sizeof(uint32_t) - r); // make sure it ends on a 32 bit boundary + x += sizeof(uint32_t); // for ssrc + return x; + } + + std::size_t NeededBytesWithExtraItem(uint8_t itemdatalength) + { + std::size_t x, r; + x = totalitemsize + sizeof(RTCPSDESHeader) + (std::size_t) itemdatalength + 1; + r = x % sizeof(uint32_t); + if (r != 0) + x += (sizeof(uint32_t) - r); // make sure it ends on a 32 bit boundary + x += sizeof(uint32_t); // for ssrc + return x; + } + + void AddItem(uint8_t *buf, std::size_t len) + { + Buffer b(buf, len); + totalitemsize += len; + items.push_back(b); + } + + uint32_t ssrc; + std::list items; + private: + std::size_t totalitemsize; + }; + + class SDES + { + public: + SDES() + { + sdesit = sdessources.end(); + } + ~SDES() + { + Clear(); + } + + void Clear() + { + std::list::const_iterator it; + + for (it = sdessources.begin(); it != sdessources.end(); it++) + delete *it; + sdessources.clear(); + } + + int AddSSRC(uint32_t ssrc) + { + SDESSource *s = new SDESSource(ssrc); + sdessources.push_back(s); + sdesit = sdessources.end(); + sdesit--; + return 0; + } + + int AddItem(uint8_t *buf, std::size_t len) + { + if (sdessources.empty()) + return ERR_RTP_RTCPCOMPPACKBUILDER_NOCURRENTSOURCE; + (*sdesit)->AddItem(buf, len); + return 0; + } + + std::size_t NeededBytes() + { + std::list::const_iterator it; + std::size_t x = 0; + std::size_t n, d, r; + + if (sdessources.empty()) + return 0; + + for (it = sdessources.begin(); it != sdessources.end(); it++) + x += (*it)->NeededBytes(); + n = sdessources.size(); + d = n / 31; + r = n % 31; + if (r != 0) + d++; + x += d * sizeof(RTCPCommonHeader); + return x; + } + + std::size_t NeededBytesWithExtraItem(uint8_t itemdatalength) + { + std::list::const_iterator it; + std::size_t x = 0; + std::size_t n, d, r; + + if (sdessources.empty()) + return 0; + + for (it = sdessources.begin(); it != sdesit; it++) + x += (*it)->NeededBytes(); + x += (*sdesit)->NeededBytesWithExtraItem(itemdatalength); + n = sdessources.size(); + d = n / 31; + r = n % 31; + if (r != 0) + d++; + x += d * sizeof(RTCPCommonHeader); + return x; + } + + std::size_t NeededBytesWithExtraSource() + { + std::list::const_iterator it; + std::size_t x = 0; + std::size_t n, d, r; + + if (sdessources.empty()) + return 0; + + for (it = sdessources.begin(); it != sdessources.end(); it++) + x += (*it)->NeededBytes(); + + // for the extra source we'll need at least 8 bytes (ssrc and four 0 bytes) + x += sizeof(uint32_t) * 2; + + n = sdessources.size() + 1; // also, the number of sources will increase + d = n / 31; + r = n % 31; + if (r != 0) + d++; + x += d * sizeof(RTCPCommonHeader); + return x; + } + + std::list sdessources; + private: + std::list::const_iterator sdesit; + }; + + RTPEndian m_endian; + std::size_t maximumpacketsize; + uint8_t *buffer; + bool external; + bool arebuilding; + + Report report; + SDES sdes; + + std::list byepackets; + std::size_t byesize; + + std::list apppackets; + std::size_t appsize; + + void ClearBuildBuffers(); +}; + +} // end namespace + +#endif // RTCPCOMPOUNDPACKETBUILDER_H + diff --git a/qrtplib/rtcppacket.h b/qrtplib/rtcppacket.h new file mode 100644 index 000000000..84092e340 --- /dev/null +++ b/qrtplib/rtcppacket.h @@ -0,0 +1,110 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +/** + * \file rtcppacket.h + */ + +#ifndef RTCPPACKET_H + +#define RTCPPACKET_H + +#include "rtpconfig.h" +#include "rtptypes.h" +#include + +namespace qrtplib +{ + +class RTCPCompoundPacket; + +/** Base class for specific types of RTCP packets. */ +class RTCPPacket +{ +public: + /** Identifies the specific kind of RTCP packet. */ + enum PacketType + { + SR, /**< An RTCP sender report. */ + RR, /**< An RTCP receiver report. */ + SDES, /**< An RTCP source description packet. */ + BYE, /**< An RTCP bye packet. */ + APP, /**< An RTCP packet containing application specific data. */ + Unknown /**< The type of RTCP packet was not recognized. */ + }; +protected: + RTCPPacket(PacketType t, uint8_t *d, std::size_t dlen) : + data(d), datalen(dlen), packettype(t) + { + knownformat = false; + } +public: + virtual ~RTCPPacket() + { + } + + /** Returns \c true if the subclass was able to interpret the data and \c false otherwise. */ + bool IsKnownFormat() const + { + return knownformat; + } + + /** Returns the actual packet type which the subclass implements. */ + PacketType GetPacketType() const + { + return packettype; + } + + /** Returns a pointer to the data of this RTCP packet. */ + uint8_t *GetPacketData() + { + return data; + } + + /** Returns the length of this RTCP packet. */ + std::size_t GetPacketLength() const + { + return datalen; + } + +protected: + uint8_t *data; + std::size_t datalen; + bool knownformat; +private: + const PacketType packettype; +}; + +} // end namespace + +#endif // RTCPPACKET_H + diff --git a/qrtplib/rtcppacketbuilder.cpp b/qrtplib/rtcppacketbuilder.cpp new file mode 100644 index 000000000..70cd75cd1 --- /dev/null +++ b/qrtplib/rtcppacketbuilder.cpp @@ -0,0 +1,738 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +#include "rtcppacketbuilder.h" +#include "rtpsources.h" +#include "rtppacketbuilder.h" +#include "rtcpscheduler.h" +#include "rtpsourcedata.h" +#include "rtcpcompoundpacketbuilder.h" + +namespace qrtplib +{ + +RTCPPacketBuilder::RTCPPacketBuilder(RTPSources &s, RTPPacketBuilder &pb) : + sources(s), rtppacketbuilder(pb), prevbuildtime(0, 0), transmissiondelay(0, 0) +{ + init = false; + timeinit.Dummy(); +} + +RTCPPacketBuilder::~RTCPPacketBuilder() +{ + Destroy(); +} + +int RTCPPacketBuilder::Init(std::size_t maxpacksize, double tsunit, const void *cname, std::size_t cnamelen) +{ + if (init) + return ERR_RTP_RTCPPACKETBUILDER_ALREADYINIT; + if (maxpacksize < RTP_MINPACKETSIZE) + return ERR_RTP_RTCPPACKETBUILDER_ILLEGALMAXPACKSIZE; + if (tsunit < 0.0) + return ERR_RTP_RTCPPACKETBUILDER_ILLEGALTIMESTAMPUNIT; + + if (cnamelen > 255) + cnamelen = 255; + + maxpacketsize = maxpacksize; + timestampunit = tsunit; + + int status; + + if ((status = ownsdesinfo.SetCNAME((const uint8_t *) cname, cnamelen)) < 0) + return status; + + ClearAllSourceFlags(); + + interval_name = -1; + interval_email = -1; + interval_location = -1; + interval_phone = -1; + interval_tool = -1; + interval_note = -1; + + sdesbuildcount = 0; + transmissiondelay = RTPTime(0, 0); + + firstpacket = true; + processingsdes = false; + init = true; + return 0; +} + +void RTCPPacketBuilder::Destroy() +{ + if (!init) + return; + ownsdesinfo.Clear(); + init = false; +} + +int RTCPPacketBuilder::BuildNextPacket(RTCPCompoundPacket **pack) +{ + if (!init) + return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; + + RTCPCompoundPacketBuilder *rtcpcomppack; + int status; + bool sender = false; + RTPSourceData *srcdat; + + *pack = 0; + + rtcpcomppack = new RTCPCompoundPacketBuilder(); + + if ((status = rtcpcomppack->InitBuild(maxpacketsize)) < 0) + { + delete rtcpcomppack; + return status; + } + + if ((srcdat = sources.GetOwnSourceInfo()) != 0) + { + if (srcdat->IsSender()) + sender = true; + } + + uint32_t ssrc = rtppacketbuilder.GetSSRC(); + RTPTime curtime = RTPTime::CurrentTime(); + + if (sender) + { + RTPTime rtppacktime = rtppacketbuilder.GetPacketTime(); + uint32_t rtppacktimestamp = rtppacketbuilder.GetPacketTimestamp(); + uint32_t packcount = rtppacketbuilder.GetPacketCount(); + uint32_t octetcount = rtppacketbuilder.GetPayloadOctetCount(); + RTPTime diff = curtime; + diff -= rtppacktime; + diff += transmissiondelay; // the sample being sampled at this very instant will need a larger timestamp + + uint32_t tsdiff = (uint32_t) ((diff.GetDouble() / timestampunit) + 0.5); + uint32_t rtptimestamp = rtppacktimestamp + tsdiff; + RTPNTPTime ntptimestamp = curtime.GetNTPTime(); + + if ((status = rtcpcomppack->StartSenderReport(ssrc, ntptimestamp, rtptimestamp, packcount, octetcount)) < 0) + { + delete rtcpcomppack; + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + return status; + } + } + else + { + if ((status = rtcpcomppack->StartReceiverReport(ssrc)) < 0) + { + delete rtcpcomppack; + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + return status; + } + } + + uint8_t *owncname; + std::size_t owncnamelen; + + owncname = ownsdesinfo.GetCNAME(&owncnamelen); + + if ((status = rtcpcomppack->AddSDESSource(ssrc)) < 0) + { + delete rtcpcomppack; + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + return status; + } + if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::CNAME, owncname, owncnamelen)) < 0) + { + delete rtcpcomppack; + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + return status; + } + + if (!processingsdes) + { + int added, skipped; + bool full, atendoflist; + + if ((status = FillInReportBlocks(rtcpcomppack, curtime, sources.GetTotalCount(), &full, &added, &skipped, &atendoflist)) < 0) + { + delete rtcpcomppack; + return status; + } + + if (full && added == 0) + { + delete rtcpcomppack; + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + } + + if (!full) + { + processingsdes = true; + sdesbuildcount++; + + ClearAllSourceFlags(); + + doname = false; + doemail = false; + doloc = false; + dophone = false; + dotool = false; + donote = false; + if (interval_name > 0 && ((sdesbuildcount % interval_name) == 0)) + doname = true; + if (interval_email > 0 && ((sdesbuildcount % interval_email) == 0)) + doemail = true; + if (interval_location > 0 && ((sdesbuildcount % interval_location) == 0)) + doloc = true; + if (interval_phone > 0 && ((sdesbuildcount % interval_phone) == 0)) + dophone = true; + if (interval_tool > 0 && ((sdesbuildcount % interval_tool) == 0)) + dotool = true; + if (interval_note > 0 && ((sdesbuildcount % interval_note) == 0)) + donote = true; + + bool processedall; + int itemcount; + + if ((status = FillInSDES(rtcpcomppack, &full, &processedall, &itemcount)) < 0) + { + delete rtcpcomppack; + return status; + } + + if (processedall) + { + processingsdes = false; + ClearAllSDESFlags(); + if (!full && skipped > 0) + { + // if the packet isn't full and we skipped some + // sources that we already got in a previous packet, + // we can add some of them now + + bool atendoflist; + + if ((status = FillInReportBlocks(rtcpcomppack, curtime, skipped, &full, &added, &skipped, &atendoflist)) < 0) + { + delete rtcpcomppack; + return status; + } + } + } + } + } + else // previous sdes processing wasn't finished + { + bool processedall; + int itemcount; + bool full; + + if ((status = FillInSDES(rtcpcomppack, &full, &processedall, &itemcount)) < 0) + { + delete rtcpcomppack; + return status; + } + + if (itemcount == 0) // Big problem: packet size is too small to let any progress happen + { + delete rtcpcomppack; + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + } + + if (processedall) + { + processingsdes = false; + ClearAllSDESFlags(); + if (!full) + { + // if the packet isn't full and we skipped some + // we can add some report blocks + + int added, skipped; + bool atendoflist; + + if ((status = FillInReportBlocks(rtcpcomppack, curtime, sources.GetTotalCount(), &full, &added, &skipped, &atendoflist)) < 0) + { + delete rtcpcomppack; + return status; + } + if (atendoflist) // filled in all possible sources + ClearAllSourceFlags(); + } + } + } + + if ((status = rtcpcomppack->EndBuild()) < 0) + { + delete rtcpcomppack; + return status; + } + + *pack = rtcpcomppack; + firstpacket = false; + prevbuildtime = curtime; + return 0; +} + +void RTCPPacketBuilder::ClearAllSourceFlags() +{ + if (sources.GotoFirstSource()) + { + do + { + RTPSourceData *srcdat = sources.GetCurrentSourceInfo(); + srcdat->SetProcessedInRTCP(false); + } while (sources.GotoNextSource()); + } +} + +int RTCPPacketBuilder::FillInReportBlocks(RTCPCompoundPacketBuilder *rtcpcomppack, const RTPTime &curtime, int maxcount, bool *full, int *added, int *skipped, bool *atendoflist) +{ + RTPSourceData *srcdat; + int addedcount = 0; + int skippedcount = 0; + bool done = false; + bool filled = false; + bool atend = false; + int status; + + if (sources.GotoFirstSource()) + { + do + { + bool shouldprocess = false; + + srcdat = sources.GetCurrentSourceInfo(); + if (!srcdat->IsOwnSSRC()) // don't send to ourselves + { + if (!srcdat->IsCSRC()) // p 35: no reports should go to CSRCs + { + if (srcdat->INF_HasSentData()) // if this isn't true, INF_GetLastRTPPacketTime() won't make any sense + { + if (firstpacket) + shouldprocess = true; + else + { + // p 35: only if rtp packets were received since the last RTP packet, a report block + // should be added + + RTPTime lastrtptime = srcdat->INF_GetLastRTPPacketTime(); + + if (lastrtptime > prevbuildtime) + shouldprocess = true; + } + } + } + } + + if (shouldprocess) + { + if (srcdat->IsProcessedInRTCP()) // already covered this one + { + skippedcount++; + } + else + { + uint32_t rr_ssrc = srcdat->GetSSRC(); + uint32_t num = srcdat->INF_GetNumPacketsReceivedInInterval(); + uint32_t prevseq = srcdat->INF_GetSavedExtendedSequenceNumber(); + uint32_t curseq = srcdat->INF_GetExtendedHighestSequenceNumber(); + uint32_t expected = curseq - prevseq; + uint8_t fraclost; + + if (expected < num) // got duplicates + fraclost = 0; + else + { + double lost = (double) (expected - num); + double frac = lost / ((double) expected); + fraclost = (uint8_t) (frac * 256.0); + } + + expected = curseq - srcdat->INF_GetBaseSequenceNumber(); + num = srcdat->INF_GetNumPacketsReceived(); + + uint32_t diff = expected - num; + int32_t *packlost = (int32_t *) &diff; + + uint32_t jitter = srcdat->INF_GetJitter(); + uint32_t lsr; + uint32_t dlsr; + + if (!srcdat->SR_HasInfo()) + { + lsr = 0; + dlsr = 0; + } + else + { + RTPNTPTime srtime = srcdat->SR_GetNTPTimestamp(); + uint32_t m = (srtime.GetMSW() & 0xFFFF); + uint32_t l = ((srtime.GetLSW() >> 16) & 0xFFFF); + lsr = ((m << 16) | l); + + RTPTime diff = curtime; + diff -= srcdat->SR_GetReceiveTime(); + double diff2 = diff.GetDouble(); + diff2 *= 65536.0; + dlsr = (uint32_t) diff2; + } + + status = rtcpcomppack->AddReportBlock(rr_ssrc, fraclost, *packlost, curseq, jitter, lsr, dlsr); + if (status < 0) + { + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + { + done = true; + filled = true; + } + else + return status; + } + else + { + addedcount++; + if (addedcount >= maxcount) + { + done = true; + if (!sources.GotoNextSource()) + atend = true; + } + srcdat->INF_StartNewInterval(); + srcdat->SetProcessedInRTCP(true); + } + } + } + + if (!done) + { + if (!sources.GotoNextSource()) + { + atend = true; + done = true; + } + } + + } while (!done); + } + + *added = addedcount; + *skipped = skippedcount; + *full = filled; + + if (!atend) // search for available sources + { + bool shouldprocess = false; + + do + { + srcdat = sources.GetCurrentSourceInfo(); + if (!srcdat->IsOwnSSRC()) // don't send to ourselves + { + if (!srcdat->IsCSRC()) // p 35: no reports should go to CSRCs + { + if (srcdat->INF_HasSentData()) // if this isn't true, INF_GetLastRTPPacketTime() won't make any sense + { + if (firstpacket) + shouldprocess = true; + else + { + // p 35: only if rtp packets were received since the last RTP packet, a report block + // should be added + + RTPTime lastrtptime = srcdat->INF_GetLastRTPPacketTime(); + + if (lastrtptime > prevbuildtime) + shouldprocess = true; + } + } + } + } + + if (shouldprocess) + { + if (srcdat->IsProcessedInRTCP()) + shouldprocess = false; + } + + if (!shouldprocess) + { + if (!sources.GotoNextSource()) + atend = true; + } + + } while (!atend && !shouldprocess); + } + + *atendoflist = atend; + return 0; +} + +int RTCPPacketBuilder::FillInSDES(RTCPCompoundPacketBuilder *rtcpcomppack, bool *full, bool *processedall, int *added) +{ + int status; + uint8_t *data; + std::size_t datalen; + + *full = false; + *processedall = false; + *added = 0; + + // We don't need to add a SSRC for our own data, this is still set + // from adding the CNAME + if (doname) + { + if (!ownsdesinfo.ProcessedName()) + { + data = ownsdesinfo.GetName(&datalen); + if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::NAME, data, datalen)) < 0) + { + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + { + *full = true; + return 0; + } + } + (*added)++; + ownsdesinfo.SetProcessedName(true); + } + } + if (doemail) + { + if (!ownsdesinfo.ProcessedEMail()) + { + data = ownsdesinfo.GetEMail(&datalen); + if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::EMAIL, data, datalen)) < 0) + { + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + { + *full = true; + return 0; + } + } + (*added)++; + ownsdesinfo.SetProcessedEMail(true); + } + } + if (doloc) + { + if (!ownsdesinfo.ProcessedLocation()) + { + data = ownsdesinfo.GetLocation(&datalen); + if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::LOC, data, datalen)) < 0) + { + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + { + *full = true; + return 0; + } + } + (*added)++; + ownsdesinfo.SetProcessedLocation(true); + } + } + if (dophone) + { + if (!ownsdesinfo.ProcessedPhone()) + { + data = ownsdesinfo.GetPhone(&datalen); + if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::PHONE, data, datalen)) < 0) + { + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + { + *full = true; + return 0; + } + } + (*added)++; + ownsdesinfo.SetProcessedPhone(true); + } + } + if (dotool) + { + if (!ownsdesinfo.ProcessedTool()) + { + data = ownsdesinfo.GetTool(&datalen); + if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::TOOL, data, datalen)) < 0) + { + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + { + *full = true; + return 0; + } + } + (*added)++; + ownsdesinfo.SetProcessedTool(true); + } + } + if (donote) + { + if (!ownsdesinfo.ProcessedNote()) + { + data = ownsdesinfo.GetNote(&datalen); + if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::NOTE, data, datalen)) < 0) + { + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + { + *full = true; + return 0; + } + } + (*added)++; + ownsdesinfo.SetProcessedNote(true); + } + } + + *processedall = true; + return 0; +} + +void RTCPPacketBuilder::ClearAllSDESFlags() +{ + ownsdesinfo.ClearFlags(); +} + +int RTCPPacketBuilder::BuildBYEPacket(RTCPCompoundPacket **pack, const void *reason, std::size_t reasonlength, bool useSRifpossible) +{ + if (!init) + return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; + + RTCPCompoundPacketBuilder *rtcpcomppack; + int status; + + if (reasonlength > 255) + reasonlength = 255; + + *pack = 0; + + rtcpcomppack = new RTCPCompoundPacketBuilder(); + + if ((status = rtcpcomppack->InitBuild(maxpacketsize)) < 0) + { + delete rtcpcomppack; + return status; + } + + uint32_t ssrc = rtppacketbuilder.GetSSRC(); + bool useSR = false; + + if (useSRifpossible) + { + RTPSourceData *srcdat; + + if ((srcdat = sources.GetOwnSourceInfo()) != 0) + { + if (srcdat->IsSender()) + useSR = true; + } + } + + if (useSR) + { + RTPTime curtime = RTPTime::CurrentTime(); + RTPTime rtppacktime = rtppacketbuilder.GetPacketTime(); + uint32_t rtppacktimestamp = rtppacketbuilder.GetPacketTimestamp(); + uint32_t packcount = rtppacketbuilder.GetPacketCount(); + uint32_t octetcount = rtppacketbuilder.GetPayloadOctetCount(); + RTPTime diff = curtime; + diff -= rtppacktime; + + uint32_t tsdiff = (uint32_t) ((diff.GetDouble() / timestampunit) + 0.5); + uint32_t rtptimestamp = rtppacktimestamp + tsdiff; + RTPNTPTime ntptimestamp = curtime.GetNTPTime(); + + if ((status = rtcpcomppack->StartSenderReport(ssrc, ntptimestamp, rtptimestamp, packcount, octetcount)) < 0) + { + delete rtcpcomppack; + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + return status; + } + } + else + { + if ((status = rtcpcomppack->StartReceiverReport(ssrc)) < 0) + { + delete rtcpcomppack; + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + return status; + } + } + + uint8_t *owncname; + std::size_t owncnamelen; + + owncname = ownsdesinfo.GetCNAME(&owncnamelen); + + if ((status = rtcpcomppack->AddSDESSource(ssrc)) < 0) + { + delete rtcpcomppack; + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + return status; + } + if ((status = rtcpcomppack->AddSDESNormalItem(RTCPSDESPacket::CNAME, owncname, owncnamelen)) < 0) + { + delete rtcpcomppack; + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + return status; + } + + uint32_t ssrcs[1]; + + ssrcs[0] = ssrc; + + if ((status = rtcpcomppack->AddBYEPacket(ssrcs, 1, (const uint8_t *) reason, reasonlength)) < 0) + { + delete rtcpcomppack; + if (status == ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT) + return ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON; + return status; + } + + if ((status = rtcpcomppack->EndBuild()) < 0) + { + delete rtcpcomppack; + return status; + } + + *pack = rtcpcomppack; + return 0; +} + +} // end namespace + diff --git a/qrtplib/rtcppacketbuilder.h b/qrtplib/rtcppacketbuilder.h new file mode 100644 index 000000000..593e477c5 --- /dev/null +++ b/qrtplib/rtcppacketbuilder.h @@ -0,0 +1,364 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +/** + * \file rtcppacketbuilder.h + */ + +#ifndef RTCPPACKETBUILDER_H + +#define RTCPPACKETBUILDER_H + +#include "rtpconfig.h" +#include "rtptypes.h" +#include "rtperrors.h" +#include "rtcpsdesinfo.h" +#include "rtptimeutilities.h" + +#include "export.h" + +namespace qrtplib +{ + +class RTPSources; +class RTPPacketBuilder; +class RTCPScheduler; +class RTCPCompoundPacket; +class RTCPCompoundPacketBuilder; + +/** This class can be used to build RTCP compound packets, on a higher level than the RTCPCompoundPacketBuilder. + * The class RTCPPacketBuilder can be used to build RTCP compound packets. This class is more high-level + * than the RTCPCompoundPacketBuilder class: it uses the information of an RTPPacketBuilder instance and of + * an RTPSources instance to automatically generate the next compound packet which should be sent. It also + * provides functions to determine when SDES items other than the CNAME item should be sent. + */ +class QRTPLIB_API RTCPPacketBuilder +{ +public: + /** Creates an RTCPPacketBuilder instance. + * Creates an instance which will use the source table \c sources and the RTP packet builder + * \c rtppackbuilder to determine the information for the next RTCP compound packet. Optionally, + * the memory manager \c mgr can be installed. + */ + RTCPPacketBuilder(RTPSources &sources, RTPPacketBuilder &rtppackbuilder); + ~RTCPPacketBuilder(); + + /** Initializes the builder. + * Initializes the builder to use the maximum allowed packet size \c maxpacksize, timestamp unit + * \c timestampunit and the SDES CNAME item specified by \c cname with length \c cnamelen. + * The timestamp unit is defined as a time interval divided by the timestamp interval corresponding to + * that interval: for 8000 Hz audio this would be 1/8000. + */ + int Init(std::size_t maxpacksize, double timestampunit, const void *cname, std::size_t cnamelen); + + /** Cleans up the builder. */ + void Destroy(); + + /** Sets the timestamp unit to be used to \c tsunit. + * Sets the timestamp unit to be used to \c tsunit. The timestamp unit is defined as a time interval + * divided by the timestamp interval corresponding to that interval: for 8000 Hz audio this would + * be 1/8000. + */ + int SetTimestampUnit(double tsunit) + { + if (!init) + return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; + if (tsunit < 0) + return ERR_RTP_RTCPPACKETBUILDER_ILLEGALTIMESTAMPUNIT; + timestampunit = tsunit; + return 0; + } + + /** Sets the maximum size allowed size of an RTCP compound packet to \c maxpacksize. */ + int SetMaximumPacketSize(std::size_t maxpacksize) + { + if (!init) + return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; + if (maxpacksize < RTP_MINPACKETSIZE) + return ERR_RTP_RTCPPACKETBUILDER_ILLEGALMAXPACKSIZE; + maxpacketsize = maxpacksize; + return 0; + } + + /** This function allows you to inform RTCP packet builder about the delay between sampling the first + * sample of a packet and sending the packet. + * This function allows you to inform RTCP packet builder about the delay between sampling the first + * sample of a packet and sending the packet. This delay is taken into account when calculating the + * relation between RTP timestamp and wallclock time, used for inter-media synchronization. + */ + int SetPreTransmissionDelay(const RTPTime &delay) + { + if (!init) + return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; + transmissiondelay = delay; + return 0; + } + + /** Builds the next RTCP compound packet which should be sent and stores it in \c pack. */ + int BuildNextPacket(RTCPCompoundPacket **pack); + + /** Builds a BYE packet with reason for leaving specified by \c reason and length \c reasonlength. + * Builds a BYE packet with reason for leaving specified by \c reason and length \c reasonlength. If + * \c useSRifpossible is set to \c true, the RTCP compound packet will start with a sender report if + * allowed. Otherwise, a receiver report is used. + */ + int BuildBYEPacket(RTCPCompoundPacket **pack, const void *reason, std::size_t reasonlength, bool useSRifpossible = true); + + /** Sets the RTCP interval for the SDES name item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES name item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetNameInterval(int count) + { + if (!init) + return; + interval_name = count; + } + + /** Sets the RTCP interval for the SDES e-mail item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES e-mail item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetEMailInterval(int count) + { + if (!init) + return; + interval_email = count; + } + + /** Sets the RTCP interval for the SDES location item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES location item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetLocationInterval(int count) + { + if (!init) + return; + interval_location = count; + } + + /** Sets the RTCP interval for the SDES phone item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES phone item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetPhoneInterval(int count) + { + if (!init) + return; + interval_phone = count; + } + + /** Sets the RTCP interval for the SDES tool item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES tool item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetToolInterval(int count) + { + if (!init) + return; + interval_tool = count; + } + + /** Sets the RTCP interval for the SDES note item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES note item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetNoteInterval(int count) + { + if (!init) + return; + interval_note = count; + } + + /** Sets the SDES name item for the local participant to the value \c s with length \c len. */ + int SetLocalName(const void *s, std::size_t len) + { + if (!init) + return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; + return ownsdesinfo.SetName((const uint8_t *) s, len); + } + + /** Sets the SDES e-mail item for the local participant to the value \c s with length \c len. */ + int SetLocalEMail(const void *s, std::size_t len) + { + if (!init) + return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; + return ownsdesinfo.SetEMail((const uint8_t *) s, len); + } + + /** Sets the SDES location item for the local participant to the value \c s with length \c len. */ + int SetLocalLocation(const void *s, std::size_t len) + { + if (!init) + return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; + return ownsdesinfo.SetLocation((const uint8_t *) s, len); + } + + /** Sets the SDES phone item for the local participant to the value \c s with length \c len. */ + int SetLocalPhone(const void *s, std::size_t len) + { + if (!init) + return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; + return ownsdesinfo.SetPhone((const uint8_t *) s, len); + } + + /** Sets the SDES tool item for the local participant to the value \c s with length \c len. */ + int SetLocalTool(const void *s, std::size_t len) + { + if (!init) + return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; + return ownsdesinfo.SetTool((const uint8_t *) s, len); + } + + /** Sets the SDES note item for the local participant to the value \c s with length \c len. */ + int SetLocalNote(const void *s, std::size_t len) + { + if (!init) + return ERR_RTP_RTCPPACKETBUILDER_NOTINIT; + return ownsdesinfo.SetNote((const uint8_t *) s, len); + } + + /** Returns the own CNAME item with length \c len */ + uint8_t *GetLocalCNAME(std::size_t *len) const + { + if (!init) + return 0; + return ownsdesinfo.GetCNAME(len); + } +private: + void ClearAllSourceFlags(); + int FillInReportBlocks(RTCPCompoundPacketBuilder *pack, const RTPTime &curtime, int maxcount, bool *full, int *added, int *skipped, bool *atendoflist); + int FillInSDES(RTCPCompoundPacketBuilder *pack, bool *full, bool *processedall, int *added); + void ClearAllSDESFlags(); + + RTPSources &sources; + RTPPacketBuilder &rtppacketbuilder; + + bool init; + std::size_t maxpacketsize; + double timestampunit; + bool firstpacket; + RTPTime prevbuildtime, transmissiondelay; + + class RTCPSDESInfoInternal: public RTCPSDESInfo + { + public: + RTCPSDESInfoInternal() + { + ClearFlags(); + } + void ClearFlags() + { + pname = false; + pemail = false; + plocation = false; + pphone = false; + ptool = false; + pnote = false; + } + bool ProcessedName() const + { + return pname; + } + bool ProcessedEMail() const + { + return pemail; + } + bool ProcessedLocation() const + { + return plocation; + } + bool ProcessedPhone() const + { + return pphone; + } + bool ProcessedTool() const + { + return ptool; + } + bool ProcessedNote() const + { + return pnote; + } + void SetProcessedName(bool v) + { + pname = v; + } + void SetProcessedEMail(bool v) + { + pemail = v; + } + void SetProcessedLocation(bool v) + { + plocation = v; + } + void SetProcessedPhone(bool v) + { + pphone = v; + } + void SetProcessedTool(bool v) + { + ptool = v; + } + void SetProcessedNote(bool v) + { + pnote = v; + } + private: + bool pname, pemail, plocation, pphone, ptool, pnote; + }; + + RTCPSDESInfoInternal ownsdesinfo; + int interval_name, interval_email, interval_location; + int interval_phone, interval_tool, interval_note; + bool doname, doemail, doloc, dophone, dotool, donote; + bool processingsdes; + + int sdesbuildcount; +}; + +} // end namespace + +#endif // RTCPPACKETBUILDER_H + diff --git a/qrtplib/rtcprrpacket.cpp b/qrtplib/rtcprrpacket.cpp new file mode 100644 index 000000000..5c7ccf673 --- /dev/null +++ b/qrtplib/rtcprrpacket.cpp @@ -0,0 +1,68 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +#include "rtcprrpacket.h" + +namespace qrtplib +{ + +RTCPRRPacket::RTCPRRPacket(uint8_t *data, std::size_t datalength) : + RTCPPacket(RR, data, datalength) +{ + knownformat = false; + + RTCPCommonHeader *hdr; + std::size_t len = datalength; + std::size_t expectedlength; + + hdr = (RTCPCommonHeader *) data; + if (hdr->padding) + { + uint8_t padcount = data[datalength - 1]; + if ((padcount & 0x03) != 0) // not a multiple of four! (see rfc 3550 p 37) + return; + if (((std::size_t) padcount) >= len) + return; + len -= (std::size_t) padcount; + } + + expectedlength = sizeof(RTCPCommonHeader) + sizeof(uint32_t); + expectedlength += sizeof(RTCPReceiverReport) * ((int) hdr->count); + + if (expectedlength != len) + return; + + knownformat = true; +} + +} // end namespace + diff --git a/qrtplib/rtcprrpacket.h b/qrtplib/rtcprrpacket.h new file mode 100644 index 000000000..2b49ad40b --- /dev/null +++ b/qrtplib/rtcprrpacket.h @@ -0,0 +1,206 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +/** + * \file rtcprrpacket.h + */ + +#ifndef RTCPRRPACKET_H + +#define RTCPRRPACKET_H + +#include "rtpconfig.h" +#include "rtcppacket.h" +#include "rtpstructs.h" +#include "rtpendian.h" + +#include "export.h" + +namespace qrtplib +{ + +class RTCPCompoundPacket; + +/** Describes an RTCP receiver report packet. */ +class QRTPLIB_API RTCPRRPacket: public RTCPPacket +{ +public: + /** Creates an instance based on the data in \c data with length \c datalen. + * Creates an instance based on the data in \c data with length \c datalen. Since the \c data pointer + * is referenced inside the class (no copy of the data is made) one must make sure that the memory it points + * to is valid as long as the class instance exists. + */ + RTCPRRPacket(uint8_t *data, std::size_t datalen); + ~RTCPRRPacket() + { + } + + /** Returns the SSRC of the participant who sent this packet. */ + uint32_t GetSenderSSRC() const; + + /** Returns the number of reception report blocks present in this packet. */ + int GetReceptionReportCount() const; + + /** Returns the SSRC of the reception report block described by \c index which may have a value + * from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetSSRC(int index) const; + + /** Returns the `fraction lost' field of the reception report described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint8_t GetFractionLost(int index) const; + + /** Returns the number of lost packets in the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + int32_t GetLostPacketCount(int index) const; + + /** Returns the extended highest sequence number of the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetExtendedHighestSequenceNumber(int index) const; + + /** Returns the jitter field of the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetJitter(int index) const; + + /** Returns the LSR field of the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetLSR(int index) const; + + /** Returns the DLSR field of the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetDLSR(int index) const; + +private: + RTCPReceiverReport *GotoReport(int index) const; + + RTPEndian m_endian; +}; + +inline uint32_t RTCPRRPacket::GetSenderSSRC() const +{ + if (!knownformat) + return 0; + + uint32_t *ssrcptr = (uint32_t *) (data + sizeof(RTCPCommonHeader)); + return m_endian.qToHost(*ssrcptr); +} +inline int RTCPRRPacket::GetReceptionReportCount() const +{ + if (!knownformat) + return 0; + RTCPCommonHeader *hdr = (RTCPCommonHeader *) data; + return ((int) hdr->count); +} + +inline RTCPReceiverReport *RTCPRRPacket::GotoReport(int index) const +{ + RTCPReceiverReport *r = (RTCPReceiverReport *) (data + sizeof(RTCPCommonHeader) + sizeof(uint32_t) + index * sizeof(RTCPReceiverReport)); + return r; +} + +inline uint32_t RTCPRRPacket::GetSSRC(int index) const +{ + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return m_endian.qToHost(r->ssrc); +} + +inline uint8_t RTCPRRPacket::GetFractionLost(int index) const +{ + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return r->fractionlost; +} + +inline int32_t RTCPRRPacket::GetLostPacketCount(int index) const +{ + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + uint32_t count = ((uint32_t) r->packetslost[2]) | (((uint32_t) r->packetslost[1]) << 8) | (((uint32_t) r->packetslost[0]) << 16); + if ((count & 0x00800000) != 0) // test for negative number + count |= 0xFF000000; + int32_t *count2 = (int32_t *) (&count); + return (*count2); +} + +inline uint32_t RTCPRRPacket::GetExtendedHighestSequenceNumber(int index) const +{ + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return m_endian.qToHost(r->exthighseqnr); +} + +inline uint32_t RTCPRRPacket::GetJitter(int index) const +{ + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return m_endian.qToHost(r->jitter); +} + +inline uint32_t RTCPRRPacket::GetLSR(int index) const +{ + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return m_endian.qToHost(r->lsr); +} + +inline uint32_t RTCPRRPacket::GetDLSR(int index) const +{ + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return m_endian.qToHost(r->dlsr); +} + +} // end namespace + +#endif // RTCPRRPACKET_H + diff --git a/qrtplib/rtcpscheduler.cpp b/qrtplib/rtcpscheduler.cpp new file mode 100644 index 000000000..9d19ec038 --- /dev/null +++ b/qrtplib/rtcpscheduler.cpp @@ -0,0 +1,426 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +#include "rtcpscheduler.h" +#include "rtpsources.h" +#include "rtpdefines.h" +#include "rtcppacket.h" +#include "rtppacket.h" +#include "rtcpcompoundpacket.h" +#include "rtpsourcedata.h" + +#define RTCPSCHED_MININTERVAL 1.0 + +namespace qrtplib +{ + +RTCPSchedulerParams::RTCPSchedulerParams() : + mininterval(RTCP_DEFAULTMININTERVAL) +{ + bandwidth = 1000; // TODO What is a good value here? + senderfraction = RTCP_DEFAULTSENDERFRACTION; + usehalfatstartup = RTCP_DEFAULTHALFATSTARTUP; + immediatebye = RTCP_DEFAULTIMMEDIATEBYE; + timeinit.Dummy(); +} + +RTCPSchedulerParams::~RTCPSchedulerParams() +{ +} + +int RTCPSchedulerParams::SetRTCPBandwidth(double bw) +{ + if (bw < 0.0) + return ERR_RTP_SCHEDPARAMS_INVALIDBANDWIDTH; + bandwidth = bw; + return 0; +} + +int RTCPSchedulerParams::SetSenderBandwidthFraction(double fraction) +{ + if (fraction < 0.0 || fraction > 1.0) + return ERR_RTP_SCHEDPARAMS_BADFRACTION; + senderfraction = fraction; + return 0; +} + +int RTCPSchedulerParams::SetMinimumTransmissionInterval(const RTPTime &t) +{ + double t2 = t.GetDouble(); + + if (t2 < RTCPSCHED_MININTERVAL) + return ERR_RTP_SCHEDPARAMS_BADMINIMUMINTERVAL; + + mininterval = t; + return 0; +} + +RTCPScheduler::RTCPScheduler(RTPSources &s, RTPRandom &r) : + sources(s), nextrtcptime(0, 0), prevrtcptime(0, 0), rtprand(r) +{ + pmembers = 0; + byemembers = 0; + pbyemembers = 0; + avgbyepacketsize = 0; + + Reset(); + + //std::cout << (void *)(&rtprand) << std::endl; +} + +RTCPScheduler::~RTCPScheduler() +{ +} + +void RTCPScheduler::Reset() +{ + headeroverhead = 0; // user has to set this to an appropriate value + hassentrtcp = false; + firstcall = true; + avgrtcppacksize = 1000; // TODO: what is a good value for this? + byescheduled = false; + sendbyenow = false; +} + +void RTCPScheduler::AnalyseIncoming(RTCPCompoundPacket &rtcpcomppack) +{ + bool isbye = false; + RTCPPacket *p; + + rtcpcomppack.GotoFirstPacket(); + while (!isbye && ((p = rtcpcomppack.GetNextPacket()) != 0)) + { + if (p->GetPacketType() == RTCPPacket::BYE) + isbye = true; + } + + if (!isbye) + { + std::size_t packsize = headeroverhead + rtcpcomppack.GetCompoundPacketLength(); + avgrtcppacksize = (std::size_t)((1.0 / 16.0) * ((double) packsize) + (15.0 / 16.0) * ((double) avgrtcppacksize)); + } + else + { + if (byescheduled) + { + std::size_t packsize = headeroverhead + rtcpcomppack.GetCompoundPacketLength(); + avgbyepacketsize = (std::size_t)((1.0 / 16.0) * ((double) packsize) + (15.0 / 16.0) * ((double) avgbyepacketsize)); + byemembers++; + } + } +} + +void RTCPScheduler::AnalyseOutgoing(RTCPCompoundPacket &rtcpcomppack) +{ + bool isbye = false; + RTCPPacket *p; + + rtcpcomppack.GotoFirstPacket(); + while (!isbye && ((p = rtcpcomppack.GetNextPacket()) != 0)) + { + if (p->GetPacketType() == RTCPPacket::BYE) + isbye = true; + } + + if (!isbye) + { + std::size_t packsize = headeroverhead + rtcpcomppack.GetCompoundPacketLength(); + avgrtcppacksize = (std::size_t)((1.0 / 16.0) * ((double) packsize) + (15.0 / 16.0) * ((double) avgrtcppacksize)); + } + + hassentrtcp = true; +} + +RTPTime RTCPScheduler::GetTransmissionDelay() +{ + if (firstcall) + { + firstcall = false; + prevrtcptime = RTPTime::CurrentTime(); + pmembers = sources.GetActiveMemberCount(); + CalculateNextRTCPTime(); + } + + RTPTime curtime = RTPTime::CurrentTime(); + + if (curtime > nextrtcptime) // packet should be sent + return RTPTime(0, 0); + + RTPTime diff = nextrtcptime; + diff -= curtime; + + return diff; +} + +bool RTCPScheduler::IsTime() +{ + if (firstcall) + { + firstcall = false; + prevrtcptime = RTPTime::CurrentTime(); + pmembers = sources.GetActiveMemberCount(); + CalculateNextRTCPTime(); + return false; + } + + RTPTime currenttime = RTPTime::CurrentTime(); + +// // TODO: for debugging +// double diff = nextrtcptime.GetDouble() - currenttime.GetDouble(); +// +// std::cout << "Delay till next RTCP interval: " << diff << std::endl; + + if (currenttime < nextrtcptime) // timer has not yet expired + return false; + + RTPTime checktime(0, 0); + + if (!byescheduled) + { + bool aresender = false; + RTPSourceData *srcdat; + + if ((srcdat = sources.GetOwnSourceInfo()) != 0) + aresender = srcdat->IsSender(); + + checktime = CalculateTransmissionInterval(aresender); + } + else + checktime = CalculateBYETransmissionInterval(); + +// std::cout << "Calculated checktime: " << checktime.GetDouble() << std::endl; + + checktime += prevrtcptime; + + if (checktime <= currenttime) // Okay + { + byescheduled = false; + prevrtcptime = currenttime; + pmembers = sources.GetActiveMemberCount(); + CalculateNextRTCPTime(); + return true; + } + +// std::cout << "New delay: " << nextrtcptime.GetDouble() - currenttime.GetDouble() << std::endl; + + nextrtcptime = checktime; + pmembers = sources.GetActiveMemberCount(); + + return false; +} + +void RTCPScheduler::CalculateNextRTCPTime() +{ + bool aresender = false; + RTPSourceData *srcdat; + + if ((srcdat = sources.GetOwnSourceInfo()) != 0) + aresender = srcdat->IsSender(); + + nextrtcptime = RTPTime::CurrentTime(); + nextrtcptime += CalculateTransmissionInterval(aresender); +} + +RTPTime RTCPScheduler::CalculateDeterministicInterval(bool sender /* = false */) +{ + int numsenders = sources.GetSenderCount(); + int numtotal = sources.GetActiveMemberCount(); + +// std::cout << "CalculateDeterministicInterval" << std::endl; +// std::cout << " numsenders: " << numsenders << std::endl; +// std::cout << " numtotal: " << numtotal << std::endl; + + // Try to avoid division by zero: + if (numtotal == 0) + numtotal++; + + double sfraction = ((double) numsenders) / ((double) numtotal); + double C, n; + + if (sfraction <= schedparams.GetSenderBandwidthFraction()) + { + if (sender) + { + C = ((double) avgrtcppacksize) / (schedparams.GetSenderBandwidthFraction() * schedparams.GetRTCPBandwidth()); + n = (double) numsenders; + } + else + { + C = ((double) avgrtcppacksize) / ((1.0 - schedparams.GetSenderBandwidthFraction()) * schedparams.GetRTCPBandwidth()); + n = (double) (numtotal - numsenders); + } + } + else + { + C = ((double) avgrtcppacksize) / schedparams.GetRTCPBandwidth(); + n = (double) numtotal; + } + + RTPTime Tmin = schedparams.GetMinimumTransmissionInterval(); + double tmin = Tmin.GetDouble(); + + if (!hassentrtcp && schedparams.GetUseHalfAtStartup()) + tmin /= 2.0; + + double ntimesC = n * C; + double Td = (tmin > ntimesC) ? tmin : ntimesC; + + // TODO: for debugging +// std::cout << " Td: " << Td << std::endl; + + return RTPTime(Td); +} + +RTPTime RTCPScheduler::CalculateTransmissionInterval(bool sender) +{ + RTPTime Td = CalculateDeterministicInterval(sender); + double td, mul, T; + +// std::cout << "CalculateTransmissionInterval" << std::endl; + + td = Td.GetDouble(); + mul = rtprand.GetRandomDouble() + 0.5; // gives random value between 0.5 and 1.5 + T = (td * mul) / 1.21828; // see RFC 3550 p 30 + +// std::cout << " Td: " << td << std::endl; +// std::cout << " mul: " << mul << std::endl; +// std::cout << " T: " << T << std::endl; + + return RTPTime(T); +} + +void RTCPScheduler::PerformReverseReconsideration() +{ + if (firstcall) + return; + + double diff1, diff2; + int members = sources.GetActiveMemberCount(); + + RTPTime tc = RTPTime::CurrentTime(); + RTPTime tn_min_tc = nextrtcptime; + + if (tn_min_tc > tc) + tn_min_tc -= tc; + else + tn_min_tc = RTPTime(0, 0); + +// std::cout << "+tn_min_tc0 " << nextrtcptime.GetDouble()-tc.GetDouble() << std::endl; +// std::cout << "-tn_min_tc0 " << -nextrtcptime.GetDouble()+tc.GetDouble() << std::endl; +// std::cout << "tn_min_tc " << tn_min_tc.GetDouble() << std::endl; + + RTPTime tc_min_tp = tc; + + if (tc_min_tp > prevrtcptime) + tc_min_tp -= prevrtcptime; + else + tc_min_tp = 0; + + if (pmembers == 0) // avoid division by zero + pmembers++; + + diff1 = (((double) members) / ((double) pmembers)) * tn_min_tc.GetDouble(); + diff2 = (((double) members) / ((double) pmembers)) * tc_min_tp.GetDouble(); + + nextrtcptime = tc; + prevrtcptime = tc; + nextrtcptime += RTPTime(diff1); + prevrtcptime -= RTPTime(diff2); + + pmembers = members; +} + +void RTCPScheduler::ScheduleBYEPacket(std::size_t packetsize) +{ + if (byescheduled) + return; + + if (firstcall) + { + firstcall = false; + pmembers = sources.GetActiveMemberCount(); + } + + byescheduled = true; + avgbyepacketsize = packetsize + headeroverhead; + + // For now, we will always use the BYE backoff algorithm as described in rfc 3550 p 33 + + byemembers = 1; + pbyemembers = 1; + + if (schedparams.GetRequestImmediateBYE() && sources.GetActiveMemberCount() < 50) // p 34 (top) + sendbyenow = true; + else + sendbyenow = false; + + prevrtcptime = RTPTime::CurrentTime(); + nextrtcptime = prevrtcptime; + nextrtcptime += CalculateBYETransmissionInterval(); +} + +void RTCPScheduler::ActiveMemberDecrease() +{ + if (sources.GetActiveMemberCount() < pmembers) + PerformReverseReconsideration(); +} + +RTPTime RTCPScheduler::CalculateBYETransmissionInterval() +{ + if (!byescheduled) + return RTPTime(0, 0); + + if (sendbyenow) + return RTPTime(0, 0); + + double C, n; + + C = ((double) avgbyepacketsize) / ((1.0 - schedparams.GetSenderBandwidthFraction()) * schedparams.GetRTCPBandwidth()); + n = (double) byemembers; + + RTPTime Tmin = schedparams.GetMinimumTransmissionInterval(); + double tmin = Tmin.GetDouble(); + + if (schedparams.GetUseHalfAtStartup()) + tmin /= 2.0; + + double ntimesC = n * C; + double Td = (tmin > ntimesC) ? tmin : ntimesC; + + double mul = rtprand.GetRandomDouble() + 0.5; // gives random value between 0.5 and 1.5 + double T = (Td * mul) / 1.21828; // see RFC 3550 p 30 + + return RTPTime(T); +} + +} // end namespace + diff --git a/qrtplib/rtcpscheduler.h b/qrtplib/rtcpscheduler.h new file mode 100644 index 000000000..7557414bb --- /dev/null +++ b/qrtplib/rtcpscheduler.h @@ -0,0 +1,225 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +/** + * \file rtcpscheduler.h + */ + +#ifndef RTCPSCHEDULER_H + +#define RTCPSCHEDULER_H + +#include "rtpconfig.h" +#include "rtptimeutilities.h" +#include "rtprandom.h" +#include + +#include "export.h" + +namespace qrtplib +{ + +class RTCPCompoundPacket; +class RTPPacket; +class RTPSources; + +/** Describes parameters used by the RTCPScheduler class. */ +class QRTPLIB_API RTCPSchedulerParams +{ +public: + RTCPSchedulerParams(); + ~RTCPSchedulerParams(); + + /** Sets the RTCP bandwidth to be used to \c bw (in bytes per second). */ + int SetRTCPBandwidth(double bw); + + /** Returns the used RTCP bandwidth in bytes per second (default is 1000). */ + double GetRTCPBandwidth() const + { + return bandwidth; + } + + /** Sets the fraction of the RTCP bandwidth reserved for senders to \c fraction. */ + int SetSenderBandwidthFraction(double fraction); + + /** Returns the fraction of the RTCP bandwidth reserved for senders (default is 25%). */ + double GetSenderBandwidthFraction() const + { + return senderfraction; + } + + /** Sets the minimum (deterministic) interval between RTCP compound packets to \c t. */ + int SetMinimumTransmissionInterval(const RTPTime &t); + + /** Returns the minimum RTCP transmission interval (default is 5 seconds). */ + RTPTime GetMinimumTransmissionInterval() const + { + return mininterval; + } + + /** If \c usehalf is \c true, only use half the minimum interval before sending the first RTCP compound packet. */ + void SetUseHalfAtStartup(bool usehalf) + { + usehalfatstartup = usehalf; + } + + /** Returns \c true if only half the minimum interval should be used before sending the first RTCP compound packet + * (defualt is \c true). + */ + bool GetUseHalfAtStartup() const + { + return usehalfatstartup; + } + + /** If \c v is \c true, the scheduler will schedule a BYE packet to be sent immediately if allowed. */ + void SetRequestImmediateBYE(bool v) + { + immediatebye = v; + } + + /** Returns if the scheduler will schedule a BYE packet to be sent immediately if allowed + * (default is \c true). + */ + bool GetRequestImmediateBYE() const + { + return immediatebye; + } +private: + double bandwidth; + double senderfraction; + RTPTime mininterval; + bool usehalfatstartup; + bool immediatebye; +}; + +/** This class determines when RTCP compound packets should be sent. */ +class RTCPScheduler +{ +public: + /** Creates an instance which will use the source table RTPSources to determine when RTCP compound + * packets should be scheduled. + * Creates an instance which will use the source table RTPSources to determine when RTCP compound + * packets should be scheduled. Note that for correct operation the \c sources instance should have information + * about the own SSRC (added by RTPSources::CreateOwnSSRC). You must also supply a random number + * generator \c rtprand which will be used for adding randomness to the RTCP intervals. + */ + RTCPScheduler(RTPSources &sources, RTPRandom &rtprand); + ~RTCPScheduler(); + + /** Resets the scheduler. */ + void Reset(); + + /** Sets the scheduler parameters to be used to \c params. */ + void SetParameters(const RTCPSchedulerParams ¶ms) + { + schedparams = params; + } + + /** Returns the currently used scheduler parameters. */ + RTCPSchedulerParams GetParameters() const + { + return schedparams; + } + + /** Sets the header overhead from underlying protocols (for example UDP and IP) to \c numbytes. */ + void SetHeaderOverhead(std::size_t numbytes) + { + headeroverhead = numbytes; + } + + /** Returns the currently used header overhead. */ + std::size_t GetHeaderOverhead() const + { + return headeroverhead; + } + + /** For each incoming RTCP compound packet, this function has to be called for the scheduler to work correctly. */ + void AnalyseIncoming(RTCPCompoundPacket &rtcpcomppack); + + /** For each outgoing RTCP compound packet, this function has to be called for the scheduler to work correctly. */ + void AnalyseOutgoing(RTCPCompoundPacket &rtcpcomppack); + + /** This function has to be called each time a member times out or sends a BYE packet. */ + void ActiveMemberDecrease(); + + /** Asks the scheduler to schedule an RTCP compound packet containing a BYE packetl; the compound packet + * has size \c packetsize. + */ + void ScheduleBYEPacket(std::size_t packetsize); + + /** Returns the delay after which an RTCP compound will possibly have to be sent. + * Returns the delay after which an RTCP compound will possibly have to be sent. The IsTime member function + * should be called afterwards to make sure that it actually is time to send an RTCP compound packet. + */ + RTPTime GetTransmissionDelay(); + + /** This function returns \c true if it's time to send an RTCP compound packet and \c false otherwise. + * This function returns \c true if it's time to send an RTCP compound packet and \c false otherwise. + * If the function returns \c true, it will also have calculated the next time at which a packet should + * be sent, so if it is called again right away, it will return \c false. + */ + bool IsTime(); + + /** Calculates the deterministic interval at this time. + * Calculates the deterministic interval at this time. This is used - in combination with a certain multiplier - + * to time out members, senders etc. + */ + RTPTime CalculateDeterministicInterval(bool sender = false); +private: + void CalculateNextRTCPTime(); + void PerformReverseReconsideration(); + RTPTime CalculateBYETransmissionInterval(); + RTPTime CalculateTransmissionInterval(bool sender); + + RTPSources &sources; + RTCPSchedulerParams schedparams; + std::size_t headeroverhead; + std::size_t avgrtcppacksize; + bool hassentrtcp; + bool firstcall; + RTPTime nextrtcptime; + RTPTime prevrtcptime; + int pmembers; + + // for BYE packet scheduling + bool byescheduled; + int byemembers, pbyemembers; + std::size_t avgbyepacketsize; + bool sendbyenow; + + RTPRandom &rtprand; +}; + +} // end namespace + +#endif // RTCPSCHEDULER_H + diff --git a/qrtplib/rtcpsdesinfo.cpp b/qrtplib/rtcpsdesinfo.cpp new file mode 100644 index 000000000..73b43c5dd --- /dev/null +++ b/qrtplib/rtcpsdesinfo.cpp @@ -0,0 +1,179 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +#include "rtcpsdesinfo.h" + +namespace qrtplib +{ + +void RTCPSDESInfo::Clear() +{ +#ifdef RTP_SUPPORT_SDESPRIV + std::list::const_iterator it; + + for (it = privitems.begin(); it != privitems.end(); ++it) + delete *it; + privitems.clear(); +#endif // RTP_SUPPORT_SDESPRIV +} + +#ifdef RTP_SUPPORT_SDESPRIV +int RTCPSDESInfo::SetPrivateValue(const uint8_t *prefix, std::size_t prefixlen, const uint8_t *value, std::size_t valuelen) +{ + std::list::const_iterator it; + bool found; + + found = false; + it = privitems.begin(); + while (!found && it != privitems.end()) + { + uint8_t *p; + std::size_t l; + + p = (*it)->GetPrefix(&l); + if (l == prefixlen) + { + if (l <= 0) + found = true; + else if (memcmp(prefix, p, l) == 0) + found = true; + else + ++it; + } + else + ++it; + } + + SDESPrivateItem *item; + + if (found) // replace the value for this entry + item = *it; + else // no entry for this prefix found... add it + { + if (privitems.size() >= RTP_MAXPRIVITEMS) // too many items present, just ignore it + return ERR_RTP_SDES_MAXPRIVITEMS; + + int status; + + item = new SDESPrivateItem(); + + if ((status = item->SetPrefix(prefix, prefixlen)) < 0) + { + delete item; + return status; + } + privitems.push_front(item); + } + return item->SetInfo(value, valuelen); +} + +int RTCPSDESInfo::DeletePrivatePrefix(const uint8_t *prefix, std::size_t prefixlen) +{ + std::list::iterator it; + bool found; + + found = false; + it = privitems.begin(); + while (!found && it != privitems.end()) + { + uint8_t *p; + std::size_t l; + + p = (*it)->GetPrefix(&l); + if (l == prefixlen) + { + if (l <= 0) + found = true; + else if (memcmp(prefix, p, l) == 0) + found = true; + else + ++it; + } + else + ++it; + } + if (!found) + return ERR_RTP_SDES_PREFIXNOTFOUND; + + delete *it; + privitems.erase(it); + return 0; +} + +void RTCPSDESInfo::GotoFirstPrivateValue() +{ + curitem = privitems.begin(); +} + +bool RTCPSDESInfo::GetNextPrivateValue(uint8_t **prefix, std::size_t *prefixlen, uint8_t **value, std::size_t *valuelen) +{ + if (curitem == privitems.end()) + return false; + *prefix = (*curitem)->GetPrefix(prefixlen); + *value = (*curitem)->GetInfo(valuelen); + ++curitem; + return true; +} + +bool RTCPSDESInfo::GetPrivateValue(const uint8_t *prefix, std::size_t prefixlen, uint8_t **value, std::size_t *valuelen) const +{ + std::list::const_iterator it; + bool found; + + found = false; + it = privitems.begin(); + while (!found && it != privitems.end()) + { + uint8_t *p; + std::size_t l; + + p = (*it)->GetPrefix(&l); + if (l == prefixlen) + { + if (l <= 0) + found = true; + else if (memcmp(prefix, p, l) == 0) + found = true; + else + ++it; + } + else + ++it; + } + if (found) + *value = (*it)->GetInfo(valuelen); + return found; +} +#endif // RTP_SUPPORT_SDESPRIV + +} // end namespace + diff --git a/qrtplib/rtcpsdesinfo.h b/qrtplib/rtcpsdesinfo.h new file mode 100644 index 000000000..f46bfbddf --- /dev/null +++ b/qrtplib/rtcpsdesinfo.h @@ -0,0 +1,284 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +/** + * \file rtcpsdesinfo.h + */ + +#ifndef RTCPSDESINFO_H + +#define RTCPSDESINFO_H + +#include "rtpconfig.h" +#include "rtperrors.h" +#include "rtpdefines.h" +#include "rtptypes.h" +#include +#include + +#include "export.h" + +namespace qrtplib +{ + +/** The class RTCPSDESInfo is a container for RTCP SDES information. */ +class QRTPLIB_API RTCPSDESInfo +{ +public: + /** Constructs an instance, optionally installing a memory manager. */ + RTCPSDESInfo() + { + } + virtual ~RTCPSDESInfo() + { + Clear(); + } + + /** Clears all SDES information. */ + void Clear(); + + /** Sets the SDES CNAME item to \c s with length \c l. */ + int SetCNAME(const uint8_t *s, std::size_t l) + { + return SetNonPrivateItem(RTCP_SDES_ID_CNAME - 1, s, l); + } + + /** Sets the SDES name item to \c s with length \c l. */ + int SetName(const uint8_t *s, std::size_t l) + { + return SetNonPrivateItem(RTCP_SDES_ID_NAME - 1, s, l); + } + + /** Sets the SDES e-mail item to \c s with length \c l. */ + int SetEMail(const uint8_t *s, std::size_t l) + { + return SetNonPrivateItem(RTCP_SDES_ID_EMAIL - 1, s, l); + } + + /** Sets the SDES phone item to \c s with length \c l. */ + int SetPhone(const uint8_t *s, std::size_t l) + { + return SetNonPrivateItem(RTCP_SDES_ID_PHONE - 1, s, l); + } + + /** Sets the SDES location item to \c s with length \c l. */ + int SetLocation(const uint8_t *s, std::size_t l) + { + return SetNonPrivateItem(RTCP_SDES_ID_LOCATION - 1, s, l); + } + + /** Sets the SDES tool item to \c s with length \c l. */ + int SetTool(const uint8_t *s, std::size_t l) + { + return SetNonPrivateItem(RTCP_SDES_ID_TOOL - 1, s, l); + } + + /** Sets the SDES note item to \c s with length \c l. */ + int SetNote(const uint8_t *s, std::size_t l) + { + return SetNonPrivateItem(RTCP_SDES_ID_NOTE - 1, s, l); + } + +#ifdef RTP_SUPPORT_SDESPRIV + /** Sets the entry for the prefix string specified by \c prefix with length \c prefixlen to contain + * the value string specified by \c value with length \c valuelen (if the maximum allowed + * number of prefixes was reached, the error code \c ERR_RTP_SDES_MAXPRIVITEMS is returned. + */ + int SetPrivateValue(const uint8_t *prefix, std::size_t prefixlen, const uint8_t *value, std::size_t valuelen); + + /** Deletes the entry for the prefix specified by \c s with length \c len. */ + int DeletePrivatePrefix(const uint8_t *s, std::size_t len); +#endif // RTP_SUPPORT_SDESPRIV + + /** Returns the SDES CNAME item and stores its length in \c len. */ + uint8_t *GetCNAME(std::size_t *len) const + { + return GetNonPrivateItem(RTCP_SDES_ID_CNAME - 1, len); + } + + /** Returns the SDES name item and stores its length in \c len. */ + uint8_t *GetName(std::size_t *len) const + { + return GetNonPrivateItem(RTCP_SDES_ID_NAME - 1, len); + } + + /** Returns the SDES e-mail item and stores its length in \c len. */ + uint8_t *GetEMail(std::size_t *len) const + { + return GetNonPrivateItem(RTCP_SDES_ID_EMAIL - 1, len); + } + + /** Returns the SDES phone item and stores its length in \c len. */ + uint8_t *GetPhone(std::size_t *len) const + { + return GetNonPrivateItem(RTCP_SDES_ID_PHONE - 1, len); + } + + /** Returns the SDES location item and stores its length in \c len. */ + uint8_t *GetLocation(std::size_t *len) const + { + return GetNonPrivateItem(RTCP_SDES_ID_LOCATION - 1, len); + } + + /** Returns the SDES tool item and stores its length in \c len. */ + uint8_t *GetTool(std::size_t *len) const + { + return GetNonPrivateItem(RTCP_SDES_ID_TOOL - 1, len); + } + + /** Returns the SDES note item and stores its length in \c len. */ + uint8_t *GetNote(std::size_t *len) const + { + return GetNonPrivateItem(RTCP_SDES_ID_NOTE - 1, len); + } +#ifdef RTP_SUPPORT_SDESPRIV + /** Starts the iteration over the stored SDES private item prefixes and their associated values. */ + void GotoFirstPrivateValue(); + + /** Returns SDES priv item information. + * If available, returns \c true and stores the next SDES + * private item prefix in \c prefix and its length in + * \c prefixlen. The associated value and its length are + * then stored in \c value and \c valuelen. Otherwise, + * it returns \c false. + */ + bool GetNextPrivateValue(uint8_t **prefix, std::size_t *prefixlen, uint8_t **value, std::size_t *valuelen); + + /** Returns SDES priv item information. + * Looks for the entry which corresponds to the SDES private + * item prefix \c prefix with length \c prefixlen. If found, + * the function returns \c true and stores the associated + * value and its length in \c value and \c valuelen + * respectively. + */ + bool GetPrivateValue(const uint8_t *prefix, std::size_t prefixlen, uint8_t **value, std::size_t *valuelen) const; +#endif // RTP_SUPPORT_SDESPRIV +private: + int SetNonPrivateItem(int itemno, const uint8_t *s, std::size_t l) + { + if (l > RTCP_SDES_MAXITEMLENGTH) + return ERR_RTP_SDES_LENGTHTOOBIG; + return nonprivateitems[itemno].SetInfo(s, l); + } + uint8_t *GetNonPrivateItem(int itemno, std::size_t *len) const + { + return nonprivateitems[itemno].GetInfo(len); + } + + class SDESItem + { + public: + SDESItem() + { + str = 0; + length = 0; + } + ~SDESItem() + { + if (str) + delete[] str; + } + uint8_t *GetInfo(std::size_t *len) const + { + *len = length; + return str; + } + int SetInfo(const uint8_t *s, std::size_t len) + { + return SetString(&str, &length, s, len); + } + protected: + int SetString(uint8_t **dest, std::size_t *destlen, const uint8_t *s, std::size_t len) + { + if (len <= 0) + { + if (*dest) + delete[] (*dest); + *dest = 0; + *destlen = 0; + } + else + { + len = (len > RTCP_SDES_MAXITEMLENGTH) ? RTCP_SDES_MAXITEMLENGTH : len; + uint8_t *str2 = new uint8_t[len]; + memcpy(str2, s, len); + *destlen = len; + if (*dest) + delete[] (*dest); + *dest = str2; + } + return 0; + } + private: + uint8_t *str; + std::size_t length; + }; + + SDESItem nonprivateitems[RTCP_SDES_NUMITEMS_NONPRIVATE]; + +#ifdef RTP_SUPPORT_SDESPRIV + class SDESPrivateItem: public SDESItem + { + public: + SDESPrivateItem() + { + prefixlen = 0; + prefix = 0; + } + ~SDESPrivateItem() + { + if (prefix) + delete[] prefix; + } + uint8_t *GetPrefix(std::size_t *len) const + { + *len = prefixlen; + return prefix; + } + int SetPrefix(const uint8_t *s, std::size_t len) + { + return SetString(&prefix, &prefixlen, s, len); + } + private: + uint8_t *prefix; + std::size_t prefixlen; + }; + + std::list privitems; + std::list::const_iterator curitem; +#endif // RTP_SUPPORT_SDESPRIV +}; + +} // end namespace + +#endif // RTCPSDESINFO_H + diff --git a/qrtplib/rtcpsdespacket.cpp b/qrtplib/rtcpsdespacket.cpp new file mode 100644 index 000000000..5c40d7f10 --- /dev/null +++ b/qrtplib/rtcpsdespacket.cpp @@ -0,0 +1,140 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +#include "rtcpsdespacket.h" + +namespace qrtplib +{ + +RTCPSDESPacket::RTCPSDESPacket(uint8_t *data, std::size_t datalength) : + RTCPPacket(SDES, data, datalength) +{ + knownformat = false; + currentchunk = 0; + itemoffset = 0; + curchunknum = 0; + + RTCPCommonHeader *hdr = (RTCPCommonHeader *) data; + std::size_t len = datalength; + + if (hdr->padding) + { + uint8_t padcount = data[datalength - 1]; + if ((padcount & 0x03) != 0) // not a multiple of four! (see rfc 3550 p 37) + return; + if (((std::size_t) padcount) >= len) + return; + len -= (std::size_t) padcount; + } + + if (hdr->count == 0) + { + if (len != sizeof(RTCPCommonHeader)) + return; + } + else + { + int ssrccount = (int) (hdr->count); + uint8_t *chunk; + int chunkoffset; + + if (len < sizeof(RTCPCommonHeader)) + return; + + len -= sizeof(RTCPCommonHeader); + chunk = data + sizeof(RTCPCommonHeader); + + while ((ssrccount > 0) && (len > 0)) + { + if (len < (sizeof(uint32_t) * 2)) // chunk must contain at least a SSRC identifier + return; // and a (possibly empty) item + + len -= sizeof(uint32_t); + chunkoffset = sizeof(uint32_t); + + bool done = false; + while (!done) + { + if (len < 1) // at least a zero byte (end of item list) should be there + return; + + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (chunk + chunkoffset); + if (sdeshdr->sdesid == 0) // end of item list + { + len--; + chunkoffset++; + + std::size_t r = (chunkoffset & 0x03); + if (r != 0) + { + std::size_t addoffset = 4 - r; + + if (addoffset > len) + return; + len -= addoffset; + chunkoffset += addoffset; + } + done = true; + } + else + { + if (len < sizeof(RTCPSDESHeader)) + return; + + len -= sizeof(RTCPSDESHeader); + chunkoffset += sizeof(RTCPSDESHeader); + + std::size_t itemlen = (std::size_t)(sdeshdr->length); + if (itemlen > len) + return; + + len -= itemlen; + chunkoffset += itemlen; + } + } + + ssrccount--; + chunk += chunkoffset; + } + + // check for remaining bytes + if (len > 0) + return; + if (ssrccount > 0) + return; + } + + knownformat = true; +} + +} // end namespace + diff --git a/qrtplib/rtcpsdespacket.h b/qrtplib/rtcpsdespacket.h new file mode 100644 index 000000000..f51cab448 --- /dev/null +++ b/qrtplib/rtcpsdespacket.h @@ -0,0 +1,386 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +/** + * \file rtcpsdespacket.h + */ + +#ifndef RTCPSDESPACKET_H + +#define RTCPSDESPACKET_H + +#include "rtpconfig.h" +#include "rtcppacket.h" +#include "rtpstructs.h" +#include "rtpdefines.h" +#include "rtpendian.h" + +#include "export.h" + +namespace qrtplib +{ + +class RTCPCompoundPacket; + +/** Describes an RTCP source description packet. */ +class QRTPLIB_API RTCPSDESPacket: public RTCPPacket +{ +public: + /** Identifies the type of an SDES item. */ + enum ItemType + { + None, /**< Used when the iteration over the items has finished. */ + CNAME, /**< Used for a CNAME (canonical name) item. */ + NAME, /**< Used for a NAME item. */ + EMAIL, /**< Used for an EMAIL item. */ + PHONE, /**< Used for a PHONE item. */ + LOC, /**< Used for a LOC (location) item. */ + TOOL, /**< Used for a TOOL item. */ + NOTE, /**< Used for a NOTE item. */ + PRIV, /**< Used for a PRIV item. */ + Unknown /**< Used when there is an item present, but the type is not recognized. */ + }; + + /** Creates an instance based on the data in \c data with length \c datalen. + * Creates an instance based on the data in \c data with length \c datalen. Since the \c data pointer + * is referenced inside the class (no copy of the data is made) one must make sure that the memory it + * points to is valid as long as the class instance exists. + */ + RTCPSDESPacket(uint8_t *data, std::size_t datalen); + ~RTCPSDESPacket() + { + } + + /** Returns the number of SDES chunks in the SDES packet. + * Returns the number of SDES chunks in the SDES packet. Each chunk has its own SSRC identifier. + */ + int GetChunkCount() const; + + /** Starts the iteration over the chunks. + * Starts the iteration. If no SDES chunks are present, the function returns \c false. Otherwise, + * it returns \c true and sets the current chunk to be the first chunk. + */ + bool GotoFirstChunk(); + + /** Sets the current chunk to the next available chunk. + * Sets the current chunk to the next available chunk. If no next chunk is present, this function returns + * \c false, otherwise it returns \c true. + */ + bool GotoNextChunk(); + + /** Returns the SSRC identifier of the current chunk. */ + uint32_t GetChunkSSRC() const; + + /** Starts the iteration over the SDES items in the current chunk. + * Starts the iteration over the SDES items in the current chunk. If no SDES items are + * present, the function returns \c false. Otherwise, the function sets the current item + * to be the first one and returns \c true. + */ + bool GotoFirstItem(); + + /** Advances the iteration to the next item in the current chunk. + * If there's another item in the chunk, the current item is set to be the next one and the function + * returns \c true. Otherwise, the function returns \c false. + */ + bool GotoNextItem(); + + /** Returns the SDES item type of the current item in the current chunk. */ + ItemType GetItemType() const; + + /** Returns the item length of the current item in the current chunk. */ + std::size_t GetItemLength() const; + + /** Returns the item data of the current item in the current chunk. */ + uint8_t *GetItemData(); + +#ifdef RTP_SUPPORT_SDESPRIV + /** If the current item is an SDES PRIV item, this function returns the length of the + * prefix string of the private item. + */ + std::size_t GetPRIVPrefixLength() const; + + /** If the current item is an SDES PRIV item, this function returns actual data of the + * prefix string. + */ + uint8_t *GetPRIVPrefixData(); + + /** If the current item is an SDES PRIV item, this function returns the length of the + * value string of the private item. + */ + std::size_t GetPRIVValueLength() const; + + /** If the current item is an SDES PRIV item, this function returns actual value data of the + * private item. + */ + uint8_t *GetPRIVValueData(); +#endif // RTP_SUPPORT_SDESPRIV + +private: + RTPEndian m_endian; + uint8_t *currentchunk; + int curchunknum; + std::size_t itemoffset; +}; + +inline int RTCPSDESPacket::GetChunkCount() const +{ + if (!knownformat) + return 0; + RTCPCommonHeader *hdr = (RTCPCommonHeader *) data; + return ((int) hdr->count); +} + +inline bool RTCPSDESPacket::GotoFirstChunk() +{ + if (GetChunkCount() == 0) + { + currentchunk = 0; + return false; + } + currentchunk = data + sizeof(RTCPCommonHeader); + curchunknum = 1; + itemoffset = sizeof(uint32_t); + return true; +} + +inline bool RTCPSDESPacket::GotoNextChunk() +{ + if (!knownformat) + return false; + if (currentchunk == 0) + return false; + if (curchunknum == GetChunkCount()) + return false; + + std::size_t offset = sizeof(uint32_t); + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (currentchunk + sizeof(uint32_t)); + + while (sdeshdr->sdesid != 0) + { + offset += sizeof(RTCPSDESHeader); + offset += (std::size_t)(sdeshdr->length); + sdeshdr = (RTCPSDESHeader *) (currentchunk + offset); + } + offset++; // for the zero byte + if ((offset & 0x03) != 0) + offset += (4 - (offset & 0x03)); + currentchunk += offset; + curchunknum++; + itemoffset = sizeof(uint32_t); + return true; +} + +inline uint32_t RTCPSDESPacket::GetChunkSSRC() const +{ + if (!knownformat) + return 0; + if (currentchunk == 0) + return 0; + uint32_t *ssrc = (uint32_t *) currentchunk; + return m_endian.qToHost(*ssrc); +} + +inline bool RTCPSDESPacket::GotoFirstItem() +{ + if (!knownformat) + return false; + if (currentchunk == 0) + return false; + itemoffset = sizeof(uint32_t); + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (currentchunk + itemoffset); + if (sdeshdr->sdesid == 0) + return false; + return true; +} + +inline bool RTCPSDESPacket::GotoNextItem() +{ + if (!knownformat) + return false; + if (currentchunk == 0) + return false; + + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (currentchunk + itemoffset); + if (sdeshdr->sdesid == 0) + return false; + + std::size_t offset = itemoffset; + offset += sizeof(RTCPSDESHeader); + offset += (std::size_t)(sdeshdr->length); + sdeshdr = (RTCPSDESHeader *) (currentchunk + offset); + if (sdeshdr->sdesid == 0) + return false; + itemoffset = offset; + return true; +} + +inline RTCPSDESPacket::ItemType RTCPSDESPacket::GetItemType() const +{ + if (!knownformat) + return None; + if (currentchunk == 0) + return None; + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (currentchunk + itemoffset); + switch (sdeshdr->sdesid) + { + case 0: + return None; + case RTCP_SDES_ID_CNAME: + return CNAME; + case RTCP_SDES_ID_NAME: + return NAME; + case RTCP_SDES_ID_EMAIL: + return EMAIL; + case RTCP_SDES_ID_PHONE: + return PHONE; + case RTCP_SDES_ID_LOCATION: + return LOC; + case RTCP_SDES_ID_TOOL: + return TOOL; + case RTCP_SDES_ID_NOTE: + return NOTE; + case RTCP_SDES_ID_PRIVATE: + return PRIV; + default: + return Unknown; + } + return Unknown; +} + +inline std::size_t RTCPSDESPacket::GetItemLength() const +{ + if (!knownformat) + return None; + if (currentchunk == 0) + return None; + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (currentchunk + itemoffset); + if (sdeshdr->sdesid == 0) + return 0; + return (std::size_t)(sdeshdr->length); +} + +inline uint8_t *RTCPSDESPacket::GetItemData() +{ + if (!knownformat) + return 0; + if (currentchunk == 0) + return 0; + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (currentchunk + itemoffset); + if (sdeshdr->sdesid == 0) + return 0; + return (currentchunk + itemoffset + sizeof(RTCPSDESHeader)); +} + +#ifdef RTP_SUPPORT_SDESPRIV +inline std::size_t RTCPSDESPacket::GetPRIVPrefixLength() const +{ + if (!knownformat) + return 0; + if (currentchunk == 0) + return 0; + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (currentchunk + itemoffset); + if (sdeshdr->sdesid != RTCP_SDES_ID_PRIVATE) + return 0; + if (sdeshdr->length == 0) + return 0; + uint8_t *preflen = currentchunk + itemoffset + sizeof(RTCPSDESHeader); + std::size_t prefixlength = (std::size_t)(*preflen); + if (prefixlength > (std::size_t)((sdeshdr->length) - 1)) + return 0; + return prefixlength; +} + +inline uint8_t *RTCPSDESPacket::GetPRIVPrefixData() +{ + if (!knownformat) + return 0; + if (currentchunk == 0) + return 0; + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (currentchunk + itemoffset); + if (sdeshdr->sdesid != RTCP_SDES_ID_PRIVATE) + return 0; + if (sdeshdr->length == 0) + return 0; + uint8_t *preflen = currentchunk + itemoffset + sizeof(RTCPSDESHeader); + std::size_t prefixlength = (std::size_t)(*preflen); + if (prefixlength > (std::size_t)((sdeshdr->length) - 1)) + return 0; + if (prefixlength == 0) + return 0; + return (currentchunk + itemoffset + sizeof(RTCPSDESHeader) + 1); +} + +inline std::size_t RTCPSDESPacket::GetPRIVValueLength() const +{ + if (!knownformat) + return 0; + if (currentchunk == 0) + return 0; + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (currentchunk + itemoffset); + if (sdeshdr->sdesid != RTCP_SDES_ID_PRIVATE) + return 0; + if (sdeshdr->length == 0) + return 0; + uint8_t *preflen = currentchunk + itemoffset + sizeof(RTCPSDESHeader); + std::size_t prefixlength = (std::size_t)(*preflen); + if (prefixlength > (std::size_t)((sdeshdr->length) - 1)) + return 0; + return ((std::size_t)(sdeshdr->length)) - prefixlength - 1; +} + +inline uint8_t *RTCPSDESPacket::GetPRIVValueData() +{ + if (!knownformat) + return 0; + if (currentchunk == 0) + return 0; + RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *) (currentchunk + itemoffset); + if (sdeshdr->sdesid != RTCP_SDES_ID_PRIVATE) + return 0; + if (sdeshdr->length == 0) + return 0; + uint8_t *preflen = currentchunk + itemoffset + sizeof(RTCPSDESHeader); + std::size_t prefixlength = (std::size_t)(*preflen); + if (prefixlength > (std::size_t)((sdeshdr->length) - 1)) + return 0; + std::size_t valuelen = ((std::size_t)(sdeshdr->length)) - prefixlength - 1; + if (valuelen == 0) + return 0; + return (currentchunk + itemoffset + sizeof(RTCPSDESHeader) + 1 + prefixlength); +} + +#endif // RTP_SUPPORT_SDESPRIV + +} // end namespace + +#endif // RTCPSDESPACKET_H + diff --git a/qrtplib/rtcpsrpacket.cpp b/qrtplib/rtcpsrpacket.cpp new file mode 100644 index 000000000..119bb102a --- /dev/null +++ b/qrtplib/rtcpsrpacket.cpp @@ -0,0 +1,68 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +#include "rtcpsrpacket.h" + +namespace qrtplib +{ + +RTCPSRPacket::RTCPSRPacket(uint8_t *data, std::size_t datalength) : + RTCPPacket(SR, data, datalength) +{ + knownformat = false; + + RTCPCommonHeader *hdr; + std::size_t len = datalength; + std::size_t expectedlength; + + hdr = (RTCPCommonHeader *) data; + if (hdr->padding) + { + uint8_t padcount = data[datalength - 1]; + if ((padcount & 0x03) != 0) // not a multiple of four! (see rfc 3550 p 37) + return; + if (((std::size_t) padcount) >= len) + return; + len -= (std::size_t) padcount; + } + + expectedlength = sizeof(RTCPCommonHeader) + sizeof(uint32_t) + sizeof(RTCPSenderReport); + expectedlength += sizeof(RTCPReceiverReport) * ((int) hdr->count); + + if (expectedlength != len) + return; + + knownformat = true; +} + +} // end namespace + diff --git a/qrtplib/rtcpsrpacket.h b/qrtplib/rtcpsrpacket.h new file mode 100644 index 000000000..5f8f9172f --- /dev/null +++ b/qrtplib/rtcpsrpacket.h @@ -0,0 +1,253 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +/** + * \file rtcpsrpacket.h + */ + +#ifndef RTCPSRPACKET_H + +#define RTCPSRPACKET_H + +#include "rtpconfig.h" +#include "rtcppacket.h" +#include "rtptimeutilities.h" +#include "rtpstructs.h" +#include "rtpendian.h" + +#include "export.h" + +namespace qrtplib +{ + +class RTCPCompoundPacket; + +/** Describes an RTCP sender report packet. */ +class QRTPLIB_API RTCPSRPacket: public RTCPPacket +{ +public: + /** Creates an instance based on the data in \c data with length \c datalen. + * Creates an instance based on the data in \c data with length \c datalen. Since the \c data pointer + * is referenced inside the class (no copy of the data is made) one must make sure that the memory it + * points to is valid as long as the class instance exists. + */ + RTCPSRPacket(uint8_t *data, std::size_t datalength); + ~RTCPSRPacket() + { + } + + /** Returns the SSRC of the participant who sent this packet. */ + uint32_t GetSenderSSRC() const; + + /** Returns the NTP timestamp contained in the sender report. */ + RTPNTPTime GetNTPTimestamp() const; + + /** Returns the RTP timestamp contained in the sender report. */ + uint32_t GetRTPTimestamp() const; + + /** Returns the sender's packet count contained in the sender report. */ + uint32_t GetSenderPacketCount() const; + + /** Returns the sender's octet count contained in the sender report. */ + uint32_t GetSenderOctetCount() const; + + /** Returns the number of reception report blocks present in this packet. */ + int GetReceptionReportCount() const; + + /** Returns the SSRC of the reception report block described by \c index which may have a value + * from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetSSRC(int index) const; + + /** Returns the `fraction lost' field of the reception report described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint8_t GetFractionLost(int index) const; + + /** Returns the number of lost packets in the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + int32_t GetLostPacketCount(int index) const; + + /** Returns the extended highest sequence number of the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetExtendedHighestSequenceNumber(int index) const; + + /** Returns the jitter field of the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetJitter(int index) const; + + /** Returns the LSR field of the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetLSR(int index) const; + + /** Returns the DLSR field of the reception report block described by \c index which may have + * a value from 0 to GetReceptionReportCount()-1 (note that no check is performed to see if \c index is + * valid). + */ + uint32_t GetDLSR(int index) const; + +private: + RTCPReceiverReport *GotoReport(int index) const; + + RTPEndian m_endian; +}; + +inline uint32_t RTCPSRPacket::GetSenderSSRC() const +{ + if (!knownformat) + return 0; + + uint32_t *ssrcptr = (uint32_t *) (data + sizeof(RTCPCommonHeader)); + return m_endian.qToHost(*ssrcptr); +} + +inline RTPNTPTime RTCPSRPacket::GetNTPTimestamp() const +{ + if (!knownformat) + return RTPNTPTime(0, 0); + + RTCPSenderReport *sr = (RTCPSenderReport *) (data + sizeof(RTCPCommonHeader) + sizeof(uint32_t)); + return RTPNTPTime(m_endian.qToHost(sr->ntptime_msw), m_endian.qToHost(sr->ntptime_lsw)); +} + +inline uint32_t RTCPSRPacket::GetRTPTimestamp() const +{ + if (!knownformat) + return 0; + RTCPSenderReport *sr = (RTCPSenderReport *) (data + sizeof(RTCPCommonHeader) + sizeof(uint32_t)); + return m_endian.qToHost(sr->rtptimestamp); +} + +inline uint32_t RTCPSRPacket::GetSenderPacketCount() const +{ + if (!knownformat) + return 0; + RTCPSenderReport *sr = (RTCPSenderReport *) (data + sizeof(RTCPCommonHeader) + sizeof(uint32_t)); + return m_endian.qToHost(sr->packetcount); +} + +inline uint32_t RTCPSRPacket::GetSenderOctetCount() const +{ + if (!knownformat) + return 0; + RTCPSenderReport *sr = (RTCPSenderReport *) (data + sizeof(RTCPCommonHeader) + sizeof(uint32_t)); + return m_endian.qToHost(sr->octetcount); +} + +inline int RTCPSRPacket::GetReceptionReportCount() const +{ + if (!knownformat) + return 0; + RTCPCommonHeader *hdr = (RTCPCommonHeader *) data; + return ((int) hdr->count); +} + +inline RTCPReceiverReport *RTCPSRPacket::GotoReport(int index) const +{ + RTCPReceiverReport *r = (RTCPReceiverReport *) (data + sizeof(RTCPCommonHeader) + sizeof(uint32_t) + sizeof(RTCPSenderReport) + index * sizeof(RTCPReceiverReport)); + return r; +} + +inline uint32_t RTCPSRPacket::GetSSRC(int index) const +{ + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return m_endian.qToHost(r->ssrc); +} + +inline uint8_t RTCPSRPacket::GetFractionLost(int index) const +{ + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return r->fractionlost; +} + +inline int32_t RTCPSRPacket::GetLostPacketCount(int index) const +{ + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + uint32_t count = ((uint32_t) r->packetslost[2]) | (((uint32_t) r->packetslost[1]) << 8) | (((uint32_t) r->packetslost[0]) << 16); + if ((count & 0x00800000) != 0) // test for negative number + count |= 0xFF000000; + int32_t *count2 = (int32_t *) (&count); + return (*count2); +} + +inline uint32_t RTCPSRPacket::GetExtendedHighestSequenceNumber(int index) const +{ + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return m_endian.qToHost(r->exthighseqnr); +} + +inline uint32_t RTCPSRPacket::GetJitter(int index) const +{ + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return m_endian.qToHost(r->jitter); +} + +inline uint32_t RTCPSRPacket::GetLSR(int index) const +{ + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return m_endian.qToHost(r->lsr); +} + +inline uint32_t RTCPSRPacket::GetDLSR(int index) const +{ + if (!knownformat) + return 0; + RTCPReceiverReport *r = GotoReport(index); + return m_endian.qToHost(r->dlsr); +} + +} // end namespace + +#endif // RTCPSRPACKET_H + diff --git a/qrtplib/rtcpunknownpacket.h b/qrtplib/rtcpunknownpacket.h new file mode 100644 index 000000000..8bb3e3e4a --- /dev/null +++ b/qrtplib/rtcpunknownpacket.h @@ -0,0 +1,76 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +/** + * \file rtcpunknownpacket.h + */ + +#ifndef RTCPUNKNOWNPACKET_H + +#define RTCPUNKNOWNPACKET_H + +#include "rtpconfig.h" +#include "rtcppacket.h" + +namespace qrtplib +{ + +class RTCPCompoundPacket; + +/** Describes an RTCP packet of unknown type. + * Describes an RTCP packet of unknown type. This class doesn't have any extra member functions besides + * the ones it inherited. Note that since an unknown packet type doesn't have any format to check + * against, the IsKnownFormat function will trivially return \c true. + */ +class RTCPUnknownPacket: public RTCPPacket +{ +public: + /** Creates an instance based on the data in \c data with length \c datalen. + * Creates an instance based on the data in \c data with length \c datalen. Since the \c data pointer + * is referenced inside the class (no copy of the data is made) one must make sure that the memory it + * points to is valid as long as the class instance exists. + */ + RTCPUnknownPacket(uint8_t *data, std::size_t datalen) : + RTCPPacket(Unknown, data, datalen) + { + // Since we don't expect a format, we'll trivially put knownformat = true + knownformat = true; + } + ~RTCPUnknownPacket() + { + } +}; + +} // end namespace + +#endif // RTCPUNKNOWNPACKET_H + diff --git a/qrtplib/rtpaddress.cpp b/qrtplib/rtpaddress.cpp new file mode 100644 index 000000000..28b5979f4 --- /dev/null +++ b/qrtplib/rtpaddress.cpp @@ -0,0 +1,85 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +#include "rtpaddress.h" + +namespace qrtplib +{ + +RTPAddress *RTPAddress::CreateCopy() const +{ + RTPAddress *a = new RTPAddress(); + a->address = address; + a->port = port; + a->rtcpsendport = rtcpsendport; + return a; +} + +bool RTPAddress::IsSameAddress(const RTPAddress *addr) const +{ + if (addr == 0) { + return false; + } + + if (addr->address.protocol() != address.protocol()) { + return false; + } + + if (addr->address == address) + { + return addr->port == port; + } + else + { + return false; + } +} + +bool RTPAddress::IsFromSameHost(const RTPAddress *addr) const +{ + if (addr == 0) { + return false; + } + + if (addr->address.protocol() != address.protocol()) { + return false; + } + + return addr->address == address; +} + +bool RTPAddress::operator==(const RTPAddress& otherAddress) +{ + return IsSameAddress(&otherAddress); +} + +} // namespace diff --git a/qrtplib/rtpaddress.h b/qrtplib/rtpaddress.h new file mode 100644 index 000000000..3d69f3941 --- /dev/null +++ b/qrtplib/rtpaddress.h @@ -0,0 +1,144 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +/** + * \file rtpaddress.h + */ + +#ifndef RTPADDRESS_H + +#define RTPADDRESS_H + +#include "rtpconfig.h" +#include +#include +#include + +#include "export.h" + +namespace qrtplib +{ + +/** This class is an abstract class which is used to specify destinations, multicast groups etc. */ +class QRTPLIB_API RTPAddress +{ +public: + /** Default constructor. Address and port set via setters */ + RTPAddress() : port(0), rtcpsendport(0) + {} + + /** Constructor with address and port */ + RTPAddress(const QHostAddress& address, uint16_t port) : + address(address), + port(port), + rtcpsendport(0) + {} + + /** Returns the type of address the actual implementation represents. */ + QAbstractSocket::NetworkLayerProtocol GetAddressType() const + { + return address.protocol(); + } + + /** Creates a copy of the RTPAddress instance. + * Creates a copy of the RTPAddress instance. If \c mgr is not NULL, the + * corresponding memory manager will be used to allocate the memory for the address + * copy. + */ + RTPAddress *CreateCopy() const; + + /** Checks if the address \c addr is the same address as the one this instance represents. + * Checks if the address \c addr is the same address as the one this instance represents. + * Implementations must be able to handle a NULL argument. + * + * Note that this function is only used for received packets, and for those + * the rtcpsendport variable is not important and should be ignored. + */ + bool IsSameAddress(const RTPAddress *addr) const; + + /** Checks if the address \c addr represents the same host as this instance. + * Checks if the address \c addr represents the same host as this instance. Implementations + * must be able to handle a NULL argument. + * + * Note that this function is only used for received packets. + */ + bool IsFromSameHost(const RTPAddress *addr) const; + + /** Equality */ + bool operator==(const RTPAddress& otherAddress); + + /** Get host address */ + const QHostAddress& getAddress() const + { + return address; + } + + /** Set host address */ + void setAddress(const QHostAddress& address) + { + this->address = address; + } + + /** Get RTP port */ + uint16_t getPort() const + { + return port; + } + + /** Set RTP port */ + void setPort(uint16_t port) + { + this->port = port; + } + + /** Get RTCP port */ + uint16_t getRtcpsendport() const + { + return rtcpsendport; + } + + /** Set RTCP port */ + void setRtcpsendport(uint16_t rtcpsendport) + { + this->rtcpsendport = rtcpsendport; + } + +private: + QHostAddress address; + uint16_t port; + uint16_t rtcpsendport; +}; + +} // end namespace + +#endif // RTPADDRESS_H + diff --git a/qrtplib/rtpcollisionlist.cpp b/qrtplib/rtpcollisionlist.cpp new file mode 100644 index 000000000..10fa99436 --- /dev/null +++ b/qrtplib/rtpcollisionlist.cpp @@ -0,0 +1,112 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +#include "rtpcollisionlist.h" +#include "rtperrors.h" + +namespace qrtplib +{ + +RTPCollisionList::RTPCollisionList() +{ + timeinit.Dummy(); +} + +void RTPCollisionList::Clear() +{ + std::list::iterator it; + + for (it = addresslist.begin(); it != addresslist.end(); it++) + delete (*it).addr; + addresslist.clear(); +} + +int RTPCollisionList::UpdateAddress(const RTPAddress *addr, const RTPTime &receivetime, bool *created) +{ + if (addr == 0) + return ERR_RTP_COLLISIONLIST_BADADDRESS; + + std::list::iterator it; + + for (it = addresslist.begin(); it != addresslist.end(); it++) + { + if (((*it).addr)->IsSameAddress(addr)) + { + (*it).recvtime = receivetime; + *created = false; + return 0; + } + } + + RTPAddress *newaddr = addr->CreateCopy(); + if (newaddr == 0) + return ERR_RTP_OUTOFMEM; + + addresslist.push_back(AddressAndTime(newaddr, receivetime)); + *created = true; + return 0; +} + +bool RTPCollisionList::HasAddress(const RTPAddress *addr) const +{ + std::list::const_iterator it; + + for (it = addresslist.begin(); it != addresslist.end(); it++) + { + if (((*it).addr)->IsSameAddress(addr)) + return true; + } + + return false; +} + +void RTPCollisionList::Timeout(const RTPTime ¤ttime, const RTPTime &timeoutdelay) +{ + std::list::iterator it; + RTPTime checktime = currenttime; + checktime -= timeoutdelay; + + it = addresslist.begin(); + while (it != addresslist.end()) + { + if ((*it).recvtime < checktime) // timeout + { + delete (*it).addr; + it = addresslist.erase(it); + } + else + it++; + } +} + +} // end namespace + diff --git a/qrtplib/rtpcollisionlist.h b/qrtplib/rtpcollisionlist.h new file mode 100644 index 000000000..e17e01c28 --- /dev/null +++ b/qrtplib/rtpcollisionlist.h @@ -0,0 +1,100 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +/** + * \file rtpcollisionlist.h + */ + +#ifndef RTPCOLLISIONLIST_H + +#define RTPCOLLISIONLIST_H + +#include "rtpconfig.h" +#include "rtpaddress.h" +#include "rtptimeutilities.h" +#include + +#include "export.h" + +namespace qrtplib +{ + +class RTPAddress; + +/** This class represents a list of addresses from which SSRC collisions were detected. */ +class QRTPLIB_API RTPCollisionList +{ +public: + /** Constructs an instance, optionally installing a memory manager. */ + RTPCollisionList(); + ~RTPCollisionList() + { + Clear(); + } + + /** Clears the list of addresses. */ + void Clear(); + + /** Updates the entry for address \c addr to indicate that a collision was detected at time \c receivetime. + * Updates the entry for address \c addr to indicate that a collision was detected at time \c receivetime. + * If the entry did not exist yet, the flag \c created is set to \c true, otherwise it is set to \c false. + */ + int UpdateAddress(const RTPAddress *addr, const RTPTime &receivetime, bool *created); + + /** Returns \c true} if the address \c addr appears in the list. */ + bool HasAddress(const RTPAddress *addr) const; + + /** Assuming that the current time is given by \c currenttime, this function times out entries which + * haven't been updated in the previous time interval specified by \c timeoutdelay. + */ + void Timeout(const RTPTime ¤ttime, const RTPTime &timeoutdelay); + +private: + class AddressAndTime + { + public: + AddressAndTime(RTPAddress *a, const RTPTime &t) : + addr(a), recvtime(t) + { + } + + RTPAddress *addr; + RTPTime recvtime; + }; + + std::list addresslist; +}; + +} // end namespace + +#endif // RTPCOLLISIONLIST_H + diff --git a/qrtplib/rtpconfig.h b/qrtplib/rtpconfig.h new file mode 100644 index 000000000..faf4e6e2f --- /dev/null +++ b/qrtplib/rtpconfig.h @@ -0,0 +1,96 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +#ifndef RTPCONFIG_UNIX_H + +#define RTPCONFIG_UNIX_H + +// Don't have + +// Don't have + +// Little endian system + +#define RTP_SOCKLENTYPE_UINT + +// No sa_len member in struct sockaddr + +#define RTP_SUPPORT_IPV4MULTICAST + +// No support for JThread was enabled + +#define RTP_SUPPORT_SDESPRIV + +// No #define RTP_SUPPORT_PROBATION + +#define RTP_SUPPORT_GETLOGINR + +// no #define RTP_SUPPORT_IPV6 + +// no #define RTP_SUPPORT_IPV6MULTICAST + +#define RTP_SUPPORT_IFADDRS + +#define RTP_SUPPORT_SENDAPP + +// No #define RTP_SUPPORT_MEMORYMANAGEMENT + +// No support for sending unknown RTCP packets + +// no #define RTP_SUPPORT_NETINET_IN + +// Not using winsock sockets + +// No QueryPerformanceCounter support + +// No ui64 suffix + +// Stdio snprintf version + +#define RTP_HAVE_ARRAYALLOC + +// No rand_s support + +// No strncpy_s support + +// No SRTP support + +#define RTP_HAVE_CLOCK_GETTIME + +#define RTP_HAVE_POLL + +// No 'WSAPoll' support + +#define RTP_HAVE_MSG_NOSIGNAL + +#endif // RTPCONFIG_UNIX_H + diff --git a/qrtplib/rtpdefines.h b/qrtplib/rtpdefines.h index c1d39af42..6406fa3e3 100644 --- a/qrtplib/rtpdefines.h +++ b/qrtplib/rtpdefines.h @@ -1,36 +1,34 @@ /* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #ifndef RTPDEFINES_H diff --git a/qrtplib/rtpendian.h b/qrtplib/rtpendian.h new file mode 100644 index 000000000..6a009da79 --- /dev/null +++ b/qrtplib/rtpendian.h @@ -0,0 +1,38 @@ +/* + * rtpendian.h + * + * Created on: Feb 27, 2018 + * Author: f4exb + */ + +#ifndef QRTPLIB_RTPENDIAN_H_ +#define QRTPLIB_RTPENDIAN_H_ + +#include + +namespace qrtplib +{ + +class RTPEndian +{ +public: + RTPEndian() + { + uint32_t endianTest32 = 1; + uint8_t *ptr = (uint8_t*) &endianTest32; + m_isLittleEndian = (*ptr == 1); + } + + template + T qToHost(const T& x) const + { + return m_isLittleEndian ? qToLittleEndian(x) : qToBigEndian(x); + } + +private: + bool m_isLittleEndian; +}; + +} + +#endif /* QRTPLIB_RTPENDIAN_H_ */ diff --git a/qrtplib/rtperrors.cpp b/qrtplib/rtperrors.cpp index 1b0eb4068..1baa7c872 100644 --- a/qrtplib/rtperrors.cpp +++ b/qrtplib/rtperrors.cpp @@ -1,275 +1,270 @@ /* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtperrors.h" #include "rtpdefines.h" #include "rtpinternalutils.h" #include -//#include "rtpdebug.h" - namespace qrtplib { struct RTPErrorInfo { - int code; - const char *description; + int code; + const char *description; }; -static RTPErrorInfo ErrorDescriptions[]= +static RTPErrorInfo ErrorDescriptions[] = { - { ERR_RTP_OUTOFMEM,"Out of memory" }, - { ERR_RTP_NOTHREADSUPPORT, "No JThread support was compiled in"}, - { ERR_RTP_COLLISIONLIST_BADADDRESS, "Passed invalid address (null) to collision list"}, - { ERR_RTP_HASHTABLE_ELEMENTALREADYEXISTS, "Element already exists in hash table"}, - { ERR_RTP_HASHTABLE_ELEMENTNOTFOUND, "Element not found in hash table"}, - { ERR_RTP_HASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX, "Function returned an illegal hash index"}, - { ERR_RTP_HASHTABLE_NOCURRENTELEMENT, "No current element selected in hash table"}, - { ERR_RTP_KEYHASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX, "Function returned an illegal hash index"}, - { ERR_RTP_KEYHASHTABLE_KEYALREADYEXISTS, "Key value already exists in key hash table"}, - { ERR_RTP_KEYHASHTABLE_KEYNOTFOUND, "Key value not found in key hash table"}, - { ERR_RTP_KEYHASHTABLE_NOCURRENTELEMENT, "No current element selected in key hash table"}, - { ERR_RTP_PACKBUILD_ALREADYINIT, "RTP packet builder is already initialized"}, - { ERR_RTP_PACKBUILD_CSRCALREADYINLIST, "The specified CSRC is already in the RTP packet builder's CSRC list"}, - { ERR_RTP_PACKBUILD_CSRCLISTFULL, "The RTP packet builder's CSRC list already contains 15 entries"}, - { ERR_RTP_PACKBUILD_CSRCNOTINLIST, "The specified CSRC was not found in the RTP packet builder's CSRC list"}, - { ERR_RTP_PACKBUILD_DEFAULTMARKNOTSET, "The RTP packet builder's default mark flag is not set"}, - { ERR_RTP_PACKBUILD_DEFAULTPAYLOADTYPENOTSET, "The RTP packet builder's default payload type is not set"}, - { ERR_RTP_PACKBUILD_DEFAULTTSINCNOTSET, "The RTP packet builder's default timestamp increment is not set"}, - { ERR_RTP_PACKBUILD_INVALIDMAXPACKETSIZE, "The specified maximum packet size for the RTP packet builder is invalid"}, - { ERR_RTP_PACKBUILD_NOTINIT, "The RTP packet builder is not initialized"}, - { ERR_RTP_PACKET_BADPAYLOADTYPE, "Invalid payload type"}, - { ERR_RTP_PACKET_DATAEXCEEDSMAXSIZE, "Tried to create an RTP packet which whould exceed the specified maximum packet size"}, - { ERR_RTP_PACKET_EXTERNALBUFFERNULL, "Illegal value (null) passed as external buffer for the RTP packet"}, - { ERR_RTP_PACKET_ILLEGALBUFFERSIZE, "Illegal buffer size specified for the RTP packet"}, - { ERR_RTP_PACKET_INVALIDPACKET, "Invalid RTP packet format"}, - { ERR_RTP_PACKET_TOOMANYCSRCS, "More than 15 CSRCs specified for the RTP packet"}, - { ERR_RTP_POLLTHREAD_ALREADYRUNNING, "Poll thread is already running"}, - { ERR_RTP_POLLTHREAD_CANTINITMUTEX, "Can't initialize a mutex for the poll thread"}, - { ERR_RTP_POLLTHREAD_CANTSTARTTHREAD, "Can't start the poll thread"}, - { ERR_RTP_RTCPCOMPOUND_INVALIDPACKET, "Invalid RTCP compound packet format"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILDING, "Already building this RTCP compound packet"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILT, "This RTCP compound packet is already built"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYGOTREPORT, "There's already a SR or RR in this RTCP compound packet"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_APPDATALENTOOBIG, "The specified APP data length for the RTCP compound packet is too big"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_BUFFERSIZETOOSMALL, "The specified buffer size for the RTCP comound packet is too small"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_ILLEGALAPPDATALENGTH, "The APP data length must be a multiple of four"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_ILLEGALSUBTYPE, "The APP packet subtype must be smaller than 32"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_INVALIDITEMTYPE, "Invalid SDES item type specified for the RTCP compound packet"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_MAXPACKETSIZETOOSMALL, "The specified maximum packet size for the RTCP compound packet is too small"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_NOCURRENTSOURCE, "Tried to add an SDES item to the RTCP compound packet when no SSRC was present"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_NOREPORTPRESENT, "An RTCP compound packet must contain a SR or RR"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING, "The RTCP compound packet builder is not initialized"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT, "Adding this data would exceed the specified maximum RTCP compound packet size"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_REPORTNOTSTARTED, "Tried to add a report block to the RTCP compound packet when no SR or RR was started"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_TOOMANYSSRCS, "Only 31 SSRCs will fit into a BYE packet for the RTCP compound packet"}, - { ERR_RTP_RTCPCOMPPACKBUILDER_TOTALITEMLENGTHTOOBIG, "The total data for the SDES PRIV item exceeds the maximum size (255 bytes) of an SDES item"}, - { ERR_RTP_RTCPPACKETBUILDER_ALREADYINIT, "The RTCP packet builder is already initialized"}, - { ERR_RTP_RTCPPACKETBUILDER_ILLEGALMAXPACKSIZE, "The specified maximum packet size for the RTCP packet builder is too small"}, - { ERR_RTP_RTCPPACKETBUILDER_ILLEGALTIMESTAMPUNIT, "Speficied an illegal timestamp unit for the the RTCP packet builder"}, - { ERR_RTP_RTCPPACKETBUILDER_NOTINIT, "The RTCP packet builder was not initialized"}, - { ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON, "The RTCP compound packet filled sooner than expected"}, - { ERR_RTP_SCHEDPARAMS_BADFRACTION, "Illegal sender bandwidth fraction specified"}, - { ERR_RTP_SCHEDPARAMS_BADMINIMUMINTERVAL, "The minimum RTCP interval specified for the scheduler is too small"}, - { ERR_RTP_SCHEDPARAMS_INVALIDBANDWIDTH, "Invalid RTCP bandwidth specified for the RTCP scheduler"}, - { ERR_RTP_SDES_LENGTHTOOBIG, "Specified size for the SDES item exceeds 255 bytes"}, - { ERR_RTP_SDES_PREFIXNOTFOUND, "The specified SDES PRIV prefix was not found"}, - { ERR_RTP_SESSION_ALREADYCREATED, "The session is already created"}, - { ERR_RTP_SESSION_CANTGETLOGINNAME, "Can't retrieve login name"}, - { ERR_RTP_SESSION_CANTINITMUTEX, "A mutex for the RTP session couldn't be initialized"}, - { ERR_RTP_SESSION_MAXPACKETSIZETOOSMALL, "The maximum packet size specified for the RTP session is too small"}, - { ERR_RTP_SESSION_NOTCREATED, "The RTP session was not created"}, - { ERR_RTP_SESSION_UNSUPPORTEDTRANSMISSIONPROTOCOL, "The requested transmission protocol for the RTP session is not supported"}, - { ERR_RTP_SESSION_USINGPOLLTHREAD, "This function is not available when using the RTP poll thread feature"}, - { ERR_RTP_SESSION_USERDEFINEDTRANSMITTERNULL, "A user-defined transmitter was requested but the supplied transmitter component is NULL"}, - { ERR_RTP_SOURCES_ALREADYHAVEOWNSSRC, "Only one source can be marked as own SSRC in the source table"}, - { ERR_RTP_SOURCES_DONTHAVEOWNSSRC, "No source was marked as own SSRC in the source table"}, - { ERR_RTP_SOURCES_ILLEGALSDESTYPE, "Illegal SDES type specified for processing into the source table"}, - { ERR_RTP_SOURCES_SSRCEXISTS, "Can't create own SSRC because this SSRC identifier is already in the source table"}, - { ERR_RTP_UDPV4TRANS_ALREADYCREATED, "The transmitter was already created"}, - { ERR_RTP_UDPV4TRANS_ALREADYINIT, "The transmitter was already initialize"}, - { ERR_RTP_UDPV4TRANS_ALREADYWAITING, "The transmitter is already waiting for incoming data"}, - { ERR_RTP_UDPV4TRANS_CANTBINDRTCPSOCKET, "The 'bind' call for the RTCP socket failed"}, - { ERR_RTP_UDPV4TRANS_CANTBINDRTPSOCKET, "The 'bind' call for the RTP socket failed"}, - { ERR_RTP_UDPV4TRANS_CANTCREATESOCKET, "Couldn't create the RTP or RTCP socket"}, - { ERR_RTP_UDPV4TRANS_CANTINITMUTEX, "Failed to initialize a mutex used by the transmitter"}, - { ERR_RTP_UDPV4TRANS_CANTSETRTCPRECEIVEBUF, "Couldn't set the receive buffer size for the RTCP socket"}, - { ERR_RTP_UDPV4TRANS_CANTSETRTCPTRANSMITBUF, "Couldn't set the transmission buffer size for the RTCP socket"}, - { ERR_RTP_UDPV4TRANS_CANTSETRTPRECEIVEBUF, "Couldn't set the receive buffer size for the RTP socket"}, - { ERR_RTP_UDPV4TRANS_CANTSETRTPTRANSMITBUF, "Couldn't set the transmission buffer size for the RTP socket"}, - { ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP, "Unable to join the specified multicast group"}, - { ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE, "The function called doens't match the current receive mode"}, - { ERR_RTP_UDPV4TRANS_ILLEGALPARAMETERS, "Illegal parameters type passed to the transmitter"}, - { ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE, "Specified address type isn't compatible with this transmitter"}, - { ERR_RTP_UDPV4TRANS_NOLOCALIPS, "Couldn't determine the local host name since the local IP list is empty"}, - { ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT, "Multicast support is not available"}, - { ERR_RTP_UDPV4TRANS_NOSUCHENTRY, "Specified entry could not be found"}, - { ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS, "The specified address is not a multicast address"}, - { ERR_RTP_UDPV4TRANS_NOTCREATED, "The 'Create' call for this transmitter has not been called"}, - { ERR_RTP_UDPV4TRANS_NOTINIT, "The 'Init' call for this transmitter has not been called"}, - { ERR_RTP_UDPV4TRANS_NOTWAITING, "The transmitter is not waiting for incoming data"}, - { ERR_RTP_UDPV4TRANS_PORTBASENOTEVEN, "The specified port base is not an even number"}, - { ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG, "The maximum packet size is too big for this transmitter"}, - { ERR_RTP_UDPV6TRANS_ALREADYCREATED, "The transmitter was already created"}, - { ERR_RTP_UDPV6TRANS_ALREADYINIT, "The transmitter was already initialize"}, - { ERR_RTP_UDPV6TRANS_ALREADYWAITING, "The transmitter is already waiting for incoming data"}, - { ERR_RTP_UDPV6TRANS_CANTBINDRTCPSOCKET, "The 'bind' call for the RTCP socket failed"}, - { ERR_RTP_UDPV6TRANS_CANTBINDRTPSOCKET, "The 'bind' call for the RTP socket failed"}, - { ERR_RTP_UDPV6TRANS_CANTCREATESOCKET, "Couldn't create the RTP or RTCP socket"}, - { ERR_RTP_UDPV6TRANS_CANTINITMUTEX, "Failed to initialize a mutex used by the transmitter"}, - { ERR_RTP_UDPV6TRANS_CANTSETRTCPRECEIVEBUF, "Couldn't set the receive buffer size for the RTCP socket"}, - { ERR_RTP_UDPV6TRANS_CANTSETRTCPTRANSMITBUF, "Couldn't set the transmission buffer size for the RTCP socket"}, - { ERR_RTP_UDPV6TRANS_CANTSETRTPRECEIVEBUF, "Couldn't set the receive buffer size for the RTP socket"}, - { ERR_RTP_UDPV6TRANS_CANTSETRTPTRANSMITBUF, "Couldn't set the transmission buffer size for the RTP socket"}, - { ERR_RTP_UDPV6TRANS_COULDNTJOINMULTICASTGROUP, "Unable to join the specified multicast group"}, - { ERR_RTP_UDPV6TRANS_DIFFERENTRECEIVEMODE, "The function called doens't match the current receive mode"}, - { ERR_RTP_UDPV6TRANS_ILLEGALPARAMETERS, "Illegal parameters type passed to the transmitter"}, - { ERR_RTP_UDPV6TRANS_INVALIDADDRESSTYPE, "Specified address type isn't compatible with this transmitter"}, - { ERR_RTP_UDPV6TRANS_NOLOCALIPS, "Couldn't determine the local host name since the local IP list is empty"}, - { ERR_RTP_UDPV6TRANS_NOMULTICASTSUPPORT, "Multicast support is not available"}, - { ERR_RTP_UDPV6TRANS_NOSUCHENTRY, "Specified entry could not be found"}, - { ERR_RTP_UDPV6TRANS_NOTAMULTICASTADDRESS, "The specified address is not a multicast address"}, - { ERR_RTP_UDPV6TRANS_NOTCREATED, "The 'Create' call for this transmitter has not been called"}, - { ERR_RTP_UDPV6TRANS_NOTINIT, "The 'Init' call for this transmitter has not been called"}, - { ERR_RTP_UDPV6TRANS_NOTWAITING, "The transmitter is not waiting for incoming data"}, - { ERR_RTP_UDPV6TRANS_PORTBASENOTEVEN, "The specified port base is not an even number"}, - { ERR_RTP_UDPV6TRANS_SPECIFIEDSIZETOOBIG, "The maximum packet size is too big for this transmitter"}, - { ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL,"The hostname is larger than the specified buffer size"}, - { ERR_RTP_SDES_MAXPRIVITEMS,"The maximum number of SDES private item prefixes was reached"}, - { ERR_RTP_INTERNALSOURCEDATA_INVALIDPROBATIONTYPE,"An invalid probation type was specified"}, - { ERR_RTP_FAKETRANS_ALREADYCREATED, "The transmitter was already created"}, - { ERR_RTP_FAKETRANS_ALREADYINIT, "The transmitter was already initialize"}, - { ERR_RTP_FAKETRANS_CANTINITMUTEX, "Failed to initialize a mutex used by the transmitter"}, - { ERR_RTP_FAKETRANS_COULDNTJOINMULTICASTGROUP, "Unable to join the specified multicast group"}, - { ERR_RTP_FAKETRANS_DIFFERENTRECEIVEMODE, "The function called doens't match the current receive mode"}, - { ERR_RTP_FAKETRANS_ILLEGALPARAMETERS, "Illegal parameters type passed to the transmitter"}, - { ERR_RTP_FAKETRANS_INVALIDADDRESSTYPE, "Specified address type isn't compatible with this transmitter"}, - { ERR_RTP_FAKETRANS_NOLOCALIPS, "Couldn't determine the local host name since the local IP list is empty"}, - { ERR_RTP_FAKETRANS_NOMULTICASTSUPPORT, "Multicast support is not available"}, - { ERR_RTP_FAKETRANS_NOSUCHENTRY, "Specified entry could not be found"}, - { ERR_RTP_FAKETRANS_NOTAMULTICASTADDRESS, "The specified address is not a multicast address"}, - { ERR_RTP_FAKETRANS_NOTCREATED, "The 'Create' call for this transmitter has not been called"}, - { ERR_RTP_FAKETRANS_NOTINIT, "The 'Init' call for this transmitter has not been called"}, - { ERR_RTP_FAKETRANS_PORTBASENOTEVEN, "The specified port base is not an even number"}, - { ERR_RTP_FAKETRANS_SPECIFIEDSIZETOOBIG, "The maximum packet size is too big for this transmitter"}, - { ERR_RTP_FAKETRANS_WAITNOTIMPLEMENTED, "The WaitForIncomingData is not implemented in the Gst transmitter"}, - { ERR_RTP_RTPRANDOMURANDOM_CANTOPEN, "Unable to open /dev/urandom for reading"}, - { ERR_RTP_RTPRANDOMURANDOM_ALREADYOPEN, "The device /dev/urandom was already opened"}, - { ERR_RTP_RTPRANDOMRANDS_NOTSUPPORTED, "The rand_s call is not supported on this platform"}, - { ERR_RTP_EXTERNALTRANS_ALREADYCREATED, "The external transmission component was already created"}, - { ERR_RTP_EXTERNALTRANS_ALREADYINIT, "The external transmission component was already initialized"}, - { ERR_RTP_EXTERNALTRANS_ALREADYWAITING, "The external transmission component is already waiting for incoming data"}, - { ERR_RTP_EXTERNALTRANS_BADRECEIVEMODE, "The external transmission component only supports accepting all incoming packets"}, - { ERR_RTP_EXTERNALTRANS_CANTINITMUTEX, "The external transmitter was unable to initialize a required mutex"}, - { ERR_RTP_EXTERNALTRANS_ILLEGALPARAMETERS, "Only parameters of type RTPExternalTransmissionParams can be passed to the external transmission component"}, - { ERR_RTP_EXTERNALTRANS_NOACCEPTLIST, "The external transmitter does not have an accept list"}, - { ERR_RTP_EXTERNALTRANS_NODESTINATIONSSUPPORTED, "The external transmitter does not have a destination list"}, - { ERR_RTP_EXTERNALTRANS_NOIGNORELIST, "The external transmitter does not have an ignore list"}, - { ERR_RTP_EXTERNALTRANS_NOMULTICASTSUPPORT, "The external transmitter does not support the multicast functions"}, - { ERR_RTP_EXTERNALTRANS_NOSENDER, "No sender has been set for this external transmitter"}, - { ERR_RTP_EXTERNALTRANS_NOTCREATED, "The external transmitter has not been created yet"}, - { ERR_RTP_EXTERNALTRANS_NOTINIT, "The external transmitter has not been initialized yet"}, - { ERR_RTP_EXTERNALTRANS_NOTWAITING, "The external transmitter is not currently waiting for incoming data"}, - { ERR_RTP_EXTERNALTRANS_SENDERROR, "The external transmitter was unable to actually send the data"}, - { ERR_RTP_EXTERNALTRANS_SPECIFIEDSIZETOOBIG, "The specified data size exceeds the maximum amount that has been set"}, - { ERR_RTP_UDPV4TRANS_CANTGETSOCKETPORT, "Unable to obtain the existing socket info using 'getsockname'"}, - { ERR_RTP_UDPV4TRANS_NOTANIPV4SOCKET, "The existing socket specified does not appear to be an IPv4 socket"}, - { ERR_RTP_UDPV4TRANS_SOCKETPORTNOTSET, "The existing socket that was specified does not have its port set yet"}, - { ERR_RTP_UDPV4TRANS_CANTGETSOCKETTYPE, "Can't get the socket type of the specified existing socket"}, - { ERR_RTP_UDPV4TRANS_INVALIDSOCKETTYPE, "The specified existing socket is not an UDP socket"}, - { ERR_RTP_UDPV4TRANS_CANTGETVALIDSOCKET, "Can't get a valid socket when trying to choose a port automatically"}, - { ERR_RTP_UDPV4TRANS_TOOMANYATTEMPTSCHOOSINGSOCKET, "Can't seem to get RTP/RTCP ports automatically, too many attempts"}, - { ERR_RTP_RTPSESSION_CHANGEREQUESTEDBUTNOTIMPLEMENTED, "Flag to change data was requested, but OnChangeRTPOrRTCPData was not reimplemented"}, - { ERR_RTP_SECURESESSION_CONTEXTALREADYINITIALIZED, "The initialization function was already called"}, - { ERR_RTP_SECURESESSION_CANTINITIALIZE_SRTPCONTEXT, "Unable to initialize libsrtp context"}, - { ERR_RTP_SECURESESSION_CANTINITMUTEX, "Unable to initialize a mutex" }, - { ERR_RTP_SECURESESSION_CONTEXTNOTINITIALIZED, "The libsrtp context initilization function must be called before it can be used"}, - { ERR_RTP_SECURESESSION_NOTENOUGHDATATOENCRYPT, "There's not enough RTP or RTCP data to encrypt"}, - { ERR_RTP_SECURESESSION_CANTENCRYPTRTPDATA, "Unable to encrypt RTP data"}, - { ERR_RTP_SECURESESSION_CANTENCRYPTRTCPDATA, "Unable to encrypt RTCP data"}, - { ERR_RTP_SECURESESSION_NOTENOUGHDATATODECRYPT, "There's not enough RTP or RTCP data to decrypt"}, - { ERR_RTP_SECURESESSION_CANTDECRYPTRTPDATA, "Unable to decrypt RTP data"}, - { ERR_RTP_SECURESESSION_CANTDECRYPTRTCPDATA, "Unable to decrypt RTCP data"}, - { ERR_RTP_ABORTDESC_ALREADYINIT, "The RTPAbortDescriptors instance is already initialized" }, - { ERR_RTP_ABORTDESC_NOTINIT, "The RTPAbortDescriptors instance is not yet initialized" }, - { ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS, "Unable to create two connected TCP sockets for the abort descriptors" }, - { ERR_RTP_ABORTDESC_CANTCREATEPIPE, "Unable to create a pipe for the abort descriptors" }, - { ERR_RTP_SESSION_THREADSAFETYCONFLICT, "For the background poll thread to be used, thread safety must also be set" }, - { ERR_RTP_SELECT_ERRORINSELECT, "Error in the call to 'select'" }, - { ERR_RTP_SELECT_SOCKETDESCRIPTORTOOLARGE, "A socket descriptor value is too large for a call to 'select' (exceeds FD_SETSIZE)" }, - { ERR_RTP_SELECT_ERRORINPOLL, "Error in the call to 'poll' or 'WSAPoll'" }, - { ERR_RTP_TCPTRANS_NOTINIT, "The TCP transmitter is not yet initialized" }, - { ERR_RTP_TCPTRANS_ALREADYINIT, "The TCP transmitter is already initialized" }, - { ERR_RTP_TCPTRANS_NOTCREATED, "The TCP transmitter is not yet created" }, - { ERR_RTP_TCPTRANS_ALREADYCREATED, "The TCP transmitter is already created" }, - { ERR_RTP_TCPTRANS_ILLEGALPARAMETERS, "The parameters for the TCP transmitter are invalid" }, - { ERR_RTP_TCPTRANS_CANTINITMUTEX, "Unable to initialize a mutex during the initialization of the TCP transmitter" }, - { ERR_RTP_TCPTRANS_ALREADYWAITING, "The TCP transmitter is already waiting for data" }, - { ERR_RTP_TCPTRANS_INVALIDADDRESSTYPE, "The address specified is not a valid address for the TCP transmitter" }, - { ERR_RTP_TCPTRANS_NOSOCKETSPECIFIED, "No socket was specified in the address used for the TCP transmitter" }, - { ERR_RTP_TCPTRANS_NOMULTICASTSUPPORT, "The TCP transmitter does not support multicasting" }, - { ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED, "The TCP transmitter does not support receive modes other than 'accept all'" }, - { ERR_RTP_TCPTRANS_SPECIFIEDSIZETOOBIG, "The maximum packet size for the TCP transmitter is limited to 64KB" }, - { ERR_RTP_TCPTRANS_NOTWAITING, "The TCP transmitter is not waiting for data" }, - { ERR_RTP_TCPTRANS_SOCKETALREADYINDESTINATIONS, "The specified destination address (socket) was already added to the destination list of the TCP transmitter" }, - { ERR_RTP_TCPTRANS_SOCKETNOTFOUNDINDESTINATIONS, "The specified destination address (socket) was not found in the list of destinations of the TCP transmitter" }, - { ERR_RTP_TCPTRANS_ERRORINSEND, "An error occurred in the TCP transmitter while sending a packet" }, - { ERR_RTP_TCPTRANS_ERRORINRECV, "An error occurred in the TCP transmitter while receiving a packet" }, - { 0,0 } -}; +{ ERR_RTP_OUTOFMEM, "Out of memory" }, +{ ERR_RTP_NOTHREADSUPPORT, "No JThread support was compiled in" }, +{ ERR_RTP_COLLISIONLIST_BADADDRESS, "Passed invalid address (null) to collision list" }, +{ ERR_RTP_HASHTABLE_ELEMENTALREADYEXISTS, "Element already exists in hash table" }, +{ ERR_RTP_HASHTABLE_ELEMENTNOTFOUND, "Element not found in hash table" }, +{ ERR_RTP_HASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX, "Function returned an illegal hash index" }, +{ ERR_RTP_HASHTABLE_NOCURRENTELEMENT, "No current element selected in hash table" }, +{ ERR_RTP_KEYHASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX, "Function returned an illegal hash index" }, +{ ERR_RTP_KEYHASHTABLE_KEYALREADYEXISTS, "Key value already exists in key hash table" }, +{ ERR_RTP_KEYHASHTABLE_KEYNOTFOUND, "Key value not found in key hash table" }, +{ ERR_RTP_KEYHASHTABLE_NOCURRENTELEMENT, "No current element selected in key hash table" }, +{ ERR_RTP_PACKBUILD_ALREADYINIT, "RTP packet builder is already initialized" }, +{ ERR_RTP_PACKBUILD_CSRCALREADYINLIST, "The specified CSRC is already in the RTP packet builder's CSRC list" }, +{ ERR_RTP_PACKBUILD_CSRCLISTFULL, "The RTP packet builder's CSRC list already contains 15 entries" }, +{ ERR_RTP_PACKBUILD_CSRCNOTINLIST, "The specified CSRC was not found in the RTP packet builder's CSRC list" }, +{ ERR_RTP_PACKBUILD_DEFAULTMARKNOTSET, "The RTP packet builder's default mark flag is not set" }, +{ ERR_RTP_PACKBUILD_DEFAULTPAYLOADTYPENOTSET, "The RTP packet builder's default payload type is not set" }, +{ ERR_RTP_PACKBUILD_DEFAULTTSINCNOTSET, "The RTP packet builder's default timestamp increment is not set" }, +{ ERR_RTP_PACKBUILD_INVALIDMAXPACKETSIZE, "The specified maximum packet size for the RTP packet builder is invalid" }, +{ ERR_RTP_PACKBUILD_NOTINIT, "The RTP packet builder is not initialized" }, +{ ERR_RTP_PACKET_BADPAYLOADTYPE, "Invalid payload type" }, +{ ERR_RTP_PACKET_DATAEXCEEDSMAXSIZE, "Tried to create an RTP packet which whould exceed the specified maximum packet size" }, +{ ERR_RTP_PACKET_EXTERNALBUFFERNULL, "Illegal value (null) passed as external buffer for the RTP packet" }, +{ ERR_RTP_PACKET_ILLEGALBUFFERSIZE, "Illegal buffer size specified for the RTP packet" }, +{ ERR_RTP_PACKET_INVALIDPACKET, "Invalid RTP packet format" }, +{ ERR_RTP_PACKET_TOOMANYCSRCS, "More than 15 CSRCs specified for the RTP packet" }, +{ ERR_RTP_POLLTHREAD_ALREADYRUNNING, "Poll thread is already running" }, +{ ERR_RTP_POLLTHREAD_CANTINITMUTEX, "Can't initialize a mutex for the poll thread" }, +{ ERR_RTP_POLLTHREAD_CANTSTARTTHREAD, "Can't start the poll thread" }, +{ ERR_RTP_RTCPCOMPOUND_INVALIDPACKET, "Invalid RTCP compound packet format" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILDING, "Already building this RTCP compound packet" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYBUILT, "This RTCP compound packet is already built" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYGOTREPORT, "There's already a SR or RR in this RTCP compound packet" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_APPDATALENTOOBIG, "The specified APP data length for the RTCP compound packet is too big" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_BUFFERSIZETOOSMALL, "The specified buffer size for the RTCP comound packet is too small" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_ILLEGALAPPDATALENGTH, "The APP data length must be a multiple of four" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_ILLEGALSUBTYPE, "The APP packet subtype must be smaller than 32" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_INVALIDITEMTYPE, "Invalid SDES item type specified for the RTCP compound packet" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_MAXPACKETSIZETOOSMALL, "The specified maximum packet size for the RTCP compound packet is too small" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_NOCURRENTSOURCE, "Tried to add an SDES item to the RTCP compound packet when no SSRC was present" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_NOREPORTPRESENT, "An RTCP compound packet must contain a SR or RR" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING, "The RTCP compound packet builder is not initialized" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT, "Adding this data would exceed the specified maximum RTCP compound packet size" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_REPORTNOTSTARTED, "Tried to add a report block to the RTCP compound packet when no SR or RR was started" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_TOOMANYSSRCS, "Only 31 SSRCs will fit into a BYE packet for the RTCP compound packet" }, +{ ERR_RTP_RTCPCOMPPACKBUILDER_TOTALITEMLENGTHTOOBIG, "The total data for the SDES PRIV item exceeds the maximum size (255 bytes) of an SDES item" }, +{ ERR_RTP_RTCPPACKETBUILDER_ALREADYINIT, "The RTCP packet builder is already initialized" }, +{ ERR_RTP_RTCPPACKETBUILDER_ILLEGALMAXPACKSIZE, "The specified maximum packet size for the RTCP packet builder is too small" }, +{ ERR_RTP_RTCPPACKETBUILDER_ILLEGALTIMESTAMPUNIT, "Speficied an illegal timestamp unit for the the RTCP packet builder" }, +{ ERR_RTP_RTCPPACKETBUILDER_NOTINIT, "The RTCP packet builder was not initialized" }, +{ ERR_RTP_RTCPPACKETBUILDER_PACKETFILLEDTOOSOON, "The RTCP compound packet filled sooner than expected" }, +{ ERR_RTP_SCHEDPARAMS_BADFRACTION, "Illegal sender bandwidth fraction specified" }, +{ ERR_RTP_SCHEDPARAMS_BADMINIMUMINTERVAL, "The minimum RTCP interval specified for the scheduler is too small" }, +{ ERR_RTP_SCHEDPARAMS_INVALIDBANDWIDTH, "Invalid RTCP bandwidth specified for the RTCP scheduler" }, +{ ERR_RTP_SDES_LENGTHTOOBIG, "Specified size for the SDES item exceeds 255 bytes" }, +{ ERR_RTP_SDES_PREFIXNOTFOUND, "The specified SDES PRIV prefix was not found" }, +{ ERR_RTP_SESSION_ALREADYCREATED, "The session is already created" }, +{ ERR_RTP_SESSION_CANTGETLOGINNAME, "Can't retrieve login name" }, +{ ERR_RTP_SESSION_CANTINITMUTEX, "A mutex for the RTP session couldn't be initialized" }, +{ ERR_RTP_SESSION_MAXPACKETSIZETOOSMALL, "The maximum packet size specified for the RTP session is too small" }, +{ ERR_RTP_SESSION_NOTCREATED, "The RTP session was not created" }, +{ ERR_RTP_SESSION_UNSUPPORTEDTRANSMISSIONPROTOCOL, "The requested transmission protocol for the RTP session is not supported" }, +{ ERR_RTP_SESSION_USINGPOLLTHREAD, "This function is not available when using the RTP poll thread feature" }, +{ ERR_RTP_SESSION_USERDEFINEDTRANSMITTERNULL, "A user-defined transmitter was requested but the supplied transmitter component is NULL" }, +{ ERR_RTP_SOURCES_ALREADYHAVEOWNSSRC, "Only one source can be marked as own SSRC in the source table" }, +{ ERR_RTP_SOURCES_DONTHAVEOWNSSRC, "No source was marked as own SSRC in the source table" }, +{ ERR_RTP_SOURCES_ILLEGALSDESTYPE, "Illegal SDES type specified for processing into the source table" }, +{ ERR_RTP_SOURCES_SSRCEXISTS, "Can't create own SSRC because this SSRC identifier is already in the source table" }, +{ ERR_RTP_UDPV4TRANS_ALREADYCREATED, "The transmitter was already created" }, +{ ERR_RTP_UDPV4TRANS_ALREADYINIT, "The transmitter was already initialize" }, +{ ERR_RTP_UDPV4TRANS_ALREADYWAITING, "The transmitter is already waiting for incoming data" }, +{ ERR_RTP_UDPV4TRANS_CANTBINDRTCPSOCKET, "The 'bind' call for the RTCP socket failed" }, +{ ERR_RTP_UDPV4TRANS_CANTBINDRTPSOCKET, "The 'bind' call for the RTP socket failed" }, +{ ERR_RTP_UDPV4TRANS_CANTCREATESOCKET, "Couldn't create the RTP or RTCP socket" }, +{ ERR_RTP_UDPV4TRANS_CANTINITMUTEX, "Failed to initialize a mutex used by the transmitter" }, +{ ERR_RTP_UDPV4TRANS_CANTSETRTCPRECEIVEBUF, "Couldn't set the receive buffer size for the RTCP socket" }, +{ ERR_RTP_UDPV4TRANS_CANTSETRTCPTRANSMITBUF, "Couldn't set the transmission buffer size for the RTCP socket" }, +{ ERR_RTP_UDPV4TRANS_CANTSETRTPRECEIVEBUF, "Couldn't set the receive buffer size for the RTP socket" }, +{ ERR_RTP_UDPV4TRANS_CANTSETRTPTRANSMITBUF, "Couldn't set the transmission buffer size for the RTP socket" }, +{ ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP, "Unable to join the specified multicast group" }, +{ ERR_RTP_UDPV4TRANS_DIFFERENTRECEIVEMODE, "The function called doens't match the current receive mode" }, +{ ERR_RTP_UDPV4TRANS_ILLEGALPARAMETERS, "Illegal parameters type passed to the transmitter" }, +{ ERR_RTP_UDPV4TRANS_INVALIDADDRESSTYPE, "Specified address type isn't compatible with this transmitter" }, +{ ERR_RTP_UDPV4TRANS_NOLOCALIPS, "Couldn't determine the local host name since the local IP list is empty" }, +{ ERR_RTP_UDPV4TRANS_NOMULTICASTSUPPORT, "Multicast support is not available" }, +{ ERR_RTP_UDPV4TRANS_NOSUCHENTRY, "Specified entry could not be found" }, +{ ERR_RTP_UDPV4TRANS_NOTAMULTICASTADDRESS, "The specified address is not a multicast address" }, +{ ERR_RTP_UDPV4TRANS_NOTCREATED, "The 'Create' call for this transmitter has not been called" }, +{ ERR_RTP_UDPV4TRANS_NOTINIT, "The 'Init' call for this transmitter has not been called" }, +{ ERR_RTP_UDPV4TRANS_NOTWAITING, "The transmitter is not waiting for incoming data" }, +{ ERR_RTP_UDPV4TRANS_PORTBASENOTEVEN, "The specified port base is not an even number" }, +{ ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG, "The maximum packet size is too big for this transmitter" }, +{ ERR_RTP_UDPV6TRANS_ALREADYCREATED, "The transmitter was already created" }, +{ ERR_RTP_UDPV6TRANS_ALREADYINIT, "The transmitter was already initialize" }, +{ ERR_RTP_UDPV6TRANS_ALREADYWAITING, "The transmitter is already waiting for incoming data" }, +{ ERR_RTP_UDPV6TRANS_CANTBINDRTCPSOCKET, "The 'bind' call for the RTCP socket failed" }, +{ ERR_RTP_UDPV6TRANS_CANTBINDRTPSOCKET, "The 'bind' call for the RTP socket failed" }, +{ ERR_RTP_UDPV6TRANS_CANTCREATESOCKET, "Couldn't create the RTP or RTCP socket" }, +{ ERR_RTP_UDPV6TRANS_CANTINITMUTEX, "Failed to initialize a mutex used by the transmitter" }, +{ ERR_RTP_UDPV6TRANS_CANTSETRTCPRECEIVEBUF, "Couldn't set the receive buffer size for the RTCP socket" }, +{ ERR_RTP_UDPV6TRANS_CANTSETRTCPTRANSMITBUF, "Couldn't set the transmission buffer size for the RTCP socket" }, +{ ERR_RTP_UDPV6TRANS_CANTSETRTPRECEIVEBUF, "Couldn't set the receive buffer size for the RTP socket" }, +{ ERR_RTP_UDPV6TRANS_CANTSETRTPTRANSMITBUF, "Couldn't set the transmission buffer size for the RTP socket" }, +{ ERR_RTP_UDPV6TRANS_COULDNTJOINMULTICASTGROUP, "Unable to join the specified multicast group" }, +{ ERR_RTP_UDPV6TRANS_DIFFERENTRECEIVEMODE, "The function called doens't match the current receive mode" }, +{ ERR_RTP_UDPV6TRANS_ILLEGALPARAMETERS, "Illegal parameters type passed to the transmitter" }, +{ ERR_RTP_UDPV6TRANS_INVALIDADDRESSTYPE, "Specified address type isn't compatible with this transmitter" }, +{ ERR_RTP_UDPV6TRANS_NOLOCALIPS, "Couldn't determine the local host name since the local IP list is empty" }, +{ ERR_RTP_UDPV6TRANS_NOMULTICASTSUPPORT, "Multicast support is not available" }, +{ ERR_RTP_UDPV6TRANS_NOSUCHENTRY, "Specified entry could not be found" }, +{ ERR_RTP_UDPV6TRANS_NOTAMULTICASTADDRESS, "The specified address is not a multicast address" }, +{ ERR_RTP_UDPV6TRANS_NOTCREATED, "The 'Create' call for this transmitter has not been called" }, +{ ERR_RTP_UDPV6TRANS_NOTINIT, "The 'Init' call for this transmitter has not been called" }, +{ ERR_RTP_UDPV6TRANS_NOTWAITING, "The transmitter is not waiting for incoming data" }, +{ ERR_RTP_UDPV6TRANS_PORTBASENOTEVEN, "The specified port base is not an even number" }, +{ ERR_RTP_UDPV6TRANS_SPECIFIEDSIZETOOBIG, "The maximum packet size is too big for this transmitter" }, +{ ERR_RTP_TRANS_BUFFERLENGTHTOOSMALL, "The hostname is larger than the specified buffer size" }, +{ ERR_RTP_SDES_MAXPRIVITEMS, "The maximum number of SDES private item prefixes was reached" }, +{ ERR_RTP_INTERNALSOURCEDATA_INVALIDPROBATIONTYPE, "An invalid probation type was specified" }, +{ ERR_RTP_FAKETRANS_ALREADYCREATED, "The transmitter was already created" }, +{ ERR_RTP_FAKETRANS_ALREADYINIT, "The transmitter was already initialize" }, +{ ERR_RTP_FAKETRANS_CANTINITMUTEX, "Failed to initialize a mutex used by the transmitter" }, +{ ERR_RTP_FAKETRANS_COULDNTJOINMULTICASTGROUP, "Unable to join the specified multicast group" }, +{ ERR_RTP_FAKETRANS_DIFFERENTRECEIVEMODE, "The function called doens't match the current receive mode" }, +{ ERR_RTP_FAKETRANS_ILLEGALPARAMETERS, "Illegal parameters type passed to the transmitter" }, +{ ERR_RTP_FAKETRANS_INVALIDADDRESSTYPE, "Specified address type isn't compatible with this transmitter" }, +{ ERR_RTP_FAKETRANS_NOLOCALIPS, "Couldn't determine the local host name since the local IP list is empty" }, +{ ERR_RTP_FAKETRANS_NOMULTICASTSUPPORT, "Multicast support is not available" }, +{ ERR_RTP_FAKETRANS_NOSUCHENTRY, "Specified entry could not be found" }, +{ ERR_RTP_FAKETRANS_NOTAMULTICASTADDRESS, "The specified address is not a multicast address" }, +{ ERR_RTP_FAKETRANS_NOTCREATED, "The 'Create' call for this transmitter has not been called" }, +{ ERR_RTP_FAKETRANS_NOTINIT, "The 'Init' call for this transmitter has not been called" }, +{ ERR_RTP_FAKETRANS_PORTBASENOTEVEN, "The specified port base is not an even number" }, +{ ERR_RTP_FAKETRANS_SPECIFIEDSIZETOOBIG, "The maximum packet size is too big for this transmitter" }, +{ ERR_RTP_FAKETRANS_WAITNOTIMPLEMENTED, "The WaitForIncomingData is not implemented in the Gst transmitter" }, +{ ERR_RTP_RTPRANDOMURANDOM_CANTOPEN, "Unable to open /dev/urandom for reading" }, +{ ERR_RTP_RTPRANDOMURANDOM_ALREADYOPEN, "The device /dev/urandom was already opened" }, +{ ERR_RTP_RTPRANDOMRANDS_NOTSUPPORTED, "The rand_s call is not supported on this platform" }, +{ ERR_RTP_EXTERNALTRANS_ALREADYCREATED, "The external transmission component was already created" }, +{ ERR_RTP_EXTERNALTRANS_ALREADYINIT, "The external transmission component was already initialized" }, +{ ERR_RTP_EXTERNALTRANS_ALREADYWAITING, "The external transmission component is already waiting for incoming data" }, +{ ERR_RTP_EXTERNALTRANS_BADRECEIVEMODE, "The external transmission component only supports accepting all incoming packets" }, +{ ERR_RTP_EXTERNALTRANS_CANTINITMUTEX, "The external transmitter was unable to initialize a required mutex" }, +{ ERR_RTP_EXTERNALTRANS_ILLEGALPARAMETERS, "Only parameters of type RTPExternalTransmissionParams can be passed to the external transmission component" }, +{ ERR_RTP_EXTERNALTRANS_NOACCEPTLIST, "The external transmitter does not have an accept list" }, +{ ERR_RTP_EXTERNALTRANS_NODESTINATIONSSUPPORTED, "The external transmitter does not have a destination list" }, +{ ERR_RTP_EXTERNALTRANS_NOIGNORELIST, "The external transmitter does not have an ignore list" }, +{ ERR_RTP_EXTERNALTRANS_NOMULTICASTSUPPORT, "The external transmitter does not support the multicast functions" }, +{ ERR_RTP_EXTERNALTRANS_NOSENDER, "No sender has been set for this external transmitter" }, +{ ERR_RTP_EXTERNALTRANS_NOTCREATED, "The external transmitter has not been created yet" }, +{ ERR_RTP_EXTERNALTRANS_NOTINIT, "The external transmitter has not been initialized yet" }, +{ ERR_RTP_EXTERNALTRANS_NOTWAITING, "The external transmitter is not currently waiting for incoming data" }, +{ ERR_RTP_EXTERNALTRANS_SENDERROR, "The external transmitter was unable to actually send the data" }, +{ ERR_RTP_EXTERNALTRANS_SPECIFIEDSIZETOOBIG, "The specified data size exceeds the maximum amount that has been set" }, +{ ERR_RTP_UDPV4TRANS_CANTGETSOCKETPORT, "Unable to obtain the existing socket info using 'getsockname'" }, +{ ERR_RTP_UDPV4TRANS_NOTANIPV4SOCKET, "The existing socket specified does not appear to be an IPv4 socket" }, +{ ERR_RTP_UDPV4TRANS_SOCKETPORTNOTSET, "The existing socket that was specified does not have its port set yet" }, +{ ERR_RTP_UDPV4TRANS_CANTGETSOCKETTYPE, "Can't get the socket type of the specified existing socket" }, +{ ERR_RTP_UDPV4TRANS_INVALIDSOCKETTYPE, "The specified existing socket is not an UDP socket" }, +{ ERR_RTP_UDPV4TRANS_CANTGETVALIDSOCKET, "Can't get a valid socket when trying to choose a port automatically" }, +{ ERR_RTP_UDPV4TRANS_TOOMANYATTEMPTSCHOOSINGSOCKET, "Can't seem to get RTP/RTCP ports automatically, too many attempts" }, +{ ERR_RTP_RTPSESSION_CHANGEREQUESTEDBUTNOTIMPLEMENTED, "Flag to change data was requested, but OnChangeRTPOrRTCPData was not reimplemented" }, +{ ERR_RTP_SECURESESSION_CONTEXTALREADYINITIALIZED, "The initialization function was already called" }, +{ ERR_RTP_SECURESESSION_CANTINITIALIZE_SRTPCONTEXT, "Unable to initialize libsrtp context" }, +{ ERR_RTP_SECURESESSION_CANTINITMUTEX, "Unable to initialize a mutex" }, +{ ERR_RTP_SECURESESSION_CONTEXTNOTINITIALIZED, "The libsrtp context initilization function must be called before it can be used" }, +{ ERR_RTP_SECURESESSION_NOTENOUGHDATATOENCRYPT, "There's not enough RTP or RTCP data to encrypt" }, +{ ERR_RTP_SECURESESSION_CANTENCRYPTRTPDATA, "Unable to encrypt RTP data" }, +{ ERR_RTP_SECURESESSION_CANTENCRYPTRTCPDATA, "Unable to encrypt RTCP data" }, +{ ERR_RTP_SECURESESSION_NOTENOUGHDATATODECRYPT, "There's not enough RTP or RTCP data to decrypt" }, +{ ERR_RTP_SECURESESSION_CANTDECRYPTRTPDATA, "Unable to decrypt RTP data" }, +{ ERR_RTP_SECURESESSION_CANTDECRYPTRTCPDATA, "Unable to decrypt RTCP data" }, +{ ERR_RTP_ABORTDESC_ALREADYINIT, "The RTPAbortDescriptors instance is already initialized" }, +{ ERR_RTP_ABORTDESC_NOTINIT, "The RTPAbortDescriptors instance is not yet initialized" }, +{ ERR_RTP_ABORTDESC_CANTCREATEABORTDESCRIPTORS, "Unable to create two connected TCP sockets for the abort descriptors" }, +{ ERR_RTP_ABORTDESC_CANTCREATEPIPE, "Unable to create a pipe for the abort descriptors" }, +{ ERR_RTP_SESSION_THREADSAFETYCONFLICT, "For the background poll thread to be used, thread safety must also be set" }, +{ ERR_RTP_SELECT_ERRORINSELECT, "Error in the call to 'select'" }, +{ ERR_RTP_SELECT_SOCKETDESCRIPTORTOOLARGE, "A socket descriptor value is too large for a call to 'select' (exceeds FD_SETSIZE)" }, +{ ERR_RTP_SELECT_ERRORINPOLL, "Error in the call to 'poll' or 'WSAPoll'" }, +{ ERR_RTP_TCPTRANS_NOTINIT, "The TCP transmitter is not yet initialized" }, +{ ERR_RTP_TCPTRANS_ALREADYINIT, "The TCP transmitter is already initialized" }, +{ ERR_RTP_TCPTRANS_NOTCREATED, "The TCP transmitter is not yet created" }, +{ ERR_RTP_TCPTRANS_ALREADYCREATED, "The TCP transmitter is already created" }, +{ ERR_RTP_TCPTRANS_ILLEGALPARAMETERS, "The parameters for the TCP transmitter are invalid" }, +{ ERR_RTP_TCPTRANS_CANTINITMUTEX, "Unable to initialize a mutex during the initialization of the TCP transmitter" }, +{ ERR_RTP_TCPTRANS_ALREADYWAITING, "The TCP transmitter is already waiting for data" }, +{ ERR_RTP_TCPTRANS_INVALIDADDRESSTYPE, "The address specified is not a valid address for the TCP transmitter" }, +{ ERR_RTP_TCPTRANS_NOSOCKETSPECIFIED, "No socket was specified in the address used for the TCP transmitter" }, +{ ERR_RTP_TCPTRANS_NOMULTICASTSUPPORT, "The TCP transmitter does not support multicasting" }, +{ ERR_RTP_TCPTRANS_RECEIVEMODENOTSUPPORTED, "The TCP transmitter does not support receive modes other than 'accept all'" }, +{ ERR_RTP_TCPTRANS_SPECIFIEDSIZETOOBIG, "The maximum packet size for the TCP transmitter is limited to 64KB" }, +{ ERR_RTP_TCPTRANS_NOTWAITING, "The TCP transmitter is not waiting for data" }, +{ ERR_RTP_TCPTRANS_SOCKETALREADYINDESTINATIONS, "The specified destination address (socket) was already added to the destination list of the TCP transmitter" }, +{ ERR_RTP_TCPTRANS_SOCKETNOTFOUNDINDESTINATIONS, "The specified destination address (socket) was not found in the list of destinations of the TCP transmitter" }, +{ ERR_RTP_TCPTRANS_ERRORINSEND, "An error occurred in the TCP transmitter while sending a packet" }, +{ ERR_RTP_TCPTRANS_ERRORINRECV, "An error occurred in the TCP transmitter while receiving a packet" }, +{ 0, 0 } }; std::string RTPGetErrorString(int errcode) { - int i; - - if (errcode >= 0) - return std::string("No error"); - - i = 0; - while (ErrorDescriptions[i].code != 0) - { - if (ErrorDescriptions[i].code == errcode) - return std::string(ErrorDescriptions[i].description); - i++; - } + int i; - char str[16]; - - RTP_SNPRINTF(str,16,"(%d)",errcode); - - return std::string("Unknown error code") + std::string(str); + if (errcode >= 0) + return std::string("No error"); + + i = 0; + while (ErrorDescriptions[i].code != 0) + { + if (ErrorDescriptions[i].code == errcode) + return std::string(ErrorDescriptions[i].description); + i++; + } + + char str[16]; + + RTP_SNPRINTF(str, 16, "(%d)", errcode); + + return std::string("Unknown error code") + std::string(str); } } // end namespace diff --git a/qrtplib/rtperrors.h b/qrtplib/rtperrors.h index 3b9763ee2..ec12c7955 100644 --- a/qrtplib/rtperrors.h +++ b/qrtplib/rtperrors.h @@ -1,36 +1,34 @@ /* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtperrors.h @@ -40,7 +38,7 @@ #define RTPERRORS_H -//#include "rtpconfig.h" +#include "rtpconfig.h" #include namespace qrtplib diff --git a/qrtplib/rtpinternalsourcedata.cpp b/qrtplib/rtpinternalsourcedata.cpp new file mode 100644 index 000000000..5ae3a5402 --- /dev/null +++ b/qrtplib/rtpinternalsourcedata.cpp @@ -0,0 +1,255 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +#include "rtpinternalsourcedata.h" +#include "rtppacket.h" +#include + +#define RTPINTERNALSOURCEDATA_MAXPROBATIONPACKETS 32 + +namespace qrtplib +{ + +RTPInternalSourceData::RTPInternalSourceData(uint32_t ssrc) : + RTPSourceData(ssrc) +{ +} + +RTPInternalSourceData::~RTPInternalSourceData() +{ +} + +// The following function should delete rtppack if necessary +int RTPInternalSourceData::ProcessRTPPacket(RTPPacket *rtppack, const RTPTime &receivetime, bool *stored, RTPSources *sources) +{ + bool accept; + double tsunit; + + *stored = false; + + if (timestampunit < 0) + tsunit = INF_GetEstimatedTimestampUnit(); + else + tsunit = timestampunit; + + stats.ProcessPacket(rtppack, receivetime, tsunit, ownssrc, &accept); + + if (!accept) + return 0; + validated = true; + + if (validated && !ownssrc) // for own ssrc these variables depend on the outgoing packets, not on the incoming + issender = true; + + bool isonprobation = !validated; + bool ispackethandled = false; + + sources->OnValidatedRTPPacket(this, rtppack, isonprobation, &ispackethandled); + if (ispackethandled) // Packet is already handled in the callback, no need to store it in the list + { + // Set 'stored' to true to avoid the packet being deallocated + *stored = true; + return 0; + } + + // Now, we can place the packet in the queue + + if (packetlist.empty()) + { + *stored = true; + packetlist.push_back(rtppack); + return 0; + } + + if (!validated) // still on probation + { + // Make sure that we don't buffer too much packets to avoid wasting memory + // on a bad source. Delete the packet in the queue with the lowest sequence + // number. + if (packetlist.size() == RTPINTERNALSOURCEDATA_MAXPROBATIONPACKETS) + { + RTPPacket *p = *(packetlist.begin()); + packetlist.pop_front(); + delete p; + } + } + + // find the right position to insert the packet + + std::list::iterator it, start; + bool done = false; + uint32_t newseqnr = rtppack->GetExtendedSequenceNumber(); + + it = packetlist.end(); + --it; + start = packetlist.begin(); + + while (!done) + { + RTPPacket *p; + uint32_t seqnr; + + p = *it; + seqnr = p->GetExtendedSequenceNumber(); + if (seqnr > newseqnr) + { + if (it != start) + --it; + else // we're at the start of the list + { + *stored = true; + done = true; + packetlist.push_front(rtppack); + } + } + else if (seqnr < newseqnr) // insert after this packet + { + ++it; + packetlist.insert(it, rtppack); + done = true; + *stored = true; + } + else // they're equal !! Drop packet + { + done = true; + } + } + + return 0; +} + +int RTPInternalSourceData::ProcessSDESItem(uint8_t sdesid, const uint8_t *data, std::size_t itemlen, const RTPTime &receivetime, bool *cnamecollis) +{ + *cnamecollis = false; + + stats.SetLastMessageTime(receivetime); + + switch (sdesid) + { + case RTCP_SDES_ID_CNAME: + { + std::size_t curlen; + uint8_t *oldcname; + + // NOTE: we're going to make sure that the CNAME is only set once. + oldcname = SDESinf.GetCNAME(&curlen); + if (curlen == 0) + { + // if CNAME is set, the source is validated + SDESinf.SetCNAME(data, itemlen); + validated = true; + } + else // check if this CNAME is equal to the one that is already present + { + if (curlen != itemlen) + *cnamecollis = true; + else + { + if (memcmp(data, oldcname, itemlen) != 0) + *cnamecollis = true; + } + } + } + break; + case RTCP_SDES_ID_NAME: + { + std::size_t oldlen; + + SDESinf.GetName(&oldlen); + if (oldlen == 0) // Name not set + return SDESinf.SetName(data, itemlen); + } + break; + case RTCP_SDES_ID_EMAIL: + { + std::size_t oldlen; + + SDESinf.GetEMail(&oldlen); + if (oldlen == 0) + return SDESinf.SetEMail(data, itemlen); + } + break; + case RTCP_SDES_ID_PHONE: + return SDESinf.SetPhone(data, itemlen); + case RTCP_SDES_ID_LOCATION: + return SDESinf.SetLocation(data, itemlen); + case RTCP_SDES_ID_TOOL: + { + std::size_t oldlen; + + SDESinf.GetTool(&oldlen); + if (oldlen == 0) + return SDESinf.SetTool(data, itemlen); + } + break; + case RTCP_SDES_ID_NOTE: + stats.SetLastNoteTime(receivetime); + return SDESinf.SetNote(data, itemlen); + } + return 0; +} + +#ifdef RTP_SUPPORT_SDESPRIV + +int RTPInternalSourceData::ProcessPrivateSDESItem(const uint8_t *prefix, std::size_t prefixlen, const uint8_t *value, std::size_t valuelen, const RTPTime &receivetime) +{ + int status; + + stats.SetLastMessageTime(receivetime); + status = SDESinf.SetPrivateValue(prefix, prefixlen, value, valuelen); + if (status == ERR_RTP_SDES_MAXPRIVITEMS) + return 0; // don't stop processing just because the number of items is full + return status; +} + +#endif // RTP_SUPPORT_SDESPRIV + +int RTPInternalSourceData::ProcessBYEPacket(const uint8_t *reason, std::size_t reasonlen, const RTPTime &receivetime) +{ + if (byereason) + { + delete[] byereason; + byereason = 0; + byereasonlen = 0; + } + + byetime = receivetime; + byereason = new uint8_t[reasonlen]; + memcpy(byereason, reason, reasonlen); + byereasonlen = reasonlen; + receivedbye = true; + stats.SetLastMessageTime(receivetime); + return 0; +} + +} // end namespace + diff --git a/qrtplib/rtpinternalsourcedata.h b/qrtplib/rtpinternalsourcedata.h new file mode 100644 index 000000000..42b9e765f --- /dev/null +++ b/qrtplib/rtpinternalsourcedata.h @@ -0,0 +1,165 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +/** + * \file rtpinternalsourcedata.h + */ + +#ifndef RTPINTERNALSOURCEDATA_H + +#define RTPINTERNALSOURCEDATA_H + +#include "rtpconfig.h" +#include "rtpsourcedata.h" +#include "rtpaddress.h" +#include "rtptimeutilities.h" +#include "rtpsources.h" + +#include "export.h" + +namespace qrtplib +{ + +class QRTPLIB_API RTPInternalSourceData: public RTPSourceData +{ +public: + RTPInternalSourceData(uint32_t ssrc); + ~RTPInternalSourceData(); + + int ProcessRTPPacket(RTPPacket *rtppack, const RTPTime &receivetime, bool *stored, RTPSources *sources); + void ProcessSenderInfo(const RTPNTPTime &ntptime, uint32_t rtptime, uint32_t packetcount, uint32_t octetcount, const RTPTime &receivetime) + { + SRprevinf = SRinf; + SRinf.Set(ntptime, rtptime, packetcount, octetcount, receivetime); + stats.SetLastMessageTime(receivetime); + } + void ProcessReportBlock(uint8_t fractionlost, int32_t lostpackets, uint32_t exthighseqnr, uint32_t jitter, uint32_t lsr, uint32_t dlsr, const RTPTime &receivetime) + { + RRprevinf = RRinf; + RRinf.Set(fractionlost, lostpackets, exthighseqnr, jitter, lsr, dlsr, receivetime); + stats.SetLastMessageTime(receivetime); + } + void UpdateMessageTime(const RTPTime &receivetime) + { + stats.SetLastMessageTime(receivetime); + } + int ProcessSDESItem(uint8_t sdesid, const uint8_t *data, std::size_t itemlen, const RTPTime &receivetime, bool *cnamecollis); +#ifdef RTP_SUPPORT_SDESPRIV + int ProcessPrivateSDESItem(const uint8_t *prefix, std::size_t prefixlen, const uint8_t *value, std::size_t valuelen, const RTPTime &receivetime); +#endif // RTP_SUPPORT_SDESPRIV + int ProcessBYEPacket(const uint8_t *reason, std::size_t reasonlen, const RTPTime &receivetime); + + int SetRTPDataAddress(const RTPAddress *a); + int SetRTCPDataAddress(const RTPAddress *a); + + void ClearSenderFlag() + { + issender = false; + } + void SentRTPPacket() + { + if (!ownssrc) + return; + RTPTime t = RTPTime::CurrentTime(); + issender = true; + stats.SetLastRTPPacketTime(t); + stats.SetLastMessageTime(t); + } + void SetOwnSSRC() + { + ownssrc = true; + validated = true; + } + void SetCSRC() + { + validated = true; + iscsrc = true; + } + void ClearNote() + { + SDESinf.SetNote(0, 0); + } + +}; + +inline int RTPInternalSourceData::SetRTPDataAddress(const RTPAddress *a) +{ + if (a == 0) + { + if (rtpaddr) + { + delete rtpaddr; + rtpaddr = 0; + } + } + else + { + RTPAddress *newaddr = a->CreateCopy(); + if (newaddr == 0) + return ERR_RTP_OUTOFMEM; + + if (rtpaddr && a != rtpaddr) + delete rtpaddr; + rtpaddr = newaddr; + } + isrtpaddrset = true; + return 0; +} + +inline int RTPInternalSourceData::SetRTCPDataAddress(const RTPAddress *a) +{ + if (a == 0) + { + if (rtcpaddr) + { + delete rtcpaddr; + rtcpaddr = 0; + } + } + else + { + RTPAddress *newaddr = a->CreateCopy(); + if (newaddr == 0) + return ERR_RTP_OUTOFMEM; + + if (rtcpaddr && a != rtcpaddr) + delete rtcpaddr; + rtcpaddr = newaddr; + } + isrtcpaddrset = true; + return 0; +} + +} // end namespace + +#endif // RTPINTERNALSOURCEDATA_H + diff --git a/qrtplib/rtpinternalutils.h b/qrtplib/rtpinternalutils.h index 503401bf2..bbe0e34b7 100644 --- a/qrtplib/rtpinternalutils.h +++ b/qrtplib/rtpinternalutils.h @@ -1,61 +1,53 @@ /* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - This file is a part of JRTPLIB - Copyright (c) 1999-2011 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2011 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #ifndef RTPINTERNALUTILS_H #define RTPINTERNALUTILS_H -//#include "rtpconfig.h" +#include "rtpconfig.h" #if defined(RTP_HAVE_SNPRINTF_S) - #include - #include - #define RTP_SNPRINTF _snprintf_s +#include +#include +#define RTP_SNPRINTF _snprintf_s #elif defined(RTP_HAVE_SNPRINTF) - #include - #include - #define RTP_SNPRINTF _snprintf +#include +#include +#define RTP_SNPRINTF _snprintf #else - #include - #define RTP_SNPRINTF snprintf -#endif - -#ifdef RTP_HAVE_STRNCPY_S - #define RTP_STRNCPY(dest, src, len) strncpy_s((dest), (len), (src), _TRUNCATE) -#else - #define RTP_STRNCPY(dest, src, len) strncpy((dest), (src), (len)) -#endif // RTP_HAVE_STRNCPY_S +#include +#define RTP_SNPRINTF snprintf +#endif #endif // RTPINTERNALUTILS_H diff --git a/qrtplib/rtpkeyhashtable.h b/qrtplib/rtpkeyhashtable.h new file mode 100644 index 000000000..cfba38d7c --- /dev/null +++ b/qrtplib/rtpkeyhashtable.h @@ -0,0 +1,336 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +/** + * \file rtpkeyhashtable.h + */ + +#ifndef RTPKEYHASHTABLE_H + +#define RTPKEYHASHTABLE_H + +#include "rtpconfig.h" +#include "rtperrors.h" + +namespace qrtplib +{ + +template +class RTPKeyHashTable +{ +public: + RTPKeyHashTable(); + ~RTPKeyHashTable() + { + Clear(); + } + + void GotoFirstElement() + { + curhashelem = firsthashelem; + } + void GotoLastElement() + { + curhashelem = lasthashelem; + } + bool HasCurrentElement() + { + return (curhashelem == 0) ? false : true; + } + int DeleteCurrentElement(); + Element &GetCurrentElement() + { + return curhashelem->GetElement(); + } + Key &GetCurrentKey() + { + return curhashelem->GetKey(); + } + int GotoElement(const Key &k); + bool HasElement(const Key &k); + void GotoNextElement(); + void GotoPreviousElement(); + void Clear(); + + int AddElement(const Key &k, const Element &elem); + int DeleteElement(const Key &k); + +private: + class HashElement + { + public: + HashElement(const Key &k, const Element &e, int index) : + key(k), element(e) + { + hashprev = 0; + hashnext = 0; + listnext = 0; + listprev = 0; + hashindex = index; + } + int GetHashIndex() + { + return hashindex; + } + Key &GetKey() + { + return key; + } + Element &GetElement() + { + return element; + } + + private: + int hashindex; + Key key; + Element element; + public: + HashElement *hashprev, *hashnext; + HashElement *listprev, *listnext; + }; + + HashElement *table[hashsize]; + HashElement *firsthashelem, *lasthashelem; + HashElement *curhashelem; +}; + +template +inline RTPKeyHashTable::RTPKeyHashTable() +{ + for (int i = 0; i < hashsize; i++) + table[i] = 0; + firsthashelem = 0; + lasthashelem = 0; +} + +template +inline int RTPKeyHashTable::DeleteCurrentElement() +{ + if (curhashelem) + { + HashElement *tmp1, *tmp2; + int index; + + // First, relink elements in current hash bucket + + index = curhashelem->GetHashIndex(); + tmp1 = curhashelem->hashprev; + tmp2 = curhashelem->hashnext; + if (tmp1 == 0) // no previous element in hash bucket + { + table[index] = tmp2; + if (tmp2 != 0) + tmp2->hashprev = 0; + } + else // there is a previous element in the hash bucket + { + tmp1->hashnext = tmp2; + if (tmp2 != 0) + tmp2->hashprev = tmp1; + } + + // Relink elements in list + + tmp1 = curhashelem->listprev; + tmp2 = curhashelem->listnext; + if (tmp1 == 0) // curhashelem is first in list + { + firsthashelem = tmp2; + if (tmp2 != 0) + tmp2->listprev = 0; + else + // curhashelem is also last in list + lasthashelem = 0; + } + else + { + tmp1->listnext = tmp2; + if (tmp2 != 0) + tmp2->listprev = tmp1; + else + // curhashelem is last in list + lasthashelem = tmp1; + } + + // finally, with everything being relinked, we can delete curhashelem + delete curhashelem; + curhashelem = tmp2; // Set to next element in list + } + else + return ERR_RTP_KEYHASHTABLE_NOCURRENTELEMENT; + return 0; +} + +template +inline int RTPKeyHashTable::GotoElement(const Key &k) +{ + int index; + bool found; + + index = GetIndex::GetIndex(k); + if (index >= hashsize) + return ERR_RTP_KEYHASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX; + + curhashelem = table[index]; + found = false; + while (!found && curhashelem != 0) + { + if (curhashelem->GetKey() == k) + found = true; + else + curhashelem = curhashelem->hashnext; + } + if (!found) + return ERR_RTP_KEYHASHTABLE_KEYNOTFOUND; + return 0; +} + +template +inline bool RTPKeyHashTable::HasElement(const Key &k) +{ + int index; + bool found; + HashElement *tmp; + + index = GetIndex::GetIndex(k); + if (index >= hashsize) + return false; + + tmp = table[index]; + found = false; + while (!found && tmp != 0) + { + if (tmp->GetKey() == k) + found = true; + else + tmp = tmp->hashnext; + } + return found; +} + +template +inline void RTPKeyHashTable::GotoNextElement() +{ + if (curhashelem) + curhashelem = curhashelem->listnext; +} + +template +inline void RTPKeyHashTable::GotoPreviousElement() +{ + if (curhashelem) + curhashelem = curhashelem->listprev; +} + +template +inline void RTPKeyHashTable::Clear() +{ + HashElement *tmp1, *tmp2; + + for (int i = 0; i < hashsize; i++) + table[i] = 0; + + tmp1 = firsthashelem; + while (tmp1 != 0) + { + tmp2 = tmp1->listnext; + delete tmp1; + tmp1 = tmp2; + } + firsthashelem = 0; + lasthashelem = 0; +} + +template +inline int RTPKeyHashTable::AddElement(const Key &k, const Element &elem) +{ + int index; + bool found; + HashElement *e, *newelem; + + index = GetIndex::GetIndex(k); + if (index >= hashsize) + return ERR_RTP_KEYHASHTABLE_FUNCTIONRETURNEDINVALIDHASHINDEX; + + e = table[index]; + found = false; + while (!found && e != 0) + { + if (e->GetKey() == k) + found = true; + else + e = e->hashnext; + } + if (found) + return ERR_RTP_KEYHASHTABLE_KEYALREADYEXISTS; + + // Okay, the key doesn't exist, so we can add the new element in the hash table + + newelem = new HashElement(k, elem, index); + + e = table[index]; + table[index] = newelem; + newelem->hashnext = e; + if (e != 0) + e->hashprev = newelem; + + // Now, we still got to add it to the linked list + + if (firsthashelem == 0) + { + firsthashelem = newelem; + lasthashelem = newelem; + } + else // there already are some elements in the list + { + lasthashelem->listnext = newelem; + newelem->listprev = lasthashelem; + lasthashelem = newelem; + } + return 0; +} + +template +inline int RTPKeyHashTable::DeleteElement(const Key &k) +{ + int status; + + status = GotoElement(k); + if (status < 0) + return status; + return DeleteCurrentElement(); +} + +} // end namespace + +#endif // RTPKEYHASHTABLE_H diff --git a/qrtplib/rtplibraryversioninternal.h b/qrtplib/rtplibraryversioninternal.h new file mode 100644 index 000000000..76eab4dc3 --- /dev/null +++ b/qrtplib/rtplibraryversioninternal.h @@ -0,0 +1,46 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +/** + * \file rtplibraryversioninternal.h + */ + +#ifndef RTPLIBRARYVERSIONINTERNAL_H + +#define RTPLIBRARYVERSIONINTERNAL_H + +#define JRTPLIB_VERSION_MAJOR 3 +#define JRTPLIB_VERSION_MINOR 11 +#define JRTPLIB_VERSION_DEBUG 1 + +#endif // RTPLIBRARYVERSIONINTERNAL_H + diff --git a/qrtplib/rtppacket.cpp b/qrtplib/rtppacket.cpp new file mode 100644 index 000000000..41355eff4 --- /dev/null +++ b/qrtplib/rtppacket.cpp @@ -0,0 +1,372 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +#include "rtppacket.h" +#include "rtpstructs.h" +#include "rtpdefines.h" +#include "rtperrors.h" +#include "rtprawpacket.h" +#include + +namespace qrtplib +{ + +void RTPPacket::Clear() +{ + hasextension = false; + hasmarker = false; + numcsrcs = 0; + payloadtype = 0; + extseqnr = 0; + timestamp = 0; + ssrc = 0; + packet = 0; + payload = 0; + packetlength = 0; + payloadlength = 0; + extid = 0; + extension = 0; + extensionlength = 0; + error = 0; + externalbuffer = false; +} + +RTPPacket::RTPPacket(RTPRawPacket &rawpack) : + receivetime(rawpack.GetReceiveTime()) +{ + Clear(); + error = ParseRawPacket(rawpack); +} + +RTPPacket::RTPPacket( + uint8_t payloadtype, + const void *payloaddata, + unsigned int payloadlen, + uint16_t seqnr, + uint32_t timestamp, + uint32_t ssrc, + bool gotmarker, + uint8_t numcsrcs, + const uint32_t *csrcs, + bool gotextension, + uint16_t extensionid, + uint16_t extensionlen_numwords, + const void *extensiondata, + unsigned int maxpacksize) : + receivetime(0, 0) +{ + Clear(); + error = BuildPacket( + payloadtype, + payloaddata, + payloadlen, + seqnr, + timestamp, + ssrc, + gotmarker, + numcsrcs, + csrcs, + gotextension, + extensionid, + extensionlen_numwords, + extensiondata, + 0, + maxpacksize); +} + +RTPPacket::RTPPacket( + uint8_t payloadtype, + const void *payloaddata, + unsigned int payloadlen, + uint16_t seqnr, + uint32_t timestamp, + uint32_t ssrc, + bool gotmarker, + uint8_t numcsrcs, + const uint32_t *csrcs, + bool gotextension, + uint16_t extensionid, + uint16_t extensionlen_numwords, + const void *extensiondata, + void *buffer, + unsigned int buffersize) : + receivetime(0, 0) +{ + Clear(); + if (buffer == 0) + error = ERR_RTP_PACKET_EXTERNALBUFFERNULL; + else if (buffersize <= 0) + error = ERR_RTP_PACKET_ILLEGALBUFFERSIZE; + else + error = BuildPacket( + payloadtype, + payloaddata, + payloadlen, + seqnr, + timestamp, + ssrc, + gotmarker, + numcsrcs, + csrcs, + gotextension, + extensionid, + extensionlen_numwords, + extensiondata, + buffer, + buffersize); +} + +int RTPPacket::ParseRawPacket(RTPRawPacket &rawpack) +{ + uint8_t *packetbytes; + unsigned int packetlen; + uint8_t payloadtype; + RTPHeader *rtpheader; + bool marker; + int csrccount; + bool hasextension; + int payloadoffset, payloadlength; + int numpadbytes; + RTPExtensionHeader *rtpextheader; + + if (!rawpack.IsRTP()) // If we didn't receive it on the RTP port, we'll ignore it + return ERR_RTP_PACKET_INVALIDPACKET; + + // The length should be at least the size of the RTP header + packetlen = rawpack.GetDataLength(); + if (packetlen < sizeof(RTPHeader)) + return ERR_RTP_PACKET_INVALIDPACKET; + + packetbytes = (uint8_t *) rawpack.GetData(); + rtpheader = (RTPHeader *) packetbytes; + + // The version number should be correct + if (rtpheader->version != RTP_VERSION) + return ERR_RTP_PACKET_INVALIDPACKET; + + // We'll check if this is possibly a RTCP packet. For this to be possible + // the marker bit and payload type combined should be either an SR or RR + // identifier + marker = (rtpheader->marker == 0) ? false : true; + payloadtype = rtpheader->payloadtype; + if (marker) + { + if (payloadtype == (RTP_RTCPTYPE_SR & 127)) // don't check high bit (this was the marker!!) + return ERR_RTP_PACKET_INVALIDPACKET; + if (payloadtype == (RTP_RTCPTYPE_RR & 127)) + return ERR_RTP_PACKET_INVALIDPACKET; + } + + csrccount = rtpheader->csrccount; + payloadoffset = sizeof(RTPHeader) + (int) (csrccount * sizeof(uint32_t)); + + if (rtpheader->padding) // adjust payload length to take padding into account + { + numpadbytes = (int) packetbytes[packetlen - 1]; // last byte contains number of padding bytes + if (numpadbytes <= 0) + return ERR_RTP_PACKET_INVALIDPACKET; + } + else + numpadbytes = 0; + + hasextension = (rtpheader->extension == 0) ? false : true; + if (hasextension) // got header extension + { + rtpextheader = (RTPExtensionHeader *) (packetbytes + payloadoffset); + payloadoffset += sizeof(RTPExtensionHeader); + + uint16_t exthdrlen = m_endian.qToHost(rtpextheader->length); + payloadoffset += ((int) exthdrlen) * sizeof(uint32_t); + } + else + { + rtpextheader = 0; + } + + payloadlength = packetlen - numpadbytes - payloadoffset; + if (payloadlength < 0) + return ERR_RTP_PACKET_INVALIDPACKET; + + // Now, we've got a valid packet, so we can create a new instance of RTPPacket + // and fill in the members + + RTPPacket::hasextension = hasextension; + if (hasextension) + { + RTPPacket::extid = m_endian.qToHost(rtpextheader->extid); + RTPPacket::extensionlength = ((int) m_endian.qToHost(rtpextheader->length)) * sizeof(uint32_t); + RTPPacket::extension = ((uint8_t *) rtpextheader) + sizeof(RTPExtensionHeader); + } + + RTPPacket::hasmarker = marker; + RTPPacket::numcsrcs = csrccount; + RTPPacket::payloadtype = payloadtype; + + // Note: we don't fill in the EXTENDED sequence number here, since we + // don't have information about the source here. We just fill in the low + // 16 bits + RTPPacket::extseqnr = (uint32_t) m_endian.qToHost(rtpheader->sequencenumber); + + RTPPacket::timestamp = m_endian.qToHost(rtpheader->timestamp); + RTPPacket::ssrc = m_endian.qToHost(rtpheader->ssrc); + RTPPacket::packet = packetbytes; + RTPPacket::payload = packetbytes + payloadoffset; + RTPPacket::packetlength = packetlen; + RTPPacket::payloadlength = payloadlength; + + return 0; +} + +uint32_t RTPPacket::GetCSRC(int num) const +{ + if (num >= numcsrcs) + return 0; + + uint8_t *csrcpos; + uint32_t *csrcval_nbo; + uint32_t csrcval_hbo; + + csrcpos = packet + sizeof(RTPHeader) + num * sizeof(uint32_t); + csrcval_nbo = (uint32_t *) csrcpos; + csrcval_hbo = m_endian.qToHost(*csrcval_nbo); + return csrcval_hbo; +} + +int RTPPacket::BuildPacket( + uint8_t payloadtype, + const void *payloaddata, + unsigned int payloadlen, + uint16_t seqnr, + uint32_t timestamp, + uint32_t ssrc, + bool gotmarker, + uint8_t numcsrcs, + const uint32_t *csrcs, + bool gotextension, + uint16_t extensionid, + uint16_t extensionlen_numwords, + const void *extensiondata, + void *buffer, + unsigned int maxsize) +{ + if (numcsrcs > RTP_MAXCSRCS) + return ERR_RTP_PACKET_TOOMANYCSRCS; + + if (payloadtype > 127) // high bit should not be used + return ERR_RTP_PACKET_BADPAYLOADTYPE; + if (payloadtype == 72 || payloadtype == 73) // could cause confusion with rtcp types + return ERR_RTP_PACKET_BADPAYLOADTYPE; + + packetlength = sizeof(RTPHeader); + packetlength += sizeof(uint32_t) * ((unsigned int) numcsrcs); + if (gotextension) + { + packetlength += sizeof(RTPExtensionHeader); + packetlength += sizeof(uint32_t) * ((unsigned int) extensionlen_numwords); + } + packetlength += payloadlen; + + if (maxsize > 0 && packetlength > maxsize) + { + packetlength = 0; + return ERR_RTP_PACKET_DATAEXCEEDSMAXSIZE; + } + + // Ok, now we'll just fill in... + + RTPHeader *rtphdr; + + if (buffer == 0) + { + packet = new uint8_t[packetlength]; + externalbuffer = false; + } + else + { + packet = (uint8_t *) buffer; + externalbuffer = true; + } + + RTPPacket::hasmarker = gotmarker; + RTPPacket::hasextension = gotextension; + RTPPacket::numcsrcs = numcsrcs; + RTPPacket::payloadtype = payloadtype; + RTPPacket::extseqnr = (uint32_t) seqnr; + RTPPacket::timestamp = timestamp; + RTPPacket::ssrc = ssrc; + RTPPacket::payloadlength = payloadlen; + RTPPacket::extid = extensionid; + RTPPacket::extensionlength = ((unsigned int) extensionlen_numwords) * sizeof(uint32_t); + + rtphdr = (RTPHeader *) packet; + rtphdr->version = RTP_VERSION; + rtphdr->padding = 0; + if (gotmarker) + rtphdr->marker = 1; + else + rtphdr->marker = 0; + if (gotextension) + rtphdr->extension = 1; + else + rtphdr->extension = 0; + rtphdr->csrccount = numcsrcs; + rtphdr->payloadtype = payloadtype & 127; // make sure high bit isn't set + rtphdr->sequencenumber = qToBigEndian(seqnr); + rtphdr->timestamp = qToBigEndian(timestamp); + rtphdr->ssrc = qToBigEndian(ssrc); + + uint32_t *curcsrc; + int i; + + curcsrc = (uint32_t *) (packet + sizeof(RTPHeader)); + for (i = 0; i < numcsrcs; i++, curcsrc++) + *curcsrc = qToBigEndian(csrcs[i]); + + payload = packet + sizeof(RTPHeader) + ((unsigned int) numcsrcs) * sizeof(uint32_t); + if (gotextension) + { + RTPExtensionHeader *rtpexthdr = (RTPExtensionHeader *) payload; + + rtpexthdr->extid = qToBigEndian(extensionid); + rtpexthdr->length = qToBigEndian((uint16_t) extensionlen_numwords); + + payload += sizeof(RTPExtensionHeader); + memcpy(payload, extensiondata, RTPPacket::extensionlength); + + payload += RTPPacket::extensionlength; + } + memcpy(payload, payloaddata, payloadlen); + return 0; +} + +} // end namespace + diff --git a/qrtplib/rtppacket.h b/qrtplib/rtppacket.h new file mode 100644 index 000000000..46c632540 --- /dev/null +++ b/qrtplib/rtppacket.h @@ -0,0 +1,258 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +/** + * \file rtppacket.h + */ + +#ifndef RTPPACKET_H + +#define RTPPACKET_H + +#include "rtpconfig.h" +#include "rtptypes.h" +#include "rtptimeutilities.h" +#include "rtpendian.h" + +namespace qrtplib +{ + +class RTPRawPacket; + +/** Represents an RTP Packet. + * The RTPPacket class can be used to parse a RTPRawPacket instance if it represents RTP data. + * The class can also be used to create a new RTP packet according to the parameters specified by + * the user. + */ +class RTPPacket +{ +public: + /** Creates an RTPPacket instance based upon the data in \c rawpack, optionally installing a memory manager. + * Creates an RTPPacket instance based upon the data in \c rawpack, optionally installing a memory manager. + * If successful, the data is moved from the raw packet to the RTPPacket instance. + */ + RTPPacket(RTPRawPacket &rawpack); + + /** Creates a new buffer for an RTP packet and fills in the fields according to the specified parameters. + * Creates a new buffer for an RTP packet and fills in the fields according to the specified parameters. + * If \c maxpacksize is not equal to zero, an error is generated if the total packet size would exceed + * \c maxpacksize. The arguments of the constructor are self-explanatory. Note that the size of a header + * extension is specified in a number of 32-bit words. A memory manager can be installed. + */ + RTPPacket(uint8_t payloadtype, + const void *payloaddata, + unsigned int payloadlen, + uint16_t seqnr, + uint32_t timestamp, + uint32_t ssrc, + bool gotmarker, + uint8_t numcsrcs, + const uint32_t *csrcs, + bool gotextension, + uint16_t extensionid, + uint16_t extensionlen_numwords, + const void *extensiondata, + unsigned int maxpacksize); + + /** This constructor is similar to the other constructor, but here data is stored in an external buffer + * \c buffer with size \c buffersize. */ + RTPPacket(uint8_t payloadtype, + const void *payloaddata, + unsigned int payloadlen, + uint16_t seqnr, + uint32_t timestamp, + uint32_t ssrc, + bool gotmarker, + uint8_t numcsrcs, + const uint32_t *csrcs, + bool gotextension, + uint16_t extensionid, + uint16_t extensionlen_numwords, + const void *extensiondata, + void *buffer, + unsigned int buffersize); + + virtual ~RTPPacket() + { + if (packet && !externalbuffer) + delete[] packet; + } + + /** If an error occurred in one of the constructors, this function returns the error code. */ + int GetCreationError() const + { + return error; + } + + /** Returns \c true if the RTP packet has a header extension and \c false otherwise. */ + bool HasExtension() const + { + return hasextension; + } + + /** Returns \c true if the marker bit was set and \c false otherwise. */ + bool HasMarker() const + { + return hasmarker; + } + + /** Returns the number of CSRCs contained in this packet. */ + int GetCSRCCount() const + { + return numcsrcs; + } + + /** Returns a specific CSRC identifier. + * Returns a specific CSRC identifier. The parameter \c num can go from 0 to GetCSRCCount()-1. + */ + uint32_t GetCSRC(int num) const; + + /** Returns the payload type of the packet. */ + uint8_t GetPayloadType() const + { + return payloadtype; + } + + /** Returns the extended sequence number of the packet. + * Returns the extended sequence number of the packet. When the packet is just received, + * only the low $16$ bits will be set. The high 16 bits can be filled in later. + */ + uint32_t GetExtendedSequenceNumber() const + { + return extseqnr; + } + + /** Returns the sequence number of this packet. */ + uint16_t GetSequenceNumber() const + { + return (uint16_t) (extseqnr & 0x0000FFFF); + } + + /** Sets the extended sequence number of this packet to \c seq. */ + void SetExtendedSequenceNumber(uint32_t seq) + { + extseqnr = seq; + } + + /** Returns the timestamp of this packet. */ + uint32_t GetTimestamp() const + { + return timestamp; + } + + /** Returns the SSRC identifier stored in this packet. */ + uint32_t GetSSRC() const + { + return ssrc; + } + + /** Returns a pointer to the data of the entire packet. */ + uint8_t *GetPacketData() const + { + return packet; + } + + /** Returns a pointer to the actual payload data. */ + uint8_t *GetPayloadData() const + { + return payload; + } + + /** Returns the length of the entire packet. */ + unsigned int GetPacketLength() const + { + return packetlength; + } + + /** Returns the payload length. */ + unsigned int GetPayloadLength() const + { + return payloadlength; + } + + /** If a header extension is present, this function returns the extension identifier. */ + uint16_t GetExtensionID() const + { + return extid; + } + + /** Returns the length of the header extension data. */ + uint8_t *GetExtensionData() const + { + return extension; + } + + /** Returns the length of the header extension data. */ + unsigned int GetExtensionLength() const + { + return extensionlength; + } + + /** Returns the time at which this packet was received. + * When an RTPPacket instance is created from an RTPRawPacket instance, the raw packet's + * reception time is stored in the RTPPacket instance. This function then retrieves that + * time. + */ + RTPTime GetReceiveTime() const + { + return receivetime; + } +private: + void Clear(); + int ParseRawPacket(RTPRawPacket &rawpack); + int BuildPacket(uint8_t payloadtype, const void *payloaddata, unsigned int payloadlen, uint16_t seqnr, uint32_t timestamp, uint32_t ssrc, bool gotmarker, uint8_t numcsrcs, + const uint32_t *csrcs, bool gotextension, uint16_t extensionid, uint16_t extensionlen_numwords, const void *extensiondata, void *buffer, unsigned int maxsize); + + RTPEndian m_endian; + int error; + + bool hasextension, hasmarker; + int numcsrcs; + + uint8_t payloadtype; + uint32_t extseqnr, timestamp, ssrc; + uint8_t *packet, *payload; + unsigned int packetlength, payloadlength; + + uint16_t extid; + uint8_t *extension; + unsigned int extensionlength; + + bool externalbuffer; + + RTPTime receivetime; +}; + +} // end namespace + +#endif // RTPPACKET_H + diff --git a/qrtplib/rtppacketbuilder.cpp b/qrtplib/rtppacketbuilder.cpp index bc91c3583..2c6467951 100644 --- a/qrtplib/rtppacketbuilder.cpp +++ b/qrtplib/rtppacketbuilder.cpp @@ -1,36 +1,34 @@ /* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtppacketbuilder.h" #include "rtperrors.h" @@ -38,92 +36,102 @@ #include "rtpsources.h" #include #include -//#include "rtpdebug.h" namespace qrtplib { -RTPPacketBuilder::RTPPacketBuilder(RTPRandom &r) : rtprnd(r), lastwallclocktime(0,0) +RTPPacketBuilder::RTPPacketBuilder(RTPRandom &r) : + rtprnd(r), + maxpacksize(0), + buffer(0), + packetlength(0), + lastwallclocktime(0, 0) { - init = false; + init = false; + deftsset = false; + defptset = false; + defmarkset = false; + defaultmark = false; + defaulttimestampinc = 0; + ssrc = 0; + timestamp = 0; + seqnr = 0; + prevrtptimestamp = 0; + lastrtptimestamp = 0; + defaultpayloadtype = 0; + numcsrcs = 0; + numpayloadbytes = 0; + numpackets = 0; + memset((char *) csrcs, 0, RTP_MAXCSRCS*sizeof(uint32_t)); + timeinit.Dummy(); + + //std::cout << (void *)(&rtprnd) << std::endl; } RTPPacketBuilder::~RTPPacketBuilder() { - Destroy(); + Destroy(); } -int RTPPacketBuilder::Init(std::size_t max) +int RTPPacketBuilder::Init(unsigned int max) { - if (init) { - return ERR_RTP_PACKBUILD_ALREADYINIT; - } + if (init) + return ERR_RTP_PACKBUILD_ALREADYINIT; + if (max <= 0) + return ERR_RTP_PACKBUILD_INVALIDMAXPACKETSIZE; - if (max <= 0) { - return ERR_RTP_PACKBUILD_INVALIDMAXPACKETSIZE; - } - - maxpacksize = max; - buffer = new uint8_t[max]; - if (buffer == 0) { - return ERR_RTP_OUTOFMEM; - } - packetlength = 0; - - CreateNewSSRC(); + maxpacksize = max; + buffer = new uint8_t[max]; + packetlength = 0; + numpackets = 0; - deftsset = false; - defptset = false; - defmarkset = false; - - numcsrcs = 0; - - init = true; - return 0; + CreateNewSSRC(); + + deftsset = false; + defptset = false; + defmarkset = false; + + numcsrcs = 0; + + init = true; + return 0; } void RTPPacketBuilder::Destroy() { - if (!init) { - return; - } - delete[] buffer; - init = false; + if (!init) + return; + delete[] buffer; + init = false; } -int RTPPacketBuilder::SetMaximumPacketSize(std::size_t max) +int RTPPacketBuilder::SetMaximumPacketSize(unsigned int max) { - uint8_t *newbuf; + uint8_t *newbuf; - if (max <= 0) { - return ERR_RTP_PACKBUILD_INVALIDMAXPACKETSIZE; - } - newbuf = new uint8_t[max]; - if (newbuf == 0) { - return ERR_RTP_OUTOFMEM; - } - - delete[] buffer; - buffer = newbuf; - maxpacksize = max; - return 0; + if (max <= 0) + return ERR_RTP_PACKBUILD_INVALIDMAXPACKETSIZE; + newbuf = new uint8_t[max]; + + delete[] buffer; + buffer = newbuf; + maxpacksize = max; + return 0; } int RTPPacketBuilder::AddCSRC(uint32_t csrc) { - if (!init) { + if (!init) return ERR_RTP_PACKBUILD_NOTINIT; - } - if (numcsrcs >= RTP_MAXCSRCS) { + if (numcsrcs >= RTP_MAXCSRCS) return ERR_RTP_PACKBUILD_CSRCLISTFULL; - } int i; - for (i = 0; i < numcsrcs; i++) { - if (csrcs[i] == csrc) { + for (i = 0; i < numcsrcs; i++) + { + if (csrcs[i] == csrc) return ERR_RTP_PACKBUILD_CSRCALREADYINLIST; - } } csrcs[numcsrcs] = csrc; numcsrcs++; @@ -132,154 +140,142 @@ int RTPPacketBuilder::AddCSRC(uint32_t csrc) int RTPPacketBuilder::DeleteCSRC(uint32_t csrc) { - if (!init) { - return ERR_RTP_PACKBUILD_NOTINIT; - } - - int i = 0; - bool found = false; + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; - while (!found && i < numcsrcs) - { - if (csrcs[i] == csrc) { - found = true; - } else { - i++; - } - } + int i = 0; + bool found = false; - if (!found) { - return ERR_RTP_PACKBUILD_CSRCNOTINLIST; - } - - // move the last csrc in the place of the deleted one - numcsrcs--; - if (numcsrcs > 0 && numcsrcs != i) { - csrcs[i] = csrcs[numcsrcs]; - } + while (!found && i < numcsrcs) + { + if (csrcs[i] == csrc) + found = true; + else + i++; + } - return 0; + if (!found) + return ERR_RTP_PACKBUILD_CSRCNOTINLIST; + + // move the last csrc in the place of the deleted one + numcsrcs--; + if (numcsrcs > 0 && numcsrcs != i) + csrcs[i] = csrcs[numcsrcs]; + return 0; } void RTPPacketBuilder::ClearCSRCList() { - if (!init) { - return; - } - numcsrcs = 0; + if (!init) + return; + numcsrcs = 0; } uint32_t RTPPacketBuilder::CreateNewSSRC() { - ssrc = rtprnd.GetRandom32(); - timestamp = rtprnd.GetRandom32(); - seqnr = rtprnd.GetRandom16(); + ssrc = rtprnd.GetRandom32(); + timestamp = rtprnd.GetRandom32(); + seqnr = rtprnd.GetRandom16(); - // p 38: the count SHOULD be reset if the sender changes its SSRC identifier - numpayloadbytes = 0; - numpackets = 0; - return ssrc; + qDebug("RTPPacketBuilder::CreateNewSSRC: timestamp: %u", timestamp); + + // p 38: the count SHOULD be reset if the sender changes its SSRC identifier + numpayloadbytes = 0; + numpackets = 0; + return ssrc; } uint32_t RTPPacketBuilder::CreateNewSSRC(RTPSources &sources) { - bool found; - - do - { - ssrc = rtprnd.GetRandom32(); - found = sources.GotEntry(ssrc); - } while (found); - - timestamp = rtprnd.GetRandom32(); - seqnr = rtprnd.GetRandom16(); + bool found; - // p 38: the count SHOULD be reset if the sender changes its SSRC identifier - numpayloadbytes = 0; - numpackets = 0; - return ssrc; + do + { + ssrc = rtprnd.GetRandom32(); + found = sources.GotEntry(ssrc); + } while (found); + + timestamp = rtprnd.GetRandom32(); + seqnr = rtprnd.GetRandom16(); + + // p 38: the count SHOULD be reset if the sender changes its SSRC identifier + numpayloadbytes = 0; + numpackets = 0; + return ssrc; } -int RTPPacketBuilder::BuildPacket(const void *data, std::size_t len) +int RTPPacketBuilder::BuildPacket(const void *data, unsigned int len) { - if (!init) - return ERR_RTP_PACKBUILD_NOTINIT; - if (!defptset) - return ERR_RTP_PACKBUILD_DEFAULTPAYLOADTYPENOTSET; - if (!defmarkset) - return ERR_RTP_PACKBUILD_DEFAULTMARKNOTSET; - if (!deftsset) - return ERR_RTP_PACKBUILD_DEFAULTTSINCNOTSET; - return PrivateBuildPacket(data,len,defaultpayloadtype,defaultmark,defaulttimestampinc,false); + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + if (!defptset) + return ERR_RTP_PACKBUILD_DEFAULTPAYLOADTYPENOTSET; + if (!defmarkset) + return ERR_RTP_PACKBUILD_DEFAULTMARKNOTSET; + if (!deftsset) + return ERR_RTP_PACKBUILD_DEFAULTTSINCNOTSET; + return PrivateBuildPacket(data, len, defaultpayloadtype, defaultmark, defaulttimestampinc, false); } -int RTPPacketBuilder::BuildPacket(const void *data, std::size_t len, - uint8_t pt,bool mark,uint32_t timestampinc) +int RTPPacketBuilder::BuildPacket(const void *data, unsigned int len, uint8_t pt, bool mark, uint32_t timestampinc) { - if (!init) { - return ERR_RTP_PACKBUILD_NOTINIT; - } - - return PrivateBuildPacket(data,len,pt,mark,timestampinc,false); + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + return PrivateBuildPacket(data, len, pt, mark, timestampinc, false); } -int RTPPacketBuilder::BuildPacketEx(const void *data, std::size_t len, - uint16_t hdrextID, const void *hdrextdata, std::size_t numhdrextwords) +int RTPPacketBuilder::BuildPacketEx(const void *data, unsigned int len, uint16_t hdrextID, const void *hdrextdata, unsigned int numhdrextwords) { - if (!init) - return ERR_RTP_PACKBUILD_NOTINIT; - if (!defptset) - return ERR_RTP_PACKBUILD_DEFAULTPAYLOADTYPENOTSET; - if (!defmarkset) - return ERR_RTP_PACKBUILD_DEFAULTMARKNOTSET; - if (!deftsset) - return ERR_RTP_PACKBUILD_DEFAULTTSINCNOTSET; - return PrivateBuildPacket(data,len,defaultpayloadtype,defaultmark,defaulttimestampinc,true,hdrextID,hdrextdata,numhdrextwords); + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + if (!defptset) + return ERR_RTP_PACKBUILD_DEFAULTPAYLOADTYPENOTSET; + if (!defmarkset) + return ERR_RTP_PACKBUILD_DEFAULTMARKNOTSET; + if (!deftsset) + return ERR_RTP_PACKBUILD_DEFAULTTSINCNOTSET; + return PrivateBuildPacket(data, len, defaultpayloadtype, defaultmark, defaulttimestampinc, true, hdrextID, hdrextdata, numhdrextwords); } -int RTPPacketBuilder::BuildPacketEx(const void *data, std::size_t len, - uint8_t pt, bool mark, uint32_t timestampinc, - uint16_t hdrextID, const void *hdrextdata, std::size_t numhdrextwords) +int RTPPacketBuilder::BuildPacketEx(const void *data, unsigned int len, uint8_t pt, bool mark, uint32_t timestampinc, uint16_t hdrextID, const void *hdrextdata, unsigned int numhdrextwords) { - if (!init) { - return ERR_RTP_PACKBUILD_NOTINIT; - } - return PrivateBuildPacket(data,len,pt,mark,timestampinc,true,hdrextID,hdrextdata,numhdrextwords); + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + return PrivateBuildPacket(data, len, pt, mark, timestampinc, true, hdrextID, hdrextdata, numhdrextwords); } -int RTPPacketBuilder::PrivateBuildPacket(const void *data, std::size_t len, - uint8_t pt, bool mark, uint32_t timestampinc, bool gotextension, - uint16_t hdrextID, const void *hdrextdata, std::size_t numhdrextwords) +int RTPPacketBuilder::PrivateBuildPacket(const void *data, unsigned int len, uint8_t pt, bool mark, uint32_t timestampinc, bool gotextension, uint16_t hdrextID, const void *hdrextdata, + unsigned int numhdrextwords) { - RTPPacket p(pt,data,len,seqnr,timestamp,ssrc,mark,numcsrcs,csrcs,gotextension,hdrextID, - (uint16_t)numhdrextwords,hdrextdata,buffer,maxpacksize,GetMemoryManager()); - int status = p.GetCreationError(); + RTPPacket p(pt, data, len, seqnr, timestamp, ssrc, mark, numcsrcs, csrcs, gotextension, hdrextID, (uint16_t) numhdrextwords, hdrextdata, buffer, maxpacksize); + int status = p.GetCreationError(); - if (status < 0) { - return status - }; - packetlength = p.GetPacketLength(); + if (status < 0) + return status; + packetlength = p.GetPacketLength(); - if (numpackets == 0) // first packet - { - lastwallclocktime = RTPTime::CurrentTime(); - lastrtptimestamp = timestamp; - prevrtptimestamp = timestamp; - } - else if (timestamp != prevrtptimestamp) - { - lastwallclocktime = RTPTime::CurrentTime(); - lastrtptimestamp = timestamp; - prevrtptimestamp = timestamp; - } - - numpayloadbytes += (uint32_t)p.GetPayloadLength(); - numpackets++; - timestamp += timestampinc; - seqnr++; + if (numpackets == 0) // first packet + { + lastwallclocktime = RTPTime::CurrentTime(); + lastrtptimestamp = timestamp; + prevrtptimestamp = timestamp; + } + else if (timestamp != prevrtptimestamp) + { + lastwallclocktime = RTPTime::CurrentTime(); + lastrtptimestamp = timestamp; + prevrtptimestamp = timestamp; + } - return 0; + numpayloadbytes += (uint32_t) p.GetPayloadLength(); + numpackets++; + timestamp += timestampinc; + seqnr++; + + //qDebug("RTPPacketBuilder::PrivateBuildPacket: numpackets: %u timestamp: %u timestampinc: %u seqnr: %u", numpackets, timestamp, timestampinc, seqnr); + + return 0; } } // end namespace diff --git a/qrtplib/rtppacketbuilder.h b/qrtplib/rtppacketbuilder.h index e0e30ba52..a1cdedb12 100644 --- a/qrtplib/rtppacketbuilder.h +++ b/qrtplib/rtppacketbuilder.h @@ -1,36 +1,34 @@ /* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtppacketbuilder.h @@ -40,233 +38,284 @@ #define RTPPACKETBUILDER_H -//#include "rtpconfig.h" +#include "rtpconfig.h" #include "rtperrors.h" #include "rtpdefines.h" #include "rtprandom.h" #include "rtptimeutilities.h" -//#include "rtptypes.h" +#include "rtptypes.h" + +#include "export.h" namespace qrtplib { class RTPSources; -/** This class can be used to build RTP packets and is a bit more high-level than the RTPPacket +/** This class can be used to build RTP packets and is a bit more high-level than the RTPPacket * class: it generates an SSRC identifier, keeps track of timestamp and sequence number etc. */ -class RTPPacketBuilder +class QRTPLIB_API RTPPacketBuilder { public: - /** Constructs an instance which will use \c rtprand for generating random numbers - * (used to initialize the SSRC value and sequence number), optionally installing a memory manager. - **/ - RTPPacketBuilder(RTPRandom &rtprand); - ~RTPPacketBuilder(); + /** Constructs an instance which will use \c rtprand for generating random numbers + * (used to initialize the SSRC value and sequence number), optionally installing a memory manager. + **/ + RTPPacketBuilder(RTPRandom &rtprand); + ~RTPPacketBuilder(); - /** Initializes the builder to only allow packets with a size below \c maxpacksize. */ - int Init(std::size_t maxpacksize); + /** Initializes the builder to only allow packets with a size below \c maxpacksize. */ + int Init(unsigned int maxpacksize); - /** Cleans up the builder. */ - void Destroy(); + /** Cleans up the builder. */ + void Destroy(); - /** Returns the number of packets which have been created with the current SSRC identifier. */ - uint32_t GetPacketCount() { if (!init) return 0; return numpackets; } + /** Returns the number of packets which have been created with the current SSRC identifier. */ + uint32_t GetPacketCount() + { + if (!init) + return 0; + return numpackets; + } - /** Returns the number of payload octets which have been generated with this SSRC identifier. */ - uint32_t GetPayloadOctetCount() { if (!init) return 0; return numpayloadbytes; } + /** Returns the number of payload octets which have been generated with this SSRC identifier. */ + uint32_t GetPayloadOctetCount() + { + if (!init) + return 0; + return numpayloadbytes; + } - /** Sets the maximum allowed packet size to \c maxpacksize. */ - int SetMaximumPacketSize(std::size_t maxpacksize); + /** Sets the maximum allowed packet size to \c maxpacksize. */ + int SetMaximumPacketSize(unsigned int maxpacksize); - /** Adds a CSRC to the CSRC list which will be stored in the RTP packets. */ - int AddCSRC(uint32_t csrc); + /** Adds a CSRC to the CSRC list which will be stored in the RTP packets. */ + int AddCSRC(uint32_t csrc); - /** Deletes a CSRC from the list which will be stored in the RTP packets. */ - int DeleteCSRC(uint32_t csrc); + /** Deletes a CSRC from the list which will be stored in the RTP packets. */ + int DeleteCSRC(uint32_t csrc); - /** Clears the CSRC list. */ - void ClearCSRCList(); - - /** Builds a packet with payload \c data and payload length \c len. - * Builds a packet with payload \c data and payload length \c len. The payload type, marker - * and timestamp increment used will be those that have been set using the \c SetDefault - * functions below. - */ - int BuildPacket(const void *data, std::size_t len); + /** Clears the CSRC list. */ + void ClearCSRCList(); - /** Builds a packet with payload \c data and payload length \c len. - * Builds a packet with payload \c data and payload length \c len. The payload type will be - * set to \c pt, the marker bit to \c mark and after building this packet, the timestamp will - * be incremented with \c timestamp. - */ - int BuildPacket(const void *data, std::size_t len, - uint8_t pt, bool mark, uint32_t timestampinc); + /** Builds a packet with payload \c data and payload length \c len. + * Builds a packet with payload \c data and payload length \c len. The payload type, marker + * and timestamp increment used will be those that have been set using the \c SetDefault + * functions below. + */ + int BuildPacket(const void *data, unsigned int len); - /** Builds a packet with payload \c data and payload length \c len. - * Builds a packet with payload \c data and payload length \c len. The payload type, marker - * and timestamp increment used will be those that have been set using the \c SetDefault - * functions below. This packet will also contain an RTP header extension with identifier - * \c hdrextID and data \c hdrextdata. The length of the header extension data is given by - * \c numhdrextwords which expresses the length in a number of 32-bit words. - */ - int BuildPacketEx(const void *data, std::size_t len, - uint16_t hdrextID, const void *hdrextdata, std::size_t numhdrextwords); + /** Builds a packet with payload \c data and payload length \c len. + * Builds a packet with payload \c data and payload length \c len. The payload type will be + * set to \c pt, the marker bit to \c mark and after building this packet, the timestamp will + * be incremented with \c timestamp. + */ + int BuildPacket(const void *data, unsigned int len, uint8_t pt, bool mark, uint32_t timestampinc); - /** Builds a packet with payload \c data and payload length \c len. - * Builds a packet with payload \c data and payload length \c len. The payload type will be set - * to \c pt, the marker bit to \c mark and after building this packet, the timestamp will - * be incremented with \c timestamp. This packet will also contain an RTP header extension - * with identifier \c hdrextID and data \c hdrextdata. The length of the header extension - * data is given by \c numhdrextwords which expresses the length in a number of 32-bit words. - */ - int BuildPacketEx(const void *data, std::size_t len, - uint8_t pt, bool mark, uint32_t timestampinc, - uint16_t hdrextID, const void *hdrextdata, std::size_t numhdrextwords); + /** Builds a packet with payload \c data and payload length \c len. + * Builds a packet with payload \c data and payload length \c len. The payload type, marker + * and timestamp increment used will be those that have been set using the \c SetDefault + * functions below. This packet will also contain an RTP header extension with identifier + * \c hdrextID and data \c hdrextdata. The length of the header extension data is given by + * \c numhdrextwords which expresses the length in a number of 32-bit words. + */ + int BuildPacketEx(const void *data, unsigned int len, uint16_t hdrextID, const void *hdrextdata, unsigned int numhdrextwords); - /** Returns a pointer to the last built RTP packet data. */ - uint8_t *GetPacket() { if (!init) return 0; return buffer; } + /** Builds a packet with payload \c data and payload length \c len. + * Builds a packet with payload \c data and payload length \c len. The payload type will be set + * to \c pt, the marker bit to \c mark and after building this packet, the timestamp will + * be incremented with \c timestamp. This packet will also contain an RTP header extension + * with identifier \c hdrextID and data \c hdrextdata. The length of the header extension + * data is given by \c numhdrextwords which expresses the length in a number of 32-bit words. + */ + int BuildPacketEx(const void *data, unsigned int len, uint8_t pt, bool mark, uint32_t timestampinc, uint16_t hdrextID, const void *hdrextdata, unsigned int numhdrextwords); - /** Returns the size of the last built RTP packet. */ - size_t GetPacketLength() { if (!init) return 0; return packetlength; } - - /** Sets the default payload type to \c pt. */ - int SetDefaultPayloadType(uint8_t pt); + /** Returns a pointer to the last built RTP packet data. */ + uint8_t *GetPacket() + { + if (!init) + return 0; + return buffer; + } - /** Sets the default marker bit to \c m. */ - int SetDefaultMark(bool m); + /** Returns the size of the last built RTP packet. */ + unsigned int GetPacketLength() + { + if (!init) + return 0; + return packetlength; + } - /** Sets the default timestamp increment to \c timestampinc. */ - int SetDefaultTimestampIncrement(uint32_t timestampinc); + /** Sets the default payload type to \c pt. */ + int SetDefaultPayloadType(uint8_t pt); - /** This function increments the timestamp with the amount given by \c inc. - * This function increments the timestamp with the amount given by \c inc. This can be useful - * if, for example, a packet was not sent because it contained only silence. Then, this function - * should be called to increment the timestamp with the appropriate amount so that the next packets - * will still be played at the correct time at other hosts. - */ - int IncrementTimestamp(uint32_t inc); + /** Sets the default marker bit to \c m. */ + int SetDefaultMark(bool m); - /** This function increments the timestamp with the amount given set by the SetDefaultTimestampIncrement - * member function. - * This function increments the timestamp with the amount given set by the SetDefaultTimestampIncrement - * member function. This can be useful if, for example, a packet was not sent because it contained only silence. - * Then, this function should be called to increment the timestamp with the appropriate amount so that the next - * packets will still be played at the correct time at other hosts. - */ - int IncrementTimestampDefault(); - - /** Creates a new SSRC to be used in generated packets. - * Creates a new SSRC to be used in generated packets. This will also generate new timestamp and - * sequence number offsets. - */ - uint32_t CreateNewSSRC(); + /** Sets the default timestamp increment to \c timestampinc. */ + int SetDefaultTimestampIncrement(uint32_t timestampinc); - /** Creates a new SSRC to be used in generated packets. - * Creates a new SSRC to be used in generated packets. This will also generate new timestamp and - * sequence number offsets. The source table \c sources is used to make sure that the chosen SSRC - * isn't used by another participant yet. - */ - uint32_t CreateNewSSRC(RTPSources &sources); + /** This function increments the timestamp with the amount given by \c inc. + * This function increments the timestamp with the amount given by \c inc. This can be useful + * if, for example, a packet was not sent because it contained only silence. Then, this function + * should be called to increment the timestamp with the appropriate amount so that the next packets + * will still be played at the correct time at other hosts. + */ + int IncrementTimestamp(uint32_t inc); - /** Returns the current SSRC identifier. */ - uint32_t GetSSRC() const { if (!init) return 0; return ssrc; } + /** This function increments the timestamp with the amount given set by the SetDefaultTimestampIncrement + * member function. + * This function increments the timestamp with the amount given set by the SetDefaultTimestampIncrement + * member function. This can be useful if, for example, a packet was not sent because it contained only silence. + * Then, this function should be called to increment the timestamp with the appropriate amount so that the next + * packets will still be played at the correct time at other hosts. + */ + int IncrementTimestampDefault(); - /** Returns the current RTP timestamp. */ - uint32_t GetTimestamp() const { if (!init) return 0; return timestamp; } + /** Creates a new SSRC to be used in generated packets. + * Creates a new SSRC to be used in generated packets. This will also generate new timestamp and + * sequence number offsets. + */ + uint32_t CreateNewSSRC(); - /** Returns the current sequence number. */ - uint16_t GetSequenceNumber() const { if (!init) return 0; return seqnr; } + /** Creates a new SSRC to be used in generated packets. + * Creates a new SSRC to be used in generated packets. This will also generate new timestamp and + * sequence number offsets. The source table \c sources is used to make sure that the chosen SSRC + * isn't used by another participant yet. + */ + uint32_t CreateNewSSRC(RTPSources &sources); - /** Returns the time at which a packet was generated. - * Returns the time at which a packet was generated. This is not necessarily the time at which - * the last RTP packet was generated: if the timestamp increment was zero, the time is not updated. - */ - RTPTime GetPacketTime() const { if (!init) return RTPTime(0,0); return lastwallclocktime; } + /** Returns the current SSRC identifier. */ + uint32_t GetSSRC() const + { + if (!init) + return 0; + return ssrc; + } - /** Returns the RTP timestamp which corresponds to the time returned by the previous function. */ - uint32_t GetPacketTimestamp() const { if (!init) return 0; return lastrtptimestamp; } + /** Returns the current RTP timestamp. */ + uint32_t GetTimestamp() const + { + if (!init) + return 0; + return timestamp; + } - /** Sets a specific SSRC to be used. - * Sets a specific SSRC to be used. Does not create a new timestamp offset or sequence number - * offset. Does not reset the packet count or byte count. Think twice before using this! - */ - void AdjustSSRC(uint32_t s) { ssrc = s; } + /** Returns the current sequence number. */ + uint16_t GetSequenceNumber() const + { + if (!init) + return 0; + return seqnr; + } + + /** Returns the time at which a packet was generated. + * Returns the time at which a packet was generated. This is not necessarily the time at which + * the last RTP packet was generated: if the timestamp increment was zero, the time is not updated. + */ + RTPTime GetPacketTime() const + { + if (!init) + return RTPTime(0, 0); + return lastwallclocktime; + } + + /** Returns the RTP timestamp which corresponds to the time returned by the previous function. */ + uint32_t GetPacketTimestamp() const + { + if (!init) + return 0; + return lastrtptimestamp; + } + + /** Sets a specific SSRC to be used. + * Sets a specific SSRC to be used. Does not create a new timestamp offset or sequence number + * offset. Does not reset the packet count or byte count. Think twice before using this! + */ + void AdjustSSRC(uint32_t s) + { + ssrc = s; + } private: - int PrivateBuildPacket(const void *data, std::size_t len, - uint8_t pt, bool mark, uint32_t timestampinc, bool gotextension, - uint16_t hdrextID = 0, const void *hdrextdata = 0, std::size_t numhdrextwords = 0); + int PrivateBuildPacket( + const void *data, + unsigned int len, uint8_t pt, + bool mark, + uint32_t timestampinc, + bool gotextension, uint16_t hdrextID = 0, + const void *hdrextdata = 0, + unsigned int numhdrextwords = 0); - RTPRandom &rtprnd; - std::size_t maxpacksize; - uint8_t *buffer; - std::size_t packetlength; - - uint32_t numpayloadbytes; - uint32_t numpackets; - bool init; + RTPRandom &rtprnd; + unsigned int maxpacksize; + uint8_t *buffer; + unsigned int packetlength; - uint32_t ssrc; - uint32_t timestamp; - uint16_t seqnr; + uint32_t numpayloadbytes; + uint32_t numpackets; + bool init; - uint32_t defaulttimestampinc; - uint8_t defaultpayloadtype; - bool defaultmark; + uint32_t ssrc; + uint32_t timestamp; + uint16_t seqnr; - bool deftsset,defptset,defmarkset; + uint32_t defaulttimestampinc; + uint8_t defaultpayloadtype; + bool defaultmark; - uint32_t csrcs[RTP_MAXCSRCS]; - int numcsrcs; + bool deftsset, defptset, defmarkset; - RTPTime lastwallclocktime; - uint32_t lastrtptimestamp; - uint32_t prevrtptimestamp; + uint32_t csrcs[RTP_MAXCSRCS]; + int numcsrcs; + + RTPTime lastwallclocktime; + uint32_t lastrtptimestamp; + uint32_t prevrtptimestamp; }; inline int RTPPacketBuilder::SetDefaultPayloadType(uint8_t pt) { - if (!init) - return ERR_RTP_PACKBUILD_NOTINIT; - defptset = true; - defaultpayloadtype = pt; - return 0; + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + defptset = true; + defaultpayloadtype = pt; + return 0; } inline int RTPPacketBuilder::SetDefaultMark(bool m) { - if (!init) - return ERR_RTP_PACKBUILD_NOTINIT; - defmarkset = true; - defaultmark = m; - return 0; + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + defmarkset = true; + defaultmark = m; + return 0; } inline int RTPPacketBuilder::SetDefaultTimestampIncrement(uint32_t timestampinc) { - if (!init) - return ERR_RTP_PACKBUILD_NOTINIT; - deftsset = true; - defaulttimestampinc = timestampinc; - return 0; + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + deftsset = true; + defaulttimestampinc = timestampinc; + return 0; } inline int RTPPacketBuilder::IncrementTimestamp(uint32_t inc) { - if (!init) - return ERR_RTP_PACKBUILD_NOTINIT; - timestamp += inc; - return 0; + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + timestamp += inc; + return 0; } inline int RTPPacketBuilder::IncrementTimestampDefault() { - if (!init) - return ERR_RTP_PACKBUILD_NOTINIT; - if (!deftsset) - return ERR_RTP_PACKBUILD_DEFAULTTSINCNOTSET; - timestamp += defaulttimestampinc; - return 0; + if (!init) + return ERR_RTP_PACKBUILD_NOTINIT; + if (!deftsset) + return ERR_RTP_PACKBUILD_DEFAULTTSINCNOTSET; + timestamp += defaulttimestampinc; + return 0; } } // end namespace diff --git a/qrtplib/rtprandom.cpp b/qrtplib/rtprandom.cpp index 56b3bd165..5fbb5562f 100644 --- a/qrtplib/rtprandom.cpp +++ b/qrtplib/rtprandom.cpp @@ -1,89 +1,79 @@ /* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ + +#if defined(WIN32) && !defined(_WIN32_WCE) +#define _CRT_RAND_S +#endif // WIN32 || _WIN32_WCE #include "rtprandom.h" +#include "rtprandomrands.h" #include "rtprandomurandom.h" #include "rtprandomrand48.h" -#include -#ifndef WIN32 - #include -#else - #include - #include -#endif // WIN32 -//#include "rtpdebug.h" +#include +#include + +#include namespace qrtplib { uint32_t RTPRandom::PickSeed() { - uint32_t x; -// TODO: do it for MinGW see sdrbase/util/uid.h,cpp + uint32_t x; + x = (uint32_t) getpid(); + QDateTime currentDateTime = QDateTime::currentDateTime(); + x += currentDateTime.toTime_t(); #if defined(WIN32) - x = (uint32_t)GetCurrentProcessId(); - - FILETIME ft; - SYSTEMTIME st; - - GetSystemTime(&st); - SystemTimeToFileTime(&st,&ft); - - x += ft.dwLowDateTime; - x ^= (uint32_t)((uint8_t *)this - (uint8_t *)0); + x += QDateTime::currentMSecsSinceEpoch() % 1000; #else - x = (uint32_t)getpid(); - x += (uint32_t)time(0); - x += (uint32_t)clock(); - x ^= (uint32_t)((uint8_t *)this - (uint8_t *)0); + x += (uint32_t)clock(); #endif - return x; + x ^= (uint32_t)((uint8_t *)this - (uint8_t *)0); + return x; } RTPRandom *RTPRandom::CreateDefaultRandomNumberGenerator() { - RTPRandomURandom *r = new RTPRandomURandom(); - RTPRandom *rRet = r; + RTPRandomURandom *r = new RTPRandomURandom(); + RTPRandom *rRet = r; - if (r->Init() < 0) // fall back to rand48 - { - delete r; - rRet = new RTPRandomRand48(); - } - - return rRet; + if (r->Init() < 0) // fall back to rand48 + { + delete r; + rRet = new RTPRandomRand48(); + } + + return rRet; } } // end namespace diff --git a/qrtplib/rtprandom.h b/qrtplib/rtprandom.h index e8316366b..249914c6a 100644 --- a/qrtplib/rtprandom.h +++ b/qrtplib/rtprandom.h @@ -1,36 +1,34 @@ /* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtprandom.h @@ -40,40 +38,45 @@ #define RTPRANDOM_H -//#include "rtpconfig.h" -//#include "rtptypes.h" +#include "rtpconfig.h" +#include "rtptypes.h" #include -#include -#define RTPRANDOM_2POWMIN63 1.08420217248550443400745280086994171142578125e-19 +#include "export.h" + +#define RTPRANDOM_2POWMIN63 1.08420217248550443400745280086994171142578125e-19 namespace qrtplib { /** Interface for generating random numbers. */ -class RTPRandom +class QRTPLIB_API RTPRandom { public: - RTPRandom() { } - virtual ~RTPRandom() { } + RTPRandom() + { + } + virtual ~RTPRandom() + { + } - /** Returns a random eight bit value. */ - virtual uint8_t GetRandom8() = 0; + /** Returns a random eight bit value. */ + virtual uint8_t GetRandom8() = 0; - /** Returns a random sixteen bit value. */ - virtual uint16_t GetRandom16() = 0; + /** Returns a random sixteen bit value. */ + virtual uint16_t GetRandom16() = 0; - /** Returns a random thirty-two bit value. */ - virtual uint32_t GetRandom32() = 0; + /** Returns a random thirty-two bit value. */ + virtual uint32_t GetRandom32() = 0; - /** Returns a random number between $0.0$ and $1.0$. */ - virtual double GetRandomDouble() = 0; + /** Returns a random number between $0.0$ and $1.0$. */ + virtual double GetRandomDouble() = 0; - /** Can be used by subclasses to generate a seed for a random number generator. */ - uint32_t PickSeed(); + /** Can be used by subclasses to generate a seed for a random number generator. */ + uint32_t PickSeed(); - /** Allocate a default random number generator based on your platform. */ - static RTPRandom *CreateDefaultRandomNumberGenerator(); + /** Allocate a default random number generator based on your platform. */ + static RTPRandom *CreateDefaultRandomNumberGenerator(); }; } // end namespace diff --git a/qrtplib/rtprandomrand48.cpp b/qrtplib/rtprandomrand48.cpp index d14bed196..8e634f939 100644 --- a/qrtplib/rtprandomrand48.cpp +++ b/qrtplib/rtprandomrand48.cpp @@ -1,50 +1,49 @@ /* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtprandomrand48.h" +#include namespace qrtplib { RTPRandomRand48::RTPRandomRand48() { - SetSeed(PickSeed()); + SetSeed(PickSeed()); } RTPRandomRand48::RTPRandomRand48(uint32_t seed) { - SetSeed(seed); + SetSeed(seed); } RTPRandomRand48::~RTPRandomRand48() @@ -53,36 +52,40 @@ RTPRandomRand48::~RTPRandomRand48() void RTPRandomRand48::SetSeed(uint32_t seed) { - state = ((uint64_t)seed) << 16 | 0x330EULL; + state = ((uint64_t) seed) << 16 | 0x330EULL; } uint8_t RTPRandomRand48::GetRandom8() { - uint32_t x = ((GetRandom32() >> 24)&0xff); + uint32_t x = ((GetRandom32() >> 24) & 0xff); - return (uint8_t)x; + return (uint8_t) x; } uint16_t RTPRandomRand48::GetRandom16() { - uint32_t x = ((GetRandom32() >> 16)&0xffff); + uint32_t x = ((GetRandom32() >> 16) & 0xffff); - return (uint16_t)x; + return (uint16_t) x; } uint32_t RTPRandomRand48::GetRandom32() { - state = ((0x5DEECE66DULL*state) + 0xBULL)&0x0000ffffffffffffULL; - uint32_t x = (uint32_t)((state>>16)&0xffffffffULL); - return x; + state = ((0x5DEECE66DULL * state) + 0xBULL) & 0x0000ffffffffffffULL; + uint32_t x = (uint32_t) ((state >> 16) & 0xffffffffULL); + qDebug("RTPRandomRand48::GetRandom32: %u", x); + + return x; } double RTPRandomRand48::GetRandomDouble() { - state = ((0x5DEECE66DULL*state) + 0xBULL)&0x0000ffffffffffffULL; - int64_t x = (int64_t)state; - double y = 3.552713678800500929355621337890625e-15 * (double)x; - return y; + + state = ((0x5DEECE66DULL * state) + 0xBULL) & 0x0000ffffffffffffULL; + int64_t x = (int64_t) state; + + double y = 3.552713678800500929355621337890625e-15 * (double) x; + return y; } } // end namespace diff --git a/qrtplib/rtprandomrand48.h b/qrtplib/rtprandomrand48.h index 10b36a7e1..a4282166a 100644 --- a/qrtplib/rtprandomrand48.h +++ b/qrtplib/rtprandomrand48.h @@ -1,36 +1,34 @@ /* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtprandomrand48.h @@ -40,29 +38,31 @@ #define RTPRANDOMRAND48_H -//#include "rtpconfig.h" +#include "rtpconfig.h" #include "rtprandom.h" #include -#include + +#include "export.h" namespace qrtplib { /** A random number generator using the algorithm of the rand48 set of functions. */ -class RTPRandomRand48 : public RTPRandom +class QRTPLIB_API RTPRandomRand48: public RTPRandom { public: - RTPRandomRand48(); - RTPRandomRand48(uint32_t seed); - ~RTPRandomRand48(); + RTPRandomRand48(); + RTPRandomRand48(uint32_t seed); + ~RTPRandomRand48(); - uint8_t GetRandom8(); - uint16_t GetRandom16(); - uint32_t GetRandom32(); - double GetRandomDouble(); + uint8_t GetRandom8(); + uint16_t GetRandom16(); + uint32_t GetRandom32(); + double GetRandomDouble(); private: - void SetSeed(uint32_t seed); - uint64_t state; + void SetSeed(uint32_t seed); + + uint64_t state; }; } // end namespace diff --git a/qrtplib/rtprandomrands.cpp b/qrtplib/rtprandomrands.cpp new file mode 100644 index 000000000..b81749dc5 --- /dev/null +++ b/qrtplib/rtprandomrands.cpp @@ -0,0 +1,197 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +#include "rtpconfig.h" +#ifdef RTP_HAVE_RAND_S +#define _CRT_RAND_S +#endif // RTP_HAVE_RAND_S + +#include "rtprandomrands.h" +#include "rtperrors.h" +#include +#include + +// If compiling on VC 8 or later for full Windows, we'll attempt to use rand_s, +// which generates better random numbers. However, its only supported on Windows XP, +// Windows Server 2003, and later, so we'll do a run-time check to see if we can +// use it (it won't work on Windows 2000 SP4 for example). + +namespace qrtplib +{ + +#ifndef RTP_HAVE_RAND_S + +RTPRandomRandS::RTPRandomRandS() +{ + initialized = false; +} + +RTPRandomRandS::~RTPRandomRandS() +{ +} + +int RTPRandomRandS::Init() +{ + return ERR_RTP_RTPRANDOMRANDS_NOTSUPPORTED; +} + +uint8_t RTPRandomRandS::GetRandom8() +{ + return 0; +} + +uint16_t RTPRandomRandS::GetRandom16() +{ + return 0; +} + +uint32_t RTPRandomRandS::GetRandom32() +{ + return 0; +} + +double RTPRandomRandS::GetRandomDouble() +{ + return 0; +} + +#else + +RTPRandomRandS::RTPRandomRandS() +{ + initialized = false; +} + +RTPRandomRandS::~RTPRandomRandS() +{ +} + +int RTPRandomRandS::Init() +{ + if (initialized) // doesn't matter then + return 0; + + HMODULE hAdvApi32 = LoadLibrary(TEXT("ADVAPI32.DLL")); + if(hAdvApi32 != NULL) + { + if(NULL != GetProcAddress( hAdvApi32, "SystemFunction036" )) // RtlGenRandom + { + initialized = true; + } + FreeLibrary(hAdvApi32); + hAdvApi32 = NULL; + } + + if (!initialized) + return ERR_RTP_RTPRANDOMRANDS_NOTSUPPORTED; + return 0; +} + +// rand_s gives a number between 0 and UINT_MAX. We'll assume that UINT_MAX is at least 8 bits + +uint8_t RTPRandomRandS::GetRandom8() +{ + if (!initialized) + return 0; + + unsigned int r; + + rand_s(&r); + + return (uint8_t)(r&0xff); +} + +uint16_t RTPRandomRandS::GetRandom16() +{ + if (!initialized) + return 0; + + unsigned int r; + int shift = 0; + uint16_t x = 0; + + for (int i = 0; i < 2; i++, shift += 8) + { + rand_s(&r); + + x ^= ((uint16_t)r << shift); + } + + return x; +} + +uint32_t RTPRandomRandS::GetRandom32() +{ + if (!initialized) + return 0; + + unsigned int r; + int shift = 0; + uint32_t x = 0; + + for (int i = 0; i < 4; i++, shift += 8) + { + rand_s(&r); + + x ^= ((uint32_t)r << shift); + } + + return x; +} + +double RTPRandomRandS::GetRandomDouble() +{ + if (!initialized) + return 0; + + unsigned int r; + int shift = 0; + uint64_t x = 0; + + for (int i = 0; i < 8; i++, shift += 8) + { + rand_s(&r); + + x ^= ((uint64_t)r << shift); + } + + x &= 0x7fffffffffffffffULL; + + int64_t x2 = (int64_t)x; + return RTPRANDOM_2POWMIN63*(double)x2; +} + +#endif // RTP_HAVE_RAND_S + +} + // end namespace + diff --git a/qrtplib/rtprandomrands.h b/qrtplib/rtprandomrands.h new file mode 100644 index 000000000..f5a0cf377 --- /dev/null +++ b/qrtplib/rtprandomrands.h @@ -0,0 +1,72 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +/** + * \file rtprandomrands.h + */ + +#ifndef RTPRANDOMRANDS_H + +#define RTPRANDOMRANDS_H + +#include "rtpconfig.h" +#include "rtprandom.h" + +#include "export.h" + +namespace qrtplib +{ + +/** A random number generator which tries to use the \c rand_s function on the + * Win32 platform. + */ +class QRTPLIB_API RTPRandomRandS: public RTPRandom +{ +public: + RTPRandomRandS(); + ~RTPRandomRandS(); + + /** Initialize the random number generator. */ + int Init(); + + uint8_t GetRandom8(); + uint16_t GetRandom16(); + uint32_t GetRandom32(); + double GetRandomDouble(); +private: + bool initialized; +}; + +} // end namespace + +#endif // RTPRANDOMRANDS_H + diff --git a/qrtplib/rtprandomurandom.cpp b/qrtplib/rtprandomurandom.cpp index ac3efa12f..6fbb9a291 100644 --- a/qrtplib/rtprandomurandom.cpp +++ b/qrtplib/rtprandomurandom.cpp @@ -1,130 +1,148 @@ /* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ #include "rtprandomurandom.h" #include "rtperrors.h" - -//#include "rtpdebug.h" +#include +#include namespace qrtplib { RTPRandomURandom::RTPRandomURandom() { - device = 0; + device = 0; } RTPRandomURandom::~RTPRandomURandom() { - if (device) - fclose(device); + if (device) { + fclose(device); + } } int RTPRandomURandom::Init() { - if (device) - return ERR_RTP_RTPRANDOMURANDOM_ALREADYOPEN; + if (device) { + return ERR_RTP_RTPRANDOMURANDOM_ALREADYOPEN; + } - device = fopen("/dev/urandom","rb"); - if (device == 0) - return ERR_RTP_RTPRANDOMURANDOM_CANTOPEN; + device = fopen("/dev/urandom", "rb"); - return 0; + if (device == 0) { + return ERR_RTP_RTPRANDOMURANDOM_CANTOPEN; + } + + return 0; } uint8_t RTPRandomURandom::GetRandom8() { - if (!device) - return 0; + if (!device) + { + qWarning("RTPRandomURandom::GetRandom8: no device"); + return 0; + } - uint8_t value; + uint8_t value; - if (fread(&value, sizeof(uint8_t), 1, device) == sizeof(uint8_t)) { - return value; - } else { - return 0; - } + if (fread(&value, 1, sizeof(uint8_t), device) != sizeof(uint8_t)) + { + qWarning("RTPRandomURandom::GetRandom8: cannot read unsigned 8 bit value from device"); + return 0; + } + + return value; } uint16_t RTPRandomURandom::GetRandom16() { - if (!device) - return 0; - - uint16_t value; - - if (fread(&value, sizeof(uint16_t), 1, device) == sizeof(uint16_t)) { - return value; - } else { + if (!device) + { + qWarning("RTPRandomURandom::GetRandom16: no device"); return 0; } + + uint16_t value; + + if (fread(&value, 1, sizeof(uint16_t), device) != sizeof(uint16_t)) + { + qWarning("RTPRandomURandom::GetRandom16: cannot read unsigned 16 bit value from device"); + return 0; + } + + return value; } uint32_t RTPRandomURandom::GetRandom32() { - if (!device) - return 0; - - uint32_t value; - - if (fread(&value, sizeof(uint32_t), 1, device) == sizeof(uint32_t)) { - return value; - } else { + if (!device) + { + qWarning("RTPRandomURandom::GetRandom32: no device"); return 0; } - return value; + uint32_t value; + + if (fread(&value, 1, sizeof(uint32_t), device) != sizeof(uint32_t)) + { + qWarning("RTPRandomURandom::GetRandom32: cannot read unsigned 32 bit value from device"); + return 0; + } + + return value; } double RTPRandomURandom::GetRandomDouble() { - if (!device) - return 0; - - uint64_t value; - - if (fread(&value, sizeof(uint64_t), 1, device) == sizeof(uint64_t)) - { - value &= 0x7fffffffffffffffULL; - int64_t value2 = (int64_t)value; - double x = RTPRANDOM_2POWMIN63*(double)value2; - return x; - } - else + if (!device) { + qWarning("RTPRandomURandom::GetRandomDouble: no device"); return 0; } + + uint64_t value; + + if (fread(&value, 1, sizeof(uint64_t), device) != sizeof(uint64_t)) + { + qWarning("RTPRandomURandom::GetRandomDouble: cannot read unsigned 64 bit value from device"); + return 0; + } + + value &= 0x7fffffffffffffffULL; + int64_t value2 = (int64_t) value; + double x = RTPRANDOM_2POWMIN63 * (double) value2; + + return x; } } // end namespace diff --git a/qrtplib/rtprandomurandom.h b/qrtplib/rtprandomurandom.h index 160820a94..67b2d1647 100644 --- a/qrtplib/rtprandomurandom.h +++ b/qrtplib/rtprandomurandom.h @@ -1,36 +1,34 @@ /* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtprandomurandom.h @@ -40,29 +38,31 @@ #define RTPRANDOMURANDOM_H -//#include "rtpconfig.h" +#include "rtpconfig.h" #include "rtprandom.h" #include +#include "export.h" + namespace qrtplib { /** A random number generator which uses bytes delivered by the /dev/urandom device. */ -class RTPRandomURandom : public RTPRandom +class QRTPLIB_API RTPRandomURandom: public RTPRandom { public: - RTPRandomURandom(); - ~RTPRandomURandom(); + RTPRandomURandom(); + ~RTPRandomURandom(); - /** Initialize the random number generator. */ - int Init(); + /** Initialize the random number generator. */ + int Init(); - uint8_t GetRandom8(); - uint16_t GetRandom16(); - uint32_t GetRandom32(); - double GetRandomDouble(); + uint8_t GetRandom8(); + uint16_t GetRandom16(); + uint32_t GetRandom32(); + double GetRandomDouble(); private: - FILE *device; + FILE *device; }; } // end namespace diff --git a/qrtplib/rtprawpacket.h b/qrtplib/rtprawpacket.h new file mode 100644 index 000000000..df00af0a6 --- /dev/null +++ b/qrtplib/rtprawpacket.h @@ -0,0 +1,153 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +/** + * \file rtprawpacket.h + */ + +#ifndef RTPRAWPACKET_H + +#define RTPRAWPACKET_H + +#include "rtpconfig.h" +#include "rtptimeutilities.h" +#include "rtpaddress.h" +#include "rtptypes.h" + +namespace qrtplib +{ + +/** This class is used by the transmission component to store the incoming RTP and RTCP data in. */ +class RTPRawPacket +{ +public: + /** Creates an instance which stores data from \c data with length \c datalen. + * Creates an instance which stores data from \c data with length \c datalen. Only the pointer + * to the data is stored, no actual copy is made! The address from which this packet originated + * is set to \c address and the time at which the packet was received is set to \c recvtime. + * The flag which indicates whether this data is RTP or RTCP data is set to \c rtp. A memory + * manager can be installed as well. + */ + RTPRawPacket(const uint8_t *data, std::size_t datalen, const RTPAddress& address, RTPTime &recvtime, bool rtp); + ~RTPRawPacket(); + + /** Returns the pointer to the data which is contained in this packet. */ + uint8_t *GetData() + { + return packetdata; + } + + /** Returns the length of the packet described by this instance. */ + std::size_t GetDataLength() const + { + return packetdatalength; + } + + /** Returns the time at which this packet was received. */ + RTPTime GetReceiveTime() const + { + return receivetime; + } + + /** Returns the address stored in this packet. */ + const RTPAddress& GetSenderAddress() const + { + return senderaddress; + } + + /** Returns \c true if this data is RTP data, \c false if it is RTCP data. */ + bool IsRTP() const + { + return isrtp; + } + + /** Deallocates the previously stored data and replaces it with the data that's + * specified, can be useful when e.g. decrypting data in RTPSession::OnChangeIncomingData */ + void SetData(const uint8_t *data, std::size_t datalen); + + /** Deallocates the currently stored RTPAddress instance and replaces it + * with the one that's specified (you probably don't need this function). */ + void SetSenderAddress(const RTPAddress& address); +private: + void DeleteData(); + + uint8_t *packetdata; + std::size_t packetdatalength; + RTPTime receivetime; + RTPAddress senderaddress; + bool isrtp; +}; + +inline RTPRawPacket::RTPRawPacket(const uint8_t *data, std::size_t datalen, const RTPAddress& address, RTPTime &recvtime, bool rtp) : + receivetime(recvtime) +{ + packetdata = new uint8_t[datalen]; + memcpy(packetdata, data, datalen); + packetdatalength = datalen; + senderaddress = address; + isrtp = rtp; +} + +inline RTPRawPacket::~RTPRawPacket() +{ + DeleteData(); +} + +inline void RTPRawPacket::DeleteData() +{ + if (packetdata) + { + delete[] packetdata; + packetdata = 0; + } +} + +inline void RTPRawPacket::SetData(const uint8_t *data, std::size_t datalen) +{ + if (packetdata) { + delete[] packetdata; + } + + packetdata = new uint8_t[datalen]; + memcpy(packetdata, data, datalen); + packetdatalength = datalen; +} + +inline void RTPRawPacket::SetSenderAddress(const RTPAddress& address) +{ + senderaddress = address; +} + +} // end namespace + +#endif // RTPRAWPACKET_H + diff --git a/qrtplib/rtpsession.cpp b/qrtplib/rtpsession.cpp new file mode 100644 index 000000000..a9d42a8d2 --- /dev/null +++ b/qrtplib/rtpsession.cpp @@ -0,0 +1,1232 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +#include "rtpsession.h" +#include "rtperrors.h" +// TODO: this is for Create with transmitter creation. See if we keep it. +//#include "rtpudpv4transmitter.h" +//#include "rtptcptransmitter.h" +//#include "rtpexternaltransmitter.h" +#include "rtpsessionparams.h" +#include "rtpdefines.h" +#include "rtprawpacket.h" +#include "rtppacket.h" +#include "rtptimeutilities.h" +#include "rtprandomrand48.h" +#include "rtprandomrands.h" +#include "rtprandomurandom.h" +#ifdef RTP_SUPPORT_SENDAPP +#include "rtcpcompoundpacket.h" +#endif // RTP_SUPPORT_SENDAPP +#include "rtpinternalutils.h" + +#include +#include + +#include + + +namespace qrtplib +{ + +RTPSession::RTPSession(RTPRandom *r) : + rtprnd(GetRandomNumberGenerator(r)), + sources(*this), + packetbuilder(*rtprnd), + rtcpsched(sources, *rtprnd), + rtcpbuilder(sources, packetbuilder) +{ + // We're not going to set these flags in Create, so that the constructor of a derived class + // can already change them + m_changeIncomingData = false; + m_changeOutgoingData = false; + + created = false; + timeinit.Dummy(); + + //std::cout << (void *)(rtprnd) << std::endl; +} + +RTPSession::~RTPSession() +{ + Destroy(); + + if (deletertprnd) + delete rtprnd; +} + +//int RTPSession::Create(const RTPSessionParams &sessparams, const RTPTransmissionParams *transparams /* = 0 */, RTPTransmitter::TransmissionProtocol protocol) +//{ +// int status; +// +// if (created) +// return ERR_RTP_SESSION_ALREADYCREATED; +// +// usingpollthread = sessparams.IsUsingPollThread(); +// +// useSR_BYEifpossible = sessparams.GetSenderReportForBYE(); +// sentpackets = false; +// +// // Check max packet size +// +// if ((maxpacksize = sessparams.GetMaximumPacketSize()) < RTP_MINPACKETSIZE) +// return ERR_RTP_SESSION_MAXPACKETSIZETOOSMALL; +// +// // Initialize the transmission component +// +// rtptrans = 0; +// switch (protocol) +// { + // TODO: see if we keep this Create method or use the one with the transmitter specified +// case RTPTransmitter::IPv4UDPProto: +// rtptrans = new RTPUDPv4Transmitter(); +// break; +// case RTPTransmitter::ExternalProto: +// rtptrans = new RTPExternalTransmitter(); +// break; +// case RTPTransmitter::UserDefinedProto: +// rtptrans = NewUserDefinedTransmitter(); +// if (rtptrans == 0) +// return ERR_RTP_SESSION_USERDEFINEDTRANSMITTERNULL; +// break; +// case RTPTransmitter::TCPProto: +// rtptrans = new RTPTCPTransmitter(); +// break; + +// default: +// return ERR_RTP_SESSION_UNSUPPORTEDTRANSMISSIONPROTOCOL; +// } +// +// if (rtptrans == 0) +// return ERR_RTP_OUTOFMEM; +// if ((status = rtptrans->Init()) < 0) +// { +// delete rtptrans; +// return status; +// } +// if ((status = rtptrans->Create(maxpacksize, transparams)) < 0) +// { +// delete rtptrans; +// return status; +// } +// +// deletetransmitter = true; +// return InternalCreate(sessparams); +//} + +int RTPSession::Create(const RTPSessionParams &sessparams, RTPTransmitter *transmitter) +{ + int status; + + if (created) + return ERR_RTP_SESSION_ALREADYCREATED; + + usingpollthread = sessparams.IsUsingPollThread(); + + useSR_BYEifpossible = sessparams.GetSenderReportForBYE(); + sentpackets = false; + + // Check max packet size + + if ((maxpacksize = sessparams.GetMaximumPacketSize()) < RTP_MINPACKETSIZE) + return ERR_RTP_SESSION_MAXPACKETSIZETOOSMALL; + + rtptrans = transmitter; + + if ((status = rtptrans->SetMaximumPacketSize(maxpacksize)) < 0) + return status; + + deletetransmitter = false; + return InternalCreate(sessparams); +} + +int RTPSession::InternalCreate(const RTPSessionParams &sessparams) +{ + int status; + + // Initialize packet builder + + if ((status = packetbuilder.Init(maxpacksize)) < 0) + { + if (deletetransmitter) + delete rtptrans; + return status; + } + + if (sessparams.GetUsePredefinedSSRC()) + packetbuilder.AdjustSSRC(sessparams.GetPredefinedSSRC()); + + // Add our own ssrc to the source table + + if ((status = sources.CreateOwnSSRC(packetbuilder.GetSSRC())) < 0) + { + packetbuilder.Destroy(); + if (deletetransmitter) + delete rtptrans; + return status; + } + + // Set the initial receive mode + + if ((status = rtptrans->SetReceiveMode(sessparams.GetReceiveMode())) < 0) + { + packetbuilder.Destroy(); + sources.Clear(); + if (deletetransmitter) + delete rtptrans; + return status; + } + + // Init the RTCP packet builder + + double timestampunit = sessparams.GetOwnTimestampUnit(); + uint8_t buf[1024] = {0}; + std::size_t buflen = 1024; + std::string forcedcname = sessparams.GetCNAME(); + + if (forcedcname.length() == 0) + { + if ((status = CreateCNAME(buf, &buflen, sessparams.GetResolveLocalHostname())) < 0) + { + packetbuilder.Destroy(); + sources.Clear(); + if (deletetransmitter) + delete rtptrans; + return status; + } + } + else + { + strncpy((char * )buf, forcedcname.c_str(), buflen); + buf[buflen - 1] = 0; + buflen = strlen((char *) buf); + } + + if ((status = rtcpbuilder.Init(maxpacksize, timestampunit, buf, buflen)) < 0) + { + packetbuilder.Destroy(); + sources.Clear(); + if (deletetransmitter) + delete rtptrans; + return status; + } + + // Set scheduler parameters + + rtcpsched.Reset(); + rtcpsched.SetHeaderOverhead(rtptrans->GetHeaderOverhead()); + + RTCPSchedulerParams schedparams; + + sessionbandwidth = sessparams.GetSessionBandwidth(); + controlfragment = sessparams.GetControlTrafficFraction(); + + if ((status = schedparams.SetRTCPBandwidth(sessionbandwidth * controlfragment)) < 0) + { + if (deletetransmitter) + delete rtptrans; + packetbuilder.Destroy(); + sources.Clear(); + rtcpbuilder.Destroy(); + return status; + } + if ((status = schedparams.SetSenderBandwidthFraction(sessparams.GetSenderControlBandwidthFraction())) < 0) + { + if (deletetransmitter) + delete rtptrans; + packetbuilder.Destroy(); + sources.Clear(); + rtcpbuilder.Destroy(); + return status; + } + if ((status = schedparams.SetMinimumTransmissionInterval(sessparams.GetMinimumRTCPTransmissionInterval())) < 0) + { + if (deletetransmitter) + delete rtptrans; + packetbuilder.Destroy(); + sources.Clear(); + rtcpbuilder.Destroy(); + return status; + } + schedparams.SetUseHalfAtStartup(sessparams.GetUseHalfRTCPIntervalAtStartup()); + schedparams.SetRequestImmediateBYE(sessparams.GetRequestImmediateBYE()); + + rtcpsched.SetParameters(schedparams); + + // copy other parameters + + acceptownpackets = sessparams.AcceptOwnPackets(); + membermultiplier = sessparams.GetSourceTimeoutMultiplier(); + sendermultiplier = sessparams.GetSenderTimeoutMultiplier(); + byemultiplier = sessparams.GetBYETimeoutMultiplier(); + collisionmultiplier = sessparams.GetCollisionTimeoutMultiplier(); + notemultiplier = sessparams.GetNoteTimeoutMultiplier(); + + // Do thread stuff if necessary + + created = true; + return 0; +} + +void RTPSession::Destroy() +{ + if (!created) + return; + + if (deletetransmitter) + delete rtptrans; + packetbuilder.Destroy(); + rtcpbuilder.Destroy(); + rtcpsched.Reset(); + collisionlist.Clear(); + sources.Clear(); + + std::list::const_iterator it; + + for (it = byepackets.begin(); it != byepackets.end(); it++) + delete *it; + byepackets.clear(); + + created = false; +} + +void RTPSession::BYEDestroy(const RTPTime &maxwaittime, const void *reason, std::size_t reasonlength) +{ + if (!created) + return; + + // first, stop the thread so we have full control over all components + + RTPTime stoptime = RTPTime::CurrentTime(); + stoptime += maxwaittime; + + // add bye packet to the list if we've sent data + + RTCPCompoundPacket *pack; + + if (sentpackets) + { + int status; + + reasonlength = (reasonlength > RTCP_BYE_MAXREASONLENGTH) ? RTCP_BYE_MAXREASONLENGTH : reasonlength; + status = rtcpbuilder.BuildBYEPacket(&pack, reason, reasonlength, useSR_BYEifpossible); + if (status >= 0) + { + byepackets.push_back(pack); + + if (byepackets.size() == 1) + rtcpsched.ScheduleBYEPacket(pack->GetCompoundPacketLength()); + } + } + + if (!byepackets.empty()) + { + bool done = false; + + while (!done) + { + RTPTime curtime = RTPTime::CurrentTime(); + + if (curtime >= stoptime) + done = true; + + if (rtcpsched.IsTime()) + { + pack = *(byepackets.begin()); + byepackets.pop_front(); + + SendRTCPData(pack->GetCompoundPacketData(), pack->GetCompoundPacketLength()); + + OnSendRTCPCompoundPacket(pack); // we'll place this after the actual send to avoid tampering + + delete pack; + if (!byepackets.empty()) // more bye packets to send, schedule them + rtcpsched.ScheduleBYEPacket((*(byepackets.begin()))->GetCompoundPacketLength()); + else + done = true; + } + if (!done) + RTPTime::Wait(RTPTime(0, 100000)); + } + } + + if (deletetransmitter) + delete rtptrans; + packetbuilder.Destroy(); + rtcpbuilder.Destroy(); + rtcpsched.Reset(); + collisionlist.Clear(); + sources.Clear(); + + // clear rest of bye packets + std::list::const_iterator it; + + for (it = byepackets.begin(); it != byepackets.end(); it++) + delete *it; + byepackets.clear(); + + created = false; +} + +bool RTPSession::IsActive() +{ + return created; +} + +uint32_t RTPSession::GetLocalSSRC() +{ + if (!created) + return 0; + + uint32_t ssrc; + + ssrc = packetbuilder.GetSSRC(); + return ssrc; +} + +int RTPSession::AddDestination(const RTPAddress &addr) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + return rtptrans->AddDestination(addr); +} + +int RTPSession::DeleteDestination(const RTPAddress &addr) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + return rtptrans->DeleteDestination(addr); +} + +void RTPSession::ClearDestinations() +{ + if (!created) + return; + rtptrans->ClearDestinations(); +} + +bool RTPSession::SupportsMulticasting() +{ + if (!created) + return false; + return rtptrans->SupportsMulticasting(); +} + +int RTPSession::JoinMulticastGroup(const RTPAddress &addr) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + return rtptrans->JoinMulticastGroup(addr); +} + +int RTPSession::LeaveMulticastGroup(const RTPAddress &addr) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + return rtptrans->LeaveMulticastGroup(addr); +} + +int RTPSession::SendPacket(const void *data, std::size_t len) +{ + int status; + + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + if ((status = packetbuilder.BuildPacket(data, len)) < 0) + { + return status; + } + if ((status = SendRTPData(packetbuilder.GetPacket(), packetbuilder.GetPacketLength())) < 0) + { + return status; + } + + sources.SentRTPPacket(); + sentpackets = true; + return 0; +} + +int RTPSession::SendPacket(const void *data, std::size_t len, uint8_t pt, bool mark, uint32_t timestampinc) +{ + int status; + + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + if ((status = packetbuilder.BuildPacket(data, len, pt, mark, timestampinc)) < 0) + { + return status; + } + if ((status = SendRTPData(packetbuilder.GetPacket(), packetbuilder.GetPacketLength())) < 0) + { + return status; + } + + sources.SentRTPPacket(); + sentpackets = true; + return 0; +} + +int RTPSession::SendPacketEx(const void *data, std::size_t len, uint16_t hdrextID, const void *hdrextdata, std::size_t numhdrextwords) +{ + int status; + + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + if ((status = packetbuilder.BuildPacketEx(data, len, hdrextID, hdrextdata, numhdrextwords)) < 0) + { + return status; + } + if ((status = SendRTPData(packetbuilder.GetPacket(), packetbuilder.GetPacketLength())) < 0) + { + return status; + } + + sources.SentRTPPacket(); + sentpackets = true; + return 0; +} + +int RTPSession::SendPacketEx(const void *data, std::size_t len, uint8_t pt, bool mark, uint32_t timestampinc, uint16_t hdrextID, const void *hdrextdata, std::size_t numhdrextwords) +{ + int status; + + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + if ((status = packetbuilder.BuildPacketEx(data, len, pt, mark, timestampinc, hdrextID, hdrextdata, numhdrextwords)) < 0) + { + return status; + } + if ((status = SendRTPData(packetbuilder.GetPacket(), packetbuilder.GetPacketLength())) < 0) + { + return status; + } + + sources.SentRTPPacket(); + sentpackets = true; + return 0; +} + +#ifdef RTP_SUPPORT_SENDAPP + +int RTPSession::SendRTCPAPPPacket(uint8_t subtype, const uint8_t name[4], const void *appdata, std::size_t appdatalen) +{ + int status; + + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + uint32_t ssrc = packetbuilder.GetSSRC(); + + RTCPCompoundPacketBuilder pb; + + status = pb.InitBuild(maxpacksize); + + if (status < 0) + return status; + + //first packet in an rtcp compound packet should always be SR or RR + if ((status = pb.StartReceiverReport(ssrc)) < 0) + return status; + + //add SDES packet with CNAME item + if ((status = pb.AddSDESSource(ssrc)) < 0) + return status; + + std::size_t owncnamelen = 0; + uint8_t *owncname = rtcpbuilder.GetLocalCNAME(&owncnamelen); + + if ((status = pb.AddSDESNormalItem(RTCPSDESPacket::CNAME, owncname, owncnamelen)) < 0) + { + return status; + } + + //add our application specific packet + if ((status = pb.AddAPPPacket(subtype, ssrc, name, appdata, appdatalen)) < 0) + return status; + + if ((status = pb.EndBuild()) < 0) + return status; + + //send packet + status = SendRTCPData(pb.GetCompoundPacketData(), pb.GetCompoundPacketLength()); + if (status < 0) + return status; + + sentpackets = true; + + return pb.GetCompoundPacketLength(); +} + +#endif // RTP_SUPPORT_SENDAPP + +int RTPSession::SendRawData(const void *data, std::size_t len, bool usertpchannel) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + + if (usertpchannel) + status = rtptrans->SendRTPData(data, len); + else + status = rtptrans->SendRTCPData(data, len); + return status; +} + +int RTPSession::SetDefaultPayloadType(uint8_t pt) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + + status = packetbuilder.SetDefaultPayloadType(pt); + return status; +} + +int RTPSession::SetDefaultMark(bool m) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + + status = packetbuilder.SetDefaultMark(m); + return status; +} + +int RTPSession::SetDefaultTimestampIncrement(uint32_t timestampinc) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + + status = packetbuilder.SetDefaultTimestampIncrement(timestampinc); + return status; +} + +int RTPSession::IncrementTimestamp(uint32_t inc) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + + status = packetbuilder.IncrementTimestamp(inc); + return status; +} + +int RTPSession::IncrementTimestampDefault() +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + + status = packetbuilder.IncrementTimestampDefault(); + return status; +} + +int RTPSession::SetPreTransmissionDelay(const RTPTime &delay) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + + status = rtcpbuilder.SetPreTransmissionDelay(delay); + return status; +} + +RTPTransmissionInfo *RTPSession::GetTransmissionInfo() +{ + if (!created) + return 0; + return rtptrans->GetTransmissionInfo(); +} + +void RTPSession::DeleteTransmissionInfo(RTPTransmissionInfo *inf) +{ + if (!created) + return; + rtptrans->DeleteTransmissionInfo(inf); +} + +RTPTime RTPSession::GetRTCPDelay() +{ + if (!created) + return RTPTime(0, 0); + if (usingpollthread) + return RTPTime(0, 0); + + RTPTime t = rtcpsched.GetTransmissionDelay(); + return t; +} + +int RTPSession::BeginDataAccess() +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + return 0; +} + +bool RTPSession::GotoFirstSource() +{ + if (!created) + return false; + return sources.GotoFirstSource(); +} + +bool RTPSession::GotoNextSource() +{ + if (!created) + return false; + return sources.GotoNextSource(); +} + +bool RTPSession::GotoPreviousSource() +{ + if (!created) + return false; + return sources.GotoPreviousSource(); +} + +bool RTPSession::GotoFirstSourceWithData() +{ + if (!created) + return false; + return sources.GotoFirstSourceWithData(); +} + +bool RTPSession::GotoNextSourceWithData() +{ + if (!created) + return false; + return sources.GotoNextSourceWithData(); +} + +bool RTPSession::GotoPreviousSourceWithData() +{ + if (!created) + return false; + return sources.GotoPreviousSourceWithData(); +} + +RTPSourceData *RTPSession::GetCurrentSourceInfo() +{ + if (!created) + return 0; + return sources.GetCurrentSourceInfo(); +} + +RTPSourceData *RTPSession::GetSourceInfo(uint32_t ssrc) +{ + if (!created) + return 0; + return sources.GetSourceInfo(ssrc); +} + +RTPPacket *RTPSession::GetNextPacket() +{ + if (!created) + return 0; + return sources.GetNextPacket(); +} + +uint16_t RTPSession::GetNextSequenceNumber() const +{ + return packetbuilder.GetSequenceNumber(); +} + +void RTPSession::DeletePacket(RTPPacket *p) +{ + delete p; +} + +int RTPSession::EndDataAccess() +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + return 0; +} + +int RTPSession::SetReceiveMode(RTPTransmitter::ReceiveMode m) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + return rtptrans->SetReceiveMode(m); +} + +int RTPSession::AddToIgnoreList(const RTPAddress &addr) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + return rtptrans->AddToIgnoreList(addr); +} + +int RTPSession::DeleteFromIgnoreList(const RTPAddress &addr) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + return rtptrans->DeleteFromIgnoreList(addr); +} + +void RTPSession::ClearIgnoreList() +{ + if (!created) + return; + rtptrans->ClearIgnoreList(); +} + +int RTPSession::AddToAcceptList(const RTPAddress &addr) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + return rtptrans->AddToAcceptList(addr); +} + +int RTPSession::DeleteFromAcceptList(const RTPAddress &addr) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + return rtptrans->DeleteFromAcceptList(addr); +} + +void RTPSession::ClearAcceptList() +{ + if (!created) + return; + rtptrans->ClearAcceptList(); +} + +int RTPSession::SetMaximumPacketSize(std::size_t s) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + if (s < RTP_MINPACKETSIZE) + return ERR_RTP_SESSION_MAXPACKETSIZETOOSMALL; + + int status; + + if ((status = rtptrans->SetMaximumPacketSize(s)) < 0) + return status; + + if ((status = packetbuilder.SetMaximumPacketSize(s)) < 0) + { + // restore previous max packet size + rtptrans->SetMaximumPacketSize(maxpacksize); + return status; + } + if ((status = rtcpbuilder.SetMaximumPacketSize(s)) < 0) + { + // restore previous max packet size + packetbuilder.SetMaximumPacketSize(maxpacksize); + rtptrans->SetMaximumPacketSize(maxpacksize); + return status; + } + maxpacksize = s; + return 0; +} + +int RTPSession::SetSessionBandwidth(double bw) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + RTCPSchedulerParams p = rtcpsched.GetParameters(); + status = p.SetRTCPBandwidth(bw * controlfragment); + if (status >= 0) + { + rtcpsched.SetParameters(p); + sessionbandwidth = bw; + } + return status; +} + +int RTPSession::SetTimestampUnit(double u) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + + status = rtcpbuilder.SetTimestampUnit(u); + return status; +} + +void RTPSession::SetNameInterval(int count) +{ + if (!created) + return; + rtcpbuilder.SetNameInterval(count); +} + +void RTPSession::SetEMailInterval(int count) +{ + if (!created) + return; + rtcpbuilder.SetEMailInterval(count); +} + +void RTPSession::SetLocationInterval(int count) +{ + if (!created) + return; + rtcpbuilder.SetLocationInterval(count); +} + +void RTPSession::SetPhoneInterval(int count) +{ + if (!created) + return; + rtcpbuilder.SetPhoneInterval(count); +} + +void RTPSession::SetToolInterval(int count) +{ + if (!created) + return; + rtcpbuilder.SetToolInterval(count); +} + +void RTPSession::SetNoteInterval(int count) +{ + if (!created) + return; + rtcpbuilder.SetNoteInterval(count); +} + +int RTPSession::SetLocalName(const void *s, std::size_t len) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + status = rtcpbuilder.SetLocalName(s, len); + return status; +} + +int RTPSession::SetLocalEMail(const void *s, std::size_t len) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + status = rtcpbuilder.SetLocalEMail(s, len); + return status; +} + +int RTPSession::SetLocalLocation(const void *s, std::size_t len) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + status = rtcpbuilder.SetLocalLocation(s, len); + return status; +} + +int RTPSession::SetLocalPhone(const void *s, std::size_t len) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + status = rtcpbuilder.SetLocalPhone(s, len); + return status; +} + +int RTPSession::SetLocalTool(const void *s, std::size_t len) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + status = rtcpbuilder.SetLocalTool(s, len); + return status; +} + +int RTPSession::SetLocalNote(const void *s, std::size_t len) +{ + if (!created) + return ERR_RTP_SESSION_NOTCREATED; + + int status; + status = rtcpbuilder.SetLocalNote(s, len); + return status; +} + +int RTPSession::ProcessPolledData() +{ + RTPRawPacket *rawpack; + int status; + + while ((rawpack = rtptrans->GetNextPacket()) != 0) + { + if (m_changeIncomingData) + { + // Provide a way to change incoming data, for decryption for example + if (!OnChangeIncomingData(rawpack)) + { + delete rawpack; + continue; + } + } + + sources.ClearOwnCollisionFlag(); + + // since our sources instance also uses the scheduler (analysis of incoming packets) + // we'll lock it + if ((status = sources.ProcessRawPacket(rawpack, rtptrans, acceptownpackets)) < 0) + { + delete rawpack; + return status; + } + + if (sources.DetectedOwnCollision()) // collision handling! + { + bool created; + + if ((status = collisionlist.UpdateAddress(&rawpack->GetSenderAddress(), rawpack->GetReceiveTime(), &created)) < 0) + { + delete rawpack; + return status; + } + + if (created) // first time we've encountered this address, send bye packet and + { // change our own SSRC + bool hassentpackets = sentpackets; + + if (hassentpackets) + { + // Only send BYE packet if we've actually sent data using this + // SSRC + + RTCPCompoundPacket *rtcpcomppack; + + if ((status = rtcpbuilder.BuildBYEPacket(&rtcpcomppack, 0, 0, useSR_BYEifpossible)) < 0) + { + delete rawpack; + return status; + } + + byepackets.push_back(rtcpcomppack); + if (byepackets.size() == 1) // was the first packet, schedule a BYE packet (otherwise there's already one scheduled) + { + rtcpsched.ScheduleBYEPacket(rtcpcomppack->GetCompoundPacketLength()); + } + } + // bye packet is built and scheduled, now change our SSRC + // and reset the packet count in the transmitter + + uint32_t newssrc = packetbuilder.CreateNewSSRC(sources); + + sentpackets = false; + + // remove old entry in source table and add new one + + if ((status = sources.DeleteOwnSSRC()) < 0) + { + delete rawpack; + return status; + } + if ((status = sources.CreateOwnSSRC(newssrc)) < 0) + { + delete rawpack; + return status; + } + } + } + delete rawpack; + } + + RTPTime d = rtcpsched.CalculateDeterministicInterval(false); + + RTPTime t = RTPTime::CurrentTime(); + double Td = d.GetDouble(); + RTPTime sendertimeout = RTPTime(Td * sendermultiplier); + RTPTime generaltimeout = RTPTime(Td * membermultiplier); + RTPTime byetimeout = RTPTime(Td * byemultiplier); + RTPTime colltimeout = RTPTime(Td * collisionmultiplier); + RTPTime notetimeout = RTPTime(Td * notemultiplier); + + sources.MultipleTimeouts(t, sendertimeout, byetimeout, generaltimeout, notetimeout); + collisionlist.Timeout(t, colltimeout); + + // We'll check if it's time for RTCP stuff + + bool istime = rtcpsched.IsTime(); + + if (istime) + { + RTCPCompoundPacket *pack; + + // we'll check if there's a bye packet to send, or just a normal packet + + if (byepackets.empty()) + { + if ((status = rtcpbuilder.BuildNextPacket(&pack)) < 0) + { + return status; + } + if ((status = SendRTCPData(pack->GetCompoundPacketData(), pack->GetCompoundPacketLength())) < 0) + { + delete pack; + return status; + } + + sentpackets = true; + + OnSendRTCPCompoundPacket(pack); // we'll place this after the actual send to avoid tampering + } + else + { + pack = *(byepackets.begin()); + byepackets.pop_front(); + + if ((status = SendRTCPData(pack->GetCompoundPacketData(), pack->GetCompoundPacketLength())) < 0) + { + delete pack; + return status; + } + + sentpackets = true; + + OnSendRTCPCompoundPacket(pack); // we'll place this after the actual send to avoid tampering + + if (!byepackets.empty()) // more bye packets to send, schedule them + { + rtcpsched.ScheduleBYEPacket((*(byepackets.begin()))->GetCompoundPacketLength()); + } + } + + rtcpsched.AnalyseOutgoing(*pack); + + delete pack; + } + return 0; +} + +int RTPSession::CreateCNAME(uint8_t *buffer, std::size_t *bufferlength, bool resolve __attribute__((unused))) +{ + buffer[*bufferlength - 1] = 0; + + std::size_t offset = strlen((const char *) buffer); + if (offset < (*bufferlength - 1)) + buffer[offset] = (uint8_t) '@'; + offset++; + + std::size_t buflen2 = *bufferlength - offset; + + QString hostnameStr = QHostInfo::localHostName(); + int hostnameSize = hostnameStr.size(); + + strncpy((char * )(buffer + offset), hostnameStr.toStdString().c_str(), buflen2); + *bufferlength = offset + hostnameSize; + + if (*bufferlength > RTCP_SDES_MAXITEMLENGTH) + *bufferlength = RTCP_SDES_MAXITEMLENGTH; + + return 0; +} + +RTPRandom *RTPSession::GetRandomNumberGenerator(RTPRandom *r) +{ + RTPRandom *rnew = 0; + + if (r == 0) + { + rnew = RTPRandom::CreateDefaultRandomNumberGenerator(); + deletertprnd = true; + } + else + { + rnew = r; + deletertprnd = false; + } + + return rnew; +} + +int RTPSession::SendRTPData(const void *data, std::size_t len) +{ + if (!m_changeOutgoingData) + return rtptrans->SendRTPData(data, len); + + void *pSendData = 0; + std::size_t sendLen = 0; + int status = 0; + + status = OnChangeRTPOrRTCPData(data, len, true, &pSendData, &sendLen); + if (status < 0) + return status; + + if (pSendData) + { + status = rtptrans->SendRTPData(pSendData, sendLen); + OnSentRTPOrRTCPData(pSendData, sendLen, true); + } + + return status; +} + +int RTPSession::SendRTCPData(const void *data, std::size_t len) +{ + if (!m_changeOutgoingData) + return rtptrans->SendRTCPData(data, len); + + void *pSendData = 0; + std::size_t sendLen = 0; + int status = 0; + + status = OnChangeRTPOrRTCPData(data, len, false, &pSendData, &sendLen); + if (status < 0) + return status; + + if (pSendData) + { + status = rtptrans->SendRTCPData(pSendData, sendLen); + OnSentRTPOrRTCPData(pSendData, sendLen, false); + } + + return status; +} + +} // end namespace + diff --git a/qrtplib/rtpsession.h b/qrtplib/rtpsession.h new file mode 100644 index 000000000..179609e8f --- /dev/null +++ b/qrtplib/rtpsession.h @@ -0,0 +1,664 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +/** + * \file rtpsession.h + */ + +#ifndef RTPSESSION_H + +#define RTPSESSION_H + +#include "rtpconfig.h" +#include "rtppacketbuilder.h" +#include "rtpsessionsources.h" +#include "rtptransmitter.h" +#include "rtpcollisionlist.h" +#include "rtcpscheduler.h" +#include "rtcppacketbuilder.h" +#include "rtptimeutilities.h" +#include "rtcpcompoundpacketbuilder.h" +#include + +#include "export.h" + +namespace qrtplib +{ + +class RTPTransmitter; +class RTPSessionParams; +class RTPTransmissionParams; +class RTPAddress; +class RTPSourceData; +class RTPPacket; +class RTPPollThread; +class RTPTransmissionInfo; +class RTCPCompoundPacket; +class RTCPPacket; +class RTCPAPPPacket; + +/** High level class for using RTP. + * For most RTP based applications, the RTPSession class will probably be the one to use. It handles + * the RTCP part completely internally, so the user can focus on sending and receiving the actual data. + * In case you want to use SRTP, you should create an RTPSecureSession derived class. + * \note The RTPSession class is not meant to be thread safe. The user should use some kind of locking + * mechanism to prevent different threads from using the same RTPSession instance. + */ +class QRTPLIB_API RTPSession +{ +public: + /** Constructs an RTPSession instance, optionally using a specific instance of a random + * number generator, and optionally installing a memory manager. + * Constructs an RTPSession instance, optionally using a specific instance of a random + * number generator, and optionally installing a memory manager. If no random number generator + * is specified, the RTPSession object will try to use either a RTPRandomURandom or + * RTPRandomRandS instance. If neither is available on the current platform, a RTPRandomRand48 + * instance will be used instead. By specifying a random number generator yourself, it is + * possible to use the same generator in several RTPSession instances. + */ + RTPSession(RTPRandom *rnd = 0); + virtual ~RTPSession(); + + /** Creates an RTP session. + * This function creates an RTP session with parameters \c sessparams, which will use a transmitter + * corresponding to \c proto. Parameters for this transmitter can be specified as well. If \c + * proto is of type RTPTransmitter::UserDefinedProto, the NewUserDefinedTransmitter function must + * be implemented. + */ + //int Create(const RTPSessionParams &sessparams, const RTPTransmissionParams *transparams = 0, RTPTransmitter::TransmissionProtocol proto = RTPTransmitter::IPv4UDPProto); + + /** Creates an RTP session using \c transmitter as transmission component. + * This function creates an RTP session with parameters \c sessparams, which will use the + * transmission component \c transmitter. Initialization and destruction of the transmitter + * will not be done by the RTPSession class if this Create function is used. This function + * can be useful if you which to reuse the transmission component in another RTPSession + * instance, once the original RTPSession isn't using the transmitter anymore. + */ + int Create(const RTPSessionParams &sessparams, RTPTransmitter *transmitter); + + /** Leaves the session without sending a BYE packet. */ + void Destroy(); + + /** Sends a BYE packet and leaves the session. + * Sends a BYE packet and leaves the session. At most a time \c maxwaittime will be waited to + * send the BYE packet. If this time expires, the session will be left without sending a BYE packet. + * The BYE packet will contain as reason for leaving \c reason with length \c reasonlength. + */ + void BYEDestroy(const RTPTime &maxwaittime, const void *reason, std::size_t reasonlength); + + /** Returns whether the session has been created or not. */ + bool IsActive(); + + /** Returns our own SSRC. */ + uint32_t GetLocalSSRC(); + + /** Adds \c addr to the list of destinations. */ + int AddDestination(const RTPAddress &addr); + + /** Deletes \c addr from the list of destinations. */ + int DeleteDestination(const RTPAddress &addr); + + /** Clears the list of destinations. */ + void ClearDestinations(); + + /** Returns \c true if multicasting is supported. */ + bool SupportsMulticasting(); + + /** Joins the multicast group specified by \c addr. */ + int JoinMulticastGroup(const RTPAddress &addr); + + /** Leaves the multicast group specified by \c addr. */ + int LeaveMulticastGroup(const RTPAddress &addr); + + /** Sends the RTP packet with payload \c data which has length \c len. + * Sends the RTP packet with payload \c data which has length \c len. + * The used payload type, marker and timestamp increment will be those that have been set + * using the \c SetDefault member functions. + */ + int SendPacket(const void *data, std::size_t len); + + /** Sends the RTP packet with payload \c data which has length \c len. + * It will use payload type \c pt, marker \c mark and after the packet has been built, the + * timestamp will be incremented by \c timestampinc. + */ + int SendPacket(const void *data, std::size_t len, uint8_t pt, bool mark, uint32_t timestampinc); + + /** Sends the RTP packet with payload \c data which has length \c len. + * The packet will contain a header extension with identifier \c hdrextID and containing data + * \c hdrextdata. The length of this data is given by \c numhdrextwords and is specified in a + * number of 32-bit words. The used payload type, marker and timestamp increment will be those that + * have been set using the \c SetDefault member functions. + */ + int SendPacketEx(const void *data, std::size_t len, uint16_t hdrextID, const void *hdrextdata, std::size_t numhdrextwords); + + /** Sends the RTP packet with payload \c data which has length \c len. + * It will use payload type \c pt, marker \c mark and after the packet has been built, the + * timestamp will be incremented by \c timestampinc. The packet will contain a header + * extension with identifier \c hdrextID and containing data \c hdrextdata. The length + * of this data is given by \c numhdrextwords and is specified in a number of 32-bit words. + */ + int SendPacketEx(const void *data, std::size_t len, uint8_t pt, bool mark, uint32_t timestampinc, uint16_t hdrextID, const void *hdrextdata, std::size_t numhdrextwords); +#ifdef RTP_SUPPORT_SENDAPP + /** If sending of RTCP APP packets was enabled at compile time, this function creates a compound packet + * containing an RTCP APP packet and sends it immediately. + * If sending of RTCP APP packets was enabled at compile time, this function creates a compound packet + * containing an RTCP APP packet and sends it immediately. If successful, the function returns the number + * of bytes in the RTCP compound packet. Note that this immediate sending is not compliant with the RTP + * specification, so use with care. + */ + int SendRTCPAPPPacket(uint8_t subtype, const uint8_t name[4], const void *appdata, std::size_t appdatalen); +#endif // RTP_SUPPORT_SENDAPP + + /** With this function raw data can be sent directly over the RTP or + * RTCP channel (if they are different); the data is **not** passed through the + * RTPSession::OnChangeRTPOrRTCPData function. */ + int SendRawData(const void *data, std::size_t len, bool usertpchannel); + + /** Sets the default payload type for RTP packets to \c pt. */ + int SetDefaultPayloadType(uint8_t pt); + + /** Sets the default marker for RTP packets to \c m. */ + int SetDefaultMark(bool m); + + /** Sets the default value to increment the timestamp with to \c timestampinc. */ + int SetDefaultTimestampIncrement(uint32_t timestampinc); + + /** This function increments the timestamp with the amount given by \c inc. + * This function increments the timestamp with the amount given by \c inc. This can be useful + * if, for example, a packet was not sent because it contained only silence. Then, this function + * should be called to increment the timestamp with the appropriate amount so that the next packets + * will still be played at the correct time at other hosts. + */ + int IncrementTimestamp(uint32_t inc); + + /** This function increments the timestamp with the amount given set by the SetDefaultTimestampIncrement + * member function. + * This function increments the timestamp with the amount given set by the SetDefaultTimestampIncrement + * member function. This can be useful if, for example, a packet was not sent because it contained only silence. + * Then, this function should be called to increment the timestamp with the appropriate amount so that the next + * packets will still be played at the correct time at other hosts. + */ + int IncrementTimestampDefault(); + + /** This function allows you to inform the library about the delay between sampling the first + * sample of a packet and sending the packet. + * This function allows you to inform the library about the delay between sampling the first + * sample of a packet and sending the packet. This delay is taken into account when calculating the + * relation between RTP timestamp and wallclock time, used for inter-media synchronization. + */ + int SetPreTransmissionDelay(const RTPTime &delay); + + /** This function returns an instance of a subclass of RTPTransmissionInfo which will give some + * additional information about the transmitter (a list of local IP addresses for example). + * This function returns an instance of a subclass of RTPTransmissionInfo which will give some + * additional information about the transmitter (a list of local IP addresses for example). The user + * has to free the returned instance when it is no longer needed, preferably using the DeleteTransmissionInfo + * function. + */ + RTPTransmissionInfo *GetTransmissionInfo(); + + /** Frees the memory used by the transmission information \c inf. */ + void DeleteTransmissionInfo(RTPTransmissionInfo *inf); + + /** Returns the time interval after which an RTCP compound packet may have to be sent (only works when + * you're not using the poll thread. + */ + RTPTime GetRTCPDelay(); + + /** The following member functions (till EndDataAccess}) need to be accessed between a call + * to BeginDataAccess and EndDataAccess. + * The BeginDataAccess function makes sure that the poll thread won't access the source table + * at the same time that you're using it. When the EndDataAccess is called, the lock on the + * source table is freed again. + */ + int BeginDataAccess(); + + /** Starts the iteration over the participants by going to the first member in the table. + * Starts the iteration over the participants by going to the first member in the table. + * If a member was found, the function returns \c true, otherwise it returns \c false. + */ + bool GotoFirstSource(); + + /** Sets the current source to be the next source in the table. + * Sets the current source to be the next source in the table. If we're already at the last + * source, the function returns \c false, otherwise it returns \c true. + */ + bool GotoNextSource(); + + /** Sets the current source to be the previous source in the table. + * Sets the current source to be the previous source in the table. If we're at the first source, + * the function returns \c false, otherwise it returns \c true. + */ + bool GotoPreviousSource(); + + /** Sets the current source to be the first source in the table which has RTPPacket instances + * that we haven't extracted yet. + * Sets the current source to be the first source in the table which has RTPPacket instances + * that we haven't extracted yet. If no such member was found, the function returns \c false, + * otherwise it returns \c true. + */ + bool GotoFirstSourceWithData(); + + /** Sets the current source to be the next source in the table which has RTPPacket instances + * that we haven't extracted yet. + * Sets the current source to be the next source in the table which has RTPPacket instances + * that we haven't extracted yet. If no such member was found, the function returns \c false, + * otherwise it returns \c true. + */ + bool GotoNextSourceWithData(); + + /** Sets the current source to be the previous source in the table which has RTPPacket + * instances that we haven't extracted yet. + * Sets the current source to be the previous source in the table which has RTPPacket + * instances that we haven't extracted yet. If no such member was found, the function returns \c false, + * otherwise it returns \c true. + */ + bool GotoPreviousSourceWithData(); + + /** Returns the \c RTPSourceData instance for the currently selected participant. */ + RTPSourceData *GetCurrentSourceInfo(); + + /** Returns the \c RTPSourceData instance for the participant identified by \c ssrc, + * or NULL if no such entry exists. + */ + RTPSourceData *GetSourceInfo(uint32_t ssrc); + + /** Extracts the next packet from the received packets queue of the current participant, + * or NULL if no more packets are available. + * Extracts the next packet from the received packets queue of the current participant, + * or NULL if no more packets are available. When the packet is no longer needed, its + * memory should be freed using the DeletePacket member function. + */ + RTPPacket *GetNextPacket(); + + /** Returns the Sequence Number that will be used in the next SendPacket function call. */ + uint16_t GetNextSequenceNumber() const; + + /** Frees the memory used by \c p. */ + void DeletePacket(RTPPacket *p); + + /** See BeginDataAccess. */ + int EndDataAccess(); + + /** Sets the receive mode to \c m. + * Sets the receive mode to \c m. Note that when the receive mode is changed, the list of + * addresses to be ignored ot accepted will be cleared. + */ + int SetReceiveMode(RTPTransmitter::ReceiveMode m); + + /** Adds \c addr to the list of addresses to ignore. */ + int AddToIgnoreList(const RTPAddress &addr); + + /** Deletes \c addr from the list of addresses to ignore. */ + int DeleteFromIgnoreList(const RTPAddress &addr); + + /** Clears the list of addresses to ignore. */ + void ClearIgnoreList(); + + /** Adds \c addr to the list of addresses to accept. */ + int AddToAcceptList(const RTPAddress &addr); + + /** Deletes \c addr from the list of addresses to accept. */ + int DeleteFromAcceptList(const RTPAddress &addr); + + /** Clears the list of addresses to accept. */ + void ClearAcceptList(); + + /** Sets the maximum allowed packet size to \c s. */ + int SetMaximumPacketSize(std::size_t s); + + /** Sets the session bandwidth to \c bw, which is specified in bytes per second. */ + int SetSessionBandwidth(double bw); + + /** Sets the timestamp unit for our own data. + * Sets the timestamp unit for our own data. The timestamp unit is defined as a time interval in + * seconds divided by the corresponding timestamp interval. For example, for 8000 Hz audio, the + * timestamp unit would typically be 1/8000. Since this value is initially set to an illegal value, + * the user must set this to an allowed value to be able to create a session. + */ + int SetTimestampUnit(double u); + + /** Sets the RTCP interval for the SDES name item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES name item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetNameInterval(int count); + + /** Sets the RTCP interval for the SDES e-mail item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES e-mail item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetEMailInterval(int count); + + /** Sets the RTCP interval for the SDES location item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES location item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetLocationInterval(int count); + + /** Sets the RTCP interval for the SDES phone item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES phone item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetPhoneInterval(int count); + + /** Sets the RTCP interval for the SDES tool item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES tool item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetToolInterval(int count); + + /** Sets the RTCP interval for the SDES note item. + * After all possible sources in the source table have been processed, the class will check if other + * SDES items need to be sent. If \c count is zero or negative, nothing will happen. If \c count + * is positive, an SDES note item will be added after the sources in the source table have been + * processed \c count times. + */ + void SetNoteInterval(int count); + + /** Sets the SDES name item for the local participant to the value \c s with length \c len. */ + int SetLocalName(const void *s, std::size_t len); + + /** Sets the SDES e-mail item for the local participant to the value \c s with length \c len. */ + int SetLocalEMail(const void *s, std::size_t len); + + /** Sets the SDES location item for the local participant to the value \c s with length \c len. */ + int SetLocalLocation(const void *s, std::size_t len); + + /** Sets the SDES phone item for the local participant to the value \c s with length \c len. */ + int SetLocalPhone(const void *s, std::size_t len); + + /** Sets the SDES tool item for the local participant to the value \c s with length \c len. */ + int SetLocalTool(const void *s, std::size_t len); + + /** Sets the SDES note item for the local participant to the value \c s with length \c len. */ + int SetLocalNote(const void *s, std::size_t len); + +protected: + /** Allocate a user defined transmitter. + * In case you specified in the Create function that you want to use a + * user defined transmitter, you should override this function. The RTPTransmitter + * instance returned by this function will then be used to send and receive RTP and + * RTCP packets. Note that when the session is destroyed, this RTPTransmitter + * instance will be destroyed as well. + */ + virtual RTPTransmitter *NewUserDefinedTransmitter(); + + /** Is called when an incoming RTP packet is about to be processed. + * Is called when an incoming RTP packet is about to be processed. This is _not_ + * a good function to process an RTP packet in, in case you want to avoid iterating + * over the sources using the GotoFirst/GotoNext functions. In that case, the + * RTPSession::OnValidatedRTPPacket function should be used. + */ + virtual void OnRTPPacket(RTPPacket *pack, const RTPTime &receivetime, const RTPAddress *senderaddress); + + /** Is called when an incoming RTCP packet is about to be processed. */ + virtual void OnRTCPCompoundPacket(RTCPCompoundPacket *pack, const RTPTime &receivetime, const RTPAddress *senderaddress); + + /** Is called when an SSRC collision was detected. + * Is called when an SSRC collision was detected. The instance \c srcdat is the one present in + * the table, the address \c senderaddress is the one that collided with one of the addresses + * and \c isrtp indicates against which address of \c srcdat the check failed. + */ + virtual void OnSSRCCollision(RTPSourceData *srcdat, const RTPAddress *senderaddress, bool isrtp); + + /** Is called when another CNAME was received than the one already present for source \c srcdat. */ + virtual void OnCNAMECollision(RTPSourceData *srcdat, const RTPAddress *senderaddress, const uint8_t *cname, std::size_t cnamelength); + + /** Is called when a new entry \c srcdat is added to the source table. */ + virtual void OnNewSource(RTPSourceData *srcdat); + + /** Is called when the entry \c srcdat is about to be deleted from the source table. */ + virtual void OnRemoveSource(RTPSourceData *srcdat); + + /** Is called when participant \c srcdat is timed out. */ + virtual void OnTimeout(RTPSourceData *srcdat); + + /** Is called when participant \c srcdat is timed after having sent a BYE packet. */ + virtual void OnBYETimeout(RTPSourceData *srcdat); + + /** Is called when an RTCP APP packet \c apppacket has been received at time \c receivetime + * from address \c senderaddress. + */ + virtual void OnAPPPacket(RTCPAPPPacket *apppacket, const RTPTime &receivetime, const RTPAddress *senderaddress); + + /** Is called when an unknown RTCP packet type was detected. */ + virtual void OnUnknownPacketType(RTCPPacket *rtcppack, const RTPTime &receivetime, const RTPAddress *senderaddress); + + /** Is called when an unknown packet format for a known packet type was detected. */ + virtual void OnUnknownPacketFormat(RTCPPacket *rtcppack, const RTPTime &receivetime, const RTPAddress *senderaddress); + + /** Is called when the SDES NOTE item for source \c srcdat has been timed out. */ + virtual void OnNoteTimeout(RTPSourceData *srcdat); + + /** Is called when an RTCP sender report has been processed for this source. */ + virtual void OnRTCPSenderReport(RTPSourceData *srcdat); + + /** Is called when an RTCP receiver report has been processed for this source. */ + virtual void OnRTCPReceiverReport(RTPSourceData *srcdat); + + /** Is called when a specific SDES item was received for this source. */ + virtual void OnRTCPSDESItem(RTPSourceData *srcdat, RTCPSDESPacket::ItemType t, const void *itemdata, std::size_t itemlength); +#ifdef RTP_SUPPORT_SDESPRIV + /** Is called when a specific SDES item of 'private' type was received for this source. */ + virtual void OnRTCPSDESPrivateItem(RTPSourceData *srcdat, const void *prefixdata, std::size_t prefixlen, const void *valuedata, std::size_t valuelen); +#endif // RTP_SUPPORT_SDESPRIV + + /** Is called when a BYE packet has been processed for source \c srcdat. */ + virtual void OnBYEPacket(RTPSourceData *srcdat); + + /** Is called when an RTCP compound packet has just been sent (useful to inspect outgoing RTCP data). */ + virtual void OnSendRTCPCompoundPacket(RTCPCompoundPacket *pack); + + /** If this is set to true, outgoing data will be passed through RTPSession::OnChangeRTPOrRTCPData + * and RTPSession::OnSentRTPOrRTCPData, allowing you to modify the data (e.g. to encrypt it). */ + void SetChangeOutgoingData(bool change) + { + m_changeOutgoingData = change; + } + + /** If this is set to true, incoming data will be passed through RTPSession::OnChangeIncomingData, + * allowing you to modify the data (e.g. to decrypt it). */ + void SetChangeIncomingData(bool change) + { + m_changeIncomingData = change; + } + + /** If RTPSession::SetChangeOutgoingData was sent to true, overriding this you can change the + * data packet that will actually be sent, for example adding encryption. + * If RTPSession::SetChangeOutgoingData was sent to true, overriding this you can change the + * data packet that will actually be sent, for example adding encryption. + * Note that no memory management will be performed on the `senddata` pointer you fill in, + * so if it needs to be deleted at some point you need to take care of this in some way + * yourself, a good way may be to do this in RTPSession::OnSentRTPOrRTCPData. If `senddata` is + * set to 0, no packet will be sent out. This also provides a way to turn off sending RTCP + * packets if desired. */ + virtual int OnChangeRTPOrRTCPData(const void *origdata, std::size_t origlen, bool isrtp, void **senddata, std::size_t *sendlen); + + /** This function is called when an RTP or RTCP packet was sent, it can be helpful + * when data was allocated in RTPSession::OnChangeRTPOrRTCPData to deallocate it + * here. */ + virtual void OnSentRTPOrRTCPData(void *senddata, std::size_t sendlen, bool isrtp); + + /** By overriding this function, the raw incoming data can be inspected + * and modified (e.g. for encryption). + * By overriding this function, the raw incoming data can be inspected + * and modified (e.g. for encryption). If the function returns `false`, + * the packet is discarded. + */ + virtual bool OnChangeIncomingData(RTPRawPacket *rawpack); + + /** Allows you to use an RTP packet from the specified source directly. + * Allows you to use an RTP packet from the specified source directly. If + * `ispackethandled` is set to `true`, the packet will no longer be stored in this + * source's packet list. Note that if you do set this flag, you'll need to + * deallocate the packet yourself at an appropriate time. + * + * The difference with RTPSession::OnRTPPacket is that that + * function will always process the RTP packet further and is therefore not + * really suited to actually do something with the data. + */ + virtual void OnValidatedRTPPacket(RTPSourceData *srcdat, RTPPacket *rtppack, bool isonprobation, bool *ispackethandled); +private: + int InternalCreate(const RTPSessionParams &sessparams); + int CreateCNAME(uint8_t *buffer, std::size_t *bufferlength, bool resolve); + int ProcessPolledData(); + int ProcessRTCPCompoundPacket(RTCPCompoundPacket &rtcpcomppack, RTPRawPacket *pack); + RTPRandom *GetRandomNumberGenerator(RTPRandom *r); + int SendRTPData(const void *data, std::size_t len); + int SendRTCPData(const void *data, std::size_t len); + + RTPRandom *rtprnd; + bool deletertprnd; + + RTPTransmitter *rtptrans; + bool created; + bool deletetransmitter; + bool usingpollthread; + bool acceptownpackets; + bool useSR_BYEifpossible; + std::size_t maxpacksize; + double sessionbandwidth; + double controlfragment; + double sendermultiplier; + double byemultiplier; + double membermultiplier; + double collisionmultiplier; + double notemultiplier; + bool sentpackets; + + bool m_changeIncomingData, m_changeOutgoingData; + + RTPSessionSources sources; + RTPPacketBuilder packetbuilder; + RTCPScheduler rtcpsched; + RTCPPacketBuilder rtcpbuilder; + RTPCollisionList collisionlist; + + std::list byepackets; + + friend class RTPSessionSources; + friend class RTCPSessionPacketBuilder; +}; + +inline RTPTransmitter *RTPSession::NewUserDefinedTransmitter() +{ + return 0; +} +inline void RTPSession::OnRTPPacket(RTPPacket *, const RTPTime &, const RTPAddress *) +{ +} +inline void RTPSession::OnRTCPCompoundPacket(RTCPCompoundPacket *, const RTPTime &, const RTPAddress *) +{ +} +inline void RTPSession::OnSSRCCollision(RTPSourceData *, const RTPAddress *, bool) +{ +} +inline void RTPSession::OnCNAMECollision(RTPSourceData *, const RTPAddress *, const uint8_t *, std::size_t) +{ +} +inline void RTPSession::OnNewSource(RTPSourceData *) +{ +} +inline void RTPSession::OnRemoveSource(RTPSourceData *) +{ +} +inline void RTPSession::OnTimeout(RTPSourceData *) +{ +} +inline void RTPSession::OnBYETimeout(RTPSourceData *) +{ +} +inline void RTPSession::OnAPPPacket(RTCPAPPPacket *, const RTPTime &, const RTPAddress *) +{ +} +inline void RTPSession::OnUnknownPacketType(RTCPPacket *, const RTPTime &, const RTPAddress *) +{ +} +inline void RTPSession::OnUnknownPacketFormat(RTCPPacket *, const RTPTime &, const RTPAddress *) +{ +} +inline void RTPSession::OnNoteTimeout(RTPSourceData *) +{ +} +inline void RTPSession::OnRTCPSenderReport(RTPSourceData *) +{ +} +inline void RTPSession::OnRTCPReceiverReport(RTPSourceData *) +{ +} +inline void RTPSession::OnRTCPSDESItem(RTPSourceData *, RTCPSDESPacket::ItemType, const void *, std::size_t) +{ +} + +#ifdef RTP_SUPPORT_SDESPRIV +inline void RTPSession::OnRTCPSDESPrivateItem(RTPSourceData *, const void *, std::size_t, const void *, std::size_t) +{ +} +#endif // RTP_SUPPORT_SDESPRIV + +inline void RTPSession::OnBYEPacket(RTPSourceData *) +{ +} +inline void RTPSession::OnSendRTCPCompoundPacket(RTCPCompoundPacket *) +{ +} + +inline int RTPSession::OnChangeRTPOrRTCPData(const void *, std::size_t, bool, void **, std::size_t *) +{ + return ERR_RTP_RTPSESSION_CHANGEREQUESTEDBUTNOTIMPLEMENTED; +} +inline void RTPSession::OnSentRTPOrRTCPData(void *, std::size_t, bool) +{ +} +inline bool RTPSession::OnChangeIncomingData(RTPRawPacket *) +{ + return true; +} +inline void RTPSession::OnValidatedRTPPacket(RTPSourceData *, RTPPacket *, bool, bool *) +{ +} + +} // end namespace + +#endif // RTPSESSION_H + diff --git a/qrtplib/rtpsessionparams.cpp b/qrtplib/rtpsessionparams.cpp new file mode 100644 index 000000000..40b611899 --- /dev/null +++ b/qrtplib/rtpsessionparams.cpp @@ -0,0 +1,81 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +#include "rtpconfig.h" +#include "rtpsessionparams.h" +#include "rtpdefines.h" +#include "rtperrors.h" + +namespace qrtplib +{ + +RTPSessionParams::RTPSessionParams() : + mininterval(0, 0) +{ + usepollthread = false; + m_needThreadSafety = false; + maxpacksize = RTP_DEFAULTPACKETSIZE; + receivemode = RTPTransmitter::AcceptAll; + acceptown = false; + owntsunit = -1; // The user will have to set it to the correct value himself + resolvehostname = false; + + mininterval = RTPTime(RTCP_DEFAULTMININTERVAL); + sessionbandwidth = RTP_DEFAULTSESSIONBANDWIDTH; + controlfrac = RTCP_DEFAULTBANDWIDTHFRACTION; + senderfrac = RTCP_DEFAULTSENDERFRACTION; + usehalfatstartup = RTCP_DEFAULTHALFATSTARTUP; + immediatebye = RTCP_DEFAULTIMMEDIATEBYE; + SR_BYE = RTCP_DEFAULTSRBYE; + + sendermultiplier = RTP_SENDERTIMEOUTMULTIPLIER; + generaltimeoutmultiplier = RTP_MEMBERTIMEOUTMULTIPLIER; + byetimeoutmultiplier = RTP_BYETIMEOUTMULTIPLIER; + collisionmultiplier = RTP_COLLISIONTIMEOUTMULTIPLIER; + notemultiplier = RTP_NOTETTIMEOUTMULTIPLIER; + + usepredefinedssrc = false; + predefinedssrc = 0; +} + +int RTPSessionParams::SetUsePollThread(bool usethread __attribute__((unused))) +{ + return ERR_RTP_NOTHREADSUPPORT; +} + +int RTPSessionParams::SetNeedThreadSafety(bool __attribute__((unused))) +{ + return ERR_RTP_NOTHREADSUPPORT; +} + +} // end namespace + diff --git a/qrtplib/rtpsessionparams.h b/qrtplib/rtpsessionparams.h new file mode 100644 index 000000000..b58cc424c --- /dev/null +++ b/qrtplib/rtpsessionparams.h @@ -0,0 +1,374 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +/** + * \file rtpsessionparams.h + */ + +#ifndef RTPSESSIONPARAMS_H + +#define RTPSESSIONPARAMS_H + +#include "rtpconfig.h" +#include "rtptypes.h" +#include "rtptransmitter.h" +#include "rtptimeutilities.h" +#include "rtpsources.h" + +#include "export.h" + +namespace qrtplib +{ + +/** Describes the parameters for to be used by an RTPSession instance. + * Describes the parameters for to be used by an RTPSession instance. Note that the own timestamp + * unit must be set to a valid number, otherwise the session can't be created. + */ +class QRTPLIB_API RTPSessionParams +{ +public: + RTPSessionParams(); + + /** If \c usethread is \c true, the session will use a poll thread to automatically process incoming + * data and to send RTCP packets when necessary. + */ + int SetUsePollThread(bool usethread); + + /** if `s` is `true`, the session will use mutexes in case multiple threads + * are at work. */ + int SetNeedThreadSafety(bool s); + + /** Returns whether the session should use a poll thread or not (default is \c true). */ + bool IsUsingPollThread() const + { + return usepollthread; + } + + /** Sets the maximum allowed packet size for the session. */ + void SetMaximumPacketSize(std::size_t max) + { + maxpacksize = max; + } + + /** Returns the maximum allowed packet size (default is 1400 bytes). */ + std::size_t GetMaximumPacketSize() const + { + return maxpacksize; + } + + /** If the argument is \c true, the session should accept its own packets and store + * them accordingly in the source table. + */ + void SetAcceptOwnPackets(bool accept) + { + acceptown = accept; + } + + /** Returns \c true if the session should accept its own packets (default is \c false). */ + bool AcceptOwnPackets() const + { + return acceptown; + } + + /** Sets the receive mode to be used by the session. */ + void SetReceiveMode(RTPTransmitter::ReceiveMode recvmode) + { + receivemode = recvmode; + } + + /** Sets the receive mode to be used by the session (default is: accept all packets). */ + RTPTransmitter::ReceiveMode GetReceiveMode() const + { + return receivemode; + } + + /** Sets the timestamp unit for our own data. + * Sets the timestamp unit for our own data. The timestamp unit is defined as a time interval in + * seconds divided by the corresponding timestamp interval. For example, for 8000 Hz audio, the + * timestamp unit would typically be 1/8000. Since this value is initially set to an illegal value, + * the user must set this to an allowed value to be able to create a session. + */ + void SetOwnTimestampUnit(double tsunit) + { + owntsunit = tsunit; + } + + /** Returns the currently set timestamp unit. */ + double GetOwnTimestampUnit() const + { + return owntsunit; + } + + /** Sets a flag indicating if a DNS lookup should be done to determine our hostname (to construct a CNAME item). + * If \c v is set to \c true, the session will ask the transmitter to find a host name based upon the IP + * addresses in its list of local IP addresses. If set to \c false, a call to \c gethostname or something + * similar will be used to find the local hostname. Note that the first method might take some time. + */ + void SetResolveLocalHostname(bool v) + { + resolvehostname = v; + } + + /** Returns whether the local hostname should be determined from the transmitter's list of local IP addresses + * or not (default is \c false). + */ + bool GetResolveLocalHostname() const + { + return resolvehostname; + } + + /** Sets the session bandwidth in bytes per second. */ + void SetSessionBandwidth(double sessbw) + { + sessionbandwidth = sessbw; + } + + /** Returns the session bandwidth in bytes per second (default is 10000 bytes per second). */ + double GetSessionBandwidth() const + { + return sessionbandwidth; + } + + /** Sets the fraction of the session bandwidth to be used for control traffic. */ + void SetControlTrafficFraction(double frac) + { + controlfrac = frac; + } + + /** Returns the fraction of the session bandwidth that will be used for control traffic (default is 5%). */ + double GetControlTrafficFraction() const + { + return controlfrac; + } + + /** Sets the minimum fraction of the control traffic that will be used by senders. */ + void SetSenderControlBandwidthFraction(double frac) + { + senderfrac = frac; + } + + /** Returns the minimum fraction of the control traffic that will be used by senders (default is 25%). */ + double GetSenderControlBandwidthFraction() const + { + return senderfrac; + } + + /** Set the minimal time interval between sending RTCP packets. */ + void SetMinimumRTCPTransmissionInterval(const RTPTime &t) + { + mininterval = t; + } + + /** Returns the minimal time interval between sending RTCP packets (default is 5 seconds). */ + RTPTime GetMinimumRTCPTransmissionInterval() const + { + return mininterval; + } + + /** If \c usehalf is set to \c true, the session will only wait half of the calculated RTCP + * interval before sending its first RTCP packet. + */ + void SetUseHalfRTCPIntervalAtStartup(bool usehalf) + { + usehalfatstartup = usehalf; + } + + /** Returns whether the session will only wait half of the calculated RTCP interval before sending its + * first RTCP packet or not (default is \c true). + */ + bool GetUseHalfRTCPIntervalAtStartup() const + { + return usehalfatstartup; + } + + /** If \c v is \c true, the session will send a BYE packet immediately if this is allowed. */ + void SetRequestImmediateBYE(bool v) + { + immediatebye = v; + } + + /** Returns whether the session should send a BYE packet immediately (if allowed) or not (default is \c true). */ + bool GetRequestImmediateBYE() const + { + return immediatebye; + } + + /** When sending a BYE packet, this indicates whether it will be part of an RTCP compound packet + * that begins with a sender report (if allowed) or a receiver report. + */ + void SetSenderReportForBYE(bool v) + { + SR_BYE = v; + } + + /** Returns \c true if a BYE packet will be sent in an RTCP compound packet which starts with a + * sender report; if a receiver report will be used, the function returns \c false (default is \c true). + */ + bool GetSenderReportForBYE() const + { + return SR_BYE; + } + + /** Sets the multiplier to be used when timing out senders. */ + void SetSenderTimeoutMultiplier(double m) + { + sendermultiplier = m; + } + + /** Returns the multiplier to be used when timing out senders (default is 2). */ + double GetSenderTimeoutMultiplier() const + { + return sendermultiplier; + } + + /** Sets the multiplier to be used when timing out members. */ + void SetSourceTimeoutMultiplier(double m) + { + generaltimeoutmultiplier = m; + } + + /** Returns the multiplier to be used when timing out members (default is 5). */ + double GetSourceTimeoutMultiplier() const + { + return generaltimeoutmultiplier; + } + + /** Sets the multiplier to be used when timing out a member after it has sent a BYE packet. */ + void SetBYETimeoutMultiplier(double m) + { + byetimeoutmultiplier = m; + } + + /** Returns the multiplier to be used when timing out a member after it has sent a BYE packet (default is 1). */ + double GetBYETimeoutMultiplier() const + { + return byetimeoutmultiplier; + } + + /** Sets the multiplier to be used when timing out entries in the collision table. */ + void SetCollisionTimeoutMultiplier(double m) + { + collisionmultiplier = m; + } + + /** Returns the multiplier to be used when timing out entries in the collision table (default is 10). */ + double GetCollisionTimeoutMultiplier() const + { + return collisionmultiplier; + } + + /** Sets the multiplier to be used when timing out SDES NOTE information. */ + void SetNoteTimeoutMultiplier(double m) + { + notemultiplier = m; + } + + /** Returns the multiplier to be used when timing out SDES NOTE information (default is 25). */ + double GetNoteTimeoutMultiplier() const + { + return notemultiplier; + } + + /** Sets a flag which indicates if a predefined SSRC identifier should be used. */ + void SetUsePredefinedSSRC(bool f) + { + usepredefinedssrc = f; + } + + /** Returns a flag indicating if a predefined SSRC should be used. */ + bool GetUsePredefinedSSRC() const + { + return usepredefinedssrc; + } + + /** Sets the SSRC which will be used if RTPSessionParams::GetUsePredefinedSSRC returns true. */ + void SetPredefinedSSRC(uint32_t ssrc) + { + predefinedssrc = ssrc; + } + + /** Returns the SSRC which will be used if RTPSessionParams::GetUsePredefinedSSRC returns true. */ + uint32_t GetPredefinedSSRC() const + { + return predefinedssrc; + } + + /** Forces this string to be used as the CNAME identifier. */ + void SetCNAME(const std::string &s) + { + cname = s; + } + + /** Returns the currently set CNAME, is blank when this will be generated automatically (the default). */ + std::string GetCNAME() const + { + return cname; + } + + /** Returns `true` if thread safety was requested using RTPSessionParams::SetNeedThreadSafety. */ + bool NeedThreadSafety() const + { + return m_needThreadSafety; + } +private: + bool acceptown; + bool usepollthread; + std::size_t maxpacksize; + double owntsunit; + RTPTransmitter::ReceiveMode receivemode; + bool resolvehostname; + + double sessionbandwidth; + double controlfrac; + double senderfrac; + RTPTime mininterval; + bool usehalfatstartup; + bool immediatebye; + bool SR_BYE; + + double sendermultiplier; + double generaltimeoutmultiplier; + double byetimeoutmultiplier; + double collisionmultiplier; + double notemultiplier; + + bool usepredefinedssrc; + uint32_t predefinedssrc; + + std::string cname; + bool m_needThreadSafety; +}; + +} // end namespace + +#endif // RTPSESSIONPARAMS_H + diff --git a/qrtplib/rtpsessionsources.cpp b/qrtplib/rtpsessionsources.cpp new file mode 100644 index 000000000..045fa1cee --- /dev/null +++ b/qrtplib/rtpsessionsources.cpp @@ -0,0 +1,139 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +#include "rtpsessionsources.h" +#include "rtpsession.h" +#include "rtpsourcedata.h" + +namespace qrtplib +{ + +void RTPSessionSources::OnRTPPacket(RTPPacket *pack, const RTPTime &receivetime, const RTPAddress *senderaddress) +{ + rtpsession.OnRTPPacket(pack, receivetime, senderaddress); +} + +void RTPSessionSources::OnRTCPCompoundPacket(RTCPCompoundPacket *pack, const RTPTime &receivetime, const RTPAddress *senderaddress) +{ + if (senderaddress != 0) // don't analyse own RTCP packets again (they're already analysed on their way out) + rtpsession.rtcpsched.AnalyseIncoming(*pack); + rtpsession.OnRTCPCompoundPacket(pack, receivetime, senderaddress); +} + +void RTPSessionSources::OnSSRCCollision(RTPSourceData *srcdat, const RTPAddress *senderaddress, bool isrtp) +{ + if (srcdat->IsOwnSSRC()) + owncollision = true; + rtpsession.OnSSRCCollision(srcdat, senderaddress, isrtp); +} + +void RTPSessionSources::OnCNAMECollision(RTPSourceData *srcdat, const RTPAddress *senderaddress, const uint8_t *cname, std::size_t cnamelength) +{ + rtpsession.OnCNAMECollision(srcdat, senderaddress, cname, cnamelength); +} + +void RTPSessionSources::OnNewSource(RTPSourceData *srcdat) +{ + rtpsession.OnNewSource(srcdat); +} + +void RTPSessionSources::OnRemoveSource(RTPSourceData *srcdat) +{ + rtpsession.OnRemoveSource(srcdat); +} + +void RTPSessionSources::OnTimeout(RTPSourceData *srcdat) +{ + rtpsession.rtcpsched.ActiveMemberDecrease(); + rtpsession.OnTimeout(srcdat); +} + +void RTPSessionSources::OnBYETimeout(RTPSourceData *srcdat) +{ + rtpsession.OnBYETimeout(srcdat); +} + +void RTPSessionSources::OnBYEPacket(RTPSourceData *srcdat) +{ + rtpsession.rtcpsched.ActiveMemberDecrease(); + rtpsession.OnBYEPacket(srcdat); +} + +void RTPSessionSources::OnAPPPacket(RTCPAPPPacket *apppacket, const RTPTime &receivetime, const RTPAddress *senderaddress) +{ + rtpsession.OnAPPPacket(apppacket, receivetime, senderaddress); +} + +void RTPSessionSources::OnUnknownPacketType(RTCPPacket *rtcppack, const RTPTime &receivetime, const RTPAddress *senderaddress) +{ + rtpsession.OnUnknownPacketType(rtcppack, receivetime, senderaddress); +} + +void RTPSessionSources::OnUnknownPacketFormat(RTCPPacket *rtcppack, const RTPTime &receivetime, const RTPAddress *senderaddress) +{ + rtpsession.OnUnknownPacketFormat(rtcppack, receivetime, senderaddress); +} + +void RTPSessionSources::OnNoteTimeout(RTPSourceData *srcdat) +{ + rtpsession.OnNoteTimeout(srcdat); +} + +void RTPSessionSources::OnValidatedRTPPacket(RTPSourceData *srcdat, RTPPacket *rtppack, bool isonprobation, bool *ispackethandled) +{ + rtpsession.OnValidatedRTPPacket(srcdat, rtppack, isonprobation, ispackethandled); +} + +void RTPSessionSources::OnRTCPSenderReport(RTPSourceData *srcdat) +{ + rtpsession.OnRTCPSenderReport(srcdat); +} + +void RTPSessionSources::OnRTCPReceiverReport(RTPSourceData *srcdat) +{ + rtpsession.OnRTCPReceiverReport(srcdat); +} + +void RTPSessionSources::OnRTCPSDESItem(RTPSourceData *srcdat, RTCPSDESPacket::ItemType t, const void *itemdata, std::size_t itemlength) +{ + rtpsession.OnRTCPSDESItem(srcdat, t, itemdata, itemlength); +} + +#ifdef RTP_SUPPORT_SDESPRIV +void RTPSessionSources::OnRTCPSDESPrivateItem(RTPSourceData *srcdat, const void *prefixdata, std::size_t prefixlen, const void *valuedata, std::size_t valuelen) +{ + rtpsession.OnRTCPSDESPrivateItem(srcdat, prefixdata, prefixlen, valuedata, valuelen); +} +#endif // RTP_SUPPORT_SDESPRIV + +} // end namespace + diff --git a/qrtplib/rtpsessionsources.h b/qrtplib/rtpsessionsources.h new file mode 100644 index 000000000..fbb6e22a6 --- /dev/null +++ b/qrtplib/rtpsessionsources.h @@ -0,0 +1,98 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +/** + * \file rtpsessionsources.h + */ + +#ifndef RTPSESSIONSOURCES_H + +#define RTPSESSIONSOURCES_H + +#include "rtpconfig.h" +#include "rtpsources.h" + +#include "export.h" + +namespace qrtplib +{ + +class RTPSession; + +class QRTPLIB_API RTPSessionSources: public RTPSources +{ +public: + RTPSessionSources(RTPSession &sess) : + rtpsession(sess) + { + owncollision = false; + } + ~RTPSessionSources() + { + } + void ClearOwnCollisionFlag() + { + owncollision = false; + } + bool DetectedOwnCollision() const + { + return owncollision; + } +private: + void OnRTPPacket(RTPPacket *pack, const RTPTime &receivetime, const RTPAddress *senderaddress); + void OnRTCPCompoundPacket(RTCPCompoundPacket *pack, const RTPTime &receivetime, const RTPAddress *senderaddress); + void OnSSRCCollision(RTPSourceData *srcdat, const RTPAddress *senderaddress, bool isrtp); + void OnCNAMECollision(RTPSourceData *srcdat, const RTPAddress *senderaddress, const uint8_t *cname, std::size_t cnamelength); + void OnNewSource(RTPSourceData *srcdat); + void OnRemoveSource(RTPSourceData *srcdat); + void OnTimeout(RTPSourceData *srcdat); + void OnBYETimeout(RTPSourceData *srcdat); + void OnBYEPacket(RTPSourceData *srcdat); + void OnAPPPacket(RTCPAPPPacket *apppacket, const RTPTime &receivetime, const RTPAddress *senderaddress); + void OnUnknownPacketType(RTCPPacket *rtcppack, const RTPTime &receivetime, const RTPAddress *senderaddress); + void OnUnknownPacketFormat(RTCPPacket *rtcppack, const RTPTime &receivetime, const RTPAddress *senderaddress); + void OnNoteTimeout(RTPSourceData *srcdat); + void OnValidatedRTPPacket(RTPSourceData *srcdat, RTPPacket *rtppack, bool isonprobation, bool *ispackethandled); + void OnRTCPSenderReport(RTPSourceData *srcdat); + void OnRTCPReceiverReport(RTPSourceData *srcdat); + void OnRTCPSDESItem(RTPSourceData *srcdat, RTCPSDESPacket::ItemType t, const void *itemdata, std::size_t itemlength); +#ifdef RTP_SUPPORT_SDESPRIV + void OnRTCPSDESPrivateItem(RTPSourceData *srcdat, const void *prefixdata, std::size_t prefixlen, const void *valuedata, std::size_t valuelen); +#endif // RTP_SUPPORT_SDESPRIV + + RTPSession &rtpsession; + bool owncollision; +}; + +} // end namespace + +#endif // RTPSESSIONSOURCES_H diff --git a/qrtplib/rtpsocketutil.h b/qrtplib/rtpsocketutil.h new file mode 100644 index 000000000..ea648afc5 --- /dev/null +++ b/qrtplib/rtpsocketutil.h @@ -0,0 +1,62 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +/** + * \file rtpsocketutil.h + */ + +#ifndef RTPSOCKETUTIL_H + +#define RTPSOCKETUTIL_H + +#include "rtpconfig.h" +#ifdef RTP_SOCKETTYPE_WINSOCK +#include "rtptypes.h" +#endif // RTP_SOCKETTYPE_WINSOCK + +namespace qrtplib +{ + +#ifndef RTP_SOCKETTYPE_WINSOCK + +typedef int SocketType; + +#else + +typedef SOCKET SocketType; + +#endif // RTP_SOCKETTYPE_WINSOCK + +} + // end namespace + +#endif // RTPSOCKETUTIL_H diff --git a/qrtplib/rtpsourcedata.cpp b/qrtplib/rtpsourcedata.cpp new file mode 100644 index 000000000..398bda7fe --- /dev/null +++ b/qrtplib/rtpsourcedata.cpp @@ -0,0 +1,265 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +#include "rtpsourcedata.h" +#include "rtpdefines.h" +#include "rtpaddress.h" + +#define ACCEPTPACKETCODE \ + *accept = true; \ + \ + sentdata = true; \ + packetsreceived++; \ + numnewpackets++; \ + \ + if (pack->GetExtendedSequenceNumber() == 0) \ + { \ + baseseqnr = 0x0000FFFF; \ + numcycles = 0x00010000; \ + } \ + else \ + baseseqnr = pack->GetExtendedSequenceNumber() - 1; \ + \ + exthighseqnr = baseseqnr + 1; \ + prevpacktime = receivetime; \ + prevexthighseqnr = baseseqnr; \ + savedextseqnr = baseseqnr; \ + \ + pack->SetExtendedSequenceNumber(exthighseqnr); \ + \ + prevtimestamp = pack->GetTimestamp(); \ + lastmsgtime = prevpacktime; \ + if (!ownpacket) /* for own packet, this value is set on an outgoing packet */ \ + lastrtptime = prevpacktime; + +namespace qrtplib +{ + +void RTPSourceStats::ProcessPacket( + RTPPacket *pack, + const RTPTime &receivetime, + double tsunit, + bool ownpacket, + bool *accept) +{ + // Note that the sequence number in the RTP packet is still just the + // 16 bit number contained in the RTP header + + if (!sentdata) // no valid packets received yet + { + ACCEPTPACKETCODE + } + else // already got packets + { + uint16_t maxseq16; + uint32_t extseqnr; + + // Adjust max extended sequence number and set extende seq nr of packet + + *accept = true; + packetsreceived++; + numnewpackets++; + + maxseq16 = (uint16_t) (exthighseqnr & 0x0000FFFF); + if (pack->GetExtendedSequenceNumber() >= maxseq16) + { + extseqnr = numcycles + pack->GetExtendedSequenceNumber(); + exthighseqnr = extseqnr; + } + else + { + uint16_t dif1, dif2; + + dif1 = ((uint16_t) pack->GetExtendedSequenceNumber()); + dif1 -= maxseq16; + dif2 = maxseq16; + dif2 -= ((uint16_t) pack->GetExtendedSequenceNumber()); + if (dif1 < dif2) + { + numcycles += 0x00010000; + extseqnr = numcycles + pack->GetExtendedSequenceNumber(); + exthighseqnr = extseqnr; + } + else + extseqnr = numcycles + pack->GetExtendedSequenceNumber(); + } + + pack->SetExtendedSequenceNumber(extseqnr); + + // Calculate jitter + + if (tsunit > 0) + { +#if 0 + RTPTime curtime = receivetime; + double diffts1,diffts2,diff; + + curtime -= prevpacktime; + diffts1 = curtime.GetDouble()/tsunit; + diffts2 = (double)pack->GetTimestamp() - (double)prevtimestamp; + diff = diffts1 - diffts2; + if (diff < 0) + diff = -diff; + diff -= djitter; + diff /= 16.0; + djitter += diff; + jitter = (uint32_t)djitter; +#else + RTPTime curtime = receivetime; + double diffts1, diffts2, diff; + uint32_t curts = pack->GetTimestamp(); + + curtime -= prevpacktime; + diffts1 = curtime.GetDouble() / tsunit; + + if (curts > prevtimestamp) + { + uint32_t unsigneddiff = curts - prevtimestamp; + + if (unsigneddiff < 0x10000000) // okay, curts realy is larger than prevtimestamp + diffts2 = (double) unsigneddiff; + else + { + // wraparound occurred and curts is actually smaller than prevtimestamp + + unsigneddiff = -unsigneddiff; // to get the actual difference (in absolute value) + diffts2 = -((double) unsigneddiff); + } + } + else if (curts < prevtimestamp) + { + uint32_t unsigneddiff = prevtimestamp - curts; + + if (unsigneddiff < 0x10000000) // okay, curts really is smaller than prevtimestamp + diffts2 = -((double) unsigneddiff); // negative since we actually need curts-prevtimestamp + else + { + // wraparound occurred and curts is actually larger than prevtimestamp + + unsigneddiff = -unsigneddiff; // to get the actual difference (in absolute value) + diffts2 = (double) unsigneddiff; + } + } + else + diffts2 = 0; + + diff = diffts1 - diffts2; + if (diff < 0) + diff = -diff; + diff -= djitter; + diff /= 16.0; + djitter += diff; + jitter = (uint32_t) djitter; +#endif + } + else + { + djitter = 0; + jitter = 0; + } + + prevpacktime = receivetime; + prevtimestamp = pack->GetTimestamp(); + lastmsgtime = prevpacktime; + if (!ownpacket) // for own packet, this value is set on an outgoing packet + lastrtptime = prevpacktime; + } +} + +RTPSourceData::RTPSourceData(uint32_t s) : + byetime(0, 0) +{ + ssrc = s; + issender = false; + iscsrc = false; + timestampunit = -1; + receivedbye = false; + byereason = 0; + byereasonlen = 0; + rtpaddr = 0; + rtcpaddr = 0; + ownssrc = false; + validated = false; + processedinrtcp = false; + isrtpaddrset = false; + isrtcpaddrset = false; +} + +RTPSourceData::~RTPSourceData() +{ + FlushPackets(); + if (byereason) + delete[] byereason; + if (rtpaddr) + delete rtpaddr; + if (rtcpaddr) + delete rtcpaddr; +} + +double RTPSourceData::INF_GetEstimatedTimestampUnit() const +{ + if (!SRprevinf.HasInfo()) + return -1.0; + + RTPTime t1 = RTPTime(SRinf.GetNTPTimestamp()); + RTPTime t2 = RTPTime(SRprevinf.GetNTPTimestamp()); + if (t1.IsZero() || t2.IsZero()) // one of the times couldn't be calculated + return -1.0; + + if (t1 <= t2) + return -1.0; + + t1 -= t2; // get the time difference + + uint32_t tsdiff = SRinf.GetRTPTimestamp() - SRprevinf.GetRTPTimestamp(); + + return (t1.GetDouble() / ((double) tsdiff)); +} + +RTPTime RTPSourceData::INF_GetRoundtripTime() const +{ + if (!RRinf.HasInfo()) + return RTPTime(0, 0); + if (RRinf.GetDelaySinceLastSR() == 0 && RRinf.GetLastSRTimestamp() == 0) + return RTPTime(0, 0); + + RTPNTPTime recvtime = RRinf.GetReceiveTime().GetNTPTime(); + uint32_t rtt = ((recvtime.GetMSW() & 0xFFFF) << 16) | ((recvtime.GetLSW() >> 16) & 0xFFFF); + rtt -= RRinf.GetLastSRTimestamp(); + rtt -= RRinf.GetDelaySinceLastSR(); + + double drtt = (((double) rtt) / 65536.0); + return RTPTime(drtt); +} + +} // end namespace + diff --git a/qrtplib/rtpsourcedata.h b/qrtplib/rtpsourcedata.h new file mode 100644 index 000000000..b71256687 --- /dev/null +++ b/qrtplib/rtpsourcedata.h @@ -0,0 +1,801 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +/** + * \file rtpsourcedata.h + */ + +#ifndef RTPSOURCEDATA_H + +#define RTPSOURCEDATA_H + +#include "rtpconfig.h" +#include "rtptimeutilities.h" +#include "rtppacket.h" +#include "rtcpsdesinfo.h" +#include "rtptypes.h" +#include "rtpsources.h" +#include + +#include "export.h" + +namespace qrtplib +{ + +class RTPAddress; + +class QRTPLIB_API RTCPSenderReportInfo +{ +public: + RTCPSenderReportInfo() : + ntptimestamp(0, 0), receivetime(0, 0) + { + hasinfo = false; + rtptimestamp = 0; + packetcount = 0; + bytecount = 0; + } + void Set(const RTPNTPTime &ntptime, uint32_t rtptime, uint32_t pcount, uint32_t bcount, const RTPTime &rcvtime) + { + ntptimestamp = ntptime; + rtptimestamp = rtptime; + packetcount = pcount; + bytecount = bcount; + receivetime = rcvtime; + hasinfo = true; + } + + bool HasInfo() const + { + return hasinfo; + } + RTPNTPTime GetNTPTimestamp() const + { + return ntptimestamp; + } + uint32_t GetRTPTimestamp() const + { + return rtptimestamp; + } + uint32_t GetPacketCount() const + { + return packetcount; + } + uint32_t GetByteCount() const + { + return bytecount; + } + RTPTime GetReceiveTime() const + { + return receivetime; + } +private: + bool hasinfo; + RTPNTPTime ntptimestamp; + uint32_t rtptimestamp; + uint32_t packetcount; + uint32_t bytecount; + RTPTime receivetime; +}; + +class RTCPReceiverReportInfo +{ +public: + RTCPReceiverReportInfo() : + receivetime(0, 0) + { + hasinfo = false; + fractionlost = 0; + packetslost = 0; + exthighseqnr = 0; + jitter = 0; + lsr = 0; + dlsr = 0; + } + void Set(uint8_t fraclost, int32_t plost, uint32_t exthigh, uint32_t jit, uint32_t l, uint32_t dl, const RTPTime &rcvtime) + { + fractionlost = ((double) fraclost) / 256.0; + packetslost = plost; + exthighseqnr = exthigh; + jitter = jit; + lsr = l; + dlsr = dl; + receivetime = rcvtime; + hasinfo = true; + } + + bool HasInfo() const + { + return hasinfo; + } + double GetFractionLost() const + { + return fractionlost; + } + int32_t GetPacketsLost() const + { + return packetslost; + } + uint32_t GetExtendedHighestSequenceNumber() const + { + return exthighseqnr; + } + uint32_t GetJitter() const + { + return jitter; + } + uint32_t GetLastSRTimestamp() const + { + return lsr; + } + uint32_t GetDelaySinceLastSR() const + { + return dlsr; + } + RTPTime GetReceiveTime() const + { + return receivetime; + } +private: + bool hasinfo; + double fractionlost; + int32_t packetslost; + uint32_t exthighseqnr; + uint32_t jitter; + uint32_t lsr; + uint32_t dlsr; + RTPTime receivetime; +}; + +class RTPSourceStats +{ +public: + RTPSourceStats(); + void ProcessPacket( + RTPPacket *pack, + const RTPTime &receivetime, + double tsunit, + bool ownpacket, + bool *accept); + + bool HasSentData() const + { + return sentdata; + } + uint32_t GetNumPacketsReceived() const + { + return packetsreceived; + } + uint32_t GetBaseSequenceNumber() const + { + return baseseqnr; + } + uint32_t GetExtendedHighestSequenceNumber() const + { + return exthighseqnr; + } + uint32_t GetJitter() const + { + return jitter; + } + + int32_t GetNumPacketsReceivedInInterval() const + { + return numnewpackets; + } + uint32_t GetSavedExtendedSequenceNumber() const + { + return savedextseqnr; + } + void StartNewInterval() + { + numnewpackets = 0; + savedextseqnr = exthighseqnr; + } + + void SetLastMessageTime(const RTPTime &t) + { + lastmsgtime = t; + } + RTPTime GetLastMessageTime() const + { + return lastmsgtime; + } + void SetLastRTPPacketTime(const RTPTime &t) + { + lastrtptime = t; + } + RTPTime GetLastRTPPacketTime() const + { + return lastrtptime; + } + + void SetLastNoteTime(const RTPTime &t) + { + lastnotetime = t; + } + RTPTime GetLastNoteTime() const + { + return lastnotetime; + } +private: + bool sentdata; + uint32_t packetsreceived; + uint32_t numcycles; // shifted left 16 bits + uint32_t baseseqnr; + uint32_t exthighseqnr, prevexthighseqnr; + uint32_t jitter, prevtimestamp; + double djitter; + RTPTime prevpacktime; + RTPTime lastmsgtime; + RTPTime lastrtptime; + RTPTime lastnotetime; + uint32_t numnewpackets; + uint32_t savedextseqnr; +}; + +inline RTPSourceStats::RTPSourceStats() : + prevpacktime(0, 0), lastmsgtime(0, 0), lastrtptime(0, 0), lastnotetime(0, 0) +{ + sentdata = false; + packetsreceived = 0; + baseseqnr = 0; + exthighseqnr = 0; + prevexthighseqnr = 0; + jitter = 0; + numcycles = 0; + numnewpackets = 0; + prevtimestamp = 0; + djitter = 0; + savedextseqnr = 0; +} + +/** Describes an entry in the RTPSources source table. */ +class RTPSourceData +{ +protected: + RTPSourceData(uint32_t ssrc); + virtual ~RTPSourceData(); +public: + /** Extracts the first packet of this participants RTP packet queue. */ + RTPPacket *GetNextPacket(); + + /** Clears the participant's RTP packet list. */ + void FlushPackets(); + + /** Returns \c true if there are RTP packets which can be extracted. */ + bool HasData() const + { + if (!validated) + return false; + return packetlist.empty() ? false : true; + } + + /** Returns the SSRC identifier for this member. */ + uint32_t GetSSRC() const + { + return ssrc; + } + + /** Returns \c true if the participant was added using the RTPSources member function CreateOwnSSRC and + * returns \c false otherwise. + */ + bool IsOwnSSRC() const + { + return ownssrc; + } + + /** Returns \c true if the source identifier is actually a CSRC from an RTP packet. */ + bool IsCSRC() const + { + return iscsrc; + } + + /** Returns \c true if this member is marked as a sender and \c false if not. */ + bool IsSender() const + { + return issender; + } + + /** Returns \c true if the participant is validated, which is the case if a number of + * consecutive RTP packets have been received or if a CNAME item has been received for + * this participant. + */ + bool IsValidated() const + { + return validated; + } + + /** Returns \c true if the source was validated and had not yet sent a BYE packet. */ + bool IsActive() const + { + if (!validated) + return false; + if (receivedbye) + return false; + return true; + } + + /** This function is used by the RTCPPacketBuilder class to mark whether this participant's + * information has been processed in a report block or not. + */ + void SetProcessedInRTCP(bool v) + { + processedinrtcp = v; + } + + /** This function is used by the RTCPPacketBuilder class and returns whether this participant + * has been processed in a report block or not. + */ + bool IsProcessedInRTCP() const + { + return processedinrtcp; + } + + /** Returns \c true if the address from which this participant's RTP packets originate has + * already been set. + */ + bool IsRTPAddressSet() const + { + return isrtpaddrset; + } + + /** Returns \c true if the address from which this participant's RTCP packets originate has + * already been set. + */ + bool IsRTCPAddressSet() const + { + return isrtcpaddrset; + } + + /** Returns the address from which this participant's RTP packets originate. + * Returns the address from which this participant's RTP packets originate. If the address has + * been set and the returned value is NULL, this indicates that it originated from the local + * participant. + */ + const RTPAddress *GetRTPDataAddress() const + { + return rtpaddr; + } + + /** Returns the address from which this participant's RTCP packets originate. + * Returns the address from which this participant's RTCP packets originate. If the address has + * been set and the returned value is NULL, this indicates that it originated from the local + * participant. + */ + const RTPAddress *GetRTCPDataAddress() const + { + return rtcpaddr; + } + + /** Returns \c true if we received a BYE message for this participant and \c false otherwise. */ + bool ReceivedBYE() const + { + return receivedbye; + } + + /** Returns the reason for leaving contained in the BYE packet of this participant. + * Returns the reason for leaving contained in the BYE packet of this participant. The length of + * the reason is stored in \c len. + */ + uint8_t *GetBYEReason(std::size_t *len) const + { + *len = byereasonlen; + return byereason; + } + + /** Returns the time at which the BYE packet was received. */ + RTPTime GetBYETime() const + { + return byetime; + } + + /** Sets the value for the timestamp unit to be used in jitter calculations for data received from this participant. + * Sets the value for the timestamp unit to be used in jitter calculations for data received from this participant. + * If not set, the library uses an approximation for the timestamp unit which is calculated from two consecutive + * RTCP sender reports. The timestamp unit is defined as a time interval divided by the corresponding timestamp + * interval. For 8000 Hz audio this would be 1/8000. For video, often a timestamp unit of 1/90000 is used. + */ + void SetTimestampUnit(double tsu) + { + timestampunit = tsu; + } + + /** Returns the timestamp unit used for this participant. */ + double GetTimestampUnit() const + { + return timestampunit; + } + + /** Returns \c true if an RTCP sender report has been received from this participant. */ + bool SR_HasInfo() const + { + return SRinf.HasInfo(); + } + + /** Returns the NTP timestamp contained in the last sender report. */ + RTPNTPTime SR_GetNTPTimestamp() const + { + return SRinf.GetNTPTimestamp(); + } + + /** Returns the RTP timestamp contained in the last sender report. */ + uint32_t SR_GetRTPTimestamp() const + { + return SRinf.GetRTPTimestamp(); + } + + /** Returns the packet count contained in the last sender report. */ + uint32_t SR_GetPacketCount() const + { + return SRinf.GetPacketCount(); + } + + /** Returns the octet count contained in the last sender report. */ + uint32_t SR_GetByteCount() const + { + return SRinf.GetByteCount(); + } + + /** Returns the time at which the last sender report was received. */ + RTPTime SR_GetReceiveTime() const + { + return SRinf.GetReceiveTime(); + } + + /** Returns \c true if more than one RTCP sender report has been received. */ + bool SR_Prev_HasInfo() const + { + return SRprevinf.HasInfo(); + } + + /** Returns the NTP timestamp contained in the second to last sender report. */ + RTPNTPTime SR_Prev_GetNTPTimestamp() const + { + return SRprevinf.GetNTPTimestamp(); + } + + /** Returns the RTP timestamp contained in the second to last sender report. */ + uint32_t SR_Prev_GetRTPTimestamp() const + { + return SRprevinf.GetRTPTimestamp(); + } + + /** Returns the packet count contained in the second to last sender report. */ + uint32_t SR_Prev_GetPacketCount() const + { + return SRprevinf.GetPacketCount(); + } + + /** Returns the octet count contained in the second to last sender report. */ + uint32_t SR_Prev_GetByteCount() const + { + return SRprevinf.GetByteCount(); + } + + /** Returns the time at which the second to last sender report was received. */ + RTPTime SR_Prev_GetReceiveTime() const + { + return SRprevinf.GetReceiveTime(); + } + + /** Returns \c true if this participant sent a receiver report with information about the reception of our data. */ + bool RR_HasInfo() const + { + return RRinf.HasInfo(); + } + + /** Returns the fraction lost value from the last report. */ + double RR_GetFractionLost() const + { + return RRinf.GetFractionLost(); + } + + /** Returns the number of lost packets contained in the last report. */ + int32_t RR_GetPacketsLost() const + { + return RRinf.GetPacketsLost(); + } + + /** Returns the extended highest sequence number contained in the last report. */ + uint32_t RR_GetExtendedHighestSequenceNumber() const + { + return RRinf.GetExtendedHighestSequenceNumber(); + } + + /** Returns the jitter value from the last report. */ + uint32_t RR_GetJitter() const + { + return RRinf.GetJitter(); + } + + /** Returns the LSR value from the last report. */ + uint32_t RR_GetLastSRTimestamp() const + { + return RRinf.GetLastSRTimestamp(); + } + + /** Returns the DLSR value from the last report. */ + uint32_t RR_GetDelaySinceLastSR() const + { + return RRinf.GetDelaySinceLastSR(); + } + + /** Returns the time at which the last report was received. */ + RTPTime RR_GetReceiveTime() const + { + return RRinf.GetReceiveTime(); + } + + /** Returns \c true if this participant sent more than one receiver report with information + * about the reception of our data. + */ + bool RR_Prev_HasInfo() const + { + return RRprevinf.HasInfo(); + } + + /** Returns the fraction lost value from the second to last report. */ + double RR_Prev_GetFractionLost() const + { + return RRprevinf.GetFractionLost(); + } + + /** Returns the number of lost packets contained in the second to last report. */ + int32_t RR_Prev_GetPacketsLost() const + { + return RRprevinf.GetPacketsLost(); + } + + /** Returns the extended highest sequence number contained in the second to last report. */ + uint32_t RR_Prev_GetExtendedHighestSequenceNumber() const + { + return RRprevinf.GetExtendedHighestSequenceNumber(); + } + + /** Returns the jitter value from the second to last report. */ + uint32_t RR_Prev_GetJitter() const + { + return RRprevinf.GetJitter(); + } + + /** Returns the LSR value from the second to last report. */ + uint32_t RR_Prev_GetLastSRTimestamp() const + { + return RRprevinf.GetLastSRTimestamp(); + } + + /** Returns the DLSR value from the second to last report. */ + uint32_t RR_Prev_GetDelaySinceLastSR() const + { + return RRprevinf.GetDelaySinceLastSR(); + } + + /** Returns the time at which the second to last report was received. */ + RTPTime RR_Prev_GetReceiveTime() const + { + return RRprevinf.GetReceiveTime(); + } + + /** Returns \c true if validated RTP packets have been received from this participant. */ + bool INF_HasSentData() const + { + return stats.HasSentData(); + } + + /** Returns the total number of received packets from this participant. */ + int32_t INF_GetNumPacketsReceived() const + { + return stats.GetNumPacketsReceived(); + } + + /** Returns the base sequence number of this participant. */ + uint32_t INF_GetBaseSequenceNumber() const + { + return stats.GetBaseSequenceNumber(); + } + + /** Returns the extended highest sequence number received from this participant. */ + uint32_t INF_GetExtendedHighestSequenceNumber() const + { + return stats.GetExtendedHighestSequenceNumber(); + } + + /** Returns the current jitter value for this participant. */ + uint32_t INF_GetJitter() const + { + return stats.GetJitter(); + } + + /** Returns the time at which something was last heard from this member. */ + RTPTime INF_GetLastMessageTime() const + { + return stats.GetLastMessageTime(); + } + + /** Returns the time at which the last RTP packet was received. */ + RTPTime INF_GetLastRTPPacketTime() const + { + return stats.GetLastRTPPacketTime(); + } + + /** Returns the estimated timestamp unit, calculated from two consecutive sender reports. */ + double INF_GetEstimatedTimestampUnit() const; + + /** Returns the number of packets received since a new interval was started with INF_StartNewInterval. */ + uint32_t INF_GetNumPacketsReceivedInInterval() const + { + return stats.GetNumPacketsReceivedInInterval(); + } + + /** Returns the extended sequence number which was stored by the INF_StartNewInterval call. */ + uint32_t INF_GetSavedExtendedSequenceNumber() const + { + return stats.GetSavedExtendedSequenceNumber(); + } + + /** Starts a new interval to count received packets in; this also stores the current extended highest sequence + * number to be able to calculate the packet loss during the interval. + */ + void INF_StartNewInterval() + { + stats.StartNewInterval(); + } + + /** Estimates the round trip time by using the LSR and DLSR info from the last receiver report. */ + RTPTime INF_GetRoundtripTime() const; + + /** Returns the time at which the last SDES NOTE item was received. */ + RTPTime INF_GetLastSDESNoteTime() const + { + return stats.GetLastNoteTime(); + } + + /** Returns a pointer to the SDES CNAME item of this participant and stores its length in \c len. */ + uint8_t *SDES_GetCNAME(std::size_t *len) const + { + return SDESinf.GetCNAME(len); + } + + /** Returns a pointer to the SDES name item of this participant and stores its length in \c len. */ + uint8_t *SDES_GetName(std::size_t *len) const + { + return SDESinf.GetName(len); + } + + /** Returns a pointer to the SDES e-mail item of this participant and stores its length in \c len. */ + uint8_t *SDES_GetEMail(std::size_t *len) const + { + return SDESinf.GetEMail(len); + } + + /** Returns a pointer to the SDES phone item of this participant and stores its length in \c len. */ + uint8_t *SDES_GetPhone(std::size_t *len) const + { + return SDESinf.GetPhone(len); + } + + /** Returns a pointer to the SDES location item of this participant and stores its length in \c len. */ + uint8_t *SDES_GetLocation(std::size_t *len) const + { + return SDESinf.GetLocation(len); + } + + /** Returns a pointer to the SDES tool item of this participant and stores its length in \c len. */ + uint8_t *SDES_GetTool(std::size_t *len) const + { + return SDESinf.GetTool(len); + } + + /** Returns a pointer to the SDES note item of this participant and stores its length in \c len. */ + uint8_t *SDES_GetNote(std::size_t *len) const + { + return SDESinf.GetNote(len); + } + +#ifdef RTP_SUPPORT_SDESPRIV + /** Starts the iteration over the stored SDES private item prefixes and their associated values. */ + void SDES_GotoFirstPrivateValue() + { + SDESinf.GotoFirstPrivateValue(); + } + + /** If available, returns \c true and stores the next SDES private item prefix in \c prefix and its length in + * \c prefixlen; the associated value and its length are then stored in \c value and \c valuelen. + */ + bool SDES_GetNextPrivateValue(uint8_t **prefix, std::size_t *prefixlen, uint8_t **value, std::size_t *valuelen) + { + return SDESinf.GetNextPrivateValue(prefix, prefixlen, value, valuelen); + } + + /** Looks for the entry which corresponds to the SDES private item prefix \c prefix with length + * \c prefixlen; if found, the function returns \c true and stores the associated value and + * its length in \c value and \c valuelen respectively. + */ + bool SDES_GetPrivateValue(uint8_t *prefix, std::size_t prefixlen, uint8_t **value, std::size_t *valuelen) const + { + return SDESinf.GetPrivateValue(prefix, prefixlen, value, valuelen); + } +#endif // RTP_SUPPORT_SDESPRIV + +protected: + std::list packetlist; + + uint32_t ssrc; + bool ownssrc; + bool iscsrc; + double timestampunit; + bool receivedbye; + bool validated; + bool processedinrtcp; + bool issender; + + RTCPSenderReportInfo SRinf, SRprevinf; + RTCPReceiverReportInfo RRinf, RRprevinf; + RTPSourceStats stats; + RTCPSDESInfo SDESinf; + + bool isrtpaddrset, isrtcpaddrset; + RTPAddress *rtpaddr, *rtcpaddr; + + RTPTime byetime; + uint8_t *byereason; + std::size_t byereasonlen; +}; + +inline RTPPacket *RTPSourceData::GetNextPacket() +{ + if (!validated) + return 0; + + RTPPacket *p; + + if (packetlist.empty()) + return 0; + p = *(packetlist.begin()); + packetlist.pop_front(); + return p; +} + +inline void RTPSourceData::FlushPackets() +{ + std::list::const_iterator it; + + for (it = packetlist.begin(); it != packetlist.end(); ++it) + delete *it; + packetlist.clear(); +} + +} // end namespace + +#endif // RTPSOURCEDATA_H + diff --git a/qrtplib/rtpsources.cpp b/qrtplib/rtpsources.cpp new file mode 100644 index 000000000..3d49a7348 --- /dev/null +++ b/qrtplib/rtpsources.cpp @@ -0,0 +1,1255 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +#include "rtpsources.h" +#include "rtperrors.h" +#include "rtprawpacket.h" +#include "rtpinternalsourcedata.h" +#include "rtptimeutilities.h" +#include "rtpdefines.h" +#include "rtcpcompoundpacket.h" +#include "rtcppacket.h" +#include "rtcpapppacket.h" +#include "rtcpbyepacket.h" +#include "rtcpsdespacket.h" +#include "rtcpsrpacket.h" +#include "rtcprrpacket.h" +#include "rtptransmitter.h" + +namespace qrtplib +{ + +RTPSources::RTPSources() +{ + totalcount = 0; + sendercount = 0; + activecount = 0; + owndata = 0; +} + +RTPSources::~RTPSources() +{ + Clear(); +} + +void RTPSources::Clear() +{ + ClearSourceList(); +} + +void RTPSources::ClearSourceList() +{ + sourcelist.GotoFirstElement(); + while (sourcelist.HasCurrentElement()) + { + RTPInternalSourceData *sourcedata; + + sourcedata = sourcelist.GetCurrentElement(); + delete sourcedata; + sourcelist.GotoNextElement(); + } + sourcelist.Clear(); + owndata = 0; + totalcount = 0; + sendercount = 0; + activecount = 0; +} + +int RTPSources::CreateOwnSSRC(uint32_t ssrc) +{ + if (owndata != 0) + return ERR_RTP_SOURCES_ALREADYHAVEOWNSSRC; + if (GotEntry(ssrc)) + return ERR_RTP_SOURCES_SSRCEXISTS; + + int status; + bool created; + + status = ObtainSourceDataInstance(ssrc, &owndata, &created); + if (status < 0) + { + owndata = 0; // just to make sure + return status; + } + owndata->SetOwnSSRC(); + owndata->SetRTPDataAddress(0); + owndata->SetRTCPDataAddress(0); + + // we've created a validated ssrc, so we should increase activecount + activecount++; + + OnNewSource(owndata); + return 0; +} + +int RTPSources::DeleteOwnSSRC() +{ + if (owndata == 0) + return ERR_RTP_SOURCES_DONTHAVEOWNSSRC; + + uint32_t ssrc = owndata->GetSSRC(); + + sourcelist.GotoElement(ssrc); + sourcelist.DeleteCurrentElement(); + + totalcount--; + if (owndata->IsSender()) + sendercount--; + if (owndata->IsActive()) + activecount--; + + OnRemoveSource(owndata); + + delete owndata; + owndata = 0; + return 0; +} + +void RTPSources::SentRTPPacket() +{ + if (owndata == 0) + return; + + bool prevsender = owndata->IsSender(); + + owndata->SentRTPPacket(); + if (!prevsender && owndata->IsSender()) + sendercount++; +} + +int RTPSources::ProcessRawPacket(RTPRawPacket *rawpack, RTPTransmitter *rtptrans, bool acceptownpackets) +{ + RTPTransmitter *transmitters[1]; + int num; + + transmitters[0] = rtptrans; + if (rtptrans == 0) + num = 0; + else + num = 1; + return ProcessRawPacket(rawpack, transmitters, num, acceptownpackets); +} + +int RTPSources::ProcessRawPacket(RTPRawPacket *rawpack, RTPTransmitter *rtptrans[], int numtrans, bool acceptownpackets) +{ + int status; + + if (rawpack->IsRTP()) // RTP packet + { + RTPPacket *rtppack; + + // First, we'll see if the packet can be parsed + rtppack = new RTPPacket(*rawpack); + + if ((status = rtppack->GetCreationError()) < 0) + { + if (status == ERR_RTP_PACKET_INVALIDPACKET) + { + delete rtppack; + rtppack = 0; + } + else + { + delete rtppack; + return status; + } + } + + // Check if the packet was valid + if (rtppack != 0) + { + bool stored = false; + bool ownpacket = false; + int i; + const RTPAddress& senderaddress = rawpack->GetSenderAddress(); + + for (i = 0; !ownpacket && i < numtrans; i++) + { + if (rtptrans[i]->ComesFromThisTransmitter(senderaddress)) + ownpacket = true; + } + + // Check if the packet is our own. + if (ownpacket) + { + // Now it depends on the user's preference + // what to do with this packet: + if (acceptownpackets) + { + // sender addres for own packets has to be NULL! + if ((status = ProcessRTPPacket(rtppack, rawpack->GetReceiveTime(), 0, &stored)) < 0) + { + if (!stored) + delete rtppack; + return status; + } + } + } + else + { + if ((status = ProcessRTPPacket(rtppack, rawpack->GetReceiveTime(), &senderaddress, &stored)) < 0) + { + if (!stored) + delete rtppack; + return status; + } + } + if (!stored) + delete rtppack; + } + } + else // RTCP packet + { + RTCPCompoundPacket rtcpcomppack(*rawpack); + bool valid = false; + + if ((status = rtcpcomppack.GetCreationError()) < 0) + { + if (status != ERR_RTP_RTCPCOMPOUND_INVALIDPACKET) + return status; + } + else + valid = true; + + if (valid) + { + bool ownpacket = false; + int i; + const RTPAddress& senderaddress = rawpack->GetSenderAddress(); + + for (i = 0; !ownpacket && i < numtrans; i++) + { + if (rtptrans[i]->ComesFromThisTransmitter(senderaddress)) + ownpacket = true; + } + + // First check if it's a packet of this session. + if (ownpacket) + { + if (acceptownpackets) + { + // sender address for own packets has to be NULL + status = ProcessRTCPCompoundPacket(&rtcpcomppack, rawpack->GetReceiveTime(), 0); + if (status < 0) + return status; + } + } + else // not our own packet + { + status = ProcessRTCPCompoundPacket(&rtcpcomppack, rawpack->GetReceiveTime(), &rawpack->GetSenderAddress()); + if (status < 0) + return status; + } + } + } + + return 0; +} + +int RTPSources::ProcessRTPPacket(RTPPacket *rtppack, const RTPTime &receivetime, const RTPAddress *senderaddress, bool *stored) +{ + uint32_t ssrc; + RTPInternalSourceData *srcdat; + int status; + bool created; + + OnRTPPacket(rtppack, receivetime, senderaddress); + + *stored = false; + + ssrc = rtppack->GetSSRC(); + if ((status = ObtainSourceDataInstance(ssrc, &srcdat, &created)) < 0) + return status; + + if (created) + { + if ((status = srcdat->SetRTPDataAddress(senderaddress)) < 0) + return status; + } + else // got a previously existing source + { + if (CheckCollision(srcdat, senderaddress, true)) + return 0; // ignore packet on collision + } + + bool prevsender = srcdat->IsSender(); + bool prevactive = srcdat->IsActive(); + + uint32_t CSRCs[RTP_MAXCSRCS]; + int numCSRCs = rtppack->GetCSRCCount(); + if (numCSRCs > RTP_MAXCSRCS) // shouldn't happen, but better to check than go out of bounds + numCSRCs = RTP_MAXCSRCS; + + for (int i = 0; i < numCSRCs; i++) + CSRCs[i] = rtppack->GetCSRC(i); + + // The packet comes from a valid source, we can process it further now + // The following function should delete rtppack itself if something goes + // wrong + if ((status = srcdat->ProcessRTPPacket(rtppack, receivetime, stored, this)) < 0) + return status; + + // NOTE: we cannot use 'rtppack' anymore since it may have been deleted in + // OnValidatedRTPPacket + + if (!prevsender && srcdat->IsSender()) + sendercount++; + if (!prevactive && srcdat->IsActive()) + activecount++; + + if (created) + OnNewSource(srcdat); + + if (srcdat->IsValidated()) // process the CSRCs + { + RTPInternalSourceData *csrcdat; + bool createdcsrc; + + int num = numCSRCs; + int i; + + for (i = 0; i < num; i++) + { + if ((status = ObtainSourceDataInstance(CSRCs[i], &csrcdat, &createdcsrc)) < 0) + return status; + if (createdcsrc) + { + csrcdat->SetCSRC(); + if (csrcdat->IsActive()) + activecount++; + OnNewSource(csrcdat); + } + else // already found an entry, possibly because of RTCP data + { + if (!CheckCollision(csrcdat, senderaddress, true)) + csrcdat->SetCSRC(); + } + } + } + + return 0; +} + +int RTPSources::ProcessRTCPCompoundPacket(RTCPCompoundPacket *rtcpcomppack, const RTPTime &receivetime, const RTPAddress *senderaddress) +{ + RTCPPacket *rtcppack; + int status; + bool gotownssrc = ((owndata == 0) ? false : true); + uint32_t ownssrc = ((owndata != 0) ? owndata->GetSSRC() : 0); + + OnRTCPCompoundPacket(rtcpcomppack, receivetime, senderaddress); + + rtcpcomppack->GotoFirstPacket(); + while ((rtcppack = rtcpcomppack->GetNextPacket()) != 0) + { + if (rtcppack->IsKnownFormat()) + { + switch (rtcppack->GetPacketType()) + { + case RTCPPacket::SR: + { + RTCPSRPacket *p = (RTCPSRPacket *) rtcppack; + uint32_t senderssrc = p->GetSenderSSRC(); + + status = ProcessRTCPSenderInfo(senderssrc, p->GetNTPTimestamp(), p->GetRTPTimestamp(), p->GetSenderPacketCount(), p->GetSenderOctetCount(), receivetime, + senderaddress); + if (status < 0) + return status; + + bool gotinfo = false; + if (gotownssrc) + { + int i; + int num = p->GetReceptionReportCount(); + for (i = 0; i < num; i++) + { + if (p->GetSSRC(i) == ownssrc) // data is meant for us + { + gotinfo = true; + status = ProcessRTCPReportBlock(senderssrc, p->GetFractionLost(i), p->GetLostPacketCount(i), p->GetExtendedHighestSequenceNumber(i), p->GetJitter(i), + p->GetLSR(i), p->GetDLSR(i), receivetime, senderaddress); + if (status < 0) + return status; + } + } + } + if (!gotinfo) + { + status = UpdateReceiveTime(senderssrc, receivetime, senderaddress); + if (status < 0) + return status; + } + } + break; + case RTCPPacket::RR: + { + RTCPRRPacket *p = (RTCPRRPacket *) rtcppack; + uint32_t senderssrc = p->GetSenderSSRC(); + + bool gotinfo = false; + + if (gotownssrc) + { + int i; + int num = p->GetReceptionReportCount(); + for (i = 0; i < num; i++) + { + if (p->GetSSRC(i) == ownssrc) + { + gotinfo = true; + status = ProcessRTCPReportBlock(senderssrc, p->GetFractionLost(i), p->GetLostPacketCount(i), p->GetExtendedHighestSequenceNumber(i), p->GetJitter(i), + p->GetLSR(i), p->GetDLSR(i), receivetime, senderaddress); + if (status < 0) + return status; + } + } + } + if (!gotinfo) + { + status = UpdateReceiveTime(senderssrc, receivetime, senderaddress); + if (status < 0) + return status; + } + } + break; + case RTCPPacket::SDES: + { + RTCPSDESPacket *p = (RTCPSDESPacket *) rtcppack; + + if (p->GotoFirstChunk()) + { + do + { + uint32_t sdesssrc = p->GetChunkSSRC(); + bool updated = false; + if (p->GotoFirstItem()) + { + do + { + RTCPSDESPacket::ItemType t; + + if ((t = p->GetItemType()) != RTCPSDESPacket::PRIV) + { + updated = true; + status = ProcessSDESNormalItem(sdesssrc, t, p->GetItemLength(), p->GetItemData(), receivetime, senderaddress); + if (status < 0) + return status; + } +#ifdef RTP_SUPPORT_SDESPRIV + else + { + updated = true; + status = ProcessSDESPrivateItem(sdesssrc, p->GetPRIVPrefixLength(), p->GetPRIVPrefixData(), p->GetPRIVValueLength(), p->GetPRIVValueData(), + receivetime, senderaddress); + if (status < 0) + return status; + } +#endif // RTP_SUPPORT_SDESPRIV + } while (p->GotoNextItem()); + } + if (!updated) + { + status = UpdateReceiveTime(sdesssrc, receivetime, senderaddress); + if (status < 0) + return status; + } + } while (p->GotoNextChunk()); + } + } + break; + case RTCPPacket::BYE: + { + RTCPBYEPacket *p = (RTCPBYEPacket *) rtcppack; + int i; + int num = p->GetSSRCCount(); + + for (i = 0; i < num; i++) + { + uint32_t byessrc = p->GetSSRC(i); + status = ProcessBYE(byessrc, p->GetReasonLength(), p->GetReasonData(), receivetime, senderaddress); + if (status < 0) + return status; + } + } + break; + case RTCPPacket::APP: + { + RTCPAPPPacket *p = (RTCPAPPPacket *) rtcppack; + + OnAPPPacket(p, receivetime, senderaddress); + } + break; + case RTCPPacket::Unknown: + default: + { + OnUnknownPacketType(rtcppack, receivetime, senderaddress); + } + break; + } + } + else + { + OnUnknownPacketFormat(rtcppack, receivetime, senderaddress); + } + } + + return 0; +} + +bool RTPSources::GotoFirstSource() +{ + sourcelist.GotoFirstElement(); + if (sourcelist.HasCurrentElement()) + return true; + return false; +} + +bool RTPSources::GotoNextSource() +{ + sourcelist.GotoNextElement(); + if (sourcelist.HasCurrentElement()) + return true; + return false; +} + +bool RTPSources::GotoPreviousSource() +{ + sourcelist.GotoPreviousElement(); + if (sourcelist.HasCurrentElement()) + return true; + return false; +} + +bool RTPSources::GotoFirstSourceWithData() +{ + bool found = false; + + sourcelist.GotoFirstElement(); + while (!found && sourcelist.HasCurrentElement()) + { + RTPInternalSourceData *srcdat; + + srcdat = sourcelist.GetCurrentElement(); + if (srcdat->HasData()) + found = true; + else + sourcelist.GotoNextElement(); + } + + return found; +} + +bool RTPSources::GotoNextSourceWithData() +{ + bool found = false; + + sourcelist.GotoNextElement(); + while (!found && sourcelist.HasCurrentElement()) + { + RTPInternalSourceData *srcdat; + + srcdat = sourcelist.GetCurrentElement(); + if (srcdat->HasData()) + found = true; + else + sourcelist.GotoNextElement(); + } + + return found; +} + +bool RTPSources::GotoPreviousSourceWithData() +{ + bool found = false; + + sourcelist.GotoPreviousElement(); + while (!found && sourcelist.HasCurrentElement()) + { + RTPInternalSourceData *srcdat; + + srcdat = sourcelist.GetCurrentElement(); + if (srcdat->HasData()) + found = true; + else + sourcelist.GotoPreviousElement(); + } + + return found; +} + +RTPSourceData *RTPSources::GetCurrentSourceInfo() +{ + if (!sourcelist.HasCurrentElement()) + return 0; + return sourcelist.GetCurrentElement(); +} + +RTPSourceData *RTPSources::GetSourceInfo(uint32_t ssrc) +{ + if (sourcelist.GotoElement(ssrc) < 0) + return 0; + if (!sourcelist.HasCurrentElement()) + return 0; + return sourcelist.GetCurrentElement(); +} + +bool RTPSources::GotEntry(uint32_t ssrc) +{ + return sourcelist.HasElement(ssrc); +} + +RTPPacket *RTPSources::GetNextPacket() +{ + if (!sourcelist.HasCurrentElement()) + return 0; + + RTPInternalSourceData *srcdat = sourcelist.GetCurrentElement(); + RTPPacket *pack = srcdat->GetNextPacket(); + return pack; +} + +int RTPSources::ProcessRTCPSenderInfo(uint32_t ssrc, const RTPNTPTime &ntptime, uint32_t rtptime, uint32_t packetcount, uint32_t octetcount, const RTPTime &receivetime, + const RTPAddress *senderaddress) +{ + RTPInternalSourceData *srcdat; + bool created; + int status; + + status = GetRTCPSourceData(ssrc, senderaddress, &srcdat, &created); + if (status < 0) + return status; + if (srcdat == 0) + return 0; + + srcdat->ProcessSenderInfo(ntptime, rtptime, packetcount, octetcount, receivetime); + + // Call the callback + if (created) + OnNewSource(srcdat); + + OnRTCPSenderReport(srcdat); + + return 0; +} + +int RTPSources::ProcessRTCPReportBlock(uint32_t ssrc, uint8_t fractionlost, int32_t lostpackets, uint32_t exthighseqnr, uint32_t jitter, uint32_t lsr, uint32_t dlsr, + const RTPTime &receivetime, const RTPAddress *senderaddress) +{ + RTPInternalSourceData *srcdat; + bool created; + int status; + + status = GetRTCPSourceData(ssrc, senderaddress, &srcdat, &created); + if (status < 0) + return status; + if (srcdat == 0) + return 0; + + srcdat->ProcessReportBlock(fractionlost, lostpackets, exthighseqnr, jitter, lsr, dlsr, receivetime); + + // Call the callback + if (created) + OnNewSource(srcdat); + + OnRTCPReceiverReport(srcdat); + + return 0; +} + +int RTPSources::ProcessSDESNormalItem(uint32_t ssrc, RTCPSDESPacket::ItemType t, std::size_t itemlength, const void *itemdata, const RTPTime &receivetime, + const RTPAddress *senderaddress) +{ + RTPInternalSourceData *srcdat; + bool created, cnamecollis; + int status; + uint8_t sdesid; + bool prevactive; + + switch (t) + { + case RTCPSDESPacket::CNAME: + sdesid = RTCP_SDES_ID_CNAME; + break; + case RTCPSDESPacket::NAME: + sdesid = RTCP_SDES_ID_NAME; + break; + case RTCPSDESPacket::EMAIL: + sdesid = RTCP_SDES_ID_EMAIL; + break; + case RTCPSDESPacket::PHONE: + sdesid = RTCP_SDES_ID_PHONE; + break; + case RTCPSDESPacket::LOC: + sdesid = RTCP_SDES_ID_LOCATION; + break; + case RTCPSDESPacket::TOOL: + sdesid = RTCP_SDES_ID_TOOL; + break; + case RTCPSDESPacket::NOTE: + sdesid = RTCP_SDES_ID_NOTE; + break; + default: + return ERR_RTP_SOURCES_ILLEGALSDESTYPE; + } + + status = GetRTCPSourceData(ssrc, senderaddress, &srcdat, &created); + if (status < 0) + return status; + if (srcdat == 0) + return 0; + + prevactive = srcdat->IsActive(); + status = srcdat->ProcessSDESItem(sdesid, (const uint8_t *) itemdata, itemlength, receivetime, &cnamecollis); + if (!prevactive && srcdat->IsActive()) + activecount++; + + // Call the callback + if (created) + OnNewSource(srcdat); + if (cnamecollis) + OnCNAMECollision(srcdat, senderaddress, (const uint8_t *) itemdata, itemlength); + + if (status >= 0) + OnRTCPSDESItem(srcdat, t, itemdata, itemlength); + + return status; +} + +#ifdef RTP_SUPPORT_SDESPRIV +int RTPSources::ProcessSDESPrivateItem(uint32_t ssrc, std::size_t prefixlen, const void *prefixdata, std::size_t valuelen, const void *valuedata, const RTPTime &receivetime, + const RTPAddress *senderaddress) +{ + RTPInternalSourceData *srcdat; + bool created; + int status; + + status = GetRTCPSourceData(ssrc, senderaddress, &srcdat, &created); + if (status < 0) + return status; + if (srcdat == 0) + return 0; + + status = srcdat->ProcessPrivateSDESItem((const uint8_t *) prefixdata, prefixlen, (const uint8_t *) valuedata, valuelen, receivetime); + // Call the callback + if (created) + OnNewSource(srcdat); + + if (status >= 0) + OnRTCPSDESPrivateItem(srcdat, prefixdata, prefixlen, valuedata, valuelen); + + return status; +} +#endif //RTP_SUPPORT_SDESPRIV + +int RTPSources::ProcessBYE(uint32_t ssrc, std::size_t reasonlength, const void *reasondata, const RTPTime &receivetime, const RTPAddress *senderaddress) +{ + RTPInternalSourceData *srcdat; + bool created; + int status; + bool prevactive; + + status = GetRTCPSourceData(ssrc, senderaddress, &srcdat, &created); + if (status < 0) + return status; + if (srcdat == 0) + return 0; + + // we'll ignore BYE packets for our own ssrc + if (srcdat == owndata) + return 0; + + prevactive = srcdat->IsActive(); + srcdat->ProcessBYEPacket((const uint8_t *) reasondata, reasonlength, receivetime); + if (prevactive && !srcdat->IsActive()) + activecount--; + + // Call the callback + if (created) + OnNewSource(srcdat); + OnBYEPacket(srcdat); + return 0; +} + +int RTPSources::ObtainSourceDataInstance(uint32_t ssrc, RTPInternalSourceData **srcdat, bool *created) +{ + RTPInternalSourceData *srcdat2; + int status; + + if (sourcelist.GotoElement(ssrc) < 0) // No entry for this source + { + srcdat2 = new RTPInternalSourceData(ssrc); + if ((status = sourcelist.AddElement(ssrc, srcdat2)) < 0) + { + delete srcdat2; + return status; + } + *srcdat = srcdat2; + *created = true; + totalcount++; + } + else + { + *srcdat = sourcelist.GetCurrentElement(); + *created = false; + } + return 0; +} + +int RTPSources::GetRTCPSourceData(uint32_t ssrc, const RTPAddress *senderaddress, RTPInternalSourceData **srcdat2, bool *newsource) +{ + int status; + bool created; + RTPInternalSourceData *srcdat; + + *srcdat2 = 0; + + if ((status = ObtainSourceDataInstance(ssrc, &srcdat, &created)) < 0) + return status; + + if (created) + { + if ((status = srcdat->SetRTCPDataAddress(senderaddress)) < 0) + return status; + } + else // got a previously existing source + { + if (CheckCollision(srcdat, senderaddress, false)) + return 0; // ignore packet on collision + } + + *srcdat2 = srcdat; + *newsource = created; + + return 0; +} + +int RTPSources::UpdateReceiveTime(uint32_t ssrc, const RTPTime &receivetime, const RTPAddress *senderaddress) +{ + RTPInternalSourceData *srcdat; + bool created; + int status; + + status = GetRTCPSourceData(ssrc, senderaddress, &srcdat, &created); + if (status < 0) + return status; + if (srcdat == 0) + return 0; + + // We got valid SSRC info + srcdat->UpdateMessageTime(receivetime); + + // Call the callback + if (created) + OnNewSource(srcdat); + + return 0; +} + +void RTPSources::Timeout(const RTPTime &curtime, const RTPTime &timeoutdelay) +{ + int newtotalcount = 0; + int newsendercount = 0; + int newactivecount = 0; + RTPTime checktime = curtime; + checktime -= timeoutdelay; + + sourcelist.GotoFirstElement(); + while (sourcelist.HasCurrentElement()) + { + RTPInternalSourceData *srcdat = sourcelist.GetCurrentElement(); + RTPTime lastmsgtime = srcdat->INF_GetLastMessageTime(); + + // we don't want to time out ourselves + if ((srcdat != owndata) && (lastmsgtime < checktime)) // timeout + { + + totalcount--; + if (srcdat->IsSender()) + sendercount--; + if (srcdat->IsActive()) + activecount--; + + sourcelist.DeleteCurrentElement(); + + OnTimeout(srcdat); + OnRemoveSource(srcdat); + delete srcdat; + } + else + { + newtotalcount++; + if (srcdat->IsSender()) + newsendercount++; + if (srcdat->IsActive()) + newactivecount++; + sourcelist.GotoNextElement(); + } + } + + totalcount = newtotalcount; // just to play it safe + sendercount = newsendercount; + activecount = newactivecount; +} + +void RTPSources::SenderTimeout(const RTPTime &curtime, const RTPTime &timeoutdelay) +{ + int newtotalcount = 0; + int newsendercount = 0; + int newactivecount = 0; + RTPTime checktime = curtime; + checktime -= timeoutdelay; + + sourcelist.GotoFirstElement(); + while (sourcelist.HasCurrentElement()) + { + RTPInternalSourceData *srcdat = sourcelist.GetCurrentElement(); + + newtotalcount++; + if (srcdat->IsActive()) + newactivecount++; + + if (srcdat->IsSender()) + { + RTPTime lastrtppacktime = srcdat->INF_GetLastRTPPacketTime(); + + if (lastrtppacktime < checktime) // timeout + { + srcdat->ClearSenderFlag(); + sendercount--; + } + else + newsendercount++; + } + sourcelist.GotoNextElement(); + } + + totalcount = newtotalcount; // just to play it safe + sendercount = newsendercount; + activecount = newactivecount; +} + +void RTPSources::BYETimeout(const RTPTime &curtime, const RTPTime &timeoutdelay) +{ + int newtotalcount = 0; + int newsendercount = 0; + int newactivecount = 0; + RTPTime checktime = curtime; + checktime -= timeoutdelay; + + sourcelist.GotoFirstElement(); + while (sourcelist.HasCurrentElement()) + { + RTPInternalSourceData *srcdat = sourcelist.GetCurrentElement(); + + if (srcdat->ReceivedBYE()) + { + RTPTime byetime = srcdat->GetBYETime(); + + if ((srcdat != owndata) && (checktime > byetime)) + { + totalcount--; + if (srcdat->IsSender()) + sendercount--; + if (srcdat->IsActive()) + activecount--; + sourcelist.DeleteCurrentElement(); + OnBYETimeout(srcdat); + OnRemoveSource(srcdat); + delete srcdat; + } + else + { + newtotalcount++; + if (srcdat->IsSender()) + newsendercount++; + if (srcdat->IsActive()) + newactivecount++; + sourcelist.GotoNextElement(); + } + } + else + { + newtotalcount++; + if (srcdat->IsSender()) + newsendercount++; + if (srcdat->IsActive()) + newactivecount++; + sourcelist.GotoNextElement(); + } + } + + totalcount = newtotalcount; // just to play it safe + sendercount = newsendercount; + activecount = newactivecount; +} + +void RTPSources::NoteTimeout(const RTPTime &curtime, const RTPTime &timeoutdelay) +{ + int newtotalcount = 0; + int newsendercount = 0; + int newactivecount = 0; + RTPTime checktime = curtime; + checktime -= timeoutdelay; + + sourcelist.GotoFirstElement(); + while (sourcelist.HasCurrentElement()) + { + RTPInternalSourceData *srcdat = sourcelist.GetCurrentElement(); + std::size_t notelen; + + srcdat->SDES_GetNote(¬elen); + if (notelen != 0) // Note has been set + { + RTPTime notetime = srcdat->INF_GetLastSDESNoteTime(); + + if (checktime > notetime) + { + srcdat->ClearNote(); + OnNoteTimeout(srcdat); + } + } + + newtotalcount++; + if (srcdat->IsSender()) + newsendercount++; + if (srcdat->IsActive()) + newactivecount++; + sourcelist.GotoNextElement(); + } + + totalcount = newtotalcount; // just to play it safe + sendercount = newsendercount; + activecount = newactivecount; + +} + +void RTPSources::MultipleTimeouts(const RTPTime &curtime, const RTPTime &sendertimeout, const RTPTime &byetimeout, const RTPTime &generaltimeout, const RTPTime ¬etimeout) +{ + int newtotalcount = 0; + int newsendercount = 0; + int newactivecount = 0; + RTPTime senderchecktime = curtime; + RTPTime byechecktime = curtime; + RTPTime generaltchecktime = curtime; + RTPTime notechecktime = curtime; + senderchecktime -= sendertimeout; + byechecktime -= byetimeout; + generaltchecktime -= generaltimeout; + notechecktime -= notetimeout; + + sourcelist.GotoFirstElement(); + while (sourcelist.HasCurrentElement()) + { + RTPInternalSourceData *srcdat = sourcelist.GetCurrentElement(); + bool deleted, issender, isactive; + bool byetimeout, normaltimeout, notetimeout; + + std::size_t notelen; + + issender = srcdat->IsSender(); + isactive = srcdat->IsActive(); + deleted = false; + byetimeout = false; + normaltimeout = false; + notetimeout = false; + + srcdat->SDES_GetNote(¬elen); + if (notelen != 0) // Note has been set + { + RTPTime notetime = srcdat->INF_GetLastSDESNoteTime(); + + if (notechecktime > notetime) + { + notetimeout = true; + srcdat->ClearNote(); + } + } + + if (srcdat->ReceivedBYE()) + { + RTPTime byetime = srcdat->GetBYETime(); + + if ((srcdat != owndata) && (byechecktime > byetime)) + { + sourcelist.DeleteCurrentElement(); + deleted = true; + byetimeout = true; + } + } + + if (!deleted) + { + RTPTime lastmsgtime = srcdat->INF_GetLastMessageTime(); + + if ((srcdat != owndata) && (lastmsgtime < generaltchecktime)) + { + sourcelist.DeleteCurrentElement(); + deleted = true; + normaltimeout = true; + } + } + + if (!deleted) + { + newtotalcount++; + + if (issender) + { + RTPTime lastrtppacktime = srcdat->INF_GetLastRTPPacketTime(); + + if (lastrtppacktime < senderchecktime) + { + srcdat->ClearSenderFlag(); + sendercount--; + } + else + newsendercount++; + } + + if (isactive) + newactivecount++; + + if (notetimeout) + OnNoteTimeout(srcdat); + + sourcelist.GotoNextElement(); + } + else // deleted entry + { + if (issender) + sendercount--; + if (isactive) + activecount--; + totalcount--; + + if (byetimeout) + OnBYETimeout(srcdat); + if (normaltimeout) + OnTimeout(srcdat); + OnRemoveSource(srcdat); + delete srcdat; + } + } + + totalcount = newtotalcount; // just to play it safe + sendercount = newsendercount; + activecount = newactivecount; +} + +bool RTPSources::CheckCollision(RTPInternalSourceData *srcdat, const RTPAddress *senderaddress, bool isrtp) +{ + bool isset, otherisset; + const RTPAddress *addr, *otheraddr; + + if (isrtp) + { + isset = srcdat->IsRTPAddressSet(); + addr = srcdat->GetRTPDataAddress(); + otherisset = srcdat->IsRTCPAddressSet(); + otheraddr = srcdat->GetRTCPDataAddress(); + } + else + { + isset = srcdat->IsRTCPAddressSet(); + addr = srcdat->GetRTCPDataAddress(); + otherisset = srcdat->IsRTPAddressSet(); + otheraddr = srcdat->GetRTPDataAddress(); + } + + if (!isset) + { + if (otherisset) // got other address, can check if it comes from same host + { + if (otheraddr == 0) // other came from our own session + { + if (senderaddress != 0) + { + OnSSRCCollision(srcdat, senderaddress, isrtp); + return true; + } + + // Ok, store it + + if (isrtp) + srcdat->SetRTPDataAddress(senderaddress); + else + srcdat->SetRTCPDataAddress(senderaddress); + } + else + { + if (!otheraddr->IsFromSameHost(senderaddress)) + { + OnSSRCCollision(srcdat, senderaddress, isrtp); + return true; + } + + // Ok, comes from same host, store the address + + if (isrtp) + srcdat->SetRTPDataAddress(senderaddress); + else + srcdat->SetRTCPDataAddress(senderaddress); + } + } + else // no other address, store this one + { + if (isrtp) + srcdat->SetRTPDataAddress(senderaddress); + else + srcdat->SetRTCPDataAddress(senderaddress); + } + } + else // already got an address + { + if (addr == 0) + { + if (senderaddress != 0) + { + OnSSRCCollision(srcdat, senderaddress, isrtp); + return true; + } + } + else + { + if (!addr->IsSameAddress(senderaddress)) + { + OnSSRCCollision(srcdat, senderaddress, isrtp); + return true; + } + } + } + + return false; +} + +} // end namespace + diff --git a/qrtplib/rtpsources.h b/qrtplib/rtpsources.h new file mode 100644 index 000000000..4b5c2e1c3 --- /dev/null +++ b/qrtplib/rtpsources.h @@ -0,0 +1,434 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +/** + * \file rtpsources.h + */ + +#ifndef RTPSOURCES_H + +#define RTPSOURCES_H + +#include "rtpconfig.h" +#include "rtpkeyhashtable.h" +#include "rtcpsdespacket.h" +#include "rtptypes.h" + +#include "export.h" + +#define RTPSOURCES_HASHSIZE 8317 + +namespace qrtplib +{ + +class QRTPLIB_API RTPSources_GetHashIndex +{ +public: + static int GetIndex(const uint32_t &ssrc) + { + return ssrc % RTPSOURCES_HASHSIZE; + } +}; + +class RTPNTPTime; +class RTPTransmitter; +class RTCPAPPPacket; +class RTPInternalSourceData; +class RTPRawPacket; +class RTPPacket; +class RTPTime; +class RTPAddress; +class RTPSourceData; + +/** Represents a table in which information about the participating sources is kept. + * Represents a table in which information about the participating sources is kept. The class has member + * functions to process RTP and RTCP data and to iterate over the participants. Note that a NULL address + * is used to identify packets from our own session. The class also provides some overridable functions + * which can be used to catch certain events (new SSRC, SSRC collision, ...). + */ +class QRTPLIB_API RTPSources +{ +public: + /** Type of probation to use for new sources. */ + enum ProbationType + { + NoProbation, /**< Don't use the probation algorithm; accept RTP packets immediately. */ + ProbationDiscard, /**< Discard incoming RTP packets originating from a source that's on probation. */ + ProbationStore /**< Store incoming RTP packet from a source that's on probation for later retrieval. */ + }; + + /** In the constructor you can select the probation type you'd like to use and also a memory manager. */ + RTPSources(); + virtual ~RTPSources(); + + /** Clears the source table. */ + void Clear(); + + /** Creates an entry for our own SSRC identifier. */ + int CreateOwnSSRC(uint32_t ssrc); + + /** Deletes the entry for our own SSRC identifier. */ + int DeleteOwnSSRC(); + + /** This function should be called if our own session has sent an RTP packet. + * This function should be called if our own session has sent an RTP packet. + * For our own SSRC entry, the sender flag is updated based upon outgoing packets instead of incoming packets. + */ + void SentRTPPacket(); + + /** Processes a raw packet \c rawpack. + * Processes a raw packet \c rawpack. The instance \c trans will be used to check if this + * packet is one of our own packets. The flag \c acceptownpackets indicates whether own packets should be + * accepted or ignored. + */ + int ProcessRawPacket(RTPRawPacket *rawpack, RTPTransmitter *trans, bool acceptownpackets); + + /** Processes a raw packet \c rawpack. + * Processes a raw packet \c rawpack. Every transmitter in the array \c trans of length \c numtrans + * is used to check if the packet is from our own session. The flag \c acceptownpackets indicates + * whether own packets should be accepted or ignored. + */ + int ProcessRawPacket(RTPRawPacket *rawpack, RTPTransmitter *trans[], int numtrans, bool acceptownpackets); + + /** Processes an RTPPacket instance \c rtppack which was received at time \c receivetime and + * which originated from \c senderaddres. + * Processes an RTPPacket instance \c rtppack which was received at time \c receivetime and + * which originated from \c senderaddres. The \c senderaddress parameter must be NULL if + * the packet was sent by the local participant. The flag \c stored indicates whether the packet + * was stored in the table or not. If so, the \c rtppack instance may not be deleted. + */ + int ProcessRTPPacket(RTPPacket *rtppack, const RTPTime &receivetime, const RTPAddress *senderaddress, bool *stored); + + /** Processes the RTCP compound packet \c rtcpcomppack which was received at time \c receivetime from \c senderaddress. + * Processes the RTCP compound packet \c rtcpcomppack which was received at time \c receivetime from \c senderaddress. + * The \c senderaddress parameter must be NULL if the packet was sent by the local participant. + */ + int ProcessRTCPCompoundPacket(RTCPCompoundPacket *rtcpcomppack, const RTPTime &receivetime, const RTPAddress *senderaddress); + + /** Process the sender information of SSRC \c ssrc into the source table. + * Process the sender information of SSRC \c ssrc into the source table. The information was received + * at time \c receivetime from address \c senderaddress. The \c senderaddress} parameter must be NULL + * if the packet was sent by the local participant. + */ + int ProcessRTCPSenderInfo(uint32_t ssrc, const RTPNTPTime &ntptime, uint32_t rtptime, uint32_t packetcount, uint32_t octetcount, const RTPTime &receivetime, + const RTPAddress *senderaddress); + + /** Processes the report block information which was sent by participant \c ssrc into the source table. + * Processes the report block information which was sent by participant \c ssrc into the source table. + * The information was received at time \c receivetime from address \c senderaddress The \c senderaddress + * parameter must be NULL if the packet was sent by the local participant. + */ + int ProcessRTCPReportBlock(uint32_t ssrc, uint8_t fractionlost, int32_t lostpackets, uint32_t exthighseqnr, uint32_t jitter, uint32_t lsr, uint32_t dlsr, + const RTPTime &receivetime, const RTPAddress *senderaddress); + + /** Processes the non-private SDES item from source \c ssrc into the source table. + * Processes the non-private SDES item from source \c ssrc into the source table. The information was + * received at time \c receivetime from address \c senderaddress. The \c senderaddress parameter must + * be NULL if the packet was sent by the local participant. + */ + int ProcessSDESNormalItem(uint32_t ssrc, RTCPSDESPacket::ItemType t, std::size_t itemlength, const void *itemdata, const RTPTime &receivetime, const RTPAddress *senderaddress); +#ifdef RTP_SUPPORT_SDESPRIV + /** Processes the SDES private item from source \c ssrc into the source table. + * Processes the SDES private item from source \c ssrc into the source table. The information was + * received at time \c receivetime from address \c senderaddress. The \c senderaddress + * parameter must be NULL if the packet was sent by the local participant. + */ + int ProcessSDESPrivateItem(uint32_t ssrc, std::size_t prefixlen, const void *prefixdata, std::size_t valuelen, const void *valuedata, const RTPTime &receivetime, + const RTPAddress *senderaddress); +#endif //RTP_SUPPORT_SDESPRIV + /** Processes the BYE message for SSRC \c ssrc. + * Processes the BYE message for SSRC \c ssrc. The information was received at time \c receivetime from + * address \c senderaddress. The \c senderaddress parameter must be NULL if the packet was sent by the + * local participant. + */ + int ProcessBYE(uint32_t ssrc, std::size_t reasonlength, const void *reasondata, const RTPTime &receivetime, const RTPAddress *senderaddress); + + /** If we heard from source \c ssrc, but no actual data was added to the source table (for example, if + * no report block was meant for us), this function can e used to indicate that something was received from + * this source. + * If we heard from source \c ssrc, but no actual data was added to the source table (for example, if + * no report block was meant for us), this function can e used to indicate that something was received from + * this source. This will prevent a premature timeout for this participant. The message was received at time + * \c receivetime from address \c senderaddress. The \c senderaddress parameter must be NULL if the + * packet was sent by the local participant. + */ + int UpdateReceiveTime(uint32_t ssrc, const RTPTime &receivetime, const RTPAddress *senderaddress); + + /** Starts the iteration over the participants by going to the first member in the table. + * Starts the iteration over the participants by going to the first member in the table. + * If a member was found, the function returns \c true, otherwise it returns \c false. + */ + bool GotoFirstSource(); + + /** Sets the current source to be the next source in the table. + * Sets the current source to be the next source in the table. If we're already at the last source, + * the function returns \c false, otherwise it returns \c true. + */ + bool GotoNextSource(); + + /** Sets the current source to be the previous source in the table. + * Sets the current source to be the previous source in the table. If we're at the first source, + * the function returns \c false, otherwise it returns \c true. + */ + bool GotoPreviousSource(); + + /** Sets the current source to be the first source in the table which has RTPPacket instances + * that we haven't extracted yet. + * Sets the current source to be the first source in the table which has RTPPacket instances + * that we haven't extracted yet. If no such member was found, the function returns \c false, + * otherwise it returns \c true. + */ + bool GotoFirstSourceWithData(); + + /** Sets the current source to be the next source in the table which has RTPPacket instances that + * we haven't extracted yet. + * Sets the current source to be the next source in the table which has RTPPacket instances that + * we haven't extracted yet. If no such member was found, the function returns \c false, + * otherwise it returns \c true. + */ + bool GotoNextSourceWithData(); + + /** Sets the current source to be the previous source in the table which has RTPPacket instances + * that we haven't extracted yet. + * Sets the current source to be the previous source in the table which has RTPPacket instances + * that we haven't extracted yet. If no such member was found, the function returns \c false, + * otherwise it returns \c true. + */ + bool GotoPreviousSourceWithData(); + + /** Returns the RTPSourceData instance for the currently selected participant. */ + RTPSourceData *GetCurrentSourceInfo(); + + /** Returns the RTPSourceData instance for the participant identified by \c ssrc, or + * NULL if no such entry exists. + */ + RTPSourceData *GetSourceInfo(uint32_t ssrc); + + /** Extracts the next packet from the received packets queue of the current participant. */ + RTPPacket *GetNextPacket(); + + /** Returns \c true if an entry for participant \c ssrc exists and \c false otherwise. */ + bool GotEntry(uint32_t ssrc); + + /** If present, it returns the RTPSourceData instance of the entry which was created by CreateOwnSSRC. */ + RTPSourceData *GetOwnSourceInfo() + { + return (RTPSourceData *) owndata; + } + + /** Assuming that the current time is \c curtime, time out the members from whom we haven't heard + * during the previous time interval \c timeoutdelay. + */ + void Timeout(const RTPTime &curtime, const RTPTime &timeoutdelay); + + /** Assuming that the current time is \c curtime, remove the sender flag for senders from whom we haven't + * received any RTP packets during the previous time interval \c timeoutdelay. + */ + void SenderTimeout(const RTPTime &curtime, const RTPTime &timeoutdelay); + + /** Assuming that the current time is \c curtime, remove the members who sent a BYE packet more than + * the time interval \c timeoutdelay ago. + */ + void BYETimeout(const RTPTime &curtime, const RTPTime &timeoutdelay); + + /** Assuming that the current time is \c curtime, clear the SDES NOTE items which haven't been updated + * during the previous time interval \c timeoutdelay. + */ + void NoteTimeout(const RTPTime &curtime, const RTPTime &timeoutdelay); + + /** Combines the functions SenderTimeout, BYETimeout, Timeout and NoteTimeout. + * Combines the functions SenderTimeout, BYETimeout, Timeout and NoteTimeout. This is more efficient + * than calling all four functions since only one iteration is needed in this function. + */ + void MultipleTimeouts(const RTPTime &curtime, const RTPTime &sendertimeout, const RTPTime &byetimeout, const RTPTime &generaltimeout, const RTPTime ¬etimeout); + + /** Returns the number of participants which are marked as a sender. */ + int GetSenderCount() const + { + return sendercount; + } + + /** Returns the total number of entries in the source table. */ + int GetTotalCount() const + { + return totalcount; + } + + /** Returns the number of members which have been validated and which haven't sent a BYE packet yet. */ + int GetActiveMemberCount() const + { + return activecount; + } + +protected: + /** Is called when an RTP packet is about to be processed. */ + virtual void OnRTPPacket(RTPPacket *pack, const RTPTime &receivetime, const RTPAddress *senderaddress); + + /** Is called when an RTCP compound packet is about to be processed. */ + virtual void OnRTCPCompoundPacket(RTCPCompoundPacket *pack, const RTPTime &receivetime, const RTPAddress *senderaddress); + + /** Is called when an SSRC collision was detected. + * Is called when an SSRC collision was detected. The instance \c srcdat is the one present in + * the table, the address \c senderaddress is the one that collided with one of the addresses + * and \c isrtp indicates against which address of \c srcdat the check failed. + */ + virtual void OnSSRCCollision(RTPSourceData *srcdat, const RTPAddress *senderaddress, bool isrtp); + + /** Is called when another CNAME was received than the one already present for source \c srcdat. */ + virtual void OnCNAMECollision(RTPSourceData *srcdat, const RTPAddress *senderaddress, const uint8_t *cname, std::size_t cnamelength); + + /** Is called when a new entry \c srcdat is added to the source table. */ + virtual void OnNewSource(RTPSourceData *srcdat); + + /** Is called when the entry \c srcdat is about to be deleted from the source table. */ + virtual void OnRemoveSource(RTPSourceData *srcdat); + + /** Is called when participant \c srcdat is timed out. */ + virtual void OnTimeout(RTPSourceData *srcdat); + + /** Is called when participant \c srcdat is timed after having sent a BYE packet. */ + virtual void OnBYETimeout(RTPSourceData *srcdat); + + /** Is called when a BYE packet has been processed for source \c srcdat. */ + virtual void OnBYEPacket(RTPSourceData *srcdat); + + /** Is called when an RTCP sender report has been processed for this source. */ + virtual void OnRTCPSenderReport(RTPSourceData *srcdat); + + /** Is called when an RTCP receiver report has been processed for this source. */ + virtual void OnRTCPReceiverReport(RTPSourceData *srcdat); + + /** Is called when a specific SDES item was received for this source. */ + virtual void OnRTCPSDESItem(RTPSourceData *srcdat, RTCPSDESPacket::ItemType t, const void *itemdata, std::size_t itemlength); +#ifdef RTP_SUPPORT_SDESPRIV + /** Is called when a specific SDES item of 'private' type was received for this source. */ + virtual void OnRTCPSDESPrivateItem(RTPSourceData *srcdat, const void *prefixdata, std::size_t prefixlen, const void *valuedata, std::size_t valuelen); +#endif // RTP_SUPPORT_SDESPRIV + + /** Is called when an RTCP APP packet \c apppacket has been received at time \c receivetime + * from address \c senderaddress. + */ + virtual void OnAPPPacket(RTCPAPPPacket *apppacket, const RTPTime &receivetime, const RTPAddress *senderaddress); + + /** Is called when an unknown RTCP packet type was detected. */ + virtual void OnUnknownPacketType(RTCPPacket *rtcppack, const RTPTime &receivetime, const RTPAddress *senderaddress); + + /** Is called when an unknown packet format for a known packet type was detected. */ + virtual void OnUnknownPacketFormat(RTCPPacket *rtcppack, const RTPTime &receivetime, const RTPAddress *senderaddress); + + /** Is called when the SDES NOTE item for source \c srcdat has been timed out. */ + virtual void OnNoteTimeout(RTPSourceData *srcdat); + + /** Allows you to use an RTP packet from the specified source directly. + * Allows you to use an RTP packet from the specified source directly. If + * `ispackethandled` is set to `true`, the packet will no longer be stored in this + * source's packet list. */ + virtual void OnValidatedRTPPacket(RTPSourceData *srcdat, RTPPacket *rtppack, bool isonprobation, bool *ispackethandled); +private: + void ClearSourceList(); + int ObtainSourceDataInstance(uint32_t ssrc, RTPInternalSourceData **srcdat, bool *created); + int GetRTCPSourceData(uint32_t ssrc, const RTPAddress *senderaddress, RTPInternalSourceData **srcdat, bool *newsource); + bool CheckCollision(RTPInternalSourceData *srcdat, const RTPAddress *senderaddress, bool isrtp); + + RTPKeyHashTable sourcelist; + + int sendercount; + int totalcount; + int activecount; + + RTPInternalSourceData *owndata; + + friend class RTPInternalSourceData; +}; + +// Inlining the default implementations to avoid unused-parameter errors. +inline void RTPSources::OnRTPPacket(RTPPacket *, const RTPTime &, const RTPAddress *) +{ +} +inline void RTPSources::OnRTCPCompoundPacket(RTCPCompoundPacket *, const RTPTime &, const RTPAddress *) +{ +} +inline void RTPSources::OnSSRCCollision(RTPSourceData *, const RTPAddress *, bool) +{ +} +inline void RTPSources::OnCNAMECollision(RTPSourceData *, const RTPAddress *, const uint8_t *, std::size_t) +{ +} +inline void RTPSources::OnNewSource(RTPSourceData *) +{ +} +inline void RTPSources::OnRemoveSource(RTPSourceData *) +{ +} +inline void RTPSources::OnTimeout(RTPSourceData *) +{ +} +inline void RTPSources::OnBYETimeout(RTPSourceData *) +{ +} +inline void RTPSources::OnBYEPacket(RTPSourceData *) +{ +} +inline void RTPSources::OnRTCPSenderReport(RTPSourceData *) +{ +} +inline void RTPSources::OnRTCPReceiverReport(RTPSourceData *) +{ +} +inline void RTPSources::OnRTCPSDESItem(RTPSourceData *, RTCPSDESPacket::ItemType, const void *, std::size_t) +{ +} +#ifdef RTP_SUPPORT_SDESPRIV +inline void RTPSources::OnRTCPSDESPrivateItem(RTPSourceData *, const void *, std::size_t, const void *, std::size_t) +{ +} +#endif // RTP_SUPPORT_SDESPRIV +inline void RTPSources::OnAPPPacket(RTCPAPPPacket *, const RTPTime &, const RTPAddress *) +{ +} +inline void RTPSources::OnUnknownPacketType(RTCPPacket *, const RTPTime &, const RTPAddress *) +{ +} +inline void RTPSources::OnUnknownPacketFormat(RTCPPacket *, const RTPTime &, const RTPAddress *) +{ +} +inline void RTPSources::OnNoteTimeout(RTPSourceData *) +{ +} +inline void RTPSources::OnValidatedRTPPacket(RTPSourceData *, RTPPacket *, bool, bool *) +{ +} + +} // end namespace + +#endif // RTPSOURCES_H + diff --git a/qrtplib/rtpstructs.h b/qrtplib/rtpstructs.h new file mode 100644 index 000000000..152b08819 --- /dev/null +++ b/qrtplib/rtpstructs.h @@ -0,0 +1,128 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +/** + * \file rtpstructs.h + */ + +#ifndef RTPSTRUCTS_H + +#define RTPSTRUCTS_H + +#include "rtpconfig.h" +#include "rtptypes.h" + +namespace qrtplib +{ + +struct RTPHeader +{ +#ifdef RTP_BIG_ENDIAN + uint8_t version:2; + uint8_t padding:1; + uint8_t extension:1; + uint8_t csrccount:4; + + uint8_t marker:1; + uint8_t payloadtype:7; +#else // little endian + uint8_t csrccount :4; + uint8_t extension :1; + uint8_t padding :1; + uint8_t version :2; + + uint8_t payloadtype :7; + uint8_t marker :1; +#endif // RTP_BIG_ENDIAN + + uint16_t sequencenumber; + uint32_t timestamp; + uint32_t ssrc; +}; + +struct RTPExtensionHeader +{ + uint16_t extid; + uint16_t length; +}; + +struct RTPSourceIdentifier +{ + uint32_t ssrc; +}; + +struct RTCPCommonHeader +{ +#ifdef RTP_BIG_ENDIAN + uint8_t version:2; + uint8_t padding:1; + uint8_t count:5; +#else // little endian + uint8_t count :5; + uint8_t padding :1; + uint8_t version :2; +#endif // RTP_BIG_ENDIAN + + uint8_t packettype; + uint16_t length; +}; + +struct RTCPSenderReport +{ + uint32_t ntptime_msw; + uint32_t ntptime_lsw; + uint32_t rtptimestamp; + uint32_t packetcount; + uint32_t octetcount; +}; + +struct RTCPReceiverReport +{ + uint32_t ssrc; // Identifies about which SSRC's data this report is... + uint8_t fractionlost; + uint8_t packetslost[3]; + uint32_t exthighseqnr; + uint32_t jitter; + uint32_t lsr; + uint32_t dlsr; +}; + +struct RTCPSDESHeader +{ + uint8_t sdesid; + uint8_t length; +}; + +} // end namespace + +#endif // RTPSTRUCTS + diff --git a/qrtplib/rtptimeutilities.cpp b/qrtplib/rtptimeutilities.cpp index 9554d57fd..c5900efc2 100644 --- a/qrtplib/rtptimeutilities.cpp +++ b/qrtplib/rtptimeutilities.cpp @@ -1,46 +1,47 @@ /* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ -//#include "rtpconfig.h" +#include "rtpconfig.h" #include "rtptimeutilities.h" -namespace jrtplib +namespace qrtplib { RTPTimeInitializerObject::RTPTimeInitializerObject() { - dummy = -1; + dummy = -1; } +RTPTimeInitializerObject timeinit; + } // end namespace + diff --git a/qrtplib/rtptimeutilities.h b/qrtplib/rtptimeutilities.h index 9c1078efa..d474ff2fc 100644 --- a/qrtplib/rtptimeutilities.h +++ b/qrtplib/rtptimeutilities.h @@ -1,36 +1,34 @@ /* - Rewritten to fit into the Qt Network framework - Copyright (c) 2018 Edouard Griffiths, F4EXB - This file is a part of JRTPLIB - Copyright (c) 1999-2017 Jori Liesenborgs + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs - Contact: jori.liesenborgs@gmail.com + Contact: jori.liesenborgs@gmail.com - This library was developed at the Expertise Centre for Digital Media - (http://www.edm.uhasselt.be), a research center of the Hasselt University - (http://www.uhasselt.be). The library is based upon work done for - my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. -*/ + */ /** * \file rtptimeutilities.h @@ -40,284 +38,377 @@ #define RTPTIMEUTILITIES_H -//#include "rtpconfig.h" -//#include "rtptypes.h" +#include "rtpconfig.h" +#include "rtptypes.h" +#ifndef RTP_HAVE_QUERYPERFORMANCECOUNTER #include #include #include -#include +#endif // RTP_HAVE_QUERYPERFORMANCECOUNTER -#define RTP_NTPTIMEOFFSET 2208988800UL +#include "export.h" + +#define RTP_NTPTIMEOFFSET 2208988800UL + +#ifdef RTP_HAVE_VSUINT64SUFFIX +#define C1000000 1000000ui64 +#define CEPOCH 11644473600000000ui64 +#else #define C1000000 1000000ULL #define CEPOCH 11644473600000000ULL +#endif // RTP_HAVE_VSUINT64SUFFIX -namespace jrtplib +namespace qrtplib { /** - * This is a simple wrapper for the most significant word (MSW) and least + * This is a simple wrapper for the most significant word (MSW) and least * significant word (LSW) of an NTP timestamp. */ -class RTPNTPTime +class QRTPLIB_API RTPNTPTime { public: - /** This constructor creates and instance with MSW \c m and LSW \c l. */ - RTPNTPTime(uint32_t m, uint32_t l) { msw = m ; lsw = l; } + /** This constructor creates and instance with MSW \c m and LSW \c l. */ + RTPNTPTime(uint32_t m, uint32_t l) + { + msw = m; + lsw = l; + } - /** Returns the most significant word. */ - uint32_t GetMSW() const { return msw; } + /** Returns the most significant word. */ + uint32_t GetMSW() const + { + return msw; + } - /** Returns the least significant word. */ - uint32_t GetLSW() const { return lsw; } + /** Returns the least significant word. */ + uint32_t GetLSW() const + { + return lsw; + } private: - uint32_t msw,lsw; + uint32_t msw, lsw; }; /** This class is used to specify wallclock time, delay intervals etc. - * This class is used to specify wallclock time, delay intervals etc. + * This class is used to specify wallclock time, delay intervals etc. * It stores a number of seconds and a number of microseconds. */ -class RTPTime +class QRTPLIB_API RTPTime { public: - /** Returns an RTPTime instance representing the current wallclock time. - * Returns an RTPTime instance representing the current wallclock time. This is expressed - * as a number of seconds since 00:00:00 UTC, January 1, 1970. - */ - static RTPTime CurrentTime(); + /** Returns an RTPTime instance representing the current wallclock time. + * Returns an RTPTime instance representing the current wallclock time. This is expressed + * as a number of seconds since 00:00:00 UTC, January 1, 1970. + */ + static RTPTime CurrentTime(); - /** This function waits the amount of time specified in \c delay. */ - static void Wait(const RTPTime &delay); - - /** Creates an RTPTime instance representing \c t, which is expressed in units of seconds. */ - RTPTime(double t); + /** This function waits the amount of time specified in \c delay. */ + static void Wait(const RTPTime &delay); - /** Creates an instance that corresponds to \c ntptime. - * Creates an instance that corresponds to \c ntptime. If - * the conversion cannot be made, both the seconds and the - * microseconds are set to zero. - */ - RTPTime(RTPNTPTime ntptime); + /** Creates an RTPTime instance representing \c t, which is expressed in units of seconds. */ + RTPTime(double t); - /** Creates an instance corresponding to \c seconds and \c microseconds. */ - RTPTime(int64_t seconds, uint32_t microseconds); + /** Creates an instance that corresponds to \c ntptime. + * Creates an instance that corresponds to \c ntptime. If + * the conversion cannot be made, both the seconds and the + * microseconds are set to zero. + */ + RTPTime(RTPNTPTime ntptime); - /** Returns the number of seconds stored in this instance. */ - int64_t GetSeconds() const; + /** Creates an instance corresponding to \c seconds and \c microseconds. */ + RTPTime(int64_t seconds, uint32_t microseconds); - /** Returns the number of microseconds stored in this instance. */ - uint32_t GetMicroSeconds() const; + /** Returns the number of seconds stored in this instance. */ + int64_t GetSeconds() const; - /** Returns the time stored in this instance, expressed in units of seconds. */ - double GetDouble() const { return m_t; } + /** Returns the number of microseconds stored in this instance. */ + uint32_t GetMicroSeconds() const; - /** Returns the NTP time corresponding to the time stored in this instance. */ - RTPNTPTime GetNTPTime() const; + /** Returns the time stored in this instance, expressed in units of seconds. */ + double GetDouble() const + { + return m_t; + } - RTPTime &operator-=(const RTPTime &t); - RTPTime &operator+=(const RTPTime &t); - bool operator<(const RTPTime &t) const; - bool operator>(const RTPTime &t) const; - bool operator<=(const RTPTime &t) const; - bool operator>=(const RTPTime &t) const; + /** Returns the NTP time corresponding to the time stored in this instance. */ + RTPNTPTime GetNTPTime() const; - bool IsZero() const { return m_t == 0; } + RTPTime &operator-=(const RTPTime &t); + RTPTime &operator+=(const RTPTime &t); + bool operator<(const RTPTime &t) const; + bool operator>(const RTPTime &t) const; + bool operator<=(const RTPTime &t) const; + bool operator>=(const RTPTime &t) const; + + bool IsZero() const + { + return m_t == 0; + } private: - double m_t; +#ifdef RTP_HAVE_QUERYPERFORMANCECOUNTER + static inline uint64_t CalculateMicroseconds(uint64_t performancecount,uint64_t performancefrequency); +#endif // RTP_HAVE_QUERYPERFORMANCECOUNTER + + double m_t; }; inline RTPTime::RTPTime(double t) { - m_t = t; + m_t = t; } inline RTPTime::RTPTime(int64_t seconds, uint32_t microseconds) { - if (seconds >= 0) - { - m_t = (double)seconds + 1e-6*(double)microseconds; - } - else - { - int64_t possec = -seconds; + if (seconds >= 0) + { + m_t = (double) seconds + 1e-6 * (double) microseconds; + } + else + { + int64_t possec = -seconds; - m_t = (double)possec + 1e-6*(double)microseconds; - m_t = -m_t; - } + m_t = (double) possec + 1e-6 * (double) microseconds; + m_t = -m_t; + } } inline RTPTime::RTPTime(RTPNTPTime ntptime) { - if (ntptime.GetMSW() < RTP_NTPTIMEOFFSET) - { - m_t = 0; - } - else - { - uint32_t sec = ntptime.GetMSW() - RTP_NTPTIMEOFFSET; - - double x = (double)ntptime.GetLSW(); - x /= (65536.0*65536.0); - x *= 1000000.0; - uint32_t microsec = (uint32_t)x; + if (ntptime.GetMSW() < RTP_NTPTIMEOFFSET) + { + m_t = 0; + } + else + { + uint32_t sec = ntptime.GetMSW() - RTP_NTPTIMEOFFSET; - m_t = (double)sec + 1e-6*(double)microsec; - } + double x = (double) ntptime.GetLSW(); + x /= (65536.0 * 65536.0); + x *= 1000000.0; + uint32_t microsec = (uint32_t) x; + + m_t = (double) sec + 1e-6 * (double) microsec; + } } inline int64_t RTPTime::GetSeconds() const { - return (int64_t)m_t; + return (int64_t) m_t; } inline uint32_t RTPTime::GetMicroSeconds() const { - uint32_t microsec; + uint32_t microsec; - if (m_t >= 0) - { - int64_t sec = (int64_t)m_t; - microsec = (uint32_t)(1e6*(m_t - (double)sec) + 0.5); - } - else // m_t < 0 - { - int64_t sec = (int64_t)(-m_t); - microsec = (uint32_t)(1e6*((-m_t) - (double)sec) + 0.5); - } + if (m_t >= 0) + { + int64_t sec = (int64_t) m_t; + microsec = (uint32_t) (1e6 * (m_t - (double) sec) + 0.5); + } + else // m_t < 0 + { + int64_t sec = (int64_t) (-m_t); + microsec = (uint32_t) (1e6 * ((-m_t) - (double) sec) + 0.5); + } - if (microsec >= 1000000) - return 999999; - // Unsigned, it can never be less than 0 - // if (microsec < 0) - // return 0; - return microsec; + if (microsec >= 1000000) + return 999999; + // Unsigned, it can never be less than 0 + // if (microsec < 0) + // return 0; + return microsec; } -#ifdef RTP_HAVE_CLOCK_GETTIME -inline double RTPTime_timespecToDouble(struct timespec &ts) +#ifdef RTP_HAVE_QUERYPERFORMANCECOUNTER + +inline uint64_t RTPTime::CalculateMicroseconds(uint64_t performancecount,uint64_t performancefrequency) { - return (double)ts.tv_sec + 1e-9*(double)ts.tv_nsec; + uint64_t f = performancefrequency; + uint64_t a = performancecount; + uint64_t b = a/f; + uint64_t c = a%f; // a = b*f+c => (a*1000000)/f = b*1000000+(c*1000000)/f + + return b*C1000000+(c*C1000000)/f; } inline RTPTime RTPTime::CurrentTime() { - static bool s_initialized = false; - static double s_startOffet = 0; + static int inited = 0; + static uint64_t microseconds, initmicroseconds; + static LARGE_INTEGER performancefrequency; - if (!s_initialized) - { - s_initialized = true; + uint64_t emulate_microseconds, microdiff; + SYSTEMTIME systemtime; + FILETIME filetime; - // Get the corresponding times in system time and monotonic time - struct timespec tpSys, tpMono; + LARGE_INTEGER performancecount; - clock_gettime(CLOCK_REALTIME, &tpSys); - clock_gettime(CLOCK_MONOTONIC, &tpMono); + QueryPerformanceCounter(&performancecount); - double tSys = RTPTime_timespecToDouble(tpSys); - double tMono = RTPTime_timespecToDouble(tpMono); + if(!inited) + { + inited = 1; + QueryPerformanceFrequency(&performancefrequency); + GetSystemTime(&systemtime); + SystemTimeToFileTime(&systemtime,&filetime); + microseconds = ( ((uint64_t)(filetime.dwHighDateTime) << 32) + (uint64_t)(filetime.dwLowDateTime) ) / (uint64_t)10; + microseconds-= CEPOCH; // EPOCH + initmicroseconds = CalculateMicroseconds(performancecount.QuadPart, performancefrequency.QuadPart); + } - s_startOffet = tSys - tMono; - return tSys; - } + emulate_microseconds = CalculateMicroseconds(performancecount.QuadPart, performancefrequency.QuadPart); - struct timespec tpMono; - clock_gettime(CLOCK_MONOTONIC, &tpMono); + microdiff = emulate_microseconds - initmicroseconds; - double tMono0 = RTPTime_timespecToDouble(tpMono); - return tMono0 + s_startOffet; + double t = 1e-6*(double)(microseconds + microdiff); + return RTPTime(t); +} + +inline void RTPTime::Wait(const RTPTime &delay) +{ + if (delay.m_t <= 0) + return; + + uint64_t sec = (uint64_t)delay.m_t; + uint32_t microsec = (uint32_t)(1e6*(delay.m_t-(double)sec)); + DWORD t = ((DWORD)sec)*1000+(((DWORD)microsec)/1000); + Sleep(t); +} + +#else // unix style + +#ifdef RTP_HAVE_CLOCK_GETTIME +inline double RTPTime_timespecToDouble(struct timespec &ts) +{ + return (double) ts.tv_sec + 1e-9 * (double) ts.tv_nsec; +} + +inline RTPTime RTPTime::CurrentTime() +{ + static bool s_initialized = false; + static double s_startOffet = 0; + + if (!s_initialized) + { + s_initialized = true; + + // Get the corresponding times in system time and monotonic time + struct timespec tpSys, tpMono; + + clock_gettime(CLOCK_REALTIME, &tpSys); + clock_gettime(CLOCK_MONOTONIC, &tpMono); + + double tSys = RTPTime_timespecToDouble(tpSys); + double tMono = RTPTime_timespecToDouble(tpMono); + + s_startOffet = tSys - tMono; + return tSys; + } + + struct timespec tpMono; + clock_gettime(CLOCK_MONOTONIC, &tpMono); + + double tMono0 = RTPTime_timespecToDouble(tpMono); + return tMono0 + s_startOffet; } #else // gettimeofday fallback inline RTPTime RTPTime::CurrentTime() { - struct timeval tv; - - gettimeofday(&tv,0); - return RTPTime((uint64_t)tv.tv_sec,(uint32_t)tv.tv_usec); + struct timeval tv; + + gettimeofday(&tv,0); + return RTPTime((uint64_t)tv.tv_sec,(uint32_t)tv.tv_usec); } #endif // RTP_HAVE_CLOCK_GETTIME inline void RTPTime::Wait(const RTPTime &delay) { - if (delay.m_t <= 0) - return; + if (delay.m_t <= 0) + return; - uint64_t sec = (uint64_t)delay.m_t; - uint64_t nanosec = (uint32_t)(1e9*(delay.m_t-(double)sec)); + uint64_t sec = (uint64_t) delay.m_t; + uint64_t nanosec = (uint32_t) (1e9 * (delay.m_t - (double) sec)); - struct timespec req,rem; - int ret; + struct timespec req, rem; + int ret; - req.tv_sec = (time_t)sec; - req.tv_nsec = ((long)nanosec); - do - { - ret = nanosleep(&req,&rem); - req = rem; - } while (ret == -1 && errno == EINTR); + req.tv_sec = (time_t) sec; + req.tv_nsec = ((long) nanosec); + do + { + ret = nanosleep(&req, &rem); + req = rem; + } while (ret == -1 && errno == EINTR); } +#endif // RTP_HAVE_QUERYPERFORMANCECOUNTER + inline RTPTime &RTPTime::operator-=(const RTPTime &t) -{ - m_t -= t.m_t; - return *this; +{ + m_t -= t.m_t; + return *this; } inline RTPTime &RTPTime::operator+=(const RTPTime &t) -{ - m_t += t.m_t; - return *this; +{ + m_t += t.m_t; + return *this; } inline RTPNTPTime RTPTime::GetNTPTime() const { - uint32_t sec = (uint32_t)m_t; - uint32_t microsec = (uint32_t)((m_t - (double)sec)*1e6); + uint32_t sec = (uint32_t) m_t; + uint32_t microsec = (uint32_t) ((m_t - (double) sec) * 1e6); - uint32_t msw = sec+RTP_NTPTIMEOFFSET; - uint32_t lsw; - double x; - - x = microsec/1000000.0; - x *= (65536.0*65536.0); - lsw = (uint32_t)x; + uint32_t msw = sec + RTP_NTPTIMEOFFSET; + uint32_t lsw; + double x; - return RTPNTPTime(msw,lsw); + x = microsec / 1000000.0; + x *= (65536.0 * 65536.0); + lsw = (uint32_t) x; + + return RTPNTPTime(msw, lsw); } inline bool RTPTime::operator<(const RTPTime &t) const { - return m_t < t.m_t; + return m_t < t.m_t; } inline bool RTPTime::operator>(const RTPTime &t) const { - return m_t > t.m_t; + return m_t > t.m_t; } inline bool RTPTime::operator<=(const RTPTime &t) const { - return m_t <= t.m_t; + return m_t <= t.m_t; } inline bool RTPTime::operator>=(const RTPTime &t) const { - return m_t >= t.m_t; + return m_t >= t.m_t; } -class RTPTimeInitializerObject +class QRTPLIB_API RTPTimeInitializerObject { public: - RTPTimeInitializerObject(); - void Dummy() { dummy++; } + RTPTimeInitializerObject(); + void Dummy() + { + dummy++; + } private: - int dummy; + int dummy; }; +extern QRTPLIB_API RTPTimeInitializerObject timeinit; + } // end namespace - #endif // RTPTIMEUTILITIES_H diff --git a/qrtplib/rtptransmitter.h b/qrtplib/rtptransmitter.h new file mode 100644 index 000000000..595e1bd7b --- /dev/null +++ b/qrtplib/rtptransmitter.h @@ -0,0 +1,252 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +/** + * \file rtptransmitter.h + */ + +#ifndef RTPTRANSMITTER_H + +#define RTPTRANSMITTER_H + +#include "rtpconfig.h" +#include "rtptypes.h" +#include "rtptimeutilities.h" +#include + +namespace qrtplib +{ + +class RTPRawPacket; +class RTPAddress; +class RTPTransmissionParams; +class RTPTime; +class RTPTransmissionInfo; + +/** Abstract class from which actual transmission components should be derived. + * Abstract class from which actual transmission components should be derived. + * The abstract class RTPTransmitter specifies the interface for + * actual transmission components. Currently, three implementations exist: + * an UDP over IPv4 transmitter, an UDP over IPv6 transmitter and a transmitter + * which can be used to use an external transmission mechanism. + */ +class RTPTransmitter +{ +public: + /** Used to identify a specific transmitter. + * If UserDefinedProto is used in the RTPSession::Create function, the RTPSession + * virtual member function NewUserDefinedTransmitter will be called to create + * a transmission component. + */ + enum TransmissionProtocol + { + IPv4UDPProto, /**< Specifies the internal UDP over IPv4 transmitter. */ + IPv6UDPProto, /**< Specifies the internal UDP over IPv6 transmitter. */ + TCPProto, /**< Specifies the internal TCP transmitter. */ + ExternalProto, /**< Specifies the transmitter which can send packets using an external mechanism, and which can have received packets injected into it - see RTPExternalTransmitter for additional information. */ + UserDefinedProto /**< Specifies a user defined, external transmitter. */ + }; + + /** Three kind of receive modes can be specified. */ + enum ReceiveMode + { + AcceptAll, /**< All incoming data is accepted, no matter where it originated from. */ + AcceptSome, /**< Only data coming from specific sources will be accepted. */ + IgnoreSome /**< All incoming data is accepted, except for data coming from a specific set of sources. */ + }; +protected: + /** Constructor in which you can specify a memory manager to use. */ + RTPTransmitter() + { + timeinit.Dummy(); + } +public: + virtual ~RTPTransmitter() + { + } + + /** This function must be called before the transmission component can be used. */ + virtual int Init() = 0; + + /** Prepares the component to be used. + * Prepares the component to be used. The parameter \c maxpacksize specifies the maximum size + * a packet can have: if the packet is larger it will not be transmitted. The \c transparams + * parameter specifies a pointer to an RTPTransmissionParams instance. This is also an abstract + * class and each actual component will define its own parameters by inheriting a class + * from RTPTransmissionParams. If \c transparams is NULL, the default transmission parameters + * for the component will be used. + */ + virtual int Create(std::size_t maxpacksize, const RTPTransmissionParams *transparams) = 0; + + /** Bind the RTP and RTCP sockets to ports that were set at creation time */ + virtual int BindSockets() = 0; + + /** By calling this function, buffers are cleared and the component cannot be used anymore. + * By calling this function, buffers are cleared and the component cannot be used anymore. + * Only when the Create function is called again can the component be used again. */ + virtual void Destroy() = 0; + + /** Returns additional information about the transmitter. + * This function returns an instance of a subclass of RTPTransmissionInfo which will give + * some additional information about the transmitter (a list of local IP addresses for example). + * Currently, either an instance of RTPUDPv4TransmissionInfo or RTPUDPv6TransmissionInfo is + * returned, depending on the type of the transmitter. The user has to deallocate the returned + * instance when it is no longer needed, which can be done using RTPTransmitter::DeleteTransmissionInfo. + */ + virtual RTPTransmissionInfo *GetTransmissionInfo() = 0; + + /** Deallocates the information returned by RTPTransmitter::GetTransmissionInfo . + * Deallocates the information returned by RTPTransmitter::GetTransmissionInfo . + */ + virtual void DeleteTransmissionInfo(RTPTransmissionInfo *inf) = 0; + + /** Returns \c true if the address specified by \c addr is one of the addresses of the transmitter. */ + virtual bool ComesFromThisTransmitter(const RTPAddress& addr) = 0; + + /** Returns the amount of bytes that will be added to the RTP packet by the underlying layers (excluding + * the link layer). */ + virtual std::size_t GetHeaderOverhead() = 0; + + /** Send a packet with length \c len containing \c data to all RTP addresses of the current destination list. */ + virtual int SendRTPData(const void *data, std::size_t len) = 0; + + /** Send a packet with length \c len containing \c data to all RTCP addresses of the current destination list. */ + virtual int SendRTCPData(const void *data, std::size_t len) = 0; + + /** Adds the address specified by \c addr to the list of destinations. */ + virtual int AddDestination(const RTPAddress &addr) = 0; + + /** Deletes the address specified by \c addr from the list of destinations. */ + virtual int DeleteDestination(const RTPAddress &addr) = 0; + + /** Clears the list of destinations. */ + virtual void ClearDestinations() = 0; + + /** Returns \c true if the transmission component supports multicasting. */ + virtual bool SupportsMulticasting() = 0; + + /** Joins the multicast group specified by \c addr. */ + virtual int JoinMulticastGroup(const RTPAddress &addr) = 0; + + /** Leaves the multicast group specified by \c addr. */ + virtual int LeaveMulticastGroup(const RTPAddress &addr) = 0; + + /** Sets the receive mode. + * Sets the receive mode to \c m, which is one of the following: RTPTransmitter::AcceptAll, + * RTPTransmitter::AcceptSome or RTPTransmitter::IgnoreSome. Note that if the receive + * mode is changed, all information about the addresses to ignore to accept is lost. + */ + virtual int SetReceiveMode(RTPTransmitter::ReceiveMode m) = 0; + + /** Adds \c addr to the list of addresses to ignore. */ + virtual int AddToIgnoreList(const RTPAddress &addr) = 0; + + /** Deletes \c addr from the list of addresses to accept. */ + virtual int DeleteFromIgnoreList(const RTPAddress &addr)= 0; + + /** Clears the list of addresses to ignore. */ + virtual void ClearIgnoreList() = 0; + + /** Adds \c addr to the list of addresses to accept. */ + virtual int AddToAcceptList(const RTPAddress &addr) = 0; + + /** Deletes \c addr from the list of addresses to accept. */ + virtual int DeleteFromAcceptList(const RTPAddress &addr) = 0; + + /** Clears the list of addresses to accept. */ + virtual void ClearAcceptList() = 0; + + /** Sets the maximum packet size which the transmitter should allow to \c s. */ + virtual int SetMaximumPacketSize(std::size_t s) = 0; + + /** Returns the raw data of a received RTP packet + * in an RTPRawPacket instance. */ + virtual RTPRawPacket *GetNextPacket() = 0; +}; + +/** Base class for transmission parameters. + * This class is an abstract class which will have a specific implementation for a + * specific kind of transmission component. All actual implementations inherit the + * GetTransmissionProtocol function which identifies the component type for which + * these parameters are valid. + */ +class RTPTransmissionParams +{ +protected: + RTPTransmissionParams(RTPTransmitter::TransmissionProtocol p) + { + protocol = p; + } +public: + virtual ~RTPTransmissionParams() + { + } + + /** Returns the transmitter type for which these parameters are valid. */ + RTPTransmitter::TransmissionProtocol GetTransmissionProtocol() const + { + return protocol; + } +private: + RTPTransmitter::TransmissionProtocol protocol; +}; + +/** Base class for additional information about the transmitter. + * This class is an abstract class which will have a specific implementation for a + * specific kind of transmission component. All actual implementations inherit the + * GetTransmissionProtocol function which identifies the component type for which + * these parameters are valid. + */ +class RTPTransmissionInfo +{ +protected: + RTPTransmissionInfo(RTPTransmitter::TransmissionProtocol p) + { + protocol = p; + } +public: + virtual ~RTPTransmissionInfo() + { + } + /** Returns the transmitter type for which these parameters are valid. */ + RTPTransmitter::TransmissionProtocol GetTransmissionProtocol() const + { + return protocol; + } +private: + RTPTransmitter::TransmissionProtocol protocol; +}; + +} // end namespace + +#endif // RTPTRANSMITTER_H + diff --git a/qrtplib/rtptypes.h b/qrtplib/rtptypes.h new file mode 100644 index 000000000..aa93f8026 --- /dev/null +++ b/qrtplib/rtptypes.h @@ -0,0 +1,4 @@ +#include "rtpconfig.h" + +#include +#include diff --git a/qrtplib/rtptypes_win.h b/qrtplib/rtptypes_win.h new file mode 100644 index 000000000..3dbae45c0 --- /dev/null +++ b/qrtplib/rtptypes_win.h @@ -0,0 +1,53 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +#ifndef RTPTYPES_WIN_H + +#define RTPTYPES_WIN_H + +#ifndef INTTYPES_DEFINED + +#define INTTYPES_DEFINED + +typedef char int8_t; +typedef unsigned char uint8_t; +typedef short int16_t; +typedef unsigned short uint16_t; +typedef int int32_t; +typedef unsigned int uint32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; + +#endif // INTTYPES_DEFINED + +#endif // RTPTYPES_WIN_H + diff --git a/qrtplib/rtpudptransmitter.cpp b/qrtplib/rtpudptransmitter.cpp new file mode 100644 index 000000000..4b1d1ff1c --- /dev/null +++ b/qrtplib/rtpudptransmitter.cpp @@ -0,0 +1,553 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +#include "rtpudptransmitter.h" +#include "rtperrors.h" +#include "rtpaddress.h" +#include "rtpstructs.h" +#include "rtprawpacket.h" + +#include + +namespace qrtplib +{ + +RTPUDPTransmitter::RTPUDPTransmitter() : + m_rawPacketQueueLock(QMutex::Recursive) +{ + m_created = false; + m_init = false; + m_rtcpsock = 0; + m_rtpsock = 0; + m_deletesocketswhendone = false; + m_waitingfordata = false; + m_rtcpPort = 0; + m_rtpPort = 0; + m_receivemode = RTPTransmitter::AcceptAll; + m_maxpacksize = 0; + memset(m_rtpBuffer, 0, m_absoluteMaxPackSize); + memset(m_rtcpBuffer, 0, m_absoluteMaxPackSize); +} + +RTPUDPTransmitter::~RTPUDPTransmitter() +{ + Destroy(); +} + +int RTPUDPTransmitter::Init() +{ + if (m_init) { + return ERR_RTP_UDPV4TRANS_ALREADYINIT; + } + + m_init = true; + return 0; +} + +int RTPUDPTransmitter::Create(std::size_t maximumpacketsize, const RTPTransmissionParams *transparams) +{ + const RTPUDPTransmissionParams *params, defaultparams; + qint64 size; + + if (maximumpacketsize > m_absoluteMaxPackSize) { + return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; + } + + if (!m_init) { + return ERR_RTP_UDPV4TRANS_NOTINIT; + } + + if (m_created) { + return ERR_RTP_UDPV4TRANS_ALREADYCREATED; + } + + // Obtain transmission parameters + + if (transparams == 0) { + params = &defaultparams; + } + else + { + if (transparams->GetTransmissionProtocol() != RTPTransmitter::IPv4UDPProto) + { + return ERR_RTP_UDPV4TRANS_ILLEGALPARAMETERS; + } + params = (const RTPUDPTransmissionParams *) transparams; + } + + // Determine the port numbers + + m_localIP = params->GetBindIP(); + + if (params->GetAllowOddPortbase()) + { + m_rtpPort = params->GetPortbase(); + m_rtcpPort = params->GetForcedRTCPPort(); + } + else + { + if (params->GetPortbase() % 2 == 0) + { + m_rtpPort = params->GetPortbase(); + m_rtcpPort = m_rtpPort + 1; + } + else + { + return ERR_RTP_UDPV4TRANS_PORTBASENOTEVEN; + } + } + + if (params->GetUseExistingSockets(&m_rtpsock, &m_rtcpsock)) + { + m_deletesocketswhendone = false; + } + else + { + m_deletesocketswhendone = true; + + m_rtpsock = new QUdpSocket(); + + // If we're multiplexing, we're just going to set the RTCP socket to equal the RTP socket + if (params->GetRTCPMultiplexing()) + { + m_rtcpsock = m_rtpsock; + m_rtcpPort = m_rtpPort; + } else { + m_rtcpsock = new QUdpSocket(); + } + } + + // set socket buffer sizes + + size = params->GetRTPReceiveBufferSize(); + m_rtpsock->setReadBufferSize(size); + + if (m_rtpsock != m_rtcpsock) + { + size = params->GetRTCPReceiveBufferSize(); + m_rtcpsock->setReadBufferSize(size); + } + + m_maxpacksize = maximumpacketsize; + m_multicastInterface = params->GetMulticastInterface(); + m_receivemode = RTPTransmitter::AcceptAll; + + m_waitingfordata = false; + m_created = true; + + return 0; +} + +int RTPUDPTransmitter::BindSockets() +{ + if (!m_rtpsock->bind(m_localIP, m_rtpPort)) { + return ERR_RTP_UDPV4TRANS_CANTBINDRTPSOCKET; + } + + connect(m_rtpsock, SIGNAL(readyRead()), this, SLOT(readRTPPendingDatagrams())); + + if (m_rtpsock != m_rtcpsock) + { + if (!m_rtcpsock->bind(m_localIP, m_rtcpPort)) { + return ERR_RTP_UDPV4TRANS_CANTBINDRTCPSOCKET; + } + + connect(m_rtcpsock, SIGNAL(readyRead()), this, SLOT(readRTCPPendingDatagrams())); + } + + return 0; +} + +void RTPUDPTransmitter::Destroy() +{ + if (!m_init) { + return; + } + + if (!m_created) + { + return; + } + + if (m_deletesocketswhendone) + { + if (m_rtpsock != m_rtcpsock) { + delete m_rtcpsock; + } + + delete m_rtpsock; + } + + m_created = false; +} + +RTPTransmissionInfo *RTPUDPTransmitter::GetTransmissionInfo() +{ + if (!m_init) { + return 0; + } + + RTPTransmissionInfo *tinf = new RTPUDPTransmissionInfo(m_localIP, m_rtpsock, m_rtcpsock, m_rtpPort, m_rtcpPort); + + return tinf; +} + +void RTPUDPTransmitter::DeleteTransmissionInfo(RTPTransmissionInfo *inf) +{ + if (!m_init) { + return; + } + + delete inf; +} + +bool RTPUDPTransmitter::ComesFromThisTransmitter(const RTPAddress& addr) +{ + if (addr.getAddress() != m_localIP) { + return false; + } + + return (addr.getPort() == m_rtpPort) && (addr.getRtcpsendport() == m_rtcpPort); +} + +int RTPUDPTransmitter::SendRTPData(const void *data, std::size_t len) +{ + if (!m_init) { + return ERR_RTP_UDPV4TRANS_NOTINIT; + } + + if (!m_created) { + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + + if (len > m_maxpacksize) + { + return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; + } + + std::list::const_iterator it = m_destinations.begin(); + + for (; it != m_destinations.end(); ++it) + { + m_rtpsock->writeDatagram((const char*) data, (qint64) len, it->getAddress(), it->getPort()); + } + + return 0; +} + +int RTPUDPTransmitter::SendRTCPData(const void *data, std::size_t len) +{ + if (!m_init) { + return ERR_RTP_UDPV4TRANS_NOTINIT; + } + + if (!m_created) { + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + + if (len > m_maxpacksize) { + return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; + } + + std::list::const_iterator it = m_destinations.begin(); + + for (; it != m_destinations.end(); ++it) + { + m_rtcpsock->writeDatagram((const char*) data, (qint64) len, it->getAddress(), it->getRtcpsendport()); + } + + return 0; +} + +int RTPUDPTransmitter::AddDestination(const RTPAddress &addr) +{ + m_destinations.push_back(addr); + return 0; +} + +int RTPUDPTransmitter::DeleteDestination(const RTPAddress &addr) +{ + m_destinations.remove(addr); + return 0; +} + +void RTPUDPTransmitter::ClearDestinations() +{ + m_destinations.clear(); +} + +bool RTPUDPTransmitter::SupportsMulticasting() +{ + QNetworkInterface::InterfaceFlags flags = m_multicastInterface.flags(); + QAbstractSocket::SocketState rtpSocketState = m_rtpsock->state(); + QAbstractSocket::SocketState rtcpSocketState = m_rtcpsock->state(); + return m_multicastInterface.isValid() + && (rtpSocketState & QAbstractSocket::BoundState) + && (rtcpSocketState & QAbstractSocket::BoundState) + && (flags & QNetworkInterface::CanMulticast) + && (flags & QNetworkInterface::IsRunning) + && !(flags & QNetworkInterface::IsLoopBack); +} + +int RTPUDPTransmitter::JoinMulticastGroup(const RTPAddress &addr) +{ + if (!m_init) { + return ERR_RTP_UDPV4TRANS_NOTINIT; + } + + if (!m_created) { + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + + if (!SupportsMulticasting()) { + return ERR_RTP_UDPV6TRANS_NOMULTICASTSUPPORT; + } + + if (m_rtpsock->joinMulticastGroup(addr.getAddress(), m_multicastInterface)) + { + if (m_rtpsock != m_rtcpsock) + { + if (!m_rtcpsock->joinMulticastGroup(addr.getAddress(), m_multicastInterface)) { + return ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP; + } + } + } + else + { + return ERR_RTP_UDPV4TRANS_COULDNTJOINMULTICASTGROUP; + } + + return 0; +} + +int RTPUDPTransmitter::LeaveMulticastGroup(const RTPAddress &addr) +{ + if (!m_init) { + return ERR_RTP_UDPV4TRANS_NOTINIT; + } + + if (!m_created) { + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + + if (!SupportsMulticasting()) { + return ERR_RTP_UDPV6TRANS_NOMULTICASTSUPPORT; + } + + m_rtpsock->leaveMulticastGroup(addr.getAddress()); + + if (m_rtpsock != m_rtcpsock) + { + m_rtcpsock->leaveMulticastGroup(addr.getAddress()); + } + + return 0; +} + +int RTPUDPTransmitter::SetReceiveMode(RTPTransmitter::ReceiveMode m) +{ + if (!m_init) { + return ERR_RTP_UDPV4TRANS_NOTINIT; + } + + if (!m_created) { + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + + if (m != m_receivemode) { + m_receivemode = m; + } + + return 0; +} + +int RTPUDPTransmitter::AddToIgnoreList(const RTPAddress &addr) +{ + m_ignoreList.push_back(addr); + return 0; +} + +int RTPUDPTransmitter::DeleteFromIgnoreList(const RTPAddress &addr) +{ + m_ignoreList.remove(addr); + return 0; +} + +void RTPUDPTransmitter::ClearIgnoreList() +{ + m_ignoreList.clear(); +} + +int RTPUDPTransmitter::AddToAcceptList(const RTPAddress &addr) +{ + m_acceptList.push_back(addr); + return 0; +} + +int RTPUDPTransmitter::DeleteFromAcceptList(const RTPAddress &addr) +{ + m_acceptList.remove(addr); + return 0; +} + +void RTPUDPTransmitter::ClearAcceptList() +{ + m_acceptList.clear(); +} + +int RTPUDPTransmitter::SetMaximumPacketSize(std::size_t s) +{ + if (!m_init) { + return ERR_RTP_UDPV4TRANS_NOTINIT; + } + + if (!m_created) { + return ERR_RTP_UDPV4TRANS_NOTCREATED; + } + + if (s > m_absoluteMaxPackSize) { + return ERR_RTP_UDPV4TRANS_SPECIFIEDSIZETOOBIG; + } + + m_maxpacksize = s; + return 0; +} + +RTPRawPacket *RTPUDPTransmitter::GetNextPacket() +{ + QMutexLocker locker(&m_rawPacketQueueLock); + + if (m_rawPacketQueue.isEmpty()) { + return 0; + } else { + return m_rawPacketQueue.takeFirst(); + } +} + +void RTPUDPTransmitter::readRTPPendingDatagrams() +{ + while (m_rtpsock->hasPendingDatagrams()) + { + RTPTime curtime = RTPTime::CurrentTime(); + QHostAddress remoteAddress; + quint16 remotePort; + qint64 pendingDataSize = m_rtpsock->pendingDatagramSize(); + qint64 bytesRead = m_rtpsock->readDatagram(m_rtpBuffer, pendingDataSize, &remoteAddress, &remotePort); + qDebug("RTPUDPTransmitter::readRTPPendingDatagrams: %lld bytes read from %s:%d", + bytesRead, + qPrintable(remoteAddress.toString()), + remotePort); + + RTPAddress rtpAddress; + rtpAddress.setAddress(remoteAddress); + rtpAddress.setPort(remotePort); + + if (ShouldAcceptData(rtpAddress)) + { + bool isrtp = true; + + if (m_rtpsock == m_rtcpsock) // check payload type when multiplexing + { + if ((std::size_t) bytesRead > sizeof(RTCPCommonHeader)) + { + RTCPCommonHeader *rtcpheader = (RTCPCommonHeader *) m_rtpBuffer; + uint8_t packettype = rtcpheader->packettype; + + if (packettype >= 200 && packettype <= 204) { + isrtp = false; + } + } + } + + RTPRawPacket *pack = new RTPRawPacket((uint8_t *) m_rtpBuffer, bytesRead, rtpAddress, curtime, isrtp); + + m_rawPacketQueueLock.lock(); + m_rawPacketQueue.append(pack); + m_rawPacketQueueLock.unlock(); + + emit NewDataAvailable(); + } + } +} + +void RTPUDPTransmitter::readRTCPPendingDatagrams() +{ + while (m_rtcpsock->hasPendingDatagrams()) + { + RTPTime curtime = RTPTime::CurrentTime(); + QHostAddress remoteAddress; + quint16 remotePort; + qint64 pendingDataSize = m_rtcpsock->pendingDatagramSize(); + qint64 bytesRead = m_rtcpsock->readDatagram(m_rtcpBuffer, pendingDataSize, &remoteAddress, &remotePort); + qDebug("RTPUDPTransmitter::readRTCPPendingDatagrams: %lld bytes read from %s:%d", + bytesRead, + qPrintable(remoteAddress.toString()), + remotePort); + + RTPAddress rtpAddress; + rtpAddress.setAddress(remoteAddress); + rtpAddress.setPort(remotePort); + + if (ShouldAcceptData(rtpAddress)) + { + RTPRawPacket *pack = new RTPRawPacket((uint8_t *) m_rtcpBuffer, bytesRead, rtpAddress, curtime, false); + + m_rawPacketQueueLock.lock(); + m_rawPacketQueue.append(pack); + m_rawPacketQueueLock.unlock(); + + emit NewDataAvailable(); + } + } +} + +bool RTPUDPTransmitter::ShouldAcceptData(const RTPAddress& rtpAddress) +{ + if (m_receivemode == RTPTransmitter::AcceptAll) + { + return true; + } + else if (m_receivemode == RTPTransmitter::AcceptSome) + { + std::list::iterator findIt = std::find(m_acceptList.begin(), m_acceptList.end(), rtpAddress); + return findIt != m_acceptList.end(); + } + else // this is RTPTransmitter::IgnoreSome + { + std::list::iterator findIt = std::find(m_ignoreList.begin(), m_ignoreList.end(), rtpAddress); + return findIt == m_ignoreList.end(); + } +} + +} // namespace + + diff --git a/qrtplib/rtpudptransmitter.h b/qrtplib/rtpudptransmitter.h new file mode 100644 index 000000000..ded4f8a3c --- /dev/null +++ b/qrtplib/rtpudptransmitter.h @@ -0,0 +1,380 @@ +/* + + This file is a part of JRTPLIB + Copyright (c) 1999-2017 Jori Liesenborgs + + Contact: jori.liesenborgs@gmail.com + + This library was developed at the Expertise Centre for Digital Media + (http://www.edm.uhasselt.be), a research center of the Hasselt University + (http://www.uhasselt.be). The library is based upon work done for + my thesis at the School for Knowledge Technology (Belgium/The Netherlands). + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + */ + +#ifndef QRTPLIB_RTPUDPTRANSMITTER_H_ +#define QRTPLIB_RTPUDPTRANSMITTER_H_ + +#include "rtptransmitter.h" +#include "export.h" + +#include +#include +#include +#include +#include + +#include +#include + +#define RTPUDPV4TRANS_HASHSIZE 8317 +#define RTPUDPV4TRANS_DEFAULTPORTBASE 5000 +#define RTPUDPV4TRANS_RTPRECEIVEBUFFER 32768 +#define RTPUDPV4TRANS_RTCPRECEIVEBUFFER 32768 +#define RTPUDPV4TRANS_RTPTRANSMITBUFFER 32768 +#define RTPUDPV4TRANS_RTCPTRANSMITBUFFER 32768 + +class QUdpSocket; + +namespace qrtplib +{ + +/** Parameters for the UDP transmitter. */ +class QRTPLIB_API RTPUDPTransmissionParams: public RTPTransmissionParams +{ +public: + RTPUDPTransmissionParams(); + + /** Sets the IP address which is used to bind the sockets to \c bindAddress. */ + void SetBindIP(const QHostAddress& bindAddress) { + m_bindAddress = bindAddress; + } + + /** Sets the multicast interface IP address. */ + void SetMulticastInterface(const QNetworkInterface& mcastInterface) { + m_mcastInterface = mcastInterface; + } + + /** Sets the RTP portbase to \c pbase, which has to be an even number + * unless RTPUDPv4TransmissionParams::SetAllowOddPortbase was called; + * a port number of zero will cause a port to be chosen automatically. */ + void SetPortbase(uint16_t pbase) + { + m_portbase = pbase; + } + + /** Returns the IP address which will be used to bind the sockets. */ + QHostAddress GetBindIP() const + { + return m_bindAddress; + } + + /** Returns the multicast interface IP address. */ + QNetworkInterface GetMulticastInterface() const + { + return m_mcastInterface; + } + + /** Returns the RTP portbase which will be used (default is 5000). */ + uint16_t GetPortbase() const + { + return m_portbase; + } + + /** Sets the RTP socket's send buffer size. */ + void SetRTPSendBufferSize(int s) + { + m_rtpsendbufsz = s; + } + + /** Sets the RTP socket's receive buffer size. */ + void SetRTPReceiveBufferSize(int s) + { + m_rtprecvbufsz = s; + } + + /** Sets the RTCP socket's send buffer size. */ + void SetRTCPSendBufferSize(int s) + { + m_rtcpsendbufsz = s; + } + + /** Sets the RTCP socket's receive buffer size. */ + void SetRTCPReceiveBufferSize(int s) + { + m_rtcprecvbufsz = s; + } + + /** Enables or disables multiplexing RTCP traffic over the RTP channel, so that only a single port is used. */ + void SetRTCPMultiplexing(bool f) + { + m_rtcpmux = f; + } + + /** Can be used to allow the RTP port base to be any number, not just even numbers. */ + void SetAllowOddPortbase(bool f) + { + m_allowoddportbase = f; + } + + /** Force the RTCP socket to use a specific port, not necessarily one more than + * the RTP port (set this to zero to disable). */ + void SetForcedRTCPPort(uint16_t rtcpport) + { + m_forcedrtcpport = rtcpport; + } + + /** Use sockets that have already been created, no checks on port numbers + * will be done, and no buffer sizes will be set; you'll need to close + * the sockets yourself when done, it will **not** be done automatically. */ + void SetUseExistingSockets(QUdpSocket *rtpsocket, QUdpSocket *rtcpsocket) + { + m_rtpsock = rtpsocket; + m_rtcpsock = rtcpsocket; + m_useexistingsockets = true; + } + + /** Returns the RTP socket's send buffer size. */ + int GetRTPSendBufferSize() const + { + return m_rtpsendbufsz; + } + + /** Returns the RTP socket's receive buffer size. */ + int GetRTPReceiveBufferSize() const + { + return m_rtprecvbufsz; + } + + /** Returns the RTCP socket's send buffer size. */ + int GetRTCPSendBufferSize() const + { + return m_rtcpsendbufsz; + } + + /** Returns the RTCP socket's receive buffer size. */ + int GetRTCPReceiveBufferSize() const + { + return m_rtcprecvbufsz; + } + + /** Returns a flag indicating if RTCP traffic will be multiplexed over the RTP channel. */ + bool GetRTCPMultiplexing() const + { + return m_rtcpmux; + } + + /** If true, any RTP portbase will be allowed, not just even numbers. */ + bool GetAllowOddPortbase() const + { + return m_allowoddportbase; + } + + /** If non-zero, the specified port will be used to receive RTCP traffic. */ + uint16_t GetForcedRTCPPort() const + { + return m_forcedrtcpport; + } + + /** Returns true and fills in sockets if existing sockets were set + * using RTPUDPv4TransmissionParams::SetUseExistingSockets. */ + bool GetUseExistingSockets(QUdpSocket **rtpsocket, QUdpSocket **rtcpsocket) const + { + if (!m_useexistingsockets) { + return false; + } + + *rtpsocket = m_rtpsock; + *rtcpsocket = m_rtcpsock; + + return true; + } + +private: + QHostAddress m_bindAddress; + QNetworkInterface m_mcastInterface; + uint16_t m_portbase; + int m_rtpsendbufsz, m_rtprecvbufsz; + int m_rtcpsendbufsz, m_rtcprecvbufsz; + bool m_rtcpmux; + bool m_allowoddportbase; + uint16_t m_forcedrtcpport; + + QUdpSocket *m_rtpsock, *m_rtcpsock; + bool m_useexistingsockets; +}; + +inline RTPUDPTransmissionParams::RTPUDPTransmissionParams() : + RTPTransmissionParams(RTPTransmitter::IPv4UDPProto) +{ + m_portbase = RTPUDPV4TRANS_DEFAULTPORTBASE; + m_rtpsendbufsz = RTPUDPV4TRANS_RTPTRANSMITBUFFER; + m_rtprecvbufsz = RTPUDPV4TRANS_RTPRECEIVEBUFFER; + m_rtcpsendbufsz = RTPUDPV4TRANS_RTCPTRANSMITBUFFER; + m_rtcprecvbufsz = RTPUDPV4TRANS_RTCPRECEIVEBUFFER; + m_rtcpmux = false; + m_allowoddportbase = false; + m_forcedrtcpport = 0; + m_rtpsock = 0; + m_rtcpsock = 0; + m_useexistingsockets = false; +} + +/** Additional information about the UDP over IPv4 transmitter. */ +class QRTPLIB_API RTPUDPTransmissionInfo: public RTPTransmissionInfo +{ +public: + RTPUDPTransmissionInfo( + QHostAddress localIP, + QUdpSocket *rtpsock, + QUdpSocket *rtcpsock, + uint16_t rtpport, + uint16_t rtcpport) : + RTPTransmissionInfo(RTPTransmitter::IPv4UDPProto) + { + m_localIP = localIP; + m_rtpsocket = rtpsock; + m_rtcpsocket = rtcpsock; + m_rtpPort = rtpport; + m_rtcpPort = rtcpport; + } + + ~RTPUDPTransmissionInfo() + { + } + + /** Returns the socket descriptor used for receiving and transmitting RTP packets. */ + QUdpSocket *GetRTPSocket() const + { + return m_rtpsocket; + } + + /** Returns the socket descriptor used for receiving and transmitting RTCP packets. */ + QUdpSocket *GetRTCPSocket() const + { + return m_rtcpsocket; + } + + /** Returns the port number that the RTP socket receives packets on. */ + uint16_t GetRTPPort() const + { + return m_rtpPort; + } + + /** Returns the port number that the RTCP socket receives packets on. */ + uint16_t GetRTCPPort() const + { + return m_rtcpPort; + } +private: + QHostAddress m_localIP; + QUdpSocket *m_rtpsocket, *m_rtcpsocket; + uint16_t m_rtpPort, m_rtcpPort; +}; + +#define RTPUDPTRANS_HEADERSIZE (20+8) + +/** An UDP transmission component. + * This class inherits the RTPTransmitter interface and implements a transmission component + * which uses UDP to send and receive RTP and RTCP data. The component's parameters + * are described by the class RTPUDPTransmissionParams. The GetTransmissionInfo member function + * returns an instance of type RTPUDPTransmissionInfo. + */ +class QRTPLIB_API RTPUDPTransmitter: public QObject, public RTPTransmitter +{ + Q_OBJECT +public: + RTPUDPTransmitter(); + virtual ~RTPUDPTransmitter(); + + virtual int Init(); + virtual int Create(std::size_t maxpacksize, const RTPTransmissionParams *transparams); + virtual int BindSockets(); + void moveToThread(QThread *thread); + virtual void Destroy(); + virtual RTPTransmissionInfo *GetTransmissionInfo(); + virtual void DeleteTransmissionInfo(RTPTransmissionInfo *inf); + + virtual bool ComesFromThisTransmitter(const RTPAddress& addr); + virtual std::size_t GetHeaderOverhead() + { + return RTPUDPTRANS_HEADERSIZE; + } + + virtual int SendRTPData(const void *data, std::size_t len); + virtual int SendRTCPData(const void *data, std::size_t len); + + virtual int AddDestination(const RTPAddress &addr); + virtual int DeleteDestination(const RTPAddress &addr); + virtual void ClearDestinations(); + + virtual bool SupportsMulticasting(); + virtual int JoinMulticastGroup(const RTPAddress &addr); + virtual int LeaveMulticastGroup(const RTPAddress &addr); + + virtual int SetReceiveMode(RTPTransmitter::ReceiveMode m); + virtual int AddToIgnoreList(const RTPAddress &addr); + virtual int DeleteFromIgnoreList(const RTPAddress &addr); + virtual void ClearIgnoreList(); + virtual int AddToAcceptList(const RTPAddress &addr); + virtual int DeleteFromAcceptList(const RTPAddress &addr); + virtual void ClearAcceptList(); + virtual int SetMaximumPacketSize(std::size_t s); + + virtual RTPRawPacket *GetNextPacket(); + + +private: + bool m_init; + bool m_created; + bool m_waitingfordata; + QUdpSocket *m_rtpsock, *m_rtcpsock; + bool m_deletesocketswhendone; + QHostAddress m_localIP; //!< from parameters bind IP + QNetworkInterface m_multicastInterface; //!< from parameters multicast interface + uint16_t m_rtpPort, m_rtcpPort; + RTPTransmitter::ReceiveMode m_receivemode; + + std::size_t m_maxpacksize; + static const std::size_t m_absoluteMaxPackSize = 65535; + char m_rtpBuffer[m_absoluteMaxPackSize]; + char m_rtcpBuffer[m_absoluteMaxPackSize]; + + std::list m_destinations; + std::list m_acceptList; + std::list m_ignoreList; + QQueue m_rawPacketQueue; + QMutex m_rawPacketQueueLock; + + bool ShouldAcceptData(const RTPAddress& address); + +private slots: + void readRTPPendingDatagrams(); + void readRTCPPendingDatagrams(); + +signals: + void NewDataAvailable(); +}; + + +} // namespace + +#endif /* QRTPLIB_RTPUDPTRANSMITTER_H_ */ diff --git a/rescuesdriq/readme.md b/rescuesdriq/readme.md new file mode 100644 index 000000000..6d2092bc0 --- /dev/null +++ b/rescuesdriq/readme.md @@ -0,0 +1,65 @@ +

Repair or reformat record (.sdriq) files

+ +

Usage

+ +This utility attempts to repair .sdriq files that have their header corrupted or with a pre version 4.2.1 header. Since version 4.2.1 a CRC32 checksum is present and the file will not be played if the check of the header content against the CRC32 fails. + +The header is composed as follows: + + - Sample rate in S/s (4 bytes, 32 bits) + - Center frequency in Hz (8 bytes, 64 bits) + - Start time Unix timestamp epoch in seconds (8 bytes, 64 bits) + - Sample size as 16 or 24 bits (4 bytes, 32 bits) + - filler with all zeroes (4 bytes, 32 bits) + - CRC32 (IEEE) of the 28 bytes above (4 bytes, 32 bits) + +The header size is 32 bytes in total which is a multiple of 8 bytes thus occupies an integer number of samples whether in 16 or 24 bits mode. When migrating from a pre version 4.2.1 header you may crunch a very small amount of samples. + +You can replace values in the header with the following options: + + - -sr uint + Sample rate (S/s) + - -cf uint + Center frequency (Hz) + - -ts string + start time RFC3339 (ex: 2006-01-02T15:04:05Z) + - -now + use now for start time + - -sz uint + Sample size (16 or 24) (default 16) + +You need to specify an input file. If no output file is specified the current header values are printed to the console and the program exits: + + - -in string + input file (default "foo") + +To convert to a new file you need to specify the output file: + + - -out string + output file (default "foo") + +You can specify a block size in multiples of 4k for the copy. Large blocks will yield a faster copy but a larger output file. With the default of 1 (4k) the copy does not take much time anyway: + + - -bz uint + Copy block size in multiple of 4k (default 1) + +

Build

+ +The program is written in go and is provided only in source code form. Compiling it is very easy: + +

Install go

+ +You will usually find a `golang` package in your distribution. For example in Ubuntu or Debian you can install it with `sudo apt-get install golang`. You can find binary distributions for many systems at the [Go download site](https://golang.org/dl/) + +

Build the program

+ +In this directory just do `go build` + +

Unit testing

+ +Unit test (very simple) is located in `rescuesdriq_test.go`. It uses the [Go Convey](https://github.com/smartystreets/goconvey) framework. You should first install it with: +`go get github.com/smartystreets/goconvey` + +You can run unit test from command line with: `go test` + +Or with the Go Convey server that you start from this directory with: `$GOPATH/bin/goconvey` where `$GOPATH` is the path to your go installation. diff --git a/rescuesdriq/rescuesdriq.go b/rescuesdriq/rescuesdriq.go new file mode 100644 index 000000000..adbfdf619 --- /dev/null +++ b/rescuesdriq/rescuesdriq.go @@ -0,0 +1,187 @@ +package main + +import ( + "bufio" + "bytes" + "encoding/binary" + "flag" + "fmt" + "hash/crc32" + "io" + "os" + "time" +) + +type HeaderStd struct { + SampleRate uint32 + CenterFrequency uint64 + StartTimestamp int64 + SampleSize uint32 + Filler uint32 + CRC32 uint32 +} + +func check(e error) { + if e != nil { + panic(e) + } +} + +func analyze(r *bufio.Reader) HeaderStd { + headerbuf := make([]byte, 32) // This is a full header with CRC + n, err := r.Read(headerbuf) + if err != nil && err != io.EOF { + panic(err) + } + if n != 32 { + panic("Header too small") + } + + var header HeaderStd + headerr := bytes.NewReader(headerbuf) + err = binary.Read(headerr, binary.LittleEndian, &header) + check(err) + + return header +} + +func writeHeader(writer *bufio.Writer, header *HeaderStd) { + var bin_buf bytes.Buffer + binary.Write(&bin_buf, binary.LittleEndian, header) + noh, err := writer.Write(bin_buf.Bytes()) + check(err) + fmt.Printf("Wrote %d bytes header\n", noh) +} + +func setCRC(header *HeaderStd) { + var bin_buf bytes.Buffer + header.Filler = 0 + binary.Write(&bin_buf, binary.LittleEndian, header) + header.CRC32 = crc32.ChecksumIEEE(bin_buf.Bytes()[0:28]) +} + +func GetCRC(header *HeaderStd) uint32 { + var bin_buf bytes.Buffer + header.Filler = 0 + binary.Write(&bin_buf, binary.LittleEndian, header) + return crc32.ChecksumIEEE(bin_buf.Bytes()[0:28]) +} + +func printHeader(header *HeaderStd) { + fmt.Println("Sample rate:", header.SampleRate) + fmt.Println("Frequency :", header.CenterFrequency) + fmt.Println("Sample Size:", header.SampleSize) + tm := time.Unix(header.StartTimestamp, 0) + fmt.Println("Start :", tm) + fmt.Println("CRC32 :", header.CRC32) + fmt.Println("CRC32 OK :", GetCRC(header)) +} + +func copyContent(reader *bufio.Reader, writer *bufio.Writer, blockSize uint) { + p := make([]byte, blockSize*4096) // buffer in 4k multiples + var sz int64 = 0 + + for { + n, err := reader.Read(p) + + if err != nil { + if err == io.EOF { + writer.Write(p[0:n]) + sz += int64(n) + break + } else { + fmt.Println("An error occured during content copy. Aborting") + break + } + } else { + writer.Write(p) + sz += int64(blockSize) * 4096 + } + + fmt.Printf("Wrote %d bytes\r", sz) + } + + fmt.Printf("Wrote %d bytes\r", sz) +} + +func main() { + inFileStr := flag.String("in", "foo", "input file") + outFileStr := flag.String("out", "foo", "output file") + sampleRate := flag.Uint("sr", 0, "Sample rate (S/s)") + centerFreq := flag.Uint64("cf", 0, "Center frequency (Hz)") + sampleSize := flag.Uint("sz", 16, "Sample size (16 or 24)") + timeStr := flag.String("ts", "", "start time RFC3339 (ex: 2006-01-02T15:04:05Z)") + timeNow := flag.Bool("now", false, "use now for start time") + blockSize := flag.Uint("bz", 1, "Copy block size in multiple of 4k") + + flag.Parse() + flagSeen := make(map[string]bool) + flag.Visit(func(f *flag.Flag) { flagSeen[f.Name] = true }) + + if flagSeen["in"] { + fmt.Println("input file :", *inFileStr) + + // open input file + fi, err := os.Open(*inFileStr) + check(err) + // close fi on exit and check for its returned error + defer func() { + err := fi.Close() + check(err) + }() + // make a read buffer + reader := bufio.NewReader(fi) + var headerOrigin HeaderStd = analyze(reader) + printHeader(&headerOrigin) + + if flagSeen["out"] { + if flagSeen["sr"] { + headerOrigin.SampleRate = uint32(*sampleRate) + } else if flagSeen["cf"] { + headerOrigin.CenterFrequency = *centerFreq + } else if flagSeen["sz"] { + if (*sampleSize == 16) || (*sampleSize == 24) { + headerOrigin.SampleSize = uint32(*sampleSize) + } else { + fmt.Println("Incorrect sample size specified. Defaulting to 16") + headerOrigin.SampleSize = 16 + } + } else if flagSeen["ts"] { + t, err := time.Parse(time.RFC3339, *timeStr) + if err == nil { + headerOrigin.StartTimestamp = t.Unix() + } else { + fmt.Println("Incorrect time specified. Defaulting to now") + headerOrigin.StartTimestamp = int64(time.Now().Unix()) + } + } else if *timeNow { + headerOrigin.StartTimestamp = int64(time.Now().Unix()) + } + + fmt.Println("\nHeader is now") + printHeader(&headerOrigin) + setCRC(&headerOrigin) + fmt.Println("CRC32 :", headerOrigin.CRC32) + fmt.Println("Output file:", *outFileStr) + + fo, err := os.Create(*outFileStr) + check(err) + + defer func() { + err := fo.Close() + check(err) + }() + + writer := bufio.NewWriter(fo) + + writeHeader(writer, &headerOrigin) + copyContent(reader, writer, *blockSize) + + fmt.Println("\nCopy done") + writer.Flush() + } + + } else { + fmt.Println("No input file given") + } +} diff --git a/rescuesdriq/rescuesdriq_test.go b/rescuesdriq/rescuesdriq_test.go new file mode 100644 index 000000000..3046a2c1b --- /dev/null +++ b/rescuesdriq/rescuesdriq_test.go @@ -0,0 +1,26 @@ +package main + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestSpec(t *testing.T) { + + // Only pass t into top-level Convey calls + Convey("Given a header structure", t, func() { + var header HeaderStd + header.SampleRate = 75000 + header.CenterFrequency = 435000000 + header.StartTimestamp = 1539083921 + header.SampleSize = 16 + header.Filler = 0 + + crc32 := GetCRC(&header) + + Convey("The CRC32 value should be 2294957931", func() { + So(crc32, ShouldEqual, 2294957931) + }) + }) +} diff --git a/sdrangel.windows.pro b/sdrangel.windows.pro index f68e51083..a27c13f78 100644 --- a/sdrangel.windows.pro +++ b/sdrangel.windows.pro @@ -11,42 +11,43 @@ CONFIG += ordered SUBDIRS = serialdv SUBDIRS += httpserver SUBDIRS += logging +SUBDIRS += qrtplib SUBDIRS += swagger SUBDIRS += sdrbase SUBDIRS += sdrgui -CONFIG(MINGW64)SUBDIRS += nanomsg SUBDIRS += fcdhid SUBDIRS += fcdlib SUBDIRS += libairspy SUBDIRS += libairspyhf -SUBDIRS += libbladerf +#SUBDIRS += libbladerf SUBDIRS += libhackrf -SUBDIRS += libiio -SUBDIRS += libsqlite3 -SUBDIRS += liblimesuite -SUBDIRS += libperseus +CONFIG(!MSVC):SUBDIRS += libiio +CONFIG(!MSVC):SUBDIRS += liblimesuite +CONFIG(!MSVC):SUBDIRS += libperseus SUBDIRS += librtlsdr SUBDIRS += devices SUBDIRS += mbelib SUBDIRS += dsdcc -CONFIG(MINGW64)SUBDIRS += cm256cc +#SUBDIRS += cm256cc SUBDIRS += plugins/samplesource/airspy SUBDIRS += plugins/samplesource/airspyhf -SUBDIRS += plugins/samplesource/bladerfinput +SUBDIRS += plugins/samplesource/bladerf1input +SUBDIRS += plugins/samplesource/bladerf2input SUBDIRS += plugins/samplesource/filesource SUBDIRS += plugins/samplesource/hackrfinput SUBDIRS += plugins/samplesource/limesdrinput -SUBDIRS += plugins/samplesource/plutosdrinput -CONFIG(MINGW64)SUBDIRS += plugins/samplesource/sdrdaemonsource +CONFIG(!MSVC):SUBDIRS += plugins/samplesource/plutosdrinput +#SUBDIRS += plugins/samplesource/sdrdaemonsource SUBDIRS += plugins/samplesource/rtlsdr SUBDIRS += plugins/samplesource/testsource SUBDIRS += plugins/samplesink/filesink -SUBDIRS += plugins/samplesink/bladerfoutput +SUBDIRS += plugins/samplesink/bladerf1output +SUBDIRS += plugins/samplesink/bladerf2output SUBDIRS += plugins/samplesink/hackrfoutput SUBDIRS += plugins/samplesink/limesdroutput -SUBDIRS += plugins/samplesink/plutosdroutput +CONFIG(!MSVC):SUBDIRS += plugins/samplesink/plutosdroutput +#SUBDIRS += plugins/samplesink/sdrdaemonsink SUBDIRS += plugins/channelrx/chanalyzer -SUBDIRS += plugins/channelrx/chanalyzerng SUBDIRS += plugins/channelrx/demodam SUBDIRS += plugins/channelrx/demodatv SUBDIRS += plugins/channelrx/demodbfm @@ -55,14 +56,12 @@ SUBDIRS += plugins/channelrx/demodlora SUBDIRS += plugins/channelrx/demodnfm SUBDIRS += plugins/channelrx/demodssb SUBDIRS += plugins/channelrx/demodwfm -SUBDIRS += plugins/channelrx/tcpsrc -SUBDIRS += plugins/channelrx/udpsrc +SUBDIRS += plugins/channelrx/udpsink SUBDIRS += plugins/channeltx/modam -CONFIG(MINGW64)SUBDIRS += plugins/channeltx/modatv SUBDIRS += plugins/channeltx/modnfm SUBDIRS += plugins/channeltx/modssb SUBDIRS += plugins/channeltx/modwfm -SUBDIRS += plugins/channeltx/udpsink +SUBDIRS += plugins/channeltx/udpsource # Main app must be last SUBDIRS += app diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index 185fc1f0e..9613b434a 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -3,14 +3,18 @@ project (sdrbase) #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined") set(sdrbase_SOURCES - audio/audiodeviceinfo.cpp + audio/audiocompressor.cpp + audio/audiodevicemanager.cpp audio/audiofifo.cpp audio/audiooutput.cpp audio/audioinput.cpp audio/audionetsink.cpp - + channel/channelsinkapi.cpp channel/channelsourceapi.cpp + channel/sdrdaemondataqueue.cpp + channel/sdrdaemondatareadqueue.cpp + commands/command.cpp dsp/afsquelch.cpp @@ -21,23 +25,29 @@ set(sdrbase_SOURCES dsp/ctcssdetector.cpp dsp/cwkeyer.cpp dsp/cwkeyersettings.cpp - dsp/decimatorsf.cpp + dsp/decimatorsif.cpp + dsp/decimatorsff.cpp + dsp/decimatorsfi.cpp dsp/dspcommands.cpp dsp/dspengine.cpp dsp/dspdevicesourceengine.cpp dsp/dspdevicesinkengine.cpp + dsp/fftcorr.cpp dsp/fftengine.cpp - dsp/fftfilt.cxx + dsp/fftfilt.cpp dsp/fftwindow.cpp dsp/filterrc.cpp dsp/filtermbe.cpp dsp/filerecord.cpp + dsp/freqlockcomplex.cpp dsp/interpolator.cpp dsp/hbfiltertraits.cpp dsp/lowpass.cpp dsp/nco.cpp dsp/ncof.cpp dsp/phaselock.cpp + dsp/phaselockcomplex.cpp + dsp/projector.cpp dsp/samplesinkfifo.cpp dsp/samplesourcefifo.cpp dsp/samplesinkfifodoublebuffered.cpp @@ -65,25 +75,27 @@ set(sdrbase_SOURCES util/message.cpp util/messagequeue.cpp util/prettyprint.cpp + util/rtpsink.cpp util/syncmessenger.cpp util/samplesourceserializer.cpp util/simpleserializer.cpp #util/spinlock.cpp util/uid.cpp - - plugin/plugininterface.cpp + + plugin/plugininterface.cpp plugin/pluginapi.cpp plugin/pluginmanager.cpp - + webapi/webapiadapterinterface.cpp webapi/webapirequestmapper.cpp webapi/webapiserver.cpp - + mainparser.cpp ) set(sdrbase_HEADERS - audio/audiodeviceinfo.h + audio/audiocompressor.h + audio/audiodevicemanager.h audio/audiofifo.h audio/audiooutput.h audio/audioinput.h @@ -91,6 +103,10 @@ set(sdrbase_HEADERS channel/channelsinkapi.h channel/channelsourceapi.h + channel/sdrdaemondataqueue.h + channel/sdrdaemondatareadqueue.h + channel/sdrdaemondatablock.h + commands/command.h dsp/afsquelch.h @@ -102,7 +118,9 @@ set(sdrbase_HEADERS dsp/cwkeyer.h dsp/cwkeyersettings.h dsp/decimators.h - dsp/decimatorsf.h + dsp/decimatorsif.h + dsp/decimatorsff.h + dsp/decimatorsfi.h dsp/decimatorsu.h dsp/interpolators.h dsp/dspcommands.h @@ -110,6 +128,7 @@ set(sdrbase_HEADERS dsp/dspdevicesourceengine.h dsp/dspdevicesinkengine.h dsp/dsptypes.h + dsp/fftcorr.h dsp/fftengine.h dsp/fftfilt.h dsp/fftwengine.h @@ -117,6 +136,7 @@ set(sdrbase_HEADERS dsp/filterrc.h dsp/filtermbe.h dsp/filerecord.h + dsp/freqlockcomplex.h dsp/gfft.h dsp/iirfilter.h dsp/interpolator.h @@ -124,8 +144,11 @@ set(sdrbase_HEADERS dsp/inthalfbandfilter.h dsp/inthalfbandfilterdb.h dsp/inthalfbandfilterdbf.h - dsp/inthalfbandfiltereo1.h - dsp/inthalfbandfiltereo1i.h + dsp/inthalfbandfiltereo.h + # dsp/inthalfbandfiltereo1.h + # dsp/inthalfbandfiltereo1i.h + # dsp/inthalfbandfiltereo2.h + dsp/inthalfbandfiltereof.h dsp/inthalfbandfilterst.h dsp/inthalfbandfiltersti.h dsp/kissfft.h @@ -137,6 +160,8 @@ set(sdrbase_HEADERS dsp/ncof.h dsp/phasediscri.h dsp/phaselock.h + dsp/phaselockcomplex.h + dsp/projector.h dsp/recursivefilters.h dsp/samplesinkfifo.h dsp/samplesourcefifo.h @@ -167,22 +192,23 @@ set(sdrbase_HEADERS util/CRC64.h util/db.h util/doublebuffer.h - util/export.h + util/doublebufferfifo.h util/fixedtraits.h util/message.h util/messagequeue.h util/movingaverage.h util/prettyprint.h + util/rtpsink.h util/syncmessenger.h util/samplesourceserializer.h util/simpleserializer.h #util/spinlock.h util/uid.h - + webapi/webapiadapterinterface.h webapi/webapirequestmapper.h webapi/webapiserver - + mainparser.h ) @@ -215,19 +241,6 @@ else(FFTW3F_FOUND) add_definitions(-DUSE_KISSFFT) endif(FFTW3F_FOUND) -if (JRTPLIB_FOUND) - set(sdrbase_HEADERS - ${sdrbase_HEADERS} - util/rtpsink.h - ) - set(sdrbase_SOURCES - ${sdrbase_SOURCES} - util/rtpsink.cpp - ) - add_definitions(-DHAS_JRTPLIB) - include_directories(${JRTPLIB_INCLUDE_DIR}) -endif(JRTPLIB_FOUND) - if (LIBSERIALDV_FOUND) set(sdrbase_SOURCES ${sdrbase_SOURCES} @@ -269,13 +282,16 @@ add_library(sdrbase SHARED include_directories( ${CMAKE_CURRENT_BINARY_DIR} . + ${CMAKE_SOURCE_DIR}/exports ${CMAKE_SOURCE_DIR}/httpserver + ${CMAKE_SOURCE_DIR}/qrtplib ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ) target_link_libraries(sdrbase ${QT_LIBRARIES} httpserver + qrtplib swagger ) @@ -283,10 +299,6 @@ if(FFTW3F_FOUND) target_link_libraries(sdrbase ${FFTW3F_LIBRARIES}) endif(FFTW3F_FOUND) -if (JRTPLIB_FOUND) - target_link_libraries(sdrbase ${JRTPLIB_LIBRARIES}) -endif(JRTPLIB_FOUND) - if(LIBSERIALDV_FOUND) target_link_libraries(sdrbase ${LIBSERIALDV_LIBRARY}) endif(LIBSERIALDV_FOUND) @@ -295,10 +307,10 @@ if (BUILD_DEBIAN) target_link_libraries(sdrbase serialdv) endif (BUILD_DEBIAN) -set_target_properties(sdrbase PROPERTIES DEFINE_SYMBOL "sdrangel_EXPORTS") +set_target_properties(sdrbase PROPERTIES DEFINE_SYMBOL "sdrbase_EXPORTS") target_compile_features(sdrbase PRIVATE cxx_generalized_initializers) # cmake >= 3.1.0 -qt5_use_modules(sdrbase Core Multimedia) +target_link_libraries(sdrbase Qt5::Core Qt5::Multimedia) install(TARGETS sdrbase DESTINATION lib) diff --git a/sdrbase/audio/audiocompressor.cpp b/sdrbase/audio/audiocompressor.cpp new file mode 100644 index 000000000..fd25257f7 --- /dev/null +++ b/sdrbase/audio/audiocompressor.cpp @@ -0,0 +1,87 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "audiocompressor.h" + +AudioCompressor::AudioCompressor() +{ + fillLUT2(); +} + +AudioCompressor::~AudioCompressor() +{} + +void AudioCompressor::fillLUT() +{ + for (int i=0; i<8192; i++) { + m_lut[i] = (24576/8192)*i; + } + + for (int i=8192; i<2*8192; i++) { + m_lut[i] = 24576 + 0.5f*(i-8192); + } + + for (int i=2*8192; i<3*8192; i++) { + m_lut[i] = 24576 + 4096 + 0.25f*(i-2*8192); + } + + for (int i=3*8192; i<4*8192; i++) { + m_lut[i] = 24576 + 4096 + 2048 + 0.125f*(i-3*8192); + } +} + +void AudioCompressor::fillLUT2() +{ + for (int i=0; i<4096; i++) { + m_lut[i] = (24576/4096)*i; + } + + for (int i=4096; i<2*4096; i++) { + m_lut[i] = 24576 + 0.5f*(i-4096); + } + + for (int i=2*4096; i<3*4096; i++) { + m_lut[i] = 24576 + 2048 + 0.25f*(i-2*4096); + } + + for (int i=3*4096; i<4*4096; i++) { + m_lut[i] = 24576 + 2048 + 1024 + 0.125f*(i-3*4096); + } + + for (int i=4*4096; i<5*4096; i++) { + m_lut[i] = 24576 + 2048 + 1024 + 512 + 0.0625f*(i-4*4096); + } + + for (int i=5*4096; i<6*4096; i++) { + m_lut[i] = 24576 + 2048 + 1024 + 512 + 256 + 0.03125f*(i-5*4096); + } + + for (int i=6*4096; i<7*4096; i++) { + m_lut[i] = 24576 + 2048 + 1024 + 512 + 256 + 128 + 0.015625f*(i-6*4096); + } + + for (int i=7*4096; i<8*4096; i++) { + m_lut[i] = 24576 + 2048 + 1024 + 512 + 256 + 128 + 64 + 0.0078125f*(i-7*4096); + } +} + +int16_t AudioCompressor::compress(int16_t sample) +{ + int16_t sign = sample < 0 ? -1 : 1; + int16_t abs = sample < 0 ? -sample : sample; + return sign * m_lut[abs]; +} diff --git a/sdrbase/audio/audiocompressor.h b/sdrbase/audio/audiocompressor.h new file mode 100644 index 000000000..7f7ba0edd --- /dev/null +++ b/sdrbase/audio/audiocompressor.h @@ -0,0 +1,39 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_AUDIO_AUDIOCOMPRESSOR_H_ +#define SDRBASE_AUDIO_AUDIOCOMPRESSOR_H_ + +#include +#include "export.h" + +class SDRBASE_API AudioCompressor +{ +public: + AudioCompressor(); + ~AudioCompressor(); + void fillLUT(); //!< 4 bands + void fillLUT2(); //!< 8 bands (default) + int16_t compress(int16_t sample); + +private: + int16_t m_lut[32768]; +}; + + + +#endif /* SDRBASE_AUDIO_AUDIOCOMPRESSOR_H_ */ diff --git a/sdrbase/audio/audiodeviceinfo.cpp b/sdrbase/audio/audiodeviceinfo.cpp deleted file mode 100644 index 0a37a17fb..000000000 --- a/sdrbase/audio/audiodeviceinfo.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2017 F4EXB // -// written by Edouard Griffiths // -// // -// This program is free software; you can redistribute it and/or modify // -// it under the terms of the GNU General Public License as published by // -// the Free Software Foundation as version 3 of the License, or // -// // -// This program is distributed in the hope that it will be useful, // -// but WITHOUT ANY WARRANTY; without even the implied warranty of // -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // -// GNU General Public License V3 for more details. // -// // -// You should have received a copy of the GNU General Public License // -// along with this program. If not, see . // -/////////////////////////////////////////////////////////////////////////////////// - -#include "audio/audiodeviceinfo.h" -#include "util/simpleserializer.h" - -AudioDeviceInfo::AudioDeviceInfo() : - m_inputDeviceIndex(-1), // default device - m_outputDeviceIndex(-1), // default device - m_inputVolume(1.0f) -{ - m_inputDevicesInfo = QAudioDeviceInfo::availableDevices(QAudio::AudioInput); - m_outputDevicesInfo = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput); -} - -void AudioDeviceInfo::resetToDefaults() -{ - m_inputDeviceIndex = -1; - m_outputDeviceIndex = -1; - m_inputVolume = 1.0f; -} - -QByteArray AudioDeviceInfo::serialize() const -{ - SimpleSerializer s(1); - s.writeS32(1, m_inputDeviceIndex); - s.writeS32(2, m_outputDeviceIndex); - s.writeFloat(3, m_inputVolume); - return s.final(); -} - -bool AudioDeviceInfo::deserialize(const QByteArray& data) -{ - SimpleDeserializer d(data); - - if(!d.isValid()) { - resetToDefaults(); - return false; - } - - if(d.getVersion() == 1) - { - d.readS32(1, &m_inputDeviceIndex, -1); - d.readS32(2, &m_outputDeviceIndex, -1); - d.readFloat(3, &m_inputVolume, 1.0f); - return true; - } - else - { - resetToDefaults(); - return false; - } -} - -void AudioDeviceInfo::setInputDeviceIndex(int inputDeviceIndex) -{ - int nbDevices = m_inputDevicesInfo.size(); - m_inputDeviceIndex = inputDeviceIndex < -1 ? -1 : inputDeviceIndex >= nbDevices ? nbDevices-1 : inputDeviceIndex; -} - -void AudioDeviceInfo::setOutputDeviceIndex(int outputDeviceIndex) -{ - int nbDevices = m_outputDevicesInfo.size(); - m_outputDeviceIndex = outputDeviceIndex < -1 ? -1 : outputDeviceIndex >= nbDevices ? nbDevices-1 : outputDeviceIndex; -} - -void AudioDeviceInfo::setInputVolume(float inputVolume) -{ - m_inputVolume = inputVolume < 0.0 ? 0.0 : inputVolume > 1.0 ? 1.0 : inputVolume; -} diff --git a/sdrbase/audio/audiodevicemanager.cpp b/sdrbase/audio/audiodevicemanager.cpp new file mode 100644 index 000000000..1b6d90c8d --- /dev/null +++ b/sdrbase/audio/audiodevicemanager.cpp @@ -0,0 +1,761 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 F4EXB // +// written by Edouard Griffiths // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "audio/audiodevicemanager.h" +#include "util/simpleserializer.h" +#include "util/messagequeue.h" +#include "dsp/dspcommands.h" + +#include +#include +#include + +const float AudioDeviceManager::m_defaultAudioInputVolume = 0.15f; +const QString AudioDeviceManager::m_defaultUDPAddress = "127.0.0.1"; +const QString AudioDeviceManager::m_defaultDeviceName = "System default device"; + +QDataStream& operator<<(QDataStream& ds, const AudioDeviceManager::InputDeviceInfo& info) +{ + ds << info.sampleRate << info.volume; + return ds; +} + +QDataStream& operator>>(QDataStream& ds, AudioDeviceManager::InputDeviceInfo& info) +{ + ds >> info.sampleRate >> info.volume; + return ds; +} + +QDataStream& operator<<(QDataStream& ds, const AudioDeviceManager::OutputDeviceInfo& info) +{ + ds << info.sampleRate << info.udpAddress << info.udpPort << info.copyToUDP << info.udpUseRTP << (int) info.udpChannelMode; + return ds; +} + +QDataStream& operator>>(QDataStream& ds, AudioDeviceManager::OutputDeviceInfo& info) +{ + int intChannelMode; + ds >> info.sampleRate >> info.udpAddress >> info.udpPort >> info.copyToUDP >> info.udpUseRTP >> intChannelMode; + info.udpChannelMode = (AudioOutput::UDPChannelMode) intChannelMode; + return ds; +} + +AudioDeviceManager::AudioDeviceManager() +{ + qDebug("AudioDeviceManager::AudioDeviceManager: scan input devices"); + m_inputDevicesInfo = QAudioDeviceInfo::availableDevices(QAudio::AudioInput); + + for (int i = 0; i < m_inputDevicesInfo.size(); i++) { + qDebug("AudioDeviceManager::AudioDeviceManager: input device #%d: %s", i, qPrintable(m_inputDevicesInfo[i].deviceName())); + } + + qDebug("AudioDeviceManager::AudioDeviceManager: scan output devices"); + m_outputDevicesInfo = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput); + + for (int i = 0; i < m_outputDevicesInfo.size(); i++) { + qDebug("AudioDeviceManager::AudioDeviceManager: output device #%d: %s", i, qPrintable(m_outputDevicesInfo[i].deviceName())); + } +} + +AudioDeviceManager::~AudioDeviceManager() +{ + QMap::iterator it = m_audioOutputs.begin(); + + for (; it != m_audioOutputs.end(); ++it) { + delete(*it); + } +} + +bool AudioDeviceManager::getOutputDeviceName(int outputDeviceIndex, QString &deviceName) const +{ + if (outputDeviceIndex < 0) + { + deviceName = m_defaultDeviceName; + return true; + } + else + { + if (outputDeviceIndex < m_outputDevicesInfo.size()) + { + deviceName = m_outputDevicesInfo[outputDeviceIndex].deviceName(); + return true; + } + else + { + return false; + } + } +} + +bool AudioDeviceManager::getInputDeviceName(int inputDeviceIndex, QString &deviceName) const +{ + if (inputDeviceIndex < 0) + { + deviceName = m_defaultDeviceName; + return true; + } + else + { + if (inputDeviceIndex < m_inputDevicesInfo.size()) + { + deviceName = m_inputDevicesInfo[inputDeviceIndex].deviceName(); + return true; + } + else + { + return false; + } + } +} + +int AudioDeviceManager::getOutputDeviceIndex(const QString &deviceName) const +{ + for (int i = 0; i < m_outputDevicesInfo.size(); i++) + { + //qDebug("AudioDeviceManager::getOutputDeviceIndex: %d: %s|%s", i, qPrintable(deviceName), qPrintable(m_outputDevicesInfo[i].deviceName())); + if (deviceName == m_outputDevicesInfo[i].deviceName()) { + return i; + } + } + + return -1; // system default +} + +int AudioDeviceManager::getInputDeviceIndex(const QString &deviceName) const +{ + for (int i = 0; i < m_inputDevicesInfo.size(); i++) + { + //qDebug("AudioDeviceManager::getInputDeviceIndex: %d: %s|%s", i, qPrintable(deviceName), qPrintable(m_inputDevicesInfo[i].deviceName())); + if (deviceName == m_inputDevicesInfo[i].deviceName()) { + return i; + } + } + + return -1; // system default +} + + +void AudioDeviceManager::resetToDefaults() +{ +} + +QByteArray AudioDeviceManager::serialize() const +{ + qDebug("AudioDeviceManager::serialize"); + debugAudioInputInfos(); + debugAudioOutputInfos(); + + SimpleSerializer s(1); + QByteArray data; + + serializeInputMap(data); + s.writeBlob(1, data); + serializeOutputMap(data); + s.writeBlob(2, data); + + return s.final(); +} + +void AudioDeviceManager::serializeInputMap(QByteArray& data) const +{ + QDataStream *stream = new QDataStream(&data, QIODevice::WriteOnly); + *stream << m_audioInputInfos; + delete stream; +} + +void AudioDeviceManager::serializeOutputMap(QByteArray& data) const +{ + QDataStream *stream = new QDataStream(&data, QIODevice::WriteOnly); + *stream << m_audioOutputInfos; + delete stream; +} + +bool AudioDeviceManager::deserialize(const QByteArray& data) +{ + qDebug("AudioDeviceManager::deserialize"); + + SimpleDeserializer d(data); + + if(!d.isValid()) { + resetToDefaults(); + return false; + } + + if(d.getVersion() == 1) + { + QByteArray data; + + d.readBlob(1, &data); + deserializeInputMap(data); + d.readBlob(2, &data); + deserializeOutputMap(data); + + debugAudioInputInfos(); + debugAudioOutputInfos(); + + return true; + } + else + { + resetToDefaults(); + return false; + } +} + +void AudioDeviceManager::deserializeInputMap(QByteArray& data) +{ + QDataStream readStream(&data, QIODevice::ReadOnly); + readStream >> m_audioInputInfos; +} + +void AudioDeviceManager::deserializeOutputMap(QByteArray& data) +{ + QDataStream readStream(&data, QIODevice::ReadOnly); + readStream >> m_audioOutputInfos; +} + +void AudioDeviceManager::addAudioSink(AudioFifo* audioFifo, MessageQueue *sampleSinkMessageQueue, int outputDeviceIndex) +{ + qDebug("AudioDeviceManager::addAudioSink: %d: %p", outputDeviceIndex, audioFifo); + + if (m_audioOutputs.find(outputDeviceIndex) == m_audioOutputs.end()) { + m_audioOutputs[outputDeviceIndex] = new AudioOutput(); + } + + if (m_audioOutputs[outputDeviceIndex]->getNbFifos() == 0) { + startAudioOutput(outputDeviceIndex); + } + + if (m_audioSinkFifos.find(audioFifo) == m_audioSinkFifos.end()) // new FIFO + { + m_audioOutputs[outputDeviceIndex]->addFifo(audioFifo); + m_audioSinkFifos[audioFifo] = outputDeviceIndex; // register audio FIFO + m_audioFifoToSinkMessageQueues[audioFifo] = sampleSinkMessageQueue; + m_outputDeviceSinkMessageQueues[outputDeviceIndex].append(sampleSinkMessageQueue); + } + else + { + int audioOutputDeviceIndex = m_audioSinkFifos[audioFifo]; + + if (audioOutputDeviceIndex != outputDeviceIndex) // change of audio device + { + removeAudioSink(audioFifo); // remove from current + m_audioOutputs[outputDeviceIndex]->addFifo(audioFifo); // add to new + m_audioSinkFifos[audioFifo] = outputDeviceIndex; // new index + m_outputDeviceSinkMessageQueues[audioOutputDeviceIndex].removeOne(sampleSinkMessageQueue); + m_outputDeviceSinkMessageQueues[outputDeviceIndex].append(sampleSinkMessageQueue); + } + } +} + +void AudioDeviceManager::removeAudioSink(AudioFifo* audioFifo) +{ + qDebug("AudioDeviceManager::removeAudioSink: %p", audioFifo); + + if (m_audioSinkFifos.find(audioFifo) == m_audioSinkFifos.end()) + { + qWarning("AudioDeviceManager::removeAudioSink: audio FIFO %p not found", audioFifo); + return; + } + + int audioOutputDeviceIndex = m_audioSinkFifos[audioFifo]; + m_audioOutputs[audioOutputDeviceIndex]->removeFifo(audioFifo); + + if (m_audioOutputs[audioOutputDeviceIndex]->getNbFifos() == 0) { + stopAudioOutput(audioOutputDeviceIndex); + } + + m_audioSinkFifos.remove(audioFifo); // unregister audio FIFO + m_outputDeviceSinkMessageQueues[audioOutputDeviceIndex].removeOne(m_audioFifoToSinkMessageQueues[audioFifo]); + m_audioFifoToSinkMessageQueues.remove(audioFifo); +} + +void AudioDeviceManager::addAudioSource(AudioFifo* audioFifo, MessageQueue *sampleSourceMessageQueue, int inputDeviceIndex) +{ + qDebug("AudioDeviceManager::addAudioSource: %d: %p", inputDeviceIndex, audioFifo); + + if (m_audioInputs.find(inputDeviceIndex) == m_audioInputs.end()) { + m_audioInputs[inputDeviceIndex] = new AudioInput(); + } + + if (m_audioInputs[inputDeviceIndex]->getNbFifos() == 0) { + startAudioInput(inputDeviceIndex); + } + + if (m_audioSourceFifos.find(audioFifo) == m_audioSourceFifos.end()) // new FIFO + { + m_audioInputs[inputDeviceIndex]->addFifo(audioFifo); + } + else + { + int audioInputDeviceIndex = m_audioSourceFifos[audioFifo]; + + if (audioInputDeviceIndex != inputDeviceIndex) // change of audio device + { + removeAudioSource(audioFifo); // remove from current + m_audioInputs[inputDeviceIndex]->addFifo(audioFifo); // add to new + } + } + + m_audioSourceFifos[audioFifo] = inputDeviceIndex; // register audio FIFO + m_audioFifoToSourceMessageQueues[audioFifo] = sampleSourceMessageQueue; + m_outputDeviceSinkMessageQueues[inputDeviceIndex].append(sampleSourceMessageQueue); +} + +void AudioDeviceManager::removeAudioSource(AudioFifo* audioFifo) +{ + qDebug("AudioDeviceManager::removeAudioSource: %p", audioFifo); + + if (m_audioSourceFifos.find(audioFifo) == m_audioSourceFifos.end()) + { + qWarning("AudioDeviceManager::removeAudioSource: audio FIFO %p not found", audioFifo); + return; + } + + int audioInputDeviceIndex = m_audioSourceFifos[audioFifo]; + m_audioInputs[audioInputDeviceIndex]->removeFifo(audioFifo); + + if (m_audioInputs[audioInputDeviceIndex]->getNbFifos() == 0) { + stopAudioInput(audioInputDeviceIndex); + } + + m_audioSourceFifos.remove(audioFifo); // unregister audio FIFO + m_inputDeviceSourceMessageQueues[audioInputDeviceIndex].removeOne(m_audioFifoToSourceMessageQueues[audioFifo]); + m_audioFifoToSourceMessageQueues.remove(audioFifo); +} + +void AudioDeviceManager::startAudioOutput(int outputDeviceIndex) +{ + unsigned int sampleRate; + QString udpAddress; + quint16 udpPort; + bool copyAudioToUDP; + bool udpUseRTP; + AudioOutput::UDPChannelMode udpChannelMode; + QString deviceName; + + if (getOutputDeviceName(outputDeviceIndex, deviceName)) + { + if (m_audioOutputInfos.find(deviceName) == m_audioOutputInfos.end()) + { + sampleRate = m_defaultAudioSampleRate; + udpAddress = m_defaultUDPAddress; + udpPort = m_defaultUDPPort; + copyAudioToUDP = false; + udpUseRTP = false; + udpChannelMode = AudioOutput::UDPChannelLeft; + } + else + { + sampleRate = m_audioOutputInfos[deviceName].sampleRate; + udpAddress = m_audioOutputInfos[deviceName].udpAddress; + udpPort = m_audioOutputInfos[deviceName].udpPort; + copyAudioToUDP = m_audioOutputInfos[deviceName].copyToUDP; + udpUseRTP = m_audioOutputInfos[deviceName].udpUseRTP; + udpChannelMode = m_audioOutputInfos[deviceName].udpChannelMode; + } + + m_audioOutputs[outputDeviceIndex]->start(outputDeviceIndex, sampleRate); + m_audioOutputInfos[deviceName].sampleRate = m_audioOutputs[outputDeviceIndex]->getRate(); // update with actual rate + m_audioOutputInfos[deviceName].udpAddress = udpAddress; + m_audioOutputInfos[deviceName].udpPort = udpPort; + m_audioOutputInfos[deviceName].copyToUDP = copyAudioToUDP; + m_audioOutputInfos[deviceName].udpUseRTP = udpUseRTP; + m_audioOutputInfos[deviceName].udpChannelMode = udpChannelMode; + } + else + { + qWarning("AudioDeviceManager::startAudioOutput: unknown device index %d", outputDeviceIndex); + } +} + +void AudioDeviceManager::stopAudioOutput(int outputDeviceIndex) +{ + m_audioOutputs[outputDeviceIndex]->stop(); +} + +void AudioDeviceManager::startAudioInput(int inputDeviceIndex) +{ + unsigned int sampleRate; + float volume; + QString deviceName; + + if (getInputDeviceName(inputDeviceIndex, deviceName)) + { + if (m_audioInputInfos.find(deviceName) == m_audioInputInfos.end()) + { + sampleRate = m_defaultAudioSampleRate; + volume = m_defaultAudioInputVolume; + } + else + { + sampleRate = m_audioInputInfos[deviceName].sampleRate; + volume = m_audioInputInfos[deviceName].volume; + } + + m_audioInputs[inputDeviceIndex]->start(inputDeviceIndex, sampleRate); + m_audioInputs[inputDeviceIndex]->setVolume(volume); + m_audioInputInfos[deviceName].sampleRate = m_audioInputs[inputDeviceIndex]->getRate(); + m_audioInputInfos[deviceName].volume = volume; + } + else + { + qWarning("AudioDeviceManager::startAudioInput: unknown device index %d", inputDeviceIndex); + } +} + +void AudioDeviceManager::stopAudioInput(int inputDeviceIndex) +{ + m_audioInputs[inputDeviceIndex]->stop(); +} + +bool AudioDeviceManager::getInputDeviceInfo(const QString& deviceName, InputDeviceInfo& deviceInfo) const +{ + if (m_audioInputInfos.find(deviceName) == m_audioInputInfos.end()) + { + return false; + } + else + { + deviceInfo = m_audioInputInfos[deviceName]; + return true; + } +} + +bool AudioDeviceManager::getOutputDeviceInfo(const QString& deviceName, OutputDeviceInfo& deviceInfo) const +{ + if (m_audioOutputInfos.find(deviceName) == m_audioOutputInfos.end()) + { + return false; + } + else + { + deviceInfo = m_audioOutputInfos[deviceName]; + return true; + } +} + +int AudioDeviceManager::getInputSampleRate(int inputDeviceIndex) +{ + QString deviceName; + + if (!getInputDeviceName(inputDeviceIndex, deviceName)) + { + qDebug("AudioDeviceManager::getInputSampleRate: unknown device index %d", inputDeviceIndex); + return m_defaultAudioSampleRate; + } + + InputDeviceInfo deviceInfo; + + if (!getInputDeviceInfo(deviceName, deviceInfo)) + { + qDebug("AudioDeviceManager::getInputSampleRate: unknown device %s", qPrintable(deviceName)); + return m_defaultAudioSampleRate; + } + else + { + return deviceInfo.sampleRate; + } +} + +int AudioDeviceManager::getOutputSampleRate(int outputDeviceIndex) +{ + QString deviceName; + + if (!getOutputDeviceName(outputDeviceIndex, deviceName)) + { + qDebug("AudioDeviceManager::getOutputSampleRate: unknown device index %d", outputDeviceIndex); + return m_defaultAudioSampleRate; + } + + OutputDeviceInfo deviceInfo; + + if (!getOutputDeviceInfo(deviceName, deviceInfo)) + { + qDebug("AudioDeviceManager::getOutputSampleRate: unknown device %s", qPrintable(deviceName)); + return m_defaultAudioSampleRate; + } + else + { + return deviceInfo.sampleRate; + } +} + + +void AudioDeviceManager::setInputDeviceInfo(int inputDeviceIndex, const InputDeviceInfo& deviceInfo) +{ + QString deviceName; + + if (!getInputDeviceName(inputDeviceIndex, deviceName)) + { + qWarning("AudioDeviceManager::setInputDeviceInfo: unknown device index %d", inputDeviceIndex); + return; + } + + InputDeviceInfo oldDeviceInfo; + + if (!getInputDeviceInfo(deviceName, oldDeviceInfo)) + { + qDebug("AudioDeviceManager::setInputDeviceInfo: unknown device %s", qPrintable(deviceName)); + } + + m_audioInputInfos[deviceName] = deviceInfo; + + if (m_audioInputs.find(inputDeviceIndex) == m_audioInputs.end()) { // no FIFO registered yet hence no audio input has been allocated yet + return; + } + + AudioInput *audioInput = m_audioInputs[inputDeviceIndex]; + + if (oldDeviceInfo.sampleRate != deviceInfo.sampleRate) + { + audioInput->stop(); + audioInput->start(inputDeviceIndex, deviceInfo.sampleRate); + m_audioInputInfos[deviceName].sampleRate = audioInput->getRate(); // store actual sample rate + + // send message to attached channels + QList::const_iterator it = m_inputDeviceSourceMessageQueues[inputDeviceIndex].begin(); + + for (; it != m_inputDeviceSourceMessageQueues[inputDeviceIndex].end(); ++it) + { + DSPConfigureAudio *msg = new DSPConfigureAudio(m_audioInputInfos[deviceName].sampleRate); + (*it)->push(msg); + } + } + + audioInput->setVolume(deviceInfo.volume); +} + +void AudioDeviceManager::setOutputDeviceInfo(int outputDeviceIndex, const OutputDeviceInfo& deviceInfo) +{ + QString deviceName; + + if (!getOutputDeviceName(outputDeviceIndex, deviceName)) + { + qWarning("AudioDeviceManager::setOutputDeviceInfo: unknown device index %d", outputDeviceIndex); + return; + } + + OutputDeviceInfo oldDeviceInfo; + + if (!getOutputDeviceInfo(deviceName, oldDeviceInfo)) + { + qInfo("AudioDeviceManager::setOutputDeviceInfo: unknown device %s", qPrintable(deviceName)); + } + + m_audioOutputInfos[deviceName] = deviceInfo; + + if (m_audioOutputs.find(outputDeviceIndex) == m_audioOutputs.end()) + { + qWarning("AudioDeviceManager::setOutputDeviceInfo: index: %d device: %s no FIFO registered yet hence no audio output has been allocated yet", + outputDeviceIndex, qPrintable(deviceName)); + return; + } + + AudioOutput *audioOutput = m_audioOutputs[outputDeviceIndex]; + + if (oldDeviceInfo.sampleRate != deviceInfo.sampleRate) + { + audioOutput->stop(); + audioOutput->start(outputDeviceIndex, deviceInfo.sampleRate); + m_audioOutputInfos[deviceName].sampleRate = audioOutput->getRate(); // store actual sample rate + + // send message to attached channels + QList::const_iterator it = m_outputDeviceSinkMessageQueues[outputDeviceIndex].begin(); + + for (; it != m_outputDeviceSinkMessageQueues[outputDeviceIndex].end(); ++it) + { + DSPConfigureAudio *msg = new DSPConfigureAudio(m_audioOutputInfos[deviceName].sampleRate); + (*it)->push(msg); + } + } + + audioOutput->setUdpCopyToUDP(deviceInfo.copyToUDP); + audioOutput->setUdpDestination(deviceInfo.udpAddress, deviceInfo.udpPort); + audioOutput->setUdpUseRTP(deviceInfo.udpUseRTP); + audioOutput->setUdpChannelMode(deviceInfo.udpChannelMode); + audioOutput->setUdpChannelFormat(deviceInfo.udpChannelMode == AudioOutput::UDPChannelStereo, deviceInfo.sampleRate); + + qDebug("AudioDeviceManager::setOutputDeviceInfo: index: %d device: %s updated", + outputDeviceIndex, qPrintable(deviceName)); +} + +void AudioDeviceManager::unsetOutputDeviceInfo(int outputDeviceIndex) +{ + QString deviceName; + + if (!getOutputDeviceName(outputDeviceIndex, deviceName)) + { + qWarning("AudioDeviceManager::unsetOutputDeviceInfo: unknown device index %d", outputDeviceIndex); + return; + } + + OutputDeviceInfo oldDeviceInfo; + + if (!getOutputDeviceInfo(deviceName, oldDeviceInfo)) + { + qDebug("AudioDeviceManager::unsetOutputDeviceInfo: unregistered device %s", qPrintable(deviceName)); + return; + } + + m_audioOutputInfos.remove(deviceName); + + if (m_audioOutputs.find(outputDeviceIndex) == m_audioOutputs.end()) { // no FIFO registered yet hence no audio output has been allocated yet + return; + } + + stopAudioOutput(outputDeviceIndex); + startAudioOutput(outputDeviceIndex); + + if (oldDeviceInfo.sampleRate != m_audioOutputInfos[deviceName].sampleRate) + { + // send message to attached channels + QList::const_iterator it = m_outputDeviceSinkMessageQueues[outputDeviceIndex].begin(); + + for (; it != m_outputDeviceSinkMessageQueues[outputDeviceIndex].end(); ++it) + { + DSPConfigureAudio *msg = new DSPConfigureAudio(m_audioOutputInfos[deviceName].sampleRate); + (*it)->push(msg); + } + } +} + +void AudioDeviceManager::unsetInputDeviceInfo(int inputDeviceIndex) +{ + QString deviceName; + + if (!getInputDeviceName(inputDeviceIndex, deviceName)) + { + qWarning("AudioDeviceManager::unsetInputDeviceInfo: unknown device index %d", inputDeviceIndex); + return; + } + + InputDeviceInfo oldDeviceInfo; + + if (!getInputDeviceInfo(deviceName, oldDeviceInfo)) + { + qDebug("AudioDeviceManager::unsetInputDeviceInfo: unregistered device %s", qPrintable(deviceName)); + return; + } + + m_audioInputInfos.remove(deviceName); + + if (m_audioInputs.find(inputDeviceIndex) == m_audioInputs.end()) { // no FIFO registered yet hence no audio input has been allocated yet + return; + } + + stopAudioInput(inputDeviceIndex); + startAudioInput(inputDeviceIndex); + + if (oldDeviceInfo.sampleRate != m_audioInputInfos[deviceName].sampleRate) + { + // send message to attached channels + QList::const_iterator it = m_inputDeviceSourceMessageQueues[inputDeviceIndex].begin(); + + for (; it != m_inputDeviceSourceMessageQueues[inputDeviceIndex].end(); ++it) + { + DSPConfigureAudio *msg = new DSPConfigureAudio(m_audioInputInfos[deviceName].sampleRate); + (*it)->push(msg); + } + } +} + +void AudioDeviceManager::inputInfosCleanup() +{ + QSet deviceNames; + deviceNames.insert(m_defaultDeviceName); + QList::const_iterator itd = m_inputDevicesInfo.begin(); + + for (; itd != m_inputDevicesInfo.end(); ++itd) + { + qDebug("AudioDeviceManager::inputInfosCleanup: device: %s", qPrintable(itd->deviceName())); + deviceNames.insert(itd->deviceName()); + } + + QMap::iterator itm = m_audioInputInfos.begin(); + + for (; itm != m_audioInputInfos.end();) + { + if (!deviceNames.contains(itm.key())) + { + qDebug("AudioDeviceManager::inputInfosCleanup: removing key: %s", qPrintable(itm.key())); + m_audioInputInfos.erase(itm++); + } + else + { + ++itm; + } + } +} + +void AudioDeviceManager::outputInfosCleanup() +{ + QSet deviceNames; + deviceNames.insert(m_defaultDeviceName); + QList::const_iterator itd = m_outputDevicesInfo.begin(); + + for (; itd != m_outputDevicesInfo.end(); ++itd) + { + qDebug("AudioDeviceManager::outputInfosCleanup: device: %s", qPrintable(itd->deviceName())); + deviceNames.insert(itd->deviceName()); + } + + QMap::iterator itm = m_audioOutputInfos.begin(); + + for (; itm != m_audioOutputInfos.end();) + { + if (!deviceNames.contains(itm.key())) + { + qDebug("AudioDeviceManager::outputInfosCleanup: removing key: %s", qPrintable(itm.key())); + m_audioOutputInfos.erase(itm++); + } + else + { + ++itm; + } + } +} + +void AudioDeviceManager::debugAudioInputInfos() const +{ + QMap::const_iterator it = m_audioInputInfos.begin(); + + for (; it != m_audioInputInfos.end(); ++it) + { + qDebug() << "AudioDeviceManager::debugAudioInputInfos:" + << " name: " << it.key() + << " sampleRate: " << it.value().sampleRate + << " volume: " << it.value().volume; + } +} + +void AudioDeviceManager::debugAudioOutputInfos() const +{ + QMap::const_iterator it = m_audioOutputInfos.begin(); + + for (; it != m_audioOutputInfos.end(); ++it) + { + qDebug() << "AudioDeviceManager::debugAudioOutputInfos:" + << " name: " << it.key() + << " sampleRate: " << it.value().sampleRate + << " udpAddress: " << it.value().udpAddress + << " udpPort: " << it.value().udpPort + << " copyToUDP: " << it.value().copyToUDP + << " udpUseRTP: " << it.value().udpUseRTP + << " udpChannelMode: " << (int) it.value().udpChannelMode; + } +} diff --git a/sdrbase/audio/audiodevicemanager.h b/sdrbase/audio/audiodevicemanager.h new file mode 100644 index 000000000..4ee715bc6 --- /dev/null +++ b/sdrbase/audio/audiodevicemanager.h @@ -0,0 +1,158 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 F4EXB // +// written by Edouard Griffiths // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_AUDIODEVICEMANGER_H +#define INCLUDE_AUDIODEVICEMANGER_H + +#include +#include +#include +#include + +#include "audio/audioinput.h" +#include "audio/audiooutput.h" +#include "export.h" + +class QDataStream; +class AudioFifo; +class MessageQueue; + +class SDRBASE_API AudioDeviceManager { +public: + class InputDeviceInfo + { + public: + InputDeviceInfo() : + sampleRate(m_defaultAudioSampleRate), + volume(m_defaultAudioInputVolume) + {} + void resetToDefaults() { + sampleRate = m_defaultAudioSampleRate; + volume = m_defaultAudioInputVolume; + } + unsigned int sampleRate; + float volume; + friend QDataStream& operator<<(QDataStream& ds, const InputDeviceInfo& info); + friend QDataStream& operator>>(QDataStream& ds, InputDeviceInfo& info); + }; + + class OutputDeviceInfo + { + public: + OutputDeviceInfo() : + sampleRate(m_defaultAudioSampleRate), + udpAddress(m_defaultUDPAddress), + udpPort(m_defaultUDPPort), + copyToUDP(false), + udpUseRTP(false), + udpChannelMode(AudioOutput::UDPChannelLeft) + {} + void resetToDefaults() { + sampleRate = m_defaultAudioSampleRate; + udpAddress = m_defaultUDPAddress; + udpPort = m_defaultUDPPort; + copyToUDP = false; + udpUseRTP = false; + udpChannelMode = AudioOutput::UDPChannelLeft; + } + unsigned int sampleRate; + QString udpAddress; + quint16 udpPort; + bool copyToUDP; + bool udpUseRTP; + AudioOutput::UDPChannelMode udpChannelMode; + friend QDataStream& operator<<(QDataStream& ds, const OutputDeviceInfo& info); + friend QDataStream& operator>>(QDataStream& ds, OutputDeviceInfo& info); + }; + + AudioDeviceManager(); + ~AudioDeviceManager(); + + const QList& getInputDevices() const { return m_inputDevicesInfo; } + const QList& getOutputDevices() const { return m_outputDevicesInfo; } + + bool getOutputDeviceName(int outputDeviceIndex, QString &deviceName) const; + bool getInputDeviceName(int inputDeviceIndex, QString &deviceName) const; + int getOutputDeviceIndex(const QString &deviceName) const; + int getInputDeviceIndex(const QString &deviceName) const; + + void addAudioSink(AudioFifo* audioFifo, MessageQueue *sampleSinkMessageQueue, int outputDeviceIndex = -1); //!< Add the audio sink + void removeAudioSink(AudioFifo* audioFifo); //!< Remove the audio sink + + void addAudioSource(AudioFifo* audioFifo, MessageQueue *sampleSourceMessageQueue, int inputDeviceIndex = -1); //!< Add an audio source + void removeAudioSource(AudioFifo* audioFifo); //!< Remove an audio source + + bool getInputDeviceInfo(const QString& deviceName, InputDeviceInfo& deviceInfo) const; + bool getOutputDeviceInfo(const QString& deviceName, OutputDeviceInfo& deviceInfo) const; + int getInputSampleRate(int inputDeviceIndex = -1); + int getOutputSampleRate(int outputDeviceIndex = -1); + void setInputDeviceInfo(int inputDeviceIndex, const InputDeviceInfo& deviceInfo); + void setOutputDeviceInfo(int outputDeviceIndex, const OutputDeviceInfo& deviceInfo); + void unsetInputDeviceInfo(int inputDeviceIndex); + void unsetOutputDeviceInfo(int outputDeviceIndex); + void inputInfosCleanup(); //!< Remove input info from map for input devices not present + void outputInfosCleanup(); //!< Remove output info from map for output devices not present + + static const unsigned int m_defaultAudioSampleRate = 48000; + static const float m_defaultAudioInputVolume; + static const QString m_defaultUDPAddress; + static const quint16 m_defaultUDPPort = 9998; + static const QString m_defaultDeviceName; + +private: + QList m_inputDevicesInfo; + QList m_outputDevicesInfo; + + QMap m_audioSinkFifos; //< audio sink FIFO to audio output device index-1 map + QMap m_audioFifoToSinkMessageQueues; //!< audio sink FIFO to attached sink message queue + QMap > m_outputDeviceSinkMessageQueues; //!< sink message queues attached to device + QMap m_audioOutputs; //!< audio device index to audio output map (index -1 is default device) + QMap m_audioOutputInfos; //!< audio device name to audio output info + + QMap m_audioSourceFifos; //< audio source FIFO to audio input device index-1 map + QMap m_audioFifoToSourceMessageQueues; //!< audio source FIFO to attached source message queue + QMap > m_inputDeviceSourceMessageQueues; //!< sink message queues attached to device + QMap m_audioInputs; //!< audio device index to audio input map (index -1 is default device) + QMap m_audioInputInfos; //!< audio device name to audio input device info + + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + + void startAudioOutput(int outputDeviceIndex); + void stopAudioOutput(int outputDeviceIndex); + void startAudioInput(int inputDeviceIndex); + void stopAudioInput(int inputDeviceIndex); + + void serializeInputMap(QByteArray& data) const; + void deserializeInputMap(QByteArray& data); + void debugAudioInputInfos() const; + + void serializeOutputMap(QByteArray& data) const; + void deserializeOutputMap(QByteArray& data); + void debugAudioOutputInfos() const; + + friend class MainSettings; +}; + +QDataStream& operator<<(QDataStream& ds, const AudioDeviceManager::InputDeviceInfo& info); +QDataStream& operator>>(QDataStream& ds, AudioDeviceManager::InputDeviceInfo& info); + +QDataStream& operator<<(QDataStream& ds, const AudioDeviceManager::OutputDeviceInfo& info); +QDataStream& operator>>(QDataStream& ds, AudioDeviceManager::OutputDeviceInfo& info); + +#endif // INCLUDE_AUDIODEVICEMANGER_H diff --git a/sdrbase/audio/audiofifo.cpp b/sdrbase/audio/audiofifo.cpp index 92734fb84..26ca348dc 100644 --- a/sdrbase/audio/audiofifo.cpp +++ b/sdrbase/audio/audiofifo.cpp @@ -19,14 +19,13 @@ #include #include "dsp/dsptypes.h" #include "audio/audiofifo.h" +#include "audio/audionetsink.h" #define MIN(x, y) ((x) < (y) ? (x) : (y)) AudioFifo::AudioFifo() : m_fifo(0), - m_sampleSize(sizeof(AudioSample)), - m_udpSink(0), - m_copyToUDP(false) + m_sampleSize(sizeof(AudioSample)) { m_size = 0; m_fill = 0; @@ -36,9 +35,7 @@ AudioFifo::AudioFifo() : AudioFifo::AudioFifo(uint32_t numSamples) : m_fifo(0), - m_sampleSize(sizeof(AudioSample)), - m_udpSink(0), - m_copyToUDP(false) + m_sampleSize(sizeof(AudioSample)) { QMutexLocker mutexLocker(&m_mutex); @@ -55,9 +52,6 @@ AudioFifo::~AudioFifo() m_fifo = 0; } - m_writeWaitCondition.wakeOne(); - m_readWaitCondition.wakeOne(); - m_size = 0; } @@ -68,73 +62,27 @@ bool AudioFifo::setSize(uint32_t numSamples) return create(numSamples); } -uint AudioFifo::write(const quint8* data, uint32_t numSamples, int timeout_ms) +uint AudioFifo::write(const quint8* data, uint32_t numSamples) { - QTime time; uint32_t total; uint32_t remaining; uint32_t copyLen; - if (m_copyToUDP && m_udpSink) - { - m_udpSink->write((AudioSample *) data, numSamples); - } - - if(m_fifo == 0) - { + if (m_fifo == 0) { return 0; } - time.start(); m_mutex.lock(); - if(timeout_ms == 0) - { - total = MIN(numSamples, m_size - m_fill); - } - else - { - total = numSamples; - } - + total = MIN(numSamples, m_size - m_fill); remaining = total; - while (remaining > 0) + while (remaining != 0) { if (isFull()) { - if (time.elapsed() < timeout_ms) - { - m_writeWaitLock.lock(); - m_mutex.unlock(); - int ms = timeout_ms - time.elapsed(); - - if(ms < 1) - { - ms = 1; - } - - bool ok = m_writeWaitCondition.wait(&m_writeWaitLock, ms); - m_writeWaitLock.unlock(); - - if(!ok) - { - return total - remaining; - } - - m_mutex.lock(); - - if(m_fifo == 0) - { - m_mutex.unlock(); - return 0; - } - } - else - { - m_mutex.unlock(); - return total - remaining; - } + m_mutex.unlock(); + return total - remaining; // written so far } copyLen = MIN(remaining, m_size - m_fill); @@ -145,86 +93,44 @@ uint AudioFifo::write(const quint8* data, uint32_t numSamples, int timeout_ms) m_fill += copyLen; data += copyLen * m_sampleSize; remaining -= copyLen; - m_readWaitCondition.wakeOne(); } m_mutex.unlock(); return total; } -uint AudioFifo::read(quint8* data, uint32_t numSamples, int timeout_ms) +uint AudioFifo::read(quint8* data, uint32_t numSamples) { - QTime time; uint32_t total; uint32_t remaining; uint32_t copyLen; - if(m_fifo == 0) - { + if (m_fifo == 0) { return 0; } - time.start(); m_mutex.lock(); - if(timeout_ms == 0) - { - total = MIN(numSamples, m_fill); - } - else - { - total = numSamples; - } - + total = MIN(numSamples, m_fill); remaining = total; - while(remaining > 0) + while (remaining != 0) { - if(isEmpty()) + if (isEmpty()) { - if(time.elapsed() < timeout_ms) - { - m_readWaitLock.lock(); - m_mutex.unlock(); - int ms = timeout_ms - time.elapsed(); - - if(ms < 1) - { - ms = 1; - } - - bool ok = m_readWaitCondition.wait(&m_readWaitLock, ms); - m_readWaitLock.unlock(); - - if(!ok) - { - return total - remaining; - } - - m_mutex.lock(); - - if(m_fifo == 0) - { - m_mutex.unlock(); - return 0; - } - } - else - { - m_mutex.unlock(); - return total - remaining; - } + m_mutex.unlock(); + return total - remaining; // read so far } copyLen = MIN(remaining, m_fill); copyLen = MIN(copyLen, m_size - m_head); memcpy(data, m_fifo + (m_head * m_sampleSize), copyLen * m_sampleSize); + m_head += copyLen; m_head %= m_size; m_fill -= copyLen; data += copyLen * m_sampleSize; remaining -= copyLen; - m_writeWaitCondition.wakeOne(); } m_mutex.unlock(); @@ -243,7 +149,6 @@ uint AudioFifo::drain(uint32_t numSamples) m_head = (m_head + numSamples) % m_size; m_fill -= numSamples; - m_writeWaitCondition.wakeOne(); return numSamples; } @@ -254,8 +159,6 @@ void AudioFifo::clear() m_fill = 0; m_head = 0; m_tail = 0; - - m_writeWaitCondition.wakeOne(); } bool AudioFifo::create(uint32_t numSamples) @@ -266,7 +169,6 @@ bool AudioFifo::create(uint32_t numSamples) m_fifo = 0; } - m_size = 0; m_fill = 0; m_head = 0; m_tail = 0; @@ -274,5 +176,5 @@ bool AudioFifo::create(uint32_t numSamples) m_fifo = new qint8[numSamples * m_sampleSize]; m_size = numSamples; - return m_fifo != 0; + return true; } diff --git a/sdrbase/audio/audiofifo.h b/sdrbase/audio/audiofifo.h index 71859ef53..bab5d0177 100644 --- a/sdrbase/audio/audiofifo.h +++ b/sdrbase/audio/audiofifo.h @@ -23,10 +23,9 @@ #include #include "dsp/dsptypes.h" -#include "util/export.h" -#include "util/udpsink.h" +#include "export.h" -class SDRANGEL_API AudioFifo : public QObject { +class SDRBASE_API AudioFifo : public QObject { Q_OBJECT public: AudioFifo(); @@ -35,8 +34,8 @@ public: bool setSize(uint32_t numSamples); - uint32_t write(const quint8* data, uint32_t numSamples, int timeout_ms = INT_MAX); - uint32_t read(quint8* data, uint32_t numSamples, int timeout_ms = INT_MAX); + uint32_t write(const quint8* data, uint32_t numSamples); + uint32_t read(quint8* data, uint32_t numSamples); uint32_t drain(uint32_t numSamples); void clear(); @@ -47,9 +46,6 @@ public: inline bool isFull() const { return m_fill == m_size; } inline uint32_t size() const { return m_size; } - void setUDPSink(UDPSink *udpSink) { m_udpSink = udpSink; } - void setCopyToUDP(bool copyToUDP) { m_copyToUDP = copyToUDP; } - private: QMutex m_mutex; @@ -62,14 +58,6 @@ private: uint32_t m_head; uint32_t m_tail; - QMutex m_writeWaitLock; - QMutex m_readWaitLock; - QWaitCondition m_writeWaitCondition; - QWaitCondition m_readWaitCondition; - - UDPSink *m_udpSink; - bool m_copyToUDP; - bool create(uint32_t numSamples); }; diff --git a/sdrbase/audio/audioinput.cpp b/sdrbase/audio/audioinput.cpp index bafa6718d..316100dbb 100644 --- a/sdrbase/audio/audioinput.cpp +++ b/sdrbase/audio/audioinput.cpp @@ -37,7 +37,7 @@ AudioInput::~AudioInput() QMutexLocker mutexLocker(&m_mutex); - for (AudioFifos::iterator it = m_audioFifos.begin(); it != m_audioFifos.end(); ++it) + for (std::list::iterator it = m_audioFifos.begin(); it != m_audioFifos.end(); ++it) { delete *it; } @@ -94,7 +94,7 @@ bool AudioInput::start(int device, int rate) if (m_audioFormat.sampleSize() != 16) { - qWarning("AudioInput::start: Audio device ( %s ) failed", qPrintable(devInfo.defaultInputDevice().deviceName())); + qWarning("AudioInput::start: Audio device '%s' failed", qPrintable(devInfo.defaultInputDevice().deviceName())); return false; } @@ -116,79 +116,6 @@ bool AudioInput::start(int device, int rate) return true; } -bool AudioInput::startImmediate(int device, int rate) -{ - - if (QIODevice::isOpen()) - { - qInfo("AudioInput::startImmediate: already started"); - return true; - } - - QAudioDeviceInfo devInfo; - QMutexLocker mutexLocker(&m_mutex); - - if (device < 0) - { - devInfo = QAudioDeviceInfo::defaultInputDevice(); - qWarning("AudioInput::startImmediate: using default device %s", qPrintable(devInfo.defaultInputDevice().deviceName())); - } - else - { - QList devicesInfo = QAudioDeviceInfo::availableDevices(QAudio::AudioInput); - - if (device < devicesInfo.size()) - { - devInfo = devicesInfo[device]; - qWarning("AudioInput::startImmediate: using audio device #%d: %s", device, qPrintable(devInfo.defaultInputDevice().deviceName())); - } - else - { - devInfo = QAudioDeviceInfo::defaultInputDevice(); - qWarning("AudioInput::startImmediate: audio device #%d does not exist. Using default device %s", device, qPrintable(devInfo.defaultInputDevice().deviceName())); - } - } - - //QAudioDeviceInfo devInfo(QAudioDeviceInfo::defaultOutputDevice()); - - m_audioFormat.setSampleRate(rate); - m_audioFormat.setChannelCount(2); - m_audioFormat.setSampleSize(16); - m_audioFormat.setCodec("audio/pcm"); - m_audioFormat.setByteOrder(QAudioFormat::LittleEndian); - m_audioFormat.setSampleType(QAudioFormat::SignedInt); - - if (!devInfo.isFormatSupported(m_audioFormat)) - { - m_audioFormat = devInfo.nearestFormat(m_audioFormat); - qWarning("AudioInput::startImmediate: %d Hz S16_LE audio format not supported. New rate: %d", rate, m_audioFormat.sampleRate()); - } - else - { - qInfo("AudioInput::startImmediate: audio format OK"); - } - - if (m_audioFormat.sampleSize() != 16) - { - qWarning("AudioInput::startImmediate: Audio device ( %s ) failed", qPrintable(devInfo.defaultInputDevice().deviceName())); - return false; - } - - m_audioInput = new QAudioInput(devInfo, m_audioFormat); - m_audioInput->setVolume(m_volume); - - QIODevice::open(QIODevice::ReadWrite); - - m_audioInput->start(this); - - if (m_audioInput->state() != QAudio::ActiveState) - { - qWarning("AudioInput::startImmediate: cannot start"); - } - - return true; -} - void AudioInput::stop() { qDebug("AudioInput::stop"); @@ -210,21 +137,6 @@ void AudioInput::stop() } } -void AudioInput::stopImmediate() -{ - if (!QIODevice::isOpen()) - { - qInfo("AudioInput::stopImmediate: already stopped"); - } - else - { - qDebug("AudioInput::stopImmediate"); - QMutexLocker mutexLocker(&m_mutex); - QIODevice::close(); - delete m_audioInput; - } -} - void AudioInput::addFifo(AudioFifo* audioFifo) { QMutexLocker mutexLocker(&m_mutex); @@ -267,9 +179,9 @@ qint64 AudioInput::writeData(const char *data, qint64 len) return 0; } - for (AudioFifos::iterator it = m_audioFifos.begin(); it != m_audioFifos.end(); ++it) + for (std::list::iterator it = m_audioFifos.begin(); it != m_audioFifos.end(); ++it) { - (*it)->write(reinterpret_cast(data), len/4, 10); + (*it)->write(reinterpret_cast(data), len/4); } return len; diff --git a/sdrbase/audio/audioinput.h b/sdrbase/audio/audioinput.h index 645143a0f..16ebb6555 100644 --- a/sdrbase/audio/audioinput.h +++ b/sdrbase/audio/audioinput.h @@ -22,14 +22,14 @@ #include #include #include -#include "util/export.h" +#include "export.h" class QAudioInput; class AudioFifo; class AudioOutputPipe; -class SDRANGEL_API AudioInput : public QIODevice { +class SDRBASE_API AudioInput : public QIODevice { public: AudioInput(); virtual ~AudioInput(); @@ -37,11 +37,9 @@ public: bool start(int device, int rate); void stop(); - bool startImmediate(int device, int rate); - void stopImmediate(); - void addFifo(AudioFifo* audioFifo); void removeFifo(AudioFifo* audioFifo); + int getNbFifos() const { return m_audioFifos.size(); } uint getRate() const { return m_audioFormat.sampleRate(); } void setOnExit(bool onExit) { m_onExit = onExit; } @@ -54,8 +52,7 @@ private: bool m_onExit; float m_volume; - typedef std::list AudioFifos; - AudioFifos m_audioFifos; + std::list m_audioFifos; std::vector m_mixBuffer; QAudioFormat m_audioFormat; diff --git a/sdrbase/audio/audionetsink.cpp b/sdrbase/audio/audionetsink.cpp index 50e4a2437..70c7dac0f 100644 --- a/sdrbase/audio/audionetsink.cpp +++ b/sdrbase/audio/audionetsink.cpp @@ -16,53 +16,46 @@ /////////////////////////////////////////////////////////////////////////////////// #include "audionetsink.h" -#include "util/udpsink.h" -#ifdef HAS_JRTPLIB #include "util/rtpsink.h" -#endif + +#include +#include const int AudioNetSink::m_udpBlockSize = 512; -AudioNetSink::AudioNetSink(QObject *parent, bool stereo) : +AudioNetSink::AudioNetSink(QObject *parent) : m_type(SinkUDP), - m_udpBufferAudioMono(0), - m_udpBufferAudioStereo(0), - m_rtpBufferAudio(0) + m_rtpBufferAudio(0), + m_bufferIndex(0), + m_port(9998) { - if (stereo) { - m_udpBufferAudioStereo = new UDPSink(parent, m_udpBlockSize); - } else { - m_udpBufferAudioMono = new UDPSink(parent, m_udpBlockSize); - } + memset(m_data, 0, 65536); + m_udpSocket = new QUdpSocket(parent); +} -#ifdef HAS_JRTPLIB - m_rtpBufferAudio = new RTPSink("127.0.0.1", 9999, stereo ? RTPSink::PayloadL16Stereo : RTPSink::PayloadL16Mono); -#endif +AudioNetSink::AudioNetSink(QObject *parent, int sampleRate, bool stereo) : + m_type(SinkUDP), + m_rtpBufferAudio(0), + m_bufferIndex(0), + m_port(9998) +{ + memset(m_data, 0, 65536); + m_udpSocket = new QUdpSocket(parent); + m_rtpBufferAudio = new RTPSink(m_udpSocket, sampleRate, stereo); } AudioNetSink::~AudioNetSink() { - if (m_udpBufferAudioMono) { - delete m_udpBufferAudioMono; - } - - if (m_udpBufferAudioStereo) { - delete m_udpBufferAudioStereo; - } -#ifdef HAS_JRTPLIB if (m_rtpBufferAudio) { delete m_rtpBufferAudio; } -#endif + + m_udpSocket->deleteLater(); // this thread is not the owner thread (was moved) } bool AudioNetSink::isRTPCapable() const { -#ifdef HAS_JRTPLIB - return m_rtpBufferAudio->isValid(); -#else - return false; -#endif + return m_rtpBufferAudio && m_rtpBufferAudio->isValid(); } bool AudioNetSink::selectType(SinkType type) @@ -70,94 +63,125 @@ bool AudioNetSink::selectType(SinkType type) if (type == SinkUDP) { m_type = SinkUDP; - return true; } - else if (type == SinkRTP) + else // this is SinkRTP { -#ifdef HAS_JRTPLIB m_type = SinkRTP; - return true; -#else - m_type = SinkUDP; - return false; -#endif - } - else - { - return false; } + + return true; } void AudioNetSink::setDestination(const QString& address, uint16_t port) { - if (m_udpBufferAudioMono) { - m_udpBufferAudioMono->setDestination(address, port); - } - if (m_udpBufferAudioStereo) { - m_udpBufferAudioStereo->setDestination(address, port); - } + m_address.setAddress(const_cast(address)); + m_port = port; -#ifdef HAS_JRTPLIB if (m_rtpBufferAudio) { m_rtpBufferAudio->setDestination(address, port); } -#endif } -#ifdef HAS_JRTPLIB void AudioNetSink::addDestination(const QString& address, uint16_t port) { if (m_rtpBufferAudio) { m_rtpBufferAudio->addDestination(address, port); } } -#else -void AudioNetSink::addDestination(const QString& address __attribute__((unused)), uint16_t port __attribute__((unused))) -{ -} -#endif -#ifdef HAS_JRTPLIB void AudioNetSink::deleteDestination(const QString& address, uint16_t port) { if (m_rtpBufferAudio) { m_rtpBufferAudio->deleteDestination(address, port); } } -#else -void AudioNetSink::deleteDestination(const QString& address __attribute__((unused)), uint16_t port __attribute__((unused))) + +void AudioNetSink::setParameters(bool stereo, int sampleRate) { + if (m_rtpBufferAudio) { + m_rtpBufferAudio->setPayloadInformation(stereo ? RTPSink::PayloadL16Stereo : RTPSink::PayloadL16Mono, sampleRate); + } } -#endif void AudioNetSink::write(qint16 sample) { - if (m_udpBufferAudioMono == 0) { - return; + if (m_type == SinkUDP) + { + if (m_bufferIndex >= m_udpBlockSize) + { + m_udpSocket->writeDatagram((const char*)m_data, (qint64 ) m_udpBlockSize, m_address, m_port); + m_bufferIndex = 0; + } + else + { + qint16 *p = (qint16*) &m_data[m_bufferIndex]; + *p = sample; + m_bufferIndex += sizeof(qint16); + } } - - if (m_type == SinkUDP) { - m_udpBufferAudioMono->write(sample); - } else if (m_type == SinkRTP) { -#ifdef HAS_JRTPLIB + else if (m_type == SinkRTP) + { m_rtpBufferAudio->write((uint8_t *) &sample); -#endif } } -void AudioNetSink::write(const AudioSample& sample) +void AudioNetSink::write(qint16 lSample, qint16 rSample) { - if (m_udpBufferAudioStereo == 0) { - return; + if (m_type == SinkUDP) + { + if (m_bufferIndex >= m_udpBlockSize) + { + m_udpSocket->writeDatagram((const char*)m_data, (qint64 ) m_udpBlockSize, m_address, m_port); + m_bufferIndex = 0; + } + else + { + qint16 *p = (qint16*) &m_data[m_bufferIndex]; + *p = lSample; + m_bufferIndex += sizeof(qint16); + p = (qint16*) &m_data[m_bufferIndex]; + *p = rSample; + m_bufferIndex += sizeof(qint16); + } } - - if (m_type == SinkUDP) { - m_udpBufferAudioStereo->write(sample); - } else if (m_type == SinkRTP) { -#ifdef HAS_JRTPLIB - m_rtpBufferAudio->write((uint8_t *) &sample); -#endif + else if (m_type == SinkRTP) + { + m_rtpBufferAudio->write((uint8_t *) &lSample, (uint8_t *) &rSample); } } +void AudioNetSink::write(AudioSample* samples, uint32_t numSamples) +{ + if (m_type == SinkUDP) + { + int samplesIndex = 0; + + if (m_bufferIndex + numSamples*sizeof(AudioSample) >= m_udpBlockSize) // fill remainder of buffer and send it + { + memcpy(&m_data[m_bufferIndex], &samples[samplesIndex], m_udpBlockSize - m_bufferIndex); // fill remainder of buffer + m_udpSocket->writeDatagram((const char*)m_data, (qint64 ) m_udpBlockSize, m_address, m_port); + m_bufferIndex = 0; + samplesIndex += (m_udpBlockSize - m_bufferIndex) / sizeof(AudioSample); + numSamples -= (m_udpBlockSize - m_bufferIndex) / sizeof(AudioSample); + } + + while (numSamples > m_udpBlockSize/sizeof(AudioSample)) // send directly from input without buffering + { + m_udpSocket->writeDatagram((const char*)&samples[samplesIndex], (qint64 ) m_udpBlockSize, m_address, m_port); + samplesIndex += m_udpBlockSize/sizeof(AudioSample); + numSamples -= m_udpBlockSize/sizeof(AudioSample); + } + + memcpy(&m_data[m_bufferIndex], &samples[samplesIndex], numSamples*sizeof(AudioSample)); + } + else if (m_type == SinkRTP) + { + m_rtpBufferAudio->write((uint8_t *) samples, numSamples*2); // 2 x 16 bit sample + } +} + +void AudioNetSink::moveToThread(QThread *thread) +{ + m_udpSocket->moveToThread(thread); +} diff --git a/sdrbase/audio/audionetsink.h b/sdrbase/audio/audionetsink.h index 08dc27618..38c237e15 100644 --- a/sdrbase/audio/audionetsink.h +++ b/sdrbase/audio/audionetsink.h @@ -18,14 +18,18 @@ #ifndef SDRBASE_AUDIO_AUDIONETSINK_H_ #define SDRBASE_AUDIO_AUDIONETSINK_H_ -#include #include "dsp/dsptypes.h" -#include "util/export.h" +#include "export.h" -template class UDPSink; +#include +#include +#include + +class QUdpSocket; class RTPSink; +class QThread; -class SDRANGEL_API AudioNetSink { +class SDRBASE_API AudioNetSink { public: typedef enum { @@ -33,26 +37,34 @@ public: SinkRTP } SinkType; - AudioNetSink(QObject *parent, bool stereo = false); + AudioNetSink(QObject *parent); //!< without RTP + AudioNetSink(QObject *parent, int sampleRate, bool stereo); //!< with RTP ~AudioNetSink(); void setDestination(const QString& address, uint16_t port); void addDestination(const QString& address, uint16_t port); void deleteDestination(const QString& address, uint16_t port); + void setParameters(bool stereo, int sampleRate); void write(qint16 sample); - void write(const AudioSample& sample); + void write(qint16 lSample, qint16 rSample); + void write(AudioSample* samples, uint32_t numSamples); bool isRTPCapable() const; bool selectType(SinkType type); + void moveToThread(QThread *thread); + static const int m_udpBlockSize; protected: SinkType m_type; - UDPSink *m_udpBufferAudioMono; - UDPSink *m_udpBufferAudioStereo; + QUdpSocket *m_udpSocket; RTPSink *m_rtpBufferAudio; + char m_data[65536]; + unsigned int m_bufferIndex; + QHostAddress m_address; + unsigned int m_port; }; diff --git a/sdrbase/audio/audiooutput.cpp b/sdrbase/audio/audiooutput.cpp index b3256c695..bd1d7def9 100644 --- a/sdrbase/audio/audiooutput.cpp +++ b/sdrbase/audio/audiooutput.cpp @@ -19,12 +19,16 @@ #include #include #include -#include "audio/audiooutput.h" -#include "audio/audiofifo.h" +#include "audiooutput.h" +#include "audiofifo.h" +#include "audionetsink.h" AudioOutput::AudioOutput() : m_mutex(QMutex::Recursive), m_audioOutput(0), + m_audioNetSink(0), + m_copyAudioToUdp(false), + m_udpChannelMode(UDPChannelLeft), m_audioUsageCount(0), m_onExit(false), m_audioFifos() @@ -33,30 +37,30 @@ AudioOutput::AudioOutput() : AudioOutput::~AudioOutput() { - stop(); - - QMutexLocker mutexLocker(&m_mutex); - - for (AudioFifos::iterator it = m_audioFifos.begin(); it != m_audioFifos.end(); ++it) - { - delete *it; - } - - m_audioFifos.clear(); +// stop(); +// +// QMutexLocker mutexLocker(&m_mutex); +// +// for (std::list::iterator it = m_audioFifos.begin(); it != m_audioFifos.end(); ++it) +// { +// delete *it; +// } +// +// m_audioFifos.clear(); } bool AudioOutput::start(int device, int rate) { - if (m_audioUsageCount == 0) - { +// if (m_audioUsageCount == 0) +// { QMutexLocker mutexLocker(&m_mutex); QAudioDeviceInfo devInfo; if (device < 0) { devInfo = QAudioDeviceInfo::defaultOutputDevice(); - qWarning("AudioOutput::start: using default device %s", qPrintable(devInfo.defaultOutputDevice().deviceName())); + qWarning("AudioOutput::start: using system default device %s", qPrintable(devInfo.defaultOutputDevice().deviceName())); } else { @@ -65,12 +69,12 @@ bool AudioOutput::start(int device, int rate) if (device < devicesInfo.size()) { devInfo = devicesInfo[device]; - qWarning("AudioOutput::start: using audio device #%d: %s", device, qPrintable(devInfo.defaultOutputDevice().deviceName())); + qWarning("AudioOutput::start: using audio device #%d: %s", device, qPrintable(devInfo.deviceName())); } else { devInfo = QAudioDeviceInfo::defaultOutputDevice(); - qWarning("AudioOutput::start: audio device #%d does not exist. Using default device %s", device, qPrintable(devInfo.defaultOutputDevice().deviceName())); + qWarning("AudioOutput::start: audio device #%d does not exist. Using system default device %s", device, qPrintable(devInfo.defaultOutputDevice().deviceName())); } } @@ -86,7 +90,14 @@ bool AudioOutput::start(int device, int rate) if (!devInfo.isFormatSupported(m_audioFormat)) { m_audioFormat = devInfo.nearestFormat(m_audioFormat); - qWarning("AudioOutput::start: %d Hz S16_LE audio format not supported. New rate: %d", rate, m_audioFormat.sampleRate()); + std::ostringstream os; + os << " sampleRate: " << m_audioFormat.sampleRate() + << " channelCount: " << m_audioFormat.channelCount() + << " sampleSize: " << m_audioFormat.sampleSize() + << " codec: " << m_audioFormat.codec().toStdString() + << " byteOrder: " << (m_audioFormat.byteOrder() == QAudioFormat::BigEndian ? "BE" : "LE") + << " sampleType: " << (int) m_audioFormat.sampleType(); + qWarning("AudioOutput::start: format %d Hz 2xS16LE audio/pcm not supported. Using: %s", rate, os.str().c_str()); } else { @@ -95,11 +106,12 @@ bool AudioOutput::start(int device, int rate) if (m_audioFormat.sampleSize() != 16) { - qWarning("AudioOutput::start: Audio device ( %s ) failed", qPrintable(devInfo.defaultOutputDevice().deviceName())); + qWarning("AudioOutput::start: Audio device '%s' failed", qPrintable(devInfo.defaultOutputDevice().deviceName())); return false; } m_audioOutput = new QAudioOutput(devInfo, m_audioFormat); + m_audioNetSink = new AudioNetSink(0, m_audioFormat.sampleRate(), false); QIODevice::open(QIODevice::ReadOnly); @@ -109,117 +121,38 @@ bool AudioOutput::start(int device, int rate) { qWarning("AudioOutput::start: cannot start"); } - } - - m_audioUsageCount++; +// } +// +// m_audioUsageCount++; return true; } -bool AudioOutput::startImmediate(int device, int rate) -{ - if (QIODevice::isOpen()) - { - qInfo("AudioOutput::startImmediate: already open"); - return true; - } - - QMutexLocker mutexLocker(&m_mutex); - QAudioDeviceInfo devInfo; - - if (device < 0) - { - devInfo = QAudioDeviceInfo::defaultOutputDevice(); - qWarning("AudioOutput::startImmediate: using default device %s", qPrintable(devInfo.defaultOutputDevice().deviceName())); - } - else - { - QList devicesInfo = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput); - - if (device < devicesInfo.size()) - { - devInfo = devicesInfo[device]; - qWarning("AudioOutput::startImmediate: using audio device #%d: %s", device, qPrintable(devInfo.defaultOutputDevice().deviceName())); - } - else - { - devInfo = QAudioDeviceInfo::defaultOutputDevice(); - qWarning("AudioOutput::startImmediate: audio device #%d does not exist. Using default device %s", device, qPrintable(devInfo.defaultOutputDevice().deviceName())); - } - } - - //QAudioDeviceInfo devInfo(QAudioDeviceInfo::defaultOutputDevice()); - - m_audioFormat.setSampleRate(rate); - m_audioFormat.setChannelCount(2); - m_audioFormat.setSampleSize(16); - m_audioFormat.setCodec("audio/pcm"); - m_audioFormat.setByteOrder(QAudioFormat::LittleEndian); - m_audioFormat.setSampleType(QAudioFormat::SignedInt); - - if (!devInfo.isFormatSupported(m_audioFormat)) - { - m_audioFormat = devInfo.nearestFormat(m_audioFormat); - qWarning("AudioOutput::startImmediate: %d Hz S16_LE audio format not supported. New rate: %d", rate, m_audioFormat.sampleRate()); - } - else - { - qInfo("AudioOutput::startImmediate: audio format OK"); - } - - if (m_audioFormat.sampleSize() != 16) - { - qWarning("AudioOutput::startImmediate: Audio device ( %s ) failed", qPrintable(devInfo.defaultOutputDevice().deviceName())); - return false; - } - - m_audioOutput = new QAudioOutput(devInfo, m_audioFormat); - - QIODevice::open(QIODevice::ReadOnly); - - m_audioOutput->start(this); - - if (m_audioOutput->state() != QAudio::ActiveState) - { - qWarning("AudioOutput::startImmediate: cannot start"); - } - - return true; -} - void AudioOutput::stop() { qDebug("AudioOutput::stop"); - if (m_audioUsageCount > 0) - { - m_audioUsageCount--; + QMutexLocker mutexLocker(&m_mutex); + m_audioOutput->stop(); + QIODevice::close(); + delete m_audioNetSink; + m_audioNetSink = 0; + delete m_audioOutput; - if (m_audioUsageCount == 0) - { - QMutexLocker mutexLocker(&m_mutex); - QIODevice::close(); - - if (!m_onExit) { - delete m_audioOutput; - } - } - } -} - -void AudioOutput::stopImmediate() -{ - if (!QIODevice::isOpen()) - { - qInfo("AudioOutput::stopImmediate"); - } - else - { - qDebug("AudioOutput::stopImmediate"); - QMutexLocker mutexLocker(&m_mutex); - QIODevice::close(); - delete m_audioOutput; - } +// if (m_audioUsageCount > 0) +// { +// m_audioUsageCount--; +// +// if (m_audioUsageCount == 0) +// { +// QMutexLocker mutexLocker(&m_mutex); +// QIODevice::close(); +// +// if (!m_onExit) { +// delete m_audioOutput; +// } +// } +// } } void AudioOutput::addFifo(AudioFifo* audioFifo) @@ -243,6 +176,37 @@ bool AudioOutput::open(OpenMode mode) return false; }*/ +void AudioOutput::setUdpDestination(const QString& address, uint16_t port) +{ + if (m_audioNetSink) { + m_audioNetSink->setDestination(address, port); + } +} + +void AudioOutput::setUdpCopyToUDP(bool copyToUDP) +{ + m_copyAudioToUdp = copyToUDP; +} + +void AudioOutput::setUdpUseRTP(bool useRTP) +{ + if (m_audioNetSink) { + m_audioNetSink->selectType(useRTP ? AudioNetSink::SinkRTP : AudioNetSink::SinkUDP); + } +} + +void AudioOutput::setUdpChannelMode(UDPChannelMode udpChannelMode) +{ + m_udpChannelMode = udpChannelMode; +} + +void AudioOutput::setUdpChannelFormat(bool stereo, int sampleRate) +{ + if (m_audioNetSink) { + m_audioNetSink->setParameters(stereo, sampleRate); + } +} + qint64 AudioOutput::readData(char* data, qint64 maxLen) { //qDebug("AudioOutput::readData: %lld", maxLen); @@ -253,31 +217,31 @@ qint64 AudioOutput::readData(char* data, qint64 maxLen) // QMutexLocker mutexLocker(&m_mutex); //#endif - unsigned int framesPerBuffer = maxLen / 4; + unsigned int samplesPerBuffer = maxLen / 4; - if (framesPerBuffer == 0) + if (samplesPerBuffer == 0) { return 0; } - if (m_mixBuffer.size() < framesPerBuffer * 2) + if (m_mixBuffer.size() < samplesPerBuffer * 2) { - m_mixBuffer.resize(framesPerBuffer * 2); // allocate 2 qint32 per frame (stereo) + m_mixBuffer.resize(samplesPerBuffer * 2); // allocate 2 qint32 per sample (stereo) - if (m_mixBuffer.size() != framesPerBuffer * 2) + if (m_mixBuffer.size() != samplesPerBuffer * 2) { return 0; } } - memset(&m_mixBuffer[0], 0x00, 2 * framesPerBuffer * sizeof(m_mixBuffer[0])); // start with silence + memset(&m_mixBuffer[0], 0x00, 2 * samplesPerBuffer * sizeof(m_mixBuffer[0])); // start with silence // sum up a block from all fifos - for (AudioFifos::iterator it = m_audioFifos.begin(); it != m_audioFifos.end(); ++it) + for (std::list::iterator it = m_audioFifos.begin(); it != m_audioFifos.end(); ++it) { // use outputBuffer as temp - yes, one memcpy could be saved - uint samples = (*it)->read((quint8*) data, framesPerBuffer, 1); + unsigned int samples = (*it)->read((quint8*) data, samplesPerBuffer); const qint16* src = (const qint16*) data; std::vector::iterator dst = m_mixBuffer.begin(); @@ -286,7 +250,7 @@ qint64 AudioOutput::readData(char* data, qint64 maxLen) // qDebug("AudioOutput::readData: read %d samples vs %d requested", samples, framesPerBuffer); // } - for (uint i = 0; i < samples; i++) + for (unsigned int i = 0; i < samples; i++) { *dst += *src; ++src; @@ -300,44 +264,64 @@ qint64 AudioOutput::readData(char* data, qint64 maxLen) //std::vector::const_iterator src = m_mixBuffer.begin(); // Valgrind optim qint16* dst = (qint16*) data; - qint32 s; + qint32 sl, sr; - for (uint i = 0; i < framesPerBuffer; i++) + for (unsigned int i = 0; i < samplesPerBuffer; i++) { // left channel //s = *src++; // Valgrind optim - s = m_mixBuffer[2*i]; + sl = m_mixBuffer[2*i]; - if(s < -32768) + if(sl < -32768) { - s = -32768; + sl = -32768; } - else if (s > 32767) + else if (sl > 32767) { - s = 32767; + sl = 32767; } - *dst++ = s; + *dst++ = sl; // right channel //s = *src++; // Valgrind optim - s = m_mixBuffer[2*i + 1]; + sr = m_mixBuffer[2*i + 1]; - if(s < -32768) + if(sr < -32768) { - s = -32768; + sr = -32768; } - else if (s > 32767) + else if (sr > 32767) { - s = 32767; + sr = 32767; } - *dst++ = s; + *dst++ = sr; + + if ((m_copyAudioToUdp) && (m_audioNetSink)) + { + switch (m_udpChannelMode) + { + case UDPChannelStereo: + m_audioNetSink->write(sl, sr); + break; + case UDPChannelMixed: + m_audioNetSink->write((sl+sr)/2); + break; + case UDPChannelRight: + m_audioNetSink->write(sr); + break; + case UDPChannelLeft: + default: + m_audioNetSink->write(sl); + break; + } + } } - return framesPerBuffer * 4; + return samplesPerBuffer * 4; } qint64 AudioOutput::writeData(const char* data, qint64 len) diff --git a/sdrbase/audio/audiooutput.h b/sdrbase/audio/audiooutput.h index a6d5b6ced..34da30f3e 100644 --- a/sdrbase/audio/audiooutput.h +++ b/sdrbase/audio/audiooutput.h @@ -23,37 +23,53 @@ #include #include #include -#include "util/export.h" +#include +#include "export.h" class QAudioOutput; class AudioFifo; class AudioOutputPipe; +class AudioNetSink; -class SDRANGEL_API AudioOutput : QIODevice { +class SDRBASE_API AudioOutput : QIODevice { public: + enum UDPChannelMode + { + UDPChannelLeft, + UDPChannelRight, + UDPChannelMixed, + UDPChannelStereo + }; + AudioOutput(); virtual ~AudioOutput(); bool start(int device, int rate); void stop(); - bool startImmediate(int device, int rate); - void stopImmediate(); - void addFifo(AudioFifo* audioFifo); void removeFifo(AudioFifo* audioFifo); + int getNbFifos() const { return m_audioFifos.size(); } - uint getRate() const { return m_audioFormat.sampleRate(); } + unsigned int getRate() const { return m_audioFormat.sampleRate(); } void setOnExit(bool onExit) { m_onExit = onExit; } + void setUdpDestination(const QString& address, uint16_t port); + void setUdpCopyToUDP(bool copyToUDP); + void setUdpUseRTP(bool useRTP); + void setUdpChannelMode(UDPChannelMode udpChannelMode); + void setUdpChannelFormat(bool stereo, int sampleRate); + private: QMutex m_mutex; QAudioOutput* m_audioOutput; + AudioNetSink* m_audioNetSink; + bool m_copyAudioToUdp; + UDPChannelMode m_udpChannelMode; uint m_audioUsageCount; bool m_onExit; - typedef std::list AudioFifos; - AudioFifos m_audioFifos; + std::list m_audioFifos; std::vector m_mixBuffer; QAudioFormat m_audioFormat; diff --git a/sdrbase/channel/channelsinkapi.h b/sdrbase/channel/channelsinkapi.h index dba578bf9..ecfa13bf3 100644 --- a/sdrbase/channel/channelsinkapi.h +++ b/sdrbase/channel/channelsinkapi.h @@ -23,14 +23,15 @@ #include #include -#include "util/export.h" +#include "export.h" namespace SWGSDRangel { class SWGChannelSettings; + class SWGChannelReport; } -class SDRANGEL_API ChannelSinkAPI { +class SDRBASE_API ChannelSinkAPI { public: ChannelSinkAPI(const QString& name); virtual ~ChannelSinkAPI() {} @@ -45,6 +46,24 @@ public: virtual QByteArray serialize() const = 0; virtual bool deserialize(const QByteArray& data) = 0; +#ifdef _MSC_VER + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) + { errorMessage = "Not implemented"; return 501; } + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) + { errorMessage = "Not implemented"; return 501; } + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage) + { errorMessage = "Not implemented"; return 501; } +#else virtual int webapiSettingsGet( SWGSDRangel::SWGChannelSettings& response __attribute__((unused)), QString& errorMessage) @@ -57,6 +76,12 @@ public: QString& errorMessage) { errorMessage = "Not implemented"; return 501; } + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response __attribute__((unused)), + QString& errorMessage) + { errorMessage = "Not implemented"; return 501; } +#endif + int getIndexInDeviceSet() const { return m_indexInDeviceSet; } void setIndexInDeviceSet(int indexInDeviceSet) { m_indexInDeviceSet = indexInDeviceSet; } uint64_t getUID() const { return m_uid; } diff --git a/sdrbase/channel/channelsourceapi.h b/sdrbase/channel/channelsourceapi.h index 192ee239f..44466bd33 100644 --- a/sdrbase/channel/channelsourceapi.h +++ b/sdrbase/channel/channelsourceapi.h @@ -22,14 +22,15 @@ #include #include -#include "util/export.h" +#include "export.h" namespace SWGSDRangel { class SWGChannelSettings; + class SWGChannelReport; } -class SDRANGEL_API ChannelSourceAPI { +class SDRBASE_API ChannelSourceAPI { public: ChannelSourceAPI(const QString& name); virtual ~ChannelSourceAPI() {} @@ -37,8 +38,8 @@ public: virtual void getIdentifier(QString& id) = 0; virtual void getTitle(QString& title) = 0; - virtual void setName(const QString& name) = 0; - virtual QString getName() const = 0; + virtual void setName(const QString& name) { m_name = name; }; + virtual QString getName() const { return m_name; }; virtual qint64 getCenterFrequency() const = 0; virtual QByteArray serialize() const = 0; @@ -56,6 +57,11 @@ public: QString& errorMessage) { errorMessage = "Not implemented"; return 501; } + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response __attribute__((unused)), + QString& errorMessage) + { errorMessage = "Not implemented"; return 501; } + int getIndexInDeviceSet() const { return m_indexInDeviceSet; } void setIndexInDeviceSet(int indexInDeviceSet) { m_indexInDeviceSet = indexInDeviceSet; } uint64_t getUID() const { return m_uid; } diff --git a/sdrbase/channel/sdrdaemondatablock.h b/sdrbase/channel/sdrdaemondatablock.h new file mode 100644 index 000000000..1b877f8f1 --- /dev/null +++ b/sdrbase/channel/sdrdaemondatablock.h @@ -0,0 +1,166 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon sink channel (Rx) data block // +// // +// SDRdaemon is a detached SDR front end that handles the interface with a // +// physical device and sends or receives the I/Q samples stream to or from a // +// SDRangel instance via UDP. It is controlled via a Web REST API. // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRDAEMON_CHANNEL_SDRDAEMONDATABLOCK_H_ +#define SDRDAEMON_CHANNEL_SDRDAEMONDATABLOCK_H_ + +#include +#include +#include +#include +#include "dsp/dsptypes.h" + +#define UDPSINKFEC_UDPSIZE 512 +#define UDPSINKFEC_NBORIGINALBLOCKS 128 +//#define UDPSINKFEC_NBTXBLOCKS 8 + +#pragma pack(push, 1) +struct SDRDaemonMetaDataFEC +{ + uint32_t m_centerFrequency; //!< 4 center frequency in kHz + uint32_t m_sampleRate; //!< 8 sample rate in Hz + uint8_t m_sampleBytes; //!< 9 4 LSB: number of bytes per sample (2 or 4) + uint8_t m_sampleBits; //!< 10 number of effective bits per sample (deprecated) + uint8_t m_nbOriginalBlocks; //!< 11 number of blocks with original (protected) data + uint8_t m_nbFECBlocks; //!< 12 number of blocks carrying FEC + uint32_t m_tv_sec; //!< 16 seconds of timestamp at start time of super-frame processing + uint32_t m_tv_usec; //!< 20 microseconds of timestamp at start time of super-frame processing + uint32_t m_crc32; //!< 24 CRC32 of the above + + bool operator==(const SDRDaemonMetaDataFEC& rhs) + { + return (memcmp((const void *) this, (const void *) &rhs, 12) == 0); // Only the 12 first bytes are relevant + } + + void init() + { + m_centerFrequency = 0; + m_sampleRate = 0; + m_sampleBytes = 0; + m_sampleBits = 0; + m_nbOriginalBlocks = 0; + m_nbFECBlocks = 0; + m_tv_sec = 0; + m_tv_usec = 0; + m_crc32 = 0; + } +}; + +struct SDRDaemonHeader +{ + uint16_t m_frameIndex; + uint8_t m_blockIndex; + uint8_t m_sampleBytes; //!< number of bytes per sample (2 or 4) for this block + uint8_t m_sampleBits; //!< number of bits per sample + uint8_t m_filler; + uint16_t m_filler2; + + void init() + { + m_frameIndex = 0; + m_blockIndex = 0; + m_sampleBytes = 2; + m_sampleBits = 16; + m_filler = 0; + m_filler2 = 0; + } +}; + +static const int SDRDaemonUdpSize = UDPSINKFEC_UDPSIZE; +static const int SDRDaemonNbOrginalBlocks = UDPSINKFEC_NBORIGINALBLOCKS; +static const int SDRDaemonNbBytesPerBlock = UDPSINKFEC_UDPSIZE - sizeof(SDRDaemonHeader); + +struct SDRDaemonProtectedBlock +{ + uint8_t buf[SDRDaemonNbBytesPerBlock]; + + void init() { + std::fill(buf, buf+SDRDaemonNbBytesPerBlock, 0); + } +}; + +struct SDRDaemonSuperBlock +{ + SDRDaemonHeader m_header; + SDRDaemonProtectedBlock m_protectedBlock; + + void init() + { + m_header.init(); + m_protectedBlock.init(); + } +}; +#pragma pack(pop) + +struct SDRDaemonTxControlBlock +{ + bool m_complete; + bool m_processed; + uint16_t m_frameIndex; + int m_nbBlocksFEC; + int m_txDelay; + QString m_dataAddress; + uint16_t m_dataPort; + + SDRDaemonTxControlBlock() { + m_complete = false; + m_processed = false; + m_frameIndex = 0; + m_nbBlocksFEC = 0; + m_txDelay = 100; + m_dataAddress = "127.0.0.1"; + m_dataPort = 9090; + } +}; + +struct SDRDaemonRxControlBlock +{ + int m_blockCount; //!< number of blocks received for this frame + int m_originalCount; //!< number of original blocks received + int m_recoveryCount; //!< number of recovery blocks received + bool m_metaRetrieved; //!< true if meta data (block zero) was retrieved + int m_frameIndex; //!< this frame index or -1 if unset + + SDRDaemonRxControlBlock() { + m_blockCount = 0; + m_originalCount = 0; + m_recoveryCount = 0; + m_metaRetrieved = false; + m_frameIndex = -1; + } +}; + +class SDRDaemonDataBlock +{ +public: + SDRDaemonDataBlock() { + m_superBlocks = new SDRDaemonSuperBlock[256]; + } + ~SDRDaemonDataBlock() { + delete[] m_superBlocks; + } + SDRDaemonTxControlBlock m_txControlBlock; + SDRDaemonRxControlBlock m_rxControlBlock; + SDRDaemonSuperBlock *m_superBlocks; +}; + +#endif /* SDRDAEMON_CHANNEL_SDRDAEMONDATABLOCK_H_ */ diff --git a/sdrbase/channel/sdrdaemondataqueue.cpp b/sdrbase/channel/sdrdaemondataqueue.cpp new file mode 100644 index 000000000..b2be6ea41 --- /dev/null +++ b/sdrbase/channel/sdrdaemondataqueue.cpp @@ -0,0 +1,86 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon sink channel (Rx) data blocks queue // +// // +// SDRdaemon is a detached SDR front end that handles the interface with a // +// physical device and sends or receives the I/Q samples stream to or from a // +// SDRangel instance via UDP. It is controlled via a Web REST API. // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include "channel/sdrdaemondataqueue.h" +#include "channel/sdrdaemondatablock.h" + +SDRDaemonDataQueue::SDRDaemonDataQueue(QObject* parent) : + QObject(parent), + m_lock(QMutex::Recursive), + m_queue() +{ +} + +SDRDaemonDataQueue::~SDRDaemonDataQueue() +{ + SDRDaemonDataBlock* data; + + while ((data = pop()) != 0) + { + qDebug() << "SDRDaemonDataQueue::~SDRDaemonDataQueue: data block was still in queue"; + delete data; + } +} + +void SDRDaemonDataQueue::push(SDRDaemonDataBlock* data, bool emitSignal) +{ + if (data) + { + m_lock.lock(); + m_queue.append(data); + m_lock.unlock(); + } + + if (emitSignal) + { + emit dataBlockEnqueued(); + } +} + +SDRDaemonDataBlock* SDRDaemonDataQueue::pop() +{ + QMutexLocker locker(&m_lock); + + if (m_queue.isEmpty()) + { + return 0; + } + else + { + return m_queue.takeFirst(); + } +} + +int SDRDaemonDataQueue::size() +{ + QMutexLocker locker(&m_lock); + + return m_queue.size(); +} + +void SDRDaemonDataQueue::clear() +{ + QMutexLocker locker(&m_lock); + m_queue.clear(); +} diff --git a/plugins/channelrx/demodatv/atvscreeninterface.h b/sdrbase/channel/sdrdaemondataqueue.h similarity index 52% rename from plugins/channelrx/demodatv/atvscreeninterface.h rename to sdrbase/channel/sdrdaemondataqueue.h index 3a16c59aa..25817a21d 100644 --- a/plugins/channelrx/demodatv/atvscreeninterface.h +++ b/sdrbase/channel/sdrdaemondataqueue.h @@ -1,9 +1,11 @@ /////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2017 F4HKW // -// for F4EXB / SDRAngel // +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // // // -// OpenGL interface modernization. // -// See: http://doc.qt.io/qt-5/qopenglshaderprogram.html // +// SDRdaemon sink channel (Rx) data blocks queue // +// // +// SDRdaemon is a detached SDR front end that handles the interface with a // +// physical device and sends or receives the I/Q samples stream to or from a // +// SDRangel instance via UDP. It is controlled via a Web REST API. // // // // This program is free software; you can redistribute it and/or modify // // it under the terms of the GNU General Public License as published by // @@ -18,29 +20,34 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef PLUGINS_CHANNELRX_DEMODATV_ATVSCREENINTERFACE_H_ -#define PLUGINS_CHANNELRX_DEMODATV_ATVSCREENINTERFACE_H_ +#ifndef SDRDAEMON_CHANNEL_SDRDAEMONDATAQUEUE_H_ +#define SDRDAEMON_CHANNEL_SDRDAEMONDATAQUEUE_H_ + +#include +#include +#include + +class SDRDaemonDataBlock; + +class SDRDaemonDataQueue : public QObject { + Q_OBJECT -class ATVScreenInterface -{ public: - ATVScreenInterface() : - m_blnRenderImmediate(false) - {} + SDRDaemonDataQueue(QObject* parent = NULL); + ~SDRDaemonDataQueue(); - virtual ~ATVScreenInterface() {} + void push(SDRDaemonDataBlock* dataBlock, bool emitSignal = true); //!< Push daa block onto queue + SDRDaemonDataBlock* pop(); //!< Pop message from queue - virtual void resizeATVScreen(int intCols __attribute__((unused)), int intRows __attribute__((unused))) {} - virtual void renderImage(unsigned char * objData __attribute__((unused))) {} - virtual bool selectRow(int intLine __attribute__((unused))) { return false; } - virtual bool setDataColor(int intCol __attribute__((unused)), int intRed __attribute__((unused)), int intGreen __attribute__((unused)), int intBlue __attribute__((unused))) { return false; } - void setRenderImmediate(bool blnRenderImmediate) { m_blnRenderImmediate = blnRenderImmediate; } + int size(); //!< Returns queue size + void clear(); //!< Empty queue -protected: - bool m_blnRenderImmediate; +signals: + void dataBlockEnqueued(); +private: + QMutex m_lock; + QQueue m_queue; }; - - -#endif /* PLUGINS_CHANNELRX_DEMODATV_ATVSCREENINTERFACE_H_ */ +#endif /* SDRDAEMON_CHANNEL_SDRDAEMONDATAQUEUE_H_ */ diff --git a/sdrbase/channel/sdrdaemondatareadqueue.cpp b/sdrbase/channel/sdrdaemondatareadqueue.cpp new file mode 100644 index 000000000..601a5f12a --- /dev/null +++ b/sdrbase/channel/sdrdaemondatareadqueue.cpp @@ -0,0 +1,161 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon sink channel (Rx) data blocks to read queue // +// // +// SDRdaemon is a detached SDR front end that handles the interface with a // +// physical device and sends or receives the I/Q samples stream to or from a // +// SDRangel instance via UDP. It is controlled via a Web REST API. // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "channel/sdrdaemondatablock.h" +#include "channel/sdrdaemondatareadqueue.h" + +const uint32_t SDRDaemonDataReadQueue::MinimumMaxSize = 10; + +SDRDaemonDataReadQueue::SDRDaemonDataReadQueue() : + m_dataBlock(0), + m_maxSize(MinimumMaxSize), + m_blockIndex(1), + m_sampleIndex(0), + m_sampleCount(0), + m_full(false) +{} + +SDRDaemonDataReadQueue::~SDRDaemonDataReadQueue() +{ + SDRDaemonDataBlock* data; + + while ((data = pop()) != 0) + { + qDebug("SDRDaemonDataReadQueue::~SDRDaemonDataReadQueue: data block was still in queue"); + delete data; + } +} + +void SDRDaemonDataReadQueue::push(SDRDaemonDataBlock* dataBlock) +{ + if (length() >= m_maxSize) + { + qWarning("SDRDaemonDataReadQueue::push: queue is full"); + m_full = true; // stop filling the queue + SDRDaemonDataBlock *data = m_dataReadQueue.takeLast(); + delete data; + } + + if (m_full) { + m_full = (length() > m_maxSize/2); // do not fill queue again before queue is half size + } + + if (!m_full) { + m_dataReadQueue.append(dataBlock); + } +} + +SDRDaemonDataBlock* SDRDaemonDataReadQueue::pop() +{ + if (m_dataReadQueue.isEmpty()) + { + return 0; + } + else + { + m_blockIndex = 1; + m_sampleIndex = 0; + + return m_dataReadQueue.takeFirst(); + } +} + +void SDRDaemonDataReadQueue::setSize(uint32_t size) +{ + if (size != m_maxSize) { + m_maxSize = size < MinimumMaxSize ? MinimumMaxSize : size; + } +} + +void SDRDaemonDataReadQueue::readSample(Sample& s, bool scaleForTx) +{ + // depletion/repletion state + if (m_dataBlock == 0) + { + if (length() >= m_maxSize/2) + { + qDebug("SDRDaemonDataReadQueue::readSample: initial pop new block: queue size: %u", length()); + m_blockIndex = 1; + m_dataBlock = m_dataReadQueue.takeFirst(); + convertDataToSample(s, m_blockIndex, m_sampleIndex, scaleForTx); + m_sampleIndex++; + m_sampleCount++; + } + else + { + s = Sample{0, 0}; + } + + return; + } + + int sampleSize = m_dataBlock->m_superBlocks[m_blockIndex].m_header.m_sampleBytes * 2; + uint32_t samplesPerBlock = SDRDaemonNbBytesPerBlock / sampleSize; + + if (m_sampleIndex < samplesPerBlock) + { + convertDataToSample(s, m_blockIndex, m_sampleIndex, scaleForTx); + m_sampleIndex++; + m_sampleCount++; + } + else + { + m_sampleIndex = 0; + m_blockIndex++; + + if (m_blockIndex < SDRDaemonNbOrginalBlocks) + { + convertDataToSample(s, m_blockIndex, m_sampleIndex, scaleForTx); + m_sampleIndex++; + m_sampleCount++; + } + else + { + if (m_dataBlock) + { + delete m_dataBlock; + m_dataBlock = 0; + + if (length() == 0) { + qWarning("SDRDaemonDataReadQueue::readSample: try to pop new block but queue is empty"); + } + } + + if (length() > 0) + { + //qDebug("SDRDaemonDataReadQueue::readSample: pop new block: queue size: %u", length()); + m_blockIndex = 1; + m_dataBlock = m_dataReadQueue.takeFirst(); + convertDataToSample(s, m_blockIndex, m_sampleIndex, scaleForTx); + m_sampleIndex++; + m_sampleCount++; + } + else + { + s = Sample{0, 0}; + } + } + } +} + + + diff --git a/sdrbase/channel/sdrdaemondatareadqueue.h b/sdrbase/channel/sdrdaemondatareadqueue.h new file mode 100644 index 000000000..ce120f8d2 --- /dev/null +++ b/sdrbase/channel/sdrdaemondatareadqueue.h @@ -0,0 +1,93 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB. // +// // +// SDRdaemon sink channel (Rx) data blocks to read queue // +// // +// SDRdaemon is a detached SDR front end that handles the interface with a // +// physical device and sends or receives the I/Q samples stream to or from a // +// SDRangel instance via UDP. It is controlled via a Web REST API. // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRDAEMON_CHANNEL_SDRDAEMONDATAREADQUEUE_H_ +#define SDRDAEMON_CHANNEL_SDRDAEMONDATAREADQUEUE_H_ + +#include + +class SDRDaemonDataBlock; +class Sample; + +class SDRDaemonDataReadQueue +{ +public: + SDRDaemonDataReadQueue(); + ~SDRDaemonDataReadQueue(); + + void push(SDRDaemonDataBlock* dataBlock); //!< push block on the queue + SDRDaemonDataBlock* pop(); //!< Pop block from the queue + void readSample(Sample& s, bool scaleForTx = false); //!< Read sample from queue possibly scaling to Tx size + uint32_t length() const { return m_dataReadQueue.size(); } //!< Returns queue length + uint32_t size() const { return m_maxSize; } //!< Returns queue size (max length) + void setSize(uint32_t size); //!< Sets the queue size (max length) + uint32_t readSampleCount() const { return m_sampleCount; } //!< Returns the absolute number of samples read + + static const uint32_t MinimumMaxSize; + +private: + QQueue m_dataReadQueue; + SDRDaemonDataBlock *m_dataBlock; + uint32_t m_maxSize; + uint32_t m_blockIndex; + uint32_t m_sampleIndex; + uint32_t m_sampleCount; //!< use a counter capped below 2^31 as it is going to be converted to an int in the web interface + bool m_full; //!< full condition was hit + + inline void convertDataToSample(Sample& s, uint32_t blockIndex, uint32_t sampleIndex, bool scaleForTx) + { + int sampleSize = m_dataBlock->m_superBlocks[blockIndex].m_header.m_sampleBytes * 2; // I/Q sample size in data block + int samplebits = m_dataBlock->m_superBlocks[blockIndex].m_header.m_sampleBits; // I or Q sample size in bits + int32_t iconv, qconv; + + if (sizeof(Sample) == sampleSize) // generally 16->16 or 24->24 bits + { + s = *((Sample*) &(m_dataBlock->m_superBlocks[blockIndex].m_protectedBlock.buf[sampleIndex*sampleSize])); + } + else if ((sizeof(Sample) == 4) && (sampleSize == 8)) // generally 24->16 bits + { + iconv = ((int32_t*) &(m_dataBlock->m_superBlocks[blockIndex].m_protectedBlock.buf[sampleIndex*sampleSize]))[0]; + qconv = ((int32_t*) &(m_dataBlock->m_superBlocks[blockIndex].m_protectedBlock.buf[sampleIndex*sampleSize+4]))[0]; + iconv >>= scaleForTx ? (SDR_TX_SAMP_SZ-SDR_RX_SAMP_SZ) : (samplebits-SDR_RX_SAMP_SZ); + qconv >>= scaleForTx ? (SDR_TX_SAMP_SZ-SDR_RX_SAMP_SZ) : (samplebits-SDR_RX_SAMP_SZ); + s.setReal(iconv); + s.setImag(qconv); + } + else if ((sizeof(Sample) == 8) && (sampleSize == 4)) // generally 16->24 bits + { + iconv = ((int16_t*) &(m_dataBlock->m_superBlocks[blockIndex].m_protectedBlock.buf[sampleIndex*sampleSize]))[0]; + qconv = ((int16_t*) &(m_dataBlock->m_superBlocks[blockIndex].m_protectedBlock.buf[sampleIndex*sampleSize+2]))[0]; + iconv <<= scaleForTx ? (SDR_TX_SAMP_SZ-samplebits) : (SDR_RX_SAMP_SZ-samplebits); + qconv <<= scaleForTx ? (SDR_TX_SAMP_SZ-samplebits) : (SDR_RX_SAMP_SZ-samplebits); + s.setReal(iconv); + s.setImag(qconv); + } + else + { + s = Sample{0, 0}; + } + } +}; + + + +#endif /* SDRDAEMON_CHANNEL_SDRDAEMONDATAREADQUEUE_H_ */ diff --git a/sdrbase/commands/command.h b/sdrbase/commands/command.h index 172ac0d31..8103b1791 100644 --- a/sdrbase/commands/command.h +++ b/sdrbase/commands/command.h @@ -25,7 +25,9 @@ #include #include -class Command : public QObject +#include "export.h" + +class SDRBASE_API Command : public QObject { Q_OBJECT public: diff --git a/sdrbase/device/deviceenumerator.h b/sdrbase/device/deviceenumerator.h index 92938d5ac..14dae4048 100644 --- a/sdrbase/device/deviceenumerator.h +++ b/sdrbase/device/deviceenumerator.h @@ -20,10 +20,11 @@ #include #include "plugin/plugininterface.h" +#include "export.h" class PluginManager; -class DeviceEnumerator +class SDRBASE_API DeviceEnumerator { public: DeviceEnumerator(); diff --git a/sdrbase/device/devicesinkapi.h b/sdrbase/device/devicesinkapi.h index 7566aeef7..1467dd64b 100644 --- a/sdrbase/device/devicesinkapi.h +++ b/sdrbase/device/devicesinkapi.h @@ -21,7 +21,7 @@ #include #include "dsp/dspdevicesinkengine.h" -#include "util/export.h" +#include "export.h" class BasebandSampleSource; class ThreadedBasebandSampleSource; @@ -33,7 +33,7 @@ class Preset; class DeviceSourceAPI; class ChannelSourceAPI; -class SDRANGEL_API DeviceSinkAPI : public QObject { +class SDRBASE_API DeviceSinkAPI : public QObject { Q_OBJECT public: diff --git a/sdrbase/device/devicesourceapi.h b/sdrbase/device/devicesourceapi.h index 828506a9e..72286b7ec 100644 --- a/sdrbase/device/devicesourceapi.h +++ b/sdrbase/device/devicesourceapi.h @@ -23,7 +23,7 @@ #include "dsp/dspdevicesourceengine.h" -#include "util/export.h" +#include "export.h" class BasebandSampleSink; class ThreadedBasebandSampleSink; @@ -35,7 +35,7 @@ class Preset; class DeviceSinkAPI; class ChannelSinkAPI; -class SDRANGEL_API DeviceSourceAPI : public QObject { +class SDRBASE_API DeviceSourceAPI : public QObject { Q_OBJECT public: diff --git a/sdrbase/dsp/afsquelch.cpp b/sdrbase/dsp/afsquelch.cpp index 361478d12..dee0e4216 100644 --- a/sdrbase/dsp/afsquelch.cpp +++ b/sdrbase/dsp/afsquelch.cpp @@ -18,16 +18,16 @@ #include "dsp/afsquelch.h" #undef M_PI -#define M_PI 3.14159265358979323846 +#define M_PI 3.14159265358979323846 -AFSquelch::AFSquelch(unsigned int nbTones, const double *tones) : +AFSquelch::AFSquelch() : m_nbAvg(128), - m_N(0), - m_sampleRate(0), + m_N(24), + m_sampleRate(48000), m_samplesProcessed(0), m_samplesAvgProcessed(0), m_maxPowerIndex(0), - m_nTones(nbTones), + m_nTones(2), m_samplesAttack(0), m_attackCount(0), m_samplesDecay(0), @@ -46,9 +46,9 @@ AFSquelch::AFSquelch(unsigned int nbTones, const double *tones) : for (unsigned int j = 0; j < m_nTones; ++j) { - m_toneSet[j] = tones[j]; - m_k[j] = ((double)m_N * m_toneSet[j]) / (double)m_sampleRate; - m_coef[j] = 2.0 * cos((2.0 * M_PI * m_toneSet[j])/(double)m_sampleRate); + m_toneSet[j] = j == 0 ? 1000.0 : 6000.0; + m_k[j] = ((double)m_N * m_toneSet[j]) / (double) m_sampleRate; + m_coef[j] = 2.0 * cos((2.0 * M_PI * m_toneSet[j])/(double) m_sampleRate); m_u0[j] = 0.0; m_u1[j] = 0.0; m_power[j] = 0.0; @@ -67,19 +67,19 @@ AFSquelch::~AFSquelch() delete[] m_power; } - void AFSquelch::setCoefficients( unsigned int N, unsigned int nbAvg, - unsigned int _samplerate, - unsigned int _samplesAttack, - unsigned int _samplesDecay) + unsigned int sampleRate, + unsigned int samplesAttack, + unsigned int samplesDecay, + const double *tones) { m_N = N; // save the basic parameters for use during analysis m_nbAvg = nbAvg; - m_sampleRate = _samplerate; - m_samplesAttack = _samplesAttack; - m_samplesDecay = _samplesDecay; + m_sampleRate = sampleRate; + m_samplesAttack = samplesAttack; + m_samplesDecay = samplesDecay; m_movingAverages.resize(m_nTones, MovingAverage(m_nbAvg, 0.0)); m_samplesProcessed = 0; m_samplesAvgProcessed = 0; @@ -97,8 +97,10 @@ void AFSquelch::setCoefficients( // for later display. The tone set is specified in the // constructor. Notice that the resulting coefficients are // independent of N. + for (unsigned int j = 0; j < m_nTones; ++j) { + m_toneSet[j] = tones[j] < ((double) m_sampleRate) * 0.4 ? tones[j] : ((double) m_sampleRate) * 0.4; // guarantee 80% Nyquist rate m_k[j] = ((double)m_N * m_toneSet[j]) / (double)m_sampleRate; m_coef[j] = 2.0 * cos((2.0 * M_PI * m_toneSet[j])/(double)m_sampleRate); m_u0[j] = 0.0; diff --git a/sdrbase/dsp/afsquelch.h b/sdrbase/dsp/afsquelch.h index d3e66447c..089a98c7c 100644 --- a/sdrbase/dsp/afsquelch.h +++ b/sdrbase/dsp/afsquelch.h @@ -19,24 +19,25 @@ #include "dsp/dsptypes.h" #include "dsp/movingaverage.h" +#include "export.h" /** AFSquelch: AF squelch class based on the Modified Goertzel * algorithm. */ -class AFSquelch { +class SDRBASE_API AFSquelch { public: - // allows user defined tone pair - AFSquelch(unsigned int nbTones, - const double *tones); + // constructor with default values + AFSquelch(); virtual ~AFSquelch(); // setup the basic parameters and coefficients void setCoefficients( unsigned int N, //!< the algorithm "block" size unsigned int nbAvg, //!< averaging size - unsigned int SampleRate, //!< input signal sample rate - unsigned int _samplesAttack, //!< number of results before squelch opens - unsigned int _samplesDecay); //!< number of results keeping squelch open + unsigned int sampleRate, //!< input signal sample rate + unsigned int samplesAttack, //!< number of results before squelch opens + unsigned int samplesDecay, //!< number of results keeping squelch open + const double *tones); //!< center frequency of tones tested // set the detection threshold void setThreshold(double _threshold); diff --git a/sdrbase/dsp/agc.cpp b/sdrbase/dsp/agc.cpp index 94b77cd0b..93671c617 100644 --- a/sdrbase/dsp/agc.cpp +++ b/sdrbase/dsp/agc.cpp @@ -10,8 +10,6 @@ #include "util/stepfunctions.h" -#define StepLengthMax 2400 // 50ms - AGC::AGC(int historySize, double R) : m_u0(1.0), m_R(R), @@ -49,7 +47,7 @@ MagAGC::MagAGC(int historySize, double R, double threshold) : m_threshold(threshold), m_thresholdEnable(true), m_gate(0), - m_stepLength(std::min(StepLengthMax, historySize/2)), + m_stepLength(std::min(2400, historySize/2)), // max 50 ms (at 48 kHz) m_stepDelta(1.0/m_stepLength), m_stepUpCounter(0), m_stepDownCounter(m_stepLength), @@ -63,10 +61,10 @@ MagAGC::MagAGC(int historySize, double R, double threshold) : MagAGC::~MagAGC() {} -void MagAGC::resize(int historySize, Real R) +void MagAGC::resize(int historySize, int stepLength, Real R) { m_R2 = R*R; - m_stepLength = std::min(StepLengthMax, historySize/2); + m_stepLength = stepLength; m_stepDelta = 1.0 / m_stepLength; m_stepUpCounter = 0; m_stepDownCounter = m_stepLength; @@ -106,13 +104,19 @@ double MagAGC::feedAndGetValue(const Complex& ci) { if (m_squared) { - double u0 = m_R / m_moving_average.average(); - m_u0 = (u0 * m_magsq > m_clampMax) ? m_clampMax / m_magsq : u0; + if (m_magsq > m_clampMax) { + m_u0 = m_clampMax / m_magsq; + } else { + m_u0 = m_R / m_moving_average.average(); + } } else { - double u02 = m_R2 / m_moving_average.average(); - m_u0 = (u02 * m_magsq > m_clampMax) ? sqrt(m_clampMax / m_magsq) : sqrt(u02); + if (sqrt(m_magsq) > m_clampMax) { + m_u0 = m_clampMax / sqrt(m_magsq); + } else { + m_u0 = m_R / sqrt(m_moving_average.average()); + } } } else @@ -176,3 +180,27 @@ double MagAGC::feedAndGetValue(const Complex& ci) return m_u0; } } + +float MagAGC::getStepDownValue() const +{ + if (m_count < m_stepDownDelay) + { + return 1.0f; + } + else + { + return StepFunctions::smootherstep(m_stepDownCounter * m_stepDelta); + } +} + +float MagAGC::getStepValue() const +{ + if (m_count < m_stepDownDelay) + { + return StepFunctions::smootherstep(m_stepUpCounter * m_stepDelta); // step up + } + else + { + return StepFunctions::smootherstep(m_stepDownCounter * m_stepDelta); // step down + } +} diff --git a/sdrbase/dsp/agc.h b/sdrbase/dsp/agc.h index 1c107bd88..196b523af 100644 --- a/sdrbase/dsp/agc.h +++ b/sdrbase/dsp/agc.h @@ -10,8 +10,9 @@ #include "movingaverage.h" #include "util/movingaverage.h" +#include "export.h" -class AGC +class SDRBASE_API AGC { public: AGC(int historySize, double R); @@ -32,13 +33,13 @@ protected: }; -class MagAGC : public AGC +class SDRBASE_API MagAGC : public AGC { public: MagAGC(int historySize, double R, double threshold); virtual ~MagAGC(); void setSquared(bool squared) { m_squared = squared; } - void resize(int historySize, Real R); + void resize(int historySize, int stepLength, Real R); void setOrder(double R); virtual void feed(Complex& ci); double feedAndGetValue(const Complex& ci); @@ -49,6 +50,10 @@ public: void setStepDownDelay(int stepDownDelay) { m_stepDownDelay = stepDownDelay; } void setClamping(bool clamping) { m_clamping = clamping; } void setClampMax(double clampMax) { m_clampMax = clampMax; } + int getStepDownDelay() const { return m_stepDownDelay; } + float getStepDownValue() const; + float getStepValue() const; + private: bool m_squared; //!< use squared magnitude (power) to compute AGC value double m_magsq; //!< current squared magnitude (power) @@ -84,6 +89,13 @@ public: m_moving_average.resize(AvgSize, initial); } + void resizeNew(uint32_t newSize, Real initial, Real cutoff=0, Real clip=0) + { + m_cutoff = cutoff; + m_clip = clip; + m_moving_average.resize(newSize, initial); + } + void fill(double value) { m_moving_average.fill(value); diff --git a/sdrbase/dsp/basebandsamplesink.cpp b/sdrbase/dsp/basebandsamplesink.cpp index 7887f768d..81c9f53a9 100644 --- a/sdrbase/dsp/basebandsamplesink.cpp +++ b/sdrbase/dsp/basebandsamplesink.cpp @@ -1,5 +1,6 @@ -#include -#include "util/message.h" +#include "basebandsamplesink.h" + +MESSAGE_CLASS_DEFINITION(BasebandSampleSink::MsgThreadedSink, Message) BasebandSampleSink::BasebandSampleSink() : m_guiMessageQueue(0) diff --git a/sdrbase/dsp/basebandsamplesink.h b/sdrbase/dsp/basebandsamplesink.h index 9897d280f..21e122434 100644 --- a/sdrbase/dsp/basebandsamplesink.h +++ b/sdrbase/dsp/basebandsamplesink.h @@ -20,14 +20,36 @@ #include #include "dsp/dsptypes.h" -#include "util/export.h" +#include "export.h" #include "util/messagequeue.h" +#include "util/message.h" class Message; -class SDRANGEL_API BasebandSampleSink : public QObject { +class SDRBASE_API BasebandSampleSink : public QObject { Q_OBJECT public: + /** Used to notify on which thread the sample sink is now running (with ThreadedSampleSink) */ + class MsgThreadedSink : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const QThread* getThread() const { return m_thread; } + + static MsgThreadedSink* create(const QThread* thread) + { + return new MsgThreadedSink(thread); + } + + private: + const QThread *m_thread; + + MsgThreadedSink(const QThread *thread) : + Message(), + m_thread(thread) + { } + }; + BasebandSampleSink(); virtual ~BasebandSampleSink(); diff --git a/sdrbase/dsp/basebandsamplesource.h b/sdrbase/dsp/basebandsamplesource.h index 4daac5840..f9f2a66a9 100644 --- a/sdrbase/dsp/basebandsamplesource.h +++ b/sdrbase/dsp/basebandsamplesource.h @@ -21,12 +21,12 @@ #include #include "dsp/dsptypes.h" #include "dsp/samplesourcefifo.h" -#include "util/export.h" +#include "export.h" #include "util/messagequeue.h" class Message; -class SDRANGEL_API BasebandSampleSource : public QObject { +class SDRBASE_API BasebandSampleSource : public QObject { Q_OBJECT public: BasebandSampleSource(); diff --git a/sdrbase/dsp/channelmarker.cpp b/sdrbase/dsp/channelmarker.cpp index 4900dcdaa..1b242caf2 100644 --- a/sdrbase/dsp/channelmarker.cpp +++ b/sdrbase/dsp/channelmarker.cpp @@ -37,14 +37,11 @@ ChannelMarker::ChannelMarker(QObject* parent) : m_highlighted(false), m_color(m_colorTable[m_nextColor]), m_movable(true), - m_udpReceivePort(9999), - m_udpSendPort(9998), m_fScaleDisplayType(FScaleDisplay_freq) { ++m_nextColor; if(m_colorTable[m_nextColor] == 0) m_nextColor = 0; - setUDPAddress("127.0.0.1"); } void ChannelMarker::emitChangedByAPI() @@ -118,36 +115,11 @@ void ChannelMarker::setColor(const QColor& color) emit changedByAPI(); } -void ChannelMarker::setUDPAddress(const QString& udpAddress) -{ - m_udpAddress = udpAddress; - m_displayAddressReceive = QString(tr("%1:%2").arg(getUDPAddress()).arg(getUDPSendPort())); - m_displayAddressReceive = QString(tr("%1:%2").arg(getUDPAddress()).arg(getUDPReceivePort())); - emit changedByAPI(); -} - -void ChannelMarker::setUDPReceivePort(quint16 port) -{ - m_udpReceivePort = port; - m_displayAddressReceive = QString(tr("%1:%2").arg(getUDPAddress()).arg(getUDPReceivePort())); - emit changedByAPI(); -} - -void ChannelMarker::setUDPSendPort(quint16 port) -{ - m_udpSendPort = port; - m_displayAddressSend = QString(tr("%1:%2").arg(getUDPAddress()).arg(getUDPSendPort())); - emit changedByAPI(); -} - void ChannelMarker::resetToDefaults() { setTitle("Channel"); setCenterFrequency(0); setColor(Qt::white); - setUDPAddress("127.0.0.1"); - setUDPSendPort(9999); - setUDPReceivePort(9999); setFrequencyScaleDisplayType(FScaleDisplay_freq); } @@ -157,9 +129,6 @@ QByteArray ChannelMarker::serialize() const s.writeS32(1, getCenterFrequency()); s.writeU32(2, getColor().rgb()); s.writeString(3, getTitle()); - s.writeString(4, getUDPAddress()); - s.writeU32(5, (quint32) getUDPReceivePort()); - s.writeU32(6, (quint32) getUDPSendPort()); s.writeS32(7, (int) getFrequencyScaleDisplayType()); return s.final(); } @@ -189,12 +158,6 @@ bool ChannelMarker::deserialize(const QByteArray& data) } d.readString(3, &strtmp, "Channel"); setTitle(strtmp); - d.readString(4, &strtmp, "127.0.0.1"); - setUDPAddress(strtmp); - d.readU32(5, &u32tmp, 9999); - setUDPReceivePort(u32tmp); - d.readU32(6, &u32tmp, 9999); - setUDPSendPort(u32tmp); d.readS32(7, &tmp, 0); if ((tmp >= 0) && (tmp < FScaleDisplay_none)) { setFrequencyScaleDisplayType((frequencyScaleDisplay_t) tmp); diff --git a/sdrbase/dsp/channelmarker.h b/sdrbase/dsp/channelmarker.h index 30befb315..125a4cf58 100644 --- a/sdrbase/dsp/channelmarker.h +++ b/sdrbase/dsp/channelmarker.h @@ -6,9 +6,9 @@ #include #include "settings/serializable.h" -#include "util/export.h" +#include "export.h" -class SDRANGEL_API ChannelMarker : public QObject, public Serializable { +class SDRBASE_API ChannelMarker : public QObject, public Serializable { Q_OBJECT public: @@ -66,15 +66,6 @@ public: void setMovable(bool movable) { m_movable = movable; } bool getMovable() const { return m_movable; } - void setUDPAddress(const QString& udpAddress); - const QString& getUDPAddress() const { return m_udpAddress; } - - void setUDPReceivePort(quint16 port); - quint16 getUDPReceivePort() const { return m_udpReceivePort; } - - void setUDPSendPort(quint16 port); - quint16 getUDPSendPort() const { return m_udpSendPort; } - void setFrequencyScaleDisplayType(frequencyScaleDisplay_t type) { m_fScaleDisplayType = type; } frequencyScaleDisplay_t getFrequencyScaleDisplayType() const { return m_fScaleDisplayType; } @@ -100,9 +91,6 @@ protected: bool m_highlighted; QColor m_color; bool m_movable; - QString m_udpAddress; - quint16 m_udpReceivePort; - quint16 m_udpSendPort; frequencyScaleDisplay_t m_fScaleDisplayType; void resetToDefaults(); diff --git a/sdrbase/dsp/ctcssdetector.h b/sdrbase/dsp/ctcssdetector.h index e3799c07c..419b9ed10 100644 --- a/sdrbase/dsp/ctcssdetector.h +++ b/sdrbase/dsp/ctcssdetector.h @@ -10,12 +10,13 @@ #define INCLUDE_GPL_DSP_CTCSSDETECTOR_H_ #include "dsp/dsptypes.h" +#include "export.h" /** CTCSSDetector: Continuous Tone Coded Squelch System * tone detector class based on the Modified Goertzel * algorithm. */ -class CTCSSDetector { +class SDRBASE_API CTCSSDetector { public: // Constructors and Destructor CTCSSDetector(); diff --git a/sdrbase/dsp/cwkeyer.h b/sdrbase/dsp/cwkeyer.h index 445da213b..a1c977a0c 100644 --- a/sdrbase/dsp/cwkeyer.h +++ b/sdrbase/dsp/cwkeyer.h @@ -21,14 +21,14 @@ #include #include -#include "util/export.h" +#include "export.h" #include "util/message.h" #include "cwkeyersettings.h" /** * Ancillary class to smooth out CW transitions with a sine shape */ -class CWSmoother +class SDRBASE_API CWSmoother { public: CWSmoother(); @@ -46,7 +46,7 @@ private: float *m_fadeOutSamples; }; -class SDRANGEL_API CWKeyer : public QObject { +class SDRBASE_API CWKeyer : public QObject { Q_OBJECT public: @@ -127,7 +127,7 @@ private: bool m_dot; bool m_dash; bool m_elementOn; - char m_asciiChar; + signed char m_asciiChar; CWKeyIambicState m_keyIambicState; CWTextState m_textState; CWSmoother m_cwSmoother; diff --git a/sdrbase/dsp/cwkeyersettings.h b/sdrbase/dsp/cwkeyersettings.h index 49fcbfd95..5ebaf9401 100644 --- a/sdrbase/dsp/cwkeyersettings.h +++ b/sdrbase/dsp/cwkeyersettings.h @@ -21,8 +21,11 @@ #include #include -struct CWKeyerSettings +#include "export.h" + +class SDRBASE_API CWKeyerSettings { +public: typedef enum { CWNone, diff --git a/sdrbase/dsp/decimators.h b/sdrbase/dsp/decimators.h index a854edc75..ee54b56e9 100644 --- a/sdrbase/dsp/decimators.h +++ b/sdrbase/dsp/decimators.h @@ -18,15 +18,7 @@ #define INCLUDE_GPL_DSP_DECIMATORS_H_ #include "dsp/dsptypes.h" -#ifdef SDR_RX_SAMPLE_24BIT -#include "dsp/inthalfbandfilterdb.h" -#else -#ifdef USE_SSE4_1 -#include "dsp/inthalfbandfiltereo1.h" -#else -#include "dsp/inthalfbandfilterdb.h" -#endif -#endif +#include "dsp/inthalfbandfiltereo.h" #define DECIMATORS_HB_FILTER_ORDER 64 @@ -282,14 +274,15 @@ struct TripleByteLE } __attribute__((__packed__)); -template +/** Decimators with integer input and integer output */ +template class Decimators { public: // interleaved I/Q input buffer void decimate1(SampleVector::iterator* it, const T* buf, qint32 len); void decimate2_u(SampleVector::iterator* it, const T* buf, qint32 len); - void decimate2_inf(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate2_inf(SampleVector::iterator* it, const T* buf, qint32 len); void decimate2_sup(SampleVector::iterator* it, const T* buf, qint32 len); void decimate2_cen(SampleVector::iterator* it, const T* buf, qint32 len); void decimate4_inf(SampleVector::iterator* it, const T* buf, qint32 len); @@ -331,33 +324,24 @@ public: private: #ifdef SDR_RX_SAMPLE_24BIT - IntHalfbandFilterDB m_decimator2; // 1st stages - IntHalfbandFilterDB m_decimator4; // 2nd stages - IntHalfbandFilterDB m_decimator8; // 3rd stages - IntHalfbandFilterDB m_decimator16; // 4th stages - IntHalfbandFilterDB m_decimator32; // 5th stages - IntHalfbandFilterDB m_decimator64; // 6th stages + IntHalfbandFilterEO m_decimator2; // 1st stages + IntHalfbandFilterEO m_decimator4; // 2nd stages + IntHalfbandFilterEO m_decimator8; // 3rd stages + IntHalfbandFilterEO m_decimator16; // 4th stages + IntHalfbandFilterEO m_decimator32; // 5th stages + IntHalfbandFilterEO m_decimator64; // 6th stages #else -#ifdef USE_SSE4_1 - IntHalfbandFilterEO1 m_decimator2; // 1st stages - IntHalfbandFilterEO1 m_decimator4; // 2nd stages - IntHalfbandFilterEO1 m_decimator8; // 3rd stages - IntHalfbandFilterEO1 m_decimator16; // 4th stages - IntHalfbandFilterEO1 m_decimator32; // 5th stages - IntHalfbandFilterEO1 m_decimator64; // 6th stages -#else - IntHalfbandFilterDB m_decimator2; // 1st stages - IntHalfbandFilterDB m_decimator4; // 2nd stages - IntHalfbandFilterDB m_decimator8; // 3rd stages - IntHalfbandFilterDB m_decimator16; // 4th stages - IntHalfbandFilterDB m_decimator32; // 5th stages - IntHalfbandFilterDB m_decimator64; // 6th stages -#endif + IntHalfbandFilterEO m_decimator2; // 1st stages + IntHalfbandFilterEO m_decimator4; // 2nd stages + IntHalfbandFilterEO m_decimator8; // 3rd stages + IntHalfbandFilterEO m_decimator16; // 4th stages + IntHalfbandFilterEO m_decimator32; // 5th stages + IntHalfbandFilterEO m_decimator64; // 6th stages #endif }; -template -void Decimators::decimate1(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate1(SampleVector::iterator* it, const T* buf, qint32 len) { qint32 xreal, yimag; @@ -371,8 +355,8 @@ void Decimators::decimate1(SampleVector::iterat } } -template -void Decimators::decimate1(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +template +void Decimators::decimate1(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) { qint32 xreal, yimag; @@ -386,10 +370,10 @@ void Decimators::decimate1(SampleVector::iterat } } -template -void Decimators::decimate2_u(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate2_u(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal, yimag; + StorageType xreal, yimag; for (int pos = 0; pos < len - 7; pos += 8) { @@ -407,10 +391,10 @@ void Decimators::decimate2_u(SampleVector::iter } } -template -void Decimators::decimate2_u(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +template +void Decimators::decimate2_u(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) { - AccuType xreal, yimag; + StorageType xreal, yimag; for (int pos = 0; pos < len - 3; pos += 4) { @@ -430,683 +414,2230 @@ void Decimators::decimate2_u(SampleVector::iter } } -template -void Decimators::decimate2_inf(SampleVector::iterator* it, const T* buf, qint32 len) +// No filtering: bad for Rx OK for signal tracking +//template +//void Decimators::decimate2_inf(SampleVector::iterator* it, const T* buf, qint32 len) +//{ +// StorageType xreal, yimag; +// +// for (int pos = 0; pos < len - 7; pos += 8) +// { +// xreal = (buf[pos+0] - buf[pos+3]) << decimation_shifts::pre2; +// yimag = (buf[pos+1] + buf[pos+2]) << decimation_shifts::pre2; +// (**it).setReal(xreal >> decimation_shifts::post2); +// (**it).setImag(yimag >> decimation_shifts::post2); +// ++(*it); +// +// xreal = (buf[pos+7] - buf[pos+4]) << decimation_shifts::pre2; +// yimag = (- buf[pos+5] - buf[pos+6]) << decimation_shifts::pre2; +// (**it).setReal(xreal >> decimation_shifts::post2); +// (**it).setImag(yimag >> decimation_shifts::post2); +// ++(*it); +// } +//} + +//template +//void Decimators::decimate2_inf(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +//{ +// StorageType xreal, yimag; +// +// for (int pos = 0; pos < len - 3; pos += 4) +// { +// // 0: I[0] 1: Q[0] 2: I[1] 3: Q[1] +// xreal = (bufI[pos] - bufQ[pos+1]) << decimation_shifts::pre2; +// yimag = (bufQ[pos] + bufI[pos+1]) << decimation_shifts::pre2; +// (**it).setReal(xreal >> decimation_shifts::post2); +// (**it).setImag(yimag >> decimation_shifts::post2); +// ++(*it); +// +// // 4: I[2] 5: Q[2] 6: I[3] 7: Q[3] +// xreal = (bufQ[pos+3] - bufI[pos+2]) << decimation_shifts::pre2; +// yimag = (- bufQ[pos+2] - bufI[pos+3]) << decimation_shifts::pre2; +// (**it).setReal(xreal >> decimation_shifts::post2); +// (**it).setImag(yimag >> decimation_shifts::post2); +// ++(*it); +// } +//} + +template +void Decimators::decimate2_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal, yimag; + StorageType buf2[4]; - for (int pos = 0; pos < len - 7; pos += 8) - { - xreal = (buf[pos+0] - buf[pos+3]) << decimation_shifts::pre2; - yimag = (buf[pos+1] + buf[pos+2]) << decimation_shifts::pre2; - (**it).setReal(xreal >> decimation_shifts::post2); - (**it).setImag(yimag >> decimation_shifts::post2); - ++(*it); - - xreal = (buf[pos+7] - buf[pos+4]) << decimation_shifts::pre2; - yimag = (- buf[pos+5] - buf[pos+6]) << decimation_shifts::pre2; - (**it).setReal(xreal >> decimation_shifts::post2); - (**it).setImag(yimag >> decimation_shifts::post2); - ++(*it); - } -} - -template -void Decimators::decimate2_inf(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) -{ - AccuType xreal, yimag; - - for (int pos = 0; pos < len - 3; pos += 4) + for (int pos = 0; pos < len - 7; pos += 8) { - // 0: I[0] 1: Q[0] 2: I[1] 3: Q[1] - xreal = (bufI[pos] - bufQ[pos+1]) << decimation_shifts::pre2; - yimag = (bufQ[pos] + bufI[pos+1]) << decimation_shifts::pre2; - (**it).setReal(xreal >> decimation_shifts::post2); - (**it).setImag(yimag >> decimation_shifts::post2); + m_decimator2.myDecimateInf( + buf[pos+0] << decimation_shifts::pre2, + buf[pos+1] << decimation_shifts::pre2, + buf[pos+2] << decimation_shifts::pre2, + buf[pos+3] << decimation_shifts::pre2, + buf[pos+4] << decimation_shifts::pre2, + buf[pos+5] << decimation_shifts::pre2, + buf[pos+6] << decimation_shifts::pre2, + buf[pos+7] << decimation_shifts::pre2, + &buf2[0]); + + (**it).setReal(buf2[0] >> decimation_shifts::post2); + (**it).setImag(buf2[1] >> decimation_shifts::post2); ++(*it); - // 4: I[2] 5: Q[2] 6: I[3] 7: Q[3] - xreal = (bufQ[pos+3] - bufI[pos+2]) << decimation_shifts::pre2; - yimag = (- bufQ[pos+2] - bufI[pos+3]) << decimation_shifts::pre2; - (**it).setReal(xreal >> decimation_shifts::post2); - (**it).setImag(yimag >> decimation_shifts::post2); + (**it).setReal(buf2[2] >> decimation_shifts::post2); + (**it).setImag(buf2[3] >> decimation_shifts::post2); ++(*it); } } -template -void Decimators::decimate2_sup(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate2_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal, yimag; + StorageType buf2[4]; - for (int pos = 0; pos < len - 7; pos += 8) - { - xreal = (buf[pos+1] - buf[pos+2]) << decimation_shifts::pre2; - yimag = (- buf[pos+0] - buf[pos+3]) << decimation_shifts::pre2; - (**it).setReal(xreal >> decimation_shifts::post2); - (**it).setImag(yimag >> decimation_shifts::post2); - ++(*it); - - xreal = (buf[pos+6] - buf[pos+5]) << decimation_shifts::pre2; - yimag = (buf[pos+4] + buf[pos+7]) << decimation_shifts::pre2; - (**it).setReal(xreal >> decimation_shifts::post2); - (**it).setImag(yimag >> decimation_shifts::post2); - ++(*it); - } -} - -template -void Decimators::decimate2_sup(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) -{ - AccuType xreal, yimag; - - for (int pos = 0; pos < len - 3; pos += 4) + for (int pos = 0; pos < len - 7; pos += 8) { - // 0: I[0] 1: Q[0] 2: I[1] 3: Q[1] - xreal = (bufQ[pos] - bufI[pos+1]) << decimation_shifts::pre2; - yimag = (- bufI[pos] - bufQ[pos+1]) << decimation_shifts::pre2; - (**it).setReal(xreal >> decimation_shifts::post2); - (**it).setImag(yimag >> decimation_shifts::post2); + m_decimator2.myDecimateSup( + buf[pos+0] << decimation_shifts::pre2, + buf[pos+1] << decimation_shifts::pre2, + buf[pos+2] << decimation_shifts::pre2, + buf[pos+3] << decimation_shifts::pre2, + buf[pos+4] << decimation_shifts::pre2, + buf[pos+5] << decimation_shifts::pre2, + buf[pos+6] << decimation_shifts::pre2, + buf[pos+7] << decimation_shifts::pre2, + &buf2[0]); + + (**it).setReal(buf2[0] >> decimation_shifts::post2); + (**it).setImag(buf2[1] >> decimation_shifts::post2); ++(*it); - // 4: I[2] 5: Q[2] 6: I[3] 7: Q[3] - xreal = (bufI[pos+3] - bufQ[pos+2]) << decimation_shifts::pre2; - yimag = (bufI[pos+2] + bufQ[pos+3]) << decimation_shifts::pre2; - (**it).setReal(xreal >> decimation_shifts::post2); - (**it).setImag(yimag >> decimation_shifts::post2); + (**it).setReal(buf2[2] >> decimation_shifts::post2); + (**it).setImag(buf2[3] >> decimation_shifts::post2); ++(*it); } } -template -void Decimators::decimate4_inf(SampleVector::iterator* it, const T* buf, qint32 len) +// No filtering: bad for Rx OK for signal tracking +//template +//void Decimators::decimate2_sup(SampleVector::iterator* it, const T* buf, qint32 len) +//{ +// StorageType xreal, yimag; +// +// for (int pos = 0; pos < len - 7; pos += 8) +// { +// xreal = (buf[pos+1] - buf[pos+2]) << decimation_shifts::pre2; +// yimag = (- buf[pos+0] - buf[pos+3]) << decimation_shifts::pre2; +// (**it).setReal(xreal >> decimation_shifts::post2); +// (**it).setImag(yimag >> decimation_shifts::post2); +// ++(*it); +// +// xreal = (buf[pos+6] - buf[pos+5]) << decimation_shifts::pre2; +// yimag = (buf[pos+4] + buf[pos+7]) << decimation_shifts::pre2; +// (**it).setReal(xreal >> decimation_shifts::post2); +// (**it).setImag(yimag >> decimation_shifts::post2); +// ++(*it); +// } +//} + +template +void Decimators::decimate4_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal, yimag; + StorageType buf2[8], buf4[4]; - for (int pos = 0; pos < len - 7; pos += 8) - { - xreal = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) << decimation_shifts::pre4; - yimag = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) << decimation_shifts::pre4; - - (**it).setReal(xreal >> decimation_shifts::post4); - (**it).setImag(yimag >> decimation_shifts::post4); - - ++(*it); - } -} - -template -void Decimators::decimate4_inf(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) -{ - AccuType xreal, yimag; - - for (int pos = 0; pos < len - 3; pos += 4) + for (int pos = 0; pos < len - 15; pos += 16) { - xreal = (bufI[pos] - bufQ[pos+1] + bufQ[pos+3] - bufI[pos+2]) << decimation_shifts::pre4; - yimag = (bufQ[pos] - bufQ[pos+2] + bufI[pos+1] - bufI[pos+3]) << decimation_shifts::pre4; + m_decimator2.myDecimateInf( + buf[pos+0] << decimation_shifts::pre4, + buf[pos+1] << decimation_shifts::pre4, + buf[pos+2] << decimation_shifts::pre4, + buf[pos+3] << decimation_shifts::pre4, + buf[pos+4] << decimation_shifts::pre4, + buf[pos+5] << decimation_shifts::pre4, + buf[pos+6] << decimation_shifts::pre4, + buf[pos+7] << decimation_shifts::pre4, + &buf2[0]); - (**it).setReal(xreal >> decimation_shifts::post4); - (**it).setImag(yimag >> decimation_shifts::post4); + m_decimator2.myDecimateInf( + buf[pos+8] << decimation_shifts::pre4, + buf[pos+9] << decimation_shifts::pre4, + buf[pos+10] << decimation_shifts::pre4, + buf[pos+11] << decimation_shifts::pre4, + buf[pos+12] << decimation_shifts::pre4, + buf[pos+13] << decimation_shifts::pre4, + buf[pos+14] << decimation_shifts::pre4, + buf[pos+15] << decimation_shifts::pre4, + &buf2[4]); + m_decimator4.myDecimateSup( + buf2[0], + buf2[1], + buf2[2], + buf2[3], + buf2[4], + buf2[5], + buf2[6], + buf2[7], + &buf4[0]); + + (**it).setReal(buf4[0] >> decimation_shifts::post4); + (**it).setImag(buf4[1] >> decimation_shifts::post4); + ++(*it); + + (**it).setReal(buf4[2] >> decimation_shifts::post4); + (**it).setImag(buf4[3] >> decimation_shifts::post4); ++(*it); } } -template -void Decimators::decimate4_sup(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate4_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - // Sup (USB): - // x y x y x y x y / x -> 1,-2,-5,6 / y -> -0,-3,4,7 - // [ rotate: 1, 0, -2, 3, -5, -4, 6, -7] - // Inf (LSB): - // x y x y x y x y / x -> 0,-3,-4,7 / y -> 1,2,-5,-6 - // [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] - AccuType xreal, yimag; + StorageType buf2[8], buf4[4]; - for (int pos = 0; pos < len - 7; pos += 8) - { - xreal = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) << decimation_shifts::pre4; - yimag = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]) << decimation_shifts::pre4; - - (**it).setReal(xreal >> decimation_shifts::post4); - (**it).setImag(yimag >> decimation_shifts::post4); - - ++(*it); - } -} - -template -void Decimators::decimate4_sup(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) -{ - AccuType xreal, yimag; - - for (int pos = 0; pos < len - 3; pos += 4) + for (int pos = 0; pos < len - 15; pos += 16) { - xreal = (bufQ[pos] - bufI[pos+1] - bufQ[pos+2] + bufI[pos+3]) << decimation_shifts::pre4; - yimag = (- bufI[pos] - bufQ[pos+1] + bufI[pos+2] + bufQ[pos+3]) << decimation_shifts::pre4; + m_decimator2.myDecimateSup( + buf[pos+0] << decimation_shifts::pre4, + buf[pos+1] << decimation_shifts::pre4, + buf[pos+2] << decimation_shifts::pre4, + buf[pos+3] << decimation_shifts::pre4, + buf[pos+4] << decimation_shifts::pre4, + buf[pos+5] << decimation_shifts::pre4, + buf[pos+6] << decimation_shifts::pre4, + buf[pos+7] << decimation_shifts::pre4, + &buf2[0]); - (**it).setReal(xreal >> decimation_shifts::post4); - (**it).setImag(yimag >> decimation_shifts::post4); + m_decimator2.myDecimateSup( + buf[pos+8] << decimation_shifts::pre4, + buf[pos+9] << decimation_shifts::pre4, + buf[pos+10] << decimation_shifts::pre4, + buf[pos+11] << decimation_shifts::pre4, + buf[pos+12] << decimation_shifts::pre4, + buf[pos+13] << decimation_shifts::pre4, + buf[pos+14] << decimation_shifts::pre4, + buf[pos+15] << decimation_shifts::pre4, + &buf2[4]); + m_decimator4.myDecimateInf( + buf2[0], + buf2[1], + buf2[2], + buf2[3], + buf2[4], + buf2[5], + buf2[6], + buf2[7], + &buf4[0]); + + (**it).setReal(buf4[0] >> decimation_shifts::post4); + (**it).setImag(buf4[1] >> decimation_shifts::post4); + ++(*it); + + (**it).setReal(buf4[2] >> decimation_shifts::post4); + (**it).setImag(buf4[3] >> decimation_shifts::post4); ++(*it); } } -template -void Decimators::decimate8_inf(SampleVector::iterator* it, const T* buf, qint32 len) +// No filtering: bad for Rx OK for signal tracking +//template +//void Decimators::decimate4_inf(SampleVector::iterator* it, const T* buf, qint32 len) +//{ +// StorageType xreal, yimag; +// +// for (int pos = 0; pos < len - 7; pos += 8) +// { +// xreal = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) << decimation_shifts::pre4; +// yimag = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) << decimation_shifts::pre4; +// +// (**it).setReal(xreal >> decimation_shifts::post4); +// (**it).setImag(yimag >> decimation_shifts::post4); +// +// ++(*it); +// } +//} + +// No filtering: bad for Rx OK for signal tracking +//template +//void Decimators::decimate4_sup(SampleVector::iterator* it, const T* buf, qint32 len) +//{ +// // Sup (USB): +// // x y x y x y x y / x -> 1,-2,-5,6 / y -> -0,-3,4,7 +// // [ rotate: 1, 0, -2, 3, -5, -4, 6, -7] +// // Inf (LSB): +// // x y x y x y x y / x -> 0,-3,-4,7 / y -> 1,2,-5,-6 +// // [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] +// StorageType xreal, yimag; +// +// for (int pos = 0; pos < len - 7; pos += 8) +// { +// xreal = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) << decimation_shifts::pre4; +// yimag = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]) << decimation_shifts::pre4; +// +// (**it).setReal(xreal >> decimation_shifts::post4); +// (**it).setImag(yimag >> decimation_shifts::post4); +// +// ++(*it); +// } +//} + +template +void Decimators::decimate8_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal[2], yimag[2]; + StorageType buf2[16], buf4[8], buf8[4]; - for (int pos = 0; pos < len - 15; pos += 8) + for (int pos = 0; pos < len - 31; pos += 32) { - xreal[0] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) << decimation_shifts::pre8; - yimag[0] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) << decimation_shifts::pre8; - pos += 8; + m_decimator2.myDecimateInf( + buf[pos+0] << decimation_shifts::pre8, + buf[pos+1] << decimation_shifts::pre8, + buf[pos+2] << decimation_shifts::pre8, + buf[pos+3] << decimation_shifts::pre8, + buf[pos+4] << decimation_shifts::pre8, + buf[pos+5] << decimation_shifts::pre8, + buf[pos+6] << decimation_shifts::pre8, + buf[pos+7] << decimation_shifts::pre8, + &buf2[0]); - xreal[1] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) << decimation_shifts::pre8; - yimag[1] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) << decimation_shifts::pre8; + m_decimator2.myDecimateInf( + buf[pos+8] << decimation_shifts::pre8, + buf[pos+9] << decimation_shifts::pre8, + buf[pos+10] << decimation_shifts::pre8, + buf[pos+11] << decimation_shifts::pre8, + buf[pos+12] << decimation_shifts::pre8, + buf[pos+13] << decimation_shifts::pre8, + buf[pos+14] << decimation_shifts::pre8, + buf[pos+15] << decimation_shifts::pre8, + &buf2[4]); - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimateInf( + buf[pos+16] << decimation_shifts::pre8, + buf[pos+17] << decimation_shifts::pre8, + buf[pos+18] << decimation_shifts::pre8, + buf[pos+19] << decimation_shifts::pre8, + buf[pos+20] << decimation_shifts::pre8, + buf[pos+21] << decimation_shifts::pre8, + buf[pos+22] << decimation_shifts::pre8, + buf[pos+23] << decimation_shifts::pre8, + &buf2[8]); - (**it).setReal(xreal[1] >> decimation_shifts::post8); - (**it).setImag(yimag[1] >> decimation_shifts::post8); + m_decimator2.myDecimateInf( + buf[pos+24] << decimation_shifts::pre8, + buf[pos+25] << decimation_shifts::pre8, + buf[pos+26] << decimation_shifts::pre8, + buf[pos+27] << decimation_shifts::pre8, + buf[pos+28] << decimation_shifts::pre8, + buf[pos+29] << decimation_shifts::pre8, + buf[pos+30] << decimation_shifts::pre8, + buf[pos+31] << decimation_shifts::pre8, + &buf2[12]); - ++(*it); + m_decimator4.myDecimateSup( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateSup( + &buf2[8], + &buf4[4]); + + m_decimator8.myDecimateCen( + &buf4[0], + &buf8[0]); + + (**it).setReal(buf8[0] >> decimation_shifts::post8); + (**it).setImag(buf8[1] >> decimation_shifts::post8); + ++(*it); + + (**it).setReal(buf8[2] >> decimation_shifts::post8); + (**it).setImag(buf8[3] >> decimation_shifts::post8); + ++(*it); } } -template -void Decimators::decimate8_inf(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +template +void Decimators::decimate8_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal[2], yimag[2]; + StorageType buf2[16], buf4[8], buf8[4]; - for (int pos = 0; pos < len - 7; pos += 4) + for (int pos = 0; pos < len - 31; pos += 32) { - xreal[0] = (bufI[pos] - bufQ[pos+1] + bufQ[pos+3] - bufI[pos+2]) << decimation_shifts::pre8; - yimag[0] = (bufQ[pos] - bufQ[pos+2] + bufI[pos+1] - bufI[pos+3]) << decimation_shifts::pre8; - pos += 4; + m_decimator2.myDecimateSup( + buf[pos+0] << decimation_shifts::pre8, + buf[pos+1] << decimation_shifts::pre8, + buf[pos+2] << decimation_shifts::pre8, + buf[pos+3] << decimation_shifts::pre8, + buf[pos+4] << decimation_shifts::pre8, + buf[pos+5] << decimation_shifts::pre8, + buf[pos+6] << decimation_shifts::pre8, + buf[pos+7] << decimation_shifts::pre8, + &buf2[0]); - xreal[1] = (bufI[pos] - bufQ[pos+1] + bufQ[pos+3] - bufI[pos+2]) << decimation_shifts::pre8; - yimag[1] = (bufQ[pos] - bufQ[pos+2] + bufI[pos+1] - bufQ[pos+3]) << decimation_shifts::pre8; + m_decimator2.myDecimateSup( + buf[pos+8] << decimation_shifts::pre8, + buf[pos+9] << decimation_shifts::pre8, + buf[pos+10] << decimation_shifts::pre8, + buf[pos+11] << decimation_shifts::pre8, + buf[pos+12] << decimation_shifts::pre8, + buf[pos+13] << decimation_shifts::pre8, + buf[pos+14] << decimation_shifts::pre8, + buf[pos+15] << decimation_shifts::pre8, + &buf2[4]); - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimateSup( + buf[pos+16] << decimation_shifts::pre8, + buf[pos+17] << decimation_shifts::pre8, + buf[pos+18] << decimation_shifts::pre8, + buf[pos+19] << decimation_shifts::pre8, + buf[pos+20] << decimation_shifts::pre8, + buf[pos+21] << decimation_shifts::pre8, + buf[pos+22] << decimation_shifts::pre8, + buf[pos+23] << decimation_shifts::pre8, + &buf2[8]); - (**it).setReal(xreal[1] >> decimation_shifts::post8); - (**it).setImag(yimag[1] >> decimation_shifts::post8); + m_decimator2.myDecimateSup( + buf[pos+24] << decimation_shifts::pre8, + buf[pos+25] << decimation_shifts::pre8, + buf[pos+26] << decimation_shifts::pre8, + buf[pos+27] << decimation_shifts::pre8, + buf[pos+28] << decimation_shifts::pre8, + buf[pos+29] << decimation_shifts::pre8, + buf[pos+30] << decimation_shifts::pre8, + buf[pos+31] << decimation_shifts::pre8, + &buf2[12]); + m_decimator4.myDecimateInf( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateInf( + &buf2[8], + &buf4[4]); + + m_decimator8.myDecimateCen( + &buf4[0], + &buf8[0]); + + (**it).setReal(buf8[0] >> decimation_shifts::post8); + (**it).setImag(buf8[1] >> decimation_shifts::post8); + ++(*it); + + (**it).setReal(buf8[2] >> decimation_shifts::post8); + (**it).setImag(buf8[3] >> decimation_shifts::post8); ++(*it); } } -template -void Decimators::decimate8_sup(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate16_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal[2], yimag[2]; + StorageType buf2[32], buf4[16], buf8[8], buf16[4]; - for (int pos = 0; pos < len - 15; pos += 8) - { - xreal[0] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) << decimation_shifts::pre8; - yimag[0] = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]) << decimation_shifts::pre8; - pos += 8; - - xreal[1] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) << decimation_shifts::pre8; - yimag[1] = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]) << decimation_shifts::pre8; - - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - - (**it).setReal(xreal[1] >> decimation_shifts::post8); - (**it).setImag(yimag[1] >> decimation_shifts::post8); - - ++(*it); - } -} - -template -void Decimators::decimate8_sup(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) -{ - AccuType xreal[2], yimag[2]; - - for (int pos = 0; pos < len - 7; pos += 4) + for (int pos = 0; pos < len - 63; pos += 64) { - xreal[0] = (bufQ[pos] - bufI[pos+1] - bufQ[pos+2] + bufI[pos+3]) << decimation_shifts::pre8; - yimag[0] = (- bufI[pos] - bufQ[pos+1] + bufI[pos+2] + bufQ[pos+3]) << decimation_shifts::pre8; - pos += 4; + m_decimator2.myDecimateInf( + buf[pos+0] << decimation_shifts::pre16, + buf[pos+1] << decimation_shifts::pre16, + buf[pos+2] << decimation_shifts::pre16, + buf[pos+3] << decimation_shifts::pre16, + buf[pos+4] << decimation_shifts::pre16, + buf[pos+5] << decimation_shifts::pre16, + buf[pos+6] << decimation_shifts::pre16, + buf[pos+7] << decimation_shifts::pre16, + &buf2[0]); - xreal[1] = (bufQ[pos] - bufI[pos+1] - bufQ[pos+2] + bufI[pos+3]) << decimation_shifts::pre8; - yimag[1] = (- bufI[pos] - bufQ[pos+1] + bufI[pos+2] + bufQ[pos+3]) << decimation_shifts::pre8; + m_decimator2.myDecimateInf( + buf[pos+8] << decimation_shifts::pre16, + buf[pos+9] << decimation_shifts::pre16, + buf[pos+10] << decimation_shifts::pre16, + buf[pos+11] << decimation_shifts::pre16, + buf[pos+12] << decimation_shifts::pre16, + buf[pos+13] << decimation_shifts::pre16, + buf[pos+14] << decimation_shifts::pre16, + buf[pos+15] << decimation_shifts::pre16, + &buf2[4]); - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimateInf( + buf[pos+16] << decimation_shifts::pre16, + buf[pos+17] << decimation_shifts::pre16, + buf[pos+18] << decimation_shifts::pre16, + buf[pos+19] << decimation_shifts::pre16, + buf[pos+20] << decimation_shifts::pre16, + buf[pos+21] << decimation_shifts::pre16, + buf[pos+22] << decimation_shifts::pre16, + buf[pos+23] << decimation_shifts::pre16, + &buf2[8]); - (**it).setReal(xreal[1] >> decimation_shifts::post8); - (**it).setImag(yimag[1] >> decimation_shifts::post8); + m_decimator2.myDecimateInf( + buf[pos+24] << decimation_shifts::pre16, + buf[pos+25] << decimation_shifts::pre16, + buf[pos+26] << decimation_shifts::pre16, + buf[pos+27] << decimation_shifts::pre16, + buf[pos+28] << decimation_shifts::pre16, + buf[pos+29] << decimation_shifts::pre16, + buf[pos+30] << decimation_shifts::pre16, + buf[pos+31] << decimation_shifts::pre16, + &buf2[12]); + m_decimator2.myDecimateInf( + buf[pos+32] << decimation_shifts::pre16, + buf[pos+33] << decimation_shifts::pre16, + buf[pos+34] << decimation_shifts::pre16, + buf[pos+35] << decimation_shifts::pre16, + buf[pos+36] << decimation_shifts::pre16, + buf[pos+37] << decimation_shifts::pre16, + buf[pos+38] << decimation_shifts::pre16, + buf[pos+39] << decimation_shifts::pre16, + &buf2[16]); + + m_decimator2.myDecimateInf( + buf[pos+40] << decimation_shifts::pre16, + buf[pos+41] << decimation_shifts::pre16, + buf[pos+42] << decimation_shifts::pre16, + buf[pos+43] << decimation_shifts::pre16, + buf[pos+44] << decimation_shifts::pre16, + buf[pos+45] << decimation_shifts::pre16, + buf[pos+46] << decimation_shifts::pre16, + buf[pos+47] << decimation_shifts::pre16, + &buf2[20]); + + m_decimator2.myDecimateInf( + buf[pos+48] << decimation_shifts::pre16, + buf[pos+49] << decimation_shifts::pre16, + buf[pos+50] << decimation_shifts::pre16, + buf[pos+51] << decimation_shifts::pre16, + buf[pos+52] << decimation_shifts::pre16, + buf[pos+53] << decimation_shifts::pre16, + buf[pos+54] << decimation_shifts::pre16, + buf[pos+55] << decimation_shifts::pre16, + &buf2[24]); + + m_decimator2.myDecimateInf( + buf[pos+56] << decimation_shifts::pre16, + buf[pos+57] << decimation_shifts::pre16, + buf[pos+58] << decimation_shifts::pre16, + buf[pos+59] << decimation_shifts::pre16, + buf[pos+60] << decimation_shifts::pre16, + buf[pos+61] << decimation_shifts::pre16, + buf[pos+62] << decimation_shifts::pre16, + buf[pos+63] << decimation_shifts::pre16, + &buf2[28]); + + m_decimator4.myDecimateSup( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateSup( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateSup( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateSup( + &buf2[24], + &buf4[12]); + + m_decimator8.myDecimateSup( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateSup( + &buf4[8], + &buf8[4]); + + m_decimator16.myDecimateCen( + &buf8[0], + &buf16[0]); + + (**it).setReal(buf16[0] >> decimation_shifts::post16); + (**it).setImag(buf16[1] >> decimation_shifts::post16); + ++(*it); + + (**it).setReal(buf16[2] >> decimation_shifts::post16); + (**it).setImag(buf16[3] >> decimation_shifts::post16); ++(*it); } } -template -void Decimators::decimate16_inf(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate16_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - // Offset tuning: 4x downsample and rotate, then - // downsample 4x more. [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] - AccuType xreal[4], yimag[4]; + StorageType buf2[32], buf4[16], buf8[8], buf16[4]; - for (int pos = 0; pos < len - 31; ) - { - for (int i = 0; i < 4; i++) - { - xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) << decimation_shifts::pre16; - yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) << decimation_shifts::pre16; - pos += 8; - } - - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); - - (**it).setReal(xreal[3] >> decimation_shifts::post16); - (**it).setImag(yimag[3] >> decimation_shifts::post16); - - ++(*it); - } -} - -template -void Decimators::decimate16_inf(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) -{ - // Offset tuning: 4x downsample and rotate, then - // downsample 4x more. [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] - AccuType xreal[4], yimag[4]; - - for (int pos = 0; pos < len - 15; ) + for (int pos = 0; pos < len - 63; pos += 64) { - for (int i = 0; i < 4; i++) - { - xreal[i] = (bufI[pos] - bufI[pos+1] + bufQ[pos+3] - bufI[pos+2]) << decimation_shifts::pre16; - yimag[i] = (bufQ[pos] - bufQ[pos+2] + bufI[pos+1] - bufI[pos+3]) << decimation_shifts::pre16; - pos += 4; - } + m_decimator2.myDecimateSup( + buf[pos+0] << decimation_shifts::pre16, + buf[pos+1] << decimation_shifts::pre16, + buf[pos+2] << decimation_shifts::pre16, + buf[pos+3] << decimation_shifts::pre16, + buf[pos+4] << decimation_shifts::pre16, + buf[pos+5] << decimation_shifts::pre16, + buf[pos+6] << decimation_shifts::pre16, + buf[pos+7] << decimation_shifts::pre16, + &buf2[0]); - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2.myDecimateSup( + buf[pos+8] << decimation_shifts::pre16, + buf[pos+9] << decimation_shifts::pre16, + buf[pos+10] << decimation_shifts::pre16, + buf[pos+11] << decimation_shifts::pre16, + buf[pos+12] << decimation_shifts::pre16, + buf[pos+13] << decimation_shifts::pre16, + buf[pos+14] << decimation_shifts::pre16, + buf[pos+15] << decimation_shifts::pre16, + &buf2[4]); - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator2.myDecimateSup( + buf[pos+16] << decimation_shifts::pre16, + buf[pos+17] << decimation_shifts::pre16, + buf[pos+18] << decimation_shifts::pre16, + buf[pos+19] << decimation_shifts::pre16, + buf[pos+20] << decimation_shifts::pre16, + buf[pos+21] << decimation_shifts::pre16, + buf[pos+22] << decimation_shifts::pre16, + buf[pos+23] << decimation_shifts::pre16, + &buf2[8]); - (**it).setReal(xreal[3] >> decimation_shifts::post16); - (**it).setImag(yimag[3] >> decimation_shifts::post16); + m_decimator2.myDecimateSup( + buf[pos+24] << decimation_shifts::pre16, + buf[pos+25] << decimation_shifts::pre16, + buf[pos+26] << decimation_shifts::pre16, + buf[pos+27] << decimation_shifts::pre16, + buf[pos+28] << decimation_shifts::pre16, + buf[pos+29] << decimation_shifts::pre16, + buf[pos+30] << decimation_shifts::pre16, + buf[pos+31] << decimation_shifts::pre16, + &buf2[12]); + m_decimator2.myDecimateSup( + buf[pos+32] << decimation_shifts::pre16, + buf[pos+33] << decimation_shifts::pre16, + buf[pos+34] << decimation_shifts::pre16, + buf[pos+35] << decimation_shifts::pre16, + buf[pos+36] << decimation_shifts::pre16, + buf[pos+37] << decimation_shifts::pre16, + buf[pos+38] << decimation_shifts::pre16, + buf[pos+39] << decimation_shifts::pre16, + &buf2[16]); + + m_decimator2.myDecimateSup( + buf[pos+40] << decimation_shifts::pre16, + buf[pos+41] << decimation_shifts::pre16, + buf[pos+42] << decimation_shifts::pre16, + buf[pos+43] << decimation_shifts::pre16, + buf[pos+44] << decimation_shifts::pre16, + buf[pos+45] << decimation_shifts::pre16, + buf[pos+46] << decimation_shifts::pre16, + buf[pos+47] << decimation_shifts::pre16, + &buf2[20]); + + m_decimator2.myDecimateSup( + buf[pos+48] << decimation_shifts::pre16, + buf[pos+49] << decimation_shifts::pre16, + buf[pos+50] << decimation_shifts::pre16, + buf[pos+51] << decimation_shifts::pre16, + buf[pos+52] << decimation_shifts::pre16, + buf[pos+53] << decimation_shifts::pre16, + buf[pos+54] << decimation_shifts::pre16, + buf[pos+55] << decimation_shifts::pre16, + &buf2[24]); + + m_decimator2.myDecimateSup( + buf[pos+56] << decimation_shifts::pre16, + buf[pos+57] << decimation_shifts::pre16, + buf[pos+58] << decimation_shifts::pre16, + buf[pos+59] << decimation_shifts::pre16, + buf[pos+60] << decimation_shifts::pre16, + buf[pos+61] << decimation_shifts::pre16, + buf[pos+62] << decimation_shifts::pre16, + buf[pos+63] << decimation_shifts::pre16, + &buf2[28]); + + m_decimator4.myDecimateInf( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateInf( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateInf( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateInf( + &buf2[24], + &buf4[12]); + + m_decimator8.myDecimateInf( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateInf( + &buf4[8], + &buf8[4]); + + m_decimator16.myDecimateCen( + &buf8[0], + &buf16[0]); + + (**it).setReal(buf16[0] >> decimation_shifts::post16); + (**it).setImag(buf16[1] >> decimation_shifts::post16); + ++(*it); + + (**it).setReal(buf16[2] >> decimation_shifts::post16); + (**it).setImag(buf16[3] >> decimation_shifts::post16); ++(*it); } } -template -void Decimators::decimate16_sup(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate32_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - // Offset tuning: 4x downsample and rotate, then - // downsample 4x more. [ rotate: 1, 0, -2, 3, -5, -4, 6, -7] - AccuType xreal[4], yimag[4]; + StorageType buf2[64], buf4[32], buf8[16], buf16[8], buf32[4]; - for (int pos = 0; pos < len - 31; ) - { - for (int i = 0; i < 4; i++) - { - xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) << decimation_shifts::pre16; - yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]) << decimation_shifts::pre16; - pos += 8; - } - - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); - - (**it).setReal(xreal[3] >> decimation_shifts::post16); - (**it).setImag(yimag[3] >> decimation_shifts::post16); - - ++(*it); - } -} - -template -void Decimators::decimate16_sup(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) -{ - // Offset tuning: 4x downsample and rotate, then - // downsample 4x more. [ rotate: 1, 0, -2, 3, -5, -4, 6, -7] - AccuType xreal[4], yimag[4]; - - for (int pos = 0; pos < len - 15; ) + for (int pos = 0; pos < len - 127; pos += 128) { - for (int i = 0; i < 4; i++) - { - xreal[i] = (bufQ[pos+0] - bufI[pos+1] - bufQ[pos+2] + bufI[pos+3]) << decimation_shifts::pre16; - yimag[i] = (bufI[pos+2] + bufQ[pos+3] - bufI[pos+0] - bufQ[pos+1]) << decimation_shifts::pre16; - pos += 4; - } + m_decimator2.myDecimateInf( + buf[pos+0] << decimation_shifts::pre32, + buf[pos+1] << decimation_shifts::pre32, + buf[pos+2] << decimation_shifts::pre32, + buf[pos+3] << decimation_shifts::pre32, + buf[pos+4] << decimation_shifts::pre32, + buf[pos+5] << decimation_shifts::pre32, + buf[pos+6] << decimation_shifts::pre32, + buf[pos+7] << decimation_shifts::pre32, + &buf2[0]); - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2.myDecimateInf( + buf[pos+8] << decimation_shifts::pre32, + buf[pos+9] << decimation_shifts::pre32, + buf[pos+10] << decimation_shifts::pre32, + buf[pos+11] << decimation_shifts::pre32, + buf[pos+12] << decimation_shifts::pre32, + buf[pos+13] << decimation_shifts::pre32, + buf[pos+14] << decimation_shifts::pre32, + buf[pos+15] << decimation_shifts::pre32, + &buf2[4]); - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator2.myDecimateInf( + buf[pos+16] << decimation_shifts::pre32, + buf[pos+17] << decimation_shifts::pre32, + buf[pos+18] << decimation_shifts::pre32, + buf[pos+19] << decimation_shifts::pre32, + buf[pos+20] << decimation_shifts::pre32, + buf[pos+21] << decimation_shifts::pre32, + buf[pos+22] << decimation_shifts::pre32, + buf[pos+23] << decimation_shifts::pre32, + &buf2[8]); - (**it).setReal(xreal[3] >> decimation_shifts::post16); - (**it).setImag(yimag[3] >> decimation_shifts::post16); + m_decimator2.myDecimateInf( + buf[pos+24] << decimation_shifts::pre32, + buf[pos+25] << decimation_shifts::pre32, + buf[pos+26] << decimation_shifts::pre32, + buf[pos+27] << decimation_shifts::pre32, + buf[pos+28] << decimation_shifts::pre32, + buf[pos+29] << decimation_shifts::pre32, + buf[pos+30] << decimation_shifts::pre32, + buf[pos+31] << decimation_shifts::pre32, + &buf2[12]); + m_decimator2.myDecimateInf( + buf[pos+32] << decimation_shifts::pre32, + buf[pos+33] << decimation_shifts::pre32, + buf[pos+34] << decimation_shifts::pre32, + buf[pos+35] << decimation_shifts::pre32, + buf[pos+36] << decimation_shifts::pre32, + buf[pos+37] << decimation_shifts::pre32, + buf[pos+38] << decimation_shifts::pre32, + buf[pos+39] << decimation_shifts::pre32, + &buf2[16]); + + m_decimator2.myDecimateInf( + buf[pos+40] << decimation_shifts::pre32, + buf[pos+41] << decimation_shifts::pre32, + buf[pos+42] << decimation_shifts::pre32, + buf[pos+43] << decimation_shifts::pre32, + buf[pos+44] << decimation_shifts::pre32, + buf[pos+45] << decimation_shifts::pre32, + buf[pos+46] << decimation_shifts::pre32, + buf[pos+47] << decimation_shifts::pre32, + &buf2[20]); + + m_decimator2.myDecimateInf( + buf[pos+48] << decimation_shifts::pre32, + buf[pos+49] << decimation_shifts::pre32, + buf[pos+50] << decimation_shifts::pre32, + buf[pos+51] << decimation_shifts::pre32, + buf[pos+52] << decimation_shifts::pre32, + buf[pos+53] << decimation_shifts::pre32, + buf[pos+54] << decimation_shifts::pre32, + buf[pos+55] << decimation_shifts::pre32, + &buf2[24]); + + m_decimator2.myDecimateInf( + buf[pos+56] << decimation_shifts::pre32, + buf[pos+57] << decimation_shifts::pre32, + buf[pos+58] << decimation_shifts::pre32, + buf[pos+59] << decimation_shifts::pre32, + buf[pos+60] << decimation_shifts::pre32, + buf[pos+61] << decimation_shifts::pre32, + buf[pos+62] << decimation_shifts::pre32, + buf[pos+63] << decimation_shifts::pre32, + &buf2[28]); + + m_decimator2.myDecimateInf( + buf[pos+64] << decimation_shifts::pre32, + buf[pos+65] << decimation_shifts::pre32, + buf[pos+66] << decimation_shifts::pre32, + buf[pos+67] << decimation_shifts::pre32, + buf[pos+68] << decimation_shifts::pre32, + buf[pos+69] << decimation_shifts::pre32, + buf[pos+70] << decimation_shifts::pre32, + buf[pos+71] << decimation_shifts::pre32, + &buf2[32]); + + m_decimator2.myDecimateInf( + buf[pos+72] << decimation_shifts::pre32, + buf[pos+73] << decimation_shifts::pre32, + buf[pos+74] << decimation_shifts::pre32, + buf[pos+75] << decimation_shifts::pre32, + buf[pos+76] << decimation_shifts::pre32, + buf[pos+77] << decimation_shifts::pre32, + buf[pos+78] << decimation_shifts::pre32, + buf[pos+79] << decimation_shifts::pre32, + &buf2[36]); + + m_decimator2.myDecimateInf( + buf[pos+80] << decimation_shifts::pre32, + buf[pos+81] << decimation_shifts::pre32, + buf[pos+82] << decimation_shifts::pre32, + buf[pos+83] << decimation_shifts::pre32, + buf[pos+84] << decimation_shifts::pre32, + buf[pos+85] << decimation_shifts::pre32, + buf[pos+86] << decimation_shifts::pre32, + buf[pos+87] << decimation_shifts::pre32, + &buf2[40]); + + m_decimator2.myDecimateInf( + buf[pos+88] << decimation_shifts::pre32, + buf[pos+89] << decimation_shifts::pre32, + buf[pos+90] << decimation_shifts::pre32, + buf[pos+91] << decimation_shifts::pre32, + buf[pos+92] << decimation_shifts::pre32, + buf[pos+93] << decimation_shifts::pre32, + buf[pos+94] << decimation_shifts::pre32, + buf[pos+95] << decimation_shifts::pre32, + &buf2[44]); + + m_decimator2.myDecimateInf( + buf[pos+96] << decimation_shifts::pre32, + buf[pos+97] << decimation_shifts::pre32, + buf[pos+98] << decimation_shifts::pre32, + buf[pos+99] << decimation_shifts::pre32, + buf[pos+100] << decimation_shifts::pre32, + buf[pos+101] << decimation_shifts::pre32, + buf[pos+102] << decimation_shifts::pre32, + buf[pos+103] << decimation_shifts::pre32, + &buf2[48]); + + m_decimator2.myDecimateInf( + buf[pos+104] << decimation_shifts::pre32, + buf[pos+105] << decimation_shifts::pre32, + buf[pos+106] << decimation_shifts::pre32, + buf[pos+107] << decimation_shifts::pre32, + buf[pos+108] << decimation_shifts::pre32, + buf[pos+109] << decimation_shifts::pre32, + buf[pos+110] << decimation_shifts::pre32, + buf[pos+111] << decimation_shifts::pre32, + &buf2[52]); + + m_decimator2.myDecimateInf( + buf[pos+112] << decimation_shifts::pre32, + buf[pos+113] << decimation_shifts::pre32, + buf[pos+114] << decimation_shifts::pre32, + buf[pos+115] << decimation_shifts::pre32, + buf[pos+116] << decimation_shifts::pre32, + buf[pos+117] << decimation_shifts::pre32, + buf[pos+118] << decimation_shifts::pre32, + buf[pos+119] << decimation_shifts::pre32, + &buf2[56]); + + m_decimator2.myDecimateInf( + buf[pos+120] << decimation_shifts::pre32, + buf[pos+121] << decimation_shifts::pre32, + buf[pos+122] << decimation_shifts::pre32, + buf[pos+123] << decimation_shifts::pre32, + buf[pos+124] << decimation_shifts::pre32, + buf[pos+125] << decimation_shifts::pre32, + buf[pos+126] << decimation_shifts::pre32, + buf[pos+127] << decimation_shifts::pre32, + &buf2[60]); + + m_decimator4.myDecimateSup( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateSup( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateSup( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateSup( + &buf2[24], + &buf4[12]); + + m_decimator4.myDecimateSup( + &buf2[32], + &buf4[16]); + + m_decimator4.myDecimateSup( + &buf2[40], + &buf4[20]); + + m_decimator4.myDecimateSup( + &buf2[48], + &buf4[24]); + + m_decimator4.myDecimateSup( + &buf2[56], + &buf4[28]); + + m_decimator8.myDecimateSup( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateSup( + &buf4[8], + &buf8[4]); + + m_decimator8.myDecimateSup( + &buf4[16], + &buf8[8]); + + m_decimator8.myDecimateSup( + &buf4[24], + &buf8[12]); + + m_decimator16.myDecimateSup( + &buf8[0], + &buf16[0]); + + m_decimator16.myDecimateSup( + &buf8[8], + &buf16[4]); + + m_decimator32.myDecimateCen( + &buf16[0], + &buf32[0]); + + (**it).setReal(buf32[0] >> decimation_shifts::post32); + (**it).setImag(buf32[1] >> decimation_shifts::post32); + ++(*it); + + (**it).setReal(buf32[2] >> decimation_shifts::post32); + (**it).setImag(buf32[3] >> decimation_shifts::post32); ++(*it); } } -template -void Decimators::decimate32_inf(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate32_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal[8], yimag[8]; + StorageType buf2[64], buf4[32], buf8[16], buf16[8], buf32[4]; - for (int pos = 0; pos < len - 63; ) - { - for (int i = 0; i < 8; i++) - { - xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) << decimation_shifts::pre32; - yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) << decimation_shifts::pre32; - pos += 8; - } - - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); - m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); - - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); - m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); - - m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); - - (**it).setReal(xreal[7] >> decimation_shifts::post32); - (**it).setImag(yimag[7] >> decimation_shifts::post32); - - ++(*it); - } -} - -template -void Decimators::decimate32_inf(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) -{ - AccuType xreal[8], yimag[8]; - - for (int pos = 0; pos < len - 31; ) + for (int pos = 0; pos < len - 127; pos += 128) { - for (int i = 0; i < 8; i++) - { - xreal[i] = (bufI[pos+0] - bufQ[pos+1] + bufQ[pos+3] - bufI[pos+2]) << decimation_shifts::pre32; - yimag[i] = (bufQ[pos+0] - bufQ[pos+2] + bufI[pos+1] - bufI[pos+3]) << decimation_shifts::pre32; - pos += 4; - } + m_decimator2.myDecimateSup( + buf[pos+0] << decimation_shifts::pre32, + buf[pos+1] << decimation_shifts::pre32, + buf[pos+2] << decimation_shifts::pre32, + buf[pos+3] << decimation_shifts::pre32, + buf[pos+4] << decimation_shifts::pre32, + buf[pos+5] << decimation_shifts::pre32, + buf[pos+6] << decimation_shifts::pre32, + buf[pos+7] << decimation_shifts::pre32, + &buf2[0]); - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); - m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + m_decimator2.myDecimateSup( + buf[pos+8] << decimation_shifts::pre32, + buf[pos+9] << decimation_shifts::pre32, + buf[pos+10] << decimation_shifts::pre32, + buf[pos+11] << decimation_shifts::pre32, + buf[pos+12] << decimation_shifts::pre32, + buf[pos+13] << decimation_shifts::pre32, + buf[pos+14] << decimation_shifts::pre32, + buf[pos+15] << decimation_shifts::pre32, + &buf2[4]); - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); - m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + m_decimator2.myDecimateSup( + buf[pos+16] << decimation_shifts::pre32, + buf[pos+17] << decimation_shifts::pre32, + buf[pos+18] << decimation_shifts::pre32, + buf[pos+19] << decimation_shifts::pre32, + buf[pos+20] << decimation_shifts::pre32, + buf[pos+21] << decimation_shifts::pre32, + buf[pos+22] << decimation_shifts::pre32, + buf[pos+23] << decimation_shifts::pre32, + &buf2[8]); - m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + m_decimator2.myDecimateSup( + buf[pos+24] << decimation_shifts::pre32, + buf[pos+25] << decimation_shifts::pre32, + buf[pos+26] << decimation_shifts::pre32, + buf[pos+27] << decimation_shifts::pre32, + buf[pos+28] << decimation_shifts::pre32, + buf[pos+29] << decimation_shifts::pre32, + buf[pos+30] << decimation_shifts::pre32, + buf[pos+31] << decimation_shifts::pre32, + &buf2[12]); - (**it).setReal(xreal[7] >> decimation_shifts::post32); - (**it).setImag(yimag[7] >> decimation_shifts::post32); + m_decimator2.myDecimateSup( + buf[pos+32] << decimation_shifts::pre32, + buf[pos+33] << decimation_shifts::pre32, + buf[pos+34] << decimation_shifts::pre32, + buf[pos+35] << decimation_shifts::pre32, + buf[pos+36] << decimation_shifts::pre32, + buf[pos+37] << decimation_shifts::pre32, + buf[pos+38] << decimation_shifts::pre32, + buf[pos+39] << decimation_shifts::pre32, + &buf2[16]); + m_decimator2.myDecimateSup( + buf[pos+40] << decimation_shifts::pre32, + buf[pos+41] << decimation_shifts::pre32, + buf[pos+42] << decimation_shifts::pre32, + buf[pos+43] << decimation_shifts::pre32, + buf[pos+44] << decimation_shifts::pre32, + buf[pos+45] << decimation_shifts::pre32, + buf[pos+46] << decimation_shifts::pre32, + buf[pos+47] << decimation_shifts::pre32, + &buf2[20]); + + m_decimator2.myDecimateSup( + buf[pos+48] << decimation_shifts::pre32, + buf[pos+49] << decimation_shifts::pre32, + buf[pos+50] << decimation_shifts::pre32, + buf[pos+51] << decimation_shifts::pre32, + buf[pos+52] << decimation_shifts::pre32, + buf[pos+53] << decimation_shifts::pre32, + buf[pos+54] << decimation_shifts::pre32, + buf[pos+55] << decimation_shifts::pre32, + &buf2[24]); + + m_decimator2.myDecimateSup( + buf[pos+56] << decimation_shifts::pre32, + buf[pos+57] << decimation_shifts::pre32, + buf[pos+58] << decimation_shifts::pre32, + buf[pos+59] << decimation_shifts::pre32, + buf[pos+60] << decimation_shifts::pre32, + buf[pos+61] << decimation_shifts::pre32, + buf[pos+62] << decimation_shifts::pre32, + buf[pos+63] << decimation_shifts::pre32, + &buf2[28]); + + m_decimator2.myDecimateSup( + buf[pos+64] << decimation_shifts::pre32, + buf[pos+65] << decimation_shifts::pre32, + buf[pos+66] << decimation_shifts::pre32, + buf[pos+67] << decimation_shifts::pre32, + buf[pos+68] << decimation_shifts::pre32, + buf[pos+69] << decimation_shifts::pre32, + buf[pos+70] << decimation_shifts::pre32, + buf[pos+71] << decimation_shifts::pre32, + &buf2[32]); + + m_decimator2.myDecimateSup( + buf[pos+72] << decimation_shifts::pre32, + buf[pos+73] << decimation_shifts::pre32, + buf[pos+74] << decimation_shifts::pre32, + buf[pos+75] << decimation_shifts::pre32, + buf[pos+76] << decimation_shifts::pre32, + buf[pos+77] << decimation_shifts::pre32, + buf[pos+78] << decimation_shifts::pre32, + buf[pos+79] << decimation_shifts::pre32, + &buf2[36]); + + m_decimator2.myDecimateSup( + buf[pos+80] << decimation_shifts::pre32, + buf[pos+81] << decimation_shifts::pre32, + buf[pos+82] << decimation_shifts::pre32, + buf[pos+83] << decimation_shifts::pre32, + buf[pos+84] << decimation_shifts::pre32, + buf[pos+85] << decimation_shifts::pre32, + buf[pos+86] << decimation_shifts::pre32, + buf[pos+87] << decimation_shifts::pre32, + &buf2[40]); + + m_decimator2.myDecimateSup( + buf[pos+88] << decimation_shifts::pre32, + buf[pos+89] << decimation_shifts::pre32, + buf[pos+90] << decimation_shifts::pre32, + buf[pos+91] << decimation_shifts::pre32, + buf[pos+92] << decimation_shifts::pre32, + buf[pos+93] << decimation_shifts::pre32, + buf[pos+94] << decimation_shifts::pre32, + buf[pos+95] << decimation_shifts::pre32, + &buf2[44]); + + m_decimator2.myDecimateSup( + buf[pos+96] << decimation_shifts::pre32, + buf[pos+97] << decimation_shifts::pre32, + buf[pos+98] << decimation_shifts::pre32, + buf[pos+99] << decimation_shifts::pre32, + buf[pos+100] << decimation_shifts::pre32, + buf[pos+101] << decimation_shifts::pre32, + buf[pos+102] << decimation_shifts::pre32, + buf[pos+103] << decimation_shifts::pre32, + &buf2[48]); + + m_decimator2.myDecimateSup( + buf[pos+104] << decimation_shifts::pre32, + buf[pos+105] << decimation_shifts::pre32, + buf[pos+106] << decimation_shifts::pre32, + buf[pos+107] << decimation_shifts::pre32, + buf[pos+108] << decimation_shifts::pre32, + buf[pos+109] << decimation_shifts::pre32, + buf[pos+110] << decimation_shifts::pre32, + buf[pos+111] << decimation_shifts::pre32, + &buf2[52]); + + m_decimator2.myDecimateSup( + buf[pos+112] << decimation_shifts::pre32, + buf[pos+113] << decimation_shifts::pre32, + buf[pos+114] << decimation_shifts::pre32, + buf[pos+115] << decimation_shifts::pre32, + buf[pos+116] << decimation_shifts::pre32, + buf[pos+117] << decimation_shifts::pre32, + buf[pos+118] << decimation_shifts::pre32, + buf[pos+119] << decimation_shifts::pre32, + &buf2[56]); + + m_decimator2.myDecimateSup( + buf[pos+120] << decimation_shifts::pre32, + buf[pos+121] << decimation_shifts::pre32, + buf[pos+122] << decimation_shifts::pre32, + buf[pos+123] << decimation_shifts::pre32, + buf[pos+124] << decimation_shifts::pre32, + buf[pos+125] << decimation_shifts::pre32, + buf[pos+126] << decimation_shifts::pre32, + buf[pos+127] << decimation_shifts::pre32, + &buf2[60]); + + m_decimator4.myDecimateInf( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateInf( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateInf( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateInf( + &buf2[24], + &buf4[12]); + + m_decimator4.myDecimateInf( + &buf2[32], + &buf4[16]); + + m_decimator4.myDecimateInf( + &buf2[40], + &buf4[20]); + + m_decimator4.myDecimateInf( + &buf2[48], + &buf4[24]); + + m_decimator4.myDecimateInf( + &buf2[56], + &buf4[28]); + + m_decimator8.myDecimateInf( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateInf( + &buf4[8], + &buf8[4]); + + m_decimator8.myDecimateInf( + &buf4[16], + &buf8[8]); + + m_decimator8.myDecimateInf( + &buf4[24], + &buf8[12]); + + m_decimator16.myDecimateInf( + &buf8[0], + &buf16[0]); + + m_decimator16.myDecimateInf( + &buf8[8], + &buf16[4]); + + m_decimator32.myDecimateCen( + &buf16[0], + &buf32[0]); + + (**it).setReal(buf32[0] >> decimation_shifts::post32); + (**it).setImag(buf32[1] >> decimation_shifts::post32); + ++(*it); + + (**it).setReal(buf32[2] >> decimation_shifts::post32); + (**it).setImag(buf32[3] >> decimation_shifts::post32); ++(*it); } } -template -void Decimators::decimate32_sup(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate64_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal[8], yimag[8]; + StorageType buf2[128], buf4[64], buf8[32], buf16[16], buf32[8], buf64[4]; - for (int pos = 0; pos < len - 63; ) - { - for (int i = 0; i < 8; i++) - { - xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) << decimation_shifts::pre32; - yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]) << decimation_shifts::pre32; - pos += 8; - } - - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); - m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); - - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); - m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); - - m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); - - (**it).setReal(xreal[7] >> decimation_shifts::post32); - (**it).setImag(yimag[7] >> decimation_shifts::post32); - - ++(*it); - } -} - -template -void Decimators::decimate32_sup(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) -{ - AccuType xreal[8], yimag[8]; - - for (int pos = 0; pos < len - 31; ) + for (int pos = 0; pos < len - 255; pos += 256) { - for (int i = 0; i < 8; i++) - { - xreal[i] = (bufQ[pos+0] - bufI[pos+1] - bufQ[pos+2] + bufI[pos+3]) << decimation_shifts::pre32; - yimag[i] = (bufI[pos+2] + bufQ[pos+3] - bufI[pos+0] - bufQ[pos+1]) << decimation_shifts::pre32; - pos += 4; - } + m_decimator2.myDecimateInf( + buf[pos+0] << decimation_shifts::pre64, + buf[pos+1] << decimation_shifts::pre64, + buf[pos+2] << decimation_shifts::pre64, + buf[pos+3] << decimation_shifts::pre64, + buf[pos+4] << decimation_shifts::pre64, + buf[pos+5] << decimation_shifts::pre64, + buf[pos+6] << decimation_shifts::pre64, + buf[pos+7] << decimation_shifts::pre64, + &buf2[0]); - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); - m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + m_decimator2.myDecimateInf( + buf[pos+8] << decimation_shifts::pre64, + buf[pos+9] << decimation_shifts::pre64, + buf[pos+10] << decimation_shifts::pre64, + buf[pos+11] << decimation_shifts::pre64, + buf[pos+12] << decimation_shifts::pre64, + buf[pos+13] << decimation_shifts::pre64, + buf[pos+14] << decimation_shifts::pre64, + buf[pos+15] << decimation_shifts::pre64, + &buf2[4]); - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); - m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + m_decimator2.myDecimateInf( + buf[pos+16] << decimation_shifts::pre64, + buf[pos+17] << decimation_shifts::pre64, + buf[pos+18] << decimation_shifts::pre64, + buf[pos+19] << decimation_shifts::pre64, + buf[pos+20] << decimation_shifts::pre64, + buf[pos+21] << decimation_shifts::pre64, + buf[pos+22] << decimation_shifts::pre64, + buf[pos+23] << decimation_shifts::pre64, + &buf2[8]); - m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + m_decimator2.myDecimateInf( + buf[pos+24] << decimation_shifts::pre64, + buf[pos+25] << decimation_shifts::pre64, + buf[pos+26] << decimation_shifts::pre64, + buf[pos+27] << decimation_shifts::pre64, + buf[pos+28] << decimation_shifts::pre64, + buf[pos+29] << decimation_shifts::pre64, + buf[pos+30] << decimation_shifts::pre64, + buf[pos+31] << decimation_shifts::pre64, + &buf2[12]); - (**it).setReal(xreal[7] >> decimation_shifts::post32); - (**it).setImag(yimag[7] >> decimation_shifts::post32); + m_decimator2.myDecimateInf( + buf[pos+32] << decimation_shifts::pre64, + buf[pos+33] << decimation_shifts::pre64, + buf[pos+34] << decimation_shifts::pre64, + buf[pos+35] << decimation_shifts::pre64, + buf[pos+36] << decimation_shifts::pre64, + buf[pos+37] << decimation_shifts::pre64, + buf[pos+38] << decimation_shifts::pre64, + buf[pos+39] << decimation_shifts::pre64, + &buf2[16]); + m_decimator2.myDecimateInf( + buf[pos+40] << decimation_shifts::pre64, + buf[pos+41] << decimation_shifts::pre64, + buf[pos+42] << decimation_shifts::pre64, + buf[pos+43] << decimation_shifts::pre64, + buf[pos+44] << decimation_shifts::pre64, + buf[pos+45] << decimation_shifts::pre64, + buf[pos+46] << decimation_shifts::pre64, + buf[pos+47] << decimation_shifts::pre64, + &buf2[20]); + + m_decimator2.myDecimateInf( + buf[pos+48] << decimation_shifts::pre64, + buf[pos+49] << decimation_shifts::pre64, + buf[pos+50] << decimation_shifts::pre64, + buf[pos+51] << decimation_shifts::pre64, + buf[pos+52] << decimation_shifts::pre64, + buf[pos+53] << decimation_shifts::pre64, + buf[pos+54] << decimation_shifts::pre64, + buf[pos+55] << decimation_shifts::pre64, + &buf2[24]); + + m_decimator2.myDecimateInf( + buf[pos+56] << decimation_shifts::pre64, + buf[pos+57] << decimation_shifts::pre64, + buf[pos+58] << decimation_shifts::pre64, + buf[pos+59] << decimation_shifts::pre64, + buf[pos+60] << decimation_shifts::pre64, + buf[pos+61] << decimation_shifts::pre64, + buf[pos+62] << decimation_shifts::pre64, + buf[pos+63] << decimation_shifts::pre64, + &buf2[28]); + + m_decimator2.myDecimateInf( + buf[pos+64] << decimation_shifts::pre64, + buf[pos+65] << decimation_shifts::pre64, + buf[pos+66] << decimation_shifts::pre64, + buf[pos+67] << decimation_shifts::pre64, + buf[pos+68] << decimation_shifts::pre64, + buf[pos+69] << decimation_shifts::pre64, + buf[pos+70] << decimation_shifts::pre64, + buf[pos+71] << decimation_shifts::pre64, + &buf2[32]); + + m_decimator2.myDecimateInf( + buf[pos+72] << decimation_shifts::pre64, + buf[pos+73] << decimation_shifts::pre64, + buf[pos+74] << decimation_shifts::pre64, + buf[pos+75] << decimation_shifts::pre64, + buf[pos+76] << decimation_shifts::pre64, + buf[pos+77] << decimation_shifts::pre64, + buf[pos+78] << decimation_shifts::pre64, + buf[pos+79] << decimation_shifts::pre64, + &buf2[36]); + + m_decimator2.myDecimateInf( + buf[pos+80] << decimation_shifts::pre64, + buf[pos+81] << decimation_shifts::pre64, + buf[pos+82] << decimation_shifts::pre64, + buf[pos+83] << decimation_shifts::pre64, + buf[pos+84] << decimation_shifts::pre64, + buf[pos+85] << decimation_shifts::pre64, + buf[pos+86] << decimation_shifts::pre64, + buf[pos+87] << decimation_shifts::pre64, + &buf2[40]); + + m_decimator2.myDecimateInf( + buf[pos+88] << decimation_shifts::pre64, + buf[pos+89] << decimation_shifts::pre64, + buf[pos+90] << decimation_shifts::pre64, + buf[pos+91] << decimation_shifts::pre64, + buf[pos+92] << decimation_shifts::pre64, + buf[pos+93] << decimation_shifts::pre64, + buf[pos+94] << decimation_shifts::pre64, + buf[pos+95] << decimation_shifts::pre64, + &buf2[44]); + + m_decimator2.myDecimateInf( + buf[pos+96] << decimation_shifts::pre64, + buf[pos+97] << decimation_shifts::pre64, + buf[pos+98] << decimation_shifts::pre64, + buf[pos+99] << decimation_shifts::pre64, + buf[pos+100] << decimation_shifts::pre64, + buf[pos+101] << decimation_shifts::pre64, + buf[pos+102] << decimation_shifts::pre64, + buf[pos+103] << decimation_shifts::pre64, + &buf2[48]); + + m_decimator2.myDecimateInf( + buf[pos+104] << decimation_shifts::pre64, + buf[pos+105] << decimation_shifts::pre64, + buf[pos+106] << decimation_shifts::pre64, + buf[pos+107] << decimation_shifts::pre64, + buf[pos+108] << decimation_shifts::pre64, + buf[pos+109] << decimation_shifts::pre64, + buf[pos+110] << decimation_shifts::pre64, + buf[pos+111] << decimation_shifts::pre64, + &buf2[52]); + + m_decimator2.myDecimateInf( + buf[pos+112] << decimation_shifts::pre64, + buf[pos+113] << decimation_shifts::pre64, + buf[pos+114] << decimation_shifts::pre64, + buf[pos+115] << decimation_shifts::pre64, + buf[pos+116] << decimation_shifts::pre64, + buf[pos+117] << decimation_shifts::pre64, + buf[pos+118] << decimation_shifts::pre64, + buf[pos+119] << decimation_shifts::pre64, + &buf2[56]); + + m_decimator2.myDecimateInf( + buf[pos+120] << decimation_shifts::pre64, + buf[pos+121] << decimation_shifts::pre64, + buf[pos+122] << decimation_shifts::pre64, + buf[pos+123] << decimation_shifts::pre64, + buf[pos+124] << decimation_shifts::pre64, + buf[pos+125] << decimation_shifts::pre64, + buf[pos+126] << decimation_shifts::pre64, + buf[pos+127] << decimation_shifts::pre64, + &buf2[60]); + + m_decimator2.myDecimateInf( + buf[pos+128] << decimation_shifts::pre64, + buf[pos+129] << decimation_shifts::pre64, + buf[pos+130] << decimation_shifts::pre64, + buf[pos+131] << decimation_shifts::pre64, + buf[pos+132] << decimation_shifts::pre64, + buf[pos+133] << decimation_shifts::pre64, + buf[pos+134] << decimation_shifts::pre64, + buf[pos+135] << decimation_shifts::pre64, + &buf2[64]); + + m_decimator2.myDecimateInf( + buf[pos+136] << decimation_shifts::pre64, + buf[pos+137] << decimation_shifts::pre64, + buf[pos+138] << decimation_shifts::pre64, + buf[pos+139] << decimation_shifts::pre64, + buf[pos+140] << decimation_shifts::pre64, + buf[pos+141] << decimation_shifts::pre64, + buf[pos+142] << decimation_shifts::pre64, + buf[pos+143] << decimation_shifts::pre64, + &buf2[68]); + + m_decimator2.myDecimateInf( + buf[pos+144] << decimation_shifts::pre64, + buf[pos+145] << decimation_shifts::pre64, + buf[pos+146] << decimation_shifts::pre64, + buf[pos+147] << decimation_shifts::pre64, + buf[pos+148] << decimation_shifts::pre64, + buf[pos+149] << decimation_shifts::pre64, + buf[pos+150] << decimation_shifts::pre64, + buf[pos+151] << decimation_shifts::pre64, + &buf2[72]); + + m_decimator2.myDecimateInf( + buf[pos+152] << decimation_shifts::pre64, + buf[pos+153] << decimation_shifts::pre64, + buf[pos+154] << decimation_shifts::pre64, + buf[pos+155] << decimation_shifts::pre64, + buf[pos+156] << decimation_shifts::pre64, + buf[pos+157] << decimation_shifts::pre64, + buf[pos+158] << decimation_shifts::pre64, + buf[pos+159] << decimation_shifts::pre64, + &buf2[76]); + + m_decimator2.myDecimateInf( + buf[pos+160] << decimation_shifts::pre64, + buf[pos+161] << decimation_shifts::pre64, + buf[pos+162] << decimation_shifts::pre64, + buf[pos+163] << decimation_shifts::pre64, + buf[pos+164] << decimation_shifts::pre64, + buf[pos+165] << decimation_shifts::pre64, + buf[pos+166] << decimation_shifts::pre64, + buf[pos+167] << decimation_shifts::pre64, + &buf2[80]); + + m_decimator2.myDecimateInf( + buf[pos+168] << decimation_shifts::pre64, + buf[pos+169] << decimation_shifts::pre64, + buf[pos+170] << decimation_shifts::pre64, + buf[pos+171] << decimation_shifts::pre64, + buf[pos+172] << decimation_shifts::pre64, + buf[pos+173] << decimation_shifts::pre64, + buf[pos+174] << decimation_shifts::pre64, + buf[pos+175] << decimation_shifts::pre64, + &buf2[84]); + + m_decimator2.myDecimateInf( + buf[pos+176] << decimation_shifts::pre64, + buf[pos+177] << decimation_shifts::pre64, + buf[pos+178] << decimation_shifts::pre64, + buf[pos+179] << decimation_shifts::pre64, + buf[pos+180] << decimation_shifts::pre64, + buf[pos+181] << decimation_shifts::pre64, + buf[pos+182] << decimation_shifts::pre64, + buf[pos+183] << decimation_shifts::pre64, + &buf2[88]); + + m_decimator2.myDecimateInf( + buf[pos+184] << decimation_shifts::pre64, + buf[pos+185] << decimation_shifts::pre64, + buf[pos+186] << decimation_shifts::pre64, + buf[pos+187] << decimation_shifts::pre64, + buf[pos+188] << decimation_shifts::pre64, + buf[pos+189] << decimation_shifts::pre64, + buf[pos+190] << decimation_shifts::pre64, + buf[pos+191] << decimation_shifts::pre64, + &buf2[92]); + + m_decimator2.myDecimateInf( + buf[pos+192] << decimation_shifts::pre64, + buf[pos+193] << decimation_shifts::pre64, + buf[pos+194] << decimation_shifts::pre64, + buf[pos+195] << decimation_shifts::pre64, + buf[pos+196] << decimation_shifts::pre64, + buf[pos+197] << decimation_shifts::pre64, + buf[pos+198] << decimation_shifts::pre64, + buf[pos+199] << decimation_shifts::pre64, + &buf2[96]); + + m_decimator2.myDecimateInf( + buf[pos+200] << decimation_shifts::pre64, + buf[pos+201] << decimation_shifts::pre64, + buf[pos+202] << decimation_shifts::pre64, + buf[pos+203] << decimation_shifts::pre64, + buf[pos+204] << decimation_shifts::pre64, + buf[pos+205] << decimation_shifts::pre64, + buf[pos+206] << decimation_shifts::pre64, + buf[pos+207] << decimation_shifts::pre64, + &buf2[100]); + + m_decimator2.myDecimateInf( + buf[pos+208] << decimation_shifts::pre64, + buf[pos+209] << decimation_shifts::pre64, + buf[pos+210] << decimation_shifts::pre64, + buf[pos+211] << decimation_shifts::pre64, + buf[pos+212] << decimation_shifts::pre64, + buf[pos+213] << decimation_shifts::pre64, + buf[pos+214] << decimation_shifts::pre64, + buf[pos+215] << decimation_shifts::pre64, + &buf2[104]); + + m_decimator2.myDecimateInf( + buf[pos+216] << decimation_shifts::pre64, + buf[pos+217] << decimation_shifts::pre64, + buf[pos+218] << decimation_shifts::pre64, + buf[pos+219] << decimation_shifts::pre64, + buf[pos+220] << decimation_shifts::pre64, + buf[pos+221] << decimation_shifts::pre64, + buf[pos+222] << decimation_shifts::pre64, + buf[pos+223] << decimation_shifts::pre64, + &buf2[108]); + + m_decimator2.myDecimateInf( + buf[pos+224] << decimation_shifts::pre64, + buf[pos+225] << decimation_shifts::pre64, + buf[pos+226] << decimation_shifts::pre64, + buf[pos+227] << decimation_shifts::pre64, + buf[pos+228] << decimation_shifts::pre64, + buf[pos+229] << decimation_shifts::pre64, + buf[pos+230] << decimation_shifts::pre64, + buf[pos+231] << decimation_shifts::pre64, + &buf2[112]); + + m_decimator2.myDecimateInf( + buf[pos+232] << decimation_shifts::pre64, + buf[pos+233] << decimation_shifts::pre64, + buf[pos+234] << decimation_shifts::pre64, + buf[pos+235] << decimation_shifts::pre64, + buf[pos+236] << decimation_shifts::pre64, + buf[pos+237] << decimation_shifts::pre64, + buf[pos+238] << decimation_shifts::pre64, + buf[pos+239] << decimation_shifts::pre64, + &buf2[116]); + + m_decimator2.myDecimateInf( + buf[pos+240] << decimation_shifts::pre64, + buf[pos+241] << decimation_shifts::pre64, + buf[pos+242] << decimation_shifts::pre64, + buf[pos+243] << decimation_shifts::pre64, + buf[pos+244] << decimation_shifts::pre64, + buf[pos+245] << decimation_shifts::pre64, + buf[pos+246] << decimation_shifts::pre64, + buf[pos+247] << decimation_shifts::pre64, + &buf2[120]); + + m_decimator2.myDecimateInf( + buf[pos+248] << decimation_shifts::pre64, + buf[pos+249] << decimation_shifts::pre64, + buf[pos+250] << decimation_shifts::pre64, + buf[pos+251] << decimation_shifts::pre64, + buf[pos+252] << decimation_shifts::pre64, + buf[pos+253] << decimation_shifts::pre64, + buf[pos+254] << decimation_shifts::pre64, + buf[pos+255] << decimation_shifts::pre64, + &buf2[124]); + + m_decimator4.myDecimateSup( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateSup( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateSup( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateSup( + &buf2[24], + &buf4[12]); + + m_decimator4.myDecimateSup( + &buf2[32], + &buf4[16]); + + m_decimator4.myDecimateSup( + &buf2[40], + &buf4[20]); + + m_decimator4.myDecimateSup( + &buf2[48], + &buf4[24]); + + m_decimator4.myDecimateSup( + &buf2[56], + &buf4[28]); + + m_decimator4.myDecimateSup( + &buf2[64], + &buf4[32]); + + m_decimator4.myDecimateSup( + &buf2[72], + &buf4[36]); + + m_decimator4.myDecimateSup( + &buf2[80], + &buf4[40]); + + m_decimator4.myDecimateSup( + &buf2[88], + &buf4[44]); + + m_decimator4.myDecimateSup( + &buf2[96], + &buf4[48]); + + m_decimator4.myDecimateSup( + &buf2[104], + &buf4[52]); + + m_decimator4.myDecimateSup( + &buf2[112], + &buf4[56]); + + m_decimator4.myDecimateSup( + &buf2[120], + &buf4[60]); + + m_decimator8.myDecimateSup( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateSup( + &buf4[8], + &buf8[4]); + + m_decimator8.myDecimateSup( + &buf4[16], + &buf8[8]); + + m_decimator8.myDecimateSup( + &buf4[24], + &buf8[12]); + + m_decimator8.myDecimateSup( + &buf4[32], + &buf8[16]); + + m_decimator8.myDecimateSup( + &buf4[40], + &buf8[20]); + + m_decimator8.myDecimateSup( + &buf4[48], + &buf8[24]); + + m_decimator8.myDecimateSup( + &buf4[56], + &buf8[28]); + + m_decimator16.myDecimateSup( + &buf8[0], + &buf16[0]); + + m_decimator16.myDecimateSup( + &buf8[8], + &buf16[4]); + + m_decimator16.myDecimateSup( + &buf8[16], + &buf16[8]); + + m_decimator16.myDecimateSup( + &buf8[24], + &buf16[12]); + + m_decimator32.myDecimateSup( + &buf16[0], + &buf32[0]); + + m_decimator32.myDecimateSup( + &buf16[8], + &buf32[4]); + + m_decimator64.myDecimateCen( + &buf32[0], + &buf64[0]); + + (**it).setReal(buf64[0] >> decimation_shifts::post64); + (**it).setImag(buf64[1] >> decimation_shifts::post64); + ++(*it); + + (**it).setReal(buf64[2] >> decimation_shifts::post64); + (**it).setImag(buf64[3] >> decimation_shifts::post64); ++(*it); } } -template -void Decimators::decimate64_inf(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate64_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal[16], yimag[16]; + StorageType buf2[128], buf4[64], buf8[32], buf16[16], buf32[8], buf64[4]; - for (int pos = 0; pos < len - 127; ) - { - for (int i = 0; i < 16; i++) - { - xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) << decimation_shifts::pre64; - yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) << decimation_shifts::pre64; - pos += 8; - } - - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); - m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); - m_decimator2.myDecimate(xreal[8], yimag[8], &xreal[9], &yimag[9]); - m_decimator2.myDecimate(xreal[10], yimag[10], &xreal[11], &yimag[11]); - m_decimator2.myDecimate(xreal[12], yimag[12], &xreal[13], &yimag[13]); - m_decimator2.myDecimate(xreal[14], yimag[14], &xreal[15], &yimag[15]); - - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); - m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); - m_decimator4.myDecimate(xreal[9], yimag[9], &xreal[11], &yimag[11]); - m_decimator4.myDecimate(xreal[13], yimag[13], &xreal[15], &yimag[15]); - - m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); - m_decimator8.myDecimate(xreal[11], yimag[11], &xreal[15], &yimag[15]); - - m_decimator16.myDecimate(xreal[7], yimag[7], &xreal[15], &yimag[15]); - - (**it).setReal(xreal[15] >> decimation_shifts::post64); - (**it).setImag(yimag[15] >> decimation_shifts::post64); - - ++(*it); - } -} - -template -void Decimators::decimate64_inf(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) -{ - AccuType xreal[16], yimag[16]; - - for (int pos = 0; pos < len - 63; ) + for (int pos = 0; pos < len - 255; pos += 256) { - for (int i = 0; i < 16; i++) - { - xreal[i] = (bufI[pos+0] - bufQ[pos+1] + bufQ[pos+3] - bufI[pos+2]) << decimation_shifts::pre64; - yimag[i] = (bufQ[pos+0] - bufQ[pos+2] + bufI[pos+1] - bufI[pos+3]) << decimation_shifts::pre64; - pos += 4; - } + m_decimator2.myDecimateSup( + buf[pos+0] << decimation_shifts::pre64, + buf[pos+1] << decimation_shifts::pre64, + buf[pos+2] << decimation_shifts::pre64, + buf[pos+3] << decimation_shifts::pre64, + buf[pos+4] << decimation_shifts::pre64, + buf[pos+5] << decimation_shifts::pre64, + buf[pos+6] << decimation_shifts::pre64, + buf[pos+7] << decimation_shifts::pre64, + &buf2[0]); - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); - m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); - m_decimator2.myDecimate(xreal[8], yimag[8], &xreal[9], &yimag[9]); - m_decimator2.myDecimate(xreal[10], yimag[10], &xreal[11], &yimag[11]); - m_decimator2.myDecimate(xreal[12], yimag[12], &xreal[13], &yimag[13]); - m_decimator2.myDecimate(xreal[14], yimag[14], &xreal[15], &yimag[15]); + m_decimator2.myDecimateSup( + buf[pos+8] << decimation_shifts::pre64, + buf[pos+9] << decimation_shifts::pre64, + buf[pos+10] << decimation_shifts::pre64, + buf[pos+11] << decimation_shifts::pre64, + buf[pos+12] << decimation_shifts::pre64, + buf[pos+13] << decimation_shifts::pre64, + buf[pos+14] << decimation_shifts::pre64, + buf[pos+15] << decimation_shifts::pre64, + &buf2[4]); - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); - m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); - m_decimator4.myDecimate(xreal[9], yimag[9], &xreal[11], &yimag[11]); - m_decimator4.myDecimate(xreal[13], yimag[13], &xreal[15], &yimag[15]); + m_decimator2.myDecimateSup( + buf[pos+16] << decimation_shifts::pre64, + buf[pos+17] << decimation_shifts::pre64, + buf[pos+18] << decimation_shifts::pre64, + buf[pos+19] << decimation_shifts::pre64, + buf[pos+20] << decimation_shifts::pre64, + buf[pos+21] << decimation_shifts::pre64, + buf[pos+22] << decimation_shifts::pre64, + buf[pos+23] << decimation_shifts::pre64, + &buf2[8]); - m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); - m_decimator8.myDecimate(xreal[11], yimag[11], &xreal[15], &yimag[15]); + m_decimator2.myDecimateSup( + buf[pos+24] << decimation_shifts::pre64, + buf[pos+25] << decimation_shifts::pre64, + buf[pos+26] << decimation_shifts::pre64, + buf[pos+27] << decimation_shifts::pre64, + buf[pos+28] << decimation_shifts::pre64, + buf[pos+29] << decimation_shifts::pre64, + buf[pos+30] << decimation_shifts::pre64, + buf[pos+31] << decimation_shifts::pre64, + &buf2[12]); - m_decimator16.myDecimate(xreal[7], yimag[7], &xreal[15], &yimag[15]); + m_decimator2.myDecimateSup( + buf[pos+32] << decimation_shifts::pre64, + buf[pos+33] << decimation_shifts::pre64, + buf[pos+34] << decimation_shifts::pre64, + buf[pos+35] << decimation_shifts::pre64, + buf[pos+36] << decimation_shifts::pre64, + buf[pos+37] << decimation_shifts::pre64, + buf[pos+38] << decimation_shifts::pre64, + buf[pos+39] << decimation_shifts::pre64, + &buf2[16]); - (**it).setReal(xreal[15] >> decimation_shifts::post64); - (**it).setImag(yimag[15] >> decimation_shifts::post64); + m_decimator2.myDecimateSup( + buf[pos+40] << decimation_shifts::pre64, + buf[pos+41] << decimation_shifts::pre64, + buf[pos+42] << decimation_shifts::pre64, + buf[pos+43] << decimation_shifts::pre64, + buf[pos+44] << decimation_shifts::pre64, + buf[pos+45] << decimation_shifts::pre64, + buf[pos+46] << decimation_shifts::pre64, + buf[pos+47] << decimation_shifts::pre64, + &buf2[20]); + m_decimator2.myDecimateSup( + buf[pos+48] << decimation_shifts::pre64, + buf[pos+49] << decimation_shifts::pre64, + buf[pos+50] << decimation_shifts::pre64, + buf[pos+51] << decimation_shifts::pre64, + buf[pos+52] << decimation_shifts::pre64, + buf[pos+53] << decimation_shifts::pre64, + buf[pos+54] << decimation_shifts::pre64, + buf[pos+55] << decimation_shifts::pre64, + &buf2[24]); + + m_decimator2.myDecimateSup( + buf[pos+56] << decimation_shifts::pre64, + buf[pos+57] << decimation_shifts::pre64, + buf[pos+58] << decimation_shifts::pre64, + buf[pos+59] << decimation_shifts::pre64, + buf[pos+60] << decimation_shifts::pre64, + buf[pos+61] << decimation_shifts::pre64, + buf[pos+62] << decimation_shifts::pre64, + buf[pos+63] << decimation_shifts::pre64, + &buf2[28]); + + m_decimator2.myDecimateSup( + buf[pos+64] << decimation_shifts::pre64, + buf[pos+65] << decimation_shifts::pre64, + buf[pos+66] << decimation_shifts::pre64, + buf[pos+67] << decimation_shifts::pre64, + buf[pos+68] << decimation_shifts::pre64, + buf[pos+69] << decimation_shifts::pre64, + buf[pos+70] << decimation_shifts::pre64, + buf[pos+71] << decimation_shifts::pre64, + &buf2[32]); + + m_decimator2.myDecimateSup( + buf[pos+72] << decimation_shifts::pre64, + buf[pos+73] << decimation_shifts::pre64, + buf[pos+74] << decimation_shifts::pre64, + buf[pos+75] << decimation_shifts::pre64, + buf[pos+76] << decimation_shifts::pre64, + buf[pos+77] << decimation_shifts::pre64, + buf[pos+78] << decimation_shifts::pre64, + buf[pos+79] << decimation_shifts::pre64, + &buf2[36]); + + m_decimator2.myDecimateSup( + buf[pos+80] << decimation_shifts::pre64, + buf[pos+81] << decimation_shifts::pre64, + buf[pos+82] << decimation_shifts::pre64, + buf[pos+83] << decimation_shifts::pre64, + buf[pos+84] << decimation_shifts::pre64, + buf[pos+85] << decimation_shifts::pre64, + buf[pos+86] << decimation_shifts::pre64, + buf[pos+87] << decimation_shifts::pre64, + &buf2[40]); + + m_decimator2.myDecimateSup( + buf[pos+88] << decimation_shifts::pre64, + buf[pos+89] << decimation_shifts::pre64, + buf[pos+90] << decimation_shifts::pre64, + buf[pos+91] << decimation_shifts::pre64, + buf[pos+92] << decimation_shifts::pre64, + buf[pos+93] << decimation_shifts::pre64, + buf[pos+94] << decimation_shifts::pre64, + buf[pos+95] << decimation_shifts::pre64, + &buf2[44]); + + m_decimator2.myDecimateSup( + buf[pos+96] << decimation_shifts::pre64, + buf[pos+97] << decimation_shifts::pre64, + buf[pos+98] << decimation_shifts::pre64, + buf[pos+99] << decimation_shifts::pre64, + buf[pos+100] << decimation_shifts::pre64, + buf[pos+101] << decimation_shifts::pre64, + buf[pos+102] << decimation_shifts::pre64, + buf[pos+103] << decimation_shifts::pre64, + &buf2[48]); + + m_decimator2.myDecimateSup( + buf[pos+104] << decimation_shifts::pre64, + buf[pos+105] << decimation_shifts::pre64, + buf[pos+106] << decimation_shifts::pre64, + buf[pos+107] << decimation_shifts::pre64, + buf[pos+108] << decimation_shifts::pre64, + buf[pos+109] << decimation_shifts::pre64, + buf[pos+110] << decimation_shifts::pre64, + buf[pos+111] << decimation_shifts::pre64, + &buf2[52]); + + m_decimator2.myDecimateSup( + buf[pos+112] << decimation_shifts::pre64, + buf[pos+113] << decimation_shifts::pre64, + buf[pos+114] << decimation_shifts::pre64, + buf[pos+115] << decimation_shifts::pre64, + buf[pos+116] << decimation_shifts::pre64, + buf[pos+117] << decimation_shifts::pre64, + buf[pos+118] << decimation_shifts::pre64, + buf[pos+119] << decimation_shifts::pre64, + &buf2[56]); + + m_decimator2.myDecimateSup( + buf[pos+120] << decimation_shifts::pre64, + buf[pos+121] << decimation_shifts::pre64, + buf[pos+122] << decimation_shifts::pre64, + buf[pos+123] << decimation_shifts::pre64, + buf[pos+124] << decimation_shifts::pre64, + buf[pos+125] << decimation_shifts::pre64, + buf[pos+126] << decimation_shifts::pre64, + buf[pos+127] << decimation_shifts::pre64, + &buf2[60]); + + m_decimator2.myDecimateSup( + buf[pos+128] << decimation_shifts::pre64, + buf[pos+129] << decimation_shifts::pre64, + buf[pos+130] << decimation_shifts::pre64, + buf[pos+131] << decimation_shifts::pre64, + buf[pos+132] << decimation_shifts::pre64, + buf[pos+133] << decimation_shifts::pre64, + buf[pos+134] << decimation_shifts::pre64, + buf[pos+135] << decimation_shifts::pre64, + &buf2[64]); + + m_decimator2.myDecimateSup( + buf[pos+136] << decimation_shifts::pre64, + buf[pos+137] << decimation_shifts::pre64, + buf[pos+138] << decimation_shifts::pre64, + buf[pos+139] << decimation_shifts::pre64, + buf[pos+140] << decimation_shifts::pre64, + buf[pos+141] << decimation_shifts::pre64, + buf[pos+142] << decimation_shifts::pre64, + buf[pos+143] << decimation_shifts::pre64, + &buf2[68]); + + m_decimator2.myDecimateSup( + buf[pos+144] << decimation_shifts::pre64, + buf[pos+145] << decimation_shifts::pre64, + buf[pos+146] << decimation_shifts::pre64, + buf[pos+147] << decimation_shifts::pre64, + buf[pos+148] << decimation_shifts::pre64, + buf[pos+149] << decimation_shifts::pre64, + buf[pos+150] << decimation_shifts::pre64, + buf[pos+151] << decimation_shifts::pre64, + &buf2[72]); + + m_decimator2.myDecimateSup( + buf[pos+152] << decimation_shifts::pre64, + buf[pos+153] << decimation_shifts::pre64, + buf[pos+154] << decimation_shifts::pre64, + buf[pos+155] << decimation_shifts::pre64, + buf[pos+156] << decimation_shifts::pre64, + buf[pos+157] << decimation_shifts::pre64, + buf[pos+158] << decimation_shifts::pre64, + buf[pos+159] << decimation_shifts::pre64, + &buf2[76]); + + m_decimator2.myDecimateSup( + buf[pos+160] << decimation_shifts::pre64, + buf[pos+161] << decimation_shifts::pre64, + buf[pos+162] << decimation_shifts::pre64, + buf[pos+163] << decimation_shifts::pre64, + buf[pos+164] << decimation_shifts::pre64, + buf[pos+165] << decimation_shifts::pre64, + buf[pos+166] << decimation_shifts::pre64, + buf[pos+167] << decimation_shifts::pre64, + &buf2[80]); + + m_decimator2.myDecimateSup( + buf[pos+168] << decimation_shifts::pre64, + buf[pos+169] << decimation_shifts::pre64, + buf[pos+170] << decimation_shifts::pre64, + buf[pos+171] << decimation_shifts::pre64, + buf[pos+172] << decimation_shifts::pre64, + buf[pos+173] << decimation_shifts::pre64, + buf[pos+174] << decimation_shifts::pre64, + buf[pos+175] << decimation_shifts::pre64, + &buf2[84]); + + m_decimator2.myDecimateSup( + buf[pos+176] << decimation_shifts::pre64, + buf[pos+177] << decimation_shifts::pre64, + buf[pos+178] << decimation_shifts::pre64, + buf[pos+179] << decimation_shifts::pre64, + buf[pos+180] << decimation_shifts::pre64, + buf[pos+181] << decimation_shifts::pre64, + buf[pos+182] << decimation_shifts::pre64, + buf[pos+183] << decimation_shifts::pre64, + &buf2[88]); + + m_decimator2.myDecimateSup( + buf[pos+184] << decimation_shifts::pre64, + buf[pos+185] << decimation_shifts::pre64, + buf[pos+186] << decimation_shifts::pre64, + buf[pos+187] << decimation_shifts::pre64, + buf[pos+188] << decimation_shifts::pre64, + buf[pos+189] << decimation_shifts::pre64, + buf[pos+190] << decimation_shifts::pre64, + buf[pos+191] << decimation_shifts::pre64, + &buf2[92]); + + m_decimator2.myDecimateSup( + buf[pos+192] << decimation_shifts::pre64, + buf[pos+193] << decimation_shifts::pre64, + buf[pos+194] << decimation_shifts::pre64, + buf[pos+195] << decimation_shifts::pre64, + buf[pos+196] << decimation_shifts::pre64, + buf[pos+197] << decimation_shifts::pre64, + buf[pos+198] << decimation_shifts::pre64, + buf[pos+199] << decimation_shifts::pre64, + &buf2[96]); + + m_decimator2.myDecimateSup( + buf[pos+200] << decimation_shifts::pre64, + buf[pos+201] << decimation_shifts::pre64, + buf[pos+202] << decimation_shifts::pre64, + buf[pos+203] << decimation_shifts::pre64, + buf[pos+204] << decimation_shifts::pre64, + buf[pos+205] << decimation_shifts::pre64, + buf[pos+206] << decimation_shifts::pre64, + buf[pos+207] << decimation_shifts::pre64, + &buf2[100]); + + m_decimator2.myDecimateSup( + buf[pos+208] << decimation_shifts::pre64, + buf[pos+209] << decimation_shifts::pre64, + buf[pos+210] << decimation_shifts::pre64, + buf[pos+211] << decimation_shifts::pre64, + buf[pos+212] << decimation_shifts::pre64, + buf[pos+213] << decimation_shifts::pre64, + buf[pos+214] << decimation_shifts::pre64, + buf[pos+215] << decimation_shifts::pre64, + &buf2[104]); + + m_decimator2.myDecimateSup( + buf[pos+216] << decimation_shifts::pre64, + buf[pos+217] << decimation_shifts::pre64, + buf[pos+218] << decimation_shifts::pre64, + buf[pos+219] << decimation_shifts::pre64, + buf[pos+220] << decimation_shifts::pre64, + buf[pos+221] << decimation_shifts::pre64, + buf[pos+222] << decimation_shifts::pre64, + buf[pos+223] << decimation_shifts::pre64, + &buf2[108]); + + m_decimator2.myDecimateSup( + buf[pos+224] << decimation_shifts::pre64, + buf[pos+225] << decimation_shifts::pre64, + buf[pos+226] << decimation_shifts::pre64, + buf[pos+227] << decimation_shifts::pre64, + buf[pos+228] << decimation_shifts::pre64, + buf[pos+229] << decimation_shifts::pre64, + buf[pos+230] << decimation_shifts::pre64, + buf[pos+231] << decimation_shifts::pre64, + &buf2[112]); + + m_decimator2.myDecimateSup( + buf[pos+232] << decimation_shifts::pre64, + buf[pos+233] << decimation_shifts::pre64, + buf[pos+234] << decimation_shifts::pre64, + buf[pos+235] << decimation_shifts::pre64, + buf[pos+236] << decimation_shifts::pre64, + buf[pos+237] << decimation_shifts::pre64, + buf[pos+238] << decimation_shifts::pre64, + buf[pos+239] << decimation_shifts::pre64, + &buf2[116]); + + m_decimator2.myDecimateSup( + buf[pos+240] << decimation_shifts::pre64, + buf[pos+241] << decimation_shifts::pre64, + buf[pos+242] << decimation_shifts::pre64, + buf[pos+243] << decimation_shifts::pre64, + buf[pos+244] << decimation_shifts::pre64, + buf[pos+245] << decimation_shifts::pre64, + buf[pos+246] << decimation_shifts::pre64, + buf[pos+247] << decimation_shifts::pre64, + &buf2[120]); + + m_decimator2.myDecimateSup( + buf[pos+248] << decimation_shifts::pre64, + buf[pos+249] << decimation_shifts::pre64, + buf[pos+250] << decimation_shifts::pre64, + buf[pos+251] << decimation_shifts::pre64, + buf[pos+252] << decimation_shifts::pre64, + buf[pos+253] << decimation_shifts::pre64, + buf[pos+254] << decimation_shifts::pre64, + buf[pos+255] << decimation_shifts::pre64, + &buf2[124]); + + m_decimator4.myDecimateInf( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateInf( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateInf( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateInf( + &buf2[24], + &buf4[12]); + + m_decimator4.myDecimateInf( + &buf2[32], + &buf4[16]); + + m_decimator4.myDecimateInf( + &buf2[40], + &buf4[20]); + + m_decimator4.myDecimateInf( + &buf2[48], + &buf4[24]); + + m_decimator4.myDecimateInf( + &buf2[56], + &buf4[28]); + + m_decimator4.myDecimateInf( + &buf2[64], + &buf4[32]); + + m_decimator4.myDecimateInf( + &buf2[72], + &buf4[36]); + + m_decimator4.myDecimateInf( + &buf2[80], + &buf4[40]); + + m_decimator4.myDecimateInf( + &buf2[88], + &buf4[44]); + + m_decimator4.myDecimateInf( + &buf2[96], + &buf4[48]); + + m_decimator4.myDecimateInf( + &buf2[104], + &buf4[52]); + + m_decimator4.myDecimateInf( + &buf2[112], + &buf4[56]); + + m_decimator4.myDecimateInf( + &buf2[120], + &buf4[60]); + + m_decimator8.myDecimateInf( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateInf( + &buf4[8], + &buf8[4]); + + m_decimator8.myDecimateInf( + &buf4[16], + &buf8[8]); + + m_decimator8.myDecimateInf( + &buf4[24], + &buf8[12]); + + m_decimator8.myDecimateInf( + &buf4[32], + &buf8[16]); + + m_decimator8.myDecimateInf( + &buf4[40], + &buf8[20]); + + m_decimator8.myDecimateInf( + &buf4[48], + &buf8[24]); + + m_decimator8.myDecimateInf( + &buf4[56], + &buf8[28]); + + m_decimator16.myDecimateInf( + &buf8[0], + &buf16[0]); + + m_decimator16.myDecimateInf( + &buf8[8], + &buf16[4]); + + m_decimator16.myDecimateInf( + &buf8[16], + &buf16[8]); + + m_decimator16.myDecimateInf( + &buf8[24], + &buf16[12]); + + m_decimator32.myDecimateInf( + &buf16[0], + &buf32[0]); + + m_decimator32.myDecimateInf( + &buf16[8], + &buf32[4]); + + m_decimator64.myDecimateCen( + &buf32[0], + &buf64[0]); + + (**it).setReal(buf64[0] >> decimation_shifts::post64); + (**it).setImag(buf64[1] >> decimation_shifts::post64); + ++(*it); + + (**it).setReal(buf64[2] >> decimation_shifts::post64); + (**it).setImag(buf64[3] >> decimation_shifts::post64); ++(*it); } } -template -void Decimators::decimate64_sup(SampleVector::iterator* it, const T* buf, qint32 len) +//template +//void Decimators::decimate2_cen(SampleVector::iterator* it, const T* buf, qint32 len) +//{ +// StorageType intbuf[2]; +// +// for (int pos = 0; pos < len - 3; pos += 4) +// { +// intbuf[0] = buf[pos+2] << decimation_shifts::pre2; +// intbuf[1] = buf[pos+3] << decimation_shifts::pre2; +// +// m_decimator2.myDecimate( +// buf[pos+0] << decimation_shifts::pre2, +// buf[pos+1] << decimation_shifts::pre2, +// &intbuf[0], +// &intbuf[1]); +// +// (**it).setReal(intbuf[0] >> decimation_shifts::post2); +// (**it).setImag(intbuf[1] >> decimation_shifts::post2); +// +// ++(*it); +// } +//} + +template +void Decimators::decimate2_cen(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal[16], yimag[16]; + StorageType buf2[4]; - for (int pos = 0; pos < len - 127; ) - { - for (int i = 0; i < 16; i++) - { - xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) << decimation_shifts::pre32; - yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]) << decimation_shifts::pre32; - pos += 8; - } - - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); - m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); - m_decimator2.myDecimate(xreal[8], yimag[8], &xreal[9], &yimag[9]); - m_decimator2.myDecimate(xreal[10], yimag[10], &xreal[11], &yimag[11]); - m_decimator2.myDecimate(xreal[12], yimag[12], &xreal[13], &yimag[13]); - m_decimator2.myDecimate(xreal[14], yimag[14], &xreal[15], &yimag[15]); - - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); - m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); - m_decimator4.myDecimate(xreal[9], yimag[9], &xreal[11], &yimag[11]); - m_decimator4.myDecimate(xreal[13], yimag[13], &xreal[15], &yimag[15]); - - m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); - m_decimator8.myDecimate(xreal[11], yimag[11], &xreal[15], &yimag[15]); - - m_decimator16.myDecimate(xreal[7], yimag[7], &xreal[15], &yimag[15]); - - (**it).setReal(xreal[15] >> decimation_shifts::post64); - (**it).setImag(yimag[15] >> decimation_shifts::post64); - - ++(*it); - } -} - -template -void Decimators::decimate64_sup(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) -{ - AccuType xreal[16], yimag[16]; - - for (int pos = 0; pos < len - 63; ) + for (int pos = 0; pos < len - 7; pos += 8) { - for (int i = 0; i < 16; i++) - { - xreal[i] = (bufQ[pos+0] - bufI[pos+1] - bufQ[pos+2] + bufI[pos+3]) << decimation_shifts::pre32; - yimag[i] = (bufI[pos+2] + bufQ[pos+3] - bufI[pos+0] - bufQ[pos+1]) << decimation_shifts::pre32; - pos += 4; - } + m_decimator2.myDecimateCen( + buf[pos+0] << decimation_shifts::pre2, + buf[pos+1] << decimation_shifts::pre2, + buf[pos+2] << decimation_shifts::pre2, + buf[pos+3] << decimation_shifts::pre2, + buf[pos+4] << decimation_shifts::pre2, + buf[pos+5] << decimation_shifts::pre2, + buf[pos+6] << decimation_shifts::pre2, + buf[pos+7] << decimation_shifts::pre2, + &buf2[0]); - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); - m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); - m_decimator2.myDecimate(xreal[8], yimag[8], &xreal[9], &yimag[9]); - m_decimator2.myDecimate(xreal[10], yimag[10], &xreal[11], &yimag[11]); - m_decimator2.myDecimate(xreal[12], yimag[12], &xreal[13], &yimag[13]); - m_decimator2.myDecimate(xreal[14], yimag[14], &xreal[15], &yimag[15]); - - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); - m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); - m_decimator4.myDecimate(xreal[9], yimag[9], &xreal[11], &yimag[11]); - m_decimator4.myDecimate(xreal[13], yimag[13], &xreal[15], &yimag[15]); - - m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); - m_decimator8.myDecimate(xreal[11], yimag[11], &xreal[15], &yimag[15]); - - m_decimator16.myDecimate(xreal[7], yimag[7], &xreal[15], &yimag[15]); - - (**it).setReal(xreal[15] >> decimation_shifts::post64); - (**it).setImag(yimag[15] >> decimation_shifts::post64); + (**it).setReal(buf2[0] >> decimation_shifts::post2); + (**it).setImag(buf2[1] >> decimation_shifts::post2); + ++(*it); + (**it).setReal(buf2[2] >> decimation_shifts::post2); + (**it).setImag(buf2[3] >> decimation_shifts::post2); ++(*it); } } -template -void Decimators::decimate2_cen(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate2_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) { - AccuType intbuf[2]; - - for (int pos = 0; pos < len - 3; pos += 4) - { - intbuf[0] = buf[pos+2] << decimation_shifts::pre2; - intbuf[1] = buf[pos+3] << decimation_shifts::pre2; - - m_decimator2.myDecimate( - buf[pos+0] << decimation_shifts::pre2, - buf[pos+1] << decimation_shifts::pre2, - &intbuf[0], - &intbuf[1]); - - (**it).setReal(intbuf[0] >> decimation_shifts::post2); - (**it).setImag(intbuf[1] >> decimation_shifts::post2); - - ++(*it); - } -} - -template -void Decimators::decimate2_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) -{ - AccuType intbuf[2]; + StorageType intbuf[2]; for (int pos = 0; pos < len - 1; pos += 2) { @@ -1125,45 +2656,53 @@ void Decimators::decimate2_cen(SampleVector::it } } -template -void Decimators::decimate4_cen(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate4_cen(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType intbuf[4]; + StorageType buf2[8], buf4[4]; - for (int pos = 0; pos < len - 7; pos += 8) + for (int pos = 0; pos < len - 15; pos += 16) { - intbuf[0] = buf[pos+2] << decimation_shifts::pre4; - intbuf[1] = buf[pos+3] << decimation_shifts::pre4; - intbuf[2] = buf[pos+6] << decimation_shifts::pre4; - intbuf[3] = buf[pos+7] << decimation_shifts::pre4; + m_decimator2.myDecimateCen( + buf[pos+0] << decimation_shifts::pre4, + buf[pos+1] << decimation_shifts::pre4, + buf[pos+2] << decimation_shifts::pre4, + buf[pos+3] << decimation_shifts::pre4, + buf[pos+4] << decimation_shifts::pre4, + buf[pos+5] << decimation_shifts::pre4, + buf[pos+6] << decimation_shifts::pre4, + buf[pos+7] << decimation_shifts::pre4, + &buf2[0]); - m_decimator2.myDecimate( - buf[pos+0] << decimation_shifts::pre4, - buf[pos+1] << decimation_shifts::pre4, - &intbuf[0], - &intbuf[1]); - m_decimator2.myDecimate( - buf[pos+4] << decimation_shifts::pre4, - buf[pos+5] << decimation_shifts::pre4, - &intbuf[2], - &intbuf[3]); + m_decimator2.myDecimateCen( + buf[pos+8] << decimation_shifts::pre4, + buf[pos+9] << decimation_shifts::pre4, + buf[pos+10] << decimation_shifts::pre4, + buf[pos+11] << decimation_shifts::pre4, + buf[pos+12] << decimation_shifts::pre4, + buf[pos+13] << decimation_shifts::pre4, + buf[pos+14] << decimation_shifts::pre4, + buf[pos+15] << decimation_shifts::pre4, + &buf2[4]); - m_decimator4.myDecimate( - intbuf[0], - intbuf[1], - &intbuf[2], - &intbuf[3]); + m_decimator4.myDecimateCen( + &buf2[0], + &buf4[0]); - (**it).setReal(intbuf[2] >> decimation_shifts::post4); - (**it).setImag(intbuf[3] >> decimation_shifts::post4); - ++(*it); + (**it).setReal(buf4[0] >> decimation_shifts::post4); + (**it).setImag(buf4[1] >> decimation_shifts::post4); + ++(*it); + + (**it).setReal(buf4[2] >> decimation_shifts::post4); + (**it).setImag(buf4[3] >> decimation_shifts::post4); + ++(*it); } } -template -void Decimators::decimate4_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +template +void Decimators::decimate4_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) { - AccuType intbuf[4]; + StorageType intbuf[4]; for (int pos = 0; pos < len - 3; pos += 4) { @@ -1195,10 +2734,10 @@ void Decimators::decimate4_cen(SampleVector::it } } -template -void Decimators::decimate8_cen(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate8_cen(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType intbuf[8]; + StorageType intbuf[8]; for (int pos = 0; pos < len - 15; pos += 16) { @@ -1255,10 +2794,10 @@ void Decimators::decimate8_cen(SampleVector::it } } -template -void Decimators::decimate8_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +template +void Decimators::decimate8_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) { - AccuType intbuf[8]; + StorageType intbuf[8]; for (int pos = 0; pos < len - 7; pos += 8) { @@ -1315,10 +2854,10 @@ void Decimators::decimate8_cen(SampleVector::it } } -template -void Decimators::decimate16_cen(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate16_cen(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType intbuf[16]; + StorageType intbuf[16]; for (int pos = 0; pos < len - 31; pos += 32) { @@ -1424,10 +2963,10 @@ void Decimators::decimate16_cen(SampleVector::i } } -template -void Decimators::decimate16_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +template +void Decimators::decimate16_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) { - AccuType intbuf[16]; + StorageType intbuf[16]; for (int pos = 0; pos < len - 15; pos += 16) { @@ -1533,10 +3072,10 @@ void Decimators::decimate16_cen(SampleVector::i } } -template -void Decimators::decimate32_cen(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate32_cen(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType intbuf[32]; + StorageType intbuf[32]; for (int pos = 0; pos < len - 63; pos += 64) { @@ -1739,10 +3278,10 @@ void Decimators::decimate32_cen(SampleVector::i } } -template -void Decimators::decimate32_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +template +void Decimators::decimate32_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) { - AccuType intbuf[32]; + StorageType intbuf[32]; for (int pos = 0; pos < len - 31; pos += 32) { @@ -1945,10 +3484,10 @@ void Decimators::decimate32_cen(SampleVector::i } } -template -void Decimators::decimate64_cen(SampleVector::iterator* it, const T* buf, qint32 len) +template +void Decimators::decimate64_cen(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType intbuf[64]; + StorageType intbuf[64]; for (int pos = 0; pos < len - 127; pos += 128) { @@ -2345,10 +3884,10 @@ void Decimators::decimate64_cen(SampleVector::i } } -template -void Decimators::decimate64_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) +template +void Decimators::decimate64_cen(SampleVector::iterator* it, const T* bufI, const T* bufQ, qint32 len) { - AccuType intbuf[64]; + StorageType intbuf[64]; for (int pos = 0; pos < len - 63; pos += 64) { diff --git a/sdrbase/dsp/decimatorsff.cpp b/sdrbase/dsp/decimatorsff.cpp new file mode 100644 index 000000000..dc7afbcb0 --- /dev/null +++ b/sdrbase/dsp/decimatorsff.cpp @@ -0,0 +1,1173 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "decimatorsff.h" + +void DecimatorsFF::decimate1(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 1; pos += 2) + { + xreal = buf[pos+0]; + yimag = buf[pos+1]; + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); // Valgrind optim (comment not repeated) + } +} + +void DecimatorsFF::decimate2_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float intbuf[2]; + + for (int pos = 0; pos < nbIAndQ - 3; pos += 4) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + + (**it).setReal(intbuf[0]); + (**it).setImag(intbuf[1]); + + ++(*it); + } +} + +void DecimatorsFF::decimate2_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (buf[pos+0] - buf[pos+3]); + yimag = (buf[pos+1] + buf[pos+2]); + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); + + xreal = (buf[pos+7] - buf[pos+4]); + yimag = (- buf[pos+5] - buf[pos+6]); + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); + } +} + +void DecimatorsFF::decimate2_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (buf[pos+1] - buf[pos+2]); + yimag = (- buf[pos+0] - buf[pos+3]); + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); + + xreal = (buf[pos+6] - buf[pos+5]); + yimag = (buf[pos+4] + buf[pos+7]); + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); + } +} + +void DecimatorsFF::decimate4_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + + (**it).setReal(xreal); + (**it).setImag(yimag); + + ++(*it); + } +} + +void DecimatorsFF::decimate4_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + // Sup (USB): + // x y x y x y x y / x -> 1,-2,-5,6 / y -> -0,-3,4,7 + // [ rotate: 1, 0, -2, 3, -5, -4, 6, -7] + // Inf (LSB): + // x y x y x y x y / x -> 0,-3,-4,7 / y -> 1,2,-5,-6 + // [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]); + + (**it).setReal(xreal); + (**it).setImag(yimag); + + ++(*it); + } +} + +void DecimatorsFF::decimate8_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal[2], yimag[2]; + + for (int pos = 0; pos < nbIAndQ - 15; pos += 8) + { + xreal[0] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[0] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + pos += 8; + + xreal[1] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[1] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + + (**it).setReal(xreal[1]); + (**it).setImag(yimag[1]); + + ++(*it); + } +} + +void DecimatorsFF::decimate8_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal[2], yimag[2]; + + for (int pos = 0; pos < nbIAndQ - 15; pos += 8) + { + xreal[0] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[0] = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]); + pos += 8; + + xreal[1] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[1] = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]); + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + + (**it).setReal(xreal[1]); + (**it).setImag(yimag[1]); + + ++(*it); + } +} + +void DecimatorsFF::decimate16_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + // Offset tuning: 4x downsample and rotate, then + // downsample 4x more. [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] + float xreal[4], yimag[4]; + + for (int pos = 0; pos < nbIAndQ - 31; ) + { + for (int i = 0; i < 4; i++) + { + xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + pos += 8; + } + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + + (**it).setReal(xreal[3]); + (**it).setImag(yimag[3]); + + ++(*it); + } +} + +void DecimatorsFF::decimate16_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + // Offset tuning: 4x downsample and rotate, then + // downsample 4x more. [ rotate: 1, 0, -2, 3, -5, -4, 6, -7] + float xreal[4], yimag[4]; + + for (int pos = 0; pos < nbIAndQ - 31; ) + { + for (int i = 0; i < 4; i++) + { + xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]); + pos += 8; + } + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + + (**it).setReal(xreal[3]); + (**it).setImag(yimag[3]); + + ++(*it); + } +} + +void DecimatorsFF::decimate32_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal[8], yimag[8]; + + for (int pos = 0; pos < nbIAndQ - 63; ) + { + for (int i = 0; i < 8; i++) + { + xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + pos += 8; + } + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); + m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + + (**it).setReal(xreal[7]); + (**it).setImag(yimag[7]); + + ++(*it); + } +} + +void DecimatorsFF::decimate32_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal[8], yimag[8]; + + for (int pos = 0; pos < nbIAndQ - 63; ) + { + for (int i = 0; i < 8; i++) + { + xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]); + pos += 8; + } + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); + m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + + (**it).setReal(xreal[7]); + (**it).setImag(yimag[7]); + + ++(*it); + } +} + +void DecimatorsFF::decimate64_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal[16], yimag[16]; + + for (int pos = 0; pos < nbIAndQ - 127; ) + { + for (int i = 0; i < 16; i++) + { + xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + pos += 8; + } + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); + m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + m_decimator2.myDecimate(xreal[8], yimag[8], &xreal[9], &yimag[9]); + m_decimator2.myDecimate(xreal[10], yimag[10], &xreal[11], &yimag[11]); + m_decimator2.myDecimate(xreal[12], yimag[12], &xreal[13], &yimag[13]); + m_decimator2.myDecimate(xreal[14], yimag[14], &xreal[15], &yimag[15]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + m_decimator4.myDecimate(xreal[9], yimag[9], &xreal[11], &yimag[11]); + m_decimator4.myDecimate(xreal[13], yimag[13], &xreal[15], &yimag[15]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + m_decimator8.myDecimate(xreal[11], yimag[11], &xreal[15], &yimag[15]); + + m_decimator16.myDecimate(xreal[7], yimag[7], &xreal[15], &yimag[15]); + + (**it).setReal(xreal[15]); + (**it).setImag(yimag[15]); + + ++(*it); + } +} + +void DecimatorsFF::decimate64_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float xreal[16], yimag[16]; + + for (int pos = 0; pos < nbIAndQ - 127; ) + { + for (int i = 0; i < 16; i++) + { + xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]); + pos += 8; + } + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); + m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + m_decimator2.myDecimate(xreal[8], yimag[8], &xreal[9], &yimag[9]); + m_decimator2.myDecimate(xreal[10], yimag[10], &xreal[11], &yimag[11]); + m_decimator2.myDecimate(xreal[12], yimag[12], &xreal[13], &yimag[13]); + m_decimator2.myDecimate(xreal[14], yimag[14], &xreal[15], &yimag[15]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + m_decimator4.myDecimate(xreal[9], yimag[9], &xreal[11], &yimag[11]); + m_decimator4.myDecimate(xreal[13], yimag[13], &xreal[15], &yimag[15]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + m_decimator8.myDecimate(xreal[11], yimag[11], &xreal[15], &yimag[15]); + + m_decimator16.myDecimate(xreal[7], yimag[7], &xreal[15], &yimag[15]); + + (**it).setReal(xreal[15]); + (**it).setImag(yimag[15]); + + ++(*it); + } +} + +void DecimatorsFF::decimate4_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float intbuf[4]; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + + (**it).setReal(intbuf[2]); + (**it).setImag(intbuf[3]); + ++(*it); + } +} + +void DecimatorsFF::decimate8_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float intbuf[8]; + + for (int pos = 0; pos < nbIAndQ - 15; pos += 16) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + intbuf[4] = buf[pos+10]; + intbuf[5] = buf[pos+11]; + intbuf[6] = buf[pos+14]; + intbuf[7] = buf[pos+15]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8], + buf[pos+9], + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12], + buf[pos+13], + &intbuf[6], + &intbuf[7]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + + (**it).setReal(intbuf[6]); + (**it).setImag(intbuf[7]); + ++(*it); + } +} + +void DecimatorsFF::decimate16_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float intbuf[16]; + + for (int pos = 0; pos < nbIAndQ - 31; pos += 32) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + intbuf[4] = buf[pos+10]; + intbuf[5] = buf[pos+11]; + intbuf[6] = buf[pos+14]; + intbuf[7] = buf[pos+15]; + intbuf[8] = buf[pos+18]; + intbuf[9] = buf[pos+19]; + intbuf[10] = buf[pos+22]; + intbuf[11] = buf[pos+23]; + intbuf[12] = buf[pos+26]; + intbuf[13] = buf[pos+27]; + intbuf[14] = buf[pos+30]; + intbuf[15] = buf[pos+31]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8], + buf[pos+9], + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12], + buf[pos+13], + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + buf[pos+16], + buf[pos+17], + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + buf[pos+20], + buf[pos+21], + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + buf[pos+24], + buf[pos+25], + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + buf[pos+28], + buf[pos+29], + &intbuf[14], + &intbuf[15]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + + (**it).setReal(intbuf[14]); + (**it).setImag(intbuf[15]); + ++(*it); + } +} + +void DecimatorsFF::decimate32_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float intbuf[32]; + + for (int pos = 0; pos < nbIAndQ - 63; pos += 64) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + intbuf[4] = buf[pos+10]; + intbuf[5] = buf[pos+11]; + intbuf[6] = buf[pos+14]; + intbuf[7] = buf[pos+15]; + intbuf[8] = buf[pos+18]; + intbuf[9] = buf[pos+19]; + intbuf[10] = buf[pos+22]; + intbuf[11] = buf[pos+23]; + intbuf[12] = buf[pos+26]; + intbuf[13] = buf[pos+27]; + intbuf[14] = buf[pos+30]; + intbuf[15] = buf[pos+31]; + intbuf[16] = buf[pos+34]; + intbuf[17] = buf[pos+35]; + intbuf[18] = buf[pos+38]; + intbuf[19] = buf[pos+39]; + intbuf[20] = buf[pos+42]; + intbuf[21] = buf[pos+43]; + intbuf[22] = buf[pos+46]; + intbuf[23] = buf[pos+47]; + intbuf[24] = buf[pos+50]; + intbuf[25] = buf[pos+51]; + intbuf[26] = buf[pos+54]; + intbuf[27] = buf[pos+55]; + intbuf[28] = buf[pos+58]; + intbuf[29] = buf[pos+59]; + intbuf[30] = buf[pos+62]; + intbuf[31] = buf[pos+63]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8], + buf[pos+9], + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12], + buf[pos+13], + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + buf[pos+16], + buf[pos+17], + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + buf[pos+20], + buf[pos+21], + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + buf[pos+24], + buf[pos+25], + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + buf[pos+28], + buf[pos+29], + &intbuf[14], + &intbuf[15]); + m_decimator2.myDecimate( + buf[pos+32], + buf[pos+33], + &intbuf[16], + &intbuf[17]); + m_decimator2.myDecimate( + buf[pos+36], + buf[pos+37], + &intbuf[18], + &intbuf[19]); + m_decimator2.myDecimate( + buf[pos+40], + buf[pos+41], + &intbuf[20], + &intbuf[21]); + m_decimator2.myDecimate( + buf[pos+44], + buf[pos+45], + &intbuf[22], + &intbuf[23]); + m_decimator2.myDecimate( + buf[pos+48], + buf[pos+49], + &intbuf[24], + &intbuf[25]); + m_decimator2.myDecimate( + buf[pos+52], + buf[pos+53], + &intbuf[26], + &intbuf[27]); + m_decimator2.myDecimate( + buf[pos+56], + buf[pos+57], + &intbuf[28], + &intbuf[29]); + m_decimator2.myDecimate( + buf[pos+60], + buf[pos+61], + &intbuf[30], + &intbuf[31]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + m_decimator4.myDecimate( + intbuf[16], + intbuf[17], + &intbuf[18], + &intbuf[19]); + m_decimator4.myDecimate( + intbuf[20], + intbuf[21], + &intbuf[22], + &intbuf[23]); + m_decimator4.myDecimate( + intbuf[24], + intbuf[25], + &intbuf[26], + &intbuf[27]); + m_decimator4.myDecimate( + intbuf[28], + intbuf[29], + &intbuf[30], + &intbuf[31]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + m_decimator8.myDecimate( + intbuf[18], + intbuf[19], + &intbuf[22], + &intbuf[23]); + m_decimator8.myDecimate( + intbuf[26], + intbuf[27], + &intbuf[30], + &intbuf[31]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + m_decimator16.myDecimate( + intbuf[22], + intbuf[23], + &intbuf[30], + &intbuf[31]); + + m_decimator32.myDecimate( + intbuf[14], + intbuf[15], + &intbuf[30], + &intbuf[31]); + + (**it).setReal(intbuf[30]); + (**it).setImag(intbuf[31]); + ++(*it); + } +} + +void DecimatorsFF::decimate64_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +{ + float intbuf[64]; + + for (int pos = 0; pos < nbIAndQ - 127; pos += 128) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + intbuf[4] = buf[pos+10]; + intbuf[5] = buf[pos+11]; + intbuf[6] = buf[pos+14]; + intbuf[7] = buf[pos+15]; + intbuf[8] = buf[pos+18]; + intbuf[9] = buf[pos+19]; + intbuf[10] = buf[pos+22]; + intbuf[11] = buf[pos+23]; + intbuf[12] = buf[pos+26]; + intbuf[13] = buf[pos+27]; + intbuf[14] = buf[pos+30]; + intbuf[15] = buf[pos+31]; + intbuf[16] = buf[pos+34]; + intbuf[17] = buf[pos+35]; + intbuf[18] = buf[pos+38]; + intbuf[19] = buf[pos+39]; + intbuf[20] = buf[pos+42]; + intbuf[21] = buf[pos+43]; + intbuf[22] = buf[pos+46]; + intbuf[23] = buf[pos+47]; + intbuf[24] = buf[pos+50]; + intbuf[25] = buf[pos+51]; + intbuf[26] = buf[pos+54]; + intbuf[27] = buf[pos+55]; + intbuf[28] = buf[pos+58]; + intbuf[29] = buf[pos+59]; + intbuf[30] = buf[pos+62]; + intbuf[31] = buf[pos+63]; + + intbuf[32] = buf[pos+66]; + intbuf[33] = buf[pos+67]; + intbuf[34] = buf[pos+70]; + intbuf[35] = buf[pos+71]; + intbuf[36] = buf[pos+74]; + intbuf[37] = buf[pos+75]; + intbuf[38] = buf[pos+78]; + intbuf[39] = buf[pos+79]; + intbuf[40] = buf[pos+82]; + intbuf[41] = buf[pos+83]; + intbuf[42] = buf[pos+86]; + intbuf[43] = buf[pos+87]; + intbuf[44] = buf[pos+90]; + intbuf[45] = buf[pos+91]; + intbuf[46] = buf[pos+94]; + intbuf[47] = buf[pos+95]; + intbuf[48] = buf[pos+98]; + intbuf[49] = buf[pos+99]; + intbuf[50] = buf[pos+102]; + intbuf[51] = buf[pos+103]; + intbuf[52] = buf[pos+106]; + intbuf[53] = buf[pos+107]; + intbuf[54] = buf[pos+110]; + intbuf[55] = buf[pos+111]; + intbuf[56] = buf[pos+114]; + intbuf[57] = buf[pos+115]; + intbuf[58] = buf[pos+118]; + intbuf[59] = buf[pos+119]; + intbuf[60] = buf[pos+122]; + intbuf[61] = buf[pos+123]; + intbuf[62] = buf[pos+126]; + intbuf[63] = buf[pos+127]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8], + buf[pos+9], + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12], + buf[pos+13], + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + buf[pos+16], + buf[pos+17], + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + buf[pos+20], + buf[pos+21], + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + buf[pos+24], + buf[pos+25], + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + buf[pos+28], + buf[pos+29], + &intbuf[14], + &intbuf[15]); + m_decimator2.myDecimate( + buf[pos+32], + buf[pos+33], + &intbuf[16], + &intbuf[17]); + m_decimator2.myDecimate( + buf[pos+36], + buf[pos+37], + &intbuf[18], + &intbuf[19]); + m_decimator2.myDecimate( + buf[pos+40], + buf[pos+41], + &intbuf[20], + &intbuf[21]); + m_decimator2.myDecimate( + buf[pos+44], + buf[pos+45], + &intbuf[22], + &intbuf[23]); + m_decimator2.myDecimate( + buf[pos+48], + buf[pos+49], + &intbuf[24], + &intbuf[25]); + m_decimator2.myDecimate( + buf[pos+52], + buf[pos+53], + &intbuf[26], + &intbuf[27]); + m_decimator2.myDecimate( + buf[pos+56], + buf[pos+57], + &intbuf[28], + &intbuf[29]); + m_decimator2.myDecimate( + buf[pos+60], + buf[pos+61], + &intbuf[30], + &intbuf[31]); + m_decimator2.myDecimate( + buf[pos+64], + buf[pos+65], + &intbuf[32], + &intbuf[33]); + m_decimator2.myDecimate( + buf[pos+68], + buf[pos+69], + &intbuf[34], + &intbuf[35]); + m_decimator2.myDecimate( + buf[pos+72], + buf[pos+73], + &intbuf[36], + &intbuf[37]); + m_decimator2.myDecimate( + buf[pos+76], + buf[pos+77], + &intbuf[38], + &intbuf[39]); + m_decimator2.myDecimate( + buf[pos+80], + buf[pos+81], + &intbuf[40], + &intbuf[41]); + m_decimator2.myDecimate( + buf[pos+84], + buf[pos+85], + &intbuf[42], + &intbuf[43]); + m_decimator2.myDecimate( + buf[pos+88], + buf[pos+89], + &intbuf[44], + &intbuf[45]); + m_decimator2.myDecimate( + buf[pos+92], + buf[pos+93], + &intbuf[46], + &intbuf[47]); + m_decimator2.myDecimate( + buf[pos+96], + buf[pos+97], + &intbuf[48], + &intbuf[49]); + m_decimator2.myDecimate( + buf[pos+100], + buf[pos+101], + &intbuf[50], + &intbuf[51]); + m_decimator2.myDecimate( + buf[pos+104], + buf[pos+105], + &intbuf[52], + &intbuf[53]); + m_decimator2.myDecimate( + buf[pos+108], + buf[pos+109], + &intbuf[54], + &intbuf[55]); + m_decimator2.myDecimate( + buf[pos+112], + buf[pos+113], + &intbuf[56], + &intbuf[57]); + m_decimator2.myDecimate( + buf[pos+116], + buf[pos+117], + &intbuf[58], + &intbuf[59]); + m_decimator2.myDecimate( + buf[pos+120], + buf[pos+121], + &intbuf[60], + &intbuf[61]); + m_decimator2.myDecimate( + buf[pos+124], + buf[pos+125], + &intbuf[62], + &intbuf[63]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + m_decimator4.myDecimate( + intbuf[16], + intbuf[17], + &intbuf[18], + &intbuf[19]); + m_decimator4.myDecimate( + intbuf[20], + intbuf[21], + &intbuf[22], + &intbuf[23]); + m_decimator4.myDecimate( + intbuf[24], + intbuf[25], + &intbuf[26], + &intbuf[27]); + m_decimator4.myDecimate( + intbuf[28], + intbuf[29], + &intbuf[30], + &intbuf[31]); + m_decimator4.myDecimate( + intbuf[32], + intbuf[33], + &intbuf[34], + &intbuf[35]); + m_decimator4.myDecimate( + intbuf[36], + intbuf[37], + &intbuf[38], + &intbuf[39]); + m_decimator4.myDecimate( + intbuf[40], + intbuf[41], + &intbuf[42], + &intbuf[43]); + m_decimator4.myDecimate( + intbuf[44], + intbuf[45], + &intbuf[46], + &intbuf[47]); + m_decimator4.myDecimate( + intbuf[48], + intbuf[49], + &intbuf[50], + &intbuf[51]); + m_decimator4.myDecimate( + intbuf[52], + intbuf[53], + &intbuf[54], + &intbuf[55]); + m_decimator4.myDecimate( + intbuf[56], + intbuf[57], + &intbuf[58], + &intbuf[59]); + m_decimator4.myDecimate( + intbuf[60], + intbuf[61], + &intbuf[62], + &intbuf[63]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + m_decimator8.myDecimate( + intbuf[18], + intbuf[19], + &intbuf[22], + &intbuf[23]); + m_decimator8.myDecimate( + intbuf[26], + intbuf[27], + &intbuf[30], + &intbuf[31]); + m_decimator8.myDecimate( + intbuf[34], + intbuf[35], + &intbuf[38], + &intbuf[39]); + m_decimator8.myDecimate( + intbuf[42], + intbuf[43], + &intbuf[46], + &intbuf[47]); + m_decimator8.myDecimate( + intbuf[50], + intbuf[51], + &intbuf[54], + &intbuf[55]); + m_decimator8.myDecimate( + intbuf[58], + intbuf[59], + &intbuf[62], + &intbuf[63]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + m_decimator16.myDecimate( + intbuf[22], + intbuf[23], + &intbuf[30], + &intbuf[31]); + m_decimator16.myDecimate( + intbuf[38], + intbuf[39], + &intbuf[46], + &intbuf[47]); + m_decimator16.myDecimate( + intbuf[54], + intbuf[55], + &intbuf[62], + &intbuf[63]); + + m_decimator32.myDecimate( + intbuf[14], + intbuf[15], + &intbuf[30], + &intbuf[31]); + m_decimator32.myDecimate( + intbuf[46], + intbuf[47], + &intbuf[62], + &intbuf[63]); + + m_decimator64.myDecimate( + intbuf[30], + intbuf[31], + &intbuf[62], + &intbuf[63]); + + (**it).setReal(intbuf[62]); + (**it).setImag(intbuf[63]); + ++(*it); + } +} + diff --git a/sdrbase/dsp/decimatorsff.h b/sdrbase/dsp/decimatorsff.h new file mode 100644 index 000000000..ca491ee58 --- /dev/null +++ b/sdrbase/dsp/decimatorsff.h @@ -0,0 +1,59 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_DECIMATORSFF_H_ +#define SDRBASE_DSP_DECIMATORSFF_H_ + +#include "dsp/inthalfbandfiltereof.h" +#include "export.h" + +#define DECIMATORSFF_HB_FILTER_ORDER 64 + +/** Decimators with float input and float output */ +class SDRBASE_API DecimatorsFF +{ +public: + void decimate1(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate2_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate2_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate2_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate4_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate4_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate4_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate8_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate8_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate8_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate16_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate16_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate16_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate32_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate32_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate32_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate64_inf(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate64_sup(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + void decimate64_cen(FSampleVector::iterator* it, const float* buf, qint32 nbIAndQ); + + IntHalfbandFilterEOF m_decimator2; // 1st stages + IntHalfbandFilterEOF m_decimator4; // 2nd stages + IntHalfbandFilterEOF m_decimator8; // 3rd stages + IntHalfbandFilterEOF m_decimator16; // 4th stages + IntHalfbandFilterEOF m_decimator32; // 5th stages + IntHalfbandFilterEOF m_decimator64; // 6th stages +}; + + + +#endif /* SDRBASE_DSP_DECIMATORSFF_H_ */ diff --git a/sdrbase/dsp/decimatorsf.cpp b/sdrbase/dsp/decimatorsfi.cpp similarity index 94% rename from sdrbase/dsp/decimatorsf.cpp rename to sdrbase/dsp/decimatorsfi.cpp index dd637ce70..e4e2d8451 100644 --- a/sdrbase/dsp/decimatorsf.cpp +++ b/sdrbase/dsp/decimatorsfi.cpp @@ -14,9 +14,9 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "decimatorsf.h" +#include "decimatorsfi.h" -void DecimatorsF::decimate1(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate1(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { float xreal, yimag; @@ -30,9 +30,9 @@ void DecimatorsF::decimate1(SampleVector::iterator* it, const float* buf, qint32 } } -void DecimatorsF::decimate2_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate2_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double intbuf[2]; + float intbuf[2]; for (int pos = 0; pos < nbIAndQ - 3; pos += 4) { @@ -52,9 +52,9 @@ void DecimatorsF::decimate2_cen(SampleVector::iterator* it, const float* buf, qi } } -void DecimatorsF::decimate2_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate2_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double xreal, yimag; + float xreal, yimag; for (int pos = 0; pos < nbIAndQ - 7; pos += 8) { @@ -72,9 +72,9 @@ void DecimatorsF::decimate2_inf(SampleVector::iterator* it, const float* buf, qi } } -void DecimatorsF::decimate2_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate2_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double xreal, yimag; + float xreal, yimag; for (int pos = 0; pos < nbIAndQ - 7; pos += 8) { @@ -92,9 +92,9 @@ void DecimatorsF::decimate2_sup(SampleVector::iterator* it, const float* buf, qi } } -void DecimatorsF::decimate4_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate4_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double xreal, yimag; + float xreal, yimag; for (int pos = 0; pos < nbIAndQ - 7; pos += 8) { @@ -108,7 +108,7 @@ void DecimatorsF::decimate4_inf(SampleVector::iterator* it, const float* buf, qi } } -void DecimatorsF::decimate4_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate4_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { // Sup (USB): // x y x y x y x y / x -> 1,-2,-5,6 / y -> -0,-3,4,7 @@ -116,7 +116,7 @@ void DecimatorsF::decimate4_sup(SampleVector::iterator* it, const float* buf, qi // Inf (LSB): // x y x y x y x y / x -> 0,-3,-4,7 / y -> 1,2,-5,-6 // [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] - double xreal, yimag; + float xreal, yimag; for (int pos = 0; pos < nbIAndQ - 7; pos += 8) { @@ -130,9 +130,9 @@ void DecimatorsF::decimate4_sup(SampleVector::iterator* it, const float* buf, qi } } -void DecimatorsF::decimate8_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate8_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double xreal[2], yimag[2]; + float xreal[2], yimag[2]; for (int pos = 0; pos < nbIAndQ - 15; pos += 8) { @@ -152,9 +152,9 @@ void DecimatorsF::decimate8_inf(SampleVector::iterator* it, const float* buf, qi } } -void DecimatorsF::decimate8_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate8_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double xreal[2], yimag[2]; + float xreal[2], yimag[2]; for (int pos = 0; pos < nbIAndQ - 15; pos += 8) { @@ -174,11 +174,11 @@ void DecimatorsF::decimate8_sup(SampleVector::iterator* it, const float* buf, qi } } -void DecimatorsF::decimate16_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate16_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { // Offset tuning: 4x downsample and rotate, then // downsample 4x more. [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] - double xreal[4], yimag[4]; + float xreal[4], yimag[4]; for (int pos = 0; pos < nbIAndQ - 31; ) { @@ -201,11 +201,11 @@ void DecimatorsF::decimate16_inf(SampleVector::iterator* it, const float* buf, q } } -void DecimatorsF::decimate16_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate16_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { // Offset tuning: 4x downsample and rotate, then // downsample 4x more. [ rotate: 1, 0, -2, 3, -5, -4, 6, -7] - double xreal[4], yimag[4]; + float xreal[4], yimag[4]; for (int pos = 0; pos < nbIAndQ - 31; ) { @@ -228,9 +228,9 @@ void DecimatorsF::decimate16_sup(SampleVector::iterator* it, const float* buf, q } } -void DecimatorsF::decimate32_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate32_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double xreal[8], yimag[8]; + float xreal[8], yimag[8]; for (int pos = 0; pos < nbIAndQ - 63; ) { @@ -258,9 +258,9 @@ void DecimatorsF::decimate32_inf(SampleVector::iterator* it, const float* buf, q } } -void DecimatorsF::decimate32_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate32_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double xreal[8], yimag[8]; + float xreal[8], yimag[8]; for (int pos = 0; pos < nbIAndQ - 63; ) { @@ -288,9 +288,9 @@ void DecimatorsF::decimate32_sup(SampleVector::iterator* it, const float* buf, q } } -void DecimatorsF::decimate64_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate64_inf(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double xreal[16], yimag[16]; + float xreal[16], yimag[16]; for (int pos = 0; pos < nbIAndQ - 127; ) { @@ -327,9 +327,9 @@ void DecimatorsF::decimate64_inf(SampleVector::iterator* it, const float* buf, q } } -void DecimatorsF::decimate64_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate64_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double xreal[16], yimag[16]; + float xreal[16], yimag[16]; for (int pos = 0; pos < nbIAndQ - 127; ) { @@ -366,9 +366,9 @@ void DecimatorsF::decimate64_sup(SampleVector::iterator* it, const float* buf, q } } -void DecimatorsF::decimate4_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate4_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double intbuf[4]; + float intbuf[4]; for (int pos = 0; pos < nbIAndQ - 7; pos += 8) { @@ -400,9 +400,9 @@ void DecimatorsF::decimate4_cen(SampleVector::iterator* it, const float* buf, qi } } -void DecimatorsF::decimate8_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate8_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double intbuf[8]; + float intbuf[8]; for (int pos = 0; pos < nbIAndQ - 15; pos += 16) { @@ -459,9 +459,9 @@ void DecimatorsF::decimate8_cen(SampleVector::iterator* it, const float* buf, qi } } -void DecimatorsF::decimate16_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate16_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double intbuf[16]; + float intbuf[16]; for (int pos = 0; pos < nbIAndQ - 31; pos += 32) { @@ -567,9 +567,9 @@ void DecimatorsF::decimate16_cen(SampleVector::iterator* it, const float* buf, q } } -void DecimatorsF::decimate32_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate32_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double intbuf[32]; + float intbuf[32]; for (int pos = 0; pos < nbIAndQ - 63; pos += 64) { @@ -772,9 +772,9 @@ void DecimatorsF::decimate32_cen(SampleVector::iterator* it, const float* buf, q } } -void DecimatorsF::decimate64_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) +void DecimatorsFI::decimate64_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ) { - double intbuf[64]; + float intbuf[64]; for (int pos = 0; pos < nbIAndQ - 127; pos += 128) { diff --git a/sdrbase/dsp/decimatorsf.h b/sdrbase/dsp/decimatorsfi.h similarity index 78% rename from sdrbase/dsp/decimatorsf.h rename to sdrbase/dsp/decimatorsfi.h index 3c631ee1e..cfeb85a82 100644 --- a/sdrbase/dsp/decimatorsf.h +++ b/sdrbase/dsp/decimatorsfi.h @@ -14,14 +14,16 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef SDRBASE_DSP_DECIMATORSF_H_ -#define SDRBASE_DSP_DECIMATORSF_H_ +#ifndef SDRBASE_DSP_DECIMATORSFI_H_ +#define SDRBASE_DSP_DECIMATORSFI_H_ -#include "dsp/inthalfbandfilterdbf.h" +#include "dsp/inthalfbandfiltereof.h" +#include "export.h" -#define DECIMATORSF_HB_FILTER_ORDER 64 +#define DECIMATORSFI_HB_FILTER_ORDER 64 -class DecimatorsF +/** Decimators with float input and integer output */ +class SDRBASE_API DecimatorsFI { public: void decimate1(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); @@ -44,14 +46,14 @@ public: void decimate64_sup(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); void decimate64_cen(SampleVector::iterator* it, const float* buf, qint32 nbIAndQ); - IntHalfbandFilterDBF m_decimator2; // 1st stages - IntHalfbandFilterDBF m_decimator4; // 2nd stages - IntHalfbandFilterDBF m_decimator8; // 3rd stages - IntHalfbandFilterDBF m_decimator16; // 4th stages - IntHalfbandFilterDBF m_decimator32; // 5th stages - IntHalfbandFilterDBF m_decimator64; // 6th stages + IntHalfbandFilterEOF m_decimator2; // 1st stages + IntHalfbandFilterEOF m_decimator4; // 2nd stages + IntHalfbandFilterEOF m_decimator8; // 3rd stages + IntHalfbandFilterEOF m_decimator16; // 4th stages + IntHalfbandFilterEOF m_decimator32; // 5th stages + IntHalfbandFilterEOF m_decimator64; // 6th stages }; -#endif /* SDRBASE_DSP_DECIMATORSF_H_ */ +#endif /* SDRBASE_DSP_DECIMATORSFI_H_ */ diff --git a/sdrbase/dsp/decimatorsif.cpp b/sdrbase/dsp/decimatorsif.cpp new file mode 100644 index 000000000..302669d68 --- /dev/null +++ b/sdrbase/dsp/decimatorsif.cpp @@ -0,0 +1,22 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "decimatorsif.h" + +const float decimation_scale<8>::scaleIn = 1.0/128.0; +const float decimation_scale<12>::scaleIn = 1.0/2048.0; +const float decimation_scale<16>::scaleIn = 1.0/32768.0; + diff --git a/sdrbase/dsp/decimatorsif.h b/sdrbase/dsp/decimatorsif.h new file mode 100644 index 000000000..7cd72e1e4 --- /dev/null +++ b/sdrbase/dsp/decimatorsif.h @@ -0,0 +1,1249 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_DECIMATORSIF_H_ +#define SDRBASE_DSP_DECIMATORSIF_H_ + +#include "dsp/dsptypes.h" +#include "dsp/inthalfbandfiltereof.h" + +#define DECIMATORS_IF_FILTER_ORDER 64 + +template +struct decimation_scale +{ + static const float scaleIn; +}; + +template +const float decimation_scale::scaleIn = 1.0; + +template<> +struct decimation_scale<8> +{ + static const float scaleIn; +}; + +template<> +struct decimation_scale<12> +{ + static const float scaleIn; +}; + +template<> +struct decimation_scale<16> +{ + static const float scaleIn; +}; + + +template +class DecimatorsIF { +public: + // interleaved I/Q input buffer + void decimate1(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate2_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate2_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate2_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate4_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate4_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate4_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate8_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate8_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate8_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate16_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate16_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate16_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate32_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate32_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate32_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate64_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate64_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + void decimate64_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ); + + IntHalfbandFilterEOF m_decimator2; // 1st stages + IntHalfbandFilterEOF m_decimator4; // 2nd stages + IntHalfbandFilterEOF m_decimator8; // 3rd stages + IntHalfbandFilterEOF m_decimator16; // 4th stages + IntHalfbandFilterEOF m_decimator32; // 5th stages + IntHalfbandFilterEOF m_decimator64; // 6th stages +}; + +template +void DecimatorsIF::decimate1(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 1; pos += 2) + { + xreal = buf[pos+0] * decimation_scale::scaleIn; + yimag = buf[pos+1] * decimation_scale::scaleIn; + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); // Valgrind optim (comment not repeated) + } +} + +template +void DecimatorsIF::decimate2_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (buf[pos+0] - buf[pos+3]) * decimation_scale::scaleIn; + yimag = (buf[pos+1] + buf[pos+2]) * decimation_scale::scaleIn; + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); + + xreal = (buf[pos+7] - buf[pos+4]) * decimation_scale::scaleIn; + yimag = (- buf[pos+5] - buf[pos+6]) * decimation_scale::scaleIn; + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); + } +} + +template +void DecimatorsIF::decimate2_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (buf[pos+1] - buf[pos+2]) * decimation_scale::scaleIn; + yimag = (- buf[pos+0] - buf[pos+3]) * decimation_scale::scaleIn; + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); + + xreal = (buf[pos+6] - buf[pos+5]) * decimation_scale::scaleIn; + yimag = (buf[pos+4] + buf[pos+7]) * decimation_scale::scaleIn; + (**it).setReal(xreal); + (**it).setImag(yimag); + ++(*it); + } +} + +template +void DecimatorsIF::decimate2_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float intbuf[2]; + + for (int pos = 0; pos < nbIAndQ - 3; pos += 4) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + + (**it).setReal(intbuf[0] * decimation_scale::scaleIn); + (**it).setImag(intbuf[1] * decimation_scale::scaleIn); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate4_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) * decimation_scale::scaleIn; + yimag = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) * decimation_scale::scaleIn; + + (**it).setReal(xreal); + (**it).setImag(yimag); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate4_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal, yimag; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + xreal = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) * decimation_scale::scaleIn; + yimag = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]) * decimation_scale::scaleIn; + + (**it).setReal(xreal); + (**it).setImag(yimag); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate4_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float intbuf[4]; + + for (int pos = 0; pos < nbIAndQ - 7; pos += 8) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + + (**it).setReal(intbuf[2] * decimation_scale::scaleIn); + (**it).setImag(intbuf[3] * decimation_scale::scaleIn); + ++(*it); + } +} + +template +void DecimatorsIF::decimate8_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal[2], yimag[2]; + + for (int pos = 0; pos < nbIAndQ - 15; pos += 8) + { + xreal[0] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[0] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + pos += 8; + + xreal[1] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[1] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + + (**it).setReal(xreal[1] * decimation_scale::scaleIn); + (**it).setImag(yimag[1] * decimation_scale::scaleIn); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate8_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal[2], yimag[2]; + + for (int pos = 0; pos < nbIAndQ - 15; pos += 8) + { + xreal[0] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[0] = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]); + pos += 8; + + xreal[1] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[1] = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]); + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + + (**it).setReal(xreal[1] * decimation_scale::scaleIn); + (**it).setImag(yimag[1] * decimation_scale::scaleIn); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate8_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float intbuf[8]; + + for (int pos = 0; pos < nbIAndQ - 15; pos += 16) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + intbuf[4] = buf[pos+10]; + intbuf[5] = buf[pos+11]; + intbuf[6] = buf[pos+14]; + intbuf[7] = buf[pos+15]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8], + buf[pos+9], + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12], + buf[pos+13], + &intbuf[6], + &intbuf[7]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + + (**it).setReal(intbuf[6] * decimation_scale::scaleIn); + (**it).setImag(intbuf[7] * decimation_scale::scaleIn); + ++(*it); + } +} + +template +void DecimatorsIF::decimate16_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal[4], yimag[4]; + + for (int pos = 0; pos < nbIAndQ - 31; ) + { + for (int i = 0; i < 4; i++) + { + xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + pos += 8; + } + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + + (**it).setReal(xreal[3] * decimation_scale::scaleIn); + (**it).setImag(yimag[3] * decimation_scale::scaleIn); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate16_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal[4], yimag[4]; + + for (int pos = 0; pos < nbIAndQ - 31; ) + { + for (int i = 0; i < 4; i++) + { + xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]); + pos += 8; + } + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + + (**it).setReal(xreal[3] * decimation_scale::scaleIn); + (**it).setImag(yimag[3] * decimation_scale::scaleIn); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate16_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float intbuf[16]; + + for (int pos = 0; pos < nbIAndQ - 31; pos += 32) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + intbuf[4] = buf[pos+10]; + intbuf[5] = buf[pos+11]; + intbuf[6] = buf[pos+14]; + intbuf[7] = buf[pos+15]; + intbuf[8] = buf[pos+18]; + intbuf[9] = buf[pos+19]; + intbuf[10] = buf[pos+22]; + intbuf[11] = buf[pos+23]; + intbuf[12] = buf[pos+26]; + intbuf[13] = buf[pos+27]; + intbuf[14] = buf[pos+30]; + intbuf[15] = buf[pos+31]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8], + buf[pos+9], + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12], + buf[pos+13], + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + buf[pos+16], + buf[pos+17], + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + buf[pos+20], + buf[pos+21], + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + buf[pos+24], + buf[pos+25], + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + buf[pos+28], + buf[pos+29], + &intbuf[14], + &intbuf[15]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + + (**it).setReal(intbuf[14] * decimation_scale::scaleIn); + (**it).setImag(intbuf[15] * decimation_scale::scaleIn); + ++(*it); + } +} + +template +void DecimatorsIF::decimate32_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal[8], yimag[8]; + + for (int pos = 0; pos < nbIAndQ - 63; ) + { + for (int i = 0; i < 8; i++) + { + xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + pos += 8; + } + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); + m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + + (**it).setReal(xreal[7] * decimation_scale::scaleIn); + (**it).setImag(yimag[7] * decimation_scale::scaleIn); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate32_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal[8], yimag[8]; + + for (int pos = 0; pos < nbIAndQ - 63; ) + { + for (int i = 0; i < 8; i++) + { + xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]); + pos += 8; + } + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); + m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + + (**it).setReal(xreal[7] * decimation_scale::scaleIn); + (**it).setImag(yimag[7] * decimation_scale::scaleIn); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate32_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float intbuf[32]; + + for (int pos = 0; pos < nbIAndQ - 63; pos += 64) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + intbuf[4] = buf[pos+10]; + intbuf[5] = buf[pos+11]; + intbuf[6] = buf[pos+14]; + intbuf[7] = buf[pos+15]; + intbuf[8] = buf[pos+18]; + intbuf[9] = buf[pos+19]; + intbuf[10] = buf[pos+22]; + intbuf[11] = buf[pos+23]; + intbuf[12] = buf[pos+26]; + intbuf[13] = buf[pos+27]; + intbuf[14] = buf[pos+30]; + intbuf[15] = buf[pos+31]; + intbuf[16] = buf[pos+34]; + intbuf[17] = buf[pos+35]; + intbuf[18] = buf[pos+38]; + intbuf[19] = buf[pos+39]; + intbuf[20] = buf[pos+42]; + intbuf[21] = buf[pos+43]; + intbuf[22] = buf[pos+46]; + intbuf[23] = buf[pos+47]; + intbuf[24] = buf[pos+50]; + intbuf[25] = buf[pos+51]; + intbuf[26] = buf[pos+54]; + intbuf[27] = buf[pos+55]; + intbuf[28] = buf[pos+58]; + intbuf[29] = buf[pos+59]; + intbuf[30] = buf[pos+62]; + intbuf[31] = buf[pos+63]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8], + buf[pos+9], + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12], + buf[pos+13], + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + buf[pos+16], + buf[pos+17], + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + buf[pos+20], + buf[pos+21], + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + buf[pos+24], + buf[pos+25], + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + buf[pos+28], + buf[pos+29], + &intbuf[14], + &intbuf[15]); + m_decimator2.myDecimate( + buf[pos+32], + buf[pos+33], + &intbuf[16], + &intbuf[17]); + m_decimator2.myDecimate( + buf[pos+36], + buf[pos+37], + &intbuf[18], + &intbuf[19]); + m_decimator2.myDecimate( + buf[pos+40], + buf[pos+41], + &intbuf[20], + &intbuf[21]); + m_decimator2.myDecimate( + buf[pos+44], + buf[pos+45], + &intbuf[22], + &intbuf[23]); + m_decimator2.myDecimate( + buf[pos+48], + buf[pos+49], + &intbuf[24], + &intbuf[25]); + m_decimator2.myDecimate( + buf[pos+52], + buf[pos+53], + &intbuf[26], + &intbuf[27]); + m_decimator2.myDecimate( + buf[pos+56], + buf[pos+57], + &intbuf[28], + &intbuf[29]); + m_decimator2.myDecimate( + buf[pos+60], + buf[pos+61], + &intbuf[30], + &intbuf[31]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + m_decimator4.myDecimate( + intbuf[16], + intbuf[17], + &intbuf[18], + &intbuf[19]); + m_decimator4.myDecimate( + intbuf[20], + intbuf[21], + &intbuf[22], + &intbuf[23]); + m_decimator4.myDecimate( + intbuf[24], + intbuf[25], + &intbuf[26], + &intbuf[27]); + m_decimator4.myDecimate( + intbuf[28], + intbuf[29], + &intbuf[30], + &intbuf[31]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + m_decimator8.myDecimate( + intbuf[18], + intbuf[19], + &intbuf[22], + &intbuf[23]); + m_decimator8.myDecimate( + intbuf[26], + intbuf[27], + &intbuf[30], + &intbuf[31]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + m_decimator16.myDecimate( + intbuf[22], + intbuf[23], + &intbuf[30], + &intbuf[31]); + + m_decimator32.myDecimate( + intbuf[14], + intbuf[15], + &intbuf[30], + &intbuf[31]); + + (**it).setReal(intbuf[30] * decimation_scale::scaleIn); + (**it).setImag(intbuf[31] * decimation_scale::scaleIn); + ++(*it); + } +} + +template +void DecimatorsIF::decimate64_inf(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal[16], yimag[16]; + + for (int pos = 0; pos < nbIAndQ - 127; ) + { + for (int i = 0; i < 16; i++) + { + xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]); + yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]); + pos += 8; + } + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); + m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + m_decimator2.myDecimate(xreal[8], yimag[8], &xreal[9], &yimag[9]); + m_decimator2.myDecimate(xreal[10], yimag[10], &xreal[11], &yimag[11]); + m_decimator2.myDecimate(xreal[12], yimag[12], &xreal[13], &yimag[13]); + m_decimator2.myDecimate(xreal[14], yimag[14], &xreal[15], &yimag[15]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + m_decimator4.myDecimate(xreal[9], yimag[9], &xreal[11], &yimag[11]); + m_decimator4.myDecimate(xreal[13], yimag[13], &xreal[15], &yimag[15]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + m_decimator8.myDecimate(xreal[11], yimag[11], &xreal[15], &yimag[15]); + + m_decimator16.myDecimate(xreal[7], yimag[7], &xreal[15], &yimag[15]); + + (**it).setReal(xreal[15] * decimation_scale::scaleIn); + (**it).setImag(yimag[15] * decimation_scale::scaleIn); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate64_sup(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float xreal[16], yimag[16]; + + for (int pos = 0; pos < nbIAndQ - 127; ) + { + for (int i = 0; i < 16; i++) + { + xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]); + yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]); + pos += 8; + } + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); + m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + m_decimator2.myDecimate(xreal[8], yimag[8], &xreal[9], &yimag[9]); + m_decimator2.myDecimate(xreal[10], yimag[10], &xreal[11], &yimag[11]); + m_decimator2.myDecimate(xreal[12], yimag[12], &xreal[13], &yimag[13]); + m_decimator2.myDecimate(xreal[14], yimag[14], &xreal[15], &yimag[15]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + m_decimator4.myDecimate(xreal[9], yimag[9], &xreal[11], &yimag[11]); + m_decimator4.myDecimate(xreal[13], yimag[13], &xreal[15], &yimag[15]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + m_decimator8.myDecimate(xreal[11], yimag[11], &xreal[15], &yimag[15]); + + m_decimator16.myDecimate(xreal[7], yimag[7], &xreal[15], &yimag[15]); + + (**it).setReal(xreal[15] * decimation_scale::scaleIn); + (**it).setImag(yimag[15] * decimation_scale::scaleIn); + + ++(*it); + } +} + +template +void DecimatorsIF::decimate64_cen(FSampleVector::iterator* it, const T* buf, qint32 nbIAndQ) +{ + float intbuf[64]; + + for (int pos = 0; pos < nbIAndQ - 127; pos += 128) + { + intbuf[0] = buf[pos+2]; + intbuf[1] = buf[pos+3]; + intbuf[2] = buf[pos+6]; + intbuf[3] = buf[pos+7]; + intbuf[4] = buf[pos+10]; + intbuf[5] = buf[pos+11]; + intbuf[6] = buf[pos+14]; + intbuf[7] = buf[pos+15]; + intbuf[8] = buf[pos+18]; + intbuf[9] = buf[pos+19]; + intbuf[10] = buf[pos+22]; + intbuf[11] = buf[pos+23]; + intbuf[12] = buf[pos+26]; + intbuf[13] = buf[pos+27]; + intbuf[14] = buf[pos+30]; + intbuf[15] = buf[pos+31]; + intbuf[16] = buf[pos+34]; + intbuf[17] = buf[pos+35]; + intbuf[18] = buf[pos+38]; + intbuf[19] = buf[pos+39]; + intbuf[20] = buf[pos+42]; + intbuf[21] = buf[pos+43]; + intbuf[22] = buf[pos+46]; + intbuf[23] = buf[pos+47]; + intbuf[24] = buf[pos+50]; + intbuf[25] = buf[pos+51]; + intbuf[26] = buf[pos+54]; + intbuf[27] = buf[pos+55]; + intbuf[28] = buf[pos+58]; + intbuf[29] = buf[pos+59]; + intbuf[30] = buf[pos+62]; + intbuf[31] = buf[pos+63]; + + intbuf[32] = buf[pos+66]; + intbuf[33] = buf[pos+67]; + intbuf[34] = buf[pos+70]; + intbuf[35] = buf[pos+71]; + intbuf[36] = buf[pos+74]; + intbuf[37] = buf[pos+75]; + intbuf[38] = buf[pos+78]; + intbuf[39] = buf[pos+79]; + intbuf[40] = buf[pos+82]; + intbuf[41] = buf[pos+83]; + intbuf[42] = buf[pos+86]; + intbuf[43] = buf[pos+87]; + intbuf[44] = buf[pos+90]; + intbuf[45] = buf[pos+91]; + intbuf[46] = buf[pos+94]; + intbuf[47] = buf[pos+95]; + intbuf[48] = buf[pos+98]; + intbuf[49] = buf[pos+99]; + intbuf[50] = buf[pos+102]; + intbuf[51] = buf[pos+103]; + intbuf[52] = buf[pos+106]; + intbuf[53] = buf[pos+107]; + intbuf[54] = buf[pos+110]; + intbuf[55] = buf[pos+111]; + intbuf[56] = buf[pos+114]; + intbuf[57] = buf[pos+115]; + intbuf[58] = buf[pos+118]; + intbuf[59] = buf[pos+119]; + intbuf[60] = buf[pos+122]; + intbuf[61] = buf[pos+123]; + intbuf[62] = buf[pos+126]; + intbuf[63] = buf[pos+127]; + + m_decimator2.myDecimate( + buf[pos+0], + buf[pos+1], + &intbuf[0], + &intbuf[1]); + m_decimator2.myDecimate( + buf[pos+4], + buf[pos+5], + &intbuf[2], + &intbuf[3]); + m_decimator2.myDecimate( + buf[pos+8], + buf[pos+9], + &intbuf[4], + &intbuf[5]); + m_decimator2.myDecimate( + buf[pos+12], + buf[pos+13], + &intbuf[6], + &intbuf[7]); + m_decimator2.myDecimate( + buf[pos+16], + buf[pos+17], + &intbuf[8], + &intbuf[9]); + m_decimator2.myDecimate( + buf[pos+20], + buf[pos+21], + &intbuf[10], + &intbuf[11]); + m_decimator2.myDecimate( + buf[pos+24], + buf[pos+25], + &intbuf[12], + &intbuf[13]); + m_decimator2.myDecimate( + buf[pos+28], + buf[pos+29], + &intbuf[14], + &intbuf[15]); + m_decimator2.myDecimate( + buf[pos+32], + buf[pos+33], + &intbuf[16], + &intbuf[17]); + m_decimator2.myDecimate( + buf[pos+36], + buf[pos+37], + &intbuf[18], + &intbuf[19]); + m_decimator2.myDecimate( + buf[pos+40], + buf[pos+41], + &intbuf[20], + &intbuf[21]); + m_decimator2.myDecimate( + buf[pos+44], + buf[pos+45], + &intbuf[22], + &intbuf[23]); + m_decimator2.myDecimate( + buf[pos+48], + buf[pos+49], + &intbuf[24], + &intbuf[25]); + m_decimator2.myDecimate( + buf[pos+52], + buf[pos+53], + &intbuf[26], + &intbuf[27]); + m_decimator2.myDecimate( + buf[pos+56], + buf[pos+57], + &intbuf[28], + &intbuf[29]); + m_decimator2.myDecimate( + buf[pos+60], + buf[pos+61], + &intbuf[30], + &intbuf[31]); + m_decimator2.myDecimate( + buf[pos+64], + buf[pos+65], + &intbuf[32], + &intbuf[33]); + m_decimator2.myDecimate( + buf[pos+68], + buf[pos+69], + &intbuf[34], + &intbuf[35]); + m_decimator2.myDecimate( + buf[pos+72], + buf[pos+73], + &intbuf[36], + &intbuf[37]); + m_decimator2.myDecimate( + buf[pos+76], + buf[pos+77], + &intbuf[38], + &intbuf[39]); + m_decimator2.myDecimate( + buf[pos+80], + buf[pos+81], + &intbuf[40], + &intbuf[41]); + m_decimator2.myDecimate( + buf[pos+84], + buf[pos+85], + &intbuf[42], + &intbuf[43]); + m_decimator2.myDecimate( + buf[pos+88], + buf[pos+89], + &intbuf[44], + &intbuf[45]); + m_decimator2.myDecimate( + buf[pos+92], + buf[pos+93], + &intbuf[46], + &intbuf[47]); + m_decimator2.myDecimate( + buf[pos+96], + buf[pos+97], + &intbuf[48], + &intbuf[49]); + m_decimator2.myDecimate( + buf[pos+100], + buf[pos+101], + &intbuf[50], + &intbuf[51]); + m_decimator2.myDecimate( + buf[pos+104], + buf[pos+105], + &intbuf[52], + &intbuf[53]); + m_decimator2.myDecimate( + buf[pos+108], + buf[pos+109], + &intbuf[54], + &intbuf[55]); + m_decimator2.myDecimate( + buf[pos+112], + buf[pos+113], + &intbuf[56], + &intbuf[57]); + m_decimator2.myDecimate( + buf[pos+116], + buf[pos+117], + &intbuf[58], + &intbuf[59]); + m_decimator2.myDecimate( + buf[pos+120], + buf[pos+121], + &intbuf[60], + &intbuf[61]); + m_decimator2.myDecimate( + buf[pos+124], + buf[pos+125], + &intbuf[62], + &intbuf[63]); + + m_decimator4.myDecimate( + intbuf[0], + intbuf[1], + &intbuf[2], + &intbuf[3]); + m_decimator4.myDecimate( + intbuf[4], + intbuf[5], + &intbuf[6], + &intbuf[7]); + m_decimator4.myDecimate( + intbuf[8], + intbuf[9], + &intbuf[10], + &intbuf[11]); + m_decimator4.myDecimate( + intbuf[12], + intbuf[13], + &intbuf[14], + &intbuf[15]); + m_decimator4.myDecimate( + intbuf[16], + intbuf[17], + &intbuf[18], + &intbuf[19]); + m_decimator4.myDecimate( + intbuf[20], + intbuf[21], + &intbuf[22], + &intbuf[23]); + m_decimator4.myDecimate( + intbuf[24], + intbuf[25], + &intbuf[26], + &intbuf[27]); + m_decimator4.myDecimate( + intbuf[28], + intbuf[29], + &intbuf[30], + &intbuf[31]); + m_decimator4.myDecimate( + intbuf[32], + intbuf[33], + &intbuf[34], + &intbuf[35]); + m_decimator4.myDecimate( + intbuf[36], + intbuf[37], + &intbuf[38], + &intbuf[39]); + m_decimator4.myDecimate( + intbuf[40], + intbuf[41], + &intbuf[42], + &intbuf[43]); + m_decimator4.myDecimate( + intbuf[44], + intbuf[45], + &intbuf[46], + &intbuf[47]); + m_decimator4.myDecimate( + intbuf[48], + intbuf[49], + &intbuf[50], + &intbuf[51]); + m_decimator4.myDecimate( + intbuf[52], + intbuf[53], + &intbuf[54], + &intbuf[55]); + m_decimator4.myDecimate( + intbuf[56], + intbuf[57], + &intbuf[58], + &intbuf[59]); + m_decimator4.myDecimate( + intbuf[60], + intbuf[61], + &intbuf[62], + &intbuf[63]); + + m_decimator8.myDecimate( + intbuf[2], + intbuf[3], + &intbuf[6], + &intbuf[7]); + m_decimator8.myDecimate( + intbuf[10], + intbuf[11], + &intbuf[14], + &intbuf[15]); + m_decimator8.myDecimate( + intbuf[18], + intbuf[19], + &intbuf[22], + &intbuf[23]); + m_decimator8.myDecimate( + intbuf[26], + intbuf[27], + &intbuf[30], + &intbuf[31]); + m_decimator8.myDecimate( + intbuf[34], + intbuf[35], + &intbuf[38], + &intbuf[39]); + m_decimator8.myDecimate( + intbuf[42], + intbuf[43], + &intbuf[46], + &intbuf[47]); + m_decimator8.myDecimate( + intbuf[50], + intbuf[51], + &intbuf[54], + &intbuf[55]); + m_decimator8.myDecimate( + intbuf[58], + intbuf[59], + &intbuf[62], + &intbuf[63]); + + m_decimator16.myDecimate( + intbuf[6], + intbuf[7], + &intbuf[14], + &intbuf[15]); + m_decimator16.myDecimate( + intbuf[22], + intbuf[23], + &intbuf[30], + &intbuf[31]); + m_decimator16.myDecimate( + intbuf[38], + intbuf[39], + &intbuf[46], + &intbuf[47]); + m_decimator16.myDecimate( + intbuf[54], + intbuf[55], + &intbuf[62], + &intbuf[63]); + + m_decimator32.myDecimate( + intbuf[14], + intbuf[15], + &intbuf[30], + &intbuf[31]); + m_decimator32.myDecimate( + intbuf[46], + intbuf[47], + &intbuf[62], + &intbuf[63]); + + m_decimator64.myDecimate( + intbuf[30], + intbuf[31], + &intbuf[62], + &intbuf[63]); + + (**it).setReal(intbuf[62] * decimation_scale::scaleIn); + (**it).setImag(intbuf[63] * decimation_scale::scaleIn); + ++(*it); + } +} + +#endif /* SDRBASE_DSP_DECIMATORSIF_H_ */ diff --git a/sdrbase/dsp/decimatorsu.h b/sdrbase/dsp/decimatorsu.h index 059e7dcf3..36f897634 100644 --- a/sdrbase/dsp/decimatorsu.h +++ b/sdrbase/dsp/decimatorsu.h @@ -24,15 +24,7 @@ #define INCLUDE_GPL_DSP_DECIMATORSU_H_ #include "dsp/dsptypes.h" -#ifdef SDR_RX_SAMPLE_24BIT -#include "dsp/inthalfbandfilterdb.h" -#else -#ifdef USE_SSE4_1 -#include "dsp/inthalfbandfiltereo1.h" -#else -#include "dsp/inthalfbandfilterdb.h" -#endif -#endif +#include "dsp/inthalfbandfiltereo.h" #define DECIMATORS_HB_FILTER_ORDER 64 @@ -180,7 +172,7 @@ struct decimation_shifts<24, 8> static const uint post64 = 0; }; -template +template class DecimatorsU { public: @@ -207,33 +199,24 @@ public: private: #ifdef SDR_RX_SAMPLE_24BIT - IntHalfbandFilterDB m_decimator2; // 1st stages - IntHalfbandFilterDB m_decimator4; // 2nd stages - IntHalfbandFilterDB m_decimator8; // 3rd stages - IntHalfbandFilterDB m_decimator16; // 4th stages - IntHalfbandFilterDB m_decimator32; // 5th stages - IntHalfbandFilterDB m_decimator64; // 6th stages + IntHalfbandFilterEO m_decimator2; // 1st stages + IntHalfbandFilterEO m_decimator4; // 2nd stages + IntHalfbandFilterEO m_decimator8; // 3rd stages + IntHalfbandFilterEO m_decimator16; // 4th stages + IntHalfbandFilterEO m_decimator32; // 5th stages + IntHalfbandFilterEO m_decimator64; // 6th stages #else -#ifdef USE_SSE4_1 - IntHalfbandFilterEO1 m_decimator2; // 1st stages - IntHalfbandFilterEO1 m_decimator4; // 2nd stages - IntHalfbandFilterEO1 m_decimator8; // 3rd stages - IntHalfbandFilterEO1 m_decimator16; // 4th stages - IntHalfbandFilterEO1 m_decimator32; // 5th stages - IntHalfbandFilterEO1 m_decimator64; // 6th stages -#else - IntHalfbandFilterDB m_decimator2; // 1st stages - IntHalfbandFilterDB m_decimator4; // 2nd stages - IntHalfbandFilterDB m_decimator8; // 3rd stages - IntHalfbandFilterDB m_decimator16; // 4th stages - IntHalfbandFilterDB m_decimator32; // 5th stages - IntHalfbandFilterDB m_decimator64; // 6th stages -#endif + IntHalfbandFilterEO m_decimator2; // 1st stages + IntHalfbandFilterEO m_decimator4; // 2nd stages + IntHalfbandFilterEO m_decimator8; // 3rd stages + IntHalfbandFilterEO m_decimator16; // 4th stages + IntHalfbandFilterEO m_decimator32; // 5th stages + IntHalfbandFilterEO m_decimator64; // 6th stages #endif }; -template -void DecimatorsU::decimate1(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate1(SampleVector::iterator* it, const T* buf, qint32 len) { qint32 xreal, yimag; @@ -247,393 +230,2253 @@ void DecimatorsU::decimate1(SampleVector } } -template -void DecimatorsU::decimate2_inf(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate2_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - qint32 xreal, yimag; + StorageType buf2[4]; - for (int pos = 0; pos < len - 7; pos += 8) - { - xreal = (buf[pos+0] - buf[pos+3]) << decimation_shifts::pre2; - yimag = (buf[pos+1] + buf[pos+2] - 2*Shift) << decimation_shifts::pre2; - (**it).setReal(xreal >> decimation_shifts::post2); - (**it).setImag(yimag >> decimation_shifts::post2); - ++(*it); + for (int pos = 0; pos < len - 7; pos += 8) + { + m_decimator2.myDecimateInf( + (buf[pos+0] - Shift) << decimation_shifts::pre2, + (buf[pos+1] - Shift) << decimation_shifts::pre2, + (buf[pos+2] - Shift) << decimation_shifts::pre2, + (buf[pos+3] - Shift) << decimation_shifts::pre2, + (buf[pos+4] - Shift) << decimation_shifts::pre2, + (buf[pos+5] - Shift) << decimation_shifts::pre2, + (buf[pos+6] - Shift) << decimation_shifts::pre2, + (buf[pos+7] - Shift) << decimation_shifts::pre2, + &buf2[0]); - xreal = (buf[pos+7] - buf[pos+4]) << decimation_shifts::pre2; - yimag = (2*Shift - buf[pos+5] - buf[pos+6]) << decimation_shifts::pre2; - (**it).setReal(xreal >> decimation_shifts::post2); - (**it).setImag(yimag >> decimation_shifts::post2); - ++(*it); - } + (**it).setReal(buf2[0] >> decimation_shifts::post2); + (**it).setImag(buf2[1] >> decimation_shifts::post2); + ++(*it); + + (**it).setReal(buf2[2] >> decimation_shifts::post2); + (**it).setImag(buf2[3] >> decimation_shifts::post2); + ++(*it); + } } -template -void DecimatorsU::decimate2_sup(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate2_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal, yimag; + StorageType buf2[4]; - for (int pos = 0; pos < len - 7; pos += 8) - { - xreal = (buf[pos+1] - buf[pos+2]) << decimation_shifts::pre2; - yimag = (2*Shift - buf[pos+0] - buf[pos+3]) << decimation_shifts::pre2; - (**it).setReal(xreal >> decimation_shifts::post2); - (**it).setImag(yimag >> decimation_shifts::post2); - ++(*it); + for (int pos = 0; pos < len - 7; pos += 8) + { + m_decimator2.myDecimateSup( + (buf[pos+0] - Shift) << decimation_shifts::pre2, + (buf[pos+1] - Shift) << decimation_shifts::pre2, + (buf[pos+2] - Shift) << decimation_shifts::pre2, + (buf[pos+3] - Shift) << decimation_shifts::pre2, + (buf[pos+4] - Shift) << decimation_shifts::pre2, + (buf[pos+5] - Shift) << decimation_shifts::pre2, + (buf[pos+6] - Shift) << decimation_shifts::pre2, + (buf[pos+7] - Shift) << decimation_shifts::pre2, + &buf2[0]); - xreal = (buf[pos+6] - buf[pos+5]) << decimation_shifts::pre2; - yimag = (buf[pos+4] + buf[pos+7] - 2*Shift) << decimation_shifts::pre2; - (**it).setReal(xreal >> decimation_shifts::post2); - (**it).setImag(yimag >> decimation_shifts::post2); - ++(*it); - } + (**it).setReal(buf2[0] >> decimation_shifts::post2); + (**it).setImag(buf2[1] >> decimation_shifts::post2); + ++(*it); + + (**it).setReal(buf2[2] >> decimation_shifts::post2); + (**it).setImag(buf2[3] >> decimation_shifts::post2); + ++(*it); + } } -template -void DecimatorsU::decimate4_inf(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate4_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal, yimag; + StorageType buf2[8], buf4[4]; - for (int pos = 0; pos < len - 7; pos += 8) - { - xreal = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) << decimation_shifts::pre4; - yimag = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) << decimation_shifts::pre4; + for (int pos = 0; pos < len - 15; pos += 16) + { + m_decimator2.myDecimateInf( + (buf[pos+0] - Shift) << decimation_shifts::pre4, + (buf[pos+1] - Shift) << decimation_shifts::pre4, + (buf[pos+2] - Shift) << decimation_shifts::pre4, + (buf[pos+3] - Shift) << decimation_shifts::pre4, + (buf[pos+4] - Shift) << decimation_shifts::pre4, + (buf[pos+5] - Shift) << decimation_shifts::pre4, + (buf[pos+6] - Shift) << decimation_shifts::pre4, + (buf[pos+7] - Shift) << decimation_shifts::pre4, + &buf2[0]); - (**it).setReal(xreal >> decimation_shifts::post4); - (**it).setImag(yimag >> decimation_shifts::post4); + m_decimator2.myDecimateInf( + (buf[pos+8] - Shift) << decimation_shifts::pre4, + (buf[pos+9] - Shift) << decimation_shifts::pre4, + (buf[pos+10] - Shift) << decimation_shifts::pre4, + (buf[pos+11] - Shift) << decimation_shifts::pre4, + (buf[pos+12] - Shift) << decimation_shifts::pre4, + (buf[pos+13] - Shift) << decimation_shifts::pre4, + (buf[pos+14] - Shift) << decimation_shifts::pre4, + (buf[pos+15] - Shift) << decimation_shifts::pre4, + &buf2[4]); - ++(*it); - } + m_decimator4.myDecimateSup( + buf2[0], + buf2[1], + buf2[2], + buf2[3], + buf2[4], + buf2[5], + buf2[6], + buf2[7], + &buf4[0]); + + (**it).setReal(buf4[0] >> decimation_shifts::post4); + (**it).setImag(buf4[1] >> decimation_shifts::post4); + ++(*it); + + (**it).setReal(buf4[2] >> decimation_shifts::post4); + (**it).setImag(buf4[3] >> decimation_shifts::post4); + ++(*it); + } } -template -void DecimatorsU::decimate4_sup(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate4_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - // Sup (USB): - // x y x y x y x y / x -> 1,-2,-5,6 / y -> -0,-3,4,7 - // [ rotate: 1, 0, -2, 3, -5, -4, 6, -7] - // Inf (LSB): - // x y x y x y x y / x -> 0,-3,-4,7 / y -> 1,2,-5,-6 - // [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] - AccuType xreal, yimag; + StorageType buf2[8], buf4[4]; - for (int pos = 0; pos < len - 7; pos += 8) - { - xreal = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) << decimation_shifts::pre4; - yimag = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]) << decimation_shifts::pre4; + for (int pos = 0; pos < len - 15; pos += 16) + { + m_decimator2.myDecimateSup( + (buf[pos+0] - Shift) << decimation_shifts::pre4, + (buf[pos+1] - Shift) << decimation_shifts::pre4, + (buf[pos+2] - Shift) << decimation_shifts::pre4, + (buf[pos+3] - Shift) << decimation_shifts::pre4, + (buf[pos+4] - Shift) << decimation_shifts::pre4, + (buf[pos+5] - Shift) << decimation_shifts::pre4, + (buf[pos+6] - Shift) << decimation_shifts::pre4, + (buf[pos+7] - Shift) << decimation_shifts::pre4, + &buf2[0]); - (**it).setReal(xreal >> decimation_shifts::post4); - (**it).setImag(yimag >> decimation_shifts::post4); + m_decimator2.myDecimateSup( + (buf[pos+8] - Shift) << decimation_shifts::pre4, + (buf[pos+9] - Shift) << decimation_shifts::pre4, + (buf[pos+10] - Shift) << decimation_shifts::pre4, + (buf[pos+11] - Shift) << decimation_shifts::pre4, + (buf[pos+12] - Shift) << decimation_shifts::pre4, + (buf[pos+13] - Shift) << decimation_shifts::pre4, + (buf[pos+14] - Shift) << decimation_shifts::pre4, + (buf[pos+15] - Shift) << decimation_shifts::pre4, + &buf2[4]); - ++(*it); - } + m_decimator4.myDecimateInf( + buf2[0], + buf2[1], + buf2[2], + buf2[3], + buf2[4], + buf2[5], + buf2[6], + buf2[7], + &buf4[0]); + + (**it).setReal(buf4[0] >> decimation_shifts::post4); + (**it).setImag(buf4[1] >> decimation_shifts::post4); + ++(*it); + + (**it).setReal(buf4[2] >> decimation_shifts::post4); + (**it).setImag(buf4[3] >> decimation_shifts::post4); + ++(*it); + } } -template -void DecimatorsU::decimate8_inf(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate8_inf(SampleVector::iterator* it, const T* buf __attribute__((unused)), qint32 len) { - AccuType xreal[2], yimag[2]; + StorageType buf2[16], buf4[8], buf8[4]; - for (int pos = 0; pos < len - 15; pos += 8) - { - xreal[0] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) << decimation_shifts::pre8; - yimag[0] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) << decimation_shifts::pre8; - pos += 8; + for (int pos = 0; pos < len - 31; pos += 32) + { + m_decimator2.myDecimateInf( + (buf[pos+0] - Shift) << decimation_shifts::pre8, + (buf[pos+1] - Shift) << decimation_shifts::pre8, + (buf[pos+2] - Shift) << decimation_shifts::pre8, + (buf[pos+3] - Shift) << decimation_shifts::pre8, + (buf[pos+4] - Shift) << decimation_shifts::pre8, + (buf[pos+5] - Shift) << decimation_shifts::pre8, + (buf[pos+6] - Shift) << decimation_shifts::pre8, + (buf[pos+7] - Shift) << decimation_shifts::pre8, + &buf2[0]); - xreal[1] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) << decimation_shifts::pre8; - yimag[1] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) << decimation_shifts::pre8; - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimateInf( + (buf[pos+8] - Shift) << decimation_shifts::pre8, + (buf[pos+9] - Shift) << decimation_shifts::pre8, + (buf[pos+10] - Shift) << decimation_shifts::pre8, + (buf[pos+11] - Shift) << decimation_shifts::pre8, + (buf[pos+12] - Shift) << decimation_shifts::pre8, + (buf[pos+13] - Shift) << decimation_shifts::pre8, + (buf[pos+14] - Shift) << decimation_shifts::pre8, + (buf[pos+15] - Shift) << decimation_shifts::pre8, + &buf2[4]); - (**it).setReal(xreal[1] >> decimation_shifts::post8); - (**it).setImag(yimag[1] >> decimation_shifts::post8); - ++(*it); - } + m_decimator2.myDecimateInf( + (buf[pos+16] - Shift) << decimation_shifts::pre8, + (buf[pos+17] - Shift) << decimation_shifts::pre8, + (buf[pos+18] - Shift) << decimation_shifts::pre8, + (buf[pos+19] - Shift) << decimation_shifts::pre8, + (buf[pos+20] - Shift) << decimation_shifts::pre8, + (buf[pos+21] - Shift) << decimation_shifts::pre8, + (buf[pos+22] - Shift) << decimation_shifts::pre8, + (buf[pos+23] - Shift) << decimation_shifts::pre8, + &buf2[8]); + + + m_decimator2.myDecimateInf( + (buf[pos+24] - Shift) << decimation_shifts::pre8, + (buf[pos+25] - Shift) << decimation_shifts::pre8, + (buf[pos+26] - Shift) << decimation_shifts::pre8, + (buf[pos+27] - Shift) << decimation_shifts::pre8, + (buf[pos+28] - Shift) << decimation_shifts::pre8, + (buf[pos+29] - Shift) << decimation_shifts::pre8, + (buf[pos+30] - Shift) << decimation_shifts::pre8, + (buf[pos+31] - Shift) << decimation_shifts::pre8, + &buf2[12]); + + m_decimator4.myDecimateSup( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateSup( + &buf2[8], + &buf4[4]); + + m_decimator8.myDecimateCen( + &buf4[0], + &buf8[0]); + + (**it).setReal(buf8[0] >> decimation_shifts::post8); + (**it).setImag(buf8[1] >> decimation_shifts::post8); + ++(*it); + + (**it).setReal(buf8[2] >> decimation_shifts::post8); + (**it).setImag(buf8[3] >> decimation_shifts::post8); + ++(*it); + } } -template -void DecimatorsU::decimate8_sup(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate8_sup(SampleVector::iterator* it, const T* buf __attribute__((unused)), qint32 len) { - AccuType xreal[2], yimag[2]; + StorageType buf2[16], buf4[8], buf8[4]; - for (int pos = 0; pos < len - 15; pos += 8) - { - xreal[0] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) << decimation_shifts::pre8; - yimag[0] = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]) << decimation_shifts::pre8; - pos += 8; + for (int pos = 0; pos < len - 31; pos += 32) + { + m_decimator2.myDecimateSup( + (buf[pos+0] - Shift) << decimation_shifts::pre8, + (buf[pos+1] - Shift) << decimation_shifts::pre8, + (buf[pos+2] - Shift) << decimation_shifts::pre8, + (buf[pos+3] - Shift) << decimation_shifts::pre8, + (buf[pos+4] - Shift) << decimation_shifts::pre8, + (buf[pos+5] - Shift) << decimation_shifts::pre8, + (buf[pos+6] - Shift) << decimation_shifts::pre8, + (buf[pos+7] - Shift) << decimation_shifts::pre8, + &buf2[0]); - xreal[1] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) << decimation_shifts::pre8; - yimag[1] = (- buf[pos+0] - buf[pos+3] + buf[pos+4] + buf[pos+7]) << decimation_shifts::pre8; - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimateSup( + (buf[pos+8] - Shift) << decimation_shifts::pre8, + (buf[pos+9] - Shift) << decimation_shifts::pre8, + (buf[pos+10] - Shift) << decimation_shifts::pre8, + (buf[pos+11] - Shift) << decimation_shifts::pre8, + (buf[pos+12] - Shift) << decimation_shifts::pre8, + (buf[pos+13] - Shift) << decimation_shifts::pre8, + (buf[pos+14] - Shift) << decimation_shifts::pre8, + (buf[pos+15] - Shift) << decimation_shifts::pre8, + &buf2[4]); - (**it).setReal(xreal[1] >> decimation_shifts::post8); - (**it).setImag(yimag[1] >> decimation_shifts::post8); - ++(*it); - } + m_decimator2.myDecimateSup( + (buf[pos+16] - Shift) << decimation_shifts::pre8, + (buf[pos+17] - Shift) << decimation_shifts::pre8, + (buf[pos+18] - Shift) << decimation_shifts::pre8, + (buf[pos+19] - Shift) << decimation_shifts::pre8, + (buf[pos+20] - Shift) << decimation_shifts::pre8, + (buf[pos+21] - Shift) << decimation_shifts::pre8, + (buf[pos+22] - Shift) << decimation_shifts::pre8, + (buf[pos+23] - Shift) << decimation_shifts::pre8, + &buf2[8]); + + + m_decimator2.myDecimateSup( + (buf[pos+24] - Shift) << decimation_shifts::pre8, + (buf[pos+25] - Shift) << decimation_shifts::pre8, + (buf[pos+26] - Shift) << decimation_shifts::pre8, + (buf[pos+27] - Shift) << decimation_shifts::pre8, + (buf[pos+28] - Shift) << decimation_shifts::pre8, + (buf[pos+29] - Shift) << decimation_shifts::pre8, + (buf[pos+30] - Shift) << decimation_shifts::pre8, + (buf[pos+31] - Shift) << decimation_shifts::pre8, + &buf2[12]); + + m_decimator4.myDecimateInf( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateInf( + &buf2[8], + &buf4[4]); + + m_decimator8.myDecimateCen( + &buf4[0], + &buf8[0]); + + (**it).setReal(buf8[0] >> decimation_shifts::post8); + (**it).setImag(buf8[1] >> decimation_shifts::post8); + ++(*it); + + (**it).setReal(buf8[2] >> decimation_shifts::post8); + (**it).setImag(buf8[3] >> decimation_shifts::post8); + ++(*it); + } } -template -void DecimatorsU::decimate16_inf(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate16_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - // Offset tuning: 4x downsample and rotate, then - // downsample 4x more. [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] - AccuType xreal[4], yimag[4]; + StorageType buf2[32], buf4[16], buf8[8], buf16[4]; - for (int pos = 0; pos < len - 31; ) - { - for (int i = 0; i < 4; i++) - { - xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) << decimation_shifts::pre16; - yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) << decimation_shifts::pre16; - pos += 8; - } + for (int pos = 0; pos < len - 63; pos += 64) + { + m_decimator2.myDecimateInf( + (buf[pos+0] - Shift) << decimation_shifts::pre16, + (buf[pos+1] - Shift) << decimation_shifts::pre16, + (buf[pos+2] - Shift) << decimation_shifts::pre16, + (buf[pos+3] - Shift) << decimation_shifts::pre16, + (buf[pos+4] - Shift) << decimation_shifts::pre16, + (buf[pos+5] - Shift) << decimation_shifts::pre16, + (buf[pos+6] - Shift) << decimation_shifts::pre16, + (buf[pos+7] - Shift) << decimation_shifts::pre16, + &buf2[0]); - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator2.myDecimateInf( + (buf[pos+8] - Shift) << decimation_shifts::pre16, + (buf[pos+9] - Shift) << decimation_shifts::pre16, + (buf[pos+10] - Shift) << decimation_shifts::pre16, + (buf[pos+11] - Shift) << decimation_shifts::pre16, + (buf[pos+12] - Shift) << decimation_shifts::pre16, + (buf[pos+13] - Shift) << decimation_shifts::pre16, + (buf[pos+14] - Shift) << decimation_shifts::pre16, + (buf[pos+15] - Shift) << decimation_shifts::pre16, + &buf2[4]); - (**it).setReal(xreal[3] >> decimation_shifts::post16); - (**it).setImag(yimag[3] >> decimation_shifts::post16); - ++(*it); - } + m_decimator2.myDecimateInf( + (buf[pos+16] - Shift) << decimation_shifts::pre16, + (buf[pos+17] - Shift) << decimation_shifts::pre16, + (buf[pos+18] - Shift) << decimation_shifts::pre16, + (buf[pos+19] - Shift) << decimation_shifts::pre16, + (buf[pos+20] - Shift) << decimation_shifts::pre16, + (buf[pos+21] - Shift) << decimation_shifts::pre16, + (buf[pos+22] - Shift) << decimation_shifts::pre16, + (buf[pos+23] - Shift) << decimation_shifts::pre16, + &buf2[8]); + + + m_decimator2.myDecimateInf( + (buf[pos+24] - Shift) << decimation_shifts::pre16, + (buf[pos+25] - Shift) << decimation_shifts::pre16, + (buf[pos+26] - Shift) << decimation_shifts::pre16, + (buf[pos+27] - Shift) << decimation_shifts::pre16, + (buf[pos+28] - Shift) << decimation_shifts::pre16, + (buf[pos+29] - Shift) << decimation_shifts::pre16, + (buf[pos+30] - Shift) << decimation_shifts::pre16, + (buf[pos+31] - Shift) << decimation_shifts::pre16, + &buf2[12]); + + + m_decimator2.myDecimateInf( + (buf[pos+32] - Shift) << decimation_shifts::pre16, + (buf[pos+33] - Shift) << decimation_shifts::pre16, + (buf[pos+34] - Shift) << decimation_shifts::pre16, + (buf[pos+35] - Shift) << decimation_shifts::pre16, + (buf[pos+36] - Shift) << decimation_shifts::pre16, + (buf[pos+37] - Shift) << decimation_shifts::pre16, + (buf[pos+38] - Shift) << decimation_shifts::pre16, + (buf[pos+39] - Shift) << decimation_shifts::pre16, + &buf2[16]); + + + m_decimator2.myDecimateInf( + (buf[pos+40] - Shift) << decimation_shifts::pre16, + (buf[pos+41] - Shift) << decimation_shifts::pre16, + (buf[pos+42] - Shift) << decimation_shifts::pre16, + (buf[pos+43] - Shift) << decimation_shifts::pre16, + (buf[pos+44] - Shift) << decimation_shifts::pre16, + (buf[pos+45] - Shift) << decimation_shifts::pre16, + (buf[pos+46] - Shift) << decimation_shifts::pre16, + (buf[pos+47] - Shift) << decimation_shifts::pre16, + &buf2[20]); + + + m_decimator2.myDecimateInf( + (buf[pos+48] - Shift) << decimation_shifts::pre16, + (buf[pos+49] - Shift) << decimation_shifts::pre16, + (buf[pos+50] - Shift) << decimation_shifts::pre16, + (buf[pos+51] - Shift) << decimation_shifts::pre16, + (buf[pos+52] - Shift) << decimation_shifts::pre16, + (buf[pos+53] - Shift) << decimation_shifts::pre16, + (buf[pos+54] - Shift) << decimation_shifts::pre16, + (buf[pos+55] - Shift) << decimation_shifts::pre16, + &buf2[24]); + + + m_decimator2.myDecimateInf( + (buf[pos+56] - Shift) << decimation_shifts::pre16, + (buf[pos+57] - Shift) << decimation_shifts::pre16, + (buf[pos+58] - Shift) << decimation_shifts::pre16, + (buf[pos+59] - Shift) << decimation_shifts::pre16, + (buf[pos+60] - Shift) << decimation_shifts::pre16, + (buf[pos+61] - Shift) << decimation_shifts::pre16, + (buf[pos+62] - Shift) << decimation_shifts::pre16, + (buf[pos+63] - Shift) << decimation_shifts::pre16, + &buf2[28]); + + m_decimator4.myDecimateSup( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateSup( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateSup( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateSup( + &buf2[24], + &buf4[12]); + + m_decimator8.myDecimateSup( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateSup( + &buf4[8], + &buf8[4]); + + m_decimator16.myDecimateCen( + &buf8[0], + &buf16[0]); + + (**it).setReal(buf16[0] >> decimation_shifts::post16); + (**it).setImag(buf16[1] >> decimation_shifts::post16); + ++(*it); + + (**it).setReal(buf16[2] >> decimation_shifts::post16); + (**it).setImag(buf16[3] >> decimation_shifts::post16); + ++(*it); + } } -template -void DecimatorsU::decimate16_sup(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate16_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - // Offset tuning: 4x downsample and rotate, then - // downsample 4x more. [ rotate: 1, 0, -2, 3, -5, -4, 6, -7] - AccuType xreal[4], yimag[4]; + StorageType buf2[32], buf4[16], buf8[8], buf16[4]; - for (int pos = 0; pos < len - 31; ) - { - for (int i = 0; i < 4; i++) - { - xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) << decimation_shifts::pre16; - yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]) << decimation_shifts::pre16; - pos += 8; - } + for (int pos = 0; pos < len - 63; pos += 64) + { + m_decimator2.myDecimateSup( + (buf[pos+0] - Shift) << decimation_shifts::pre16, + (buf[pos+1] - Shift) << decimation_shifts::pre16, + (buf[pos+2] - Shift) << decimation_shifts::pre16, + (buf[pos+3] - Shift) << decimation_shifts::pre16, + (buf[pos+4] - Shift) << decimation_shifts::pre16, + (buf[pos+5] - Shift) << decimation_shifts::pre16, + (buf[pos+6] - Shift) << decimation_shifts::pre16, + (buf[pos+7] - Shift) << decimation_shifts::pre16, + &buf2[0]); - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator2.myDecimateSup( + (buf[pos+8] - Shift) << decimation_shifts::pre16, + (buf[pos+9] - Shift) << decimation_shifts::pre16, + (buf[pos+10] - Shift) << decimation_shifts::pre16, + (buf[pos+11] - Shift) << decimation_shifts::pre16, + (buf[pos+12] - Shift) << decimation_shifts::pre16, + (buf[pos+13] - Shift) << decimation_shifts::pre16, + (buf[pos+14] - Shift) << decimation_shifts::pre16, + (buf[pos+15] - Shift) << decimation_shifts::pre16, + &buf2[4]); - (**it).setReal(xreal[3] >> decimation_shifts::post16); - (**it).setImag(yimag[3] >> decimation_shifts::post16); - ++(*it); - } + m_decimator2.myDecimateSup( + (buf[pos+16] - Shift) << decimation_shifts::pre16, + (buf[pos+17] - Shift) << decimation_shifts::pre16, + (buf[pos+18] - Shift) << decimation_shifts::pre16, + (buf[pos+19] - Shift) << decimation_shifts::pre16, + (buf[pos+20] - Shift) << decimation_shifts::pre16, + (buf[pos+21] - Shift) << decimation_shifts::pre16, + (buf[pos+22] - Shift) << decimation_shifts::pre16, + (buf[pos+23] - Shift) << decimation_shifts::pre16, + &buf2[8]); + + + m_decimator2.myDecimateSup( + (buf[pos+24] - Shift) << decimation_shifts::pre16, + (buf[pos+25] - Shift) << decimation_shifts::pre16, + (buf[pos+26] - Shift) << decimation_shifts::pre16, + (buf[pos+27] - Shift) << decimation_shifts::pre16, + (buf[pos+28] - Shift) << decimation_shifts::pre16, + (buf[pos+29] - Shift) << decimation_shifts::pre16, + (buf[pos+30] - Shift) << decimation_shifts::pre16, + (buf[pos+31] - Shift) << decimation_shifts::pre16, + &buf2[12]); + + + m_decimator2.myDecimateSup( + (buf[pos+32] - Shift) << decimation_shifts::pre16, + (buf[pos+33] - Shift) << decimation_shifts::pre16, + (buf[pos+34] - Shift) << decimation_shifts::pre16, + (buf[pos+35] - Shift) << decimation_shifts::pre16, + (buf[pos+36] - Shift) << decimation_shifts::pre16, + (buf[pos+37] - Shift) << decimation_shifts::pre16, + (buf[pos+38] - Shift) << decimation_shifts::pre16, + (buf[pos+39] - Shift) << decimation_shifts::pre16, + &buf2[16]); + + + m_decimator2.myDecimateSup( + (buf[pos+40] - Shift) << decimation_shifts::pre16, + (buf[pos+41] - Shift) << decimation_shifts::pre16, + (buf[pos+42] - Shift) << decimation_shifts::pre16, + (buf[pos+43] - Shift) << decimation_shifts::pre16, + (buf[pos+44] - Shift) << decimation_shifts::pre16, + (buf[pos+45] - Shift) << decimation_shifts::pre16, + (buf[pos+46] - Shift) << decimation_shifts::pre16, + (buf[pos+47] - Shift) << decimation_shifts::pre16, + &buf2[20]); + + + m_decimator2.myDecimateSup( + (buf[pos+48] - Shift) << decimation_shifts::pre16, + (buf[pos+49] - Shift) << decimation_shifts::pre16, + (buf[pos+50] - Shift) << decimation_shifts::pre16, + (buf[pos+51] - Shift) << decimation_shifts::pre16, + (buf[pos+52] - Shift) << decimation_shifts::pre16, + (buf[pos+53] - Shift) << decimation_shifts::pre16, + (buf[pos+54] - Shift) << decimation_shifts::pre16, + (buf[pos+55] - Shift) << decimation_shifts::pre16, + &buf2[24]); + + + m_decimator2.myDecimateSup( + (buf[pos+56] - Shift) << decimation_shifts::pre16, + (buf[pos+57] - Shift) << decimation_shifts::pre16, + (buf[pos+58] - Shift) << decimation_shifts::pre16, + (buf[pos+59] - Shift) << decimation_shifts::pre16, + (buf[pos+60] - Shift) << decimation_shifts::pre16, + (buf[pos+61] - Shift) << decimation_shifts::pre16, + (buf[pos+62] - Shift) << decimation_shifts::pre16, + (buf[pos+63] - Shift) << decimation_shifts::pre16, + &buf2[28]); + + m_decimator4.myDecimateInf( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateInf( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateInf( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateInf( + &buf2[24], + &buf4[12]); + + m_decimator8.myDecimateInf( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateInf( + &buf4[8], + &buf8[4]); + + m_decimator16.myDecimateCen( + &buf8[0], + &buf16[0]); + + (**it).setReal(buf16[0] >> decimation_shifts::post16); + (**it).setImag(buf16[1] >> decimation_shifts::post16); + ++(*it); + + (**it).setReal(buf16[2] >> decimation_shifts::post16); + (**it).setImag(buf16[3] >> decimation_shifts::post16); + ++(*it); + } } -template -void DecimatorsU::decimate32_inf(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate32_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal[8], yimag[8]; + StorageType buf2[64], buf4[32], buf8[16], buf16[8], buf32[4]; - for (int pos = 0; pos < len - 63; ) - { - for (int i = 0; i < 8; i++) - { - xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) << decimation_shifts::pre32; - yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) << decimation_shifts::pre32; - pos += 8; - } + for (int pos = 0; pos < len - 127; pos += 128) + { + m_decimator2.myDecimateInf( + (buf[pos+0] - Shift) << decimation_shifts::pre32, + (buf[pos+1] - Shift) << decimation_shifts::pre32, + (buf[pos+2] - Shift) << decimation_shifts::pre32, + (buf[pos+3] - Shift) << decimation_shifts::pre32, + (buf[pos+4] - Shift) << decimation_shifts::pre32, + (buf[pos+5] - Shift) << decimation_shifts::pre32, + (buf[pos+6] - Shift) << decimation_shifts::pre32, + (buf[pos+7] - Shift) << decimation_shifts::pre32, + &buf2[0]); - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); - m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); - m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + m_decimator2.myDecimateInf( + (buf[pos+8] - Shift) << decimation_shifts::pre32, + (buf[pos+9] - Shift) << decimation_shifts::pre32, + (buf[pos+10] - Shift) << decimation_shifts::pre32, + (buf[pos+11] - Shift) << decimation_shifts::pre32, + (buf[pos+12] - Shift) << decimation_shifts::pre32, + (buf[pos+13] - Shift) << decimation_shifts::pre32, + (buf[pos+14] - Shift) << decimation_shifts::pre32, + (buf[pos+15] - Shift) << decimation_shifts::pre32, + &buf2[4]); - m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); - (**it).setReal(xreal[7] >> decimation_shifts::post32); - (**it).setImag(yimag[7] >> decimation_shifts::post32); + m_decimator2.myDecimateInf( + (buf[pos+16] - Shift) << decimation_shifts::pre32, + (buf[pos+17] - Shift) << decimation_shifts::pre32, + (buf[pos+18] - Shift) << decimation_shifts::pre32, + (buf[pos+19] - Shift) << decimation_shifts::pre32, + (buf[pos+20] - Shift) << decimation_shifts::pre32, + (buf[pos+21] - Shift) << decimation_shifts::pre32, + (buf[pos+22] - Shift) << decimation_shifts::pre32, + (buf[pos+23] - Shift) << decimation_shifts::pre32, + &buf2[8]); - ++(*it); - } + + m_decimator2.myDecimateInf( + (buf[pos+24] - Shift) << decimation_shifts::pre32, + (buf[pos+25] - Shift) << decimation_shifts::pre32, + (buf[pos+26] - Shift) << decimation_shifts::pre32, + (buf[pos+27] - Shift) << decimation_shifts::pre32, + (buf[pos+28] - Shift) << decimation_shifts::pre32, + (buf[pos+29] - Shift) << decimation_shifts::pre32, + (buf[pos+30] - Shift) << decimation_shifts::pre32, + (buf[pos+31] - Shift) << decimation_shifts::pre32, + &buf2[12]); + + + m_decimator2.myDecimateInf( + (buf[pos+32] - Shift) << decimation_shifts::pre32, + (buf[pos+33] - Shift) << decimation_shifts::pre32, + (buf[pos+34] - Shift) << decimation_shifts::pre32, + (buf[pos+35] - Shift) << decimation_shifts::pre32, + (buf[pos+36] - Shift) << decimation_shifts::pre32, + (buf[pos+37] - Shift) << decimation_shifts::pre32, + (buf[pos+38] - Shift) << decimation_shifts::pre32, + (buf[pos+39] - Shift) << decimation_shifts::pre32, + &buf2[16]); + + + m_decimator2.myDecimateInf( + (buf[pos+40] - Shift) << decimation_shifts::pre32, + (buf[pos+41] - Shift) << decimation_shifts::pre32, + (buf[pos+42] - Shift) << decimation_shifts::pre32, + (buf[pos+43] - Shift) << decimation_shifts::pre32, + (buf[pos+44] - Shift) << decimation_shifts::pre32, + (buf[pos+45] - Shift) << decimation_shifts::pre32, + (buf[pos+46] - Shift) << decimation_shifts::pre32, + (buf[pos+47] - Shift) << decimation_shifts::pre32, + &buf2[20]); + + + m_decimator2.myDecimateInf( + (buf[pos+48] - Shift) << decimation_shifts::pre32, + (buf[pos+49] - Shift) << decimation_shifts::pre32, + (buf[pos+50] - Shift) << decimation_shifts::pre32, + (buf[pos+51] - Shift) << decimation_shifts::pre32, + (buf[pos+52] - Shift) << decimation_shifts::pre32, + (buf[pos+53] - Shift) << decimation_shifts::pre32, + (buf[pos+54] - Shift) << decimation_shifts::pre32, + (buf[pos+55] - Shift) << decimation_shifts::pre32, + &buf2[24]); + + + m_decimator2.myDecimateInf( + (buf[pos+56] - Shift) << decimation_shifts::pre32, + (buf[pos+57] - Shift) << decimation_shifts::pre32, + (buf[pos+58] - Shift) << decimation_shifts::pre32, + (buf[pos+59] - Shift) << decimation_shifts::pre32, + (buf[pos+60] - Shift) << decimation_shifts::pre32, + (buf[pos+61] - Shift) << decimation_shifts::pre32, + (buf[pos+62] - Shift) << decimation_shifts::pre32, + (buf[pos+63] - Shift) << decimation_shifts::pre32, + &buf2[28]); + + + m_decimator2.myDecimateInf( + (buf[pos+64] - Shift) << decimation_shifts::pre32, + (buf[pos+65] - Shift) << decimation_shifts::pre32, + (buf[pos+66] - Shift) << decimation_shifts::pre32, + (buf[pos+67] - Shift) << decimation_shifts::pre32, + (buf[pos+68] - Shift) << decimation_shifts::pre32, + (buf[pos+69] - Shift) << decimation_shifts::pre32, + (buf[pos+70] - Shift) << decimation_shifts::pre32, + (buf[pos+71] - Shift) << decimation_shifts::pre32, + &buf2[32]); + + + m_decimator2.myDecimateInf( + (buf[pos+72] - Shift) << decimation_shifts::pre32, + (buf[pos+73] - Shift) << decimation_shifts::pre32, + (buf[pos+74] - Shift) << decimation_shifts::pre32, + (buf[pos+75] - Shift) << decimation_shifts::pre32, + (buf[pos+76] - Shift) << decimation_shifts::pre32, + (buf[pos+77] - Shift) << decimation_shifts::pre32, + (buf[pos+78] - Shift) << decimation_shifts::pre32, + (buf[pos+79] - Shift) << decimation_shifts::pre32, + &buf2[36]); + + + m_decimator2.myDecimateInf( + (buf[pos+80] - Shift) << decimation_shifts::pre32, + (buf[pos+81] - Shift) << decimation_shifts::pre32, + (buf[pos+82] - Shift) << decimation_shifts::pre32, + (buf[pos+83] - Shift) << decimation_shifts::pre32, + (buf[pos+84] - Shift) << decimation_shifts::pre32, + (buf[pos+85] - Shift) << decimation_shifts::pre32, + (buf[pos+86] - Shift) << decimation_shifts::pre32, + (buf[pos+87] - Shift) << decimation_shifts::pre32, + &buf2[40]); + + + m_decimator2.myDecimateInf( + (buf[pos+88] - Shift) << decimation_shifts::pre32, + (buf[pos+89] - Shift) << decimation_shifts::pre32, + (buf[pos+90] - Shift) << decimation_shifts::pre32, + (buf[pos+91] - Shift) << decimation_shifts::pre32, + (buf[pos+92] - Shift) << decimation_shifts::pre32, + (buf[pos+93] - Shift) << decimation_shifts::pre32, + (buf[pos+94] - Shift) << decimation_shifts::pre32, + (buf[pos+95] - Shift) << decimation_shifts::pre32, + &buf2[44]); + + + m_decimator2.myDecimateInf( + (buf[pos+96] - Shift) << decimation_shifts::pre32, + (buf[pos+97] - Shift) << decimation_shifts::pre32, + (buf[pos+98] - Shift) << decimation_shifts::pre32, + (buf[pos+99] - Shift) << decimation_shifts::pre32, + (buf[pos+100] - Shift) << decimation_shifts::pre32, + (buf[pos+101] - Shift) << decimation_shifts::pre32, + (buf[pos+102] - Shift) << decimation_shifts::pre32, + (buf[pos+103] - Shift) << decimation_shifts::pre32, + &buf2[48]); + + + m_decimator2.myDecimateInf( + (buf[pos+104] - Shift) << decimation_shifts::pre32, + (buf[pos+105] - Shift) << decimation_shifts::pre32, + (buf[pos+106] - Shift) << decimation_shifts::pre32, + (buf[pos+107] - Shift) << decimation_shifts::pre32, + (buf[pos+108] - Shift) << decimation_shifts::pre32, + (buf[pos+109] - Shift) << decimation_shifts::pre32, + (buf[pos+110] - Shift) << decimation_shifts::pre32, + (buf[pos+111] - Shift) << decimation_shifts::pre32, + &buf2[52]); + + + m_decimator2.myDecimateInf( + (buf[pos+112] - Shift) << decimation_shifts::pre32, + (buf[pos+113] - Shift) << decimation_shifts::pre32, + (buf[pos+114] - Shift) << decimation_shifts::pre32, + (buf[pos+115] - Shift) << decimation_shifts::pre32, + (buf[pos+116] - Shift) << decimation_shifts::pre32, + (buf[pos+117] - Shift) << decimation_shifts::pre32, + (buf[pos+118] - Shift) << decimation_shifts::pre32, + (buf[pos+119] - Shift) << decimation_shifts::pre32, + &buf2[56]); + + + m_decimator2.myDecimateInf( + (buf[pos+120] - Shift) << decimation_shifts::pre32, + (buf[pos+121] - Shift) << decimation_shifts::pre32, + (buf[pos+122] - Shift) << decimation_shifts::pre32, + (buf[pos+123] - Shift) << decimation_shifts::pre32, + (buf[pos+124] - Shift) << decimation_shifts::pre32, + (buf[pos+125] - Shift) << decimation_shifts::pre32, + (buf[pos+126] - Shift) << decimation_shifts::pre32, + (buf[pos+127] - Shift) << decimation_shifts::pre32, + &buf2[60]); + + m_decimator4.myDecimateSup( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateSup( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateSup( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateSup( + &buf2[24], + &buf4[12]); + + m_decimator4.myDecimateSup( + &buf2[32], + &buf4[16]); + + m_decimator4.myDecimateSup( + &buf2[40], + &buf4[20]); + + m_decimator4.myDecimateSup( + &buf2[48], + &buf4[24]); + + m_decimator4.myDecimateSup( + &buf2[56], + &buf4[28]); + + m_decimator8.myDecimateSup( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateSup( + &buf4[8], + &buf8[4]); + + m_decimator8.myDecimateSup( + &buf4[16], + &buf8[8]); + + m_decimator8.myDecimateSup( + &buf4[24], + &buf8[12]); + + m_decimator16.myDecimateSup( + &buf8[0], + &buf16[0]); + + m_decimator16.myDecimateSup( + &buf8[8], + &buf16[4]); + + m_decimator32.myDecimateCen( + &buf16[0], + &buf32[0]); + + (**it).setReal(buf32[0] >> decimation_shifts::post32); + (**it).setImag(buf32[1] >> decimation_shifts::post32); + ++(*it); + + (**it).setReal(buf32[2] >> decimation_shifts::post32); + (**it).setImag(buf32[3] >> decimation_shifts::post32); + ++(*it); + } } -template -void DecimatorsU::decimate32_sup(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate32_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal[8], yimag[8]; + StorageType buf2[64], buf4[32], buf8[16], buf16[8], buf32[4]; - for (int pos = 0; pos < len - 63; ) - { - for (int i = 0; i < 8; i++) - { - xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) << decimation_shifts::pre32; - yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]) << decimation_shifts::pre32; - pos += 8; - } + for (int pos = 0; pos < len - 127; pos += 128) + { + m_decimator2.myDecimateSup( + (buf[pos+0] - Shift) << decimation_shifts::pre32, + (buf[pos+1] - Shift) << decimation_shifts::pre32, + (buf[pos+2] - Shift) << decimation_shifts::pre32, + (buf[pos+3] - Shift) << decimation_shifts::pre32, + (buf[pos+4] - Shift) << decimation_shifts::pre32, + (buf[pos+5] - Shift) << decimation_shifts::pre32, + (buf[pos+6] - Shift) << decimation_shifts::pre32, + (buf[pos+7] - Shift) << decimation_shifts::pre32, + &buf2[0]); - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); - m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); - m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + m_decimator2.myDecimateSup( + (buf[pos+8] - Shift) << decimation_shifts::pre32, + (buf[pos+9] - Shift) << decimation_shifts::pre32, + (buf[pos+10] - Shift) << decimation_shifts::pre32, + (buf[pos+11] - Shift) << decimation_shifts::pre32, + (buf[pos+12] - Shift) << decimation_shifts::pre32, + (buf[pos+13] - Shift) << decimation_shifts::pre32, + (buf[pos+14] - Shift) << decimation_shifts::pre32, + (buf[pos+15] - Shift) << decimation_shifts::pre32, + &buf2[4]); - m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); - (**it).setReal(xreal[7] >> decimation_shifts::post32); - (**it).setImag(yimag[7] >> decimation_shifts::post32); + m_decimator2.myDecimateSup( + (buf[pos+16] - Shift) << decimation_shifts::pre32, + (buf[pos+17] - Shift) << decimation_shifts::pre32, + (buf[pos+18] - Shift) << decimation_shifts::pre32, + (buf[pos+19] - Shift) << decimation_shifts::pre32, + (buf[pos+20] - Shift) << decimation_shifts::pre32, + (buf[pos+21] - Shift) << decimation_shifts::pre32, + (buf[pos+22] - Shift) << decimation_shifts::pre32, + (buf[pos+23] - Shift) << decimation_shifts::pre32, + &buf2[8]); - ++(*it); - } + + m_decimator2.myDecimateSup( + (buf[pos+24] - Shift) << decimation_shifts::pre32, + (buf[pos+25] - Shift) << decimation_shifts::pre32, + (buf[pos+26] - Shift) << decimation_shifts::pre32, + (buf[pos+27] - Shift) << decimation_shifts::pre32, + (buf[pos+28] - Shift) << decimation_shifts::pre32, + (buf[pos+29] - Shift) << decimation_shifts::pre32, + (buf[pos+30] - Shift) << decimation_shifts::pre32, + (buf[pos+31] - Shift) << decimation_shifts::pre32, + &buf2[12]); + + + m_decimator2.myDecimateSup( + (buf[pos+32] - Shift) << decimation_shifts::pre32, + (buf[pos+33] - Shift) << decimation_shifts::pre32, + (buf[pos+34] - Shift) << decimation_shifts::pre32, + (buf[pos+35] - Shift) << decimation_shifts::pre32, + (buf[pos+36] - Shift) << decimation_shifts::pre32, + (buf[pos+37] - Shift) << decimation_shifts::pre32, + (buf[pos+38] - Shift) << decimation_shifts::pre32, + (buf[pos+39] - Shift) << decimation_shifts::pre32, + &buf2[16]); + + + m_decimator2.myDecimateSup( + (buf[pos+40] - Shift) << decimation_shifts::pre32, + (buf[pos+41] - Shift) << decimation_shifts::pre32, + (buf[pos+42] - Shift) << decimation_shifts::pre32, + (buf[pos+43] - Shift) << decimation_shifts::pre32, + (buf[pos+44] - Shift) << decimation_shifts::pre32, + (buf[pos+45] - Shift) << decimation_shifts::pre32, + (buf[pos+46] - Shift) << decimation_shifts::pre32, + (buf[pos+47] - Shift) << decimation_shifts::pre32, + &buf2[20]); + + + m_decimator2.myDecimateSup( + (buf[pos+48] - Shift) << decimation_shifts::pre32, + (buf[pos+49] - Shift) << decimation_shifts::pre32, + (buf[pos+50] - Shift) << decimation_shifts::pre32, + (buf[pos+51] - Shift) << decimation_shifts::pre32, + (buf[pos+52] - Shift) << decimation_shifts::pre32, + (buf[pos+53] - Shift) << decimation_shifts::pre32, + (buf[pos+54] - Shift) << decimation_shifts::pre32, + (buf[pos+55] - Shift) << decimation_shifts::pre32, + &buf2[24]); + + + m_decimator2.myDecimateSup( + (buf[pos+56] - Shift) << decimation_shifts::pre32, + (buf[pos+57] - Shift) << decimation_shifts::pre32, + (buf[pos+58] - Shift) << decimation_shifts::pre32, + (buf[pos+59] - Shift) << decimation_shifts::pre32, + (buf[pos+60] - Shift) << decimation_shifts::pre32, + (buf[pos+61] - Shift) << decimation_shifts::pre32, + (buf[pos+62] - Shift) << decimation_shifts::pre32, + (buf[pos+63] - Shift) << decimation_shifts::pre32, + &buf2[28]); + + + m_decimator2.myDecimateSup( + (buf[pos+64] - Shift) << decimation_shifts::pre32, + (buf[pos+65] - Shift) << decimation_shifts::pre32, + (buf[pos+66] - Shift) << decimation_shifts::pre32, + (buf[pos+67] - Shift) << decimation_shifts::pre32, + (buf[pos+68] - Shift) << decimation_shifts::pre32, + (buf[pos+69] - Shift) << decimation_shifts::pre32, + (buf[pos+70] - Shift) << decimation_shifts::pre32, + (buf[pos+71] - Shift) << decimation_shifts::pre32, + &buf2[32]); + + + m_decimator2.myDecimateSup( + (buf[pos+72] - Shift) << decimation_shifts::pre32, + (buf[pos+73] - Shift) << decimation_shifts::pre32, + (buf[pos+74] - Shift) << decimation_shifts::pre32, + (buf[pos+75] - Shift) << decimation_shifts::pre32, + (buf[pos+76] - Shift) << decimation_shifts::pre32, + (buf[pos+77] - Shift) << decimation_shifts::pre32, + (buf[pos+78] - Shift) << decimation_shifts::pre32, + (buf[pos+79] - Shift) << decimation_shifts::pre32, + &buf2[36]); + + + m_decimator2.myDecimateSup( + (buf[pos+80] - Shift) << decimation_shifts::pre32, + (buf[pos+81] - Shift) << decimation_shifts::pre32, + (buf[pos+82] - Shift) << decimation_shifts::pre32, + (buf[pos+83] - Shift) << decimation_shifts::pre32, + (buf[pos+84] - Shift) << decimation_shifts::pre32, + (buf[pos+85] - Shift) << decimation_shifts::pre32, + (buf[pos+86] - Shift) << decimation_shifts::pre32, + (buf[pos+87] - Shift) << decimation_shifts::pre32, + &buf2[40]); + + + m_decimator2.myDecimateSup( + (buf[pos+88] - Shift) << decimation_shifts::pre32, + (buf[pos+89] - Shift) << decimation_shifts::pre32, + (buf[pos+90] - Shift) << decimation_shifts::pre32, + (buf[pos+91] - Shift) << decimation_shifts::pre32, + (buf[pos+92] - Shift) << decimation_shifts::pre32, + (buf[pos+93] - Shift) << decimation_shifts::pre32, + (buf[pos+94] - Shift) << decimation_shifts::pre32, + (buf[pos+95] - Shift) << decimation_shifts::pre32, + &buf2[44]); + + + m_decimator2.myDecimateSup( + (buf[pos+96] - Shift) << decimation_shifts::pre32, + (buf[pos+97] - Shift) << decimation_shifts::pre32, + (buf[pos+98] - Shift) << decimation_shifts::pre32, + (buf[pos+99] - Shift) << decimation_shifts::pre32, + (buf[pos+100] - Shift) << decimation_shifts::pre32, + (buf[pos+101] - Shift) << decimation_shifts::pre32, + (buf[pos+102] - Shift) << decimation_shifts::pre32, + (buf[pos+103] - Shift) << decimation_shifts::pre32, + &buf2[48]); + + + m_decimator2.myDecimateSup( + (buf[pos+104] - Shift) << decimation_shifts::pre32, + (buf[pos+105] - Shift) << decimation_shifts::pre32, + (buf[pos+106] - Shift) << decimation_shifts::pre32, + (buf[pos+107] - Shift) << decimation_shifts::pre32, + (buf[pos+108] - Shift) << decimation_shifts::pre32, + (buf[pos+109] - Shift) << decimation_shifts::pre32, + (buf[pos+110] - Shift) << decimation_shifts::pre32, + (buf[pos+111] - Shift) << decimation_shifts::pre32, + &buf2[52]); + + + m_decimator2.myDecimateSup( + (buf[pos+112] - Shift) << decimation_shifts::pre32, + (buf[pos+113] - Shift) << decimation_shifts::pre32, + (buf[pos+114] - Shift) << decimation_shifts::pre32, + (buf[pos+115] - Shift) << decimation_shifts::pre32, + (buf[pos+116] - Shift) << decimation_shifts::pre32, + (buf[pos+117] - Shift) << decimation_shifts::pre32, + (buf[pos+118] - Shift) << decimation_shifts::pre32, + (buf[pos+119] - Shift) << decimation_shifts::pre32, + &buf2[56]); + + + m_decimator2.myDecimateSup( + (buf[pos+120] - Shift) << decimation_shifts::pre32, + (buf[pos+121] - Shift) << decimation_shifts::pre32, + (buf[pos+122] - Shift) << decimation_shifts::pre32, + (buf[pos+123] - Shift) << decimation_shifts::pre32, + (buf[pos+124] - Shift) << decimation_shifts::pre32, + (buf[pos+125] - Shift) << decimation_shifts::pre32, + (buf[pos+126] - Shift) << decimation_shifts::pre32, + (buf[pos+127] - Shift) << decimation_shifts::pre32, + &buf2[60]); + + m_decimator4.myDecimateInf( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateInf( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateInf( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateInf( + &buf2[24], + &buf4[12]); + + m_decimator4.myDecimateInf( + &buf2[32], + &buf4[16]); + + m_decimator4.myDecimateInf( + &buf2[40], + &buf4[20]); + + m_decimator4.myDecimateInf( + &buf2[48], + &buf4[24]); + + m_decimator4.myDecimateInf( + &buf2[56], + &buf4[28]); + + m_decimator8.myDecimateInf( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateInf( + &buf4[8], + &buf8[4]); + + m_decimator8.myDecimateInf( + &buf4[16], + &buf8[8]); + + m_decimator8.myDecimateInf( + &buf4[24], + &buf8[12]); + + m_decimator16.myDecimateInf( + &buf8[0], + &buf16[0]); + + m_decimator16.myDecimateInf( + &buf8[8], + &buf16[4]); + + m_decimator32.myDecimateCen( + &buf16[0], + &buf32[0]); + + (**it).setReal(buf32[0] >> decimation_shifts::post32); + (**it).setImag(buf32[1] >> decimation_shifts::post32); + ++(*it); + + (**it).setReal(buf32[2] >> decimation_shifts::post32); + (**it).setImag(buf32[3] >> decimation_shifts::post32); + ++(*it); + } } -template -void DecimatorsU::decimate64_inf(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate64_inf(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal[16], yimag[16]; + StorageType buf2[128], buf4[64], buf8[32], buf16[16], buf32[8], buf64[4]; - for (int pos = 0; pos < len - 127; ) - { - for (int i = 0; i < 16; i++) - { - xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) << decimation_shifts::pre64; - yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) << decimation_shifts::pre64; - pos += 8; - } + for (int pos = 0; pos < len - 255; pos += 256) + { + m_decimator2.myDecimateInf( + (buf[pos+0] - Shift) << decimation_shifts::pre64, + (buf[pos+1] - Shift) << decimation_shifts::pre64, + (buf[pos+2] - Shift) << decimation_shifts::pre64, + (buf[pos+3] - Shift) << decimation_shifts::pre64, + (buf[pos+4] - Shift) << decimation_shifts::pre64, + (buf[pos+5] - Shift) << decimation_shifts::pre64, + (buf[pos+6] - Shift) << decimation_shifts::pre64, + (buf[pos+7] - Shift) << decimation_shifts::pre64, + &buf2[0]); - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); - m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); - m_decimator2.myDecimate(xreal[8], yimag[8], &xreal[9], &yimag[9]); - m_decimator2.myDecimate(xreal[10], yimag[10], &xreal[11], &yimag[11]); - m_decimator2.myDecimate(xreal[12], yimag[12], &xreal[13], &yimag[13]); - m_decimator2.myDecimate(xreal[14], yimag[14], &xreal[15], &yimag[15]); - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); - m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); - m_decimator4.myDecimate(xreal[9], yimag[9], &xreal[11], &yimag[11]); - m_decimator4.myDecimate(xreal[13], yimag[13], &xreal[15], &yimag[15]); + m_decimator2.myDecimateInf( + (buf[pos+8] - Shift) << decimation_shifts::pre64, + (buf[pos+9] - Shift) << decimation_shifts::pre64, + (buf[pos+10] - Shift) << decimation_shifts::pre64, + (buf[pos+11] - Shift) << decimation_shifts::pre64, + (buf[pos+12] - Shift) << decimation_shifts::pre64, + (buf[pos+13] - Shift) << decimation_shifts::pre64, + (buf[pos+14] - Shift) << decimation_shifts::pre64, + (buf[pos+15] - Shift) << decimation_shifts::pre64, + &buf2[4]); - m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); - m_decimator8.myDecimate(xreal[11], yimag[11], &xreal[15], &yimag[15]); - m_decimator16.myDecimate(xreal[7], yimag[7], &xreal[15], &yimag[15]); + m_decimator2.myDecimateInf( + (buf[pos+16] - Shift) << decimation_shifts::pre64, + (buf[pos+17] - Shift) << decimation_shifts::pre64, + (buf[pos+18] - Shift) << decimation_shifts::pre64, + (buf[pos+19] - Shift) << decimation_shifts::pre64, + (buf[pos+20] - Shift) << decimation_shifts::pre64, + (buf[pos+21] - Shift) << decimation_shifts::pre64, + (buf[pos+22] - Shift) << decimation_shifts::pre64, + (buf[pos+23] - Shift) << decimation_shifts::pre64, + &buf2[8]); - (**it).setReal(xreal[15] >> decimation_shifts::post64); - (**it).setImag(yimag[15] >> decimation_shifts::post64); - ++(*it); - } + m_decimator2.myDecimateInf( + (buf[pos+24] - Shift) << decimation_shifts::pre64, + (buf[pos+25] - Shift) << decimation_shifts::pre64, + (buf[pos+26] - Shift) << decimation_shifts::pre64, + (buf[pos+27] - Shift) << decimation_shifts::pre64, + (buf[pos+28] - Shift) << decimation_shifts::pre64, + (buf[pos+29] - Shift) << decimation_shifts::pre64, + (buf[pos+30] - Shift) << decimation_shifts::pre64, + (buf[pos+31] - Shift) << decimation_shifts::pre64, + &buf2[12]); + + + m_decimator2.myDecimateInf( + (buf[pos+32] - Shift) << decimation_shifts::pre64, + (buf[pos+33] - Shift) << decimation_shifts::pre64, + (buf[pos+34] - Shift) << decimation_shifts::pre64, + (buf[pos+35] - Shift) << decimation_shifts::pre64, + (buf[pos+36] - Shift) << decimation_shifts::pre64, + (buf[pos+37] - Shift) << decimation_shifts::pre64, + (buf[pos+38] - Shift) << decimation_shifts::pre64, + (buf[pos+39] - Shift) << decimation_shifts::pre64, + &buf2[16]); + + + m_decimator2.myDecimateInf( + (buf[pos+40] - Shift) << decimation_shifts::pre64, + (buf[pos+41] - Shift) << decimation_shifts::pre64, + (buf[pos+42] - Shift) << decimation_shifts::pre64, + (buf[pos+43] - Shift) << decimation_shifts::pre64, + (buf[pos+44] - Shift) << decimation_shifts::pre64, + (buf[pos+45] - Shift) << decimation_shifts::pre64, + (buf[pos+46] - Shift) << decimation_shifts::pre64, + (buf[pos+47] - Shift) << decimation_shifts::pre64, + &buf2[20]); + + + m_decimator2.myDecimateInf( + (buf[pos+48] - Shift) << decimation_shifts::pre64, + (buf[pos+49] - Shift) << decimation_shifts::pre64, + (buf[pos+50] - Shift) << decimation_shifts::pre64, + (buf[pos+51] - Shift) << decimation_shifts::pre64, + (buf[pos+52] - Shift) << decimation_shifts::pre64, + (buf[pos+53] - Shift) << decimation_shifts::pre64, + (buf[pos+54] - Shift) << decimation_shifts::pre64, + (buf[pos+55] - Shift) << decimation_shifts::pre64, + &buf2[24]); + + + m_decimator2.myDecimateInf( + (buf[pos+56] - Shift) << decimation_shifts::pre64, + (buf[pos+57] - Shift) << decimation_shifts::pre64, + (buf[pos+58] - Shift) << decimation_shifts::pre64, + (buf[pos+59] - Shift) << decimation_shifts::pre64, + (buf[pos+60] - Shift) << decimation_shifts::pre64, + (buf[pos+61] - Shift) << decimation_shifts::pre64, + (buf[pos+62] - Shift) << decimation_shifts::pre64, + (buf[pos+63] - Shift) << decimation_shifts::pre64, + &buf2[28]); + + + m_decimator2.myDecimateInf( + (buf[pos+64] - Shift) << decimation_shifts::pre64, + (buf[pos+65] - Shift) << decimation_shifts::pre64, + (buf[pos+66] - Shift) << decimation_shifts::pre64, + (buf[pos+67] - Shift) << decimation_shifts::pre64, + (buf[pos+68] - Shift) << decimation_shifts::pre64, + (buf[pos+69] - Shift) << decimation_shifts::pre64, + (buf[pos+70] - Shift) << decimation_shifts::pre64, + (buf[pos+71] - Shift) << decimation_shifts::pre64, + &buf2[32]); + + + m_decimator2.myDecimateInf( + (buf[pos+72] - Shift) << decimation_shifts::pre64, + (buf[pos+73] - Shift) << decimation_shifts::pre64, + (buf[pos+74] - Shift) << decimation_shifts::pre64, + (buf[pos+75] - Shift) << decimation_shifts::pre64, + (buf[pos+76] - Shift) << decimation_shifts::pre64, + (buf[pos+77] - Shift) << decimation_shifts::pre64, + (buf[pos+78] - Shift) << decimation_shifts::pre64, + (buf[pos+79] - Shift) << decimation_shifts::pre64, + &buf2[36]); + + + m_decimator2.myDecimateInf( + (buf[pos+80] - Shift) << decimation_shifts::pre64, + (buf[pos+81] - Shift) << decimation_shifts::pre64, + (buf[pos+82] - Shift) << decimation_shifts::pre64, + (buf[pos+83] - Shift) << decimation_shifts::pre64, + (buf[pos+84] - Shift) << decimation_shifts::pre64, + (buf[pos+85] - Shift) << decimation_shifts::pre64, + (buf[pos+86] - Shift) << decimation_shifts::pre64, + (buf[pos+87] - Shift) << decimation_shifts::pre64, + &buf2[40]); + + + m_decimator2.myDecimateInf( + (buf[pos+88] - Shift) << decimation_shifts::pre64, + (buf[pos+89] - Shift) << decimation_shifts::pre64, + (buf[pos+90] - Shift) << decimation_shifts::pre64, + (buf[pos+91] - Shift) << decimation_shifts::pre64, + (buf[pos+92] - Shift) << decimation_shifts::pre64, + (buf[pos+93] - Shift) << decimation_shifts::pre64, + (buf[pos+94] - Shift) << decimation_shifts::pre64, + (buf[pos+95] - Shift) << decimation_shifts::pre64, + &buf2[44]); + + + m_decimator2.myDecimateInf( + (buf[pos+96] - Shift) << decimation_shifts::pre64, + (buf[pos+97] - Shift) << decimation_shifts::pre64, + (buf[pos+98] - Shift) << decimation_shifts::pre64, + (buf[pos+99] - Shift) << decimation_shifts::pre64, + (buf[pos+100] - Shift) << decimation_shifts::pre64, + (buf[pos+101] - Shift) << decimation_shifts::pre64, + (buf[pos+102] - Shift) << decimation_shifts::pre64, + (buf[pos+103] - Shift) << decimation_shifts::pre64, + &buf2[48]); + + + m_decimator2.myDecimateInf( + (buf[pos+104] - Shift) << decimation_shifts::pre64, + (buf[pos+105] - Shift) << decimation_shifts::pre64, + (buf[pos+106] - Shift) << decimation_shifts::pre64, + (buf[pos+107] - Shift) << decimation_shifts::pre64, + (buf[pos+108] - Shift) << decimation_shifts::pre64, + (buf[pos+109] - Shift) << decimation_shifts::pre64, + (buf[pos+110] - Shift) << decimation_shifts::pre64, + (buf[pos+111] - Shift) << decimation_shifts::pre64, + &buf2[52]); + + + m_decimator2.myDecimateInf( + (buf[pos+112] - Shift) << decimation_shifts::pre64, + (buf[pos+113] - Shift) << decimation_shifts::pre64, + (buf[pos+114] - Shift) << decimation_shifts::pre64, + (buf[pos+115] - Shift) << decimation_shifts::pre64, + (buf[pos+116] - Shift) << decimation_shifts::pre64, + (buf[pos+117] - Shift) << decimation_shifts::pre64, + (buf[pos+118] - Shift) << decimation_shifts::pre64, + (buf[pos+119] - Shift) << decimation_shifts::pre64, + &buf2[56]); + + + m_decimator2.myDecimateInf( + (buf[pos+120] - Shift) << decimation_shifts::pre64, + (buf[pos+121] - Shift) << decimation_shifts::pre64, + (buf[pos+122] - Shift) << decimation_shifts::pre64, + (buf[pos+123] - Shift) << decimation_shifts::pre64, + (buf[pos+124] - Shift) << decimation_shifts::pre64, + (buf[pos+125] - Shift) << decimation_shifts::pre64, + (buf[pos+126] - Shift) << decimation_shifts::pre64, + (buf[pos+127] - Shift) << decimation_shifts::pre64, + &buf2[60]); + + + m_decimator2.myDecimateInf( + (buf[pos+128] - Shift) << decimation_shifts::pre64, + (buf[pos+129] - Shift) << decimation_shifts::pre64, + (buf[pos+130] - Shift) << decimation_shifts::pre64, + (buf[pos+131] - Shift) << decimation_shifts::pre64, + (buf[pos+132] - Shift) << decimation_shifts::pre64, + (buf[pos+133] - Shift) << decimation_shifts::pre64, + (buf[pos+134] - Shift) << decimation_shifts::pre64, + (buf[pos+135] - Shift) << decimation_shifts::pre64, + &buf2[64]); + + + m_decimator2.myDecimateInf( + (buf[pos+136] - Shift) << decimation_shifts::pre64, + (buf[pos+137] - Shift) << decimation_shifts::pre64, + (buf[pos+138] - Shift) << decimation_shifts::pre64, + (buf[pos+139] - Shift) << decimation_shifts::pre64, + (buf[pos+140] - Shift) << decimation_shifts::pre64, + (buf[pos+141] - Shift) << decimation_shifts::pre64, + (buf[pos+142] - Shift) << decimation_shifts::pre64, + (buf[pos+143] - Shift) << decimation_shifts::pre64, + &buf2[68]); + + + m_decimator2.myDecimateInf( + (buf[pos+144] - Shift) << decimation_shifts::pre64, + (buf[pos+145] - Shift) << decimation_shifts::pre64, + (buf[pos+146] - Shift) << decimation_shifts::pre64, + (buf[pos+147] - Shift) << decimation_shifts::pre64, + (buf[pos+148] - Shift) << decimation_shifts::pre64, + (buf[pos+149] - Shift) << decimation_shifts::pre64, + (buf[pos+150] - Shift) << decimation_shifts::pre64, + (buf[pos+151] - Shift) << decimation_shifts::pre64, + &buf2[72]); + + + m_decimator2.myDecimateInf( + (buf[pos+152] - Shift) << decimation_shifts::pre64, + (buf[pos+153] - Shift) << decimation_shifts::pre64, + (buf[pos+154] - Shift) << decimation_shifts::pre64, + (buf[pos+155] - Shift) << decimation_shifts::pre64, + (buf[pos+156] - Shift) << decimation_shifts::pre64, + (buf[pos+157] - Shift) << decimation_shifts::pre64, + (buf[pos+158] - Shift) << decimation_shifts::pre64, + (buf[pos+159] - Shift) << decimation_shifts::pre64, + &buf2[76]); + + + m_decimator2.myDecimateInf( + (buf[pos+160] - Shift) << decimation_shifts::pre64, + (buf[pos+161] - Shift) << decimation_shifts::pre64, + (buf[pos+162] - Shift) << decimation_shifts::pre64, + (buf[pos+163] - Shift) << decimation_shifts::pre64, + (buf[pos+164] - Shift) << decimation_shifts::pre64, + (buf[pos+165] - Shift) << decimation_shifts::pre64, + (buf[pos+166] - Shift) << decimation_shifts::pre64, + (buf[pos+167] - Shift) << decimation_shifts::pre64, + &buf2[80]); + + + m_decimator2.myDecimateInf( + (buf[pos+168] - Shift) << decimation_shifts::pre64, + (buf[pos+169] - Shift) << decimation_shifts::pre64, + (buf[pos+170] - Shift) << decimation_shifts::pre64, + (buf[pos+171] - Shift) << decimation_shifts::pre64, + (buf[pos+172] - Shift) << decimation_shifts::pre64, + (buf[pos+173] - Shift) << decimation_shifts::pre64, + (buf[pos+174] - Shift) << decimation_shifts::pre64, + (buf[pos+175] - Shift) << decimation_shifts::pre64, + &buf2[84]); + + + m_decimator2.myDecimateInf( + (buf[pos+176] - Shift) << decimation_shifts::pre64, + (buf[pos+177] - Shift) << decimation_shifts::pre64, + (buf[pos+178] - Shift) << decimation_shifts::pre64, + (buf[pos+179] - Shift) << decimation_shifts::pre64, + (buf[pos+180] - Shift) << decimation_shifts::pre64, + (buf[pos+181] - Shift) << decimation_shifts::pre64, + (buf[pos+182] - Shift) << decimation_shifts::pre64, + (buf[pos+183] - Shift) << decimation_shifts::pre64, + &buf2[88]); + + + m_decimator2.myDecimateInf( + (buf[pos+184] - Shift) << decimation_shifts::pre64, + (buf[pos+185] - Shift) << decimation_shifts::pre64, + (buf[pos+186] - Shift) << decimation_shifts::pre64, + (buf[pos+187] - Shift) << decimation_shifts::pre64, + (buf[pos+188] - Shift) << decimation_shifts::pre64, + (buf[pos+189] - Shift) << decimation_shifts::pre64, + (buf[pos+190] - Shift) << decimation_shifts::pre64, + (buf[pos+191] - Shift) << decimation_shifts::pre64, + &buf2[92]); + + + m_decimator2.myDecimateInf( + (buf[pos+192] - Shift) << decimation_shifts::pre64, + (buf[pos+193] - Shift) << decimation_shifts::pre64, + (buf[pos+194] - Shift) << decimation_shifts::pre64, + (buf[pos+195] - Shift) << decimation_shifts::pre64, + (buf[pos+196] - Shift) << decimation_shifts::pre64, + (buf[pos+197] - Shift) << decimation_shifts::pre64, + (buf[pos+198] - Shift) << decimation_shifts::pre64, + (buf[pos+199] - Shift) << decimation_shifts::pre64, + &buf2[96]); + + + m_decimator2.myDecimateInf( + (buf[pos+200] - Shift) << decimation_shifts::pre64, + (buf[pos+201] - Shift) << decimation_shifts::pre64, + (buf[pos+202] - Shift) << decimation_shifts::pre64, + (buf[pos+203] - Shift) << decimation_shifts::pre64, + (buf[pos+204] - Shift) << decimation_shifts::pre64, + (buf[pos+205] - Shift) << decimation_shifts::pre64, + (buf[pos+206] - Shift) << decimation_shifts::pre64, + (buf[pos+207] - Shift) << decimation_shifts::pre64, + &buf2[100]); + + + m_decimator2.myDecimateInf( + (buf[pos+208] - Shift) << decimation_shifts::pre64, + (buf[pos+209] - Shift) << decimation_shifts::pre64, + (buf[pos+210] - Shift) << decimation_shifts::pre64, + (buf[pos+211] - Shift) << decimation_shifts::pre64, + (buf[pos+212] - Shift) << decimation_shifts::pre64, + (buf[pos+213] - Shift) << decimation_shifts::pre64, + (buf[pos+214] - Shift) << decimation_shifts::pre64, + (buf[pos+215] - Shift) << decimation_shifts::pre64, + &buf2[104]); + + + m_decimator2.myDecimateInf( + (buf[pos+216] - Shift) << decimation_shifts::pre64, + (buf[pos+217] - Shift) << decimation_shifts::pre64, + (buf[pos+218] - Shift) << decimation_shifts::pre64, + (buf[pos+219] - Shift) << decimation_shifts::pre64, + (buf[pos+220] - Shift) << decimation_shifts::pre64, + (buf[pos+221] - Shift) << decimation_shifts::pre64, + (buf[pos+222] - Shift) << decimation_shifts::pre64, + (buf[pos+223] - Shift) << decimation_shifts::pre64, + &buf2[108]); + + + m_decimator2.myDecimateInf( + (buf[pos+224] - Shift) << decimation_shifts::pre64, + (buf[pos+225] - Shift) << decimation_shifts::pre64, + (buf[pos+226] - Shift) << decimation_shifts::pre64, + (buf[pos+227] - Shift) << decimation_shifts::pre64, + (buf[pos+228] - Shift) << decimation_shifts::pre64, + (buf[pos+229] - Shift) << decimation_shifts::pre64, + (buf[pos+230] - Shift) << decimation_shifts::pre64, + (buf[pos+231] - Shift) << decimation_shifts::pre64, + &buf2[112]); + + + m_decimator2.myDecimateInf( + (buf[pos+232] - Shift) << decimation_shifts::pre64, + (buf[pos+233] - Shift) << decimation_shifts::pre64, + (buf[pos+234] - Shift) << decimation_shifts::pre64, + (buf[pos+235] - Shift) << decimation_shifts::pre64, + (buf[pos+236] - Shift) << decimation_shifts::pre64, + (buf[pos+237] - Shift) << decimation_shifts::pre64, + (buf[pos+238] - Shift) << decimation_shifts::pre64, + (buf[pos+239] - Shift) << decimation_shifts::pre64, + &buf2[116]); + + + m_decimator2.myDecimateInf( + (buf[pos+240] - Shift) << decimation_shifts::pre64, + (buf[pos+241] - Shift) << decimation_shifts::pre64, + (buf[pos+242] - Shift) << decimation_shifts::pre64, + (buf[pos+243] - Shift) << decimation_shifts::pre64, + (buf[pos+244] - Shift) << decimation_shifts::pre64, + (buf[pos+245] - Shift) << decimation_shifts::pre64, + (buf[pos+246] - Shift) << decimation_shifts::pre64, + (buf[pos+247] - Shift) << decimation_shifts::pre64, + &buf2[120]); + + + m_decimator2.myDecimateInf( + (buf[pos+248] - Shift) << decimation_shifts::pre64, + (buf[pos+249] - Shift) << decimation_shifts::pre64, + (buf[pos+250] - Shift) << decimation_shifts::pre64, + (buf[pos+251] - Shift) << decimation_shifts::pre64, + (buf[pos+252] - Shift) << decimation_shifts::pre64, + (buf[pos+253] - Shift) << decimation_shifts::pre64, + (buf[pos+254] - Shift) << decimation_shifts::pre64, + (buf[pos+255] - Shift) << decimation_shifts::pre64, + &buf2[124]); + + m_decimator4.myDecimateSup( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateSup( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateSup( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateSup( + &buf2[24], + &buf4[12]); + + m_decimator4.myDecimateSup( + &buf2[32], + &buf4[16]); + + m_decimator4.myDecimateSup( + &buf2[40], + &buf4[20]); + + m_decimator4.myDecimateSup( + &buf2[48], + &buf4[24]); + + m_decimator4.myDecimateSup( + &buf2[56], + &buf4[28]); + + m_decimator4.myDecimateSup( + &buf2[64], + &buf4[32]); + + m_decimator4.myDecimateSup( + &buf2[72], + &buf4[36]); + + m_decimator4.myDecimateSup( + &buf2[80], + &buf4[40]); + + m_decimator4.myDecimateSup( + &buf2[88], + &buf4[44]); + + m_decimator4.myDecimateSup( + &buf2[96], + &buf4[48]); + + m_decimator4.myDecimateSup( + &buf2[104], + &buf4[52]); + + m_decimator4.myDecimateSup( + &buf2[112], + &buf4[56]); + + m_decimator4.myDecimateSup( + &buf2[120], + &buf4[60]); + + m_decimator8.myDecimateSup( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateSup( + &buf4[8], + &buf8[4]); + + m_decimator8.myDecimateSup( + &buf4[16], + &buf8[8]); + + m_decimator8.myDecimateSup( + &buf4[24], + &buf8[12]); + + m_decimator8.myDecimateSup( + &buf4[32], + &buf8[16]); + + m_decimator8.myDecimateSup( + &buf4[40], + &buf8[20]); + + m_decimator8.myDecimateSup( + &buf4[48], + &buf8[24]); + + m_decimator8.myDecimateSup( + &buf4[56], + &buf8[28]); + + m_decimator16.myDecimateSup( + &buf8[0], + &buf16[0]); + + m_decimator16.myDecimateSup( + &buf8[8], + &buf16[4]); + + m_decimator16.myDecimateSup( + &buf8[16], + &buf16[8]); + + m_decimator16.myDecimateSup( + &buf8[24], + &buf16[12]); + + m_decimator32.myDecimateSup( + &buf16[0], + &buf32[0]); + + m_decimator32.myDecimateSup( + &buf16[8], + &buf32[4]); + + m_decimator64.myDecimateCen( + &buf32[0], + &buf64[0]); + + (**it).setReal(buf64[0] >> decimation_shifts::post64); + (**it).setImag(buf64[1] >> decimation_shifts::post64); + ++(*it); + + (**it).setReal(buf64[2] >> decimation_shifts::post64); + (**it).setImag(buf64[3] >> decimation_shifts::post64); + ++(*it); + } } -template -void DecimatorsU::decimate64_sup(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate64_sup(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType xreal[16], yimag[16]; + StorageType buf2[128], buf4[64], buf8[32], buf16[16], buf32[8], buf64[4]; - for (int pos = 0; pos < len - 127; ) - { - for (int i = 0; i < 16; i++) - { - xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) << decimation_shifts::pre32; - yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]) << decimation_shifts::pre32; - pos += 8; - } + for (int pos = 0; pos < len - 255; pos += 256) + { + m_decimator2.myDecimateSup( + (buf[pos+0] - Shift) << decimation_shifts::pre64, + (buf[pos+1] - Shift) << decimation_shifts::pre64, + (buf[pos+2] - Shift) << decimation_shifts::pre64, + (buf[pos+3] - Shift) << decimation_shifts::pre64, + (buf[pos+4] - Shift) << decimation_shifts::pre64, + (buf[pos+5] - Shift) << decimation_shifts::pre64, + (buf[pos+6] - Shift) << decimation_shifts::pre64, + (buf[pos+7] - Shift) << decimation_shifts::pre64, + &buf2[0]); - m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); - m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); - m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[5]); - m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); - m_decimator2.myDecimate(xreal[8], yimag[8], &xreal[9], &yimag[9]); - m_decimator2.myDecimate(xreal[10], yimag[10], &xreal[11], &yimag[11]); - m_decimator2.myDecimate(xreal[12], yimag[12], &xreal[13], &yimag[13]); - m_decimator2.myDecimate(xreal[14], yimag[14], &xreal[15], &yimag[15]); - m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); - m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); - m_decimator4.myDecimate(xreal[9], yimag[9], &xreal[11], &yimag[11]); - m_decimator4.myDecimate(xreal[13], yimag[13], &xreal[15], &yimag[15]); + m_decimator2.myDecimateSup( + (buf[pos+8] - Shift) << decimation_shifts::pre64, + (buf[pos+9] - Shift) << decimation_shifts::pre64, + (buf[pos+10] - Shift) << decimation_shifts::pre64, + (buf[pos+11] - Shift) << decimation_shifts::pre64, + (buf[pos+12] - Shift) << decimation_shifts::pre64, + (buf[pos+13] - Shift) << decimation_shifts::pre64, + (buf[pos+14] - Shift) << decimation_shifts::pre64, + (buf[pos+15] - Shift) << decimation_shifts::pre64, + &buf2[4]); - m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); - m_decimator8.myDecimate(xreal[11], yimag[11], &xreal[15], &yimag[15]); - m_decimator16.myDecimate(xreal[7], yimag[7], &xreal[15], &yimag[15]); + m_decimator2.myDecimateSup( + (buf[pos+16] - Shift) << decimation_shifts::pre64, + (buf[pos+17] - Shift) << decimation_shifts::pre64, + (buf[pos+18] - Shift) << decimation_shifts::pre64, + (buf[pos+19] - Shift) << decimation_shifts::pre64, + (buf[pos+20] - Shift) << decimation_shifts::pre64, + (buf[pos+21] - Shift) << decimation_shifts::pre64, + (buf[pos+22] - Shift) << decimation_shifts::pre64, + (buf[pos+23] - Shift) << decimation_shifts::pre64, + &buf2[8]); - (**it).setReal(xreal[15] >> decimation_shifts::post64); - (**it).setImag(yimag[15] >> decimation_shifts::post64); - ++(*it); - } + m_decimator2.myDecimateSup( + (buf[pos+24] - Shift) << decimation_shifts::pre64, + (buf[pos+25] - Shift) << decimation_shifts::pre64, + (buf[pos+26] - Shift) << decimation_shifts::pre64, + (buf[pos+27] - Shift) << decimation_shifts::pre64, + (buf[pos+28] - Shift) << decimation_shifts::pre64, + (buf[pos+29] - Shift) << decimation_shifts::pre64, + (buf[pos+30] - Shift) << decimation_shifts::pre64, + (buf[pos+31] - Shift) << decimation_shifts::pre64, + &buf2[12]); + + + m_decimator2.myDecimateSup( + (buf[pos+32] - Shift) << decimation_shifts::pre64, + (buf[pos+33] - Shift) << decimation_shifts::pre64, + (buf[pos+34] - Shift) << decimation_shifts::pre64, + (buf[pos+35] - Shift) << decimation_shifts::pre64, + (buf[pos+36] - Shift) << decimation_shifts::pre64, + (buf[pos+37] - Shift) << decimation_shifts::pre64, + (buf[pos+38] - Shift) << decimation_shifts::pre64, + (buf[pos+39] - Shift) << decimation_shifts::pre64, + &buf2[16]); + + + m_decimator2.myDecimateSup( + (buf[pos+40] - Shift) << decimation_shifts::pre64, + (buf[pos+41] - Shift) << decimation_shifts::pre64, + (buf[pos+42] - Shift) << decimation_shifts::pre64, + (buf[pos+43] - Shift) << decimation_shifts::pre64, + (buf[pos+44] - Shift) << decimation_shifts::pre64, + (buf[pos+45] - Shift) << decimation_shifts::pre64, + (buf[pos+46] - Shift) << decimation_shifts::pre64, + (buf[pos+47] - Shift) << decimation_shifts::pre64, + &buf2[20]); + + + m_decimator2.myDecimateSup( + (buf[pos+48] - Shift) << decimation_shifts::pre64, + (buf[pos+49] - Shift) << decimation_shifts::pre64, + (buf[pos+50] - Shift) << decimation_shifts::pre64, + (buf[pos+51] - Shift) << decimation_shifts::pre64, + (buf[pos+52] - Shift) << decimation_shifts::pre64, + (buf[pos+53] - Shift) << decimation_shifts::pre64, + (buf[pos+54] - Shift) << decimation_shifts::pre64, + (buf[pos+55] - Shift) << decimation_shifts::pre64, + &buf2[24]); + + + m_decimator2.myDecimateSup( + (buf[pos+56] - Shift) << decimation_shifts::pre64, + (buf[pos+57] - Shift) << decimation_shifts::pre64, + (buf[pos+58] - Shift) << decimation_shifts::pre64, + (buf[pos+59] - Shift) << decimation_shifts::pre64, + (buf[pos+60] - Shift) << decimation_shifts::pre64, + (buf[pos+61] - Shift) << decimation_shifts::pre64, + (buf[pos+62] - Shift) << decimation_shifts::pre64, + (buf[pos+63] - Shift) << decimation_shifts::pre64, + &buf2[28]); + + + m_decimator2.myDecimateSup( + (buf[pos+64] - Shift) << decimation_shifts::pre64, + (buf[pos+65] - Shift) << decimation_shifts::pre64, + (buf[pos+66] - Shift) << decimation_shifts::pre64, + (buf[pos+67] - Shift) << decimation_shifts::pre64, + (buf[pos+68] - Shift) << decimation_shifts::pre64, + (buf[pos+69] - Shift) << decimation_shifts::pre64, + (buf[pos+70] - Shift) << decimation_shifts::pre64, + (buf[pos+71] - Shift) << decimation_shifts::pre64, + &buf2[32]); + + + m_decimator2.myDecimateSup( + (buf[pos+72] - Shift) << decimation_shifts::pre64, + (buf[pos+73] - Shift) << decimation_shifts::pre64, + (buf[pos+74] - Shift) << decimation_shifts::pre64, + (buf[pos+75] - Shift) << decimation_shifts::pre64, + (buf[pos+76] - Shift) << decimation_shifts::pre64, + (buf[pos+77] - Shift) << decimation_shifts::pre64, + (buf[pos+78] - Shift) << decimation_shifts::pre64, + (buf[pos+79] - Shift) << decimation_shifts::pre64, + &buf2[36]); + + + m_decimator2.myDecimateSup( + (buf[pos+80] - Shift) << decimation_shifts::pre64, + (buf[pos+81] - Shift) << decimation_shifts::pre64, + (buf[pos+82] - Shift) << decimation_shifts::pre64, + (buf[pos+83] - Shift) << decimation_shifts::pre64, + (buf[pos+84] - Shift) << decimation_shifts::pre64, + (buf[pos+85] - Shift) << decimation_shifts::pre64, + (buf[pos+86] - Shift) << decimation_shifts::pre64, + (buf[pos+87] - Shift) << decimation_shifts::pre64, + &buf2[40]); + + + m_decimator2.myDecimateSup( + (buf[pos+88] - Shift) << decimation_shifts::pre64, + (buf[pos+89] - Shift) << decimation_shifts::pre64, + (buf[pos+90] - Shift) << decimation_shifts::pre64, + (buf[pos+91] - Shift) << decimation_shifts::pre64, + (buf[pos+92] - Shift) << decimation_shifts::pre64, + (buf[pos+93] - Shift) << decimation_shifts::pre64, + (buf[pos+94] - Shift) << decimation_shifts::pre64, + (buf[pos+95] - Shift) << decimation_shifts::pre64, + &buf2[44]); + + + m_decimator2.myDecimateSup( + (buf[pos+96] - Shift) << decimation_shifts::pre64, + (buf[pos+97] - Shift) << decimation_shifts::pre64, + (buf[pos+98] - Shift) << decimation_shifts::pre64, + (buf[pos+99] - Shift) << decimation_shifts::pre64, + (buf[pos+100] - Shift) << decimation_shifts::pre64, + (buf[pos+101] - Shift) << decimation_shifts::pre64, + (buf[pos+102] - Shift) << decimation_shifts::pre64, + (buf[pos+103] - Shift) << decimation_shifts::pre64, + &buf2[48]); + + + m_decimator2.myDecimateSup( + (buf[pos+104] - Shift) << decimation_shifts::pre64, + (buf[pos+105] - Shift) << decimation_shifts::pre64, + (buf[pos+106] - Shift) << decimation_shifts::pre64, + (buf[pos+107] - Shift) << decimation_shifts::pre64, + (buf[pos+108] - Shift) << decimation_shifts::pre64, + (buf[pos+109] - Shift) << decimation_shifts::pre64, + (buf[pos+110] - Shift) << decimation_shifts::pre64, + (buf[pos+111] - Shift) << decimation_shifts::pre64, + &buf2[52]); + + + m_decimator2.myDecimateSup( + (buf[pos+112] - Shift) << decimation_shifts::pre64, + (buf[pos+113] - Shift) << decimation_shifts::pre64, + (buf[pos+114] - Shift) << decimation_shifts::pre64, + (buf[pos+115] - Shift) << decimation_shifts::pre64, + (buf[pos+116] - Shift) << decimation_shifts::pre64, + (buf[pos+117] - Shift) << decimation_shifts::pre64, + (buf[pos+118] - Shift) << decimation_shifts::pre64, + (buf[pos+119] - Shift) << decimation_shifts::pre64, + &buf2[56]); + + + m_decimator2.myDecimateSup( + (buf[pos+120] - Shift) << decimation_shifts::pre64, + (buf[pos+121] - Shift) << decimation_shifts::pre64, + (buf[pos+122] - Shift) << decimation_shifts::pre64, + (buf[pos+123] - Shift) << decimation_shifts::pre64, + (buf[pos+124] - Shift) << decimation_shifts::pre64, + (buf[pos+125] - Shift) << decimation_shifts::pre64, + (buf[pos+126] - Shift) << decimation_shifts::pre64, + (buf[pos+127] - Shift) << decimation_shifts::pre64, + &buf2[60]); + + + m_decimator2.myDecimateSup( + (buf[pos+128] - Shift) << decimation_shifts::pre64, + (buf[pos+129] - Shift) << decimation_shifts::pre64, + (buf[pos+130] - Shift) << decimation_shifts::pre64, + (buf[pos+131] - Shift) << decimation_shifts::pre64, + (buf[pos+132] - Shift) << decimation_shifts::pre64, + (buf[pos+133] - Shift) << decimation_shifts::pre64, + (buf[pos+134] - Shift) << decimation_shifts::pre64, + (buf[pos+135] - Shift) << decimation_shifts::pre64, + &buf2[64]); + + + m_decimator2.myDecimateSup( + (buf[pos+136] - Shift) << decimation_shifts::pre64, + (buf[pos+137] - Shift) << decimation_shifts::pre64, + (buf[pos+138] - Shift) << decimation_shifts::pre64, + (buf[pos+139] - Shift) << decimation_shifts::pre64, + (buf[pos+140] - Shift) << decimation_shifts::pre64, + (buf[pos+141] - Shift) << decimation_shifts::pre64, + (buf[pos+142] - Shift) << decimation_shifts::pre64, + (buf[pos+143] - Shift) << decimation_shifts::pre64, + &buf2[68]); + + + m_decimator2.myDecimateSup( + (buf[pos+144] - Shift) << decimation_shifts::pre64, + (buf[pos+145] - Shift) << decimation_shifts::pre64, + (buf[pos+146] - Shift) << decimation_shifts::pre64, + (buf[pos+147] - Shift) << decimation_shifts::pre64, + (buf[pos+148] - Shift) << decimation_shifts::pre64, + (buf[pos+149] - Shift) << decimation_shifts::pre64, + (buf[pos+150] - Shift) << decimation_shifts::pre64, + (buf[pos+151] - Shift) << decimation_shifts::pre64, + &buf2[72]); + + + m_decimator2.myDecimateSup( + (buf[pos+152] - Shift) << decimation_shifts::pre64, + (buf[pos+153] - Shift) << decimation_shifts::pre64, + (buf[pos+154] - Shift) << decimation_shifts::pre64, + (buf[pos+155] - Shift) << decimation_shifts::pre64, + (buf[pos+156] - Shift) << decimation_shifts::pre64, + (buf[pos+157] - Shift) << decimation_shifts::pre64, + (buf[pos+158] - Shift) << decimation_shifts::pre64, + (buf[pos+159] - Shift) << decimation_shifts::pre64, + &buf2[76]); + + + m_decimator2.myDecimateSup( + (buf[pos+160] - Shift) << decimation_shifts::pre64, + (buf[pos+161] - Shift) << decimation_shifts::pre64, + (buf[pos+162] - Shift) << decimation_shifts::pre64, + (buf[pos+163] - Shift) << decimation_shifts::pre64, + (buf[pos+164] - Shift) << decimation_shifts::pre64, + (buf[pos+165] - Shift) << decimation_shifts::pre64, + (buf[pos+166] - Shift) << decimation_shifts::pre64, + (buf[pos+167] - Shift) << decimation_shifts::pre64, + &buf2[80]); + + + m_decimator2.myDecimateSup( + (buf[pos+168] - Shift) << decimation_shifts::pre64, + (buf[pos+169] - Shift) << decimation_shifts::pre64, + (buf[pos+170] - Shift) << decimation_shifts::pre64, + (buf[pos+171] - Shift) << decimation_shifts::pre64, + (buf[pos+172] - Shift) << decimation_shifts::pre64, + (buf[pos+173] - Shift) << decimation_shifts::pre64, + (buf[pos+174] - Shift) << decimation_shifts::pre64, + (buf[pos+175] - Shift) << decimation_shifts::pre64, + &buf2[84]); + + + m_decimator2.myDecimateSup( + (buf[pos+176] - Shift) << decimation_shifts::pre64, + (buf[pos+177] - Shift) << decimation_shifts::pre64, + (buf[pos+178] - Shift) << decimation_shifts::pre64, + (buf[pos+179] - Shift) << decimation_shifts::pre64, + (buf[pos+180] - Shift) << decimation_shifts::pre64, + (buf[pos+181] - Shift) << decimation_shifts::pre64, + (buf[pos+182] - Shift) << decimation_shifts::pre64, + (buf[pos+183] - Shift) << decimation_shifts::pre64, + &buf2[88]); + + + m_decimator2.myDecimateSup( + (buf[pos+184] - Shift) << decimation_shifts::pre64, + (buf[pos+185] - Shift) << decimation_shifts::pre64, + (buf[pos+186] - Shift) << decimation_shifts::pre64, + (buf[pos+187] - Shift) << decimation_shifts::pre64, + (buf[pos+188] - Shift) << decimation_shifts::pre64, + (buf[pos+189] - Shift) << decimation_shifts::pre64, + (buf[pos+190] - Shift) << decimation_shifts::pre64, + (buf[pos+191] - Shift) << decimation_shifts::pre64, + &buf2[92]); + + + m_decimator2.myDecimateSup( + (buf[pos+192] - Shift) << decimation_shifts::pre64, + (buf[pos+193] - Shift) << decimation_shifts::pre64, + (buf[pos+194] - Shift) << decimation_shifts::pre64, + (buf[pos+195] - Shift) << decimation_shifts::pre64, + (buf[pos+196] - Shift) << decimation_shifts::pre64, + (buf[pos+197] - Shift) << decimation_shifts::pre64, + (buf[pos+198] - Shift) << decimation_shifts::pre64, + (buf[pos+199] - Shift) << decimation_shifts::pre64, + &buf2[96]); + + + m_decimator2.myDecimateSup( + (buf[pos+200] - Shift) << decimation_shifts::pre64, + (buf[pos+201] - Shift) << decimation_shifts::pre64, + (buf[pos+202] - Shift) << decimation_shifts::pre64, + (buf[pos+203] - Shift) << decimation_shifts::pre64, + (buf[pos+204] - Shift) << decimation_shifts::pre64, + (buf[pos+205] - Shift) << decimation_shifts::pre64, + (buf[pos+206] - Shift) << decimation_shifts::pre64, + (buf[pos+207] - Shift) << decimation_shifts::pre64, + &buf2[100]); + + + m_decimator2.myDecimateSup( + (buf[pos+208] - Shift) << decimation_shifts::pre64, + (buf[pos+209] - Shift) << decimation_shifts::pre64, + (buf[pos+210] - Shift) << decimation_shifts::pre64, + (buf[pos+211] - Shift) << decimation_shifts::pre64, + (buf[pos+212] - Shift) << decimation_shifts::pre64, + (buf[pos+213] - Shift) << decimation_shifts::pre64, + (buf[pos+214] - Shift) << decimation_shifts::pre64, + (buf[pos+215] - Shift) << decimation_shifts::pre64, + &buf2[104]); + + + m_decimator2.myDecimateSup( + (buf[pos+216] - Shift) << decimation_shifts::pre64, + (buf[pos+217] - Shift) << decimation_shifts::pre64, + (buf[pos+218] - Shift) << decimation_shifts::pre64, + (buf[pos+219] - Shift) << decimation_shifts::pre64, + (buf[pos+220] - Shift) << decimation_shifts::pre64, + (buf[pos+221] - Shift) << decimation_shifts::pre64, + (buf[pos+222] - Shift) << decimation_shifts::pre64, + (buf[pos+223] - Shift) << decimation_shifts::pre64, + &buf2[108]); + + + m_decimator2.myDecimateSup( + (buf[pos+224] - Shift) << decimation_shifts::pre64, + (buf[pos+225] - Shift) << decimation_shifts::pre64, + (buf[pos+226] - Shift) << decimation_shifts::pre64, + (buf[pos+227] - Shift) << decimation_shifts::pre64, + (buf[pos+228] - Shift) << decimation_shifts::pre64, + (buf[pos+229] - Shift) << decimation_shifts::pre64, + (buf[pos+230] - Shift) << decimation_shifts::pre64, + (buf[pos+231] - Shift) << decimation_shifts::pre64, + &buf2[112]); + + + m_decimator2.myDecimateSup( + (buf[pos+232] - Shift) << decimation_shifts::pre64, + (buf[pos+233] - Shift) << decimation_shifts::pre64, + (buf[pos+234] - Shift) << decimation_shifts::pre64, + (buf[pos+235] - Shift) << decimation_shifts::pre64, + (buf[pos+236] - Shift) << decimation_shifts::pre64, + (buf[pos+237] - Shift) << decimation_shifts::pre64, + (buf[pos+238] - Shift) << decimation_shifts::pre64, + (buf[pos+239] - Shift) << decimation_shifts::pre64, + &buf2[116]); + + + m_decimator2.myDecimateSup( + (buf[pos+240] - Shift) << decimation_shifts::pre64, + (buf[pos+241] - Shift) << decimation_shifts::pre64, + (buf[pos+242] - Shift) << decimation_shifts::pre64, + (buf[pos+243] - Shift) << decimation_shifts::pre64, + (buf[pos+244] - Shift) << decimation_shifts::pre64, + (buf[pos+245] - Shift) << decimation_shifts::pre64, + (buf[pos+246] - Shift) << decimation_shifts::pre64, + (buf[pos+247] - Shift) << decimation_shifts::pre64, + &buf2[120]); + + + m_decimator2.myDecimateSup( + (buf[pos+248] - Shift) << decimation_shifts::pre64, + (buf[pos+249] - Shift) << decimation_shifts::pre64, + (buf[pos+250] - Shift) << decimation_shifts::pre64, + (buf[pos+251] - Shift) << decimation_shifts::pre64, + (buf[pos+252] - Shift) << decimation_shifts::pre64, + (buf[pos+253] - Shift) << decimation_shifts::pre64, + (buf[pos+254] - Shift) << decimation_shifts::pre64, + (buf[pos+255] - Shift) << decimation_shifts::pre64, + &buf2[124]); + + m_decimator4.myDecimateInf( + &buf2[0], + &buf4[0]); + + m_decimator4.myDecimateInf( + &buf2[8], + &buf4[4]); + + m_decimator4.myDecimateInf( + &buf2[16], + &buf4[8]); + + m_decimator4.myDecimateInf( + &buf2[24], + &buf4[12]); + + m_decimator4.myDecimateInf( + &buf2[32], + &buf4[16]); + + m_decimator4.myDecimateInf( + &buf2[40], + &buf4[20]); + + m_decimator4.myDecimateInf( + &buf2[48], + &buf4[24]); + + m_decimator4.myDecimateInf( + &buf2[56], + &buf4[28]); + + m_decimator4.myDecimateInf( + &buf2[64], + &buf4[32]); + + m_decimator4.myDecimateInf( + &buf2[72], + &buf4[36]); + + m_decimator4.myDecimateInf( + &buf2[80], + &buf4[40]); + + m_decimator4.myDecimateInf( + &buf2[88], + &buf4[44]); + + m_decimator4.myDecimateInf( + &buf2[96], + &buf4[48]); + + m_decimator4.myDecimateInf( + &buf2[104], + &buf4[52]); + + m_decimator4.myDecimateInf( + &buf2[112], + &buf4[56]); + + m_decimator4.myDecimateInf( + &buf2[120], + &buf4[60]); + + m_decimator8.myDecimateInf( + &buf4[0], + &buf8[0]); + + m_decimator8.myDecimateInf( + &buf4[8], + &buf8[4]); + + m_decimator8.myDecimateInf( + &buf4[16], + &buf8[8]); + + m_decimator8.myDecimateInf( + &buf4[24], + &buf8[12]); + + m_decimator8.myDecimateInf( + &buf4[32], + &buf8[16]); + + m_decimator8.myDecimateInf( + &buf4[40], + &buf8[20]); + + m_decimator8.myDecimateInf( + &buf4[48], + &buf8[24]); + + m_decimator8.myDecimateInf( + &buf4[56], + &buf8[28]); + + m_decimator16.myDecimateInf( + &buf8[0], + &buf16[0]); + + m_decimator16.myDecimateInf( + &buf8[8], + &buf16[4]); + + m_decimator16.myDecimateInf( + &buf8[16], + &buf16[8]); + + m_decimator16.myDecimateInf( + &buf8[24], + &buf16[12]); + + m_decimator32.myDecimateInf( + &buf16[0], + &buf32[0]); + + m_decimator32.myDecimateInf( + &buf16[8], + &buf32[4]); + + m_decimator64.myDecimateCen( + &buf32[0], + &buf64[0]); + + (**it).setReal(buf64[0] >> decimation_shifts::post64); + (**it).setImag(buf64[1] >> decimation_shifts::post64); + ++(*it); + + (**it).setReal(buf64[2] >> decimation_shifts::post64); + (**it).setImag(buf64[3] >> decimation_shifts::post64); + ++(*it); + } } -template -void DecimatorsU::decimate2_cen(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate2_cen(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType intbuf[2]; + StorageType buf2[4]; - for (int pos = 0; pos < len - 3; pos += 4) - { - intbuf[0] = (buf[pos+2] - Shift) << decimation_shifts::pre2; - intbuf[1] = (buf[pos+3] - Shift) << decimation_shifts::pre2; + for (int pos = 0; pos < len - 7; pos += 8) + { + m_decimator2.myDecimateCen( + (buf[pos+0] - Shift) << decimation_shifts::pre2, + (buf[pos+1] - Shift) << decimation_shifts::pre2, + (buf[pos+2] - Shift) << decimation_shifts::pre2, + (buf[pos+3] - Shift) << decimation_shifts::pre2, + (buf[pos+4] - Shift) << decimation_shifts::pre2, + (buf[pos+5] - Shift) << decimation_shifts::pre2, + (buf[pos+6] - Shift) << decimation_shifts::pre2, + (buf[pos+7] - Shift) << decimation_shifts::pre2, + &buf2[0]); - m_decimator2.myDecimate( - (buf[pos+0] - Shift) << decimation_shifts::pre2, - (buf[pos+1] - Shift) << decimation_shifts::pre2, - &intbuf[0], - &intbuf[1]); + (**it).setReal(buf2[0] >> decimation_shifts::post2); + (**it).setImag(buf2[1] >> decimation_shifts::post2); + ++(*it); - (**it).setReal(intbuf[0] >> decimation_shifts::post2); - (**it).setImag(intbuf[1] >> decimation_shifts::post2); - ++(*it); - } + (**it).setReal(buf2[2] >> decimation_shifts::post2); + (**it).setImag(buf2[3] >> decimation_shifts::post2); + ++(*it); + } } -template -void DecimatorsU::decimate4_cen(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate4_cen(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType intbuf[4]; + StorageType buf2[8], buf4[4]; - for (int pos = 0; pos < len - 7; pos += 8) - { - intbuf[0] = (buf[pos+2] - Shift) << decimation_shifts::pre4; - intbuf[1] = (buf[pos+3] - Shift) << decimation_shifts::pre4; - intbuf[2] = (buf[pos+6] - Shift) << decimation_shifts::pre4; - intbuf[3] = (buf[pos+7] - Shift) << decimation_shifts::pre4; + for (int pos = 0; pos < len - 15; pos += 16) + { + m_decimator2.myDecimateCen( + (buf[pos+0] - Shift) << decimation_shifts::pre4, + (buf[pos+1] - Shift) << decimation_shifts::pre4, + (buf[pos+2] - Shift) << decimation_shifts::pre4, + (buf[pos+3] - Shift) << decimation_shifts::pre4, + (buf[pos+4] - Shift) << decimation_shifts::pre4, + (buf[pos+5] - Shift) << decimation_shifts::pre4, + (buf[pos+6] - Shift) << decimation_shifts::pre4, + (buf[pos+7] - Shift) << decimation_shifts::pre4, + &buf2[0]); - m_decimator2.myDecimate( - (buf[pos+0] - Shift) << decimation_shifts::pre4, - (buf[pos+1] - Shift) << decimation_shifts::pre4, - &intbuf[0], - &intbuf[1]); - m_decimator2.myDecimate( - (buf[pos+4] - Shift) << decimation_shifts::pre4, - (buf[pos+5] - Shift) << decimation_shifts::pre4, - &intbuf[2], - &intbuf[3]); + m_decimator2.myDecimateCen( + (buf[pos+8] - Shift) << decimation_shifts::pre4, + (buf[pos+9] - Shift) << decimation_shifts::pre4, + (buf[pos+10] - Shift) << decimation_shifts::pre4, + (buf[pos+11] - Shift) << decimation_shifts::pre4, + (buf[pos+12] - Shift) << decimation_shifts::pre4, + (buf[pos+13] - Shift) << decimation_shifts::pre4, + (buf[pos+14] - Shift) << decimation_shifts::pre4, + (buf[pos+15] - Shift) << decimation_shifts::pre4, + &buf2[4]); - m_decimator4.myDecimate( - intbuf[0], - intbuf[1], - &intbuf[2], - &intbuf[3]); + m_decimator4.myDecimateCen( + &buf2[0], + &buf4[0]); - (**it).setReal(intbuf[2] >> decimation_shifts::post4); - (**it).setImag(intbuf[3] >> decimation_shifts::post4); - ++(*it); - } + (**it).setReal(buf4[0] >> decimation_shifts::post4); + (**it).setImag(buf4[1] >> decimation_shifts::post4); + ++(*it); + + (**it).setReal(buf4[2] >> decimation_shifts::post4); + (**it).setImag(buf4[3] >> decimation_shifts::post4); + ++(*it); + } } -template -void DecimatorsU::decimate8_cen(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate8_cen(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType intbuf[8]; + StorageType intbuf[8]; for (int pos = 0; pos < len - 15; pos += 16) { @@ -690,10 +2533,10 @@ void DecimatorsU::decimate8_cen(SampleVe } } -template -void DecimatorsU::decimate16_cen(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate16_cen(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType intbuf[16]; + StorageType intbuf[16]; for (int pos = 0; pos < len - 31; pos += 32) { @@ -799,10 +2642,10 @@ void DecimatorsU::decimate16_cen(SampleV } } -template -void DecimatorsU::decimate32_cen(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate32_cen(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType intbuf[32]; + StorageType intbuf[32]; for (int pos = 0; pos < len - 63; pos += 64) { @@ -1005,10 +2848,10 @@ void DecimatorsU::decimate32_cen(SampleV } } -template -void DecimatorsU::decimate64_cen(SampleVector::iterator* it, const T* buf, qint32 len) +template +void DecimatorsU::decimate64_cen(SampleVector::iterator* it, const T* buf, qint32 len) { - AccuType intbuf[64]; + StorageType intbuf[64]; for (int pos = 0; pos < len - 127; pos += 128) { diff --git a/sdrbase/dsp/devicesamplesink.h b/sdrbase/dsp/devicesamplesink.h index e49b441f3..5666f5e8c 100644 --- a/sdrbase/dsp/devicesamplesink.h +++ b/sdrbase/dsp/devicesamplesink.h @@ -23,15 +23,16 @@ #include "samplesourcefifo.h" #include "util/message.h" #include "util/messagequeue.h" -#include "util/export.h" +#include "export.h" namespace SWGSDRangel { class SWGDeviceSettings; class SWGDeviceState; + class SWGDeviceReport; } -class SDRANGEL_API DeviceSampleSink : public QObject { +class SDRBASE_API DeviceSampleSink : public QObject { Q_OBJECT public: DeviceSampleSink(); @@ -74,6 +75,11 @@ public: QString& errorMessage) { errorMessage = "Not implemented"; return 501; } + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response __attribute__((unused)), + QString& errorMessage) + { errorMessage = "Not implemented"; return 501; } + MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } virtual void setMessageQueueToGUI(MessageQueue *queue) = 0; // pure virtual so that child classes must have to deal with this MessageQueue *getMessageQueueToGUI() { return m_guiMessageQueue; } diff --git a/sdrbase/dsp/devicesamplesource.cpp b/sdrbase/dsp/devicesamplesource.cpp index 4295d2f3b..1a637bc0b 100644 --- a/sdrbase/dsp/devicesamplesource.cpp +++ b/sdrbase/dsp/devicesamplesource.cpp @@ -15,6 +15,7 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include #include DeviceSampleSource::DeviceSampleSource() : @@ -39,3 +40,71 @@ void DeviceSampleSource::handleInputMessages() } } } + +qint64 DeviceSampleSource::calculateDeviceCenterFrequency( + quint64 centerFrequency, + qint64 transverterDeltaFrequency, + int log2Decim, + fcPos_t fcPos, + quint32 devSampleRate, + bool transverterMode) +{ + qint64 deviceCenterFrequency = centerFrequency; + deviceCenterFrequency -= transverterMode ? transverterDeltaFrequency : 0; + deviceCenterFrequency = deviceCenterFrequency < 0 ? 0 : deviceCenterFrequency; + qint64 f_img = deviceCenterFrequency; + + deviceCenterFrequency -= calculateFrequencyShift(log2Decim, fcPos, devSampleRate); + f_img -= 2*calculateFrequencyShift(log2Decim, fcPos, devSampleRate); + + qDebug() << "DeviceSampleSource::calculateDeviceCenterFrequency:" + << " desired center freq: " << centerFrequency << " Hz" + << " device center freq: " << deviceCenterFrequency << " Hz" + << " device sample rate: " << devSampleRate << "S/s" + << " Actual sample rate: " << devSampleRate/(1< 2: fc = +/- 1/2^n + * center + * | ^ | + * | |inf| | |sup| | + * ^ ^ + */ +qint32 DeviceSampleSource::calculateFrequencyShift( + int log2Decim, + fcPos_t fcPos, + quint32 devSampleRate) +{ + if (log2Decim == 0) { // no shift at all + return 0; + } else if (log2Decim < 3) { + if (fcPos == FC_POS_INFRA) { // shift in the square next to center frequency + return -(devSampleRate / (1<<(log2Decim+1))); + } else if (fcPos == FC_POS_SUPRA) { + return devSampleRate / (1<<(log2Decim+1)); + } else { + return 0; + } + } else { + if (fcPos == FC_POS_INFRA) { // shift centered in the square next to center frequency + return -(devSampleRate / (1<<(log2Decim))); + } else if (fcPos == FC_POS_SUPRA) { + return devSampleRate / (1<<(log2Decim)); + } else { + return 0; + } + } +} diff --git a/sdrbase/dsp/devicesamplesource.h b/sdrbase/dsp/devicesamplesource.h index dbc570b20..60c541d8a 100644 --- a/sdrbase/dsp/devicesamplesource.h +++ b/sdrbase/dsp/devicesamplesource.h @@ -24,17 +24,24 @@ #include "samplesinkfifo.h" #include "util/message.h" #include "util/messagequeue.h" -#include "util/export.h" +#include "export.h" namespace SWGSDRangel { class SWGDeviceSettings; class SWGDeviceState; + class SWGDeviceReport; } -class SDRANGEL_API DeviceSampleSource : public QObject { +class SDRBASE_API DeviceSampleSource : public QObject { Q_OBJECT public: + typedef enum { + FC_POS_INFRA = 0, + FC_POS_SUPRA, + FC_POS_CENTER + } fcPos_t; + DeviceSampleSource(); virtual ~DeviceSampleSource(); virtual void destroy() = 0; @@ -75,11 +82,29 @@ public: QString& errorMessage) { errorMessage = "Not implemented"; return 501; } + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response __attribute__((unused)), + QString& errorMessage) + { errorMessage = "Not implemented"; return 501; } + MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } virtual void setMessageQueueToGUI(MessageQueue *queue) = 0; // pure virtual so that child classes must have to deal with this MessageQueue *getMessageQueueToGUI() { return m_guiMessageQueue; } SampleSinkFifo* getSampleFifo() { return &m_sampleFifo; } + static qint64 calculateDeviceCenterFrequency( + quint64 centerFrequency, + qint64 transverterDeltaFrequency, + int log2Decim, + fcPos_t fcPos, + quint32 devSampleRate, + bool transverterMode = false); + + static qint32 calculateFrequencyShift( + int log2Decim, + fcPos_t fcPos, + quint32 devSampleRate); + protected slots: void handleInputMessages(); diff --git a/sdrbase/dsp/downchannelizer.cpp b/sdrbase/dsp/downchannelizer.cpp index cfbec834d..b8fe5f3f3 100644 --- a/sdrbase/dsp/downchannelizer.cpp +++ b/sdrbase/dsp/downchannelizer.cpp @@ -110,8 +110,6 @@ void DownChannelizer::stop() bool DownChannelizer::handleMessage(const Message& cmd) { - qDebug() << "DownChannelizer::handleMessage: " << cmd.getIdentifier(); - // TODO: apply changes only if input sample rate or requested output sample rate change. Change of center frequency has no impact. if (DSPSignalNotification::match(cmd)) @@ -144,17 +142,15 @@ bool DownChannelizer::handleMessage(const Message& cmd) return true; } + else if (BasebandSampleSink::MsgThreadedSink::match(cmd)) + { + qDebug() << "DownChannelizer::handleMessage: MsgThreadedSink: forwarded to demod"; + return m_sampleSink->handleMessage(cmd); // this message is passed to the demod + } else { + qDebug() << "DownChannelizer::handleMessage: " << cmd.getIdentifier() << " unhandled"; return false; -// if (m_sampleSink != 0) -// { -// return m_sampleSink->handleMessage(cmd); -// } -// else -// { -// return false; -// } } } @@ -194,70 +190,48 @@ void DownChannelizer::applyConfiguration() #ifdef SDR_RX_SAMPLE_24BIT DownChannelizer::FilterStage::FilterStage(Mode mode) : - m_filter(new IntHalfbandFilterDB), + m_filter(new IntHalfbandFilterEO), m_workFunction(0), m_mode(mode), - m_sse(false) + m_sse(true) { switch(mode) { case ModeCenter: - m_workFunction = &IntHalfbandFilterDB::workDecimateCenter; + m_workFunction = &IntHalfbandFilterEO::workDecimateCenter; break; case ModeLowerHalf: - m_workFunction = &IntHalfbandFilterDB::workDecimateLowerHalf; + m_workFunction = &IntHalfbandFilterEO::workDecimateLowerHalf; break; case ModeUpperHalf: - m_workFunction = &IntHalfbandFilterDB::workDecimateUpperHalf; + m_workFunction = &IntHalfbandFilterEO::workDecimateUpperHalf; break; } } #else -#ifdef USE_SSE4_1 DownChannelizer::FilterStage::FilterStage(Mode mode) : - m_filter(new IntHalfbandFilterEO1), - m_workFunction(0), - m_mode(mode), - m_sse(true) + m_filter(new IntHalfbandFilterEO), + m_workFunction(0), + m_mode(mode), + m_sse(true) { - switch(mode) { - case ModeCenter: - m_workFunction = &IntHalfbandFilterEO1::workDecimateCenter; - break; + switch(mode) { + case ModeCenter: + m_workFunction = &IntHalfbandFilterEO::workDecimateCenter; + break; - case ModeLowerHalf: - m_workFunction = &IntHalfbandFilterEO1::workDecimateLowerHalf; - break; + case ModeLowerHalf: + m_workFunction = &IntHalfbandFilterEO::workDecimateLowerHalf; + break; - case ModeUpperHalf: - m_workFunction = &IntHalfbandFilterEO1::workDecimateUpperHalf; - break; - } -} -#else -DownChannelizer::FilterStage::FilterStage(Mode mode) : - m_filter(new IntHalfbandFilterDB), - m_workFunction(0), - m_mode(mode), - m_sse(false) -{ - switch(mode) { - case ModeCenter: - m_workFunction = &IntHalfbandFilterDB::workDecimateCenter; - break; - - case ModeLowerHalf: - m_workFunction = &IntHalfbandFilterDB::workDecimateLowerHalf; - break; - - case ModeUpperHalf: - m_workFunction = &IntHalfbandFilterDB::workDecimateUpperHalf; - break; - } + case ModeUpperHalf: + m_workFunction = &IntHalfbandFilterEO::workDecimateUpperHalf; + break; + } } #endif -#endif + DownChannelizer::FilterStage::~FilterStage() { delete m_filter; diff --git a/sdrbase/dsp/downchannelizer.h b/sdrbase/dsp/downchannelizer.h index 96d031354..3e5059154 100644 --- a/sdrbase/dsp/downchannelizer.h +++ b/sdrbase/dsp/downchannelizer.h @@ -21,26 +21,18 @@ #include #include #include -#include "util/export.h" +#include "export.h" #include "util/message.h" -#ifdef SDR_RX_SAMPLE_24BIT -#include "dsp/inthalfbandfilterdb.h" -#else -#ifdef USE_SSE4_1 -#include "dsp/inthalfbandfiltereo1.h" -#else -#include "dsp/inthalfbandfilterdb.h" -#endif -#endif +#include "dsp/inthalfbandfiltereo.h" #define DOWNCHANNELIZER_HB_FILTER_ORDER 48 class MessageQueue; -class SDRANGEL_API DownChannelizer : public BasebandSampleSink { +class SDRBASE_API DownChannelizer : public BasebandSampleSink { Q_OBJECT public: - class SDRANGEL_API MsgChannelizerNotification : public Message { + class MsgChannelizerNotification : public Message { MESSAGE_CLASS_DECLARATION public: @@ -68,6 +60,7 @@ public: void configure(MessageQueue* messageQueue, int sampleRate, int centerFrequency); int getInputSampleRate() const { return m_inputSampleRate; } + int getRequestedCenterFrequency() const { return m_requestedCenterFrequency; } virtual void start(); virtual void stop(); @@ -83,17 +76,13 @@ protected: }; #ifdef SDR_RX_SAMPLE_24BIT - typedef bool (IntHalfbandFilterDB::*WorkFunction)(Sample* s); - IntHalfbandFilterDB* m_filter; + typedef bool (IntHalfbandFilterEO::*WorkFunction)(Sample* s); + IntHalfbandFilterEO* m_filter; #else -#ifdef USE_SSE4_1 - typedef bool (IntHalfbandFilterEO1::*WorkFunction)(Sample* s); - IntHalfbandFilterEO1* m_filter; -#else - typedef bool (IntHalfbandFilterDB::*WorkFunction)(Sample* s); - IntHalfbandFilterDB* m_filter; -#endif + typedef bool (IntHalfbandFilterEO::*WorkFunction)(Sample* s); + IntHalfbandFilterEO* m_filter; #endif + WorkFunction m_workFunction; Mode m_mode; bool m_sse; diff --git a/sdrbase/dsp/dspcommands.cpp b/sdrbase/dsp/dspcommands.cpp index 1c15142d1..71ab79ca2 100644 --- a/sdrbase/dsp/dspcommands.cpp +++ b/sdrbase/dsp/dspcommands.cpp @@ -46,3 +46,4 @@ MESSAGE_CLASS_DEFINITION(DSPEngineReport, Message) MESSAGE_CLASS_DEFINITION(DSPConfigureScopeVis, Message) MESSAGE_CLASS_DEFINITION(DSPSignalNotification, Message) MESSAGE_CLASS_DEFINITION(DSPConfigureChannelizer, Message) +MESSAGE_CLASS_DEFINITION(DSPConfigureAudio, Message) diff --git a/sdrbase/dsp/dspcommands.h b/sdrbase/dsp/dspcommands.h index 7ba24a242..ca1a5343b 100644 --- a/sdrbase/dsp/dspcommands.h +++ b/sdrbase/dsp/dspcommands.h @@ -21,7 +21,7 @@ #include #include "util/message.h" #include "fftwindow.h" -#include "util/export.h" +#include "export.h" class DeviceSampleSource; class BasebandSampleSink; @@ -31,31 +31,31 @@ class BasebandSampleSource; class ThreadedBasebandSampleSource; class AudioFifo; -class SDRANGEL_API DSPAcquisitionInit : public Message { +class SDRBASE_API DSPAcquisitionInit : public Message { MESSAGE_CLASS_DECLARATION }; -class SDRANGEL_API DSPAcquisitionStart : public Message { +class SDRBASE_API DSPAcquisitionStart : public Message { MESSAGE_CLASS_DECLARATION }; -class SDRANGEL_API DSPAcquisitionStop : public Message { +class SDRBASE_API DSPAcquisitionStop : public Message { MESSAGE_CLASS_DECLARATION }; -class SDRANGEL_API DSPGenerationInit : public Message { +class SDRBASE_API DSPGenerationInit : public Message { MESSAGE_CLASS_DECLARATION }; -class SDRANGEL_API DSPGenerationStart : public Message { +class SDRBASE_API DSPGenerationStart : public Message { MESSAGE_CLASS_DECLARATION }; -class SDRANGEL_API DSPGenerationStop : public Message { +class SDRBASE_API DSPGenerationStop : public Message { MESSAGE_CLASS_DECLARATION }; -class SDRANGEL_API DSPGetSourceDeviceDescription : public Message { +class SDRBASE_API DSPGetSourceDeviceDescription : public Message { MESSAGE_CLASS_DECLARATION public: @@ -66,7 +66,7 @@ private: QString m_deviceDescription; }; -class SDRANGEL_API DSPGetSinkDeviceDescription : public Message { +class SDRBASE_API DSPGetSinkDeviceDescription : public Message { MESSAGE_CLASS_DECLARATION public: @@ -77,7 +77,7 @@ private: QString m_deviceDescription; }; -class SDRANGEL_API DSPGetErrorMessage : public Message { +class SDRBASE_API DSPGetErrorMessage : public Message { MESSAGE_CLASS_DECLARATION public: @@ -88,7 +88,7 @@ private: QString m_errorMessage; }; -class SDRANGEL_API DSPSetSource : public Message { +class SDRBASE_API DSPSetSource : public Message { MESSAGE_CLASS_DECLARATION public: @@ -100,7 +100,7 @@ private: DeviceSampleSource* m_sampleSource; }; -class SDRANGEL_API DSPSetSink : public Message { +class SDRBASE_API DSPSetSink : public Message { MESSAGE_CLASS_DECLARATION public: @@ -112,7 +112,7 @@ private: DeviceSampleSink* m_sampleSink; }; -class SDRANGEL_API DSPAddBasebandSampleSink : public Message { +class SDRBASE_API DSPAddBasebandSampleSink : public Message { MESSAGE_CLASS_DECLARATION public: @@ -124,7 +124,7 @@ private: BasebandSampleSink* m_sampleSink; }; -class SDRANGEL_API DSPAddSpectrumSink : public Message { +class SDRBASE_API DSPAddSpectrumSink : public Message { MESSAGE_CLASS_DECLARATION public: @@ -136,7 +136,7 @@ private: BasebandSampleSink* m_sampleSink; }; -class SDRANGEL_API DSPAddBasebandSampleSource : public Message { +class SDRBASE_API DSPAddBasebandSampleSource : public Message { MESSAGE_CLASS_DECLARATION public: @@ -148,7 +148,7 @@ private: BasebandSampleSource* m_sampleSource; }; -class SDRANGEL_API DSPRemoveBasebandSampleSink : public Message { +class SDRBASE_API DSPRemoveBasebandSampleSink : public Message { MESSAGE_CLASS_DECLARATION public: @@ -160,7 +160,7 @@ private: BasebandSampleSink* m_sampleSink; }; -class SDRANGEL_API DSPRemoveSpectrumSink : public Message { +class SDRBASE_API DSPRemoveSpectrumSink : public Message { MESSAGE_CLASS_DECLARATION public: @@ -172,7 +172,7 @@ private: BasebandSampleSink* m_sampleSink; }; -class SDRANGEL_API DSPRemoveBasebandSampleSource : public Message { +class SDRBASE_API DSPRemoveBasebandSampleSource : public Message { MESSAGE_CLASS_DECLARATION public: @@ -184,7 +184,7 @@ private: BasebandSampleSource* m_sampleSource; }; -class SDRANGEL_API DSPAddThreadedBasebandSampleSink : public Message { +class SDRBASE_API DSPAddThreadedBasebandSampleSink : public Message { MESSAGE_CLASS_DECLARATION public: @@ -196,7 +196,7 @@ private: ThreadedBasebandSampleSink* m_threadedSampleSink; }; -class SDRANGEL_API DSPAddThreadedBasebandSampleSource : public Message { +class SDRBASE_API DSPAddThreadedBasebandSampleSource : public Message { MESSAGE_CLASS_DECLARATION public: @@ -208,7 +208,7 @@ private: ThreadedBasebandSampleSource* m_threadedSampleSource; }; -class SDRANGEL_API DSPRemoveThreadedBasebandSampleSink : public Message { +class SDRBASE_API DSPRemoveThreadedBasebandSampleSink : public Message { MESSAGE_CLASS_DECLARATION public: @@ -220,7 +220,7 @@ private: ThreadedBasebandSampleSink* m_threadedSampleSink; }; -class SDRANGEL_API DSPRemoveThreadedBasebandSampleSource : public Message { +class SDRBASE_API DSPRemoveThreadedBasebandSampleSource : public Message { MESSAGE_CLASS_DECLARATION public: @@ -232,7 +232,7 @@ private: ThreadedBasebandSampleSource* m_threadedSampleSource; }; -class SDRANGEL_API DSPAddAudioSink : public Message { +class SDRBASE_API DSPAddAudioSink : public Message { MESSAGE_CLASS_DECLARATION public: @@ -244,7 +244,7 @@ private: AudioFifo* m_audioFifo; }; -class SDRANGEL_API DSPRemoveAudioSink : public Message { +class SDRBASE_API DSPRemoveAudioSink : public Message { MESSAGE_CLASS_DECLARATION public: @@ -256,7 +256,7 @@ private: AudioFifo* m_audioFifo; }; -class SDRANGEL_API DSPConfigureSpectrumVis : public Message { +class SDRBASE_API DSPConfigureSpectrumVis : public Message { MESSAGE_CLASS_DECLARATION public: @@ -277,7 +277,7 @@ private: FFTWindow::Function m_window; }; -class SDRANGEL_API DSPConfigureCorrection : public Message { +class SDRBASE_API DSPConfigureCorrection : public Message { MESSAGE_CLASS_DECLARATION public: @@ -296,7 +296,7 @@ private: }; -class SDRANGEL_API DSPEngineReport : public Message { +class SDRBASE_API DSPEngineReport : public Message { MESSAGE_CLASS_DECLARATION public: @@ -314,7 +314,7 @@ private: quint64 m_centerFrequency; }; -class SDRANGEL_API DSPConfigureScopeVis : public Message { +class SDRBASE_API DSPConfigureScopeVis : public Message { MESSAGE_CLASS_DECLARATION public: @@ -335,7 +335,7 @@ private: Real m_triggerLevelLow; }; -class SDRANGEL_API DSPSignalNotification : public Message { +class SDRBASE_API DSPSignalNotification : public Message { MESSAGE_CLASS_DECLARATION public: @@ -353,7 +353,7 @@ private: qint64 m_centerFrequency; }; -class SDRANGEL_API DSPConfigureChannelizer : public Message { +class SDRBASE_API DSPConfigureChannelizer : public Message { MESSAGE_CLASS_DECLARATION public: @@ -371,4 +371,17 @@ private: int m_centerFrequency; }; +class SDRBASE_API DSPConfigureAudio : public Message { + MESSAGE_CLASS_DECLARATION + +public: + DSPConfigureAudio(int sampleRate) : m_sampleRate(sampleRate) + { } + + int getSampleRate() const { return m_sampleRate; } + +private: + int m_sampleRate; +}; + #endif // INCLUDE_DSPCOMMANDS_H diff --git a/sdrbase/dsp/dspdevicesinkengine.cpp b/sdrbase/dsp/dspdevicesinkengine.cpp index 1098770c5..aa6b4246f 100644 --- a/sdrbase/dsp/dspdevicesinkengine.cpp +++ b/sdrbase/dsp/dspdevicesinkengine.cpp @@ -362,7 +362,7 @@ DSPDeviceSinkEngine::State DSPDeviceSinkEngine::gotoRunning() if(!m_deviceSampleSink->start()) { - return gotoError("DSPDeviceSinkEngine::gotoRunning: Could not start sample source"); + return gotoError("DSPDeviceSinkEngine::gotoRunning: Could not start sample sink"); } for(BasebandSampleSources::const_iterator it = m_basebandSampleSources.begin(); it != m_basebandSampleSources.end(); it++) @@ -592,7 +592,7 @@ void DSPDeviceSinkEngine::checkNumberOfBasebandSources() if (m_threadedBasebandSampleSources.size() == 1) { m_threadedBasebandSampleSources.back()->setDeviceSampleSourceFifo(sampleFifo); } else if (m_basebandSampleSources.size() == 1) { - m_threadedBasebandSampleSources.back()->setDeviceSampleSourceFifo(sampleFifo); + m_basebandSampleSources.back()->setDeviceSampleSourceFifo(sampleFifo); } m_multipleSourcesDivisionFactor = 1; // for consistency but it is not used in this case diff --git a/sdrbase/dsp/dspdevicesinkengine.h b/sdrbase/dsp/dspdevicesinkengine.h index db4553dda..f97560c2d 100644 --- a/sdrbase/dsp/dspdevicesinkengine.h +++ b/sdrbase/dsp/dspdevicesinkengine.h @@ -29,14 +29,14 @@ #include "dsp/fftwindow.h" #include "util/messagequeue.h" #include "util/syncmessenger.h" -#include "util/export.h" +#include "export.h" class DeviceSampleSink; class BasebandSampleSource; class ThreadedBasebandSampleSource; class BasebandSampleSink; -class SDRANGEL_API DSPDeviceSinkEngine : public QThread { +class SDRBASE_API DSPDeviceSinkEngine : public QThread { Q_OBJECT public: diff --git a/sdrbase/dsp/dspdevicesourceengine.h b/sdrbase/dsp/dspdevicesourceengine.h index 923c0715e..053930fd0 100644 --- a/sdrbase/dsp/dspdevicesourceengine.h +++ b/sdrbase/dsp/dspdevicesourceengine.h @@ -26,14 +26,14 @@ #include "dsp/fftwindow.h" #include "util/messagequeue.h" #include "util/syncmessenger.h" -#include "util/export.h" +#include "export.h" #include "util/movingaverage.h" class DeviceSampleSource; class BasebandSampleSink; class ThreadedBasebandSampleSink; -class SDRANGEL_API DSPDeviceSourceEngine : public QThread { +class SDRBASE_API DSPDeviceSourceEngine : public QThread { Q_OBJECT public: diff --git a/sdrbase/dsp/dspengine.cpp b/sdrbase/dsp/dspengine.cpp index 524b57c4e..afa9c4453 100644 --- a/sdrbase/dsp/dspengine.cpp +++ b/sdrbase/dsp/dspengine.cpp @@ -26,8 +26,6 @@ DSPEngine::DSPEngine() : m_deviceSourceEnginesUIDSequence(0), m_deviceSinkEnginesUIDSequence(0), - m_audioOutputSampleRate(48000), // Use default output device at 48 kHz - m_audioInputSampleRate(48000), // Use default input device at 48 kHz m_audioInputDeviceIndex(-1), // default device m_audioOutputDeviceIndex(-1) // default device { @@ -37,9 +35,6 @@ DSPEngine::DSPEngine() : DSPEngine::~DSPEngine() { - m_audioOutput.setOnExit(true); - m_audioInput.setOnExit(true); - std::vector::iterator it = m_deviceSourceEngines.begin(); while (it != m_deviceSourceEngines.end()) @@ -91,74 +86,6 @@ void DSPEngine::removeLastDeviceSinkEngine() } } -void DSPEngine::startAudioOutput() -{ - m_audioOutput.start(m_audioOutputDeviceIndex, m_audioOutputSampleRate); - m_audioOutputSampleRate = m_audioOutput.getRate(); // update with actual rate -} - -void DSPEngine::stopAudioOutput() -{ - m_audioOutput.stop(); -} - -void DSPEngine::startAudioOutputImmediate() -{ - m_audioOutput.startImmediate(m_audioOutputDeviceIndex, m_audioOutputSampleRate); - m_audioOutputSampleRate = m_audioOutput.getRate(); // update with actual rate -} - -void DSPEngine::stopAudioOutputImmediate() -{ - m_audioOutput.stopImmediate(); -} - -void DSPEngine::startAudioInput() -{ - m_audioInput.start(m_audioInputDeviceIndex, m_audioInputSampleRate); - m_audioInputSampleRate = m_audioInput.getRate(); // update with actual rate -} - -void DSPEngine::stopAudioInput() -{ - m_audioInput.stop(); -} - -void DSPEngine::startAudioInputImmediate() -{ - m_audioInput.startImmediate(m_audioInputDeviceIndex, m_audioInputSampleRate); - m_audioInputSampleRate = m_audioInput.getRate(); // update with actual rate -} - -void DSPEngine::stopAudioInputImmediate() -{ - m_audioInput.stopImmediate(); -} - -void DSPEngine::addAudioSink(AudioFifo* audioFifo) -{ - qDebug("DSPEngine::addAudioSink"); - m_audioOutput.addFifo(audioFifo); -} - -void DSPEngine::removeAudioSink(AudioFifo* audioFifo) -{ - qDebug("DSPEngine::removeAudioSink"); - m_audioOutput.removeFifo(audioFifo); -} - -void DSPEngine::addAudioSource(AudioFifo* audioFifo) -{ - qDebug("DSPEngine::addAudioSource"); - m_audioInput.addFifo(audioFifo); -} - -void DSPEngine::removeAudioSource(AudioFifo* audioFifo) -{ - qDebug("DSPEngine::removeAudioSource"); - m_audioInput.removeFifo(audioFifo); -} - DSPDeviceSourceEngine *DSPEngine::getDeviceSourceEngineByUID(uint uid) { std::vector::iterator it = m_deviceSourceEngines.begin(); @@ -237,9 +164,10 @@ void DSPEngine::pushMbeFrame( int mbeVolumeIndex, unsigned char channels, bool useHP, + int upsampling, AudioFifo *audioFifo) { - m_dvSerialEngine.pushMbeFrame(mbeFrame, mbeRateIndex, mbeVolumeIndex, channels, useHP, audioFifo); + m_dvSerialEngine.pushMbeFrame(mbeFrame, mbeRateIndex, mbeVolumeIndex, channels, useHP, upsampling, audioFifo); } #else void DSPEngine::pushMbeFrame( @@ -248,6 +176,7 @@ void DSPEngine::pushMbeFrame( int mbeVolumeIndex __attribute((unused)), unsigned char channels __attribute((unused)), bool useHP __attribute((unused)), + int upsampling __attribute((unused)), AudioFifo *audioFifo __attribute((unused))) {} #endif diff --git a/sdrbase/dsp/dspengine.h b/sdrbase/dsp/dspengine.h index 7f8bac5b1..f7438cf28 100644 --- a/sdrbase/dsp/dspengine.h +++ b/sdrbase/dsp/dspengine.h @@ -20,11 +20,12 @@ #include #include - #include + +#include "audio/audiodevicemanager.h" #include "audio/audiooutput.h" #include "audio/audioinput.h" -#include "util/export.h" +#include "export.h" #ifdef DSD_USE_SERIALDV #include "dsp/dvserialengine.h" #endif @@ -32,7 +33,7 @@ class DSPDeviceSourceEngine; class DSPDeviceSinkEngine; -class SDRANGEL_API DSPEngine : public QObject { +class SDRBASE_API DSPEngine : public QObject { Q_OBJECT public: DSPEngine(); @@ -40,7 +41,7 @@ public: static DSPEngine *instance(); - uint getAudioSampleRate() const { return m_audioOutputSampleRate; } + unsigned int getDefaultAudioSampleRate() const { return AudioDeviceManager::m_defaultAudioSampleRate; } DSPDeviceSourceEngine *addDeviceSourceEngine(); void removeLastDeviceSourceEngine(); @@ -48,18 +49,7 @@ public: DSPDeviceSinkEngine *addDeviceSinkEngine(); void removeLastDeviceSinkEngine(); - void startAudioOutput(); - void stopAudioOutput(); - void startAudioOutputImmediate(); - void stopAudioOutputImmediate(); - void setAudioOutputDeviceIndex(int index) { m_audioOutputDeviceIndex = index; } - - void startAudioInput(); - void stopAudioInput(); - void startAudioInputImmediate(); - void stopAudioInputImmediate(); - void setAudioInputVolume(float volume) { m_audioInput.setVolume(volume); } - void setAudioInputDeviceIndex(int index) { m_audioInputDeviceIndex = index; } + AudioDeviceManager *getAudioDeviceManager() { return &m_audioDeviceManager; } DSPDeviceSourceEngine *getDeviceSourceEngineByIndex(uint deviceIndex) { return m_deviceSourceEngines[deviceIndex]; } DSPDeviceSourceEngine *getDeviceSourceEngineByUID(uint uid); @@ -67,12 +57,6 @@ public: DSPDeviceSinkEngine *getDeviceSinkEngineByIndex(uint deviceIndex) { return m_deviceSinkEngines[deviceIndex]; } DSPDeviceSinkEngine *getDeviceSinkEngineByUID(uint uid); - void addAudioSink(AudioFifo* audioFifo); //!< Add the audio sink - void removeAudioSink(AudioFifo* audioFifo); //!< Remove the audio sink - - void addAudioSource(AudioFifo* audioFifo); //!< Add an audio source - void removeAudioSource(AudioFifo* audioFifo); //!< Remove an audio source - // Serial DV methods: bool hasDVSerialSupport(); @@ -84,6 +68,7 @@ public: int mbeVolumeIndex, unsigned char channels, bool useHP, + int upsampling, AudioFifo *audioFifo); const QTimer& getMasterTimer() const { return m_masterTimer; } @@ -93,10 +78,7 @@ private: uint m_deviceSourceEnginesUIDSequence; std::vector m_deviceSinkEngines; uint m_deviceSinkEnginesUIDSequence; - AudioOutput m_audioOutput; - AudioInput m_audioInput; - uint m_audioOutputSampleRate; - uint m_audioInputSampleRate; + AudioDeviceManager m_audioDeviceManager; int m_audioInputDeviceIndex; int m_audioOutputDeviceIndex; QTimer m_masterTimer; diff --git a/sdrbase/dsp/dsptypes.h b/sdrbase/dsp/dsptypes.h index e3913a785..f39f9deb7 100644 --- a/sdrbase/dsp/dsptypes.h +++ b/sdrbase/dsp/dsptypes.h @@ -64,6 +64,28 @@ struct Sample FixReal m_imag; }; +struct FSample +{ + FSample() : m_real(0), m_imag(0) {} + FSample(Real real) : m_real(real), m_imag(0) {} + FSample(Real real, Real imag) : m_real(real), m_imag(imag) {} + FSample(const FSample& other) : m_real(other.m_real), m_imag(other.m_imag) {} + inline FSample& operator=(const FSample& other) { m_real = other.m_real; m_imag = other.m_imag; return *this; } + + inline FSample& operator+=(const FSample& other) { m_real += other.m_real; m_imag += other.m_imag; return *this; } + inline FSample& operator-=(const FSample& other) { m_real -= other.m_real; m_imag -= other.m_imag; return *this; } + inline FSample& operator/=(const Real& divisor) { m_real /= divisor; m_imag /= divisor; return *this; } + + inline void setReal(Real v) { m_real = v; } + inline void setImag(Real v) { m_imag = v; } + + inline Real real() const { return m_real; } + inline Real imag() const { return m_imag; } + + Real m_real; + Real m_imag; +}; + struct AudioSample { qint16 l; qint16 r; @@ -71,6 +93,7 @@ struct AudioSample { #pragma pack(pop) typedef std::vector SampleVector; +typedef std::vector FSampleVector; typedef std::vector AudioVector; #endif // INCLUDE_DSPTYPES_H diff --git a/sdrbase/dsp/dvserialengine.cpp b/sdrbase/dsp/dvserialengine.cpp index d042a2019..ddb4370b9 100644 --- a/sdrbase/dsp/dvserialengine.cpp +++ b/sdrbase/dsp/dvserialengine.cpp @@ -150,7 +150,7 @@ void DVSerialEngine::getComList() const char* sysdir = "/sys/class/tty/"; // Scan through /sys/class/tty - it contains all tty-devices in the system - n = scandir(sysdir, &namelist, NULL, NULL); + n = scandir(sysdir, &namelist, NULL, alphasort); if (n < 0) perror("scandir"); else @@ -253,6 +253,7 @@ void DVSerialEngine::pushMbeFrame( int mbeVolumeIndex, unsigned char channels, bool useLP, + int upsampling, AudioFifo *audioFifo) { std::vector::iterator it = m_controllers.begin(); @@ -264,7 +265,7 @@ void DVSerialEngine::pushMbeFrame( { if (it->worker->hasFifo(audioFifo)) { - it->worker->pushMbeFrame(mbeFrame, mbeRateIndex, mbeVolumeIndex, channels, useLP, audioFifo); + it->worker->pushMbeFrame(mbeFrame, mbeRateIndex, mbeVolumeIndex, channels, useLP, upsampling, audioFifo); done = true; } else if (it->worker->isAvailable()) @@ -282,7 +283,7 @@ void DVSerialEngine::pushMbeFrame( int wNum = itAvail - m_controllers.begin(); qDebug("DVSerialEngine::pushMbeFrame: push %p on empty queue %d", audioFifo, wNum); - itAvail->worker->pushMbeFrame(mbeFrame, mbeRateIndex, mbeVolumeIndex, channels, useLP, audioFifo); + itAvail->worker->pushMbeFrame(mbeFrame, mbeRateIndex, mbeVolumeIndex, channels, useLP, upsampling, audioFifo); } else { diff --git a/sdrbase/dsp/dvserialengine.h b/sdrbase/dsp/dvserialengine.h index f2248f90e..dfc3d1520 100644 --- a/sdrbase/dsp/dvserialengine.h +++ b/sdrbase/dsp/dvserialengine.h @@ -24,11 +24,13 @@ #include #include +#include "export.h" + class QThread; class DVSerialWorker; class AudioFifo; -class DVSerialEngine : public QObject +class SDRBASE_API DVSerialEngine : public QObject { Q_OBJECT public: @@ -47,6 +49,7 @@ public: int mbeVolumeIndex, unsigned char channels, bool useHP, + int upsampling, AudioFifo *audioFifo); private: diff --git a/sdrbase/dsp/dvserialworker.cpp b/sdrbase/dsp/dvserialworker.cpp index 8db6935a5..e72f0f5db 100644 --- a/sdrbase/dsp/dvserialworker.cpp +++ b/sdrbase/dsp/dvserialworker.cpp @@ -27,12 +27,16 @@ DVSerialWorker::DVSerialWorker() : m_running(false), m_currentGainIn(0), m_currentGainOut(0), - m_upsamplerLastValue(0), - m_phase(0) + m_upsamplerLastValue(0.0f), + m_phase(0), + m_upsampling(1), + m_volume(1.0f) { m_audioBuffer.resize(48000); m_audioBufferFill = 0; m_audioFifo = 0; + memset(m_dvAudioSamples, 0, SerialDV::MBE_AUDIO_BLOCK_SIZE*sizeof(short)); + setVolumeFactors(); } DVSerialWorker::~DVSerialWorker() @@ -79,13 +83,28 @@ void DVSerialWorker::handleInputMessages() if (MsgMbeDecode::match(*message)) { MsgMbeDecode *decodeMsg = (MsgMbeDecode *) message; - int dBVolume = (decodeMsg->getVolumeIndex() - 30) / 2; + int dBVolume = (decodeMsg->getVolumeIndex() - 30) / 4; + float volume = pow(10.0, dBVolume / 10.0f); + int upsampling = decodeMsg->getUpsampling(); + upsampling = upsampling > 6 ? 6 : upsampling < 1 ? 1 : upsampling; + + if ((volume != m_volume) || (upsampling != m_upsampling)) + { + m_volume = volume; + m_upsampling = upsampling; + setVolumeFactors(); + } m_upsampleFilter.useHP(decodeMsg->getUseHP()); - if (m_dvController.decode(m_dvAudioSamples, decodeMsg->getMbeFrame(), decodeMsg->getMbeRate(), dBVolume)) + if (m_dvController.decode(m_dvAudioSamples, decodeMsg->getMbeFrame(), decodeMsg->getMbeRate())) { - upsample6(m_dvAudioSamples, SerialDV::MBE_AUDIO_BLOCK_SIZE, decodeMsg->getChannels()); + if (upsampling > 1) { + upsample(upsampling, m_dvAudioSamples, SerialDV::MBE_AUDIO_BLOCK_SIZE, decodeMsg->getChannels()); + } else { + noUpsample(m_dvAudioSamples, SerialDV::MBE_AUDIO_BLOCK_SIZE, decodeMsg->getChannels()); + } + audioFifo = decodeMsg->getAudioFifo(); } else @@ -99,7 +118,7 @@ void DVSerialWorker::handleInputMessages() if (audioFifo) { - uint res = audioFifo->write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 10); + uint res = audioFifo->write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); if (res != m_audioBufferFill) { @@ -115,10 +134,11 @@ void DVSerialWorker::pushMbeFrame(const unsigned char *mbeFrame, int mbeVolumeIndex, unsigned char channels, bool useHP, + int upsampling, AudioFifo *audioFifo) { m_audioFifo = audioFifo; - m_inputMessageQueue.push(MsgMbeDecode::create(mbeFrame, mbeRateIndex, mbeVolumeIndex, channels, useHP, audioFifo)); + m_inputMessageQueue.push(MsgMbeDecode::create(mbeFrame, mbeRateIndex, mbeVolumeIndex, channels, useHP, upsampling, audioFifo)); } bool DVSerialWorker::isAvailable() @@ -135,19 +155,20 @@ bool DVSerialWorker::hasFifo(AudioFifo *audioFifo) return m_audioFifo == audioFifo; } -void DVSerialWorker::upsample6(short *in, int nbSamplesIn, unsigned char channels) +void DVSerialWorker::upsample(int upsampling, short *in, int nbSamplesIn, unsigned char channels) { for (int i = 0; i < nbSamplesIn; i++) { - int cur = (int) in[i]; - int prev = (int) m_upsamplerLastValue; + //float cur = m_upsampleFilter.usesHP() ? m_upsampleFilter.runHP((float) m_compressor.compress(in[i])) : (float) m_compressor.compress(in[i]); + float cur = m_upsampleFilter.usesHP() ? m_upsampleFilter.runHP((float) in[i]) : (float) in[i]; + float prev = m_upsamplerLastValue; qint16 upsample; - for (int j = 1; j < 7; j++) + for (int j = 1; j <= upsampling; j++) { - upsample = m_upsampleFilter.run((qint16) ((cur*j + prev*(6-j)) / 6)); - m_audioBuffer[m_audioBufferFill].l = channels & 1 ? upsample : 0; - m_audioBuffer[m_audioBufferFill].r = (channels>>1) & 1 ? upsample : 0; + upsample = (qint16) m_upsampleFilter.runLP(cur*m_upsamplingFactors[j] + prev*m_upsamplingFactors[upsampling-j]); + m_audioBuffer[m_audioBufferFill].l = channels & 1 ? m_compressor.compress(upsample) : 0; + m_audioBuffer[m_audioBufferFill].r = (channels>>1) & 1 ? m_compressor.compress(upsample) : 0; if (m_audioBufferFill < m_audioBuffer.size() - 1) { @@ -159,70 +180,98 @@ void DVSerialWorker::upsample6(short *in, int nbSamplesIn, unsigned char channel } } - m_upsamplerLastValue = in[i]; + m_upsamplerLastValue = cur; } } -void DVSerialWorker::upsample6(short *in, short *out, int nbSamplesIn) +void DVSerialWorker::noUpsample(short *in, int nbSamplesIn, unsigned char channels) { for (int i = 0; i < nbSamplesIn; i++) { - int cur = (int) in[i]; - int prev = (int) m_upsamplerLastValue; - short up; + float cur = m_upsampleFilter.usesHP() ? m_upsampleFilter.runHP((float) in[i]) : (float) in[i]; + m_audioBuffer[m_audioBufferFill].l = channels & 1 ? cur*m_upsamplingFactors[0] : 0; + m_audioBuffer[m_audioBufferFill].r = (channels>>1) & 1 ? cur*m_upsamplingFactors[0] : 0; -// DEBUG: -// for (int j = 0; j < 6; j++) -// { -// up = 32768.0f * cos(m_phase); -// *out = up; -// out ++; -// *out = up; -// out ++; -// m_phase += M_PI / 6.0; -// } -// -// if ((i % 2) == 1) -// { -// m_phase = 0.0f; -// } - - up = m_upsampleFilter.run((cur*1 + prev*5) / 6); - *out = up; - out++; - *out = up; - out++; - - up = m_upsampleFilter.run((cur*2 + prev*4) / 6); - *out = up; - out++; - *out = up; - out++; - - up = m_upsampleFilter.run((cur*3 + prev*3) / 6); - *out = up; - out++; - *out = up; - out++; - - up = m_upsampleFilter.run((cur*4 + prev*2) / 6); - *out = up; - out++; - *out = up; - out++; - - up = m_upsampleFilter.run((cur*5 + prev*1) / 6); - *out = up; - out++; - *out = up; - out++; - - up = m_upsampleFilter.run(in[i]); - *out = up; - out++; - *out = up; - out++; - - m_upsamplerLastValue = in[i]; + if (m_audioBufferFill < m_audioBuffer.size() - 1) + { + ++m_audioBufferFill; + } + else + { + qDebug("DVSerialWorker::noUpsample: audio buffer is full check its size"); + } } } + +void DVSerialWorker::setVolumeFactors() +{ + m_upsamplingFactors[0] = m_volume; + + for (int i = 1; i <= m_upsampling; i++) { + m_upsamplingFactors[i] = (i*m_volume) / (float) m_upsampling; + } +} + +//void DVSerialWorker::upsample6(short *in, short *out, int nbSamplesIn) +//{ +// for (int i = 0; i < nbSamplesIn; i++) +// { +// int cur = (int) in[i]; +// int prev = (int) m_upsamplerLastValue; +// short up; +// +//// DEBUG: +//// for (int j = 0; j < 6; j++) +//// { +//// up = 32768.0f * cos(m_phase); +//// *out = up; +//// out ++; +//// *out = up; +//// out ++; +//// m_phase += M_PI / 6.0; +//// } +//// +//// if ((i % 2) == 1) +//// { +//// m_phase = 0.0f; +//// } +// +// up = m_upsampleFilter.run((cur*1 + prev*5) / 6); +// *out = up; +// out++; +// *out = up; +// out++; +// +// up = m_upsampleFilter.run((cur*2 + prev*4) / 6); +// *out = up; +// out++; +// *out = up; +// out++; +// +// up = m_upsampleFilter.run((cur*3 + prev*3) / 6); +// *out = up; +// out++; +// *out = up; +// out++; +// +// up = m_upsampleFilter.run((cur*4 + prev*2) / 6); +// *out = up; +// out++; +// *out = up; +// out++; +// +// up = m_upsampleFilter.run((cur*5 + prev*1) / 6); +// *out = up; +// out++; +// *out = up; +// out++; +// +// up = m_upsampleFilter.run(in[i]); +// *out = up; +// out++; +// *out = up; +// out++; +// +// m_upsamplerLastValue = in[i]; +// } +//} diff --git a/sdrbase/dsp/dvserialworker.h b/sdrbase/dsp/dvserialworker.h index da7347037..e395a5850 100644 --- a/sdrbase/dsp/dvserialworker.h +++ b/sdrbase/dsp/dvserialworker.h @@ -29,12 +29,14 @@ #include "util/message.h" #include "util/syncmessenger.h" #include "util/messagequeue.h" +#include "export.h" #include "dsp/filtermbe.h" #include "dsp/dsptypes.h" +#include "audio/audiocompressor.h" class AudioFifo; -class DVSerialWorker : public QObject { +class SDRBASE_API DVSerialWorker : public QObject { Q_OBJECT public: class MsgTest : public Message @@ -55,6 +57,7 @@ public: int getVolumeIndex() const { return m_volumeIndex; } unsigned char getChannels() const { return m_channels % 4; } bool getUseHP() const { return m_useHP; } + int getUpsampling() const { return m_upsampling; } AudioFifo *getAudioFifo() { return m_audioFifo; } static MsgMbeDecode* create( @@ -63,9 +66,10 @@ public: int volumeIndex, unsigned char channels, bool useHP, + int upsampling, AudioFifo *audioFifo) { - return new MsgMbeDecode(mbeFrame, (SerialDV::DVRate) mbeRateIndex, volumeIndex, channels, useHP, audioFifo); + return new MsgMbeDecode(mbeFrame, (SerialDV::DVRate) mbeRateIndex, volumeIndex, channels, useHP, upsampling, audioFifo); } private: @@ -74,6 +78,7 @@ public: int m_volumeIndex; unsigned char m_channels; bool m_useHP; + int m_upsampling; AudioFifo *m_audioFifo; MsgMbeDecode(const unsigned char *mbeFrame, @@ -81,12 +86,14 @@ public: int volumeIndex, unsigned char channels, bool useHP, + int upsampling, AudioFifo *audioFifo) : Message(), m_mbeRate(mbeRate), m_volumeIndex(volumeIndex), m_channels(channels), m_useHP(useHP), + m_upsampling(upsampling), m_audioFifo(audioFifo) { memcpy((void *) m_mbeFrame, (const void *) mbeFrame, SerialDV::DVController::getNbMbeBytes(m_mbeRate)); @@ -101,6 +108,7 @@ public: int mbeVolumeIndex, unsigned char channels, bool useHP, + int upsampling, AudioFifo *audioFifo); bool open(const std::string& serialDevice); @@ -127,20 +135,26 @@ public slots: void handleInputMessages(); private: - void upsample6(short *in, short *out, int nbSamplesIn); - void upsample6(short *in, int nbSamplesIn, unsigned char channels); + //void upsample6(short *in, short *out, int nbSamplesIn); + void upsample(int upsampling, short *in, int nbSamplesIn, unsigned char channels); + void noUpsample(short *in, int nbSamplesIn, unsigned char channels); + void setVolumeFactors(); SerialDV::DVController m_dvController; - bool m_running; + volatile bool m_running; int m_currentGainIn; int m_currentGainOut; short m_dvAudioSamples[SerialDV::MBE_AUDIO_BLOCK_SIZE]; //short m_audioSamples[SerialDV::MBE_AUDIO_BLOCK_SIZE * 6 * 2]; // upsample to 48k and duplicate channel AudioVector m_audioBuffer; uint m_audioBufferFill; - short m_upsamplerLastValue; + float m_upsamplerLastValue; float m_phase; MBEAudioInterpolatorFilter m_upsampleFilter; + int m_upsampling; + float m_volume; + float m_upsamplingFactors[7]; + AudioCompressor m_compressor; }; #endif /* SDRBASE_DSP_DVSERIALWORKER_H_ */ diff --git a/sdrbase/dsp/fftcorr.cpp b/sdrbase/dsp/fftcorr.cpp new file mode 100644 index 000000000..2e4fbd242 --- /dev/null +++ b/sdrbase/dsp/fftcorr.cpp @@ -0,0 +1,111 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// FFT based cross correlation // +// // +// See: http://liquidsdr.org/blog/pll-howto/ // +// Fixed filter registers saturation // +// Added order for PSK locking. This brilliant idea actually comes from this // +// post: https://www.dsprelated.com/showthread/comp.dsp/36356-1.php // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include "fftcorr.h" + +void fftcorr::init_fft() +{ + fftA = new g_fft(flen); + fftB = new g_fft(flen); + + dataA = new cmplx[flen]; + dataB = new cmplx[flen]; + dataBj = new cmplx[flen]; + dataP = new cmplx[flen]; + + std::fill(dataA, dataA+flen, 0); + std::fill(dataB, dataB+flen, 0); + + inptrA = 0; + inptrB = 0; + outptr = 0; +} + +fftcorr::fftcorr(int len) : flen(len), flen2(len>>1) +{ + init_fft(); +} + +fftcorr::~fftcorr() +{ + delete fftA; + delete fftB; + delete[] dataA; + delete[] dataB; + delete[] dataBj; + delete[] dataP; +} + +int fftcorr::run(const cmplx& inA, const cmplx* inB, cmplx **out) +{ + dataA[inptrA++] = inA; + + if (inB) { + dataB[inptrB++] = *inB; + } + + if (inptrA < flen2) { + return 0; + } + + fftA->ComplexFFT(dataA); + + if (inB) { + fftB->ComplexFFT(dataB); + } + + if (inB) { + std::transform(dataB, dataB+flen, dataBj, [](const cmplx& c) -> cmplx { return std::conj(c); }); + } else { + std::transform(dataA, dataA+flen, dataBj, [](const cmplx& c) -> cmplx { return std::conj(c); }); + } + + std::transform(dataA, dataA+flen, dataBj, dataP, [](const cmplx& a, const cmplx& b) -> cmplx { return a*b; }); + + fftA->InverseComplexFFT(dataP); + + std::fill(dataA, dataA+flen, 0); + inptrA = 0; + + if (inB) + { + std::fill(dataB, dataB+flen, 0); + inptrB = 0; + } + + *out = dataP; + return flen2; +} + +const fftcorr::cmplx& fftcorr::run(const cmplx& inA, const cmplx* inB) +{ + cmplx *dummy; + + if (run(inA, inB, &dummy)) { + outptr = 0; + } + + return dataP[outptr++]; +} diff --git a/sdrbase/dsp/fftcorr.h b/sdrbase/dsp/fftcorr.h new file mode 100644 index 000000000..0e998e526 --- /dev/null +++ b/sdrbase/dsp/fftcorr.h @@ -0,0 +1,57 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// FFT based cross correlation // +// // +// See: http://liquidsdr.org/blog/pll-howto/ // +// Fixed filter registers saturation // +// Added order for PSK locking. This brilliant idea actually comes from this // +// post: https://www.dsprelated.com/showthread/comp.dsp/36356-1.php // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_FFTCORR_H_ +#define SDRBASE_DSP_FFTCORR_H_ + +#include +#include "gfft.h" +#include "export.h" + +class SDRBASE_API fftcorr { +public: + typedef std::complex cmplx; + fftcorr(int len); + ~fftcorr(); + + int run(const cmplx& inA, const cmplx* inB, cmplx **out); //!< if inB = 0 then run auto-correlation + const cmplx& run(const cmplx& inA, const cmplx* inB); + +private: + void init_fft(); + int flen; //!< FFT length + int flen2; //!< half FFT length + g_fft *fftA; + g_fft *fftB; + cmplx *dataA; // from A input + cmplx *dataB; // from B input + cmplx *dataBj; // conjugate of B + cmplx *dataP; // product of A with conjugate of B + int inptrA; + int inptrB; + int outptr; +}; + + +#endif /* SDRBASE_DSP_FFTCORR_H_ */ diff --git a/sdrbase/dsp/fftengine.h b/sdrbase/dsp/fftengine.h index 05665241a..78028a8d5 100644 --- a/sdrbase/dsp/fftengine.h +++ b/sdrbase/dsp/fftengine.h @@ -2,9 +2,9 @@ #define INCLUDE_FFTENGINE_H #include "dsp/dsptypes.h" -#include "util/export.h" +#include "export.h" -class SDRANGEL_API FFTEngine { +class SDRBASE_API FFTEngine { public: virtual ~FFTEngine(); diff --git a/sdrbase/dsp/fftfilt.cxx b/sdrbase/dsp/fftfilt.cpp similarity index 90% rename from sdrbase/dsp/fftfilt.cxx rename to sdrbase/dsp/fftfilt.cpp index fe6af7ab8..8b7a6698f 100644 --- a/sdrbase/dsp/fftfilt.cxx +++ b/sdrbase/dsp/fftfilt.cpp @@ -28,6 +28,7 @@ // ---------------------------------------------------------------------------- #include +#include #include #include #include @@ -130,7 +131,7 @@ void fftfilt::create_filter(float f1, float f2) for (int i = 0; i < flen2; i++) filter[i] *= _blackman(i, flen2); - fft->ComplexFFT(filter); + fft->ComplexFFT(filter); // filter was expressed in the time domain (impulse response) // normalize the output filter for unity gain float scale = 0, mag; @@ -155,7 +156,7 @@ void fftfilt::create_dsb_filter(float f2) filter[i] *= _blackman(i, flen2); } - fft->ComplexFFT(filter); + fft->ComplexFFT(filter); // filter was expressed in the time domain (impulse response) // normalize the output filter for unity gain float scale = 0, mag; @@ -182,7 +183,7 @@ void fftfilt::create_asym_filter(float fopp, float fin) filter[i] *= _blackman(i, flen2); } - fft->ComplexFFT(filter); + fft->ComplexFFT(filter); // filter was expressed in the time domain (impulse response) // normalize the output filter for unity gain float scale = 0, mag; @@ -204,7 +205,7 @@ void fftfilt::create_asym_filter(float fopp, float fin) filterOpp[i] *= _blackman(i, flen2); } - fft->ComplexFFT(filterOpp); + fft->ComplexFFT(filterOpp); // filter was expressed in the time domain (impulse response) // normalize the output filter for unity gain scale = 0; @@ -218,6 +219,32 @@ void fftfilt::create_asym_filter(float fopp, float fin) } } +// This filter is constructed directly from frequency domain response. Run with runFilt. +void fftfilt::create_rrc_filter(float fb, float a) +{ + std::fill(filter, filter+flen, 0); + + for (int i = 0; i < flen; i++) { + filter[i] = frrc(fb, a, i, flen); + } + + // normalize the output filter for unity gain + float scale = 0, mag; + for (int i = 0; i < flen; i++) + { + mag = abs(filter[i]); + if (mag > scale) { + scale = mag; + } + } + if (scale != 0) + { + for (int i = 0; i < flen; i++) { + filter[i] /= scale; + } + } +} + // test bypass int fftfilt::noFilt(const cmplx & in, cmplx **out) { @@ -298,7 +325,7 @@ int fftfilt::runSSB(const cmplx & in, cmplx **out, bool usb, bool getDC) } // Version for double sideband. You have to double the FFT size used for SSB. -int fftfilt::runDSB(const cmplx & in, cmplx **out) +int fftfilt::runDSB(const cmplx & in, cmplx **out, bool getDC) { data[inptr++] = in; if (inptr < flen2) @@ -312,6 +339,9 @@ int fftfilt::runDSB(const cmplx & in, cmplx **out) data[flen2 + i] *= filter[flen2 + i]; } + // get or reject DC component + data[0] = getDC ? data[0] : 0; + // in-place FFT: freqdata overwritten with filtered timedata fft->InverseComplexFFT(data); diff --git a/sdrbase/dsp/fftfilt.h b/sdrbase/dsp/fftfilt.h index cb8c2b923..5c3e0222c 100644 --- a/sdrbase/dsp/fftfilt.h +++ b/sdrbase/dsp/fftfilt.h @@ -7,13 +7,14 @@ #include #include "gfft.h" +#include "export.h" #undef M_PI #define M_PI 3.14159265358979323846 //---------------------------------------------------------------------- -class fftfilt { +class SDRBASE_API fftfilt { enum {NONE, BLACKMAN, HAMMING, HANNING}; public: @@ -27,11 +28,12 @@ public: void create_filter(float f1, float f2); void create_dsb_filter(float f2); void create_asym_filter(float fopp, float fin); //!< two different filters for in band and opposite band + void create_rrc_filter(float fb, float a); //!< root raised cosine. fb is half the band pass int noFilt(const cmplx& in, cmplx **out); int runFilt(const cmplx& in, cmplx **out); int runSSB(const cmplx& in, cmplx **out, bool usb, bool getDC = true); - int runDSB(const cmplx& in, cmplx **out); + int runDSB(const cmplx& in, cmplx **out, bool getDC = true); int runAsym(const cmplx & in, cmplx **out, bool usb); //!< Asymmetrical fitering can be used for vestigial sideband protected: @@ -47,17 +49,43 @@ protected: int pass; int window; - inline float fsinc(float fc, int i, int len) { - return (i == len/2) ? 2.0 * fc: - sin(2 * M_PI * fc * (i - (len/2))) / (M_PI * (i - (len/2))); + inline float fsinc(float fc, int i, int len) + { + int len2 = len/2; + return (i == len2) ? 2.0 * fc: + sin(2 * M_PI * fc * (i - len2)) / (M_PI * (i - len2)); } - inline float _blackman(int i, int len) { + inline float _blackman(int i, int len) + { return (0.42 - 0.50 * cos(2.0 * M_PI * i / len) + 0.08 * cos(4.0 * M_PI * i / len)); } + /** RRC function in the frequency domain. Zero frequency is on the sides with first half in positive frequencies + * and second half in negative frequencies */ + inline cmplx frrc(float fb, float a, int i, int len) + { + float x = i/(float)len; // normalize to [0..1] + x = 0.5-fabs(x-0.5); // apply symmetry: now both halves overlap near 0 + float tr = fb*a; // half the transition zone + + if (x < fb-tr) + { + return 1.0; // in band + } + else if (x < fb+tr) // transition + { + float y = ((x-(fb-tr)) / (2.0*tr))*M_PI; + return (cos(y) + 1.0f)/2.0f; + } + else + { + return 0.0; // out of band + } + } + void init_filter(); void init_dsb_filter(); }; @@ -65,7 +93,7 @@ protected: /* Sliding FFT filter from Fldigi */ -class sfft { +class SDRBASE_API sfft { #define K1 0.99999 public: typedef std::complex cmplx; diff --git a/sdrbase/dsp/fftwengine.h b/sdrbase/dsp/fftwengine.h index 9224ba4ad..3d269aeb4 100644 --- a/sdrbase/dsp/fftwengine.h +++ b/sdrbase/dsp/fftwengine.h @@ -5,8 +5,9 @@ #include #include #include "dsp/fftengine.h" +#include "export.h" -class FFTWEngine : public FFTEngine { +class SDRBASE_API FFTWEngine : public FFTEngine { public: FFTWEngine(); ~FFTWEngine(); diff --git a/sdrbase/dsp/fftwindow.h b/sdrbase/dsp/fftwindow.h index cca317810..4dece932e 100644 --- a/sdrbase/dsp/fftwindow.h +++ b/sdrbase/dsp/fftwindow.h @@ -22,12 +22,12 @@ #define _USE_MATH_DEFINES #include #include "dsp/dsptypes.h" -#include "util/export.h" +#include "export.h" #undef M_PI #define M_PI 3.14159265358979323846 -class SDRANGEL_API FFTWindow { +class SDRBASE_API FFTWindow { public: enum Function { Bartlett, diff --git a/sdrbase/dsp/filerecord.cpp b/sdrbase/dsp/filerecord.cpp index 82fc684c8..5a483e7d9 100644 --- a/sdrbase/dsp/filerecord.cpp +++ b/sdrbase/dsp/filerecord.cpp @@ -1,13 +1,34 @@ -#include +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015-2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include +#include + #include "dsp/dspcommands.h" #include "util/simpleserializer.h" #include "util/message.h" -#include +#include "filerecord.h" FileRecord::FileRecord() : BasebandSampleSink(), - m_fileName(std::string("test.sdriq")), + m_fileName("test.sdriq"), m_sampleRate(0), m_centerFrequency(0), m_recordOn(false), @@ -17,9 +38,9 @@ FileRecord::FileRecord() : setObjectName("FileSink"); } -FileRecord::FileRecord(const std::string& filename) : +FileRecord::FileRecord(const QString& filename) : BasebandSampleSink(), - m_fileName(std::string(filename)), + m_fileName(filename), m_sampleRate(0), m_centerFrequency(0), m_recordOn(false), @@ -34,7 +55,7 @@ FileRecord::~FileRecord() stopRecording(); } -void FileRecord::setFileName(const std::string& filename) +void FileRecord::setFileName(const QString& filename) { if (!m_recordOn) { @@ -42,6 +63,11 @@ void FileRecord::setFileName(const std::string& filename) } } +void FileRecord::genUniqueFileName(uint deviceUID) +{ + setFileName(QString("rec%1_%2.sdriq").arg(deviceUID).arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddTHH_mm_ss_zzz"))); +} + void FileRecord::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly __attribute__((unused))) { // if no recording is active, send the samples to /dev/null @@ -75,7 +101,7 @@ void FileRecord::startRecording() if (!m_sampleFile.is_open()) { qDebug() << "FileRecord::startRecording"; - m_sampleFile.open(m_fileName.c_str(), std::ios::binary); + m_sampleFile.open(m_fileName.toStdString().c_str(), std::ios::binary); m_recordOn = true; m_recordStart = true; m_byteCount = 0; @@ -110,7 +136,7 @@ bool FileRecord::handleMessage(const Message& message) } } -void FileRecord::handleConfigure(const std::string& fileName) +void FileRecord::handleConfigure(const QString& fileName) { if (fileName != m_fileName) { @@ -122,21 +148,29 @@ void FileRecord::handleConfigure(const std::string& fileName) void FileRecord::writeHeader() { - m_sampleFile.write((const char *) &m_sampleRate, sizeof(qint32)); // 4 bytes - m_sampleFile.write((const char *) &m_centerFrequency, sizeof(quint64)); // 8 bytes + Header header; + header.sampleRate = m_sampleRate; + header.centerFrequency = m_centerFrequency; std::time_t ts = time(0); - m_sampleFile.write((const char *) &ts, sizeof(std::time_t)); // 8 bytes - quint32 sampleSize = SDR_RX_SAMP_SZ; - m_sampleFile.write((const char *) &sampleSize, sizeof(int)); // 4 bytes + header.startTimeStamp = ts; + header.sampleSize = SDR_RX_SAMP_SZ; + header.filler = 0; + + writeHeader(m_sampleFile, header); } -void FileRecord::readHeader(std::ifstream& sampleFile, Header& header) +bool FileRecord::readHeader(std::ifstream& sampleFile, Header& header) { - sampleFile.read((char *) &(header.sampleRate), sizeof(qint32)); - sampleFile.read((char *) &(header.centerFrequency), sizeof(quint64)); - sampleFile.read((char *) &(header.startTimeStamp), sizeof(std::time_t)); - sampleFile.read((char *) &(header.sampleSize), sizeof(quint32)); - if ((header.sampleSize != 16) && (header.sampleSize != 24)) { // assume 16 bits if garbage (old I/Q file) - header.sampleSize = 16; - } + sampleFile.read((char *) &header, sizeof(Header)); + boost::crc_32_type crc32; + crc32.process_bytes(&header, 28); + return header.crc32 == crc32.checksum(); } + +void FileRecord::writeHeader(std::ofstream& sampleFile, Header& header) +{ + boost::crc_32_type crc32; + crc32.process_bytes(&header, 28); + header.crc32 = crc32.checksum(); + sampleFile.write((const char *) &header, sizeof(Header)); +} \ No newline at end of file diff --git a/sdrbase/dsp/filerecord.h b/sdrbase/dsp/filerecord.h index dac0be078..e5377ee13 100644 --- a/sdrbase/dsp/filerecord.h +++ b/sdrbase/dsp/filerecord.h @@ -1,5 +1,21 @@ -#ifndef INCLUDE_FILESINK_H -#define INCLUDE_FILESINK_H +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015-2018 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_FILERECORD_H +#define INCLUDE_FILERECORD_H #include #include @@ -7,28 +23,33 @@ #include #include -#include "util/export.h" +#include "export.h" class Message; -class SDRANGEL_API FileRecord : public BasebandSampleSink { +class SDRBASE_API FileRecord : public BasebandSampleSink { public: +#pragma pack(push, 1) struct Header { - qint32 sampleRate; - quint64 centerFrequency; - std::time_t startTimeStamp; - quint32 sampleSize; + quint32 sampleRate; + quint64 centerFrequency; + quint64 startTimeStamp; + quint32 sampleSize; + quint32 filler; + quint32 crc32; }; +#pragma pack(pop) FileRecord(); - FileRecord(const std::string& filename); + FileRecord(const QString& filename); virtual ~FileRecord(); quint64 getByteCount() const { return m_byteCount; } - void setFileName(const std::string& filename); + void setFileName(const QString& filename); + void genUniqueFileName(uint deviceUID); virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); virtual void start(); @@ -36,19 +57,20 @@ public: virtual bool handleMessage(const Message& message); void startRecording(); void stopRecording(); - static void readHeader(std::ifstream& samplefile, Header& header); + static bool readHeader(std::ifstream& samplefile, Header& header); //!< returns true if CRC checksum is correct else false + static void writeHeader(std::ofstream& samplefile, Header& header); private: - std::string m_fileName; - qint32 m_sampleRate; + QString m_fileName; + quint32 m_sampleRate; quint64 m_centerFrequency; bool m_recordOn; bool m_recordStart; std::ofstream m_sampleFile; quint64 m_byteCount; - void handleConfigure(const std::string& fileName); + void handleConfigure(const QString& fileName); void writeHeader(); }; -#endif // INCLUDE_FILESINK_H +#endif // INCLUDE_FILERECORD_H diff --git a/sdrbase/dsp/filtermbe.cpp b/sdrbase/dsp/filtermbe.cpp index 63b5dc9da..bad18b3f2 100644 --- a/sdrbase/dsp/filtermbe.cpp +++ b/sdrbase/dsp/filtermbe.cpp @@ -20,8 +20,8 @@ const float MBEAudioInterpolatorFilter::m_lpa[3] = {1.0, 1.392667E+00, -5.474446E-01}; const float MBEAudioInterpolatorFilter::m_lpb[3] = {3.869430E-02, 7.738860E-02, 3.869430E-02}; -const float MBEAudioInterpolatorFilter::m_hpa[3] = {1.000000e+00, 1.955578e+00, -9.565437e-01}; -const float MBEAudioInterpolatorFilter::m_hpb[3] = {9.780305e-01, -1.956061e+00, 9.780305e-01}; +const float MBEAudioInterpolatorFilter::m_hpa[3] = {1.000000e+00, 1.667871e+00, -7.156964e-01}; +const float MBEAudioInterpolatorFilter::m_hpb[3] = {8.459039e-01, -1.691760e+00, 8.459039e-01}; MBEAudioInterpolatorFilter::MBEAudioInterpolatorFilter() : m_filterLP(m_lpa, m_lpb), @@ -37,3 +37,13 @@ float MBEAudioInterpolatorFilter::run(const float& sample) { return m_useHP ? m_filterLP.run(m_filterHP.run(sample)) : m_filterLP.run(sample); } + +float MBEAudioInterpolatorFilter::runHP(const float& sample) +{ + return m_filterHP.run(sample); +} + +float MBEAudioInterpolatorFilter::runLP(const float& sample) +{ + return m_filterLP.run(sample); +} diff --git a/sdrbase/dsp/filtermbe.h b/sdrbase/dsp/filtermbe.h index f3d6788fb..2b4cc3dc1 100644 --- a/sdrbase/dsp/filtermbe.h +++ b/sdrbase/dsp/filtermbe.h @@ -58,15 +58,19 @@ */ #include "iirfilter.h" +#include "export.h" -class MBEAudioInterpolatorFilter +class SDRBASE_API MBEAudioInterpolatorFilter { public: MBEAudioInterpolatorFilter(); ~MBEAudioInterpolatorFilter(); void useHP(bool useHP) { m_useHP = useHP; } + bool usesHP() const { return m_useHP; } float run(const float& sample); + float runHP(const float& sample); + float runLP(const float& sample); private: IIRFilter m_filterLP; diff --git a/sdrbase/dsp/filterrc.h b/sdrbase/dsp/filterrc.h index 7f1af7c89..1c4a574d1 100644 --- a/sdrbase/dsp/filterrc.h +++ b/sdrbase/dsp/filterrc.h @@ -19,9 +19,10 @@ #define INCLUDE_DSP_FILTERRC_H_ #include "dsp/dsptypes.h" +#include "export.h" /** First order low-pass IIR filter for real-valued signals. */ -class LowPassFilterRC +class SDRBASE_API LowPassFilterRC { public: diff --git a/sdrbase/dsp/freqlockcomplex.cpp b/sdrbase/dsp/freqlockcomplex.cpp new file mode 100644 index 000000000..2e8b9676f --- /dev/null +++ b/sdrbase/dsp/freqlockcomplex.cpp @@ -0,0 +1,92 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// See: http://liquidsdr.org/blog/pll-howto/ // +// Fixed filter registers saturation // +// Added order for PSK locking. This brilliant idea actually comes from this // +// post: https://www.dsprelated.com/showthread/comp.dsp/36356-1.php // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "freqlockcomplex.h" +#include + +FreqLockComplex::FreqLockComplex() : + m_a0(0.998), + m_a1(0.002), + m_y(1.0, 0.0), + m_yRe(1.0), + m_yIm(0.0), + m_freq(0.0), + m_phi(0.0), + m_phiX0(0.0), + m_phiX1(0.0), + m_y1(0.0f) +{ +} + +FreqLockComplex::~FreqLockComplex() +{ +} + +void FreqLockComplex::reset() +{ + m_y.real(1.0); + m_y.imag(0.0); + m_yRe = 1.0f; + m_yIm = 0.0f; + m_freq = 0.0f; + m_phi = 0.0f; + m_phiX0 = 0.0f; + m_phiX1 = 0.0f; + m_y1 = 0.0f; +} + +void FreqLockComplex::setSampleRate(unsigned int sampleRate) +{ + m_a1 = 10.0f / sampleRate; // 1 - alpha + m_a0 = 1.0f - m_a1; // alpha + reset(); +} + +void FreqLockComplex::feed(float re, float im) +{ + m_yRe = cos(m_phi); + m_yIm = sin(m_phi); + m_y.real(m_yRe); + m_y.imag(m_yIm); + std::complex x(re, im); + m_phiX0 = std::arg(x); + + float eF = normalizeAngle(m_phiX0 - m_phiX1); + float fHat = m_a1*eF + m_a0*m_y1; + m_y1 = fHat; + + m_freq = fHat; // correct instantaneous frequency + m_phi += fHat; // advance phase with instantaneous frequency + m_phiX1 = m_phiX0; +} + +float FreqLockComplex::normalizeAngle(float angle) +{ + while (angle <= -M_PI) { + angle += 2.0*M_PI; + } + while (angle > M_PI) { + angle -= 2.0*M_PI; + } + return angle; +} + diff --git a/sdrbase/dsp/freqlockcomplex.h b/sdrbase/dsp/freqlockcomplex.h new file mode 100644 index 000000000..279fe3fc7 --- /dev/null +++ b/sdrbase/dsp/freqlockcomplex.h @@ -0,0 +1,61 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// See: http://liquidsdr.org/blog/pll-howto/ // +// Fixed filter registers saturation // +// Added order for PSK locking. This brilliant idea actually comes from this // +// post: https://www.dsprelated.com/showthread/comp.dsp/36356-1.php // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_FREQLOCKCOMPLEX_H_ +#define SDRBASE_DSP_FREQLOCKCOMPLEX_H_ + +#include "dsp/dsptypes.h" +#include "export.h" + +/** General purpose Phase-locked loop using complex analytic signal input. */ +class SDRBASE_API FreqLockComplex +{ +public: + FreqLockComplex(); + ~FreqLockComplex(); + + void reset(); + void setSampleRate(unsigned int sampleRate); + /** Feed PLL with a new signa sample */ + void feed(float re, float im); + const std::complex& getComplex() const { return m_y; } + float getReal() const { return m_yRe; } + float getImag() const { return m_yIm; } + +private: + /** Normalize angle in radians into the [-pi,+pi] region */ + static float normalizeAngle(float angle); + + float m_a0; + float m_a1; + std::complex m_y; + float m_yRe; + float m_yIm; + float m_freq; + float m_phi; + float m_phiX0; + float m_phiX1; + float m_y1; +}; + + +#endif /* SDRBASE_DSP_FREQLOCKCOMPLEX_H_ */ diff --git a/sdrbase/dsp/hbfiltertraits.cpp b/sdrbase/dsp/hbfiltertraits.cpp index 8f82c1e36..ef8da3aa1 100644 --- a/sdrbase/dsp/hbfiltertraits.cpp +++ b/sdrbase/dsp/hbfiltertraits.cpp @@ -170,7 +170,7 @@ const int32_t HBFIRFilterTraits<64>::hbCoeffs[16] = { // (qint32)( 0.317657589850154464805598308885237202048 * (1 << hbShift)), }; -const double HBFIRFilterTraits<64>::hbCoeffsF[16] = { +const float HBFIRFilterTraits<64>::hbCoeffsF[16] = { -0.0004653050334792540416659067936677729449, 0.0007120490624526883919470643391491648799, -0.0012303473710125558716887983479182366864, diff --git a/sdrbase/dsp/hbfiltertraits.h b/sdrbase/dsp/hbfiltertraits.h index f72e46765..9232c9a73 100644 --- a/sdrbase/dsp/hbfiltertraits.h +++ b/sdrbase/dsp/hbfiltertraits.h @@ -19,6 +19,7 @@ #define SDRBASE_DSP_HBFILTERTRAITS_H_ #include +#include "export.h" // uses Q1.14 format internally, input and output are S16 @@ -32,7 +33,7 @@ struct HBFIRFilterTraits }; template<> -struct HBFIRFilterTraits<16> +struct SDRBASE_API HBFIRFilterTraits<16> { static const int32_t hbOrder = 16; static const int32_t hbShift = 12; @@ -42,7 +43,7 @@ struct HBFIRFilterTraits<16> }; template<> -struct HBFIRFilterTraits<32> +struct SDRBASE_API HBFIRFilterTraits<32> { static const int32_t hbOrder = 32; static const int32_t hbShift = 12; @@ -52,7 +53,7 @@ struct HBFIRFilterTraits<32> }; template<> -struct HBFIRFilterTraits<48> +struct SDRBASE_API HBFIRFilterTraits<48> { static const int32_t hbOrder = 48; static const int32_t hbShift = 12; @@ -62,17 +63,17 @@ struct HBFIRFilterTraits<48> }; template<> -struct HBFIRFilterTraits<64> +struct SDRBASE_API HBFIRFilterTraits<64> { static const int32_t hbOrder = 64; static const int32_t hbShift = 12; static const int16_t hbMod[64+6]; static const int32_t hbCoeffs[16] __attribute__ ((aligned (32))); - static const double hbCoeffsF[16]; + static const float hbCoeffsF[16]; }; template<> -struct HBFIRFilterTraits<80> +struct SDRBASE_API HBFIRFilterTraits<80> { static const int32_t hbOrder = 80; static const int32_t hbShift = 14; @@ -82,7 +83,7 @@ struct HBFIRFilterTraits<80> }; template<> -struct HBFIRFilterTraits<96> +struct SDRBASE_API HBFIRFilterTraits<96> { static const int32_t hbOrder = 96; static const int32_t hbShift = 16; @@ -92,7 +93,7 @@ struct HBFIRFilterTraits<96> }; template<> -struct HBFIRFilterTraits<112> +struct SDRBASE_API HBFIRFilterTraits<112> { static const int32_t hbOrder = 112; static const int32_t hbShift = 18; @@ -102,7 +103,7 @@ struct HBFIRFilterTraits<112> }; template<> -struct HBFIRFilterTraits<128> +struct SDRBASE_API HBFIRFilterTraits<128> { static const int32_t hbOrder = 128; static const int32_t hbShift = 20; diff --git a/sdrbase/dsp/interpolator.h b/sdrbase/dsp/interpolator.h index b504987af..607b3fe97 100644 --- a/sdrbase/dsp/interpolator.h +++ b/sdrbase/dsp/interpolator.h @@ -5,13 +5,13 @@ #include #endif #include "dsp/dsptypes.h" -#include "util/export.h" +#include "export.h" #include #ifndef __WINDOWS__ #include #endif -class SDRANGEL_API Interpolator { +class SDRBASE_API Interpolator { public: Interpolator(); ~Interpolator(); diff --git a/sdrbase/dsp/inthalfbandfilter.h b/sdrbase/dsp/inthalfbandfilter.h index 2a0eec627..b51189344 100644 --- a/sdrbase/dsp/inthalfbandfilter.h +++ b/sdrbase/dsp/inthalfbandfilter.h @@ -21,10 +21,10 @@ #include #include "dsp/dsptypes.h" #include "dsp/hbfiltertraits.h" -#include "util/export.h" +#include "export.h" template -class SDRANGEL_API IntHalfbandFilter { +class SDRBASE_API IntHalfbandFilter { public: IntHalfbandFilter() : m_ptr(0), diff --git a/sdrbase/dsp/inthalfbandfilterdb.h b/sdrbase/dsp/inthalfbandfilterdb.h index e65dfa59c..a4b00bd8f 100644 --- a/sdrbase/dsp/inthalfbandfilterdb.h +++ b/sdrbase/dsp/inthalfbandfilterdb.h @@ -24,10 +24,10 @@ #include #include "dsp/dsptypes.h" #include "dsp/hbfiltertraits.h" -#include "util/export.h" +#include "export.h" template -class SDRANGEL_API IntHalfbandFilterDB { +class SDRBASE_API IntHalfbandFilterDB { public: IntHalfbandFilterDB(); diff --git a/sdrbase/dsp/inthalfbandfilterdbf.h b/sdrbase/dsp/inthalfbandfilterdbf.h index 01b281f50..938bf57d3 100644 --- a/sdrbase/dsp/inthalfbandfilterdbf.h +++ b/sdrbase/dsp/inthalfbandfilterdbf.h @@ -24,534 +24,20 @@ #include #include "dsp/dsptypes.h" #include "dsp/hbfiltertraits.h" -#include "util/export.h" +#include "export.h" template -class SDRANGEL_API IntHalfbandFilterDBF { +class SDRBASE_API IntHalfbandFilterDBF { public: IntHalfbandFilterDBF(); - // downsample by 2, return center part of original spectrum - bool workDecimateCenter(Sample* sample) - { - // insert sample into ring-buffer - storeSampleFixReal((FixReal) sample->real(), (FixReal) sample->imag()); - - switch(m_state) - { - case 0: - // advance write-pointer - advancePointer(); - // next state - m_state = 1; - // tell caller we don't have a new sample - return false; - - default: - // save result - doFIR(sample); - // advance write-pointer - advancePointer(); - // next state - m_state = 0; - - // tell caller we have a new sample - return true; - } - } - - // upsample by 2, return center part of original spectrum - double buffer variant - bool workInterpolateCenterZeroStuffing(Sample* sampleIn, Sample *SampleOut) - { - switch(m_state) - { - case 0: - // insert sample into ring-buffer - storeSampleFixReal((FixReal) 0, (FixReal) 0); - // save result - doFIR(SampleOut); - // advance write-pointer - advancePointer(); - // next state - m_state = 1; - // tell caller we didn't consume the sample - return false; - - default: - // insert sample into ring-buffer - storeSampleFixReal((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); - // save result - doFIR(SampleOut); - // advance write-pointer - advancePointer(); - // next state - m_state = 0; - // tell caller we consumed the sample - return true; - } - } - - /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ - bool workInterpolateCenter(Sample* sampleIn, Sample *SampleOut) - { - switch(m_state) - { - case 0: - // return the middle peak - SampleOut->setReal(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); - SampleOut->setImag(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); - m_state = 1; // next state - return false; // tell caller we didn't consume the sample - - default: - // calculate with non null samples - doInterpolateFIR(SampleOut); - - // insert sample into ring double buffer - m_samplesDB[m_ptr][0] = sampleIn->real(); - m_samplesDB[m_ptr][1] = sampleIn->imag(); - m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); - m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); - - // advance pointer - if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { - m_ptr++; - } else { - m_ptr = 0; - } - - m_state = 0; // next state - return true; // tell caller we consumed the sample - } - } - - // downsample by 2, return lower half of original spectrum - bool workDecimateLowerHalf(Sample* sample) - { - switch(m_state) - { - case 0: - // insert sample into ring-buffer - storeSampleFixReal((FixReal) -sample->imag(), (FixReal) sample->real()); - // advance write-pointer - advancePointer(); - // next state - m_state = 1; - // tell caller we don't have a new sample - return false; - - case 1: - // insert sample into ring-buffer - storeSampleFixReal((FixReal) -sample->real(), (FixReal) -sample->imag()); - // save result - doFIR(sample); - // advance write-pointer - advancePointer(); - // next state - m_state = 2; - // tell caller we have a new sample - return true; - - case 2: - // insert sample into ring-buffer - storeSampleFixReal((FixReal) sample->imag(), (FixReal) -sample->real()); - // advance write-pointer - advancePointer(); - // next state - m_state = 3; - // tell caller we don't have a new sample - return false; - - default: - // insert sample into ring-buffer - storeSampleFixReal((FixReal) sample->real(), (FixReal) sample->imag()); - // save result - doFIR(sample); - // advance write-pointer - advancePointer(); - // next state - m_state = 0; - // tell caller we have a new sample - return true; - } - } - - /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ - bool workInterpolateLowerHalf(Sample* sampleIn, Sample *sampleOut) - { - Sample s; - - switch(m_state) - { - case 0: - // return the middle peak - sampleOut->setReal(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // imag - sampleOut->setImag(-m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real - m_state = 1; // next state - return false; // tell caller we didn't consume the sample - - case 1: - // calculate with non null samples - doInterpolateFIR(&s); - sampleOut->setReal(-s.real()); - sampleOut->setImag(-s.imag()); - - // insert sample into ring double buffer - m_samplesDB[m_ptr][0] = sampleIn->real(); - m_samplesDB[m_ptr][1] = sampleIn->imag(); - m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); - m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); - - // advance pointer - if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { - m_ptr++; - } else { - m_ptr = 0; - } - - m_state = 2; // next state - return true; // tell caller we consumed the sample - - case 2: - // return the middle peak - sampleOut->setReal(-m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag - sampleOut->setImag(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // real - m_state = 3; // next state - return false; // tell caller we didn't consume the sample - - default: - // calculate with non null samples - doInterpolateFIR(&s); - sampleOut->setReal(s.real()); - sampleOut->setImag(s.imag()); - - // insert sample into ring double buffer - m_samplesDB[m_ptr][0] = sampleIn->real(); - m_samplesDB[m_ptr][1] = sampleIn->imag(); - m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); - m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); - - // advance pointer - if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { - m_ptr++; - } else { - m_ptr = 0; - } - - m_state = 0; // next state - return true; // tell caller we consumed the sample - } - } - - // upsample by 2, from lower half of original spectrum - double buffer variant - bool workInterpolateLowerHalfZeroStuffing(Sample* sampleIn, Sample *sampleOut) - { - Sample s; - - switch(m_state) - { - case 0: - // insert sample into ring-buffer - storeSampleFixReal((FixReal) 0, (FixReal) 0); - - // save result - doFIR(&s); - sampleOut->setReal(s.imag()); - sampleOut->setImag(-s.real()); - - // advance write-pointer - advancePointer(); - - // next state - m_state = 1; - - // tell caller we didn't consume the sample - return false; - - case 1: - // insert sample into ring-buffer - storeSampleFixReal((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); - - // save result - doFIR(&s); - sampleOut->setReal(-s.real()); - sampleOut->setImag(-s.imag()); - - // advance write-pointer - advancePointer(); - - // next state - m_state = 2; - - // tell caller we consumed the sample - return true; - - case 2: - // insert sample into ring-buffer - storeSampleFixReal((FixReal) 0, (FixReal) 0); - - // save result - doFIR(&s); - sampleOut->setReal(-s.imag()); - sampleOut->setImag(s.real()); - - // advance write-pointer - advancePointer(); - - // next state - m_state = 3; - - // tell caller we didn't consume the sample - return false; - - default: - // insert sample into ring-buffer - storeSampleFixReal((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); - - // save result - doFIR(&s); - sampleOut->setReal(s.real()); - sampleOut->setImag(s.imag()); - - // advance write-pointer - advancePointer(); - - // next state - m_state = 0; - - // tell caller we consumed the sample - return true; - } - } - - // downsample by 2, return upper half of original spectrum - bool workDecimateUpperHalf(Sample* sample) - { - switch(m_state) - { - case 0: - // insert sample into ring-buffer - storeSampleFixReal((FixReal) sample->imag(), (FixReal) -sample->real()); - // advance write-pointer - advancePointer(); - // next state - m_state = 1; - // tell caller we don't have a new sample - return false; - - case 1: - // insert sample into ring-buffer - storeSampleFixReal((FixReal) -sample->real(), (FixReal) -sample->imag()); - // save result - doFIR(sample); - // advance write-pointer - advancePointer(); - // next state - m_state = 2; - // tell caller we have a new sample - return true; - - case 2: - // insert sample into ring-buffer - storeSampleFixReal((FixReal) -sample->imag(), (FixReal) sample->real()); - // advance write-pointer - advancePointer(); - // next state - m_state = 3; - // tell caller we don't have a new sample - return false; - - default: - // insert sample into ring-buffer - storeSampleFixReal((FixReal) sample->real(), (FixReal) sample->imag()); - // save result - doFIR(sample); - // advance write-pointer - advancePointer(); - // next state - m_state = 0; - // tell caller we have a new sample - return true; - } - } - - /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ - bool workInterpolateUpperHalf(Sample* sampleIn, Sample *sampleOut) - { - Sample s; - - switch(m_state) - { - case 0: - // return the middle peak - sampleOut->setReal(-m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag - sampleOut->setImag(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // + real - m_state = 1; // next state - return false; // tell caller we didn't consume the sample - - case 1: - // calculate with non null samples - doInterpolateFIR(&s); - sampleOut->setReal(-s.real()); - sampleOut->setImag(-s.imag()); - - // insert sample into ring double buffer - m_samplesDB[m_ptr][0] = sampleIn->real(); - m_samplesDB[m_ptr][1] = sampleIn->imag(); - m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); - m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); - - // advance pointer - if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { - m_ptr++; - } else { - m_ptr = 0; - } - - m_state = 2; // next state - return true; // tell caller we consumed the sample - - case 2: - // return the middle peak - sampleOut->setReal(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // + imag - sampleOut->setImag(-m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real - m_state = 3; // next state - return false; // tell caller we didn't consume the sample - - default: - // calculate with non null samples - doInterpolateFIR(&s); - sampleOut->setReal(s.real()); - sampleOut->setImag(s.imag()); - - // insert sample into ring double buffer - m_samplesDB[m_ptr][0] = sampleIn->real(); - m_samplesDB[m_ptr][1] = sampleIn->imag(); - m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); - m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); - - // advance pointer - if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { - m_ptr++; - } else { - m_ptr = 0; - } - - m_state = 0; // next state - return true; // tell caller we consumed the sample - } - } - - // upsample by 2, move original spectrum to upper half - double buffer variant - bool workInterpolateUpperHalfZeroStuffing(Sample* sampleIn, Sample *sampleOut) - { - Sample s; - - switch(m_state) - { - case 0: - // insert sample into ring-buffer - storeSampleFixReal((FixReal) 0, (FixReal) 0); - - // save result - doFIR(&s); - sampleOut->setReal(-s.imag()); - sampleOut->setImag(s.real()); - - // advance write-pointer - advancePointer(); - - // next state - m_state = 1; - - // tell caller we didn't consume the sample - return false; - - case 1: - // insert sample into ring-buffer - storeSampleFixReal((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); - - // save result - doFIR(&s); - sampleOut->setReal(-s.real()); - sampleOut->setImag(-s.imag()); - - // advance write-pointer - advancePointer(); - - // next state - m_state = 2; - - // tell caller we consumed the sample - return true; - - case 2: - // insert sample into ring-buffer - storeSampleFixReal((FixReal) 0, (FixReal) 0); - - // save result - doFIR(&s); - sampleOut->setReal(s.imag()); - sampleOut->setImag(-s.real()); - - // advance write-pointer - advancePointer(); - - // next state - m_state = 3; - - // tell caller we didn't consume the sample - return false; - - default: - // insert sample into ring-buffer - storeSampleFixReal((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); - - // save result - doFIR(&s); - sampleOut->setReal(s.real()); - sampleOut->setImag(s.imag()); - - // advance write-pointer - advancePointer(); - - // next state - m_state = 0; - - // tell caller we consumed the sample - return true; - } - } - - void myDecimate(const Sample* sample1, Sample* sample2) - { - storeSampleFixReal((FixReal) sample1->real(), (FixReal) sample1->imag()); - advancePointer(); - - storeSampleFixReal((FixReal) sample2->real(), (FixReal) sample2->imag()); - doFIR(sample2); - advancePointer(); - } - void myDecimate(AccuType x1, AccuType y1, AccuType *x2, AccuType *y2) { - storeSampleAccu(x1, y1); + storeSample(x1, y1); advancePointer(); - storeSampleAccu(*x2, *y2); - doFIRAccu(x2, y2); - advancePointer(); - } - - /** Simple zero stuffing and filter */ - void myInterpolateZeroStuffing(Sample* sample1, Sample* sample2) - { - storeSampleFixReal((FixReal) sample1->real(), (FixReal) sample1->imag()); - doFIR(sample1); - advancePointer(); - - storeSampleFixReal((FixReal) 0, (FixReal) 0); - doFIR(sample2); + storeSample(*x2, *y2); + doFIR(x2, y2); advancePointer(); } @@ -585,15 +71,7 @@ protected: int m_size; int m_state; - void storeSampleFixReal(const FixReal& sampleI, const FixReal& sampleQ) - { - m_samplesDB[m_ptr][0] = sampleI / SDR_RX_SCALED; - m_samplesDB[m_ptr][1] = sampleQ / SDR_RX_SCALED; - m_samplesDB[m_ptr + m_size][0] = sampleI / SDR_RX_SCALED; - m_samplesDB[m_ptr + m_size][1] = sampleQ / SDR_RX_SCALED; - } - - void storeSampleAccu(AccuType x, AccuType y) + void storeSample(AccuType x, AccuType y) { m_samplesDB[m_ptr][0] = x; m_samplesDB[m_ptr][1] = y; @@ -606,29 +84,7 @@ protected: m_ptr = m_ptr + 1 < m_size ? m_ptr + 1: 0; } - void doFIR(Sample* sample) - { - int a = m_ptr + m_size; // tip pointer - int b = m_ptr + 1; // tail pointer - AccuType iAcc = 0; - AccuType qAcc = 0; - - for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) - { - iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffsF[i]; - qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffsF[i]; - a -= 2; - b += 2; - } - - iAcc += m_samplesDB[b-1][0] << (HBFIRFilterTraits::hbShift - 1); - qAcc += m_samplesDB[b-1][1] << (HBFIRFilterTraits::hbShift - 1); - - sample->setReal(iAcc*SDR_RX_SCALED); - sample->setImag(qAcc*SDR_RX_SCALED); - } - - void doFIRAccu(AccuType *x, AccuType *y) + void doFIR(AccuType *x, AccuType *y) { int a = m_ptr + m_size; // tip pointer int b = m_ptr + 1; // tail pointer @@ -650,27 +106,6 @@ protected: *y = qAcc; } - void doInterpolateFIR(Sample* sample) - { - qint16 a = m_ptr; - qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; - - // go through samples in buffer - AccuType iAcc = 0; - AccuType qAcc = 0; - - for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) - { - iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffsF[i]; - qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffsF[i]; - a++; - b--; - } - - sample->setReal(iAcc * SDR_RX_SCALED); - sample->setImag(qAcc * SDR_RX_SCALED); - } - void doInterpolateFIR(qint32 *x, qint32 *y) { qint16 a = m_ptr; diff --git a/sdrbase/dsp/inthalfbandfilterdbff.h b/sdrbase/dsp/inthalfbandfilterdbff.h new file mode 100644 index 000000000..2816f2696 --- /dev/null +++ b/sdrbase/dsp/inthalfbandfilterdbff.h @@ -0,0 +1,711 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// Float half-band FIR based interpolator and decimator // +// This is the double buffer variant // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_INTHALFBANDFILTER_DBFF_H +#define INCLUDE_INTHALFBANDFILTER_DBFF_H + +#include +#include "dsp/dsptypes.h" +#include "dsp/hbfiltertraits.h" +#include "export.h" + +template +class SDRBASE_API IntHalfbandFilterDBFF { +public: + IntHalfbandFilterDBFF(); + + // downsample by 2, return center part of original spectrum + bool workDecimateCenter(FSample* sample) + { + // insert sample into ring-buffer + storeSampleReal((Real) sample->real(), (Real) sample->imag()); + + switch(m_state) + { + case 0: + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + default: + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, return center part of original spectrum - double buffer variant + bool workInterpolateCenterZeroStuffing(FSample* sampleIn, FSample *SampleOut) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleReal((Real) 0, (Real) 0); + // save result + doFIR(SampleOut); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSampleReal((Real) sampleIn->real(), (Real) sampleIn->imag()); + // save result + doFIR(SampleOut); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateCenter(FSample* sampleIn, FSample *SampleOut) + { + switch(m_state) + { + case 0: + // return the middle peak + SampleOut->setReal(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); + SampleOut->setImag(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(SampleOut); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + // downsample by 2, return lower half of original spectrum + bool workDecimateLowerHalf(FSample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleReal((Real) -sample->imag(), (Real) sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + storeSampleReal((Real) -sample->real(), (Real) -sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 2; + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + storeSampleReal((Real) sample->imag(), (Real) -sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 3; + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + storeSampleReal((Real) sample->real(), (Real) sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateLowerHalf(FSample* sampleIn, FSample *sampleOut) + { + FSample s; + + switch(m_state) + { + case 0: + // return the middle peak + sampleOut->setReal(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // imag + sampleOut->setImag(-m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + case 1: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 2; // next state + return true; // tell caller we consumed the sample + + case 2: + // return the middle peak + sampleOut->setReal(-m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag + sampleOut->setImag(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // real + m_state = 3; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + // upsample by 2, from lower half of original spectrum - double buffer variant + bool workInterpolateLowerHalfZeroStuffing(FSample* sampleIn, FSample *sampleOut) + { + FSample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleReal((Real) 0, (Real) 0); + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + storeSampleReal((Real) sampleIn->real(), (Real) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + storeSampleReal((Real) 0, (Real) 0); + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSampleReal((Real) sampleIn->real(), (Real) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + // downsample by 2, return upper half of original spectrum + bool workDecimateUpperHalf(FSample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleReal((Real) sample->imag(), (Real) -sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + storeSampleReal((Real) -sample->real(), (Real) -sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 2; + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + storeSampleReal((Real) -sample->imag(), (Real) sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 3; + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + storeSampleReal((Real) sample->real(), (Real) sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateUpperHalf(FSample* sampleIn, FSample *sampleOut) + { + FSample s; + + switch(m_state) + { + case 0: + // return the middle peak + sampleOut->setReal(-m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag + sampleOut->setImag(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // + real + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + case 1: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 2; // next state + return true; // tell caller we consumed the sample + + case 2: + // return the middle peak + sampleOut->setReal(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // + imag + sampleOut->setImag(-m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real + m_state = 3; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + // upsample by 2, move original spectrum to upper half - double buffer variant + bool workInterpolateUpperHalfZeroStuffing(FSample* sampleIn, FSample *sampleOut) + { + FSample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleReal((Real) 0, (Real) 0); + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + storeSampleReal((Real) sampleIn->real(), (Real) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + storeSampleReal((Real) 0, (Real) 0); + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSampleReal((Real) sampleIn->real(), (Real) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + void myDecimate(const FSample* sample1, FSample* sample2) + { + storeSampleReal((Real) sample1->real(), (Real) sample1->imag()); + advancePointer(); + + storeSampleReal((Real) sample2->real(), (Real) sample2->imag()); + doFIR(sample2); + advancePointer(); + } + + void myDecimate(AccuType x1, AccuType y1, AccuType *x2, AccuType *y2) + { + storeSampleAccu(x1, y1); + advancePointer(); + + storeSampleAccu(*x2, *y2); + doFIRAccu(x2, y2); + advancePointer(); + } + + /** Simple zero stuffing and filter */ + void myInterpolateZeroStuffing(FSample* sample1, FSample* sample2) + { + storeSampleReal((Real) sample1->real(), (Real) sample1->imag()); + doFIR(sample1); + advancePointer(); + + storeSampleReal((Real) 0, (Real) 0); + doFIR(sample2); + advancePointer(); + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + void myInterpolate(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2) + { + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = *x1; + m_samplesDB[m_ptr][1] = *y1; + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = *x1; + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = *y1; + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + // first output sample calculated with the middle peak + *x1 = m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]; + *y1 = m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]; + + // second sample calculated with the filter + doInterpolateFIR(x2, y2); + } + +protected: + SampleType m_samplesDB[2*(HBFIRFilterTraits::hbOrder - 1)][2]; // double buffer technique + int m_ptr; + int m_size; + int m_state; + + void storeSampleReal(const Real& sampleI, const Real& sampleQ) + { + m_samplesDB[m_ptr][0] = sampleI; + m_samplesDB[m_ptr][1] = sampleQ; + m_samplesDB[m_ptr + m_size][0] = sampleI; + m_samplesDB[m_ptr + m_size][1] = sampleQ; + } + + void storeSampleAccu(AccuType x, AccuType y) + { + m_samplesDB[m_ptr][0] = x; + m_samplesDB[m_ptr][1] = y; + m_samplesDB[m_ptr + m_size][0] = x; + m_samplesDB[m_ptr + m_size][1] = y; + } + + void advancePointer() + { + m_ptr = m_ptr + 1 < m_size ? m_ptr + 1: 0; + } + + void doFIR(FSample* sample) + { + int a = m_ptr + m_size; // tip pointer + int b = m_ptr + 1; // tail pointer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffsF[i]; + a -= 2; + b += 2; + } + + iAcc += m_samplesDB[b-1][0] << (HBFIRFilterTraits::hbShift - 1); + qAcc += m_samplesDB[b-1][1] << (HBFIRFilterTraits::hbShift - 1); + + sample->setReal(iAcc); + sample->setImag(qAcc); + } + + void doFIRAccu(AccuType *x, AccuType *y) + { + int a = m_ptr + m_size; // tip pointer + int b = m_ptr + 1; // tail pointer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffsF[i]; + a -= 2; + b += 2; + } + + iAcc += m_samplesDB[b-1][0] / 2.0; + qAcc += m_samplesDB[b-1][1] / 2.0; + + *x = iAcc; // HB_SHIFT incorrect do not loose the gained bit + *y = qAcc; + } + + void doInterpolateFIR(FSample* sample) + { + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffsF[i]; + a++; + b--; + } + + sample->setReal(iAcc); + sample->setImag(qAcc); + } + + void doInterpolateFIR(Real *x, Real *y) + { + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffsF[i]; + a++; + b--; + } + + *x = iAcc; + *y = qAcc; + } +}; + +template +IntHalfbandFilterDBFF::IntHalfbandFilterDBFF() +{ + m_size = HBFIRFilterTraits::hbOrder - 1; + + for (int i = 0; i < m_size; i++) + { + m_samplesDB[i][0] = 0; + m_samplesDB[i][1] = 0; + } + + m_ptr = 0; + m_state = 0; +} + +#endif // INCLUDE_INTHALFBANDFILTER_DBFF_H diff --git a/sdrbase/dsp/inthalfbandfilterdbfi.h b/sdrbase/dsp/inthalfbandfilterdbfi.h new file mode 100644 index 000000000..f83c4fd33 --- /dev/null +++ b/sdrbase/dsp/inthalfbandfilterdbfi.h @@ -0,0 +1,711 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// Float half-band FIR based interpolator and decimator // +// This is the double buffer variant // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_INTHALFBANDFILTER_DBFI_H +#define INCLUDE_INTHALFBANDFILTER_DBFI_H + +#include +#include "dsp/dsptypes.h" +#include "dsp/hbfiltertraits.h" +#include "export.h" + +template +class SDRBASE_API IntHalfbandFilterDBFI { +public: + IntHalfbandFilterDBFI(); + + // downsample by 2, return center part of original spectrum + bool workDecimateCenter(Sample* sample) + { + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sample->real(), (FixReal) sample->imag()); + + switch(m_state) + { + case 0: + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + default: + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, return center part of original spectrum - double buffer variant + bool workInterpolateCenterZeroStuffing(Sample* sampleIn, Sample *SampleOut) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) 0, (FixReal) 0); + // save result + doFIR(SampleOut); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + // save result + doFIR(SampleOut); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateCenter(Sample* sampleIn, Sample *SampleOut) + { + switch(m_state) + { + case 0: + // return the middle peak + SampleOut->setReal(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); + SampleOut->setImag(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(SampleOut); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + // downsample by 2, return lower half of original spectrum + bool workDecimateLowerHalf(Sample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) -sample->imag(), (FixReal) sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) -sample->real(), (FixReal) -sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 2; + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sample->imag(), (FixReal) -sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 3; + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sample->real(), (FixReal) sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateLowerHalf(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // return the middle peak + sampleOut->setReal(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // imag + sampleOut->setImag(-m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + case 1: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 2; // next state + return true; // tell caller we consumed the sample + + case 2: + // return the middle peak + sampleOut->setReal(-m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag + sampleOut->setImag(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // real + m_state = 3; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + // upsample by 2, from lower half of original spectrum - double buffer variant + bool workInterpolateLowerHalfZeroStuffing(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + // downsample by 2, return upper half of original spectrum + bool workDecimateUpperHalf(Sample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sample->imag(), (FixReal) -sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) -sample->real(), (FixReal) -sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 2; + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) -sample->imag(), (FixReal) sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 3; + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sample->real(), (FixReal) sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateUpperHalf(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // return the middle peak + sampleOut->setReal(-m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag + sampleOut->setImag(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // + real + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + case 1: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 2; // next state + return true; // tell caller we consumed the sample + + case 2: + // return the middle peak + sampleOut->setReal(m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // + imag + sampleOut->setImag(-m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real + m_state = 3; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = sampleIn->real(); + m_samplesDB[m_ptr][1] = sampleIn->imag(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + // upsample by 2, move original spectrum to upper half - double buffer variant + bool workInterpolateUpperHalfZeroStuffing(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSampleFixReal((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + void myDecimate(const Sample* sample1, Sample* sample2) + { + storeSampleFixReal((FixReal) sample1->real(), (FixReal) sample1->imag()); + advancePointer(); + + storeSampleFixReal((FixReal) sample2->real(), (FixReal) sample2->imag()); + doFIR(sample2); + advancePointer(); + } + + void myDecimate(AccuType x1, AccuType y1, AccuType *x2, AccuType *y2) + { + storeSampleAccu(x1, y1); + advancePointer(); + + storeSampleAccu(*x2, *y2); + doFIRAccu(x2, y2); + advancePointer(); + } + + /** Simple zero stuffing and filter */ + void myInterpolateZeroStuffing(Sample* sample1, Sample* sample2) + { + storeSampleFixReal((FixReal) sample1->real(), (FixReal) sample1->imag()); + doFIR(sample1); + advancePointer(); + + storeSampleFixReal((FixReal) 0, (FixReal) 0); + doFIR(sample2); + advancePointer(); + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + void myInterpolate(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2) + { + // insert sample into ring double buffer + m_samplesDB[m_ptr][0] = *x1; + m_samplesDB[m_ptr][1] = *y1; + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = *x1; + m_samplesDB[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = *y1; + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + // first output sample calculated with the middle peak + *x1 = m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]; + *y1 = m_samplesDB[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]; + + // second sample calculated with the filter + doInterpolateFIR(x2, y2); + } + +protected: + SampleType m_samplesDB[2*(HBFIRFilterTraits::hbOrder - 1)][2]; // double buffer technique + int m_ptr; + int m_size; + int m_state; + + void storeSampleFixReal(const FixReal& sampleI, const FixReal& sampleQ) + { + m_samplesDB[m_ptr][0] = sampleI / SDR_RX_SCALED; + m_samplesDB[m_ptr][1] = sampleQ / SDR_RX_SCALED; + m_samplesDB[m_ptr + m_size][0] = sampleI / SDR_RX_SCALED; + m_samplesDB[m_ptr + m_size][1] = sampleQ / SDR_RX_SCALED; + } + + void storeSampleAccu(AccuType x, AccuType y) + { + m_samplesDB[m_ptr][0] = x; + m_samplesDB[m_ptr][1] = y; + m_samplesDB[m_ptr + m_size][0] = x; + m_samplesDB[m_ptr + m_size][1] = y; + } + + void advancePointer() + { + m_ptr = m_ptr + 1 < m_size ? m_ptr + 1: 0; + } + + void doFIR(Sample* sample) + { + int a = m_ptr + m_size; // tip pointer + int b = m_ptr + 1; // tail pointer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffsF[i]; + a -= 2; + b += 2; + } + + iAcc += m_samplesDB[b-1][0] << (HBFIRFilterTraits::hbShift - 1); + qAcc += m_samplesDB[b-1][1] << (HBFIRFilterTraits::hbShift - 1); + + sample->setReal(iAcc*SDR_RX_SCALED); + sample->setImag(qAcc*SDR_RX_SCALED); + } + + void doFIRAccu(AccuType *x, AccuType *y) + { + int a = m_ptr + m_size; // tip pointer + int b = m_ptr + 1; // tail pointer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffsF[i]; + a -= 2; + b += 2; + } + + iAcc += m_samplesDB[b-1][0] / 2.0; + qAcc += m_samplesDB[b-1][1] / 2.0; + + *x = iAcc; // HB_SHIFT incorrect do not loose the gained bit + *y = qAcc; + } + + void doInterpolateFIR(Sample* sample) + { + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffsF[i]; + a++; + b--; + } + + sample->setReal(iAcc * SDR_RX_SCALED); + sample->setImag(qAcc * SDR_RX_SCALED); + } + + void doInterpolateFIR(qint32 *x, qint32 *y) + { + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + AccuType iAcc = 0; + AccuType qAcc = 0; + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samplesDB[a][0] + m_samplesDB[b][0]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_samplesDB[a][1] + m_samplesDB[b][1]) * HBFIRFilterTraits::hbCoeffsF[i]; + a++; + b--; + } + + *x = iAcc * SDR_RX_SCALED; + *y = qAcc * SDR_RX_SCALED; + } +}; + +template +IntHalfbandFilterDBFI::IntHalfbandFilterDBFI() +{ + m_size = HBFIRFilterTraits::hbOrder - 1; + + for (int i = 0; i < m_size; i++) + { + m_samplesDB[i][0] = 0; + m_samplesDB[i][1] = 0; + } + + m_ptr = 0; + m_state = 0; +} + +#endif // INCLUDE_INTHALFBANDFILTER_DBF_H diff --git a/sdrbase/dsp/inthalfbandfiltereo.h b/sdrbase/dsp/inthalfbandfiltereo.h new file mode 100644 index 000000000..a5cff5344 --- /dev/null +++ b/sdrbase/dsp/inthalfbandfiltereo.h @@ -0,0 +1,934 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// Integer half-band FIR based interpolator and decimator // +// This is the even/odd double buffer variant. Really useful only when SIMD is // +// used // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_INTHALFBANDFILTEREO_H_ +#define SDRBASE_DSP_INTHALFBANDFILTEREO_H_ + +#include +#include +#include "dsp/dsptypes.h" +#include "dsp/hbfiltertraits.h" +#include "export.h" + +template +class SDRBASE_API IntHalfbandFilterEO { +public: + IntHalfbandFilterEO(); + + // downsample by 2, return center part of original spectrum + bool workDecimateCenter(Sample* sample) + { + // insert sample into ring-buffer + storeSample((FixReal) sample->real(), (FixReal) sample->imag()); + + switch(m_state) + { + case 0: + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + default: + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, return center part of original spectrum - double buffer variant + bool workInterpolateCenterZeroStuffing(Sample* sampleIn, Sample *SampleOut) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + // save result + doFIR(SampleOut); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + // save result + doFIR(SampleOut); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateCenter(Sample* sampleIn, Sample *SampleOut) + { + switch(m_state) + { + case 0: + // return the middle peak + SampleOut->setReal(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); + SampleOut->setImag(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(SampleOut); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + bool workDecimateCenter(int32_t *x, int32_t *y) + { + // insert sample into ring-buffer + storeSample32(*x, *y); + + switch(m_state) + { + case 0: + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + default: + // save result + doFIR(x, y); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + // downsample by 2, return lower half of original spectrum + bool workDecimateLowerHalf(Sample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) -sample->imag(), (FixReal) sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) -sample->real(), (FixReal) -sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 2; + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) sample->imag(), (FixReal) -sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 3; + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sample->real(), (FixReal) sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, from lower half of original spectrum - double buffer variant + bool workInterpolateLowerHalfZeroStuffing(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateLowerHalf(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // return the middle peak + sampleOut->setReal(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // imag + sampleOut->setImag(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + case 1: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 2; // next state + return true; // tell caller we consumed the sample + + case 2: + // return the middle peak + sampleOut->setReal(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag + sampleOut->setImag(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // real + m_state = 3; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + // downsample by 2, return upper half of original spectrum + bool workDecimateUpperHalf(Sample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) sample->imag(), (FixReal) -sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) -sample->real(), (FixReal) -sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 2; + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) -sample->imag(), (FixReal) sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 3; + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sample->real(), (FixReal) sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, move original spectrum to upper half - double buffer variant + bool workInterpolateUpperHalfZeroStuffing(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateUpperHalf(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // return the middle peak + sampleOut->setReal(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag + sampleOut->setImag(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // + real + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + case 1: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 2; // next state + return true; // tell caller we consumed the sample + + case 2: + // return the middle peak + sampleOut->setReal(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // + imag + sampleOut->setImag(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real + m_state = 3; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + void myDecimate(const Sample* sample1, Sample* sample2) + { + storeSample((FixReal) sample1->real(), (FixReal) sample1->imag()); + advancePointer(); + + storeSample((FixReal) sample2->real(), (FixReal) sample2->imag()); + doFIR(sample2); + advancePointer(); + } + + void myDecimate(int32_t x1, int32_t y1, int32_t *x2, int32_t *y2) + { + storeSample32(x1, y1); + advancePointer(); + + storeSample32(*x2, *y2); + doFIR(x2, y2); + advancePointer(); + } + + void myDecimateCen(int32_t x1, int32_t y1, int32_t *x2, int32_t *y2, int32_t x3, int32_t y3, int32_t *x4, int32_t *y4) + { + storeSample32(x1, y1); + advancePointer(); + + storeSample32(*x2, *y2); + doFIR(x2, y2); + advancePointer(); + + storeSample32(x3, y3); + advancePointer(); + + storeSample32(*x4, *y4); + doFIR(x4, y4); + advancePointer(); + } + + void myDecimateCen(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, int32_t x4, int32_t y4, int32_t *out) + { + storeSample32(x1, y1); + advancePointer(); + + storeSample32(x2, y2); + doFIR(&out[0], &out[1]); + advancePointer(); + + storeSample32(x3, y3); + advancePointer(); + + storeSample32(x4, y4); + doFIR(&out[2], &out[3]); + advancePointer(); + } + + void myDecimateCen(int32_t *in, int32_t *out) + { + storeSample32(in[0], in[1]); + advancePointer(); + + storeSample32(in[2], in[3]); + doFIR(&out[0], &out[1]); + advancePointer(); + + storeSample32(in[4], in[5]); + advancePointer(); + + storeSample32(in[6], in[7]); + doFIR(&out[2], &out[3]); + advancePointer(); + } + + void myDecimateInf(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, int32_t x4, int32_t y4, int32_t *out) + { + storeSample32(-y1, x1); + advancePointer(); + + storeSample32(-x2, -y2); + doFIR(&out[0], &out[1]); + advancePointer(); + + storeSample32(y3, -x3); + advancePointer(); + + storeSample32(x4, y4); + doFIR(&out[2], &out[3]); + advancePointer(); + } + + void myDecimateInf(int32_t *in, int32_t *out) + { + storeSample32(-in[1], in[0]); + advancePointer(); + + storeSample32(-in[2], -in[3]); + doFIR(&out[0], &out[1]); + advancePointer(); + + storeSample32(in[5], -in[4]); + advancePointer(); + + storeSample32(in[6], in[7]); + doFIR(&out[2], &out[3]); + advancePointer(); + } + + void myDecimateSup(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, int32_t x4, int32_t y4, int32_t *out) + { + storeSample32(y1, -x1); + advancePointer(); + + storeSample32(-x2, -y2); + doFIR(&out[0], &out[1]); + advancePointer(); + + storeSample32(-y3, x3); + advancePointer(); + + storeSample32(x4, y4); + doFIR(&out[2], &out[3]); + advancePointer(); + } + + void myDecimateSup(int32_t *in, int32_t *out) + { + storeSample32(in[1], -in[0]); + advancePointer(); + + storeSample32(-in[2], -in[3]); + doFIR(&out[0], &out[1]); + advancePointer(); + + storeSample32(-in[5], in[4]); + advancePointer(); + + storeSample32(in[6], in[7]); + doFIR(&out[2], &out[3]); + advancePointer(); + } + + /** Simple zero stuffing and filter */ + void myInterpolateZeroStuffing(Sample* sample1, Sample* sample2) + { + storeSample((FixReal) sample1->real(), (FixReal) sample1->imag()); + doFIR(sample1); + advancePointer(); + + storeSample((FixReal) 0, (FixReal) 0); + doFIR(sample2); + advancePointer(); + } + + /** Simple zero stuffing and filter */ + void myInterpolateZeroStuffing(int32_t *x1, int32_t *y1, int32_t *x2, int32_t *y2) + { + storeSample32(*x1, *y1); + doFIR(x1, y1); + advancePointer(); + + storeSample32(0, 0); + doFIR(x2, y2); + advancePointer(); + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + void myInterpolate(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2) + { + // insert sample into ring double buffer + m_samples[m_ptr][0] = *x1; + m_samples[m_ptr][1] = *y1; + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = *x1; + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = *y1; + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + // first output sample calculated with the middle peak + *x1 = m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]; + *y1 = m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]; + + // second sample calculated with the filter + doInterpolateFIR(x2, y2); + } + +protected: + EOStorageType m_even[2][HBFIRFilterTraits::hbOrder]; + EOStorageType m_odd[2][HBFIRFilterTraits::hbOrder]; + int32_t m_samples[HBFIRFilterTraits::hbOrder][2]; + + int m_ptr; + int m_size; + int m_state; + + void storeSample(const FixReal& sampleI, const FixReal& sampleQ) + { + if ((m_ptr % 2) == 0) + { + m_even[0][m_ptr/2] = sampleI; + m_even[1][m_ptr/2] = sampleQ; + m_even[0][m_ptr/2 + m_size] = sampleI; + m_even[1][m_ptr/2 + m_size] = sampleQ; + } + else + { + m_odd[0][m_ptr/2] = sampleI; + m_odd[1][m_ptr/2] = sampleQ; + m_odd[0][m_ptr/2 + m_size] = sampleI; + m_odd[1][m_ptr/2 + m_size] = sampleQ; + } + } + + void storeSample32(int32_t x, int32_t y) + { + if ((m_ptr % 2) == 0) + { + m_even[0][m_ptr/2] = x; + m_even[1][m_ptr/2] = y; + m_even[0][m_ptr/2 + m_size] = x; + m_even[1][m_ptr/2 + m_size] = y; + } + else + { + m_odd[0][m_ptr/2] = x; + m_odd[1][m_ptr/2] = y; + m_odd[0][m_ptr/2 + m_size] = x; + m_odd[1][m_ptr/2 + m_size] = y; + } + } + + void advancePointer() + { + m_ptr = m_ptr + 1 < 2*m_size ? m_ptr + 1: 0; + } + + void doFIR(Sample* sample) + { + AccuType iAcc = 0; + AccuType qAcc = 0; + + int a = m_ptr/2 + m_size; // tip pointer + int b = m_ptr/2 + 1; // tail pointer + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + if ((m_ptr % 2) == 0) + { + iAcc += (m_even[0][a] + m_even[0][b]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_even[1][a] + m_even[1][b]) * HBFIRFilterTraits::hbCoeffs[i]; + } + else + { + iAcc += (m_odd[0][a] + m_odd[0][b]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_odd[1][a] + m_odd[1][b]) * HBFIRFilterTraits::hbCoeffs[i]; + } + + a -= 1; + b += 1; + } + + if ((m_ptr % 2) == 0) + { + iAcc += ((int32_t)m_odd[0][m_ptr/2 + m_size/2]) << (HBFIRFilterTraits::hbShift - 1); + qAcc += ((int32_t)m_odd[1][m_ptr/2 + m_size/2]) << (HBFIRFilterTraits::hbShift - 1); + } + else + { + iAcc += ((int32_t)m_even[0][m_ptr/2 + m_size/2 + 1]) << (HBFIRFilterTraits::hbShift - 1); + qAcc += ((int32_t)m_even[1][m_ptr/2 + m_size/2 + 1]) << (HBFIRFilterTraits::hbShift - 1); + } + + sample->setReal(iAcc >> (HBFIRFilterTraits::hbShift -1)); + sample->setImag(qAcc >> (HBFIRFilterTraits::hbShift -1)); + } + + void doFIR(int32_t *x, int32_t *y) + { + AccuType iAcc = 0; + AccuType qAcc = 0; + + int a = m_ptr/2 + m_size; // tip pointer + int b = m_ptr/2 + 1; // tail pointer + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + if ((m_ptr % 2) == 0) + { + iAcc += (m_even[0][a] + m_even[0][b]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_even[1][a] + m_even[1][b]) * HBFIRFilterTraits::hbCoeffs[i]; + } + else + { + iAcc += (m_odd[0][a] + m_odd[0][b]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_odd[1][a] + m_odd[1][b]) * HBFIRFilterTraits::hbCoeffs[i]; + } + + a -= 1; + b += 1; + } + + if ((m_ptr % 2) == 0) + { + iAcc += ((int32_t)m_odd[0][m_ptr/2 + m_size/2]) << (HBFIRFilterTraits::hbShift - 1); + qAcc += ((int32_t)m_odd[1][m_ptr/2 + m_size/2]) << (HBFIRFilterTraits::hbShift - 1); + } + else + { + iAcc += ((int32_t)m_even[0][m_ptr/2 + m_size/2 + 1]) << (HBFIRFilterTraits::hbShift - 1); + qAcc += ((int32_t)m_even[1][m_ptr/2 + m_size/2 + 1]) << (HBFIRFilterTraits::hbShift - 1); + } + + *x = iAcc >> (HBFIRFilterTraits::hbShift -1); // HB_SHIFT incorrect do not loose the gained bit + *y = qAcc >> (HBFIRFilterTraits::hbShift -1); + } + + void doInterpolateFIR(Sample* sample) + { + AccuType iAcc = 0; + AccuType qAcc = 0; + + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samples[a][0] + m_samples[b][0]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_samples[a][1] + m_samples[b][1]) * HBFIRFilterTraits::hbCoeffs[i]; + a++; + b--; + } + + sample->setReal(iAcc >> (HBFIRFilterTraits::hbShift -1)); + sample->setImag(qAcc >> (HBFIRFilterTraits::hbShift -1)); + } + + void doInterpolateFIR(qint32 *x, qint32 *y) + { + AccuType iAcc = 0; + AccuType qAcc = 0; + + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samples[a][0] + m_samples[b][0]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_samples[a][1] + m_samples[b][1]) * HBFIRFilterTraits::hbCoeffs[i]; + a++; + b--; + } + + *x = iAcc >> (HBFIRFilterTraits::hbShift -1); + *y = qAcc >> (HBFIRFilterTraits::hbShift -1); + } +}; + +template +IntHalfbandFilterEO::IntHalfbandFilterEO() +{ + m_size = HBFIRFilterTraits::hbOrder/2; + + for (int i = 0; i < 2*m_size; i++) + { + m_even[0][i] = 0; + m_even[1][i] = 0; + m_odd[0][i] = 0; + m_odd[1][i] = 0; + m_samples[i][0] = 0; + m_samples[i][1] = 0; + } + + m_ptr = 0; + m_state = 0; +} + +#endif /* SDRBASE_DSP_INTHALFBANDFILTEREO_H_ */ diff --git a/sdrbase/dsp/inthalfbandfiltereo1.h b/sdrbase/dsp/inthalfbandfiltereo1.h index b69c1da3a..21bc112de 100644 --- a/sdrbase/dsp/inthalfbandfiltereo1.h +++ b/sdrbase/dsp/inthalfbandfiltereo1.h @@ -19,18 +19,18 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef SDRBASE_DSP_INTHALFBANDFILTEREO_H_ -#define SDRBASE_DSP_INTHALFBANDFILTEREO_H_ +#ifndef SDRBASE_DSP_INTHALFBANDFILTEREO1_H_ +#define SDRBASE_DSP_INTHALFBANDFILTEREO1_H_ #include #include #include "dsp/dsptypes.h" #include "dsp/hbfiltertraits.h" -#include "dsp/inthalfbandfiltereo1i.h" -#include "util/export.h" +//#include "dsp/inthalfbandfiltereo1i.h" +#include "export.h" template -class SDRANGEL_API IntHalfbandFilterEO1 { +class SDRBASE_API IntHalfbandFilterEO1 { public: IntHalfbandFilterEO1(); @@ -681,15 +681,15 @@ protected: int32_t iAcc = 0; int32_t qAcc = 0; -#ifdef USE_SSE4_1 - IntHalfbandFilterEO1Intrisics::work( - m_ptr, - m_even, - m_odd, - iAcc, - qAcc - ); -#else +//#if defined(USE_SSE4_1) && !defined(NO_DSP_SIMD) +// IntHalfbandFilterEO1Intrisics::work( +// m_ptr, +// m_even, +// m_odd, +// iAcc, +// qAcc +// ); +//#else int a = m_ptr/2 + m_size; // tip pointer int b = m_ptr/2 + 1; // tail pointer @@ -709,7 +709,7 @@ protected: a -= 1; b += 1; } -#endif +//#endif if ((m_ptr % 2) == 0) { @@ -731,15 +731,15 @@ protected: int32_t iAcc = 0; int32_t qAcc = 0; -#ifdef USE_SSE4_1 - IntHalfbandFilterEO1Intrisics::work( - m_ptr, - m_even, - m_odd, - iAcc, - qAcc - ); -#else +//#if defined(USE_SSE4_1) && !defined(NO_DSP_SIMD) +// IntHalfbandFilterEO1Intrisics::work( +// m_ptr, +// m_even, +// m_odd, +// iAcc, +// qAcc +// ); +//#else int a = m_ptr/2 + m_size; // tip pointer int b = m_ptr/2 + 1; // tail pointer @@ -759,7 +759,7 @@ protected: a -= 1; b += 1; } -#endif +//#endif if ((m_ptr % 2) == 0) { iAcc += ((int32_t)m_odd[0][m_ptr/2 + m_size/2]) << (HBFIRFilterTraits::hbShift - 1); @@ -837,4 +837,4 @@ IntHalfbandFilterEO1::IntHalfbandFilterEO1() m_state = 0; } -#endif /* SDRBASE_DSP_INTHALFBANDFILTEREO_H_ */ +#endif /* SDRBASE_DSP_INTHALFBANDFILTEREO1_H_ */ diff --git a/sdrbase/dsp/inthalfbandfiltereo2.h b/sdrbase/dsp/inthalfbandfiltereo2.h new file mode 100644 index 000000000..be513419b --- /dev/null +++ b/sdrbase/dsp/inthalfbandfiltereo2.h @@ -0,0 +1,832 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// Integer half-band FIR based interpolator and decimator // +// This is the even/odd double buffer variant. Really useful only when SIMD is // +// used // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_INTHALFBANDFILTEREO2_H_ +#define SDRBASE_DSP_INTHALFBANDFILTEREO2_H_ + +#include +#include +#include "dsp/dsptypes.h" +#include "dsp/hbfiltertraits.h" +#include "export.h" + +template +class SDRBASE_API IntHalfbandFilterEO2 { +public: + IntHalfbandFilterEO2(); + + // downsample by 2, return center part of original spectrum + bool workDecimateCenter(Sample* sample) + { + // insert sample into ring-buffer + storeSample((FixReal) sample->real(), (FixReal) sample->imag()); + + switch(m_state) + { + case 0: + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + default: + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, return center part of original spectrum - double buffer variant + bool workInterpolateCenterZeroStuffing(Sample* sampleIn, Sample *SampleOut) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + // save result + doFIR(SampleOut); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + // save result + doFIR(SampleOut); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateCenter(Sample* sampleIn, Sample *SampleOut) + { + switch(m_state) + { + case 0: + // return the middle peak + SampleOut->setReal(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); + SampleOut->setImag(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(SampleOut); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + bool workDecimateCenter(int32_t *x, int32_t *y) + { + // insert sample into ring-buffer + storeSample32(*x, *y); + + switch(m_state) + { + case 0: + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + default: + // save result + doFIR(x, y); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + // downsample by 2, return lower half of original spectrum + bool workDecimateLowerHalf(Sample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) -sample->imag(), (FixReal) sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) -sample->real(), (FixReal) -sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 2; + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) sample->imag(), (FixReal) -sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 3; + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sample->real(), (FixReal) sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, from lower half of original spectrum - double buffer variant + bool workInterpolateLowerHalfZeroStuffing(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateLowerHalf(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // return the middle peak + sampleOut->setReal(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // imag + sampleOut->setImag(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + case 1: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 2; // next state + return true; // tell caller we consumed the sample + + case 2: + // return the middle peak + sampleOut->setReal(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag + sampleOut->setImag(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // real + m_state = 3; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + // downsample by 2, return upper half of original spectrum + bool workDecimateUpperHalf(Sample* sample) + { + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) sample->imag(), (FixReal) -sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) -sample->real(), (FixReal) -sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 2; + // tell caller we have a new sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) -sample->imag(), (FixReal) sample->real()); + // advance write-pointer + advancePointer(); + // next state + m_state = 3; + // tell caller we don't have a new sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sample->real(), (FixReal) sample->imag()); + // save result + doFIR(sample); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + // upsample by 2, move original spectrum to upper half - double buffer variant + bool workInterpolateUpperHalfZeroStuffing(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(-s.imag()); + sampleOut->setImag(s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 1; + + // tell caller we didn't consume the sample + return false; + + case 1: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 2; + + // tell caller we consumed the sample + return true; + + case 2: + // insert sample into ring-buffer + storeSample((FixReal) 0, (FixReal) 0); + + // save result + doFIR(&s); + sampleOut->setReal(s.imag()); + sampleOut->setImag(-s.real()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 3; + + // tell caller we didn't consume the sample + return false; + + default: + // insert sample into ring-buffer + storeSample((FixReal) sampleIn->real(), (FixReal) sampleIn->imag()); + + // save result + doFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // advance write-pointer + advancePointer(); + + // next state + m_state = 0; + + // tell caller we consumed the sample + return true; + } + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + bool workInterpolateUpperHalf(Sample* sampleIn, Sample *sampleOut) + { + Sample s; + + switch(m_state) + { + case 0: + // return the middle peak + sampleOut->setReal(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // - imag + sampleOut->setImag(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // + real + m_state = 1; // next state + return false; // tell caller we didn't consume the sample + + case 1: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(-s.real()); + sampleOut->setImag(-s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 2; // next state + return true; // tell caller we consumed the sample + + case 2: + // return the middle peak + sampleOut->setReal(m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]); // + imag + sampleOut->setImag(-m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]); // - real + m_state = 3; // next state + return false; // tell caller we didn't consume the sample + + default: + // calculate with non null samples + doInterpolateFIR(&s); + sampleOut->setReal(s.real()); + sampleOut->setImag(s.imag()); + + // insert sample into ring double buffer + m_samples[m_ptr][0] = sampleIn->real(); + m_samples[m_ptr][1] = sampleIn->imag(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = sampleIn->real(); + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = sampleIn->imag(); + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + m_state = 0; // next state + return true; // tell caller we consumed the sample + } + } + + void myDecimate(const Sample* sample1, Sample* sample2) + { + storeSample((FixReal) sample1->real(), (FixReal) sample1->imag()); + advancePointer(); + + storeSample((FixReal) sample2->real(), (FixReal) sample2->imag()); + doFIR(sample2); + advancePointer(); + } + + void myDecimate(int32_t x1, int32_t y1, int32_t *x2, int32_t *y2) + { + storeSample32(x1, y1); + advancePointer(); + + storeSample32(*x2, *y2); + doFIR(x2, y2); + advancePointer(); + } + + /** Simple zero stuffing and filter */ + void myInterpolateZeroStuffing(Sample* sample1, Sample* sample2) + { + storeSample((FixReal) sample1->real(), (FixReal) sample1->imag()); + doFIR(sample1); + advancePointer(); + + storeSample((FixReal) 0, (FixReal) 0); + doFIR(sample2); + advancePointer(); + } + + /** Simple zero stuffing and filter */ + void myInterpolateZeroStuffing(int32_t *x1, int32_t *y1, int32_t *x2, int32_t *y2) + { + storeSample32(*x1, *y1); + doFIR(x1, y1); + advancePointer(); + + storeSample32(0, 0); + doFIR(x2, y2); + advancePointer(); + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + void myInterpolate(qint32 *x1, qint32 *y1, qint32 *x2, qint32 *y2) + { + // insert sample into ring double buffer + m_samples[m_ptr][0] = *x1; + m_samples[m_ptr][1] = *y1; + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = *x1; + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = *y1; + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + // first output sample calculated with the middle peak + *x1 = m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]; + *y1 = m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]; + + // second sample calculated with the filter + doInterpolateFIR(x2, y2); + } + +protected: + qint64 m_even[2][HBFIRFilterTraits::hbOrder]; // double buffer technique + qint64 m_odd[2][HBFIRFilterTraits::hbOrder]; // double buffer technique + int32_t m_samples[HBFIRFilterTraits::hbOrder][2]; // double buffer technique + + int m_ptr; + int m_size; + int m_state; + + void storeSample(const FixReal& sampleI, const FixReal& sampleQ) + { + if ((m_ptr % 2) == 0) + { + m_even[0][m_ptr/2] = sampleI; + m_even[1][m_ptr/2] = sampleQ; + m_even[0][m_ptr/2 + m_size] = sampleI; + m_even[1][m_ptr/2 + m_size] = sampleQ; + } + else + { + m_odd[0][m_ptr/2] = sampleI; + m_odd[1][m_ptr/2] = sampleQ; + m_odd[0][m_ptr/2 + m_size] = sampleI; + m_odd[1][m_ptr/2 + m_size] = sampleQ; + } + } + + void storeSample32(int32_t x, int32_t y) + { + if ((m_ptr % 2) == 0) + { + m_even[0][m_ptr/2] = x; + m_even[1][m_ptr/2] = y; + m_even[0][m_ptr/2 + m_size] = x; + m_even[1][m_ptr/2 + m_size] = y; + } + else + { + m_odd[0][m_ptr/2] = x; + m_odd[1][m_ptr/2] = y; + m_odd[0][m_ptr/2 + m_size] = x; + m_odd[1][m_ptr/2 + m_size] = y; + } + } + + void storeSample64(qint64 x, qint64 y) + { + if ((m_ptr % 2) == 0) + { + m_even[0][m_ptr/2] = x; + m_even[1][m_ptr/2] = y; + m_even[0][m_ptr/2 + m_size] = x; + m_even[1][m_ptr/2 + m_size] = y; + } + else + { + m_odd[0][m_ptr/2] = x; + m_odd[1][m_ptr/2] = y; + m_odd[0][m_ptr/2 + m_size] = x; + m_odd[1][m_ptr/2 + m_size] = y; + } + } + + void advancePointer() + { + m_ptr = m_ptr + 1 < 2*m_size ? m_ptr + 1: 0; + } + + void doFIR(Sample* sample) + { + qint64 iAcc = 0; + qint64 qAcc = 0; + + int a = m_ptr/2 + m_size; // tip pointer + int b = m_ptr/2 + 1; // tail pointer + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + if ((m_ptr % 2) == 0) + { + iAcc += (m_even[0][a] + m_even[0][b]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_even[1][a] + m_even[1][b]) * HBFIRFilterTraits::hbCoeffs[i]; + } + else + { + iAcc += (m_odd[0][a] + m_odd[0][b]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_odd[1][a] + m_odd[1][b]) * HBFIRFilterTraits::hbCoeffs[i]; + } + + a -= 1; + b += 1; + } + + if ((m_ptr % 2) == 0) + { + iAcc += ((int32_t)m_odd[0][m_ptr/2 + m_size/2]) << (HBFIRFilterTraits::hbShift - 1); + qAcc += ((int32_t)m_odd[1][m_ptr/2 + m_size/2]) << (HBFIRFilterTraits::hbShift - 1); + } + else + { + iAcc += ((int32_t)m_even[0][m_ptr/2 + m_size/2 + 1]) << (HBFIRFilterTraits::hbShift - 1); + qAcc += ((int32_t)m_even[1][m_ptr/2 + m_size/2 + 1]) << (HBFIRFilterTraits::hbShift - 1); + } + + sample->setReal(iAcc >> (HBFIRFilterTraits::hbShift -1)); + sample->setImag(qAcc >> (HBFIRFilterTraits::hbShift -1)); + } + + void doFIR(int32_t *x, int32_t *y) + { + qint64 iAcc = 0; + qint64 qAcc = 0; + + int a = m_ptr/2 + m_size; // tip pointer + int b = m_ptr/2 + 1; // tail pointer + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + if ((m_ptr % 2) == 0) + { + iAcc += (m_even[0][a] + m_even[0][b]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_even[1][a] + m_even[1][b]) * HBFIRFilterTraits::hbCoeffs[i]; + } + else + { + iAcc += (m_odd[0][a] + m_odd[0][b]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_odd[1][a] + m_odd[1][b]) * HBFIRFilterTraits::hbCoeffs[i]; + } + + a -= 1; + b += 1; + } + + if ((m_ptr % 2) == 0) + { + iAcc += ((int32_t)m_odd[0][m_ptr/2 + m_size/2]) << (HBFIRFilterTraits::hbShift - 1); + qAcc += ((int32_t)m_odd[1][m_ptr/2 + m_size/2]) << (HBFIRFilterTraits::hbShift - 1); + } + else + { + iAcc += ((int32_t)m_even[0][m_ptr/2 + m_size/2 + 1]) << (HBFIRFilterTraits::hbShift - 1); + qAcc += ((int32_t)m_even[1][m_ptr/2 + m_size/2 + 1]) << (HBFIRFilterTraits::hbShift - 1); + } + + *x = iAcc >> (HBFIRFilterTraits::hbShift -1); // HB_SHIFT incorrect do not loose the gained bit + *y = qAcc >> (HBFIRFilterTraits::hbShift -1); + } + + void doInterpolateFIR(Sample* sample) + { + qint64 iAcc = 0; + qint64 qAcc = 0; + + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samples[a][0] + m_samples[b][0]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_samples[a][1] + m_samples[b][1]) * HBFIRFilterTraits::hbCoeffs[i]; + a++; + b--; + } + + sample->setReal(iAcc >> (HBFIRFilterTraits::hbShift -1)); + sample->setImag(qAcc >> (HBFIRFilterTraits::hbShift -1)); + } + + void doInterpolateFIR(qint32 *x, qint32 *y) + { + qint64 iAcc = 0; + qint64 qAcc = 0; + + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samples[a][0] + m_samples[b][0]) * HBFIRFilterTraits::hbCoeffs[i]; + qAcc += (m_samples[a][1] + m_samples[b][1]) * HBFIRFilterTraits::hbCoeffs[i]; + a++; + b--; + } + + *x = iAcc >> (HBFIRFilterTraits::hbShift -1); + *y = qAcc >> (HBFIRFilterTraits::hbShift -1); + } +}; + +template +IntHalfbandFilterEO2::IntHalfbandFilterEO2() +{ + m_size = HBFIRFilterTraits::hbOrder/2; + + for (int i = 0; i < 2*m_size; i++) + { + m_even[0][i] = 0; + m_even[1][i] = 0; + m_odd[0][i] = 0; + m_odd[1][i] = 0; + m_samples[i][0] = 0; + m_samples[i][1] = 0; + } + + m_ptr = 0; + m_state = 0; +} + +#endif /* SDRBASE_DSP_INTHALFBANDFILTEREO2_H_ */ diff --git a/sdrbase/dsp/inthalfbandfiltereof.h b/sdrbase/dsp/inthalfbandfiltereof.h new file mode 100644 index 000000000..09f188e0a --- /dev/null +++ b/sdrbase/dsp/inthalfbandfiltereof.h @@ -0,0 +1,231 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// Integer half-band FIR based interpolator and decimator // +// This is the even/odd double buffer variant. Really useful only when SIMD is // +// used // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_INTHALFBANDFILTEREOF_H_ +#define SDRBASE_DSP_INTHALFBANDFILTEREOF_H_ + +#include +#include +#include "dsp/dsptypes.h" +#include "dsp/hbfiltertraits.h" +#include "export.h" + +template +class SDRBASE_API IntHalfbandFilterEOF { +public: + IntHalfbandFilterEOF(); + + bool workDecimateCenter(float *x, float *y) + { + // insert sample into ring-buffer + storeSample(*x, *y); + + switch(m_state) + { + case 0: + // advance write-pointer + advancePointer(); + // next state + m_state = 1; + // tell caller we don't have a new sample + return false; + + default: + // save result + doFIR(x, y); + // advance write-pointer + advancePointer(); + // next state + m_state = 0; + // tell caller we have a new sample + return true; + } + } + + void myDecimate(float x1, float y1, float *x2, float *y2) + { + storeSample(x1, y1); + advancePointer(); + + storeSample(*x2, *y2); + doFIR(x2, y2); + advancePointer(); + } + + /** Simple zero stuffing and filter */ + void myInterpolateZeroStuffing(float *x1, float *y1, float *x2, float *y2) + { + storeSample(*x1, *y1); + doFIR(x1, y1); + advancePointer(); + + storeSample(0, 0); + doFIR(x2, y2); + advancePointer(); + } + + /** Optimized upsampler by 2 not calculating FIR with inserted null samples */ + void myInterpolate(float *x1, float *y1, float *x2, float *y2) + { + // insert sample into ring double buffer + m_samples[m_ptr][0] = *x1; + m_samples[m_ptr][1] = *y1; + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][0] = *x1; + m_samples[m_ptr + HBFIRFilterTraits::hbOrder/2][1] = *y1; + + // advance pointer + if (m_ptr < (HBFIRFilterTraits::hbOrder/2) - 1) { + m_ptr++; + } else { + m_ptr = 0; + } + + // first output sample calculated with the middle peak + *x1 = m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][0]; + *y1 = m_samples[m_ptr + (HBFIRFilterTraits::hbOrder/4) - 1][1]; + + // second sample calculated with the filter + doInterpolateFIR(x2, y2); + } + +protected: + float m_even[2][HBFIRFilterTraits::hbOrder]; // double buffer technique + float m_odd[2][HBFIRFilterTraits::hbOrder]; // double buffer technique + float m_samples[HBFIRFilterTraits::hbOrder][2]; // double buffer technique + + int m_ptr; + int m_size; + int m_state; + + void storeSample(float x, float y) + { + if ((m_ptr % 2) == 0) + { + m_even[0][m_ptr/2] = x; + m_even[1][m_ptr/2] = y; + m_even[0][m_ptr/2 + m_size] = x; + m_even[1][m_ptr/2 + m_size] = y; + } + else + { + m_odd[0][m_ptr/2] = x; + m_odd[1][m_ptr/2] = y; + m_odd[0][m_ptr/2 + m_size] = x; + m_odd[1][m_ptr/2 + m_size] = y; + } + } + + void advancePointer() + { + m_ptr = m_ptr + 1 < 2*m_size ? m_ptr + 1: 0; + } + + void doFIR(float *x, float *y) + { + float iAcc = 0; + float qAcc = 0; + +//#if defined(USE_SSE4_1) && !defined(NO_DSP_SIMD) +// IntHalfbandFilterEO1Intrisics::work( +// m_ptr, +// m_even, +// m_odd, +// iAcc, +// qAcc +// ); +//#else + int a = m_ptr/2 + m_size; // tip pointer + int b = m_ptr/2 + 1; // tail pointer + + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + if ((m_ptr % 2) == 0) + { + iAcc += (m_even[0][a] + m_even[0][b]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_even[1][a] + m_even[1][b]) * HBFIRFilterTraits::hbCoeffsF[i]; + } + else + { + iAcc += (m_odd[0][a] + m_odd[0][b]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_odd[1][a] + m_odd[1][b]) * HBFIRFilterTraits::hbCoeffsF[i]; + } + + a -= 1; + b += 1; + } +//#endif + if ((m_ptr % 2) == 0) + { + iAcc += m_odd[0][m_ptr/2 + m_size/2] * 0.5f; + qAcc += m_odd[1][m_ptr/2 + m_size/2] * 0.5f; + } + else + { + iAcc += m_even[0][m_ptr/2 + m_size/2 + 1] * 0.5f; + qAcc += m_even[1][m_ptr/2 + m_size/2 + 1] * 0.5f; + } + + *x = iAcc; // HB_SHIFT incorrect do not loose the gained bit + *y = qAcc; + } + + void doInterpolateFIR(float *x, float *y) + { + qint32 iAcc = 0; + qint32 qAcc = 0; + + qint16 a = m_ptr; + qint16 b = m_ptr + (HBFIRFilterTraits::hbOrder / 2) - 1; + + // go through samples in buffer + for (int i = 0; i < HBFIRFilterTraits::hbOrder / 4; i++) + { + iAcc += (m_samples[a][0] + m_samples[b][0]) * HBFIRFilterTraits::hbCoeffsF[i]; + qAcc += (m_samples[a][1] + m_samples[b][1]) * HBFIRFilterTraits::hbCoeffsF[i]; + a++; + b--; + } + + *x = iAcc * SDR_RX_SCALED; + *y = qAcc * SDR_RX_SCALED; + } +}; + +template +IntHalfbandFilterEOF::IntHalfbandFilterEOF() +{ + m_size = HBFIRFilterTraits::hbOrder/2; + + for (int i = 0; i < 2*m_size; i++) + { + m_even[0][i] = 0.0f; + m_even[1][i] = 0.0f; + m_odd[0][i] = 0.0f; + m_odd[1][i] = 0.0f; + m_samples[i][0] = 0.0f; + m_samples[i][1] = 0.0f; + } + + m_ptr = 0; + m_state = 0; +} + +#endif /* SDRBASE_DSP_INTHALFBANDFILTEREOF_H_ */ diff --git a/sdrbase/dsp/inthalfbandfilterst.h b/sdrbase/dsp/inthalfbandfilterst.h index 94336a4fd..b940675e0 100644 --- a/sdrbase/dsp/inthalfbandfilterst.h +++ b/sdrbase/dsp/inthalfbandfilterst.h @@ -25,7 +25,7 @@ #include "dsp/dsptypes.h" #include "dsp/hbfiltertraits.h" #include "dsp/inthalfbandfiltersti.h" -#include "util/export.h" +#include "export.h" template class SDRANGEL_API IntHalfbandFilterST { diff --git a/sdrbase/dsp/kissengine.h b/sdrbase/dsp/kissengine.h index ad8e53c32..0b8faba38 100644 --- a/sdrbase/dsp/kissengine.h +++ b/sdrbase/dsp/kissengine.h @@ -3,8 +3,9 @@ #include "dsp/fftengine.h" #include "dsp/kissfft.h" +#include "export.h" -class KissEngine : public FFTEngine { +class SDRBASE_API KissEngine : public FFTEngine { public: void configure(int n, bool inverse); void transform(); diff --git a/sdrbase/dsp/movingaverage.h b/sdrbase/dsp/movingaverage.h index 0f932ab84..ef2ed5a37 100644 --- a/sdrbase/dsp/movingaverage.h +++ b/sdrbase/dsp/movingaverage.h @@ -8,7 +8,7 @@ template class MovingAverage { public: - MovingAverage(int historySize, Type initial) + MovingAverage(int historySize, Type initial) : m_index(0) { resize(historySize, initial); } diff --git a/sdrbase/dsp/nco.h b/sdrbase/dsp/nco.h index 58739fcbc..cea728650 100644 --- a/sdrbase/dsp/nco.h +++ b/sdrbase/dsp/nco.h @@ -19,9 +19,9 @@ #define INCLUDE_NCO_H #include "dsp/dsptypes.h" -#include "util/export.h" +#include "export.h" -class SDRANGEL_API NCO { +class SDRBASE_API NCO { private: enum { TableSize = (1 << 12), diff --git a/sdrbase/dsp/ncof.h b/sdrbase/dsp/ncof.h index 57fa4258f..eee258242 100644 --- a/sdrbase/dsp/ncof.h +++ b/sdrbase/dsp/ncof.h @@ -18,9 +18,9 @@ #define INCLUDE_NCOF_H #include "dsp/dsptypes.h" -#include "util/export.h" +#include "export.h" -class SDRANGEL_API NCOF { +class SDRBASE_API NCOF { private: enum { TableSize = (1 << 12), diff --git a/sdrbase/dsp/nullsink.cpp b/sdrbase/dsp/nullsink.cpp index 3537d4ff9..592735116 100644 --- a/sdrbase/dsp/nullsink.cpp +++ b/sdrbase/dsp/nullsink.cpp @@ -16,7 +16,7 @@ bool NullSink::init(const Message& message __attribute__((unused))) return false; } -void NullSink::feed(SampleVector::const_iterator begin __attribute__((unused)), SampleVector::const_iterator end __attribute__((unused)), bool positiveOnly __attribute__((unused))) +void NullSink::feed(const SampleVector::const_iterator& begin __attribute__((unused)), const SampleVector::const_iterator& end __attribute__((unused)), bool positiveOnly __attribute__((unused))) { } diff --git a/sdrbase/dsp/nullsink.h b/sdrbase/dsp/nullsink.h index aef7240eb..2e26af373 100644 --- a/sdrbase/dsp/nullsink.h +++ b/sdrbase/dsp/nullsink.h @@ -2,18 +2,18 @@ #define INCLUDE_NULLSINK_H #include -#include "util/export.h" +#include "export.h" class Message; -class SDRANGEL_API NullSink : public BasebandSampleSink { +class SDRBASE_API NullSink : public BasebandSampleSink { public: NullSink(); virtual ~NullSink(); virtual bool init(const Message& cmd); - virtual void feed(SampleVector::const_iterator begin, SampleVector::const_iterator end, bool positiveOnly); + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); virtual void start(); virtual void stop(); virtual bool handleMessage(const Message& message); diff --git a/sdrbase/dsp/phaselock.cpp b/sdrbase/dsp/phaselock.cpp index ce6c57a97..86de423ae 100644 --- a/sdrbase/dsp/phaselock.cpp +++ b/sdrbase/dsp/phaselock.cpp @@ -262,61 +262,85 @@ void PhaseLock::process(const Real& sample_in, Real *samples_out) processPhase(samples_out); // Multiply locked tone with input. - Real x = sample_in; - Real phasor_i = m_psin * x; - Real phasor_q = m_pcos * x; + Real phasor_i = m_psin * sample_in; + Real phasor_q = m_pcos * sample_in; - // Run IQ phase error through low-pass filter. - phasor_i = m_phasor_b0 * phasor_i - - m_phasor_a1 * m_phasor_i1 - - m_phasor_a2 * m_phasor_i2; - phasor_q = m_phasor_b0 * phasor_q - - m_phasor_a1 * m_phasor_q1 - - m_phasor_a2 * m_phasor_q2; - m_phasor_i2 = m_phasor_i1; - m_phasor_i1 = phasor_i; - m_phasor_q2 = m_phasor_q1; - m_phasor_q1 = phasor_q; + // Actual PLL + process_phasor(phasor_i, phasor_q); +} - // Convert I/Q ratio to estimate of phase error. - Real phase_err; +void PhaseLock::process(const Real& real_in, const Real& imag_in, Real *samples_out) +{ + m_pps_events.clear(); + + // Generate locked pilot tone. + m_psin = sin(m_phase); + m_pcos = cos(m_phase); + + // Generate output + processPhase(samples_out); + + // Multiply locked tone with input. + Real phasor_i = m_psin * real_in - m_pcos * imag_in; + Real phasor_q = m_pcos * real_in + m_psin * imag_in; + + // Actual PLL + process_phasor(phasor_i, phasor_q); +} + +void PhaseLock::process_phasor(Real& phasor_i, Real& phasor_q) +{ + // Run IQ phase error through low-pass filter. + phasor_i = m_phasor_b0 * phasor_i + - m_phasor_a1 * m_phasor_i1 + - m_phasor_a2 * m_phasor_i2; + phasor_q = m_phasor_b0 * phasor_q + - m_phasor_a1 * m_phasor_q1 + - m_phasor_a2 * m_phasor_q2; + m_phasor_i2 = m_phasor_i1; + m_phasor_i1 = phasor_i; + m_phasor_q2 = m_phasor_q1; + m_phasor_q1 = phasor_q; + + // Convert I/Q ratio to estimate of phase error. + Real phase_err; if (phasor_i > std::abs(phasor_q)) { - // We are within +/- 45 degrees from lock. - // Use simple linear approximation of arctan. - phase_err = phasor_q / phasor_i; - } else if (phasor_q > 0) { - // We are lagging more than 45 degrees behind the input. - phase_err = 1; - } else { - // We are more than 45 degrees ahead of the input. - phase_err = -1; - } + // We are within +/- 45 degrees from lock. + // Use simple linear approximation of arctan. + phase_err = phasor_q / phasor_i; + } else if (phasor_q > 0) { + // We are lagging more than 45 degrees behind the input. + phase_err = 1; + } else { + // We are more than 45 degrees ahead of the input. + phase_err = -1; + } - // Detect pilot level (conservative). - // m_pilot_level = std::min(m_pilot_level, phasor_i); - m_pilot_level = phasor_i; + // Detect pilot level (conservative). + // m_pilot_level = std::min(m_pilot_level, phasor_i); + m_pilot_level = phasor_i; - // Run phase error through loop filter and update frequency estimate. - m_freq += m_loopfilter_b0 * phase_err - + m_loopfilter_b1 * m_loopfilter_x1; - m_loopfilter_x1 = phase_err; + // Run phase error through loop filter and update frequency estimate. + m_freq += m_loopfilter_b0 * phase_err + + m_loopfilter_b1 * m_loopfilter_x1; + m_loopfilter_x1 = phase_err; - // Limit frequency to allowable range. - m_freq = std::max(m_minfreq, std::min(m_maxfreq, m_freq)); + // Limit frequency to allowable range. + m_freq = std::max(m_minfreq, std::min(m_maxfreq, m_freq)); - // Update locked phase. - m_phase += m_freq; - if (m_phase > 2.0 * M_PI) - { - m_phase -= 2.0 * M_PI; - m_pilot_periods++; + // Update locked phase. + m_phase += m_freq; + if (m_phase > 2.0 * M_PI) + { + m_phase -= 2.0 * M_PI; + m_pilot_periods++; - // Generate pulse-per-second. - if (m_pilot_periods == pilot_frequency) - { - m_pilot_periods = 0; - } - } + // Generate pulse-per-second. + if (m_pilot_periods == pilot_frequency) + { + m_pilot_periods = 0; + } + } // Update lock status. if (2 * m_pilot_level > m_minsignal) @@ -328,7 +352,7 @@ void PhaseLock::process(const Real& sample_in, Real *samples_out) } else { - m_lock_cnt = 0; + m_lock_cnt = 0; } // Drop PPS events when pilot not locked. diff --git a/sdrbase/dsp/phaselock.h b/sdrbase/dsp/phaselock.h index 8547a4f82..ab1b2af03 100644 --- a/sdrbase/dsp/phaselock.h +++ b/sdrbase/dsp/phaselock.h @@ -17,9 +17,10 @@ #include #include "dsp/dsptypes.h" +#include "export.h" /** Phase-locked loop mainly for broadcadt FM stereo pilot. */ -class PhaseLock +class SDRBASE_API PhaseLock { public: @@ -72,6 +73,7 @@ public: * This is the in flow version */ void process(const Real& sample_in, Real *samples_out); + void process(const Real& real_in, const Real& imag_in, Real *samples_out); /** Return true if the phase-locked loop is locked. */ bool locked() const @@ -110,6 +112,8 @@ private: quint64 m_pps_cnt; quint64 m_sample_cnt; std::vector m_pps_events; + + void process_phasor(Real& phasor_i, Real& phasor_q); }; class SimplePhaseLock : public PhaseLock diff --git a/sdrbase/dsp/phaselockcomplex.cpp b/sdrbase/dsp/phaselockcomplex.cpp new file mode 100644 index 000000000..0c8d604f3 --- /dev/null +++ b/sdrbase/dsp/phaselockcomplex.cpp @@ -0,0 +1,225 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// See: http://liquidsdr.org/blog/pll-howto/ // +// Fixed filter registers saturation // +// Added order for PSK locking. This brilliant idea actually comes from this // +// post: https://www.dsprelated.com/showthread/comp.dsp/36356-1.php // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include "phaselockcomplex.h" + +PhaseLockComplex::PhaseLockComplex() : + m_a1(1.0), + m_a2(1.0), + m_b0(1.0), + m_b1(1.0), + m_b2(1.0), + m_v0(0.0), + m_v1(0.0), + m_v2(0.0), + m_deltaPhi(0.0), + m_phiHat(0.0), + m_phiHatPrev(0.0), + m_y(1.0, 0.0), + m_p(1.0, 0.0), + m_yRe(1.0), + m_yIm(0.0), + m_freq(0.0), + m_freqPrev(0.0), + m_freqTest(0.0), + m_lockCount(0), + m_lockFreq(0.026f), + m_pskOrder(1), + m_lockTime(480), + m_lockTimeCount(0) +{ +} + +void PhaseLockComplex::computeCoefficients(Real wn, Real zeta, Real K) +{ + double t1 = K/(wn*wn); // + double t2 = 2*zeta/wn - 1/K; // + + double b0 = 2*K*(1.+t2/2.0f); + double b1 = 2*K*2.; + double b2 = 2*K*(1.-t2/2.0f); + + double a0 = 1 + t1/2.0f; + double a1 = -t1; + double a2 = -1 + t1/2.0f; + + qDebug("PhaseLockComplex::computeCoefficients: b_raw: %f %f %f", b0, b1, b2); + qDebug("PhaseLockComplex::computeCoefficients: a_raw: %f %f %f", a0, a1, a2); + + m_b0 = b0 / a0; + m_b1 = b1 / a0; + m_b2 = b2 / a0; + + // a0 = 1.0 is implied + m_a1 = a1 / a0; + m_a2 = a2 / a0; + + qDebug("PhaseLockComplex::computeCoefficients: b: %f %f %f", m_b0, m_b1, m_b2); + qDebug("PhaseLockComplex::computeCoefficients: a: 1.0 %f %f", m_a1, m_a2); + + reset(); +} + +void PhaseLockComplex::setPskOrder(unsigned int order) +{ + m_pskOrder = order > 0 ? order : 1; + reset(); +} + +void PhaseLockComplex::setSampleRate(unsigned int sampleRate) +{ + m_lockTime = sampleRate / 100; // 10ms for order 1 + m_lockFreq = (2.0*M_PI*(m_pskOrder > 1 ? 6.0 : 1.0)) / sampleRate; // +/- 6 Hz frequency swing + reset(); +} + +void PhaseLockComplex::reset() +{ + // reset filter accumulators and phase + m_v0 = 0.0f; + m_v1 = 0.0f; + m_v2 = 0.0f; + m_deltaPhi = 0.0f; + m_phiHat = 0.0f; + m_phiHatPrev = 0.0f; + m_y.real(1.0); + m_y.imag(0.0); + m_p.real(1.0); + m_p.imag(0.0); + m_yRe = 1.0f; + m_yIm = 0.0f; + m_freq = 0.0f; + m_freqPrev = 0.0f; + m_freqTest = 0.0f; + m_lockCount = 0; + m_lockTimeCount = 0; +} + +void PhaseLockComplex::feed(float re, float im) +{ + m_yRe = cos(m_phiHat); + m_yIm = sin(m_phiHat); + m_y.real(m_yRe); + m_y.imag(m_yIm); + std::complex x(re, im); + m_deltaPhi = std::arg(x * std::conj(m_y)); + + // bring phase 0 on any of the PSK symbols + if (m_pskOrder > 1) { + m_deltaPhi = normalizeAngle(m_pskOrder*m_deltaPhi); + } + + // advance buffer + m_v2 = m_v1; // shift center register to upper register + m_v1 = m_v0; // shift lower register to center register + + // compute new lower register + m_v0 = m_deltaPhi - m_v1*m_a1 - m_v2*m_a2; + + // compute new output + m_phiHat = m_v0*m_b0 + m_v1*m_b1 + m_v2*m_b2; + + // prevent saturation + if (m_phiHat > 2.0*M_PI) + { + m_v0 *= (m_phiHat - 2.0*M_PI) / m_phiHat; + m_v1 *= (m_phiHat - 2.0*M_PI) / m_phiHat; + m_v2 *= (m_phiHat - 2.0*M_PI) / m_phiHat; + m_phiHat -= 2.0*M_PI; + } + + if (m_phiHat < -2.0*M_PI) + { + m_v0 *= (m_phiHat + 2.0*M_PI) / m_phiHat; + m_v1 *= (m_phiHat + 2.0*M_PI) / m_phiHat; + m_v2 *= (m_phiHat + 2.0*M_PI) / m_phiHat; + m_phiHat += 2.0*M_PI; + } + + // lock and frequency estimation + if (m_pskOrder > 1) + { + float dPhi = normalizeAngle(m_phiHat - m_phiHatPrev); + m_freq = m_expAvg.feed(dPhi); + + if (m_lockTimeCount < m_lockTime-1) + { + m_lockTimeCount++; + } + else + { + float dF = m_freq - m_freqTest; + + if ((dF > -m_lockFreq) && (dF < m_lockFreq)) + { + if (m_lockCount < 20) { + m_lockCount++; + } + } + else + { + if (m_lockCount > 0) { + m_lockCount--; + } + } + + m_freqTest = m_freq; + m_lockTimeCount = 0; + } + + m_phiHatPrev = m_phiHat; + } + else + { + m_freqTest = normalizeAngle(m_phiHat - m_phiHatPrev); + m_freq = m_expAvg.feed(m_freqTest); + + float dFreq = m_freqTest - m_freqPrev; + + if ((dFreq > -0.01) && (dFreq < 0.01)) + { + if (m_lockCount < (m_lockTime-1)) { // [0..479] + m_lockCount++; + } + } + else + { + m_lockCount = 0; + } + + m_phiHatPrev = m_phiHat; + m_freqPrev = m_freqTest; + } +} + +float PhaseLockComplex::normalizeAngle(float angle) +{ + while (angle <= -M_PI) { + angle += 2.0*M_PI; + } + while (angle > M_PI) { + angle -= 2.0*M_PI; + } + return angle; +} diff --git a/sdrbase/dsp/phaselockcomplex.h b/sdrbase/dsp/phaselockcomplex.h new file mode 100644 index 000000000..247181e74 --- /dev/null +++ b/sdrbase/dsp/phaselockcomplex.h @@ -0,0 +1,110 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4EXB // +// written by Edouard Griffiths // +// // +// See: http://liquidsdr.org/blog/pll-howto/ // +// Fixed filter registers saturation // +// Added order for PSK locking. This brilliant idea actually comes from this // +// post: https://www.dsprelated.com/showthread/comp.dsp/36356-1.php // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_PHASELOCKCOMPLEX_H_ +#define SDRBASE_DSP_PHASELOCKCOMPLEX_H_ + +#include "dsp/dsptypes.h" +#include "export.h" + +/** General purpose Phase-locked loop using complex analytic signal input. */ +class SDRBASE_API PhaseLockComplex +{ +public: + PhaseLockComplex(); + /** Compute loop filter parameters (active PI design) + * \param wn PLL bandwidth relative to Nyquist frequency + * \param zeta PLL damping factor + * \param K PLL loop gain + * */ + void computeCoefficients(Real wn, Real zeta, Real K); + /** Set the PSK order for the phase comparator + * \param order 0,1: no PSK (CW), 2: BPSK, 4: QPSK, 8: 8-PSK, ... use powers of two for real cases + */ + void setPskOrder(unsigned int order); + /** Set sample rate information only for frequency and lock condition calculation */ + void setSampleRate(unsigned int sampleRate); + void reset(); + /** Feed PLL with a new signa sample */ + void feed(float re, float im); + const std::complex& getComplex() const { return m_y; } + float getReal() const { return m_yRe; } + float getImag() const { return m_yIm; } + bool locked() const { return m_pskOrder > 1 ? m_lockCount > 10 : m_lockCount > m_lockTime-2; } + float getFreq() const { return m_freq; } + float getDeltaPhi() const { return m_deltaPhi; } + float getPhiHat() const { return m_phiHat; } + +private: + class ExpAvg + { + public: + ExpAvg() : m_a0(0.999), m_a1(0.001), m_y1(0.0f) + {} + void setAlpha(const float& alpha) + { + m_a0 = alpha; + m_a1 = 1.0 - alpha; + } + float feed(const float& x) + { + float y = m_a1*x + m_a0*m_y1; + m_y1 = y; + return y; + } + private: + float m_a0; //!< alpha + float m_a1; //!< 1 - alpha + float m_y1; + }; + + /** Normalize angle in radians into the [-pi,+pi] region */ + static float normalizeAngle(float angle); + + // a0 = 1 is implied + float m_a1; + float m_a2; + float m_b0; + float m_b1; + float m_b2; + float m_v0; + float m_v1; + float m_v2; + float m_deltaPhi; + float m_phiHat; + float m_phiHatPrev; + std::complex m_y; + std::complex m_p; + float m_yRe; + float m_yIm; + float m_freq; + float m_freqPrev; + float m_freqTest; + int m_lockCount; + float m_lockFreq; + unsigned int m_pskOrder; + int m_lockTime; + int m_lockTimeCount; + ExpAvg m_expAvg; +}; + +#endif /* SDRBASE_DSP_PHASELOCKCOMPLEX_H_ */ diff --git a/sdrbase/dsp/projector.cpp b/sdrbase/dsp/projector.cpp new file mode 100644 index 000000000..887be37c0 --- /dev/null +++ b/sdrbase/dsp/projector.cpp @@ -0,0 +1,213 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 F4EXB // +// written by Edouard Griffiths // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include "projector.h" + +Projector::Projector(ProjectionType projectionType) : + m_projectionType(projectionType), + m_prevArg(0.0f), + m_cache(0), + m_cacheMaster(true) +{ +} + +Projector::~Projector() +{ +} + +Real Projector::run(const Sample& s) +{ + Real v; + + if ((m_cache) && !m_cacheMaster) { + return m_cache[(int) m_projectionType]; + } + else + { + switch (m_projectionType) + { + case ProjectionImag: + v = s.m_imag / SDR_RX_SCALEF; + break; + case ProjectionMagLin: + { + Real re = s.m_real / SDR_RX_SCALEF; + Real im = s.m_imag / SDR_RX_SCALEF; + Real magsq = re*re + im*im; + v = std::sqrt(magsq); + } + break; + case ProjectionMagSq: + { + Real re = s.m_real / SDR_RX_SCALEF; + Real im = s.m_imag / SDR_RX_SCALEF; + v = re*re + im*im; + } + break; + case ProjectionMagDB: + { + Real re = s.m_real / SDR_RX_SCALEF; + Real im = s.m_imag / SDR_RX_SCALEF; + Real magsq = re*re + im*im; + v = log10f(magsq) * 10.0f; + } + break; + case ProjectionPhase: + v = std::atan2((float) s.m_imag, (float) s.m_real) / M_PI; + break; + case ProjectionDPhase: + { + Real curArg = std::atan2((float) s.m_imag, (float) s.m_real); + Real dPhi = (curArg - m_prevArg) / M_PI; + m_prevArg = curArg; + + if (dPhi < -1.0f) { + dPhi += 2.0f; + } else if (dPhi > 1.0f) { + dPhi -= 2.0f; + } + + v = dPhi; + } + break; + case ProjectionBPSK: + { + Real arg = std::atan2((float) s.m_imag, (float) s.m_real); + v = normalizeAngle(2*arg) / (2.0*M_PI); // generic estimation around 0 + // mapping on 2 symbols + if (arg < -M_PI/2) { + v -= 1.0/2; + } else if (arg < M_PI/2) { + v += 1.0/2; + } else if (arg < M_PI) { + v -= 1.0/2; + } + } + break; + case ProjectionQPSK: + { + Real arg = std::atan2((float) s.m_imag, (float) s.m_real); + v = normalizeAngle(4*arg) / (4.0*M_PI); // generic estimation around 0 + // mapping on 4 symbols + if (arg < -3*M_PI/4) { + v -= 3.0/4; + } else if (arg < -M_PI/4) { + v -= 1.0/4; + } else if (arg < M_PI/4) { + v += 1.0/4; + } else if (arg < 3*M_PI/4) { + v += 3.0/4; + } else if (arg < M_PI) { + v -= 3.0/4; + } + } + break; + case Projection8PSK: + { + Real arg = std::atan2((float) s.m_imag, (float) s.m_real); + v = normalizeAngle(8*arg) / (8.0*M_PI); // generic estimation around 0 + // mapping on 8 symbols + if (arg < -7*M_PI/8) { + v -= 7.0/8; + } else if (arg < -5*M_PI/8) { + v -= 5.0/8; + } else if (arg < -3*M_PI/8) { + v -= 3.0/8; + } else if (arg < -M_PI/8) { + v -= 1.0/8; + } else if (arg < M_PI/8) { + v += 1.0/8; + } else if (arg < 3*M_PI/8) { + v += 3.0/8; + } else if (arg < 5*M_PI/8) { + v += 5.0/8; + } else if (arg < 7*M_PI/8) { + v += 7.0/8; + } else if (arg < M_PI) { + v -= 7.0/8; + } + } + break; + case Projection16PSK: + { + Real arg = std::atan2((float) s.m_imag, (float) s.m_real); + v = normalizeAngle(16*arg) / (16.0*M_PI); // generic estimation around 0 + // mapping on 16 symbols + if (arg < -15*M_PI/16) { + v -= 15.0/16; + } else if (arg < -13*M_PI/16) { + v -= 13.0/6; + } else if (arg < -11*M_PI/16) { + v -= 11.0/16; + } else if (arg < -9*M_PI/16) { + v -= 9.0/16; + } else if (arg < -7*M_PI/16) { + v -= 7.0/16; + } else if (arg < -5*M_PI/16) { + v -= 5.0/16; + } else if (arg < -3*M_PI/16) { + v -= 3.0/16; + } else if (arg < -M_PI/16) { + v -= 1.0/16; + } else if (arg < M_PI/16) { + v += 1.0/16; + } else if (arg < 3.0*M_PI/16) { + v += 3.0/16; + } else if (arg < 5.0*M_PI/16) { + v += 5.0/16; + } else if (arg < 7.0*M_PI/16) { + v += 7.0/16; + } else if (arg < 9.0*M_PI/16) { + v += 9.0/16; + } else if (arg < 11.0*M_PI/16) { + v += 11.0/16; + } else if (arg < 13.0*M_PI/16) { + v += 13.0/16; + } else if (arg < 15.0*M_PI/16) { + v += 15.0/16; + } else if (arg < M_PI) { + v -= 15.0/16; + } + } + break; + case ProjectionReal: + default: + v = s.m_real / SDR_RX_SCALEF; + break; + } + + if (m_cache) { + m_cache[(int) m_projectionType] = v; + } + + return v; + } +} + +Real Projector::normalizeAngle(Real angle) +{ + while (angle <= -M_PI) { + angle += 2.0*M_PI; + } + while (angle > M_PI) { + angle -= 2.0*M_PI; + } + return angle; +} + + diff --git a/sdrbase/dsp/projector.h b/sdrbase/dsp/projector.h new file mode 100644 index 000000000..3e2f65602 --- /dev/null +++ b/sdrbase/dsp/projector.h @@ -0,0 +1,55 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 F4EXB // +// written by Edouard Griffiths // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "dsptypes.h" + +class Projector +{ +public: + enum ProjectionType + { + ProjectionReal = 0, //!< Extract real part + ProjectionImag, //!< Extract imaginary part + ProjectionMagLin, //!< Calculate linear magnitude or modulus + ProjectionMagSq, //!< Calculate linear squared magnitude or power + ProjectionMagDB, //!< Calculate logarithmic (dB) of squared magnitude + ProjectionPhase, //!< Calculate phase + ProjectionDPhase, //!< Calculate phase derivative i.e. instantaneous frequency scaled to sample rate + ProjectionBPSK, //!< Phase comparator BPSK evaluation + ProjectionQPSK, //!< Phase comparator QPSK evaluation + Projection8PSK, //!< Phase comparator 8-PSK evaluation + Projection16PSK, //!< Phase comparator 16-PSK evaluation + nbProjectionTypes //!< Gives the number of projections in the enum + }; + + Projector(ProjectionType projectionType); + ~Projector(); + + ProjectionType getProjectionType() const { return m_projectionType; } + void settProjectionType(ProjectionType projectionType) { m_projectionType = projectionType; } + void setCache(Real *cache) { m_cache = cache; } + void setCacheMaster(bool cacheMaster) { m_cacheMaster = cacheMaster; } + + Real run(const Sample& s); + +private: + static Real normalizeAngle(Real angle); + ProjectionType m_projectionType; + Real m_prevArg; + Real *m_cache; + bool m_cacheMaster; +}; diff --git a/sdrbase/dsp/recursivefilters.cpp b/sdrbase/dsp/recursivefilters.cpp index 2749bfab2..b63385e07 100644 --- a/sdrbase/dsp/recursivefilters.cpp +++ b/sdrbase/dsp/recursivefilters.cpp @@ -18,11 +18,12 @@ #include "recursivefilters.h" #undef M_PI -#define M_PI 3.14159265358979323846 +#define M_PI 3.14159265358979323846 SecondOrderRecursiveFilter::SecondOrderRecursiveFilter(float samplingFrequency, float centerFrequency, float r) : m_r(r), - m_frequencyRatio(centerFrequency/samplingFrequency) + m_frequencyRatio(centerFrequency/samplingFrequency), + m_f(cos(2.0*M_PI*(m_frequencyRatio))) { init(); } @@ -33,6 +34,7 @@ SecondOrderRecursiveFilter::~SecondOrderRecursiveFilter() void SecondOrderRecursiveFilter::setFrequencies(float samplingFrequency, float centerFrequency) { m_frequencyRatio = centerFrequency / samplingFrequency; + m_f = cos(2.0*M_PI*m_frequencyRatio); init(); } @@ -44,7 +46,7 @@ void SecondOrderRecursiveFilter::setR(float r) short SecondOrderRecursiveFilter::run(short sample) { - m_v[0] = ((1.0f - m_r) * (float) sample) + (2.0f * m_r * cos(2.0*M_PI*m_frequencyRatio) * m_v[1]) - (m_r * m_r * m_v[2]); + m_v[0] = ((1.0f - m_r) * (float) sample) + (2.0f * m_r * m_f * m_v[1]) - (m_r * m_r * m_v[2]); float y = m_v[0] - m_v[2]; m_v[2] = m_v[1]; m_v[1] = m_v[0]; @@ -54,7 +56,7 @@ short SecondOrderRecursiveFilter::run(short sample) float SecondOrderRecursiveFilter::run(float sample) { - m_v[0] = ((1.0f - m_r) * sample) + (2.0f * m_r * cos(2.0*M_PI*m_frequencyRatio) * m_v[1]) - (m_r * m_r * m_v[2]); + m_v[0] = ((1.0f - m_r) * sample) + (2.0f * m_r * m_f * m_v[1]) - (m_r * m_r * m_v[2]); float y = m_v[0] - m_v[2]; m_v[2] = m_v[1]; m_v[1] = m_v[0]; diff --git a/sdrbase/dsp/recursivefilters.h b/sdrbase/dsp/recursivefilters.h index 26f6577f8..cd17b6dea 100644 --- a/sdrbase/dsp/recursivefilters.h +++ b/sdrbase/dsp/recursivefilters.h @@ -17,11 +17,13 @@ #ifndef SDRBASE_DSP_RECURSIVEFILTERS_H_ #define SDRBASE_DSP_RECURSIVEFILTERS_H_ +#include "export.h" + /** * \Brief: This is a second order bandpass filter using recursive method. r is in range ]0..1[ the higher the steeper the filter. * inspired by:http://www.ece.umd.edu/~tretter/commlab/c6713slides/FSKSlides.pdf */ -class SecondOrderRecursiveFilter +class SDRBASE_API SecondOrderRecursiveFilter { public: SecondOrderRecursiveFilter(float samplingFrequency, float centerFrequency, float r); @@ -37,6 +39,7 @@ private: float m_r; float m_frequencyRatio; + float m_f; float m_v[3]; }; diff --git a/sdrbase/dsp/samplesinkfifo.h b/sdrbase/dsp/samplesinkfifo.h index 35fcc027c..1855a6d40 100644 --- a/sdrbase/dsp/samplesinkfifo.h +++ b/sdrbase/dsp/samplesinkfifo.h @@ -22,9 +22,9 @@ #include #include #include "dsp/dsptypes.h" -#include "util/export.h" +#include "export.h" -class SDRANGEL_API SampleSinkFifo : public QObject { +class SDRBASE_API SampleSinkFifo : public QObject { Q_OBJECT private: diff --git a/sdrbase/dsp/samplesinkfifodoublebuffered.h b/sdrbase/dsp/samplesinkfifodoublebuffered.h index 74befb05d..aa37ac0a4 100644 --- a/sdrbase/dsp/samplesinkfifodoublebuffered.h +++ b/sdrbase/dsp/samplesinkfifodoublebuffered.h @@ -21,10 +21,10 @@ #include #include #include -#include "util/export.h" +#include "export.h" #include "dsp/dsptypes.h" -class SDRANGEL_API SampleSinkFifoDoubleBuffered : public QObject { +class SDRBASE_API SampleSinkFifoDoubleBuffered : public QObject { Q_OBJECT public: diff --git a/sdrbase/dsp/samplesourcefifo.h b/sdrbase/dsp/samplesourcefifo.h index f42f9c6b5..5d33eba14 100644 --- a/sdrbase/dsp/samplesourcefifo.h +++ b/sdrbase/dsp/samplesourcefifo.h @@ -21,10 +21,10 @@ #include #include #include -#include "util/export.h" +#include "export.h" #include "dsp/dsptypes.h" -class SDRANGEL_API SampleSourceFifo : public QObject { +class SDRBASE_API SampleSourceFifo : public QObject { Q_OBJECT public: @@ -43,6 +43,18 @@ public: void write(const Sample& sample); //!< write directly - phase 1 + phase 2 + /** returns ratio of off center over buffer size with sign: negative read lags and positive read leads */ + float getRWBalance() const + { + int delta; + if (m_iw > m_ir) { + delta = (m_size/2) - (m_iw - m_ir); + } else { + delta = (m_ir - m_iw) - (m_size/2); + } + return delta / (float) m_size; + } + private: uint32_t m_size; SampleVector m_data; diff --git a/sdrbase/dsp/symsync.cpp b/sdrbase/dsp/symsync.cpp new file mode 100644 index 000000000..837f24543 --- /dev/null +++ b/sdrbase/dsp/symsync.cpp @@ -0,0 +1,95 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// Symbol synchronizer or symbol clock recovery mostly encapsulating // +// liquid-dsp's symsync "object" // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "symsync.h" + +SymbolSynchronizer::SymbolSynchronizer() +{ + // For now use hardcoded values: + // - RRC filter + // - 4 samples per symbol + // - 5 symbols delay filter + // - 0.5 filter excess bandwidth factor + // - 32 filter elements for the internal polyphase filter + m_sync = symsync_crcf_create_rnyquist(LIQUID_FIRFILT_RRC, 4, 5, 0.5f, 32); + // - 0.02 loop filter bandwidth factor + symsync_crcf_set_lf_bw(m_sync, 0.01f); + // - 4 samples per symbol output rate + symsync_crcf_set_output_rate(m_sync, 4); + m_syncSampleCount = 0; +} + +SymbolSynchronizer::~SymbolSynchronizer() +{ + symsync_crcf_destroy(m_sync); +} + +Real SymbolSynchronizer::run(const Sample& s) +{ + unsigned int nn; + Real v = -1.0f; + liquid_float_complex y = (s.m_real / SDR_RX_SCALEF) + (s.m_imag / SDR_RX_SCALEF)*I; + symsync_crcf_execute(m_sync, &y, 1, m_z, &nn); + + for (unsigned int i = 0; i < nn; i++) + { + if (nn != 1) { + qDebug("SymbolSynchronizer::run: %u", nn); + } + + if (m_syncSampleCount % 4 == 0) { + v = 1.0f; + } + + if (m_syncSampleCount < 4095) { + m_syncSampleCount++; + } else { + qDebug("SymbolSynchronizer::run: tau: %f", symsync_crcf_get_tau(m_sync)); + m_syncSampleCount = 0; + } + } + + return v; +} + +liquid_float_complex SymbolSynchronizer::runZ(const Sample& s) +{ + unsigned int nn; + liquid_float_complex y = (s.m_real / SDR_RX_SCALEF) + (s.m_imag / SDR_RX_SCALEF)*I; + symsync_crcf_execute(m_sync, &y, 1, m_z, &nn); + + for (unsigned int i = 0; i < nn; i++) + { + if (nn != 1) { + qDebug("SymbolSynchronizer::run: %u", nn); + } + + if (m_syncSampleCount == 0) { + m_z0 = m_z[i]; + } + + if (m_syncSampleCount < 3) { + m_syncSampleCount++; + } else { + m_syncSampleCount = 0; + } + } + + return m_z0; +} diff --git a/sdrbase/dsp/symsync.h b/sdrbase/dsp/symsync.h new file mode 100644 index 000000000..3c4f5d908 --- /dev/null +++ b/sdrbase/dsp/symsync.h @@ -0,0 +1,38 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// Symbol synchronizer or symbol clock recovery mostly encapsulating // +// liquid-dsp's symsync "object" // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "dsp/dsptypes.h" +#include "liquid.h" +#include + +class SymbolSynchronizer +{ +public: + SymbolSynchronizer(); + ~SymbolSynchronizer(); + + Real run(const Sample& s); + liquid_float_complex runZ(const Sample& s); + +private: + symsync_crcf m_sync; + liquid_float_complex m_z[4+4]; // 4 samples per symbol. One symbol plus extra space + liquid_float_complex m_z0; + int m_syncSampleCount; +}; diff --git a/sdrbase/dsp/threadedbasebandsamplesink.cpp b/sdrbase/dsp/threadedbasebandsamplesink.cpp index 751e2ce19..e646cd7cc 100644 --- a/sdrbase/dsp/threadedbasebandsamplesink.cpp +++ b/sdrbase/dsp/threadedbasebandsamplesink.cpp @@ -76,6 +76,9 @@ ThreadedBasebandSampleSink::ThreadedBasebandSampleSink(BasebandSampleSink* sampl //moveToThread(m_thread); // FIXME: Fixed? the intermediate FIFO should be handled within the sink. Define a new type of sink that is compatible with threading m_basebandSampleSink->moveToThread(m_thread); m_threadedBasebandSampleSinkFifo->moveToThread(m_thread); + BasebandSampleSink::MsgThreadedSink *msg = BasebandSampleSink::MsgThreadedSink::create(m_thread); // inform of the new thread + m_basebandSampleSink->handleMessage(*msg); + delete msg; //m_sampleFifo.moveToThread(m_thread); //connect(&m_sampleFifo, SIGNAL(dataReady()), this, SLOT(handleData())); //m_sampleFifo.setSize(262144); @@ -85,7 +88,11 @@ ThreadedBasebandSampleSink::ThreadedBasebandSampleSink(BasebandSampleSink* sampl ThreadedBasebandSampleSink::~ThreadedBasebandSampleSink() { - delete m_threadedBasebandSampleSinkFifo; // Valgrind memcheck + if (m_thread->isRunning()) { + stop(); + } + + delete m_threadedBasebandSampleSinkFifo; // Valgrind memcheck delete m_thread; } diff --git a/sdrbase/dsp/threadedbasebandsamplesink.h b/sdrbase/dsp/threadedbasebandsamplesink.h index 93368c6bb..0cd72b9a2 100644 --- a/sdrbase/dsp/threadedbasebandsamplesink.h +++ b/sdrbase/dsp/threadedbasebandsamplesink.h @@ -23,7 +23,7 @@ #include "samplesinkfifo.h" #include "util/messagequeue.h" -#include "util/export.h" +#include "export.h" class BasebandSampleSink; class QThread; @@ -32,7 +32,7 @@ class QThread; * Because Qt is a piece of shit this class cannot be a nested protected class of ThreadedSampleSink * So let's make everything public */ -class ThreadedBasebandSampleSinkFifo : public QObject { +class SDRBASE_API ThreadedBasebandSampleSinkFifo : public QObject { Q_OBJECT public: @@ -50,7 +50,7 @@ public slots: /** * This class is a wrapper for SampleSink that runs the SampleSink object in its own thread */ -class SDRANGEL_API ThreadedBasebandSampleSink : public QObject { +class SDRBASE_API ThreadedBasebandSampleSink : public QObject { Q_OBJECT public: @@ -66,6 +66,7 @@ public: void feed(SampleVector::const_iterator begin, SampleVector::const_iterator end, bool positiveOnly); //!< Feed sink with samples QString getSampleSinkObjectName() const; + const QThread *getThread() const { return m_thread; } protected: diff --git a/sdrbase/dsp/threadedbasebandsamplesource.cpp b/sdrbase/dsp/threadedbasebandsamplesource.cpp index 57b8b125a..97d059234 100644 --- a/sdrbase/dsp/threadedbasebandsamplesource.cpp +++ b/sdrbase/dsp/threadedbasebandsamplesource.cpp @@ -36,6 +36,10 @@ ThreadedBasebandSampleSource::ThreadedBasebandSampleSource(BasebandSampleSource* ThreadedBasebandSampleSource::~ThreadedBasebandSampleSource() { + if (m_thread->isRunning()) { + stop(); + } + delete m_thread; } diff --git a/sdrbase/dsp/threadedbasebandsamplesource.h b/sdrbase/dsp/threadedbasebandsamplesource.h index defb1cfb5..0fa838516 100644 --- a/sdrbase/dsp/threadedbasebandsamplesource.h +++ b/sdrbase/dsp/threadedbasebandsamplesource.h @@ -22,7 +22,7 @@ #include "dsp/basebandsamplesource.h" #include "util/messagequeue.h" -#include "util/export.h" +#include "export.h" class BasebandSampleSource; class QThread; @@ -30,7 +30,7 @@ class QThread; /** * This class is a wrapper for BasebandSampleSource that runs the BasebandSampleSource object in its own thread */ -class SDRANGEL_API ThreadedBasebandSampleSource : public QObject { +class SDRBASE_API ThreadedBasebandSampleSource : public QObject { Q_OBJECT public: diff --git a/sdrbase/dsp/upchannelizer.h b/sdrbase/dsp/upchannelizer.h index 3b15f7a96..63698f334 100644 --- a/sdrbase/dsp/upchannelizer.h +++ b/sdrbase/dsp/upchannelizer.h @@ -21,7 +21,7 @@ #include #include #include -#include "util/export.h" +#include "export.h" #include "util/message.h" #ifdef USE_SSE4_1 #include "dsp/inthalfbandfiltereo1.h" @@ -33,10 +33,10 @@ class MessageQueue; -class SDRANGEL_API UpChannelizer : public BasebandSampleSource { +class SDRBASE_API UpChannelizer : public BasebandSampleSource { Q_OBJECT public: - class SDRANGEL_API MsgChannelizerNotification : public Message { + class MsgChannelizerNotification : public Message { MESSAGE_CLASS_DECLARATION public: diff --git a/sdrbase/dsp/wfir.h b/sdrbase/dsp/wfir.h index 981f80a24..d0225cf83 100644 --- a/sdrbase/dsp/wfir.h +++ b/sdrbase/dsp/wfir.h @@ -53,7 +53,9 @@ #ifndef _WFIR_H_ #define _WFIR_H_ -class WFIR +#include "export.h" + +class SDRBASE_API WFIR { public: enum TPassTypeName diff --git a/sdrbase/mainparser.h b/sdrbase/mainparser.h index 3d0c19765..14c5921e0 100644 --- a/sdrbase/mainparser.h +++ b/sdrbase/mainparser.h @@ -21,7 +21,9 @@ #include #include -class MainParser +#include "export.h" + +class SDRBASE_API MainParser { public: MainParser(); diff --git a/sdrbase/plugin/pluginapi.h b/sdrbase/plugin/pluginapi.h index ed7a5ad59..3f9206014 100644 --- a/sdrbase/plugin/pluginapi.h +++ b/sdrbase/plugin/pluginapi.h @@ -4,7 +4,7 @@ #include #include -#include "util/export.h" +#include "export.h" #include "plugin/plugininterface.h" class QString; @@ -13,7 +13,7 @@ class PluginManager; class MessageQueue; class PluginInstanceGUI; -class SDRANGEL_API PluginAPI : public QObject { +class SDRBASE_API PluginAPI : public QObject { Q_OBJECT public: diff --git a/sdrbase/plugin/plugininstancegui.h b/sdrbase/plugin/plugininstancegui.h index 8871c1ec4..22cb59512 100644 --- a/sdrbase/plugin/plugininstancegui.h +++ b/sdrbase/plugin/plugininstancegui.h @@ -5,12 +5,12 @@ #include #include -#include "util/export.h" +#include "export.h" class Message; class MessageQueue; -class SDRANGEL_API PluginInstanceGUI { +class SDRBASE_API PluginInstanceGUI { public: PluginInstanceGUI() { }; virtual ~PluginInstanceGUI() { }; diff --git a/sdrbase/plugin/plugininterface.h b/sdrbase/plugin/plugininterface.h index fdcaccda6..64d73793d 100644 --- a/sdrbase/plugin/plugininterface.h +++ b/sdrbase/plugin/plugininterface.h @@ -4,7 +4,9 @@ #include #include -struct PluginDescriptor { +#include "export.h" + +struct SDRBASE_API PluginDescriptor { // general plugin description const QString displayedName; const QString version; @@ -27,7 +29,7 @@ class BasebandSampleSource; class ChannelSinkAPI; class ChannelSourceAPI; -class PluginInterface { +class SDRBASE_API PluginInterface { public: struct SamplingDevice { diff --git a/sdrbase/plugin/pluginmanager.cpp b/sdrbase/plugin/pluginmanager.cpp index f94f9b481..17574b33e 100644 --- a/sdrbase/plugin/pluginmanager.cpp +++ b/sdrbase/plugin/pluginmanager.cpp @@ -55,9 +55,15 @@ PluginManager::~PluginManager() void PluginManager::loadPlugins(const QString& pluginsSubDir) { - QString applicationDirPath = QCoreApplication::instance()->applicationDirPath(); - QString applicationLibPath = applicationDirPath + "/../lib/" + pluginsSubDir; - QString applicationBuildPath = applicationDirPath + "/" + pluginsSubDir; + loadPluginsPart(pluginsSubDir); + loadPluginsFinal(); +} + +void PluginManager::loadPluginsPart(const QString& pluginsSubDir) +{ + QString applicationDirPath = QCoreApplication::instance()->applicationDirPath(); + QString applicationLibPath = applicationDirPath + "/../lib/" + pluginsSubDir; + QString applicationBuildPath = applicationDirPath + "/" + pluginsSubDir; qDebug() << "PluginManager::loadPlugins: " << qPrintable(applicationLibPath) << "," << qPrintable(applicationBuildPath); QDir pluginsLibDir = QDir(applicationLibPath); @@ -65,16 +71,19 @@ void PluginManager::loadPlugins(const QString& pluginsSubDir) loadPluginsDir(pluginsLibDir); loadPluginsDir(pluginsBuildDir); +} - qSort(m_plugins); +void PluginManager::loadPluginsFinal() +{ + qSort(m_plugins); - for (Plugins::const_iterator it = m_plugins.begin(); it != m_plugins.end(); ++it) - { - it->pluginInterface->initPlugin(&m_pluginAPI); - } + for (Plugins::const_iterator it = m_plugins.begin(); it != m_plugins.end(); ++it) + { + it->pluginInterface->initPlugin(&m_pluginAPI); + } - DeviceEnumerator::instance()->enumerateRxDevices(this); - DeviceEnumerator::instance()->enumerateTxDevices(this); + DeviceEnumerator::instance()->enumerateRxDevices(this); + DeviceEnumerator::instance()->enumerateTxDevices(this); } void PluginManager::registerRxChannel(const QString& channelIdURI, const QString& channelId, PluginInterface* plugin) diff --git a/sdrbase/plugin/pluginmanager.h b/sdrbase/plugin/pluginmanager.h index 442a21719..e313563d9 100644 --- a/sdrbase/plugin/pluginmanager.h +++ b/sdrbase/plugin/pluginmanager.h @@ -9,7 +9,7 @@ #include "plugin/plugininterface.h" #include "plugin/pluginapi.h" -#include "util/export.h" +#include "export.h" class QComboBox; class QPluginLoader; @@ -19,7 +19,7 @@ class MessageQueue; class DeviceSourceAPI; class DeviceSinkAPI; -class SDRANGEL_API PluginManager : public QObject { +class SDRBASE_API PluginManager : public QObject { Q_OBJECT public: @@ -43,6 +43,8 @@ public: PluginAPI *getPluginAPI() { return &m_pluginAPI; } void loadPlugins(const QString& pluginsSubDir); + void loadPluginsPart(const QString& pluginsSubDir); + void loadPluginsFinal(); const Plugins& getPlugins() const { return m_plugins; } // Callbacks from the plugins diff --git a/sdrbase/resources/res.qrc b/sdrbase/resources/res.qrc index 694ecf84a..36e34505d 100644 --- a/sdrbase/resources/res.qrc +++ b/sdrbase/resources/res.qrc @@ -2,13 +2,39 @@ webapi/doc/html2/index.html webapi/doc/swagger/swagger.yaml + webapi/doc/swagger/include/Airspy.yaml + webapi/doc/swagger/include/AirspyHF.yaml + webapi/doc/swagger/include/AMDemod.yaml + webapi/doc/swagger/include/AMMod.yaml + webapi/doc/swagger/include/ATVMod.yaml + webapi/doc/swagger/include/BFMDemod.yaml + webapi/doc/swagger/include/BladeRF1.yaml + webapi/doc/swagger/include/BladeRF2.yaml webapi/doc/swagger/include/CWKeyer.yaml + webapi/doc/swagger/include/DSDDemod.yaml + webapi/doc/swagger/include/FCDPro.yaml + webapi/doc/swagger/include/FCDProPlus.yaml webapi/doc/swagger/include/FileSource.yaml webapi/doc/swagger/include/HackRF.yaml webapi/doc/swagger/include/LimeSdr.yaml webapi/doc/swagger/include/NFMDemod.yaml webapi/doc/swagger/include/NFMMod.yaml + webapi/doc/swagger/include/Perseus.yaml + webapi/doc/swagger/include/PlutoSdr.yaml webapi/doc/swagger/include/RtlSdr.yaml + webapi/doc/swagger/include/DaemonSink.yaml + webapi/doc/swagger/include/DaemonSource.yaml + webapi/doc/swagger/include/SDRDaemonSource.yaml + webapi/doc/swagger/include/SDRDaemonSink.yaml + webapi/doc/swagger/include/SDRPlay.yaml + webapi/doc/swagger/include/SSBDemod.yaml + webapi/doc/swagger/include/SSBMod.yaml + webapi/doc/swagger/include/Structs.yaml + webapi/doc/swagger/include/TestSource.yaml + webapi/doc/swagger/include/UDPSource.yaml + webapi/doc/swagger/include/UDPSink.yaml + webapi/doc/swagger/include/WFMDemod.yaml + webapi/doc/swagger/include/WFMMod.yaml webapi/doc/swagger-ui/swagger-ui.js.map webapi/doc/swagger-ui/swagger-ui.js webapi/doc/swagger-ui/swagger-ui.css.map diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 0c50ca5ef..61c526a87 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -696,74 +696,734 @@ margin-bottom: 20px; @@ -1694,6 +4243,9 @@ margin-bottom: 20px;
  • devicesetChannelPost
  • +
  • + devicesetChannelReportGet +
  • devicesetChannelSettingsGet
  • @@ -1703,9 +4255,15 @@ margin-bottom: 20px;
  • devicesetChannelSettingsPut
  • +
  • + devicesetChannelsReportGet +
  • devicesetDevicePut
  • +
  • + devicesetDeviceReportGet +
  • devicesetDeviceRunDelete
  • @@ -1740,12 +4298,30 @@ margin-bottom: 20px;
  • instanceAudioGet
  • -
  • - instanceAudioPatch +
  • + instanceAudioInputCleanupPatch +
  • +
  • + instanceAudioInputDelete +
  • +
  • + instanceAudioInputPatch +
  • +
  • + instanceAudioOutputCleanupPatch +
  • +
  • + instanceAudioOutputDelete +
  • +
  • + instanceAudioOutputPatch
  • instanceChannels
  • +
  • + instanceDVSerialGet +
  • instanceDVSerialPatch
  • @@ -1807,22 +4383,18 @@ margin-bottom: 20px;