From f1f7a0058c9c8db46c2cb250da47c0b4026a9e30 Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 22 Feb 2018 22:52:49 +0100 Subject: [PATCH] DATV demod: reception of the code --- plugins/channelrx/CMakeLists.txt | 1 + plugins/channelrx/demoddatv/.qmake.stash | 15 + plugins/channelrx/demoddatv/CMakeLists.txt | 59 + plugins/channelrx/demoddatv/Makefile | 454 ++++++ .../channelrx/demoddatv/datvconstellation.h | 128 ++ plugins/channelrx/demoddatv/datvdemod.cpp | 1052 ++++++++++++ plugins/channelrx/demoddatv/datvdemod.h | 466 ++++++ plugins/channelrx/demoddatv/datvdemodgui.cpp | 754 +++++++++ plugins/channelrx/demoddatv/datvdemodgui.h | 147 ++ plugins/channelrx/demoddatv/datvdemodgui.ui | 637 ++++++++ .../channelrx/demoddatv/datvdemodplugin.cpp | 72 + plugins/channelrx/demoddatv/datvdemodplugin.h | 53 + .../channelrx/demoddatv/datvideorender.cpp | 557 +++++++ plugins/channelrx/demoddatv/datvideorender.h | 194 +++ .../channelrx/demoddatv/datvideostream.cpp | 223 +++ plugins/channelrx/demoddatv/datvideostream.h | 77 + plugins/channelrx/demoddatv/datvscreen.cpp | 206 +++ plugins/channelrx/demoddatv/datvscreen.h | 96 ++ plugins/channelrx/demoddatv/datvvideoplayer.h | 55 + plugins/channelrx/demoddatv/demoddatv.pro | 67 + plugins/channelrx/demoddatv/glshaderarray.cpp | 301 ++++ plugins/channelrx/demoddatv/glshaderarray.h | 76 + .../demoddatv/leansdr/convolutional.h | 257 +++ plugins/channelrx/demoddatv/leansdr/dsp.h | 351 ++++ plugins/channelrx/demoddatv/leansdr/dvb.h | 1405 +++++++++++++++++ .../channelrx/demoddatv/leansdr/filtergen.h | 90 ++ .../channelrx/demoddatv/leansdr/framework.h | 269 ++++ plugins/channelrx/demoddatv/leansdr/generic.h | 348 ++++ plugins/channelrx/demoddatv/leansdr/gui.h | 591 +++++++ plugins/channelrx/demoddatv/leansdr/hdlc.h | 293 ++++ plugins/channelrx/demoddatv/leansdr/iess.h | 58 + plugins/channelrx/demoddatv/leansdr/math.h | 98 ++ plugins/channelrx/demoddatv/leansdr/rs.h | 259 +++ plugins/channelrx/demoddatv/leansdr/sdr.h | 1395 ++++++++++++++++ plugins/channelrx/demoddatv/leansdr/viterbi.h | 282 ++++ plugins/channelrx/demoddatv/readme.md | 76 + 36 files changed, 11462 insertions(+) create mode 100644 plugins/channelrx/demoddatv/.qmake.stash create mode 100644 plugins/channelrx/demoddatv/CMakeLists.txt create mode 100644 plugins/channelrx/demoddatv/Makefile create mode 100644 plugins/channelrx/demoddatv/datvconstellation.h create mode 100644 plugins/channelrx/demoddatv/datvdemod.cpp create mode 100644 plugins/channelrx/demoddatv/datvdemod.h create mode 100644 plugins/channelrx/demoddatv/datvdemodgui.cpp create mode 100644 plugins/channelrx/demoddatv/datvdemodgui.h create mode 100644 plugins/channelrx/demoddatv/datvdemodgui.ui create mode 100644 plugins/channelrx/demoddatv/datvdemodplugin.cpp create mode 100644 plugins/channelrx/demoddatv/datvdemodplugin.h create mode 100644 plugins/channelrx/demoddatv/datvideorender.cpp create mode 100644 plugins/channelrx/demoddatv/datvideorender.h create mode 100644 plugins/channelrx/demoddatv/datvideostream.cpp create mode 100644 plugins/channelrx/demoddatv/datvideostream.h create mode 100644 plugins/channelrx/demoddatv/datvscreen.cpp create mode 100644 plugins/channelrx/demoddatv/datvscreen.h create mode 100644 plugins/channelrx/demoddatv/datvvideoplayer.h create mode 100644 plugins/channelrx/demoddatv/demoddatv.pro create mode 100644 plugins/channelrx/demoddatv/glshaderarray.cpp create mode 100644 plugins/channelrx/demoddatv/glshaderarray.h create mode 100644 plugins/channelrx/demoddatv/leansdr/convolutional.h create mode 100644 plugins/channelrx/demoddatv/leansdr/dsp.h create mode 100644 plugins/channelrx/demoddatv/leansdr/dvb.h create mode 100644 plugins/channelrx/demoddatv/leansdr/filtergen.h create mode 100644 plugins/channelrx/demoddatv/leansdr/framework.h create mode 100644 plugins/channelrx/demoddatv/leansdr/generic.h create mode 100644 plugins/channelrx/demoddatv/leansdr/gui.h create mode 100644 plugins/channelrx/demoddatv/leansdr/hdlc.h create mode 100644 plugins/channelrx/demoddatv/leansdr/iess.h create mode 100644 plugins/channelrx/demoddatv/leansdr/math.h create mode 100644 plugins/channelrx/demoddatv/leansdr/rs.h create mode 100644 plugins/channelrx/demoddatv/leansdr/sdr.h create mode 100644 plugins/channelrx/demoddatv/leansdr/viterbi.h create mode 100644 plugins/channelrx/demoddatv/readme.md diff --git a/plugins/channelrx/CMakeLists.txt b/plugins/channelrx/CMakeLists.txt index 3f98bb1b2..86d6bc75e 100644 --- a/plugins/channelrx/CMakeLists.txt +++ b/plugins/channelrx/CMakeLists.txt @@ -11,6 +11,7 @@ add_subdirectory(demodwfm) add_subdirectory(chanalyzer) add_subdirectory(chanalyzerng) add_subdirectory(demodatv) +add_subdirectory(demoddatv) if(LIBDSDCC_FOUND AND LIBMBE_FOUND) add_subdirectory(demoddsd) diff --git a/plugins/channelrx/demoddatv/.qmake.stash b/plugins/channelrx/demoddatv/.qmake.stash new file mode 100644 index 000000000..c6486d18c --- /dev/null +++ b/plugins/channelrx/demoddatv/.qmake.stash @@ -0,0 +1,15 @@ +QMAKE_DEFAULT_INCDIRS = \ + /usr/include/c++/6 \ + /usr/include/x86_64-linux-gnu/c++/6 \ + /usr/include/c++/6/backward \ + /usr/lib/gcc/x86_64-linux-gnu/6/include \ + /usr/local/include \ + /usr/lib/gcc/x86_64-linux-gnu/6/include-fixed \ + /usr/include/x86_64-linux-gnu \ + /usr/include +QMAKE_DEFAULT_LIBDIRS = \ + /usr/lib/gcc/x86_64-linux-gnu/6 \ + /usr/lib/x86_64-linux-gnu \ + /usr/lib \ + /lib/x86_64-linux-gnu \ + /lib diff --git a/plugins/channelrx/demoddatv/CMakeLists.txt b/plugins/channelrx/demoddatv/CMakeLists.txt new file mode 100644 index 000000000..3135612ef --- /dev/null +++ b/plugins/channelrx/demoddatv/CMakeLists.txt @@ -0,0 +1,59 @@ +project(datv) + +set(datv_SOURCES + datvdemod.cpp + datvdemodgui.cpp + datvdemodplugin.cpp + datvscreen.cpp + glshaderarray.cpp + datvideostream.cpp + datvideorender.cpp +) + +set(datv_HEADERS + datvdemod.h + datvdemodgui.h + datvdemodplugin.h + datvscreen.h + glshaderarray.h + datvideostream.h + datvideorender.h +) + +set(datv_FORMS + datvdemodgui.ui +) + +set (CMAKE_CXX_FLAGS "-Wno-unused-variable -Wno-deprecated-declarations") + +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(nfm_HEADERS_MOC ${nfm_HEADERS}) +qt5_wrap_ui(datv_FORMS_HEADERS ${datv_FORMS}) + +add_library(demoddatv SHARED + ${datv_SOURCES} + ${datv_HEADERS_MOC} + ${datv_FORMS_HEADERS} +) + +target_link_libraries(demoddatv + ${QT_LIBRARIES} + sdrbase + sdrgui + avcodec + avformat + swscale +) + +qt5_use_modules(demoddatv Core Widgets Multimedia MultimediaWidgets) + +install(TARGETS demoddatv DESTINATION lib/plugins/channelrx) diff --git a/plugins/channelrx/demoddatv/Makefile b/plugins/channelrx/demoddatv/Makefile new file mode 100644 index 000000000..0ba71e74a --- /dev/null +++ b/plugins/channelrx/demoddatv/Makefile @@ -0,0 +1,454 @@ +# CMAKE generated file: DO NOT EDIT! +# Generated by "Unix Makefiles" Generator, CMake Version 3.7 + +# Default target executed when no arguments are given to make. +default_target: all + +.PHONY : default_target + +# Allow only one "make -f Makefile2" at a time, but pass parallelism. +.NOTPARALLEL: + + +#============================================================================= +# Special targets provided by cmake. + +# Disable implicit rules so canonical targets will work. +.SUFFIXES: + + +# Remove some rules from gmake that .SUFFIXES does not remove. +SUFFIXES = + +.SUFFIXES: .hpux_make_needs_suffix_list + + +# Suppress display of executed commands. +$(VERBOSE).SILENT: + + +# A target that is always out of date. +cmake_force: + +.PHONY : cmake_force + +#============================================================================= +# Set environment variables for the build. + +# The shell in which to execute make rules. +SHELL = /bin/sh + +# The CMake executable. +CMAKE_COMMAND = /usr/bin/cmake + +# The command to remove a file. +RM = /usr/bin/cmake -E remove -f + +# Escaping for special characters. +EQUALS = = + +# The top-level source directory on which CMake was run. +CMAKE_SOURCE_DIR = /home/guest/HAM/sdrangel-master + +# The top-level build directory on which CMake was run. +CMAKE_BINARY_DIR = /home/guest/HAM/sdrangel-master + +#============================================================================= +# Targets provided globally by CMake. + +# Special rule for the target install/strip +install/strip: preinstall + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing the project stripped..." + /usr/bin/cmake -DCMAKE_INSTALL_DO_STRIP=1 -P cmake_install.cmake +.PHONY : install/strip + +# Special rule for the target install/strip +install/strip/fast: install/strip + +.PHONY : install/strip/fast + +# Special rule for the target install/local +install/local: preinstall + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing only the local directory..." + /usr/bin/cmake -DCMAKE_INSTALL_LOCAL_ONLY=1 -P cmake_install.cmake +.PHONY : install/local + +# Special rule for the target install/local +install/local/fast: install/local + +.PHONY : install/local/fast + +# Special rule for the target edit_cache +edit_cache: + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "No interactive CMake dialog available..." + /usr/bin/cmake -E echo No\ interactive\ CMake\ dialog\ available. +.PHONY : edit_cache + +# Special rule for the target edit_cache +edit_cache/fast: edit_cache + +.PHONY : edit_cache/fast + +# Special rule for the target rebuild_cache +rebuild_cache: + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running CMake to regenerate build system..." + /usr/bin/cmake -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) +.PHONY : rebuild_cache + +# Special rule for the target rebuild_cache +rebuild_cache/fast: rebuild_cache + +.PHONY : rebuild_cache/fast + +# Special rule for the target install +install: preinstall + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Install the project..." + /usr/bin/cmake -P cmake_install.cmake +.PHONY : install + +# Special rule for the target install +install/fast: preinstall/fast + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Install the project..." + /usr/bin/cmake -P cmake_install.cmake +.PHONY : install/fast + +# Special rule for the target list_install_components +list_install_components: + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Available install components are: \"Unspecified\"" +.PHONY : list_install_components + +# Special rule for the target list_install_components +list_install_components/fast: list_install_components + +.PHONY : list_install_components/fast + +# The main all target +all: cmake_check_build_system + cd /home/guest/HAM/sdrangel-master && $(CMAKE_COMMAND) -E cmake_progress_start /home/guest/HAM/sdrangel-master/CMakeFiles /home/guest/HAM/sdrangel-master/plugins/channelrx/demoddatv/CMakeFiles/progress.marks + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f CMakeFiles/Makefile2 plugins/channelrx/demoddatv/all + $(CMAKE_COMMAND) -E cmake_progress_start /home/guest/HAM/sdrangel-master/CMakeFiles 0 +.PHONY : all + +# The main clean target +clean: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f CMakeFiles/Makefile2 plugins/channelrx/demoddatv/clean +.PHONY : clean + +# The main clean target +clean/fast: clean + +.PHONY : clean/fast + +# Prepare targets for installation. +preinstall: all + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f CMakeFiles/Makefile2 plugins/channelrx/demoddatv/preinstall +.PHONY : preinstall + +# Prepare targets for installation. +preinstall/fast: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f CMakeFiles/Makefile2 plugins/channelrx/demoddatv/preinstall +.PHONY : preinstall/fast + +# clear depends +depend: + cd /home/guest/HAM/sdrangel-master && $(CMAKE_COMMAND) -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 1 +.PHONY : depend + +# Convenience name for target. +plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/rule: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f CMakeFiles/Makefile2 plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/rule +.PHONY : plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/rule + +# Convenience name for target. +demoddatv: plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/rule + +.PHONY : demoddatv + +# fast build rule for target. +demoddatv/fast: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/build.make plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/build +.PHONY : demoddatv/fast + +# Convenience name for target. +plugins/channelrx/demoddatv/CMakeFiles/demoddatv_automoc.dir/rule: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f CMakeFiles/Makefile2 plugins/channelrx/demoddatv/CMakeFiles/demoddatv_automoc.dir/rule +.PHONY : plugins/channelrx/demoddatv/CMakeFiles/demoddatv_automoc.dir/rule + +# Convenience name for target. +demoddatv_automoc: plugins/channelrx/demoddatv/CMakeFiles/demoddatv_automoc.dir/rule + +.PHONY : demoddatv_automoc + +# fast build rule for target. +demoddatv_automoc/fast: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f plugins/channelrx/demoddatv/CMakeFiles/demoddatv_automoc.dir/build.make plugins/channelrx/demoddatv/CMakeFiles/demoddatv_automoc.dir/build +.PHONY : demoddatv_automoc/fast + +datvdemod.o: datvdemod.cpp.o + +.PHONY : datvdemod.o + +# target to build an object file +datvdemod.cpp.o: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/build.make plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/datvdemod.cpp.o +.PHONY : datvdemod.cpp.o + +datvdemod.i: datvdemod.cpp.i + +.PHONY : datvdemod.i + +# target to preprocess a source file +datvdemod.cpp.i: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/build.make plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/datvdemod.cpp.i +.PHONY : datvdemod.cpp.i + +datvdemod.s: datvdemod.cpp.s + +.PHONY : datvdemod.s + +# target to generate assembly for a file +datvdemod.cpp.s: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/build.make plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/datvdemod.cpp.s +.PHONY : datvdemod.cpp.s + +datvdemodgui.o: datvdemodgui.cpp.o + +.PHONY : datvdemodgui.o + +# target to build an object file +datvdemodgui.cpp.o: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/build.make plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/datvdemodgui.cpp.o +.PHONY : datvdemodgui.cpp.o + +datvdemodgui.i: datvdemodgui.cpp.i + +.PHONY : datvdemodgui.i + +# target to preprocess a source file +datvdemodgui.cpp.i: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/build.make plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/datvdemodgui.cpp.i +.PHONY : datvdemodgui.cpp.i + +datvdemodgui.s: datvdemodgui.cpp.s + +.PHONY : datvdemodgui.s + +# target to generate assembly for a file +datvdemodgui.cpp.s: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/build.make plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/datvdemodgui.cpp.s +.PHONY : datvdemodgui.cpp.s + +datvdemodplugin.o: datvdemodplugin.cpp.o + +.PHONY : datvdemodplugin.o + +# target to build an object file +datvdemodplugin.cpp.o: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/build.make plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/datvdemodplugin.cpp.o +.PHONY : datvdemodplugin.cpp.o + +datvdemodplugin.i: datvdemodplugin.cpp.i + +.PHONY : datvdemodplugin.i + +# target to preprocess a source file +datvdemodplugin.cpp.i: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/build.make plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/datvdemodplugin.cpp.i +.PHONY : datvdemodplugin.cpp.i + +datvdemodplugin.s: datvdemodplugin.cpp.s + +.PHONY : datvdemodplugin.s + +# target to generate assembly for a file +datvdemodplugin.cpp.s: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/build.make plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/datvdemodplugin.cpp.s +.PHONY : datvdemodplugin.cpp.s + +datvideorender.o: datvideorender.cpp.o + +.PHONY : datvideorender.o + +# target to build an object file +datvideorender.cpp.o: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/build.make plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/datvideorender.cpp.o +.PHONY : datvideorender.cpp.o + +datvideorender.i: datvideorender.cpp.i + +.PHONY : datvideorender.i + +# target to preprocess a source file +datvideorender.cpp.i: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/build.make plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/datvideorender.cpp.i +.PHONY : datvideorender.cpp.i + +datvideorender.s: datvideorender.cpp.s + +.PHONY : datvideorender.s + +# target to generate assembly for a file +datvideorender.cpp.s: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/build.make plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/datvideorender.cpp.s +.PHONY : datvideorender.cpp.s + +datvideostream.o: datvideostream.cpp.o + +.PHONY : datvideostream.o + +# target to build an object file +datvideostream.cpp.o: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/build.make plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/datvideostream.cpp.o +.PHONY : datvideostream.cpp.o + +datvideostream.i: datvideostream.cpp.i + +.PHONY : datvideostream.i + +# target to preprocess a source file +datvideostream.cpp.i: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/build.make plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/datvideostream.cpp.i +.PHONY : datvideostream.cpp.i + +datvideostream.s: datvideostream.cpp.s + +.PHONY : datvideostream.s + +# target to generate assembly for a file +datvideostream.cpp.s: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/build.make plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/datvideostream.cpp.s +.PHONY : datvideostream.cpp.s + +datvscreen.o: datvscreen.cpp.o + +.PHONY : datvscreen.o + +# target to build an object file +datvscreen.cpp.o: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/build.make plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/datvscreen.cpp.o +.PHONY : datvscreen.cpp.o + +datvscreen.i: datvscreen.cpp.i + +.PHONY : datvscreen.i + +# target to preprocess a source file +datvscreen.cpp.i: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/build.make plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/datvscreen.cpp.i +.PHONY : datvscreen.cpp.i + +datvscreen.s: datvscreen.cpp.s + +.PHONY : datvscreen.s + +# target to generate assembly for a file +datvscreen.cpp.s: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/build.make plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/datvscreen.cpp.s +.PHONY : datvscreen.cpp.s + +demoddatv_automoc.o: demoddatv_automoc.cpp.o + +.PHONY : demoddatv_automoc.o + +# target to build an object file +demoddatv_automoc.cpp.o: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/build.make plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/demoddatv_automoc.cpp.o +.PHONY : demoddatv_automoc.cpp.o + +demoddatv_automoc.i: demoddatv_automoc.cpp.i + +.PHONY : demoddatv_automoc.i + +# target to preprocess a source file +demoddatv_automoc.cpp.i: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/build.make plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/demoddatv_automoc.cpp.i +.PHONY : demoddatv_automoc.cpp.i + +demoddatv_automoc.s: demoddatv_automoc.cpp.s + +.PHONY : demoddatv_automoc.s + +# target to generate assembly for a file +demoddatv_automoc.cpp.s: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/build.make plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/demoddatv_automoc.cpp.s +.PHONY : demoddatv_automoc.cpp.s + +glshaderarray.o: glshaderarray.cpp.o + +.PHONY : glshaderarray.o + +# target to build an object file +glshaderarray.cpp.o: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/build.make plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/glshaderarray.cpp.o +.PHONY : glshaderarray.cpp.o + +glshaderarray.i: glshaderarray.cpp.i + +.PHONY : glshaderarray.i + +# target to preprocess a source file +glshaderarray.cpp.i: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/build.make plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/glshaderarray.cpp.i +.PHONY : glshaderarray.cpp.i + +glshaderarray.s: glshaderarray.cpp.s + +.PHONY : glshaderarray.s + +# target to generate assembly for a file +glshaderarray.cpp.s: + cd /home/guest/HAM/sdrangel-master && $(MAKE) -f plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/build.make plugins/channelrx/demoddatv/CMakeFiles/demoddatv.dir/glshaderarray.cpp.s +.PHONY : glshaderarray.cpp.s + +# Help Target +help: + @echo "The following are some of the valid targets for this Makefile:" + @echo "... all (the default if no target is provided)" + @echo "... clean" + @echo "... depend" + @echo "... install/strip" + @echo "... install/local" + @echo "... demoddatv" + @echo "... edit_cache" + @echo "... rebuild_cache" + @echo "... install" + @echo "... list_install_components" + @echo "... demoddatv_automoc" + @echo "... datvdemod.o" + @echo "... datvdemod.i" + @echo "... datvdemod.s" + @echo "... datvdemodgui.o" + @echo "... datvdemodgui.i" + @echo "... datvdemodgui.s" + @echo "... datvdemodplugin.o" + @echo "... datvdemodplugin.i" + @echo "... datvdemodplugin.s" + @echo "... datvideorender.o" + @echo "... datvideorender.i" + @echo "... datvideorender.s" + @echo "... datvideostream.o" + @echo "... datvideostream.i" + @echo "... datvideostream.s" + @echo "... datvscreen.o" + @echo "... datvscreen.i" + @echo "... datvscreen.s" + @echo "... demoddatv_automoc.o" + @echo "... demoddatv_automoc.i" + @echo "... demoddatv_automoc.s" + @echo "... glshaderarray.o" + @echo "... glshaderarray.i" + @echo "... glshaderarray.s" +.PHONY : help + + + +#============================================================================= +# Special targets to cleanup operation of make. + +# Special rule to run CMake to check the build system integrity. +# No rule that depends on this can have commands that come from listfiles +# because they might be regenerated. +cmake_check_build_system: + cd /home/guest/HAM/sdrangel-master && $(CMAKE_COMMAND) -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0 +.PHONY : cmake_check_build_system + diff --git a/plugins/channelrx/demoddatv/datvconstellation.h b/plugins/channelrx/demoddatv/datvconstellation.h new file mode 100644 index 000000000..9f95a7a15 --- /dev/null +++ b/plugins/channelrx/demoddatv/datvconstellation.h @@ -0,0 +1,128 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4HKW // +// for F4EXB / SDRAngel // +// using LeanSDR Framework (C) 2016 F4DAV // +// // +// This 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 DATVCONSTELLATION_H +#define DATVCONSTELLATION_H + +#include + +#include "leansdr/framework.h" +#include "datvscreen.h" + +namespace leansdr +{ + + static const int DEFAULT_GUI_DECIMATION = 64; + + template struct datvconstellation : runnable + { + 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() + { + //Symbols + + while ( in.readable() >= pixels_per_frame ) + { + if ( ! phase ) + { + m_objDATVScreen->resetImage(); + + 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); + } + + } + + if ( cstln && (*cstln) ) + { + // 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); + + for ( int d=-4; d<=4; ++d ) + { + 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->renderImage(NULL); + + } + + in.read(pixels_per_frame); + + if ( ++phase >= decimation ) + { + phase = 0; + } + } + } + + //private: + pipereader< complex > in; + unsigned long phase; + //gfx g; + + 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); + } + + }; + +} + +#endif // DATVCONSTELLATION_H diff --git a/plugins/channelrx/demoddatv/datvdemod.cpp b/plugins/channelrx/demoddatv/datvdemod.cpp new file mode 100644 index 000000000..3837d0638 --- /dev/null +++ b/plugins/channelrx/demoddatv/datvdemod.cpp @@ -0,0 +1,1052 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4HKW // +// for F4EXB / SDRAngel // +// using LeanSDR Framework (C) 2016 F4DAV // +// // +// This 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 "datvdemod.h" + +#include +#include +#include +#include +#include "audio/audiooutput.h" +#include "dsp/dspengine.h" + +#include "dsp/downchannelizer.h" +#include "dsp/threadedbasebandsamplesink.h" +#include "device/devicesourceapi.h" + + +const QString DATVDemod::m_channelIdURI = "sdrangel.channel.demoddatv"; +const QString DATVDemod::m_channelId = "DATVDemod"; + +MESSAGE_CLASS_DEFINITION(DATVDemod::MsgConfigureDATVDemod, Message) +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) +{ + setObjectName("DATVDemod"); + + //*************** DATV PARAMETERS *************** + m_blnInitialized=false; + CleanUpDATVFramework(false); + + m_objVideoStream = new DATVideostream(); + + m_objRFFilter = new fftfilt(-256000.0 / 1024000.0, 256000.0 / 1024000.0, rfFilterFftLength); + + 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())); + +} + +DATVDemod::~DATVDemod() +{ + m_blnInitialized=false; + + + if(m_objRenderThread!=NULL) + { + if(m_objRenderThread->isRunning()) + { + m_objRenderThread->stopRendering(); + } + } + + //CleanUpDATVFramework(true); + + if(m_objRFFilter!=NULL) + { + //delete m_objRFFilter; + } + + if(m_objVideoStream!=NULL) + { + //m_objVideoStream->close(); + //delete m_objVideoStream; + } + + m_deviceAPI->removeChannelAPI(this); + m_deviceAPI->removeThreadedSink(m_threadedChannelizer); + delete m_threadedChannelizer; + delete m_channelizer; +} + +bool DATVDemod::SetDATVScreen(DATVScreen *objScreen) +{ + m_objRegisteredDATVScreen = objScreen; +} + +DATVideostream * DATVDemod::SetVideoRender(DATVideoRender *objScreen) +{ + m_objRegisteredVideoRender = objScreen; + + m_objRenderThread = new DATVideoRenderThread(m_objRegisteredVideoRender,m_objVideoStream); + + return m_objVideoStream; +} + + +bool DATVDemod::PlayVideo(bool blnStartStop) +{ + + if(m_objVideoStream==NULL) + { + return false; + } + + if(m_objRegisteredVideoRender==NULL) + { + return false; + } + + if(m_objRenderThread==NULL) + { + return false; + } + + if(m_objRenderThread->isRunning()) + { + if(blnStartStop==true) + { + m_objRenderThread->stopRendering(); + } + + return true; + } + + m_objRenderThread->setStreamAndRenderer(m_objRegisteredVideoRender,m_objVideoStream); + m_objVideoStream->MultiThreaded=true; + m_objRenderThread->start(); + + //m_objVideoStream->MultiThreaded=false; + //m_objRenderThread->run(); + + return true; +} + +void DATVDemod::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) +{ + Message* msgCmd = MsgConfigureDATVDemod::create(intRFBandwidth,intCenterFrequency,enmStandard, enmModulation, enmFEC, intSymbolRate, intNotchFilters, blnAllowDrift,blnFastLock,blnHDLC,blnHardMetric,blnResample, blnViterbi); + objMessageQueue->push(msgCmd); +} + +void DATVDemod::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) +{ + Real fltLowCut; + Real fltHiCut; + + m_blnInitialized=false; + + m_objSettingsMutex.lock(); + + //Recalibrage du filtre passe bande + + 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 + + m_objRunning.intMsps = intMsps; + m_objRunning.intCenterFrequency = intCenterFrequency; + m_objRunning.intRFBandwidth = intRFBandwidth; + m_objRunning.enmStandard = enmStandard; + m_objRunning.enmModulation = enmModulation; + m_objRunning.enmFEC = enmFEC; + m_objRunning.intSampleRate = intSampleRate; + m_objRunning.intSymbolRate = intSymbolRate; + m_objRunning.intNotchFilters = intNotchFilters; + m_objRunning.blnAllowDrift = blnAllowDrift; + m_objRunning.blnFastLock = blnFastLock; + m_objRunning.blnHDLC = blnHDLC; + m_objRunning.blnHardMetric = blnHardMetric; + m_objRunning.blnResample = blnResample; + m_objRunning.blnViterbi = blnViterbi; + + 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_objSettingsMutex.unlock(); + + m_blnNeedConfigUpdate=true; + + m_blnInitialized=true; +} + +void DATVDemod::CleanUpDATVFramework(bool blnRelease) +{ + //if(blnRelease==true) + if(false) + { + if(m_objScheduler!=NULL) + { + m_objScheduler->shutdown(); + 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; + + // FREQUENCY CORRECTION : DEROTATOR + if(p_derot!=NULL) delete p_derot; + if(r_derot!=NULL) delete r_derot; + + // CNR ESTIMATION + if(p_cnr!=NULL) delete p_cnr; + if(r_cnr!=NULL) delete r_cnr; + + //FILTERING + if(r_resample!=NULL) delete r_resample; + if(p_resampled!=NULL) delete p_resampled; + if(coeffs!=NULL) delete coeffs; + + // OUTPUT PREPROCESSED DATA + if(sampler!=NULL) delete sampler; + if(coeffs_sampler!=NULL) delete coeffs_sampler; + if(p_symbols!=NULL) delete p_symbols; + if(p_freq!=NULL) delete p_freq; + if(p_ss!=NULL) delete p_ss; + if(p_mer!=NULL) delete p_mer; + if(p_sampled!=NULL) delete p_sampled; + + //DECIMATION + if(p_decimated!=NULL) delete p_decimated; + if(p_decim!=NULL) delete p_decim; + if(r_ppout!=NULL) delete r_ppout; + + //GENERIC CONSTELLATION RECEIVER + if(m_objDemodulator!=NULL) delete m_objDemodulator; + + //DECONVOLUTION AND SYNCHRONIZATION + if(p_bytes!=NULL) delete p_bytes; + if(r_deconv!=NULL) delete r_deconv; + if(r!=NULL) delete r; + if(p_descrambled!=NULL) delete p_descrambled; + if(p_frames!=NULL) delete p_frames; + if(r_etr192_descrambler!=NULL) delete r_etr192_descrambler; + if(r_sync!=NULL) delete r_sync; + if(p_mpegbytes!=NULL) delete p_mpegbytes; + if(p_lock!=NULL) delete p_lock; + if(p_locktime!=NULL) delete p_locktime; + if(r_sync_mpeg!=NULL) delete r_sync_mpeg; + + + // DEINTERLEAVING + if(p_rspackets!=NULL) delete p_rspackets; + if(r_deinter!=NULL) delete r_deinter; + if(p_vbitcount!=NULL) delete p_vbitcount; + if(p_verrcount!=NULL) delete p_verrcount; + if(p_rtspackets!=NULL) delete p_rtspackets; + if(r_rsdec!=NULL) delete r_rsdec; + + //BER ESTIMATION + if(p_vber!=NULL) delete p_vber; + if(r_vber!=NULL) delete r_vber; + + // DERANDOMIZATION + if(p_tspackets!=NULL) delete p_tspackets; + if(r_derand!=NULL) delete r_derand; + + + //OUTPUT : To remove + if(r_stdout!=NULL) delete r_stdout; + if(r_videoplayer!=NULL) delete r_videoplayer; + + //CONSTELLATION + if(r_scope_symbols!=NULL) delete r_scope_symbols; + + } + + m_objScheduler=NULL; + + // INPUT + + p_rawiq = NULL; + p_rawiq_writer = NULL; + + p_preprocessed = NULL; + + // NOTCH FILTER + r_auto_notch = NULL; + p_autonotched = NULL; + + // FREQUENCY CORRECTION : DEROTATOR + p_derot = NULL; + r_derot=NULL; + + // CNR ESTIMATION + p_cnr = NULL; + r_cnr = NULL; + + //FILTERING + r_resample = NULL; + p_resampled = NULL; + coeffs = NULL; + ncoeffs=0; + + // OUTPUT PREPROCESSED DATA + sampler = NULL; + coeffs_sampler=NULL; + ncoeffs_sampler=0; + + p_symbols = NULL; + p_freq = NULL; + p_ss = NULL; + p_mer = NULL; + p_sampled = NULL; + + //DECIMATION + p_decimated = NULL; + p_decim = NULL; + r_ppout = NULL; + + //GENERIC CONSTELLATION RECEIVER + m_objDemodulator = NULL; + + //DECONVOLUTION AND SYNCHRONIZATION + p_bytes=NULL; + r_deconv=NULL; + r = NULL; + + p_descrambled = NULL; + p_frames = NULL; + r_etr192_descrambler = NULL; + r_sync = NULL; + + p_mpegbytes = NULL; + p_lock = NULL; + p_locktime = NULL; + r_sync_mpeg = NULL; + + + // DEINTERLEAVING + p_rspackets = NULL; + r_deinter = NULL; + + p_vbitcount = NULL; + p_verrcount = NULL; + p_rtspackets = NULL; + r_rsdec = NULL; + + + //BER ESTIMATION + p_vber = NULL; + r_vber = NULL; + + + // DERANDOMIZATION + p_tspackets = NULL; + r_derand = NULL; + + + //OUTPUT : To remove + r_stdout = NULL; + r_videoplayer = NULL; + + + //CONSTELLATION + r_scope_symbols = NULL; +} + +void DATVDemod::InitDATVFramework() +{ + m_blnDVBInitialized=false; + m_lngReadIQ=0; + + m_objCfg.standard = m_objRunning.enmStandard; + + m_objCfg.fec = m_objRunning.enmFEC; + m_objCfg.Fs = (float) m_objRunning.intSampleRate; + m_objCfg.Fm = (float) m_objRunning.intSymbolRate; + m_objCfg.fastlock = m_objRunning.blnFastLock; + + switch(m_objRunning.enmModulation) + { + case BPSK: + m_objCfg.constellation = cstln_lut<256>::BPSK; + break; + + case QPSK: + m_objCfg.constellation = cstln_lut<256>::QPSK; + break; + + case PSK8: + m_objCfg.constellation = cstln_lut<256>::PSK8; + break; + + case APSK16: + m_objCfg.constellation = cstln_lut<256>::APSK16; + break; + + case APSK32: + m_objCfg.constellation = cstln_lut<256>::APSK32; + break; + + case APSK64E: + m_objCfg.constellation = cstln_lut<256>::APSK64E; + break; + + case QAM16: + m_objCfg.constellation = cstln_lut<256>::QAM16; + break; + + case QAM64: + m_objCfg.constellation = cstln_lut<256>::QAM64; + break; + + case QAM256: + m_objCfg.constellation = cstln_lut<256>::QAM256; + break; + + default: + m_objCfg.constellation = 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.viterbi = m_objRunning.blnViterbi; + + + // Min buffer size for baseband data + // scopes: 1024 + // ss_estimator: 1024 + // anf: 4096 + // cstln_receiver: reads in chunks of 128+1 + BUF_BASEBAND = 4096 * m_objCfg.buf_factor; + + // Min buffer size for IQ symbols + // cstln_receiver: writes in chunks of 128/omega symbols (margin 128) + // deconv_sync: reads at least 64+32 + // A larger buffer improves performance significantly. + BUF_SYMBOLS = 1024 * m_objCfg.buf_factor; + + // Min buffer size for unsynchronized bytes + // deconv_sync: writes 32 bytes + // mpeg_sync: reads up to 204*scan_syncs = 1632 bytes + BUF_BYTES = 2048 * m_objCfg.buf_factor; + + // Min buffer size for synchronized (but interleaved) bytes + // mpeg_sync: writes 1 rspacket + // deinterleaver: reads 17*11*12+204 = 2448 bytes + BUF_MPEGBYTES = 2448 * m_objCfg.buf_factor; + + // Min buffer size for packets: 1 + BUF_PACKETS = m_objCfg.buf_factor; + + // Min buffer size for misc measurements: 1 + BUF_SLOW = m_objCfg.buf_factor; + + m_lngExpectedReadIQ = BUF_BASEBAND; + + + CleanUpDATVFramework(true); + + m_objScheduler = new scheduler(); + + //*************** + p_rawiq = new pipebuf(m_objScheduler, "rawiq", BUF_BASEBAND); + p_rawiq_writer = new pipewriter(*p_rawiq); + p_preprocessed = p_rawiq; + + // NOTCH FILTER + + if ( m_objCfg.anf ) + { + 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_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; + } + + // CNR ESTIMATION + + p_cnr = new pipebuf(m_objScheduler, "cnr", BUF_SLOW); + + if ( m_objCfg.cnr ) + { + r_cnr = new cnr_fft(m_objScheduler, *p_preprocessed, *p_cnr, m_objCfg.Fm/m_objCfg.Fs); + r_cnr->decimation = decimation(m_objCfg.Fs, 1); // 1 Hz + } + + // FILTERING + + 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; + } + } + + 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; + } + + //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); + + switch ( m_objCfg.sampler ) + { + case SAMP_NEAREST: + sampler = new nearest_sampler(); + break; + + case SAMP_LINEAR: + sampler = new linear_sampler(); + break; + + case SAMP_RRC: + { + + + 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)); + } + + 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); + + sampler = new fir_sampler(ncoeffs_sampler, coeffs_sampler, m_objCfg.rrc_steps); + break; + } + + default: + fatal("Interpolator not implemented"); + } + + m_objDemodulator = new 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 ) + { + fprintf(stderr, "Warning: non-standard constellation for DVB-S\n"); + } + } + + if ( m_objCfg.standard == DVB_S2 ) + { + // For DVB-S2 testing only. + // Constellation should be determined from PL signalling. + fprintf(stderr, "DVB-S2: Testing symbol sampler only.\n"); + } + + m_objDemodulator->cstln = make_dvbs2_constellation(m_objCfg.constellation, m_objCfg.fec); + + if ( m_objCfg.hard_metric ) + { + m_objDemodulator->cstln->harden(); + } + + 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.allow_drift ) + { + m_objDemodulator->set_allow_drift(true); + } + + 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 ) + { + r_cnr->freq_tap = &m_objDemodulator->freq_tap; + r_cnr->tap_multiplier = 1.0 / decim; + } + + //constellation + + m_objRegisteredDATVScreen->resizeDATVScreen(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; + + // DECONVOLUTION AND SYNCHRONIZATION + + p_bytes = new pipebuf(m_objScheduler, "bytes", BUF_BYTES); + + r_deconv = NULL; + + if ( m_objCfg.viterbi ) + { + if ( m_objCfg.fec==FEC23 && (m_objDemodulator->cstln->nsymbols==4 || m_objDemodulator->cstln->nsymbols==64) ) + { + m_objCfg.fec = 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); + + if ( m_objCfg.fastlock ) + { + r->resync_period = 1; + } + } + else + { + 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 ) + { + 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); + + 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; + } + + // DEINTERLEAVING + + p_rspackets = new pipebuf< rspacket >(m_objScheduler, "RS-enc packets", BUF_PACKETS); + r_deinter = new 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); + + + // 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 + // Require resolution better than 2E-5 + if ( r_vber->sample_size < 50000 ) + { + 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); + + + // OUTPUT + r_videoplayer = new 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) +{ + qint16 * ptrBufferToRelease=NULL; + float fltI; + float fltQ; + cf32 objIQ; + //Complex objC; + fftfilt::cmplx *objRF; + int intRFOut; + + +#ifdef EXTENDED_DIRECT_SAMPLE + + qint16 * ptrBuffer; + qint32 intLen; + + //********** Reading direct samples ********** + + SampleVector::const_iterator it = begin; + intLen = it->intLen; + ptrBuffer = it->ptrBuffer; + ptrBufferToRelease = ptrBuffer; + ++it; + + for(qint32 intInd=0; intIndreal(); + fltQ = it->imag(); +#endif + + + //********** demodulation ********** + + if((m_blnDVBInitialized==false) || (m_blnNeedConfigUpdate==true)) + { + m_blnNeedConfigUpdate=false; + InitDATVFramework(); + } + + //********** iq stream **************** + + if(m_lngReadIQ>p_rawiq_writer->writable()) + { + m_objScheduler->step(); + + m_objRegisteredDATVScreen->renderImage(NULL); + + 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++) + { + objIQ.re = objRF->real(); + objIQ.im = objRF->imag(); + + p_rawiq_writer->write(objIQ); + + objRF ++; + m_lngReadIQ++; + } + } + + + //********** demodulation ********** + + } + + if(ptrBufferToRelease!=NULL) + { + delete ptrBufferToRelease; + } + + //m_objSettingsMutex.unlock(); + +} + +void DATVDemod::start() +{ + //m_objTimer.start(); +} + +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()) + { + 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); + + + + qDebug() << "ATVDemod::handleMessage: MsgConfigureChannelizer: sampleRate: " << m_channelizer->getInputSampleRate() + << " centerFrequency: " << m_objRunning.intCenterFrequency; + + return true; + } + else if (MsgConfigureDATVDemod::match(cmd)) + { + MsgConfigureDATVDemod& objCfg = (MsgConfigureDATVDemod&) cmd; + + + if((objCfg.m_objMsgConfig.blnAllowDrift != m_objRunning.blnAllowDrift) + || (objCfg.m_objMsgConfig.intRFBandwidth != m_objRunning.intRFBandwidth) + || (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.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)) + { + 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.blnViterbi = objCfg.m_objMsgConfig.blnViterbi; + m_objRunning.enmFEC = objCfg.m_objMsgConfig.enmFEC; + m_objRunning.enmModulation = objCfg.m_objMsgConfig.enmModulation; + m_objRunning.enmStandard = objCfg.m_objMsgConfig.enmStandard; + m_objRunning.intNotchFilters = objCfg.m_objMsgConfig.intNotchFilters; + m_objRunning.intSymbolRate = objCfg.m_objMsgConfig.intSymbolRate; + m_objRunning.intRFBandwidth = objCfg.m_objMsgConfig.intRFBandwidth; + m_objRunning.intCenterFrequency = objCfg.m_objMsgConfig.intCenterFrequency; + + ApplySettings(); + } + + + return true; + } + else + { + return false; + } +} + +void DATVDemod::ApplySettings() +{ + + if(m_objRunning.intMsps==0) + { + return; + } + + //m_objSettingsMutex.lock(); + + InitDATVParameters(m_objRunning.intMsps, + m_objRunning.intRFBandwidth, + m_objRunning.intCenterFrequency, + m_objRunning.enmStandard, + m_objRunning.enmModulation, + m_objRunning.enmFEC, + m_objRunning.intSampleRate, + m_objRunning.intSymbolRate, + m_objRunning.intNotchFilters, + m_objRunning.blnAllowDrift, + m_objRunning.blnFastLock, + m_objRunning.blnHDLC, + m_objRunning.blnHardMetric, + m_objRunning.blnResample, + m_objRunning.blnViterbi); + +} + +int DATVDemod::GetSampleRate() +{ + return m_objRunning.intMsps; +} + diff --git a/plugins/channelrx/demoddatv/datvdemod.h b/plugins/channelrx/demoddatv/datvdemod.h new file mode 100644 index 000000000..399e596e9 --- /dev/null +++ b/plugins/channelrx/demoddatv/datvdemod.h @@ -0,0 +1,466 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4HKW // +// for F4EXB / SDRAngel // +// using LeanSDR Framework (C) 2016 F4DAV // +// // +// This 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_DATVDEMOD_H +#define INCLUDE_DATVDEMOD_H + +class DeviceSourceAPI; +class ThreadedBasebandSampleSink; +class DownChannelizer; + +#define rfFilterFftLength 1024 + +#ifndef LEANSDR_FRAMEWORK +#define LEANSDR_FRAMEWORK + +//LeanSDR + +#include "leansdr/framework.h" +#include "leansdr/generic.h" +#include "leansdr/dsp.h" +#include "leansdr/sdr.h" +#include "leansdr/dvb.h" +#include "leansdr/rs.h" +#include "leansdr/filtergen.h" + +#include "leansdr/hdlc.h" +#include "leansdr/iess.h" + +#endif + + + +#include "datvconstellation.h" +#include "datvvideoplayer.h" + +#include "channel/channelsinkapi.h" +#include +#include +#include +#include +#include +#include +#include +#include +#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 "datvideostream.h" +#include "datvideorender.h" + +using namespace leansdr; + +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); } + +struct config +{ + dvb_version standard; + dvb_sampler sampler; + + int buf_factor; // Buffer sizing + float Fs; // Sampling frequency (Hz) + float Fderot; // Shift the signal (Hz). Note: Ftune is faster + int anf; // Number of auto notch filters + bool cnr; // Measure CNR + unsigned int decim; // Decimation, 0=auto + float Fm; // QPSK symbol rate (Hz) + cstln_lut<256>::predef constellation; + code_rate fec; + float Ftune; // Bias frequency for the QPSK demodulator (Hz) + bool allow_drift; + bool fastlock; + bool viterbi; + bool hard_metric; + bool resample; + float resample_rej; // Approx. filter rejection in dB + int rrc_steps; // Discrete steps between symbols, 0=auto + float rrc_rej; // Approx. RRC filter rejection in dB + float rolloff; // Roll-off 0..1 + bool hdlc; // Expect HDLC frames instead of MPEG packets + 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) + { + } +}; + + +struct DATVConfig +{ + 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; + + DATVConfig() : + intMsps(1024000), + intRFBandwidth(1024000), + intCenterFrequency(0), + enmStandard(DVB_S), + enmModulation(BPSK), + enmFEC(FEC12), + intSampleRate(1024000), + intSymbolRate(250000), + intNotchFilters(1), + blnAllowDrift(false), + blnFastLock(false), + blnHDLC(false), + blnHardMetric(false), + blnResample(false), + blnViterbi(false) + { + } +}; + + +class DATVDemod : public BasebandSampleSink, public ChannelSinkAPI +{ + Q_OBJECT + +public: + + DATVDemod(DeviceSourceAPI *); + ~DATVDemod(); + + virtual void destroy() { delete this; } + virtual void getIdentifier(QString& id) { id = objectName(); } + virtual void getTitle(QString& title) { title = objectName(); } + virtual qint64 getCenterFrequency() const { return m_objRunning.intCenterFrequency; } + + 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); + + 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); + 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 CleanUpDATVFramework(bool blnRelease); + int GetSampleRate(); + void InitDATVFramework(); + + static const QString m_channelIdURI; + static const QString m_channelId; + + + 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) + { } + }; + + + +private: + + unsigned long m_lngExpectedReadIQ; + unsigned long m_lngReadIQ; + + //************** LEANDBV Parameters ************** + + unsigned long BUF_BASEBAND; + unsigned long BUF_SYMBOLS; + unsigned long BUF_BYTES; + unsigned long BUF_MPEGBYTES; + unsigned long BUF_PACKETS; + unsigned long BUF_SLOW; + + //************** LEANDBV Scheduler *************** + + scheduler * m_objScheduler; + struct config m_objCfg; + + bool m_blnDVBInitialized; + bool m_blnNeedConfigUpdate; + + //LeanSDR Pipe Buffer + // INPUT + + pipebuf *p_rawiq; + pipewriter *p_rawiq_writer; + pipebuf *p_preprocessed; + + // NOTCH FILTER + auto_notch *r_auto_notch; + pipebuf *p_autonotched; + + // FREQUENCY CORRECTION : DEROTATOR + pipebuf *p_derot; + rotator *r_derot; + + // CNR ESTIMATION + pipebuf *p_cnr; + cnr_fft *r_cnr; + + //FILTERING + fir_filter *r_resample; + pipebuf *p_resampled; + float *coeffs; + int ncoeffs; + + // OUTPUT PREPROCESSED DATA + sampler_interface *sampler; + float *coeffs_sampler; + int ncoeffs_sampler; + + pipebuf *p_symbols; + pipebuf *p_freq; + pipebuf *p_ss; + pipebuf *p_mer; + pipebuf *p_sampled; + + //DECIMATION + pipebuf *p_decimated; + decimator *p_decim; + + //PROCESSED DATA MONITORING + file_writer *r_ppout; + + //GENERIC CONSTELLATION RECEIVER + cstln_receiver *m_objDemodulator; + + // DECONVOLUTION AND SYNCHRONIZATION + pipebuf *p_bytes; + deconvol_sync_simple *r_deconv; + viterbi_sync *r; + pipebuf *p_descrambled; + pipebuf *p_frames; + + etr192_descrambler * r_etr192_descrambler; + hdlc_sync *r_sync; + + pipebuf *p_mpegbytes; + pipebuf *p_lock; + pipebuf *p_locktime; + mpeg_sync *r_sync_mpeg; + + + // DEINTERLEAVING + pipebuf< rspacket > *p_rspackets; + deinterleaver *r_deinter; + + // REED-SOLOMON + pipebuf *p_vbitcount; + pipebuf *p_verrcount; + pipebuf *p_rtspackets; + rs_decoder *r_rsdec; + + // BER ESTIMATION + pipebuf *p_vber; + rate_estimator *r_vber; + + // DERANDOMIZATION + pipebuf *p_tspackets; + derandomizer *r_derand; + + + //OUTPUT + file_writer *r_stdout; + datvvideoplayer *r_videoplayer; + + //CONSTELLATION + datvconstellation *r_scope_symbols; + + +private: + + DeviceSourceAPI* m_deviceAPI; + + ThreadedBasebandSampleSink* m_threadedChannelizer; + DownChannelizer* m_channelizer; + + //*************** 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; + } + }; + + + DATVConfig m_objRunning; + + QMutex m_objSettingsMutex; + + void ApplySettings(); + +}; + +#endif // INCLUDE_DATVDEMOD_H diff --git a/plugins/channelrx/demoddatv/datvdemodgui.cpp b/plugins/channelrx/demoddatv/datvdemodgui.cpp new file mode 100644 index 000000000..254d6049f --- /dev/null +++ b/plugins/channelrx/demoddatv/datvdemodgui.cpp @@ -0,0 +1,754 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4HKW // +// for F4EXB / SDRAngel // +// using LeanSDR Framework (C) 2016 F4DAV // +// // +// This 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 "datvdemodgui.h" +#include "datvideostream.h" + +#include "device/devicesourceapi.h" +#include "device/deviceuiset.h" +#include "device/devicesourceapi.h" +#include "dsp/downchannelizer.h" + +#include "dsp/threadedbasebandsamplesink.h" +#include "ui_datvdemodgui.h" +#include "plugin/pluginapi.h" +#include "util/simpleserializer.h" +#include "util/db.h" +#include "dsp/dspengine.h" +#include "mainwindow.h" + +const QString DATVDemodGUI::m_strChannelID = "sdrangel.channel.demoddatv"; + +DATVDemodGUI* DATVDemodGUI::create(PluginAPI* objPluginAPI, + DeviceUISet *deviceUISet, + BasebandSampleSink *rxChannel) +{ + DATVDemodGUI* gui = new DATVDemodGUI(objPluginAPI, deviceUISet, rxChannel); + return gui; +} + +void DATVDemodGUI::destroy() +{ + delete this; +} + +void DATVDemodGUI::setName(const QString& strName) +{ + setObjectName(strName); +} + +QString DATVDemodGUI::getName() const +{ + return objectName(); +} + +qint64 DATVDemodGUI::getCenterFrequency() const +{ + return m_objChannelMarker.getCenterFrequency(); +} + +void DATVDemodGUI::setCenterFrequency(qint64 intCenterFrequency) +{ + m_objChannelMarker.setCenterFrequency(intCenterFrequency); + applySettings(); +} + +void DATVDemodGUI::resetToDefaults() +{ + blockApplySettings(true); + + 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->spiNotchFilters->setValue(1); + ui->prgSynchro->setValue(0); + + ui->lblStatus->setText(""); + + ui->spiBandwidth->setValue(512000); + ui->spiSymbolRate->setValue(250000); + + blockApplySettings(false); + + applySettings(); +} + +QByteArray DATVDemodGUI::serialize() const +{ + SimpleSerializer s(1); + + s.writeS32(1, m_objChannelMarker.getCenterFrequency()); + s.writeU32(2, m_objChannelMarker.getColor().rgb()); + + s.writeBool(3, ui->chkAllowDrift->isChecked()); + s.writeBool(4, ui->chkFastlock->isChecked()); + s.writeBool(5, ui->chkHDLC->isChecked()); + s.writeBool(6, ui->chkHardMetric->isChecked()); + s.writeBool(7, ui->chkResample->isChecked()); + s.writeBool(8, ui->chkViterbi->isChecked()); + + s.writeS32(9, ui->cmbFEC->currentIndex()); + s.writeS32(10, ui->cmbModulation->currentIndex()); + s.writeS32(11, ui->cmbStandard->currentIndex()); + + s.writeS32(12, ui->spiNotchFilters->value()); + s.writeS32(13, ui->spiBandwidth->value()); + s.writeS32(14, ui->spiSymbolRate->value()); + + return s.final(); +} + +bool DATVDemodGUI::deserialize(const QByteArray& arrData) +{ + SimpleDeserializer d(arrData); + + if (!d.isValid()) + { + resetToDefaults(); + return false; + } + + if (d.getVersion() == 1) + { + QByteArray bytetmp; + uint32_t u32tmp; + int tmp; + bool booltmp; + + blockApplySettings(true); + m_objChannelMarker.blockSignals(true); + + d.readS32(1, &tmp, 0); + m_objChannelMarker.setCenterFrequency(tmp); + + if (d.readU32(2, &u32tmp)) + { + m_objChannelMarker.setColor(u32tmp); + } + else + { + m_objChannelMarker.setColor(Qt::magenta); + } + + d.readBool(3, &booltmp, false); + ui->chkAllowDrift->setChecked(booltmp); + + d.readBool(4, &booltmp, false); + ui->chkFastlock->setChecked(booltmp); + + d.readBool(5, &booltmp, false); + ui->chkHDLC->setChecked(booltmp); + + d.readBool(6, &booltmp, false); + ui->chkHardMetric->setChecked(booltmp); + + d.readBool(7, &booltmp, false); + ui->chkResample->setChecked(booltmp); + + d.readBool(8, &booltmp, false); + ui->chkViterbi->setChecked(booltmp); + + + d.readS32(9, &tmp, 0); + ui->cmbFEC->setCurrentIndex(tmp); + + d.readS32(10, &tmp, 0); + ui->cmbModulation->setCurrentIndex(tmp); + + d.readS32(11, &tmp, 0); + ui->cmbStandard->setCurrentIndex(tmp); + + d.readS32(12, &tmp, 1); + ui->spiNotchFilters->setValue(tmp); + + d.readS32(13, &tmp, 1024000); + ui->spiBandwidth->setValue(tmp); + + d.readS32(14, &tmp, 250000); + ui->spiSymbolRate->setValue(tmp); + + + blockApplySettings(false); + m_objChannelMarker.blockSignals(false); + + applySettings(); + return true; + } + else + { + resetToDefaults(); + return false; + } +} + +bool DATVDemodGUI::handleMessage(const Message& objMessage) +{ + return false; +} + +void DATVDemodGUI::channelMarkerChangedByCursor() +{ + if(m_intCenterFrequency!=m_objChannelMarker.getCenterFrequency()) + { + m_intCenterFrequency=m_objChannelMarker.getCenterFrequency(); + applySettings(); + } +} + +void DATVDemodGUI::channelMarkerHighlightedByCursor() +{ + setHighlighted(m_objChannelMarker.getHighlighted()); +} + +void DATVDemodGUI::channelSampleRateChanged() +{ + qDebug("DATVDemodGUI::channelSampleRateChanged"); + applySettings(); +} + +void DATVDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown) +{ +} + +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) : +DATVDemodGUI::DATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* objParent) : + RollupWidget(objParent), + ui(new Ui::DATVDemodGUI), + m_objPluginAPI(objPluginAPI), + m_deviceUISet(deviceUISet), + m_objChannelMarker(this), + m_blnBasicSettingsShown(false), + m_blnDoApplySettings(true) +{ + ui->setupUi(this); + 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); + + 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 + + m_intPreviousDecodedData=0; + m_intLastDecodedData=0; + m_intLastSpeed=0; + m_intReadyDecodedData=0; + m_blnButtonPlayClicked=false; + m_objTimer.setInterval(1000); + connect(&m_objTimer, SIGNAL(timeout()), this, SLOT(tick())); + m_objTimer.start(); + + + m_objChannelMarker.blockSignals(true); + m_objChannelMarker.setColor(Qt::magenta); + m_objChannelMarker.setBandwidth(6000000); + 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())); + + m_deviceUISet->registerRxChannelInstance(DATVDemod::m_channelIdURI, this); + 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() + +} + +DATVDemodGUI::~DATVDemodGUI() +{ + + m_deviceUISet->removeRxChannelInstance(this); + delete m_objDATVDemod; + delete ui; + +} + +void DATVDemodGUI::blockApplySettings(bool blnBlock) +{ + m_blnDoApplySettings = !blnBlock; +} + +void DATVDemodGUI::applySettings() +{ + QString strStandard; + QString strModulation; + QString strFEC; + + DATVModulation enmSelectedModulation; + dvb_version enmVersion; + code_rate enmFEC; + + if (m_blnDoApplySettings) + { + + + 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") + { + enmVersion=DVB_S; + } + else if (strStandard=="DVB-S2") + { + enmVersion=DVB_S2; + } + else + { + enmVersion=DVB_S; + } + + + //BPSK, QPSK, PSK8, APSK16, APSK32, APSK64E, QAM16, QAM64, QAM256 + + strModulation = ui->cmbModulation->currentText(); + + if(strModulation=="BPSK") + { + enmSelectedModulation=BPSK; + } + else if(strModulation=="QPSK") + { + enmSelectedModulation=QPSK; + } + else if(strModulation=="8PSK") + { + enmSelectedModulation=PSK8; + } + else if(strModulation=="16APSK") + { + enmSelectedModulation=APSK16; + } + else if(strModulation=="32APSK") + { + enmSelectedModulation=APSK32; + } + else if(strModulation=="64APSKE") + { + enmSelectedModulation=APSK64E; + } + else if(strModulation=="16QAM") + { + enmSelectedModulation=QAM16; + } + else if(strModulation=="64QAM") + { + enmSelectedModulation=QAM64; + } + else if(strModulation=="256QAM") + { + enmSelectedModulation=QAM256; + } + else + { + enmSelectedModulation=BPSK; + } + + + strFEC = ui->cmbFEC->currentText(); + + if(strFEC=="1/2") + { + enmFEC=FEC12; + } + else if(strFEC=="2/3") + { + enmFEC=FEC23; + } + else if(strFEC=="3/4") + { + enmFEC=FEC34; + } + else if(strFEC=="5/6") + { + enmFEC=FEC56; + } + else if(strFEC=="7/8") + { + enmFEC=FEC78; + } + else if(strFEC=="4/5") + { + enmFEC=FEC45; + } + else if(strFEC=="8/9") + { + enmFEC=FEC89; + } + else if(strFEC=="9/10") + { + enmFEC=FEC910; + } + else + { + enmFEC=FEC12; + } + + + 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()); + + qDebug() << "DATVDemodGUI::applySettings:" + << " .inputSampleRate: " << 0 /*m_objChannelizer->getInputSampleRate()*/ + << " m_objDATVDemod.sampleRate: " << m_objDATVDemod->GetSampleRate(); + + + } +} + +void DATVDemodGUI::leaveEvent(QEvent*) +{ + blockApplySettings(true); + m_objChannelMarker.setHighlighted(false); + blockApplySettings(false); +} + +void DATVDemodGUI::enterEvent(QEvent*) +{ + blockApplySettings(true); + m_objChannelMarker.setHighlighted(true); + blockApplySettings(false); +} + +void DATVDemodGUI::tick() +{ + if((m_intLastDecodedData-m_intPreviousDecodedData)>=0) + { + m_intLastSpeed = 8*(m_intLastDecodedData-m_intPreviousDecodedData); + ui->lblRate->setText(QString("Speed: %1b/s").arg(formatBytes(m_intLastSpeed))); + } + + m_intPreviousDecodedData = m_intLastDecodedData; + + //Try to start video rendering + m_objDATVDemod->PlayVideo(false); + + return; +} + +void DATVDemodGUI::on_cmbStandard_currentIndexChanged(const QString &arg1) +{ + applySettings(); +} + +void DATVDemodGUI::on_cmbModulation_currentIndexChanged(const QString &arg1) +{ + QString strModulation; + QString strFEC; + + strFEC = ui->cmbFEC->currentText(); + + strModulation = ui->cmbModulation->currentText(); + + if(strModulation=="16APSK") + { + if((strFEC!="2/3") + && (strFEC!="3/4") + && (strFEC!="4/5") + && (strFEC!="5/6") + && (strFEC!="4/6") + && (strFEC!="8/9") + && (strFEC!="9/10")) + { + //Reset modulation to 2/3 + + ui->cmbFEC->setCurrentIndex(1); + } + else + { + applySettings(); + } + } + else if(strModulation=="32APSK") + { + if((strFEC!="3/4") + && (strFEC!="4/5") + && (strFEC!="5/6") + && (strFEC!="8/9") + && (strFEC!="9/10")) + { + //Reset modulation to 3/4 + + ui->cmbFEC->setCurrentIndex(2); + } + else + { + applySettings(); + } + } + else + { + applySettings(); + } + +} + +void DATVDemodGUI::on_cmbFEC_currentIndexChanged(const QString &arg1) +{ + QString strFEC; + + strFEC = ui->cmbFEC->currentText(); + + if(ui->cmbModulation->currentText()=="16APSK") + { + if((strFEC!="2/3") + && (strFEC!="3/4") + && (strFEC!="4/5") + && (strFEC!="5/6") + && (strFEC!="4/6") + && (strFEC!="8/9") + && (strFEC!="9/10")) + { + //Reset modulation to BPSK + + ui->cmbModulation->setCurrentIndex(0); + } + else + { + applySettings(); + } + } + else if(ui->cmbModulation->currentText()=="32APSK") + { + if((strFEC!="3/4") + && (strFEC!="4/5") + && (strFEC!="5/6") + && (strFEC!="8/9") + && (strFEC!="9/10")) + { + //Reset modulation to BPSK + + ui->cmbModulation->setCurrentIndex(0); + } + else + { + applySettings(); + } + } + else + { + applySettings(); + } + +} + +void DATVDemodGUI::on_chkViterbi_clicked() +{ + applySettings(); +} + +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) +{ + applySettings(); +} + +void DATVDemodGUI::on_spiNotchFilters_valueChanged(int arg1) +{ + 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() +{ + + m_blnButtonPlayClicked=true; + + if(m_objDATVDemod!=NULL) + { + m_objDATVDemod->PlayVideo(true); + } +} + +/* +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() +{ + ui->screenTV_2->SetFullScreen(true); + +} + +void DATVDemodGUI::on_mouseEvent(QMouseEvent* obj) +{ +} + +QString DATVDemodGUI::formatBytes(qint64 intBytes) +{ + if(intBytes<1024) + { + return QString("%1").arg(intBytes); + } + else if(intBytes<1024*1024) + { + return QString("%1 K").arg((float)(10*intBytes/1024)/10.0f); + } + else if(intBytes<1024*1024*1024) + { + return QString("%1 M").arg((float)(10*intBytes/(1024*1024))/10.0f); + } + + return QString("%1 G").arg((float)(10*intBytes/(1024*1024*1024))/10.0f); +} + + +void DATVDemodGUI::on_StreamDataAvailable(int *intPackets, int *intBytes, int *intPercent, qint64 *intTotalReceived) +{ + ui->lblStatus->setText(QString("Decod: %1B").arg(formatBytes(*intTotalReceived))); + m_intLastDecodedData = *intTotalReceived; + + if((*intPercent)<100) + { + ui->prgSynchro->setValue(*intPercent); + } + else + { + ui->prgSynchro->setValue(100); + } + + m_intReadyDecodedData = *intBytes; + +} + +void DATVDemodGUI::on_spiBandwidth_valueChanged(int arg1) +{ + applySettings(); +} + + +void DATVDemodGUI::on_chkFastlock_clicked() +{ + applySettings(); +} diff --git a/plugins/channelrx/demoddatv/datvdemodgui.h b/plugins/channelrx/demoddatv/datvdemodgui.h new file mode 100644 index 000000000..1f1052d09 --- /dev/null +++ b/plugins/channelrx/demoddatv/datvdemodgui.h @@ -0,0 +1,147 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4HKW // +// for F4EXB / SDRAngel // +// using LeanSDR Framework (C) 2016 F4DAV // +// // +// This 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_DATVDEMODGUI_H +#define INCLUDE_DATVDEMODGUI_H + +#include "gui/rollupwidget.h" +#include +#include "dsp/channelmarker.h" +#include "dsp/movingaverage.h" + +#include "datvdemod.h" + +#include +#include +#include + + +class PluginAPI; +class DeviceUISet; +class BasebandSampleSink; +//class DeviceSourceAPI; +//class ThreadedBasebandSampleSink; +class DownChannelizer; +//class DATVDemod; + +namespace Ui +{ + class DATVDemodGUI; +} + +class DATVDemodGUI : public RollupWidget, public PluginInstanceGUI +{ + Q_OBJECT + +public: + static DATVDemodGUI* create(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel); + virtual void destroy(); + + void setName(const QString& strName); + QString getName() const; + virtual qint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 intCenterFrequency); + + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& arrData); + + virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + + virtual bool handleMessage(const Message& objMessage); + + static const QString m_strChannelID; + +private slots: + void channelMarkerChangedByCursor(); + void channelMarkerHighlightedByCursor(); + + void channelSampleRateChanged(); + void onWidgetRolled(QWidget* widget, bool rollDown); + void onMenuDoubleClicked(); + void tick(); + + void on_cmbStandard_currentIndexChanged(const QString &arg1); + void on_cmbModulation_currentIndexChanged(const QString &arg1); + 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_spiBandwidth_valueChanged(int arg1); + + + void on_chkFastlock_clicked(); + +private: + Ui::DATVDemodGUI* ui; + PluginAPI* m_objPluginAPI; + DeviceUISet* m_deviceUISet; + + //DeviceSourceAPI* m_objDeviceAPI; + ChannelMarker m_objChannelMarker; + ThreadedBasebandSampleSink* m_objThreadedChannelizer; + DownChannelizer* m_objChannelizer; + DATVDemod* m_objDATVDemod; + MessageQueue m_inputMessageQueue; + int m_intCenterFrequency; + + QTimer m_objTimer; + qint64 m_intPreviousDecodedData; + qint64 m_intLastDecodedData; + qint64 m_intLastSpeed; + int m_intReadyDecodedData; + + bool m_blnBasicSettingsShown; + bool m_blnDoApplySettings; + bool m_blnButtonPlayClicked; + + //explicit DATVDemodGUI(PluginAPI* objPluginAPI, DeviceSourceAPI *objDeviceAPI, QWidget* objParent = NULL); + explicit DATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* objParent = 0); + virtual ~DATVDemodGUI(); + + void blockApplySettings(bool blnBlock); + void applySettings(); + QString formatBytes(qint64 intBytes); + + void leaveEvent(QEvent*); + void enterEvent(QEvent*); +}; + +#endif // INCLUDE_DATVDEMODGUI_H diff --git a/plugins/channelrx/demoddatv/datvdemodgui.ui b/plugins/channelrx/demoddatv/datvdemodgui.ui new file mode 100644 index 000000000..4b374e721 --- /dev/null +++ b/plugins/channelrx/demoddatv/datvdemodgui.ui @@ -0,0 +1,637 @@ + + + DATVDemodGUI + + + + 0 + 0 + 512 + 520 + + + + + 0 + 0 + + + + + 512 + 520 + + + + + 512 + 520 + + + + + Sans Serif + 9 + + + + Qt::StrongFocus + + + DATV Demodulator + + + + + 10 + 0 + 496 + 250 + + + + + 496 + 250 + + + + + 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 + 260 + 496 + 240 + + + + + 496 + 240 + + + + + 496 + 240 + + + + VIDEO Stream + + + + + 0 + 20 + 358 + 211 + + + + + QLayout::SetMinimumSize + + + + + + 0 + 0 + + + + + 356 + 200 + + + + + 356 + 200 + + + + + + + + + + + + + 360 + 20 + 131 + 211 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + 10 + 10 + 111 + 27 + + + + Video + + + + + + 10 + 50 + 111 + 27 + + + + Full Screen + + + + + + 10 + 120 + 111 + 16 + + + + - + + + + + + 10 + 90 + 111 + 16 + + + + - + + + + + + + + RollupWidget + QWidget +
gui/rollupwidget.h
+ 1 +
+ + DATVScreen + QWidget +
datvscreen.h
+
+ + DATVideoRender + QWidget +
datvideorender.h
+ 1 +
+
+ + +
diff --git a/plugins/channelrx/demoddatv/datvdemodplugin.cpp b/plugins/channelrx/demoddatv/datvdemodplugin.cpp new file mode 100644 index 000000000..4b52fbc09 --- /dev/null +++ b/plugins/channelrx/demoddatv/datvdemodplugin.cpp @@ -0,0 +1,72 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4HKW // +// for F4EXB / SDRAngel // +// using LeanSDR Framework (C) 2016 F4DAV // +// // +// This 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 "datvdemodgui.h" +#include "datvdemodplugin.h" + +const PluginDescriptor DATVDemodPlugin::m_ptrPluginDescriptor = +{ + QString("DATV Demodulator"), + QString("3.2.0"), + QString("(c) F4HKW for SDRAngel using LeanSDR framework (c) F4DAV"), + QString("https://github.com/f4exb/sdrangel"), + true, + QString("https://github.com/f4exb/sdrangel") +}; + +DATVDemodPlugin::DATVDemodPlugin(QObject* ptrParent) : + QObject(ptrParent), + m_ptrPluginAPI(NULL) +{ + +} + +const PluginDescriptor& DATVDemodPlugin::getPluginDescriptor() const +{ + return m_ptrPluginDescriptor; + +} + +void DATVDemodPlugin::initPlugin(PluginAPI* ptrPluginAPI) +{ + m_ptrPluginAPI = ptrPluginAPI; + + // register DATV demodulator + m_ptrPluginAPI->registerRxChannel(DATVDemod::m_channelIdURI, DATVDemod::m_channelId, this); + +} + +PluginInstanceGUI* DATVDemodPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) +{ + return DATVDemodGUI::create(m_ptrPluginAPI, deviceUISet, rxChannel); +} + +BasebandSampleSink* DATVDemodPlugin::createRxChannelBS(DeviceSourceAPI *deviceAPI) +{ + return new DATVDemod(deviceAPI); +} + +ChannelSinkAPI* DATVDemodPlugin::createRxChannelCS(DeviceSourceAPI *deviceAPI) +{ + return new DATVDemod(deviceAPI); +} diff --git a/plugins/channelrx/demoddatv/datvdemodplugin.h b/plugins/channelrx/demoddatv/datvdemodplugin.h new file mode 100644 index 000000000..f2f0ca3ec --- /dev/null +++ b/plugins/channelrx/demoddatv/datvdemodplugin.h @@ -0,0 +1,53 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 F4HKW // +// for F4EXB / SDRAngel // +// using LeanSDR Framework (C) 2016 F4DAV // +// // +// This 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_DATVPLUGIN_H +#define INCLUDE_DATVPLUGIN_H + +#include +#include "plugin/plugininterface.h" + +class DeviceUISet; +class BasebandSampleSink; + +class DATVDemodPlugin : public QObject, PluginInterface +{ + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID "sdrangel.channel.demoddatv") + +public: + explicit DATVDemodPlugin(QObject* ptrParent = NULL); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* ptrPluginAPI); + + virtual PluginInstanceGUI* createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel); + virtual BasebandSampleSink* createRxChannelBS(DeviceSourceAPI *deviceAPI); + virtual ChannelSinkAPI* createRxChannelCS(DeviceSourceAPI *deviceAPI); + + +private: + static const PluginDescriptor m_ptrPluginDescriptor; + + PluginAPI* m_ptrPluginAPI; + + +}; + +#endif // INCLUDE_DATVPLUGIN_H diff --git a/plugins/channelrx/demoddatv/datvideorender.cpp b/plugins/channelrx/demoddatv/datvideorender.cpp new file mode 100644 index 000000000..c16cc4a0a --- /dev/null +++ b/plugins/channelrx/demoddatv/datvideorender.cpp @@ -0,0 +1,557 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 "datvideorender.h" + +DATVideoRender::DATVideoRender(QWidget * parent): + DATVScreen(parent) +{ + installEventFilter(this); + m_blnIsFullScreen=false; + m_blnRunning=false; + + m_blnIsFFMPEGInitialized=false; + m_blnIsOpen=false; + m_objFormatCtx=NULL; + m_objDecoderCtx=NULL; + m_objSwsCtx=NULL; + m_intVideoStreamIndex=-1; + + m_intCurrentRenderWidth=-1; + m_intCurrentRenderHeight=-1; + + m_objFrame=NULL; + m_intFrameCount=-1; +} + +bool DATVideoRender::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::MouseButtonRelease) + { + SetFullScreen(false); + return true; + } + else + { + // standard event processing + return QObject::eventFilter(obj, event); + } +} + + +void DATVideoRender::SetFullScreen(bool blnFullScreen) +{ + if(m_blnIsFullScreen==blnFullScreen) + { + return; + } + + if(blnFullScreen==true) + { + setWindowFlags(Qt::Window); + setWindowState(Qt::WindowFullScreen); + show(); + + m_blnIsFullScreen=true; + } + else + { + setWindowFlags(Qt::Widget); + setWindowState(Qt::WindowNoState); + show(); + + m_blnIsFullScreen=false; + } +} + + +static int ReadFunction(void *opaque, uint8_t *buf, int buf_size) +{ + QIODevice* objStream = reinterpret_cast(opaque); + int intNbBytes = objStream->read((char*)buf, buf_size); + return intNbBytes; +} + +static int64_t SeekFunction(void* opaque, int64_t offset, int whence) +{ + QIODevice* objStream = reinterpret_cast(opaque); + + if (whence == AVSEEK_SIZE) + { + return -1; + } + + if (objStream->isSequential()) + { + return -1; + } + + if (objStream->seek(offset)==false) + { + return -1; + } + + return objStream->pos(); +} + +bool DATVideoRender::InitializeFFMPEG() +{ + MetaData.CodecID=-1; + MetaData.PID=-1; + MetaData.Program=""; + MetaData.Stream=""; + MetaData.Width=-1; + MetaData.Height=-1; + MetaData.BitRate=-1; + MetaData.Channels=-1; + MetaData.CodecDescription= ""; + + if(m_blnIsFFMPEGInitialized==true) + { + return false; + } + + avcodec_register_all(); + av_register_all(); + + av_log_set_level(AV_LOG_FATAL); + //av_log_set_level(AV_LOG_ERROR); + + m_blnIsFFMPEGInitialized=true; + + return true; +} + +bool DATVideoRender::PreprocessStream() +{ + AVDictionary *objOpts = NULL; + AVCodec *objCodec = NULL; + + int intRet=-1; + char *objBuffer=NULL; + + //Identify stream + + if (avformat_find_stream_info(m_objFormatCtx, NULL) < 0) + { + avformat_close_input(&m_objFormatCtx); + m_objFormatCtx=NULL; + + qDebug() << "DATVideoProcess::PreprocessStream cannot find stream info"; + return false; + } + + //Find video stream + intRet = av_find_best_stream(m_objFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); + + if (intRet < 0) + { + avformat_close_input(&m_objFormatCtx); + qDebug() << "DATVideoProcess::PreprocessStream cannot find video stream"; + return false; + } + + m_intVideoStreamIndex = intRet; + + //Prepare Codec and extract meta data + + 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.Program=""; + MetaData.Stream=""; + + if(m_objFormatCtx->programs) + { + objBuffer=NULL; + + av_dict_get_string(m_objFormatCtx->programs[m_intVideoStreamIndex]->metadata,&objBuffer,':','\n'); + if(objBuffer!=NULL) + { + MetaData.Program = QString("%1").arg(objBuffer); + } + } + + objBuffer=NULL; + + av_dict_get_string(m_objFormatCtx->streams[m_intVideoStreamIndex]->metadata,&objBuffer,':','\n'); + + if(objBuffer!=NULL) + { + MetaData.Stream = QString("%1").arg(objBuffer); + } + + //Decoder + objCodec = avcodec_find_decoder(m_objDecoderCtx->codec_id); + if(objCodec==NULL) + { + avformat_close_input(&m_objFormatCtx); + m_objFormatCtx=NULL; + + qDebug() << "DATVideoProcess::PreprocessStream cannot find associated CODEC"; + return false; + } + + av_dict_set(&objOpts, "refcounted_frames", "1", 0); + + if (avcodec_open2(m_objDecoderCtx, objCodec, &objOpts) < 0) + { + avformat_close_input(&m_objFormatCtx); + m_objFormatCtx=NULL; + + qDebug() << "DATVideoProcess::PreprocessStream cannot open associated CODEC"; + return false; + } + + //Allocate Frame + m_objFrame = av_frame_alloc(); + + if (!m_objFrame) + { + avformat_close_input(&m_objFormatCtx); + m_objFormatCtx=NULL; + + qDebug() << "DATVideoProcess::PreprocessStream cannot allocate frame"; + return false; + } + + m_intFrameCount=0; + + + MetaData.Width=m_objDecoderCtx->width; + MetaData.Height=m_objDecoderCtx->height; + MetaData.BitRate=m_objDecoderCtx->bit_rate; + MetaData.Channels=m_objDecoderCtx->channels; + MetaData.CodecDescription= QString("%1").arg(objCodec->long_name); + + return true; +} + +bool DATVideoRender::OpenStream(DATVideostream *objDevice) +{ + int intIOBufferSize = 32768; + unsigned char * ptrIOBuffer = NULL; + AVIOContext * objIOCtx = NULL; + + if(m_blnRunning==true) + { + return false; + } + + //Only once execution + m_blnRunning=true; + + if(objDevice==NULL) + { + qDebug() << "DATVideoProcess::OpenStream QIODevice is NULL"; + + return false; + } + + if(m_blnIsOpen==true) + { + qDebug() << "DATVideoProcess::OpenStream already open"; + + return false; + } + + InitializeFFMPEG(); + + if(!m_blnIsFFMPEGInitialized) + { + qDebug() << "DATVideoProcess::OpenStream FFMPEG not initialized"; + + return false; + } + + if(!objDevice->open(QIODevice::ReadOnly)) + { + qDebug() << "DATVideoProcess::OpenStream cannot open QIODevice"; + + return false; + } + + //Connect QIODevice to FFMPEG Reader + + m_objFormatCtx = avformat_alloc_context(); + + if(m_objFormatCtx==NULL) + { + qDebug() << "DATVideoProcess::OpenStream cannot alloc format FFMPEG context"; + + return false; + } + + ptrIOBuffer = (unsigned char *)av_malloc(intIOBufferSize+ FF_INPUT_BUFFER_PADDING_SIZE); + + objIOCtx = avio_alloc_context( ptrIOBuffer, + intIOBufferSize, + 0, + reinterpret_cast(objDevice), + &ReadFunction, + NULL, + &SeekFunction); + + m_objFormatCtx->pb = objIOCtx; + m_objFormatCtx->flags |= AVFMT_FLAG_CUSTOM_IO; + + + if (avformat_open_input(&m_objFormatCtx, NULL , NULL, NULL) < 0) + { + qDebug() << "DATVideoProcess::OpenStream cannot open stream"; + + return false; + } + + + if(!PreprocessStream()) + { + return false; + } + + + m_blnIsOpen=true; + + m_blnRunning=false; + + return true; +} + +bool DATVideoRender::RenderStream() +{ + AVPacket objPacket; + int intGotFrame; + bool blnNeedRenderingSetup; + + if(m_blnIsOpen=false) + { + qDebug() << "DATVideoProcess::RenderStream Stream not open"; + + return false; + } + + if(m_blnRunning==true) + { + return false; + } + + //Only once execution + m_blnRunning=true; + + //********** Rendering ********** + + if (av_read_frame(m_objFormatCtx, &objPacket) < 0) + { + qDebug() << "DATVideoProcess::RenderStream reading packet error"; + + m_blnRunning=false; + return false; + } + + //Video channel + if (objPacket.stream_index == m_intVideoStreamIndex) + { + memset(m_objFrame, 0, sizeof(AVFrame)); + av_frame_unref(m_objFrame); + + intGotFrame=0; + + if(avcodec_decode_video2( m_objDecoderCtx, m_objFrame, &intGotFrame, &objPacket)<0) + { + qDebug() << "DATVideoProcess::RenderStream decoding packet error"; + + m_blnRunning=false; + return false; + } + + if(intGotFrame) + { + //Rendering and RGB Converter setup + + blnNeedRenderingSetup=(m_intFrameCount==0); + blnNeedRenderingSetup|=(m_objSwsCtx==NULL); + + if((m_intCurrentRenderWidth!=m_objFrame->width) || (m_intCurrentRenderHeight!=m_objFrame->height)) + { + blnNeedRenderingSetup=true; + } + + if(blnNeedRenderingSetup) + { + if(m_objSwsCtx!=NULL) + { + sws_freeContext(m_objSwsCtx); + m_objSwsCtx=NULL; + } + + //Convertisseur YUV -> RGB + m_objSwsCtx = sws_alloc_context(); + + av_opt_set_int(m_objSwsCtx,"srcw",m_objFrame->width,0); + av_opt_set_int(m_objSwsCtx,"srch",m_objFrame->height,0); + av_opt_set_int(m_objSwsCtx,"src_format",m_objFrame->format,0); + + av_opt_set_int(m_objSwsCtx,"dstw",m_objFrame->width,0); + 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); + + if(sws_init_context(m_objSwsCtx, NULL, NULL)<0) + { + qDebug() << "DATVideoProcess::RenderStream cannont init video data converter"; + + m_objSwsCtx=NULL; + + m_blnRunning=false; + return false; + + } + + if((m_intCurrentRenderHeight>0) && (m_intCurrentRenderWidth>0)) + { + //av_freep(&m_pbytDecodedData[0]); + //av_freep(&m_pintDecodedLineSize[0]); + } + + if(av_image_alloc(m_pbytDecodedData, m_pintDecodedLineSize,m_objFrame->width, m_objFrame->height, AV_PIX_FMT_RGB24, 1)<0) + { + qDebug() << "DATVideoProcess::RenderStream cannont init video image buffer"; + + sws_freeContext(m_objSwsCtx); + m_objSwsCtx=NULL; + + m_blnRunning=false; + return false; + + } + + //Rendering device setup + + resizeDATVScreen(m_objFrame->width,m_objFrame->height); + update(); + resetImage(); + + m_intCurrentRenderWidth=m_objFrame->width; + m_intCurrentRenderHeight=m_objFrame->height; + + MetaData.Width = m_objFrame->width; + MetaData.Height = m_objFrame->height; + } + + //Frame rendering + + if(sws_scale(m_objSwsCtx, m_objFrame->data, m_objFrame->linesize, 0, m_objFrame->height, m_pbytDecodedData, m_pintDecodedLineSize)<0) + { + qDebug() << "DATVideoProcess::RenderStream error converting video frame to RGB"; + + m_blnRunning=false; + return false; + } + + renderImage(m_pbytDecodedData[0]); + + av_frame_unref(m_objFrame); + + m_intFrameCount ++; + } + + } + + av_free_packet(&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) + { + return false; + } + + //Only once execution + m_blnRunning=true; + + if(!objDevice) + { + qDebug() << "DATVideoProcess::CloseStream QIODevice is NULL"; + + return false; + } + + if(m_blnIsOpen=false) + { + qDebug() << "DATVideoProcess::CloseStream Stream not open"; + + return false; + } + + if(!m_objFormatCtx) + { + qDebug() << "DATVideoProcess::CloseStream FFMEG Context is not initialized"; + + return false; + } + + avformat_close_input(&m_objFormatCtx); + m_objFormatCtx=NULL; + + if(m_objDecoderCtx) + { + avcodec_close(m_objDecoderCtx); + m_objDecoderCtx=NULL; + } + + + if(m_objFrame) + { + av_frame_unref(m_objFrame); + av_frame_free(&m_objFrame); + } + + if(m_objSwsCtx!=NULL) + { + sws_freeContext(m_objSwsCtx); + m_objSwsCtx=NULL; + } + + objDevice->close(); + + m_blnIsOpen=false; + + m_blnRunning=false; + + m_intCurrentRenderWidth=-1; + m_intCurrentRenderHeight=-1; + + return true; +} diff --git a/plugins/channelrx/demoddatv/datvideorender.h b/plugins/channelrx/demoddatv/datvideorender.h new file mode 100644 index 000000000..a6fb1860d --- /dev/null +++ b/plugins/channelrx/demoddatv/datvideorender.h @@ -0,0 +1,194 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 DATVIDEORENDER_H +#define DATVIDEORENDER_H + +#include +#include +#include +#include + +#include "datvscreen.h" +#include "datvideostream.h" + +extern "C" +{ +#include "libavcodec/avcodec.h" +#include "libavformat/avformat.h" + +#include +#include +#include +#include +#include +#include + +#include "libswscale/swscale.h" + +} + +struct DataTSMetaData2 +{ + int PID; + int CodecID; + QString Program; + QString Stream; + + int Width; + int Height; + int BitRate; + int Channels; + QString CodecDescription; + + DataTSMetaData2() + { + PID=-1; + CodecID=-1; + Program=""; + Stream=""; + + Width=-1; + Height=-1; + BitRate=-1; + Channels=-1; + CodecDescription=""; + } +}; + +class DATVideoRender : public DATVScreen +{ + Q_OBJECT +public: + explicit DATVideoRender(QWidget * parent); + void SetFullScreen(bool blnFullScreen); + + bool OpenStream(DATVideostream *objDevice); + bool RenderStream(); + bool CloseStream(QIODevice *objDevice); + + struct DataTSMetaData2 MetaData; + +private: + bool m_blnRunning; + bool m_blnIsFullScreen; + + bool m_blnIsFFMPEGInitialized; + bool m_blnIsOpen; + + SwsContext *m_objSwsCtx; + AVFormatContext *m_objFormatCtx; + AVCodecContext *m_objDecoderCtx; + AVFrame *m_objFrame; + + uint8_t *m_pbytDecodedData[4]; + int m_pintDecodedLineSize[4]; + + int m_intFrameCount; + int m_intVideoStreamIndex; + + int m_intCurrentRenderWidth; + int m_intCurrentRenderHeight; + + bool InitializeFFMPEG(); + bool PreprocessStream(); + + + +protected: + virtual bool eventFilter(QObject *obj, QEvent *event); + +signals: + +public slots: + +}; + +class DATVideoRenderThread: public QThread +{ + + public: + DATVideoRenderThread() + { + m_objRenderer = NULL; + m_objStream = NULL; + m_blnRenderingVideo=false; + } + + DATVideoRenderThread(DATVideoRender *objRenderer, DATVideostream *objStream) + { + m_objRenderer = objRenderer; + m_objStream = objStream; + m_blnRenderingVideo=false; + } + + void setStreamAndRenderer(DATVideoRender *objRenderer, DATVideostream *objStream) + { + m_objRenderer = objRenderer; + m_objStream = objStream; + m_blnRenderingVideo=false; + + } + + void run() + { + if(m_blnRenderingVideo==true) + { + return; + } + + if((m_objRenderer==NULL) || (m_objStream==NULL)) + { + return ; + } + + m_blnRenderingVideo=false; + + if(m_objRenderer->OpenStream(m_objStream)) + { + 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; + } + + while((m_objRenderer->RenderStream()) && (m_blnRenderingVideo==true)) + { + } + + m_objRenderer->CloseStream(m_objStream); + + } + + void stopRendering() + { + m_blnRenderingVideo=false; + } + + + private: + + DATVideoRender *m_objRenderer; + DATVideostream *m_objStream; + bool m_blnRenderingVideo; +}; + +#endif // DATVIDEORENDER_H diff --git a/plugins/channelrx/demoddatv/datvideostream.cpp b/plugins/channelrx/demoddatv/datvideostream.cpp new file mode 100644 index 000000000..88807f353 --- /dev/null +++ b/plugins/channelrx/demoddatv/datvideostream.cpp @@ -0,0 +1,223 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 "datvideostream.h" +#include + +DATVideostream::DATVideostream(): + m_objMutex(QMutex::NonRecursive) +{ + cleanUp(); + m_intTotalReceived=0; + m_intPacketReceived=0; + m_intMemoryLimit = DefaultMemoryLimit; + MultiThreaded=false; + + m_objeventLoop.connect(this,SIGNAL(onDataAvailable()), &m_objeventLoop, SLOT(quit()),Qt::QueuedConnection); +} + +DATVideostream::~DATVideostream() +{ + m_objeventLoop.disconnect(this,SIGNAL(onDataAvailable()), &m_objeventLoop, SLOT(quit())); + cleanUp(); +} + +void DATVideostream::cleanUp() +{ + if(m_objFIFO.size()>0) + { + m_objFIFO.clear(); + } + + if(m_objeventLoop.isRunning()) + { + m_objeventLoop.exit(); + } + + m_objMutex.unlock(); + + m_intBytesAvailable=0; + m_intBytesWaiting=0; + m_intQueueWaiting=0; + m_intPercentBuffer=0; +} + +bool DATVideostream::setMemoryLimit(int intMemoryLimit) +{ + if(intMemoryLimit<=0) + { + return false; + } + + m_intMemoryLimit=intMemoryLimit; + + return true; +} + +int DATVideostream::pushData(const char * chrData, int intSize) +{ + if(intSize<=0) + { + return 0; + } + + m_objMutex.lock(); + + m_intPacketReceived ++; + + m_intBytesWaiting += intSize; + if(m_intBytesWaiting>m_intMemoryLimit) + { + m_intBytesWaiting -= m_objFIFO.dequeue().size(); + } + + m_objFIFO.enqueue(QByteArray(chrData,intSize)); + m_intBytesAvailable = m_objFIFO.head().size(); + m_intTotalReceived += intSize; + + m_intQueueWaiting=m_objFIFO.count(); + + m_objMutex.unlock(); + + if((m_objeventLoop.isRunning()) + && (m_intQueueWaiting>=MinStackSize)) + { + emit onDataAvailable(); + } + + if(m_intPacketReceived%MinStackSize==1) + { + + m_intPercentBuffer = (100*m_intBytesWaiting)/m_intMemoryLimit; + if(m_intPercentBuffer>100) + { + m_intPercentBuffer=100; + } + + emit onDataPackets(&m_intQueueWaiting, &m_intBytesWaiting, &m_intPercentBuffer, &m_intTotalReceived); + } + + return intSize; +} + +bool DATVideostream::isSequential() const +{ + return true; +} + +qint64 DATVideostream::bytesAvailable() const +{ + return m_intBytesAvailable; +} + +void DATVideostream::close() +{ + QIODevice::close(); + cleanUp(); +} + +bool DATVideostream::open(OpenMode mode) +{ + //cleanUp(); + return QIODevice::open(mode); +} + +//PROTECTED + +qint64 DATVideostream::readData(char *data, qint64 len) +{ + QByteArray objCurrentArray; + int intEffectiveLen=0; + int intExpectedLen=0; + + intExpectedLen = (int) len; + + if(intExpectedLen<=0) + { + return 0; + } + + if(m_objeventLoop.isRunning()) + { + return 0; + } + + m_objMutex.lock(); + + //DATA in FIFO ? -> Waiting for DATA + if((m_objFIFO.isEmpty()) || (m_objFIFO.count(). // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef DATVIDEOSTREAM_H +#define DATVIDEOSTREAM_H + +#include +#include +#include +#include +#include +#include + +#define MinStackSize 4 +#define DefaultMemoryLimit 2048000 + +class DATVideostream : public QIODevice +{ + Q_OBJECT + +public: + DATVideostream(); + ~DATVideostream(); + + bool MultiThreaded; + + int pushData(const char * chrData, int intSize); + bool setMemoryLimit(int intMemoryLimit); + + virtual bool isSequential() const; + virtual qint64 bytesAvailable() const; + virtual void close(); + virtual bool open(OpenMode mode); + + QQueue m_objFIFO; + +signals: + + void onDataAvailable(); + void onDataPackets(int *intDataPackets, int *intDataBytes, int *intPercentBuffer,qint64 *intTotalReceived); + +protected: + + virtual qint64 readData(char *data, qint64 len); + virtual qint64 writeData(const char *data, qint64 len); + virtual qint64 readLineData(char *data, qint64 maxSize); + +private: + + QEventLoop m_objeventLoop; + QMutex m_objMutex; + int m_intMemoryLimit; + int m_intBytesAvailable; + int m_intBytesWaiting; + int m_intQueueWaiting; + int m_intPercentBuffer; + qint64 m_intTotalReceived; + qint64 m_intPacketReceived; + + void cleanUp(); +}; + +#endif // DATVIDEOSTREAM_H diff --git a/plugins/channelrx/demoddatv/datvscreen.cpp b/plugins/channelrx/demoddatv/datvscreen.cpp new file mode 100644 index 000000000..ea74c249b --- /dev/null +++ b/plugins/channelrx/demoddatv/datvscreen.cpp @@ -0,0 +1,206 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 "datvscreen.h" + +#include +#include + +DATVScreen::DATVScreen(QWidget* parent) : + QGLWidget(parent), 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 = DATV_COLS; + m_intAskedRows = DATV_ROWS; + +} + +DATVScreen::~DATVScreen() +{ + cleanup(); +} + +QRgb* DATVScreen::getRowBuffer(int intRow) +{ + if (m_blnGLContextInitialized == false) + { + return NULL; + } + + return m_objGLShaderArray.GetRowBuffer(intRow); +} + +void DATVScreen::renderImage(unsigned char * objData) +{ + m_chrLastData = objData; + m_blnDataChanged = true; + //update(); +} + +void DATVScreen::resetImage() +{ + m_objGLShaderArray.ResetPixels(); +} + +void DATVScreen::resizeDATVScreen(int intCols, int intRows) +{ + m_intAskedCols = intCols; + m_intAskedRows = intRows; +} + +void DATVScreen::initializeGL() +{ + m_objMutex.lock(); + + QOpenGLContext *objGlCurrentContext = QOpenGLContext::currentContext(); + + if (objGlCurrentContext) + { + if (QOpenGLContext::currentContext()->isValid()) + { + qDebug() << "DATVScreen::initializeGL: context:" + << " major: " << (QOpenGLContext::currentContext()->format()).majorVersion() + << " minor: " << (QOpenGLContext::currentContext()->format()).minorVersion() + << " ES: " << (QOpenGLContext::currentContext()->isOpenGLES() ? "yes" : "no"); + } + else + { + qDebug() << "DATVScreen::initializeGL: current context is invalid"; + } + } + else + { + qCritical() << "DATVScreen::initializeGL: no current context"; + return; + } + + QSurface *objSurface = objGlCurrentContext->surface(); + + if (objSurface == NULL) + { + qCritical() << "DATVScreen::initializeGL: no surface attached"; + return; + } + else + { + if (objSurface->surfaceType() != QSurface::OpenGLSurface) + { + qCritical() << "DATVScreen::initializeGL: surface is not an OpenGLSurface: " + << objSurface->surfaceType() + << " cannot use an OpenGL context"; + return; + } + else + { + qDebug() << "DATVScreen::initializeGL: OpenGL surface:" + << " class: " << (objSurface->surfaceClass() == QSurface::Window ? "Window" : "Offscreen"); + } + } + + connect(objGlCurrentContext, &QOpenGLContext::aboutToBeDestroyed, this, + &DATVScreen::cleanup); // TODO: when migrating to QOpenGLWidget + + m_blnGLContextInitialized = true; + + m_objMutex.unlock(); +} + +void DATVScreen::resizeGL(int intWidth, int intHeight) +{ + QOpenGLFunctions *ptrF = QOpenGLContext::currentContext()->functions(); + ptrF->glViewport(0, 0, intWidth, intHeight); + m_blnConfigChanged = true; +} + +void DATVScreen::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 DATVScreen::mousePressEvent(QMouseEvent* event) +{ +} + +void DATVScreen::tick() +{ + if (m_blnDataChanged) { + update(); + } +} + +void DATVScreen::connectTimer(const QTimer& objTimer) +{ + qDebug() << "DATVScreen::connectTimer"; + disconnect(&m_objTimer, SIGNAL(timeout()), this, SLOT(tick())); + connect(&objTimer, SIGNAL(timeout()), this, SLOT(tick())); + m_objTimer.stop(); +} + +void DATVScreen::cleanup() +{ + if (m_blnGLContextInitialized) + { + m_objGLShaderArray.Cleanup(); + } +} + +bool DATVScreen::selectRow(int intLine) +{ + if (m_blnGLContextInitialized) + { + return m_objGLShaderArray.SelectRow(intLine); + } +} + +bool DATVScreen::setDataColor(int intCol, int intRed, int intGreen, int intBlue) +{ + if (m_blnGLContextInitialized) + { + return m_objGLShaderArray.SetDataColor(intCol, + qRgb(intRed, intGreen, intBlue)); + } +} diff --git a/plugins/channelrx/demoddatv/datvscreen.h b/plugins/channelrx/demoddatv/datvscreen.h new file mode 100644 index 000000000..05c2ceb68 --- /dev/null +++ b/plugins/channelrx/demoddatv/datvscreen.h @@ -0,0 +1,96 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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_DATVSCREEN_H +#define INCLUDE_DATVSCREEN_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" + + + +class QPainter; + + + +class SDRANGEL_API DATVScreen: public QGLWidget +{ + Q_OBJECT + +public: + + DATVScreen(QWidget* parent = NULL); + ~DATVScreen(); + + void resizeDATVScreen(int intCols, int intRows); + void renderImage(unsigned char * objData); + QRgb* getRowBuffer(int intRow); + void resetImage(); + + bool selectRow(int intLine); + bool setDataColor(int intCol,int intRed, int intGreen, int intBlue); + + void connectTimer(const QTimer& timer); + + //Valeurs par défaut + static const int DATV_COLS=256; + static const int DATV_ROWS=256; + +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; + + void initializeGL(); + void resizeGL(int width, int height); + void paintGL(); + + void mousePressEvent(QMouseEvent*); + + unsigned char *m_chrLastData; + +protected slots: + void cleanup(); + void tick(); +}; + +#endif // INCLUDE_DATVSCREEN_H diff --git a/plugins/channelrx/demoddatv/datvvideoplayer.h b/plugins/channelrx/demoddatv/datvvideoplayer.h new file mode 100644 index 000000000..3ea33e368 --- /dev/null +++ b/plugins/channelrx/demoddatv/datvvideoplayer.h @@ -0,0 +1,55 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 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 DATVVIDEOPLAYER_H +#define DATVVIDEOPLAYER_H + +#include "leansdr/framework.h" +#include "datvideostream.h" + +namespace leansdr +{ + template struct datvvideoplayer : runnable + { + datvvideoplayer(scheduler *sch, pipebuf &_in, DATVideostream * objVideoStream) : + runnable(sch, _in.name), + in(_in), + m_objVideoStream(objVideoStream) + { + } + + void run() + { + int size = in.readable() * sizeof(T); + if ( ! size ) return; + + 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; + }; + +} + +#endif // DATVVIDEOPLAYER_H diff --git a/plugins/channelrx/demoddatv/demoddatv.pro b/plugins/channelrx/demoddatv/demoddatv.pro new file mode 100644 index 000000000..a0b3cd352 --- /dev/null +++ b/plugins/channelrx/demoddatv/demoddatv.pro @@ -0,0 +1,67 @@ +#-------------------------------------------------------- +# +# Pro file for Android and Windows builds with Qt Creator +# +#-------------------------------------------------------- + +TEMPLATE = lib +CONFIG += plugin + +QT += core gui multimedia multimediawidgets widgets opengl + +TARGET = demoddatv + +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 + +CONFIG(MINGW32):INCLUDEPATH += "D:\boost_1_58_0" +CONFIG(MINGW64):INCLUDEPATH += "D:\boost_1_58_0" +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 \ + leansdr/filtergen.h \ + leansdr/framework.h \ + leansdr/generic.h \ + leansdr/hdlc.h \ + leansdr/iess.h \ + leansdr/math.h \ + leansdr/rs.h \ + leansdr/sdr.h \ + leansdr/viterbi.h \ + datvconstellation.h \ + glshaderarray.h \ + datvvideoplayer.h \ + datvideostream.h \ + datvideorender.h + +FORMS += datvdemodgui.ui + +LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase +LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui +LIBS += -lavutil -lswscale -lavdevice -lavformat -lavcodec -lswresample + +RESOURCES = ../../../sdrbase/resources/res.qrc diff --git a/plugins/channelrx/demoddatv/glshaderarray.cpp b/plugins/channelrx/demoddatv/glshaderarray.cpp new file mode 100644 index 000000000..bb4bae585 --- /dev/null +++ b/plugins/channelrx/demoddatv/glshaderarray.cpp @@ -0,0 +1,301 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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/glshaderarray.h b/plugins/channelrx/demoddatv/glshaderarray.h new file mode 100644 index 000000000..4c34969f3 --- /dev/null +++ b/plugins/channelrx/demoddatv/glshaderarray.h @@ -0,0 +1,76 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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/demoddatv/leansdr/convolutional.h b/plugins/channelrx/demoddatv/leansdr/convolutional.h new file mode 100644 index 000000000..2b6e909d9 --- /dev/null +++ b/plugins/channelrx/demoddatv/leansdr/convolutional.h @@ -0,0 +1,257 @@ +#ifndef LEANSDR_CONVOLUTIONAL_H +#define LEANSDR_CONVOLUTIONAL_H + +namespace leansdr { + + // ALGEBRAIC DECONVOLUTION + + // QPSK 1/2 only. + // This is a straightforward implementation, provided for reference. + // deconvol_poly2 is functionally equivalent and much faster. + + 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; } + + typedef u8 decoded_byte; + + 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; + } + + private: + Thist hist; + + }; // deconvol_poly + + + // ALGEBRAIC DECONVOLUTION, OPTIMIZED + + // QPSK 1/2 only. + // Functionally equivalent to deconvol_poly, + // but processing 32 bits in parallel. + + 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; } + + 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. + + 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) +#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; + } +#else + // Unroll manually. +#define LOOP(bit) { \ + 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; \ + ++pin; \ + } + // 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 +#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"); + } +#undef LOOP +#endif + 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 +#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; + } + private: + Thist inI, inQ; + }; // deconvol_poly2 + + + // CONVOLUTIONAL ENCODER + + // QPSK 1/2 only. + + template + struct convol_poly2 { + typedef u8 uncoded_byte; + typedef u8 hardsymbol; + + 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]; + } + } + } + private: + Thist hist; + }; // convol_poly2 + + // Generic BPSK..256QAM and puncturing + + 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"); + } + private: + Thist hist; + int nhist; + Thist sersymb; + int nsersymb; + }; // convol_multipoly + +} // namespace + +#endif // LEANSDR_CONVOLUTIONAL_H diff --git a/plugins/channelrx/demoddatv/leansdr/dsp.h b/plugins/channelrx/demoddatv/leansdr/dsp.h new file mode 100644 index 000000000..cc66d73c5 --- /dev/null +++ b/plugins/channelrx/demoddatv/leansdr/dsp.h @@ -0,0 +1,351 @@ +#ifndef LEANSDR_DSP_H +#define LEANSDR_DSP_H + +#include +#include "leansdr/framework.h" +#include "leansdr/math.h" + +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) { + } + 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); + } + 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 *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 *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) { + } + 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: + pipereader in1, in2; + pipewriter out; + }; + + template + struct scaler : runnable { + Tscale scale; + 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) { + } + 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) { + } + + 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); + } + + 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); + } + } + + 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 in; + pipewriter out; + unsigned int decim; + + 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: 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; + float current_freq; + void set_freq(float f) { + for ( int i=0; i + +#include "leansdr/viterbi.h" +#include "leansdr/convolutional.h" +#include "leansdr/sdr.h" +#include "leansdr/rs.h" + +namespace leansdr { + + static const int SIZE_RSPACKET = 204; + static const int MPEG_SYNC = 0x47; + static const int MPEG_SYNC_INV = (MPEG_SYNC^0xff); + static const int MPEG_SYNC_CORRUPTED = 0x55; + + // Generic deconvolution + + enum code_rate { + FEC12, FEC23, FEC46, FEC34, FEC56, FEC78, // DVB-S + FEC45, FEC89, FEC910, // DVB-S2 + FEC_MAX + }; + + // Customize APSK radii according to code rate + + 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; + 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; + case cstln_lut<256>::APSK64E: + // EN 302 307-2, section 5.4.5, Table 13f + gamma1 = 2.4; gamma2 = 4.3; gamma3 = 7; + break; + default: + 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; + +// G1 = 0b1111001 +// G2 = 0b1011011 +// +// G1 = [ 1 1 1 1 0 0 1 ] +// G2 = [ 1 0 1 1 0 1 1 ] +// +// C = [ G2 ; +// G1 ; +// 0 G2 ; +// 0 G1 ; +// 0 0 G2 ; +// 0 0 G1 ] +// +// C = [ 1 0 1 1 0 1 1 0 0 0 0 0 0 ; +// 1 1 1 1 0 0 1 0 0 0 0 0 0 ; +// 0 1 0 1 1 0 1 1 0 0 0 0 0 ; +// 0 1 1 1 1 0 0 1 0 0 0 0 0 ; +// 0 0 1 0 1 1 0 1 1 0 0 0 0 ; +// 0 0 1 1 1 1 0 0 1 0 0 0 0 ; +// 0 0 0 1 0 1 1 0 1 1 0 0 0 ; +// 0 0 0 1 1 1 1 0 0 1 0 0 0 ; +// 0 0 0 0 1 0 1 1 0 1 1 0 0 ; +// 0 0 0 0 1 1 1 1 0 0 1 0 0 ; +// 0 0 0 0 0 1 0 1 1 0 1 1 0 ; +// 0 0 0 0 0 1 1 1 1 0 0 1 0 ; +// 0 0 0 0 0 0 1 0 1 1 0 1 1 ; +// 0 0 0 0 0 0 1 1 1 1 0 0 1 ] +// +// 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 + + 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; + } + + 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 *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<debug ) { + 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)"); + } + } + + 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; + } 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 1 byte + // 2/3 12 symbols -> 2 bytes + // 3/4 16 symbols -> 3 bytes + // 5/6 24 symbols -> 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 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; + + // 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; + } + } + + 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; + // DECONVOL + int nG; + uint32_t *conv; // [nG] Convolution polynomials; MSB is newest + uint32_t *punct; // [nG] Puncturing pattern + int punctperiod, punctweight; + iq_t *deconv; // [punctperiod] Deconvolution polynomials + iq_t *deconv2; // [punctperiod] Alternate polynomials (for fastlock) + sync_t *locked; + int skip; + + }; + + typedef deconvol_sync deconvol_sync_simple; + + 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 ) { + case FEC12: + 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; + case FEC34: + pX = 0x5; // 101 + pY = 0x6; // 110 + break; + case FEC56: + pX = 0x15; // 10101 + pY = 0x1a; // 11010 + break; + case FEC78: + 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; + } + return new deconvol_sync_simple(sch, _in, _out, DVBS_G1, DVBS_G2, pX, pY); + } + + + // 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 + }; + + // 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 + }; + + 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 + { + 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"); + } + + 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: + pipereader in; + pipewriter out; + convol_multipoly convol; + }; // dvb_convol + + + // NEW ALGEBRAIC DECONVOLUTION + + // QPSK 1/2 only; + // With DVB-S polynomials hardcoded. + + 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) + { + init_syncs(); + locked = &syncs[0]; + } + + 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 + + } // run() + + 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. + } 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; + + + // BIT ALIGNMENT AND MPEG SYNC DETECTION + + 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(); + } + } + + 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_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; + } + } + + 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_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; + unsigned char polarity; // XOR mask, 0 or 0xff + int resync_phase; + int bitphase; + bool synchronized; + int next_sync_count; + int phase8; // Position in 8-packet cycle, -1 if not synchronized + unsigned long lock_timeleft; + unsigned long locktime; + pipewriter *state_out; + pipewriter *locktime_out; + bool report_state; + }; + + + template + struct rspacket { Tbyte data[SIZE_RSPACKET]; }; + + + // INTERLEAVER + + struct interleaver : runnable { + interleaver(scheduler *sch, pipebuf< rspacket > &_in, + pipebuf< u8 > &_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 > in; + pipewriter out; + }; // interleaver + + + // DEINTERLEAVER + + template + struct deinterleaver : runnable { + deinterleaver(scheduler *sch, pipebuf &_in, + pipebuf< rspacket > &_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 in; + pipewriter< rspacket > out; + }; // deinterleaver + + + static const int SIZE_TSPACKET = 188; + struct tspacket { u8 data[SIZE_TSPACKET]; }; + + + // 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); + } + } + private: + rs_engine rs; + pipereader in; + pipewriter< rspacket > out; + }; // rs_encoder + + + // RS DECODER + + 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; + } + void run() { + if ( bitcount && bitcount->writable()<1 ) return; + if ( errcount && errcount->writable()<1 ) return; + + int nbits=0, nerrs=0; + + while ( in.readable()>=1 && out.writable()>=1 ) { + Tbyte *pin = in.rd()->data; + u8 *pout = out.wr()->data; + + 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"); + + 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); + } +#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. + } + } + + 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); + + } + if ( nbits ) { + if ( bitcount ) bitcount->write(nbits); + if ( errcount ) errcount->write(nerrs); + } + } + private: + pipereader< rspacket > in; + pipewriter out; + pipewriter *bitcount, *errcount; + }; // rs_decoder + + + // 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]); + } + 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 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 in; + pipewriter out; + }; // randomizer + + + // 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]); + } + 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 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 ( ; pindata[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; + pipereader in; + pipewriter out; + }; // derandomizer + + + // 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. + + 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; + + // 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; + + // 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; + + // 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; + + // 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; + + // 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; + + // 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; + + // 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; + + private: + pipereader in; + pipewriter out; + cstln_lut<256> *cstln; + fec_spec *fec; + 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] + int current_sync; + static const int chunk_size = 128; + int resync_phase; + 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 + { + 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; + } + + 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. + + syncs = new sync[nsyncs]; + + for ( int s=0; snrotations); +#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"); +#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; snsymbols]; + 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; + } + + 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); + } + + 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 + + while ( in.readable() >= nshifts*chunk_size + (nshifts-1) && + out.writable()*8 >= fec->bits_in*chunk_size ) { + TPM totaldiscr[nsyncs]; + for ( int s=0; sbits_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; + } + } + + }; // viterbi_sync + + + +} // namespace + +#endif // LEANSDR_DVB_H diff --git a/plugins/channelrx/demoddatv/leansdr/filtergen.h b/plugins/channelrx/demoddatv/leansdr/filtergen.h new file mode 100644 index 000000000..67e67e2c5 --- /dev/null +++ b/plugins/channelrx/demoddatv/leansdr/filtergen.h @@ -0,0 +1,90 @@ +#ifndef LEANSDR_FILTERGEN_H +#define LEANSDR_FILTERGEN_H + +#include + +namespace leansdr { + namespace filtergen { + + template + void normalize_power(int n, T *coeffs, float gain=1) { + float s2 = 0; + for ( int i=0; i + void normalize_dcgain(int n, T *coeffs, float gain=1) { + float s = 0; + for ( int i=0; i + int lowpass(int order, float Fcut, T **coeffs, float gain=1) { + int ncoeffs = order + 1; + *coeffs = new T[ncoeffs]; + for ( int i=0; i + int root_raised_cosine(int order, float Fs, float rolloff, T **coeffs) { + float B = rolloff, pi = M_PI; + int ncoeffs = (order+1) | 1; + *coeffs = new T[ncoeffs]; + for ( int i=0; i +#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) { } + }; + + struct runnable_common { + const char *name; + runnable_common(const char *_name) : name(_name) { } + virtual void run() { } + virtual void shutdown() { } +#ifdef DEBUG + ~runnable_common() { fprintf(stderr, "Deallocating %s !\n", name); } +#endif + }; + + struct window_placement { + const char *name; // NULL to terminate + int x, y, w, h; + }; + + struct scheduler { + pipebuf_common *pipes[MAX_PIPES]; + int npipes; + runnable_common *runnables[MAX_RUNNABLES]; + int nrunnables; + window_placement *windows; + bool verbose, debug; + + 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_runnable(runnable_common *r) { + if ( nrunnables == MAX_RUNNABLES ) fail("MAX_RUNNABLES"); + runnables[nrunnables++] = r; + } + void step() { + for ( int i=0; irun(); + } + 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(); + } + unsigned long long hash() { + unsigned long long h = 0; + for ( int i=0; ihash(); + 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); + } + }; + + struct runnable : runnable_common { + runnable(scheduler *_sch, const char *name) + : runnable_common(name), sch(_sch) { + sch->add_runnable(this); + } + protected: + scheduler *sch; + }; + + 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 add_reader() { + if ( nrd == MAX_READERS ) fail("too many readers"); + rds[nrd] = wr; + return nrd++; + } + void pack() { + T *rd = wr; + for ( int i=0; i + 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 + + 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 + void opt_write(pipewriter *p, T val) { + if ( p ) p->write(val); + } + + 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; + } + }; + + // 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); } + + 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 + T max(const T &x, const T &y) { return (x +#include + +#include "leansdr/math.h" + +namespace leansdr { + +////////////////////////////////////////////////////////////////////// +// Simple blocks +////////////////////////////////////////////////////////////////////// + +// [file_reader] reads raw data from a file descriptor into a [pipebuf]. +// 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; + } + + // 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; + } + + out.written(nr / sizeof(T)); + } + bool loop; +private: + 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)); + } +private: + 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"); + } + } + in.read(n); + } + T scale; + int decimation; +private: + pipereader in; + const char *format; + int fdout; + int phase; +}; + +// [file_carrayprinter] writes all data available from a [pipebuf] +// to a file descriptor on a single line. +// 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; + 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; + 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); + } +private: + pipereader in; + pipewriter out; +}; + +// [decimator] forwards 1 in N sample. + +template +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; + pipewriter out; +}; + + // [rate_estimator] accumulates counts of two quantities + // and periodically outputs their ratio. + + 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) { + } + + 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: + pipereader num, den; + pipewriter rate; + T acc_num, acc_den; + }; + + + // 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) + { + if ( nin*sizeof(Tin) != nout*sizeof(Tout) ) + fail("serializer: incompatible sizes"); + } + void run() { + while ( in.readable()>=nin && out.writable()>=nout ) { + memcpy(out.wr(), in.rd(), nout*sizeof(Tout)); + in.read(nin); + out.written(nout); + } + } + private: + int nin, nout; + pipereader in; + pipewriter out; + }; // serializer + + + // [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) { + } + 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: + T *data; + int count; + pipewriter out; + int pos; + }; // buffer_reader + + + // [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) { + } + 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: + pipereader in; + T *data; + int count; + int pos; + }; // buffer_writer + +} // namespace + +#endif // LEANSDR_GENERIC_H diff --git a/plugins/channelrx/demoddatv/leansdr/gui.h b/plugins/channelrx/demoddatv/leansdr/gui.h new file mode 100644 index 000000000..14a75453a --- /dev/null +++ b/plugins/channelrx/demoddatv/leansdr/gui.h @@ -0,0 +1,591 @@ +#ifndef LEANSDR_GUI_H +#define LEANSDR_GUI_H + +#include + +#include "framework.h" + +namespace leansdr { + + ////////////////////////////////////////////////////////////////////// + // GUI blocks + ////////////////////////////////////////////////////////////////////// + +#ifdef GUI + +#include +#include +#include + + static const int DEFAULT_GUI_DECIMATION = 64; + + struct gfx { + Display *display; + int screen; + int w, h; + Window window; + GC gc; + Pixmap dbuf; + gfx(scheduler *sch, const char *name) { + window_placement *wp; + for ( wp=sch->windows; wp && wp->name; ++wp ) + if ( ! strcmp(wp->name, name) ) break; + if ( wp && wp->name ) + init(wp->name, wp->x, wp->y, wp->w, wp->h); + else { + fprintf(stderr, "No placement hints for window '%s'\n", name); + init(name, -1, -1, 320, 240); + } + } + gfx(const char *name, int _x, int _y, int _w, int _h) { + init(name, _x, _y, _w, _h); + } + void init(const char *name, int _x, int _y, int _w, int _h) { + buttons = 0; + clicks = 0; + mmoved = false; + w = _w; + h = _h; + display = XOpenDisplay(getenv("DISPLAY")); + if ( ! display ) fatal("display"); + screen = DefaultScreen(display); + XSetWindowAttributes xswa; + xswa.event_mask = (ExposureMask| + StructureNotifyMask| + ButtonPressMask| + ButtonReleaseMask| + KeyPressMask| + KeyReleaseMask| + PointerMotionMask); + xswa.background_pixel = BlackPixel(display, screen); + window = XCreateWindow(display, DefaultRootWindow(display), + 100,100, w,h, 10, CopyFromParent,InputOutput, + CopyFromParent, CWEventMask|CWBackPixel, + &xswa); + if ( !window ) fatal("window"); + XStoreName(display, window, name); + XMapWindow(display, window); + if ( _x>=0 && _y>=0 ) + XMoveWindow(display, window, _x, _y); + dbuf = XCreatePixmap(display, window, w, h, DefaultDepth(display,screen)); + gc = XCreateGC(display, dbuf, 0, NULL); + if ( ! gc ) fatal("gc"); + } + void clear() { + setfg(0, 0, 0); + XFillRectangle(display, dbuf, gc, 0, 0, w, h); + } + void show() { + XCopyArea(display, dbuf, window, gc, 0, 0, w, h, 0, 0); + } + void sync() { + XSync(display, False); + } + void events() { + XEvent ev; + while ( XCheckWindowEvent(display, window, -1, &ev) ) { + switch ( ev.type ) { + case ButtonPress: { + int b = ev.xbutton.button; + buttons |= 1< + struct cscope : runnable { + T xymin, xymax; + unsigned long decimation; + unsigned long pixels_per_frame; + cstln_lut<256> **cstln; // Optional ptr to optional constellation + cscope(scheduler *sch, pipebuf< complex > &_in, T _xymin, T _xymax, + const char *_name=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), g(sch, name) { + } + void run() { + while ( in.readable() >= pixels_per_frame ) { + if ( ! phase ) { + draw_begin(); + g.setfg(0, 255, 0); + complex *p = in.rd(), *pend = p+pixels_per_frame; + for ( ; pre-xymin)/(xymax-xymin), + g.h - g.h*(p->im-xymin)/(xymax-xymin)); + if ( cstln && (*cstln) ) { + // Plot constellation points + g.setfg(255, 255, 255); + for ( int i=0; i<(*cstln)->nsymbols; ++i ) { + complex *p = &(*cstln)->symbols[i]; + int x = g.w*(p->re-xymin)/(xymax-xymin); + int y = g.h - g.h*(p->im-xymin)/(xymax-xymin); + for ( int d=-2; d<=2; ++d ) { + g.point(x+d, y); + g.point(x, y+d); + } + } + } + g.show(); + g.sync(); + } + in.read(pixels_per_frame); + if ( ++phase >= decimation ) phase = 0; + } + } + //private: + pipereader< complex > in; + unsigned long phase; + gfx g; + 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); + } + }; + + template + struct wavescope : runnable { + T ymin, ymax; + unsigned long decimation; + wavescope(scheduler *sch, pipebuf &_in, + T _ymin, T _ymax, const char *_name=NULL) + : runnable(sch, _name?_name:_in.name), + in(_in), ymin(_ymin), ymax(_ymax), + decimation(DEFAULT_GUI_DECIMATION), + g(sch, name), phase(0), + x(0) { + g.clear(); + } + void run() { + while ( in.readable() >= g.w ) { + if ( ! phase ) plot(in.rd(), g.w); + in.read(g.w); + if ( ++phase >= decimation ) phase = 0; + } + } + void plot(T *p, int count) { + T *pend = p + count; + g.clear(); + g.setfg(0, 255, 0); + for ( int x=0; p in; + int phase; + gfx g; + int x; + }; + + template + struct slowmultiscope : runnable { + struct chanspec { + pipebuf *in; + const char *name, *format; + unsigned char rgb[3]; + float scale; + float ymin, ymax; + enum flag { + DEFAULT = 0, + ASYNC = 1, // Read whatever is available + COUNT = 2, // Display number of items read instead of value + SUM = 4, // Display sum of values + LINE = 8, // Connect points + WRAP = 16, // Modulo min..max + DISABLED = 32, // Ignore channel + } flags; + }; + unsigned long samples_per_pixel; + float sample_freq; // Sample rate in Hz (used for cursor operations) + slowmultiscope(scheduler *sch, const chanspec *specs, int nspecs, + const char *_name) + : runnable(sch, _name?_name:"slowmultiscope"), + samples_per_pixel(1), sample_freq(1), + g(sch, name), t(0), x(0), total_samples(0) { + chans = new channel[nspecs]; + nchans = 0; + for ( int i=0; i(*specs[i].in); + chans[nchans].accum = 0; + ++nchans; + } + g.clear(); + } + void run() { + // Read up to one pixel worth of data + unsigned long count = samples_per_pixel; + for ( channel *c=chans; cspec.flags&chanspec::ASYNC) ) + count = min(count, c->in->readable()); + for ( int n=count; n--; ) { + for ( channel *c=chans; cspec.flags & chanspec::ASYNC ) + // For async channels, read any and all available data. + nr = c->in->readable(); + else + nr = 1; + g.setfg(c->spec.rgb[0], c->spec.rgb[1], c->spec.rgb[2]); + int y = -1; + while ( nr-- ) { + float v = *c->in->rd() * c->spec.scale; + if ( c->spec.flags & chanspec::COUNT ) + ++c->accum; + else if ( c->spec.flags & chanspec::SUM ) + c->accum += v; + else { + c->print_val = v; + float nv = (v-c->spec.ymin) / (c->spec.ymax-c->spec.ymin); + if ( c->spec.flags & chanspec::WRAP ) + nv = nv - floor(nv); + y = g.h - g.h*nv; + } + c->in->read(1); + } + // Display count/sum channels only when the cursor is about to move. + if ( (c->spec.flags&(chanspec::COUNT|chanspec::SUM)) && + t+1 >= samples_per_pixel ) { + T v = c->accum; + y = g.h-1 - g.h*(v-c->spec.ymin)/(c->spec.ymax-c->spec.ymin); + c->accum = 0; + c->print_val = v; + } + if ( y >= 0 ) { + if ( c->spec.flags & chanspec::LINE ) { + if ( x ) g.line(x-1, c->prev_y, x, y); + c->prev_y = y; + } else + g.point(x, y); + } + } + g.show(); + // Print instantatenous values as text + for ( int i=0; ispec.rgb[0], c->spec.rgb[1], c->spec.rgb[2]); + char text[256]; + sprintf(text, c->spec.format, c->print_val); + g.transient_text(5, 20+16*i, text); + } + run_gui(); + if ( ++t >= samples_per_pixel ) { + t = 0; + ++x; + if ( x >= g.w ) x = 0; + g.setfg(0, 0, 0); + g.line(x, 0, x, g.h-1); + } + run_gui(); + g.sync(); + } + total_samples += count; + } + void run_gui() { + g.events(); + // Print cursor time + float ct = g.mx * samples_per_pixel / sample_freq; + float tt = total_samples / sample_freq; + char text[256]; + sprintf(text, "%.3f / %.3f s", ct, tt); + g.setfg(255, 255, 255); + g.transient_text(g.w*3/4, 20, text); + } + private: + int nchans; + struct channel { + chanspec spec; + pipereader *in; + float accum; + int prev_y; + float print_val; + } *chans; + gfx g; + unsigned long t; + int x; + int total_samples; + }; + + template + struct spectrumscope : runnable { + T ymax; + float amax; + unsigned long size; + unsigned long decimation; + spectrumscope(scheduler *sch, pipebuf< complex > & _in, + T _max, const char *_name=NULL) + : runnable(sch, _name?_name:_in.name), + ymax(_max), amax(_max), + size(4096), decimation(DEFAULT_GUI_DECIMATION), + in(_in), phase(0), g(sch, name), fft(NULL) { + } + void run() { + while ( in.readable() >= size ) { + if ( ! phase ) do_fft(in.rd()); + in.read(size); + if ( ++phase >= decimation ) phase = 0; + } + } + private: + pipereader< complex > in; + int phase; + gfx g; + cfft_engine *fft; + void do_fft(complex *input) { + draw_begin(); + if ( !fft || fft->n!=size ) { + if ( fft ) delete fft; + fft = new cfft_engine(size); + } + complex *pin=input, *pend=pin+size; + complex data[size], *pout=data; + g.setfg(255, 0, 0); + for ( int x=0; pinre = (float)pin->re; + pout->im = (float)pin->im; + // g.point(x, g.h/2-pout->re*g.h/2/ymax); + } + fft->inplace(data, true); + g.setfg(0, 255, 0); + for ( int i=0; i v = data[i];; + float y = hypot(v.re, v.im); + g.line(x, g.h-1, x, g.h-1-y*g.h/amax); + } + if ( g.buttons ) { + char s[256]; + float f = 2.4e6 * (g.mx-g.w/2) / g.w; + sprintf(s, "%f", f); + g.text(16, 16, s); + } + g.show(); + g.sync(); + } + void draw_begin() { + g.clear(); + g.setfg(255, 255, 255); + g.line(g.w/2,0, g.w/2,g.h); + } + }; + + template + struct rfscope : runnable { + unsigned long size; + unsigned long decimation; + float Fs; // Sampling freq for display (Hz) + float Fc; // Center freq for display (Hz) + int ncursors; + float *cursors; // Cursor frequencies + float hzoom; // Horizontal zoom factor + float db0, dbrange; // Vertical range db0 .. db0+dbrange + float bw; // Smoothing bandwidth + rfscope(scheduler *sch, pipebuf< complex > & _in, + const char *_name=NULL) + : runnable(sch, _name?_name:_in.name), + size(4096), decimation(DEFAULT_GUI_DECIMATION), + Fs(1), Fc(0), ncursors(0), hzoom(1), + db0(-25), dbrange(50), bw(0.05), + in(_in), phase(0), g(sch, name), fft(NULL), filtered(NULL) { + } + void run() { + while ( in.readable() >= size ) { + if ( ! phase ) do_fft(in.rd()); + in.read(size); + if ( ++phase >= decimation ) phase = 0; + } + } + private: + pipereader< complex > in; + int phase; + gfx g; + cfft_engine *fft; + float *filtered; + + void do_fft(complex *input) { + g.events(); + draw_begin(); + if ( !fft || fft->n!=size ) { + if ( fft ) delete fft; + fft = new cfft_engine(size); + } + // Convert to complex and transform + complex *pin=input, *pend=pin+size; + complex data[size], *pout=data; + for ( int x=0; pinre = (float)pin->re; + pout->im = (float)pin->im; + } + fft->inplace(data, true); + float amp2[size]; + for ( int i=0; i &v = data[i];; + amp2[i] = (v.re*v.re + v.im*v.im)*size; + } + if ( ! filtered ) { + filtered = new float[size]; + for ( int i=0; i + struct genscope : runnable { + struct render { + int x, y; + char dir; // 'h'orizontal or 'v'ertical + }; + struct chanspec { + pipebuf *in; // NULL if disabled + render r; + }; + genscope(scheduler *sch, chanspec *specs, int _nchans, + const char *_name=NULL) + : runnable(sch, _name?_name:"genscope"), + nchans(_nchans), + g(sch, name) { + chans = new channel[nchans]; + for ( int i=0; i(*specs[i].in); + } + } + g.clear(); + gettimeofday(&tv, NULL); + } + struct channel { + chanspec spec; + pipereader *in; + } *chans; + int nchans; + struct timeval tv; + void run() { + g.setfg(0, 255, 0); + for ( channel *pc=chans; pcin ) continue; + int n = pc->in->readable(); + T last = pc->in->rd()[n-1]; + pc->in->read(n); + int dx = pc->spec.r.dir=='h' ? last : 0; + int dy = pc->spec.r.dir=='v' ? last : 0; + dx /= 3; + dy /= 3; + g.line(pc->spec.r.x-dx, pc->spec.r.y-dy, + pc->spec.r.x+dx, pc->spec.r.y+dy); + char txt[16]; + sprintf(txt, "%d", (int)last); + g.text(pc->spec.r.x+5, pc->spec.r.y-2, txt); + } + struct timeval newtv; + gettimeofday(&newtv, NULL); + int dt = (newtv.tv_sec-tv.tv_sec)*1000 + (newtv.tv_usec-tv.tv_usec)/1000; + if ( dt > 100 ) { + fprintf(stderr, "#"); + g.show(); + g.sync(); + g.clear(); + tv = newtv; + } + } + private: + gfx g; + }; + +#endif // GUI + +} // namespace + +#endif // LEANSDR_GUI_H diff --git a/plugins/channelrx/demoddatv/leansdr/hdlc.h b/plugins/channelrx/demoddatv/leansdr/hdlc.h new file mode 100644 index 000000000..efc24c685 --- /dev/null +++ b/plugins/channelrx/demoddatv/leansdr/hdlc.h @@ -0,0 +1,293 @@ +#ifndef LEANSDR_HDLC_H +#define LEANSDR_HDLC_H + +#include "leansdr/framework.h" + +namespace leansdr { + + // 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) + { + reset(); + } + + void reset() { shiftreg=0; inframe=false; } + + 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; + } + + private: + // Config + int minframesize, maxframesize; + u8 invertmask; + u8 *framebuf; // [maxframesize] + // State + u8 shiftreg; // Input bitstream + bool inframe; // Currently receiving a frame ? + u8 byte_out; // Accumulator for data bits + int nbits_out; // Number of data bits in byte_out + int framesize; // Number of bytes in framebuf, if inframe + u16 crc16; // CRC of framebuf[framesize] + // CRC + 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) + { + for ( int s=0; sdebug = sch->debug; + errslot = 0; + } + + 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 ( 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); + } + + 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; + int chunk_size; + pipereader in; + pipewriter out; + pipewriter *lock_out; + pipewriter *framecount_out, *fcserrcount_out; + 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]; + } syncs[NSYNCS]; + int errslot; + int cur_sync; + int resync_phase; + bool lock_state; + public: + int resync_period; + bool header16; // Output length prefix + }; // hdlc_sync + +} // namespace + +#endif // LEANSDR_HDLC_H diff --git a/plugins/channelrx/demoddatv/leansdr/iess.h b/plugins/channelrx/demoddatv/leansdr/iess.h new file mode 100644 index 000000000..778043d43 --- /dev/null +++ b/plugins/channelrx/demoddatv/leansdr/iess.h @@ -0,0 +1,58 @@ +#ifndef LEANSDR_IESS_H +#define LEANSDR_IESS_H + +#include "leansdr/framework.h" + +namespace leansdr { + + // SELF-SYNCHRONIZING DESCRAMBLER + // Per ETSI TR 192 figure 8 (except Q20/ not connected to CLOCK). + // This implementation operates on packed bits, MSB first. + + struct etr192_descrambler : runnable { + etr192_descrambler(scheduler *sch, + pipebuf &_in, // Packed scrambled bits + pipebuf &_out) // Packed bits + : runnable(sch, "etr192_dec"), + in(_in), out(_out), + shiftreg(0), counter(0) + { + } + + void run() { + int count = min(in.readable(), out.writable()); + for ( u8 *pin=in.rd(), *pend=pin+count, *pout=out.wr(); + pin>8)) & 1; + int counter_overflow = (counter==31) ? 1 : 0; + int taps = (shiftreg>>2) ^ (shiftreg>>19); + int bit_out = (taps ^ counter_overflow ^ bit_in ^ 1) & 1; + // Execute clock transition +#if 1 // Descramble + shiftreg = (shiftreg<<1) | bit_in; +#else // Scramble + shiftreg = (shiftreg<<1) | bit_out; +#endif + counter = reset_counter ? 0 : (counter+1)&31; + byte_out = (byte_out<<1) | bit_out; + } + *pout = byte_out; + } + in.read(count); + out.written(count); + } + + private: + pipereader in; + pipewriter out; + u32 shiftreg; // 20 bits + u8 counter; // 5 bits + }; // etr192_descrambler + +} // namespace + +#endif // LEANSDR_IESS_H diff --git a/plugins/channelrx/demoddatv/leansdr/math.h b/plugins/channelrx/demoddatv/leansdr/math.h new file mode 100644 index 000000000..c25d64c5d --- /dev/null +++ b/plugins/channelrx/demoddatv/leansdr/math.h @@ -0,0 +1,98 @@ +#ifndef LEANSDR_MATH_H +#define LEANSDR_MATH_H + +#include +#include + +namespace leansdr { + + template + struct complex { + T re, im; + complex() { } + 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; } + }; + + template + complex operator +(const complex &a, const complex &b) { + return complex(a.re+b.re, a.im+b.im); + } + + template + complex operator *(const complex &a, const complex &b) { + return complex(a.re*b.re-a.im*b.im, a.re*b.im+a.im*b.re); + } + + template + complex operator *(const complex &a, const T &k) { + return complex(a.re*k, a.im*k); + } + + template + 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 }; + return lut[x&15] + lut[x>>4]; + } + inline int hamming_weight(uint16_t x) { + return hamming_weight((uint8_t)x) + + hamming_weight((uint8_t)(x>>8)); + } + inline int hamming_weight(uint32_t x) { + return hamming_weight((uint16_t)x) + + hamming_weight((uint16_t)(x>>16)); + } + inline int hamming_weight(uint64_t x) { + return hamming_weight((uint32_t)x) + + hamming_weight((uint32_t)(x>>32)); + } + + inline unsigned char parity(uint8_t x) { + x ^= x>>4; + return (0x6996 >> (x&15)) & 1; // 16-entry look-up table + } + inline unsigned char parity(uint16_t x) { + return parity((uint8_t)(x^(x>>8))); + } + inline unsigned char parity(uint32_t x) { + return parity((uint16_t)(x^(x>>16))); + } + inline unsigned char parity(uint64_t x) { + return parity((uint32_t)(x^(x>>32))); + } + + inline int log2i(uint64_t x) { + int n = -1; + for ( ; x; ++n,x>>=1 ) ; + return n; + } + + // Pre-computed sin/cos for 16-bit angles + + struct trig16 { + complex lut[65536]; // TBD static and shared + trig16() { + for ( int a=0; a<65536; ++a ) { + float af = a * 2*M_PI / 65536; + lut[a].re = cosf(af); + lut[a].im = sinf(af); + } + } + inline const complex &expi(uint16_t a) const { + return lut[a]; + } + // a must fit in a int32_t, otherwise behaviour is undefined + inline const complex &expi(float a) const { + return expi((uint16_t)(int16_t)(int32_t)a); + } + }; + +} // namespace + +#endif // LEANSDR_MATH_H diff --git a/plugins/channelrx/demoddatv/leansdr/rs.h b/plugins/channelrx/demoddatv/leansdr/rs.h new file mode 100644 index 000000000..fa68a4d06 --- /dev/null +++ b/plugins/channelrx/demoddatv/leansdr/rs.h @@ -0,0 +1,259 @@ +#ifndef LEANSDR_RS_H +#define LEANSDR_RS_H + +#include "leansdr/math.h" + +#define DEBUG_RS 0 + +namespace leansdr { + + // 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. + + 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< 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])); + } +#if DEBUG_RS + fprintf(stderr, "RS generator:"); + for ( int i=0; i<=16; ++i ) fprintf(stderr, " %02x", G[i]); + fprintf(stderr, "\n"); +#endif + } + + // RS-encoded messages are interpreted as coefficients in + // GF(256) of a polynomial P. + // The syndromes are synd[i] = P(alpha^i). + // 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; + } + 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; + } + + // 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 +#if DEBUG_RS + 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])); + } +#if DEBUG_RS + 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); + } + + // 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) +#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"); +#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]); +#if DEBUG_RS + 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]; +#if DEBUG_RS + 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) +#if DEBUG_RS + 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; + } + + }; + +} // namespace + +#endif // LEANSDR_RS_H diff --git a/plugins/channelrx/demoddatv/leansdr/sdr.h b/plugins/channelrx/demoddatv/leansdr/sdr.h new file mode 100644 index 000000000..fbe0161c7 --- /dev/null +++ b/plugins/channelrx/demoddatv/leansdr/sdr.h @@ -0,0 +1,1395 @@ +#ifndef LEANSDR_SDR_H +#define LEANSDR_SDR_H + +#include "leansdr/math.h" +#include "leansdr/dsp.h" + +namespace leansdr { + + // Abbreviations for floating-point types + + typedef float f32; + + typedef complex cu8; + typedef complex cs8; + typedef complex cu16; + typedef complex cs16; + typedef complex cf32; + + + ////////////////////////////////////////////////////////////////////// + // SDR blocks + ////////////////////////////////////////////////////////////////////// + + // AUTO-NOTCH FILTER + + // Periodically detects the [n__slots] strongest peaks with a FFT, + // removes them with a first-order filter. + + + + 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]; + } + } + 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 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; + } + } + + private: + cfft_engine fft; + pipereader< complex > in; + pipewriter< complex > out; + int n__slots; + + struct slot { + int i; + complex estim; + complex *expj, *ej; + int estt; + } *__slots; + int phase; + float gain; + T agc_rms_setpoint; + }; + + + // SIGNAL STRENGTH ESTIMATOR + + // Outputs RMS values. + + 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) { + } + 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); + } + } + private: + pipereader< complex > in; + pipewriter out; + unsigned long phase; + }; + + 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) { + } + 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); + } + } + private: + pipereader< complex > in; + pipewriter out_ss, out_ampmin, out_ampmax; + unsigned long phase; + }; + + // 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) { + } + private: + pipereader< complex > in; + pipewriter< complex > 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); + } + } + }; // simple_agc + + + 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. + + 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 + + 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 + }; + + 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); +#else // BPSK at 45° + 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"); + } + } + 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. +#if 1 + while ( I<-128 || I>127 || Q<-128 || Q>127 ) { + I *= 0.5; + Q *= 0.5; + } +#endif + 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]; + } + 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 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 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 + } + } + + 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 + } + + }; // 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" + }; + + // 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; } + }; + + + // 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); + } + private: + trig16 trig; + }; // nearest_sampler + + + // 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; + } + + void update_freq(float _freqw) { freqw = _freqw; } + + private: + trig16 trig; + float freqw; + }; // linear_sampler + + + // 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) + { + } + + 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 + struct cstln_receiver : runnable { + sampler_interface *sampler; + cstln_lut<256> *cstln; + unsigned long meas_decimation; // Measurement rate + float omega, min_omega, max_omega; // Samples per symbol + float freqw, min_freqw, max_freqw; // Freq offs (65536 = 1 Hz) + float pll_adjustment; + 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(); + } + + 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("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 + } + + float freq_tap; + void refresh_freq_tap() { + freq_tap = freqw / 65536; + } + private: + struct { + complex p; // Received symbol + complex c; // Matched constellation point + } hist[3]; + pipereader< complex > in; + pipewriter out; + float est_insp, agc_gain; + float mu; // PSK time expressed in clock ticks + float phase; // 65536=2pi + // Signal estimation + float est_sp; // Estimated RMS signal power + float est_ep; // Estimated RMS error vector power + 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. + + template + struct fast_qpsk_receiver : runnable { + typedef u8 hardsymbol; + unsigned long meas_decimation; // Measurement rate + float omega, min_omega, max_omega; // Samples per symbol + signed long freqw, min_freqw, max_freqw; // Freq offs (angle per sample) + 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) + { + 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(); + } + + 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"); + + 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; + + cu8 s; + u_angle symbol_arg = 0; // Exported for constellation viewer + + 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); +#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); +#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); +#endif + + 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]; +#define HIST_FLOAT 0 +#if HIST_FLOAT + 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; + + 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); + + 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); + + 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 +#else + cu8 p; // Received symbol + cu8 c; // Matched constellation point +#endif + } hist[3]; + pipereader in; + pipewriter out; + float mu; // PSK time expressed in clock ticks. TBD fixed point. + u_angle phase; + unsigned long meas_count; + pipewriter *freq_out, *mer_out; + pipewriter *cstln_out; + }; // fast_qpsk_receiver + + + // CONSTELLATION TRANSMITTER + + // Maps symbols to I/Q points. + + 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) + { + } + 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); + } + private: + pipereader in; + pipewriter< complex > out; + }; // cstln_transmitter + + + // FREQUENCY SHIFTER + + // 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); + } + } + 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); + } + private: + pipereader< complex > in; + pipewriter< complex > out; + float lut_cos[65536]; + float lut_sin[65536]; + unsigned short index; // Current phase + }; // rotator + + + // 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 + + 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"); + } + + float bandwidth; + float *freq_tap, tap_multiplier; + int decimation; + float kavg; + + 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); + } + + 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< complex > in; + pipewriter< float > out; + cfft_engine fft; + T *avgpower; + int phase; + }; // cnr_fft + + 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) { + } + + 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); + } + } + + 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 > in; + pipewriter< float[nfft] > out; + cfft_engine fft; + T *avgpower; + int phase; + }; // spectrum + + +} // namespace + +#endif // LEANSDR_SDR_H diff --git a/plugins/channelrx/demoddatv/leansdr/viterbi.h b/plugins/channelrx/demoddatv/leansdr/viterbi.h new file mode 100644 index 000000000..96bbff967 --- /dev/null +++ b/plugins/channelrx/demoddatv/leansdr/viterbi.h @@ -0,0 +1,282 @@ +#ifndef LEANSDR_VITERBI_H +#define LEANSDR_VITERBI_H + +#include +#include +#include + +// This is a generic implementation of Viterbi with explicit +// representation of the trellis. There is special support for +// convolutional coding, but the code can handle other schemes. +// TBD This is very inefficient. For a specific trellis all loops +// can be be unrolled. + +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. + + 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 + } states[NSTATES]; + + trellis() { + for ( TS s=0; spred != NOSTATE ) { + fprintf(stderr, "Invalid convolutional code\n"); + exit(1); + } + 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"); + } + } + + }; + + // 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 { + + trellis *trell; + + 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) + { + states = &statebanks[0]; + newstates = &statebanks[1]; + for ( TS s=0; smax_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; + 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 + struct bitpath { + T val; + bitpath() : val(0) { } + void append(TUS us) { val = (val<>(DEPTH-1)*NBITS) & ((1<DATV demodulator plugin + +

Dependencies

+ + - ffmpeg + - libavcodec-dev + - libavformat-dev + +

Introduction

+ +TBD... + +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). + +

Interface

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

1: Image

+ +This is where the TV image appears. + +

2: Modulation

+ + - 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 + +For FM choose the algorithm that best suits your conditions. + +

3: Frames Per Second

+ +This combo lets you chose between a 25 FPS or 30 FPS standard. + +

4: Horizontal sync

+ +Use this button to toggle horizontal synchronization processing. + +

5: Vertical sync

+ +Use this button to toggle vertical synchronization processing. + +

6: Half image

+ +Use this button to disable (on) or enable interlacing of the two half images (off). + +

7: Reset defaults

+ +Use this push button to reset values to a standard setting: + + - 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 + +

8: Synchronization level

+ +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