DATV demod: reception of the code

This commit is contained in:
f4exb 2018-02-22 22:52:49 +01:00
parent 354d5b3b5a
commit f1f7a0058c
36 changed files with 11462 additions and 0 deletions

View File

@ -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)

View File

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

View File

@ -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)

View File

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

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef DATVCONSTELLATION_H
#define DATVCONSTELLATION_H
#include <sys/time.h>
#include "leansdr/framework.h"
#include "datvscreen.h"
namespace leansdr
{
static const int DEFAULT_GUI_DECIMATION = 64;
template<typename T> 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<T> > &_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<T> *p = in.rd(), *pend = p+pixels_per_frame;
for ( ; p<pend; ++p )
{
if(m_objDATVScreen!=NULL)
{
m_objDATVScreen->selectRow(256*(p->re-xymin)/(xymax-xymin));
m_objDATVScreen->setDataColor(256- 256*((p->im-xymin)/(xymax-xymin)),255,0,255);
}
}
if ( cstln && (*cstln) )
{
// Plot constellation points
for ( int i=0; i<(*cstln)->nsymbols; ++i )
{
complex<signed char> *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<T> > 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

File diff suppressed because it is too large Load Diff

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#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 <dsp/basebandsamplesink.h>
#include <dsp/devicesamplesource.h>
#include <dsp/dspcommands.h>
#include <dsp/downchannelizer.h>
#include <dsp/fftfilt.h>
#include <QMutex>
#include <QElapsedTimer>
#include <vector>
#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 <QBuffer>
#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<cf32> *p_rawiq;
pipewriter<cf32> *p_rawiq_writer;
pipebuf<cf32> *p_preprocessed;
// NOTCH FILTER
auto_notch<f32> *r_auto_notch;
pipebuf<cf32> *p_autonotched;
// FREQUENCY CORRECTION : DEROTATOR
pipebuf<cf32> *p_derot;
rotator<f32> *r_derot;
// CNR ESTIMATION
pipebuf<f32> *p_cnr;
cnr_fft<f32> *r_cnr;
//FILTERING
fir_filter<cf32,float> *r_resample;
pipebuf<cf32> *p_resampled;
float *coeffs;
int ncoeffs;
// OUTPUT PREPROCESSED DATA
sampler_interface<f32> *sampler;
float *coeffs_sampler;
int ncoeffs_sampler;
pipebuf<softsymbol> *p_symbols;
pipebuf<f32> *p_freq;
pipebuf<f32> *p_ss;
pipebuf<f32> *p_mer;
pipebuf<cf32> *p_sampled;
//DECIMATION
pipebuf<cf32> *p_decimated;
decimator<cf32> *p_decim;
//PROCESSED DATA MONITORING
file_writer<cf32> *r_ppout;
//GENERIC CONSTELLATION RECEIVER
cstln_receiver<f32> *m_objDemodulator;
// DECONVOLUTION AND SYNCHRONIZATION
pipebuf<u8> *p_bytes;
deconvol_sync_simple *r_deconv;
viterbi_sync *r;
pipebuf<u8> *p_descrambled;
pipebuf<u8> *p_frames;
etr192_descrambler * r_etr192_descrambler;
hdlc_sync *r_sync;
pipebuf<u8> *p_mpegbytes;
pipebuf<int> *p_lock;
pipebuf<u32> *p_locktime;
mpeg_sync<u8,0> *r_sync_mpeg;
// DEINTERLEAVING
pipebuf< rspacket<u8> > *p_rspackets;
deinterleaver<u8> *r_deinter;
// REED-SOLOMON
pipebuf<int> *p_vbitcount;
pipebuf<int> *p_verrcount;
pipebuf<tspacket> *p_rtspackets;
rs_decoder<u8,0> *r_rsdec;
// BER ESTIMATION
pipebuf<float> *p_vber;
rate_estimator<float> *r_vber;
// DERANDOMIZATION
pipebuf<tspacket> *p_tspackets;
derandomizer *r_derand;
//OUTPUT
file_writer<tspacket> *r_stdout;
datvvideoplayer<tspacket> *r_videoplayer;
//CONSTELLATION
datvconstellation<f32> *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

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDockWidget>
#include <QMainWindow>
#include <QMediaMetaData>
#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();
}

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_DATVDEMODGUI_H
#define INCLUDE_DATVDEMODGUI_H
#include "gui/rollupwidget.h"
#include <plugin/plugininstancegui.h>
#include "dsp/channelmarker.h"
#include "dsp/movingaverage.h"
#include "datvdemod.h"
#include <QVideoWidget>
#include <QMediaPlayer>
#include <QTimer>
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

View File

@ -0,0 +1,637 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DATVDemodGUI</class>
<widget class="RollupWidget" name="DATVDemodGUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>512</width>
<height>520</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>512</width>
<height>520</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>512</width>
<height>520</height>
</size>
</property>
<property name="font">
<font>
<family>Sans Serif</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="windowTitle">
<string>DATV Demodulator</string>
</property>
<widget class="QGroupBox" name="groupBox">
<property name="geometry">
<rect>
<x>10</x>
<y>0</y>
<width>496</width>
<height>250</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>496</width>
<height>250</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>496</width>
<height>250</height>
</size>
</property>
<property name="title">
<string>DATV Settings</string>
</property>
<widget class="QWidget" name="layoutWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>20</y>
<width>222</width>
<height>222</height>
</rect>
</property>
<layout class="QHBoxLayout" name="screenTVLayout">
<property name="sizeConstraint">
<enum>QLayout::SetMinimumSize</enum>
</property>
<item>
<widget class="DATVScreen" name="screenTV" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>220</width>
<height>220</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>220</width>
<height>220</height>
</size>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QFrame" name="frame">
<property name="geometry">
<rect>
<x>230</x>
<y>20</y>
<width>261</width>
<height>221</height>
</rect>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<widget class="QComboBox" name="cmbStandard">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>81</width>
<height>21</height>
</rect>
</property>
<item>
<property name="text">
<string>DVB-S</string>
</property>
</item>
<item>
<property name="text">
<string>DVB-S2</string>
</property>
</item>
</widget>
<widget class="QComboBox" name="cmbModulation">
<property name="geometry">
<rect>
<x>10</x>
<y>40</y>
<width>80</width>
<height>21</height>
</rect>
</property>
<item>
<property name="text">
<string>BPSK</string>
</property>
</item>
<item>
<property name="text">
<string>QPSK</string>
</property>
</item>
<item>
<property name="text">
<string>8PSK</string>
</property>
</item>
<item>
<property name="text">
<string>16APSK</string>
</property>
</item>
<item>
<property name="text">
<string>32APSK</string>
</property>
</item>
<item>
<property name="text">
<string>64APSKe</string>
</property>
</item>
<item>
<property name="text">
<string>16QAM</string>
</property>
</item>
<item>
<property name="text">
<string>64QAM</string>
</property>
</item>
<item>
<property name="text">
<string>256QAM</string>
</property>
</item>
</widget>
<widget class="QComboBox" name="cmbFEC">
<property name="geometry">
<rect>
<x>10</x>
<y>70</y>
<width>80</width>
<height>21</height>
</rect>
</property>
<item>
<property name="text">
<string>1/2</string>
</property>
</item>
<item>
<property name="text">
<string>2/3</string>
</property>
</item>
<item>
<property name="text">
<string>3/4</string>
</property>
</item>
<item>
<property name="text">
<string>5/6</string>
</property>
</item>
<item>
<property name="text">
<string>7/8</string>
</property>
</item>
<item>
<property name="text">
<string>4/5</string>
</property>
</item>
<item>
<property name="text">
<string>8/9</string>
</property>
</item>
<item>
<property name="text">
<string>9/10</string>
</property>
</item>
</widget>
<widget class="QCheckBox" name="chkFastlock">
<property name="geometry">
<rect>
<x>10</x>
<y>100</y>
<width>101</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>FAST LOCK</string>
</property>
</widget>
<widget class="QCheckBox" name="chkViterbi">
<property name="geometry">
<rect>
<x>140</x>
<y>140</y>
<width>81</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>VITERBI</string>
</property>
</widget>
<widget class="QCheckBox" name="chkHardMetric">
<property name="geometry">
<rect>
<x>10</x>
<y>120</y>
<width>111</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>HARD METRIC</string>
</property>
</widget>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>100</x>
<y>40</y>
<width>61</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>Symbols/s</string>
</property>
</widget>
<widget class="QLabel" name="label_2">
<property name="geometry">
<rect>
<x>100</x>
<y>10</y>
<width>71</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>Bandwidth</string>
</property>
</widget>
<widget class="QCheckBox" name="chkHDLC">
<property name="geometry">
<rect>
<x>10</x>
<y>140</y>
<width>101</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>HDLC</string>
</property>
</widget>
<widget class="QCheckBox" name="chkAllowDrift">
<property name="geometry">
<rect>
<x>140</x>
<y>100</y>
<width>111</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>ALLOW DRIFT</string>
</property>
</widget>
<widget class="QSpinBox" name="spiNotchFilters">
<property name="geometry">
<rect>
<x>170</x>
<y>70</y>
<width>81</width>
<height>23</height>
</rect>
</property>
<property name="maximum">
<number>32</number>
</property>
</widget>
<widget class="QLabel" name="label_3">
<property name="geometry">
<rect>
<x>100</x>
<y>70</y>
<width>71</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>Notch filter</string>
</property>
</widget>
<widget class="QCheckBox" name="chkResample">
<property name="geometry">
<rect>
<x>140</x>
<y>120</y>
<width>85</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>RESAMPLE</string>
</property>
</widget>
<widget class="QProgressBar" name="prgSynchro">
<property name="geometry">
<rect>
<x>70</x>
<y>190</y>
<width>181</width>
<height>20</height>
</rect>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
<widget class="QLabel" name="lblStatus">
<property name="geometry">
<rect>
<x>10</x>
<y>170</y>
<width>111</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>-</string>
</property>
</widget>
<widget class="QPushButton" name="pushButton_2">
<property name="geometry">
<rect>
<x>230</x>
<y>140</y>
<width>21</width>
<height>22</height>
</rect>
</property>
<property name="text">
<string>R</string>
</property>
</widget>
<widget class="QSpinBox" name="spiSymbolRate">
<property name="geometry">
<rect>
<x>170</x>
<y>40</y>
<width>81</width>
<height>23</height>
</rect>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>1024000000</number>
</property>
<property name="singleStep">
<number>1000</number>
</property>
</widget>
<widget class="QSpinBox" name="spiBandwidth">
<property name="geometry">
<rect>
<x>170</x>
<y>10</y>
<width>81</width>
<height>23</height>
</rect>
</property>
<property name="minimum">
<number>1000</number>
</property>
<property name="maximum">
<number>1024000000</number>
</property>
<property name="singleStep">
<number>1000</number>
</property>
</widget>
<widget class="QLabel" name="lblRate">
<property name="geometry">
<rect>
<x>130</x>
<y>170</y>
<width>121</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>-</string>
</property>
</widget>
<widget class="QLabel" name="label_4">
<property name="geometry">
<rect>
<x>10</x>
<y>190</y>
<width>61</width>
<height>15</height>
</rect>
</property>
<property name="text">
<string>Buffer:</string>
</property>
</widget>
</widget>
</widget>
<widget class="QGroupBox" name="groupBox_2">
<property name="geometry">
<rect>
<x>10</x>
<y>260</y>
<width>496</width>
<height>240</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>496</width>
<height>240</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>496</width>
<height>240</height>
</size>
</property>
<property name="title">
<string>VIDEO Stream</string>
</property>
<widget class="QWidget" name="layoutWidget_2">
<property name="geometry">
<rect>
<x>0</x>
<y>20</y>
<width>358</width>
<height>211</height>
</rect>
</property>
<layout class="QHBoxLayout" name="screenTVLayout_2">
<property name="sizeConstraint">
<enum>QLayout::SetMinimumSize</enum>
</property>
<item>
<widget class="DATVideoRender" name="screenTV_2" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>356</width>
<height>200</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>356</width>
<height>200</height>
</size>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QFrame" name="lblState">
<property name="geometry">
<rect>
<x>360</x>
<y>20</y>
<width>131</width>
<height>211</height>
</rect>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<widget class="QPushButton" name="pushButton_3">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>111</width>
<height>27</height>
</rect>
</property>
<property name="text">
<string> Video</string>
</property>
</widget>
<widget class="QPushButton" name="pushButton_4">
<property name="geometry">
<rect>
<x>10</x>
<y>50</y>
<width>111</width>
<height>27</height>
</rect>
</property>
<property name="text">
<string>Full Screen</string>
</property>
</widget>
<widget class="QLabel" name="lblRead">
<property name="geometry">
<rect>
<x>10</x>
<y>120</y>
<width>111</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>-</string>
</property>
</widget>
<widget class="QLabel" name="lblReadStatus">
<property name="geometry">
<rect>
<x>10</x>
<y>90</y>
<width>111</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>-</string>
</property>
</widget>
</widget>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>RollupWidget</class>
<extends>QWidget</extends>
<header>gui/rollupwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>DATVScreen</class>
<extends>QWidget</extends>
<header>datvscreen.h</header>
</customwidget>
<customwidget>
<class>DATVideoRender</class>
<extends>QWidget</extends>
<header>datvideorender.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QtPlugin>
#include <QAction>
#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);
}

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_DATVPLUGIN_H
#define INCLUDE_DATVPLUGIN_H
#include <QObject>
#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

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#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<QIODevice*>(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<QIODevice*>(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<void *>(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;
}

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef DATVIDEORENDER_H
#define DATVIDEORENDER_H
#include <QWidget>
#include <QEvent>
#include <QIODevice>
#include <QThread>
#include "datvscreen.h"
#include "datvideostream.h"
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include <libavutil/opt.h>
#include <libavutil/channel_layout.h>
#include <libavutil/common.h>
#include <libavutil/imgutils.h>
#include <libavutil/mathematics.h>
#include <libavutil/samplefmt.h>
#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

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "datvideostream.h"
#include <stdio.h>
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()<MinStackSize))
//|| (m_intBytesWaiting<10*len))
{
m_objMutex.unlock();
if(MultiThreaded==true)
{
while((m_objFIFO.isEmpty()) || (m_objFIFO.count()<MinStackSize))
{
QThread::msleep(5);
}
}
else
{
m_objeventLoop.exec();
}
m_objMutex.lock();
}
//Read DATA
intEffectiveLen=m_objFIFO.head().size();
if(intExpectedLen<intEffectiveLen)
{
//Partial Read
objCurrentArray = m_objFIFO.head();
memcpy((void *)data,objCurrentArray.constData(),intExpectedLen);
m_objFIFO.head().remove(0,intExpectedLen);
intEffectiveLen=intExpectedLen;
m_intBytesWaiting -= intExpectedLen;
}
else
{
//Complete Read
objCurrentArray = m_objFIFO.dequeue();
memcpy((void *)data,objCurrentArray.constData(),intEffectiveLen);
m_intBytesWaiting -= intEffectiveLen;
}
m_intQueueWaiting = m_objFIFO.count();
m_intPercentBuffer = (100*m_intBytesWaiting)/m_intMemoryLimit;
emit onDataPackets(&m_intQueueWaiting, &m_intBytesWaiting, &m_intPercentBuffer, &m_intTotalReceived);
//Next available DATA
m_intBytesAvailable = m_objFIFO.head().size();
m_objMutex.unlock();
return (qint64)intEffectiveLen;
}
qint64 DATVideostream::writeData(const char *data, qint64 len)
{
return 0;
}
qint64 DATVideostream::readLineData(char *data, qint64 maxSize)
{
return 0;
}

View File

@ -0,0 +1,77 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef DATVIDEOSTREAM_H
#define DATVIDEOSTREAM_H
#include <QIODevice>
#include <QQueue>
#include <QByteArray>
#include <QEventLoop>
#include <QMutex>
#include <QThread>
#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<QByteArray> 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

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QPainter>
#include <QMouseEvent>
#include <QOpenGLContext>
#include <QOpenGLFunctions>
#include <QSurface>
#include "datvscreen.h"
#include <algorithm>
#include <QDebug>
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));
}
}

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_DATVSCREEN_H
#define INCLUDE_DATVSCREEN_H
#include <QGLWidget>
#include <QPen>
#include <QTimer>
#include <QMutex>
#include <QFont>
#include <QMatrix4x4>
#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

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef DATVVIDEOPLAYER_H
#define DATVVIDEOPLAYER_H
#include "leansdr/framework.h"
#include "datvideostream.h"
namespace leansdr
{
template<typename T> struct datvvideoplayer : runnable
{
datvvideoplayer(scheduler *sch, pipebuf<T> &_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<T> in;
DATVideostream * m_objVideoStream;
};
}
#endif // DATVVIDEOPLAYER_H

View File

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

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#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;
}

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_GUI_GLSHADERARRAY_H_
#define INCLUDE_GUI_GLSHADERARRAY_H_
#include <QString>
#include <QOpenGLFunctions>
#include <QOpenGLFunctions_2_0>
#include <QOpenGLFunctions_2_1>
#include <QOpenGLFunctions_3_0>
#include <QOpenGLTexture>
#include <QOpenGLShaderProgram>
#include <QOpenGLContext>
#include <QMatrix4x4>
#include <QVector4D>
#include <QDebug>
#include <QColor>
#include <math.h>
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_ */

View File

@ -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<typename Tin, // Input IQ symbols
typename Thist, // Input shift register (IQIQIQ...)
Thist POLY_DECONVOL, // Taps (IQIQIQ...)
Thist POLY_ERRORS>
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<typename Tin, // Input IQ symbols
typename Thist, // Input shift registers (one for I, one for Q)
typename Tpoly, // Taps (interleaved IQIQIQ...)
Tpoly POLY_DECONVOL,
Tpoly POLY_ERRORS>
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<typename Thist, uint64_t POLY1, uint64_t POLY2>
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<typename Thist, int HISTSIZE>
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<<bps) - 1;
for ( ; count--; ++pin ) {
uncoded_byte b = *pin;
for ( int bit=8; bit--; ) {
hist = (hist>>1) | ((Thist)((b>>bit)&1)<<(HISTSIZE-1));
++nhist;
if ( nhist == bits_in ) {
for ( int p=0; p<bits_out; ++p ) {
int b = parity((Thist)(hist & polys[p]));
sersymb = (sersymb<<1) | b;
}
nhist = 0;
nsersymb += bits_out;
while ( nsersymb >= bps ) {
hardsymbol s = (sersymb >> (nsersymb-bps)) & symbmask;
*pout++ = s;
nsersymb -= bps;
}
}
}
}
// Ensure deterministic output size
// TBD We can relax this
if ( nhist || nsersymb ) fatal("partial run");
}
private:
Thist hist;
int nhist;
Thist sersymb;
int nsersymb;
}; // convol_multipoly
} // namespace
#endif // LEANSDR_CONVOLUTIONAL_H

View File

@ -0,0 +1,351 @@
#ifndef LEANSDR_DSP_H
#define LEANSDR_DSP_H
#include <math.h>
#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<typename Tin, int Zin, typename Tout, int Zout, int Gn, int Gd>
struct cconverter : runnable {
cconverter(scheduler *sch, pipebuf< complex<Tin> > &_in,
pipebuf< complex<Tout> > &_out)
: runnable(sch, "cconverter"),
in(_in), out(_out) {
}
void run() {
unsigned long count = min(in.readable(), out.writable());
complex<Tin> *pin=in.rd(), *pend=pin+count;
complex<Tout> *pout = out.wr();
for ( ; pin<pend; ++pin,++pout ) {
pout->re = Zout + (pin->re-(Tin)Zin)*Gn/Gd;
pout->im = Zout + (pin->im-(Tin)Zin)*Gn/Gd;
}
in.read(count);
out.written(count);
}
private:
pipereader< complex<Tin> > in;
pipewriter< complex<Tout> > out;
};
template<typename T>
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<n; ++i ) {
bitrev[i] = 0;
for ( int b=0; b<logn; ++b ) bitrev[i] = (bitrev[i]<<1) | ((i>>b)&1);
}
// Float constants
omega = new complex<T>[n];
omega_rev = new complex<T>[n];
for ( int i=0; i<n; ++i ) {
float a = 2.0*M_PI * i / n;
omega_rev[i].re = (omega[i].re = cosf(a));
omega_rev[i].im = - (omega[i].im = sinf(a));
}
}
void inplace(complex<T> *data, bool reverse=false) {
// Bit-reversal permutation
for ( int i=0; i<n; ++i ) {
int r = bitrev[i];
if ( r < i ) { complex<T> tmp=data[i]; data[i]=data[r]; data[r]=tmp; }
}
complex<T> *om = reverse ? omega_rev : omega;
// Danielson-Lanczos
for ( int i=0; i<logn; ++i ) {
int hbs = 1 << i;
int dom = 1 << (logn-1-i);
for ( int j=0; j<dom; ++j ) {
int p = j*hbs*2, q = p+hbs;
for ( int k=0; k<hbs; ++k ) {
complex<T> &w = om[k*dom];
complex<T> &dqk = data[q+k];
complex<T> 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<n; ++i ) {
data[i].re *= invn;
data[i].im *= invn;
}
}
}
private:
int logn;
int *bitrev;
complex<T> *omega, *omega_rev;
float invsqrtn;
};
template<typename T>
struct adder : runnable {
adder(scheduler *sch,
pipebuf<T> &_in1, pipebuf<T> &_in2, pipebuf<T> &_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<T> in1, in2;
pipewriter<T> out;
};
template<typename Tscale, typename Tin, typename Tout>
struct scaler : runnable {
Tscale scale;
scaler(scheduler *sch, Tscale _scale,
pipebuf<Tin> &_in, pipebuf<Tout> &_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<pend; ++pin,++pout ) *pout = *pin * scale;
in.read(count);
out.written(count);
}
private:
pipereader<Tin> in;
pipewriter<Tout> out;
};
// [awgb_c] generates complex white gaussian noise.
template<typename T>
struct wgn_c : runnable {
wgn_c(scheduler *sch, pipebuf< complex<T> > &_out)
: runnable(sch, "awgn"), stddev(1.0), out(_out) {
}
void run() {
int n = out.writable();
complex<T> *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<T> > out;
};
template<typename T>
struct naive_lowpass : runnable {
naive_lowpass(scheduler *sch, pipebuf<T> &_in, pipebuf<T> &_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<pend; ++pin,++pout ) {
T x = 0.0;
for ( int i=0; i<w; ++i ) x = x + pin[i];
*pout = x * k;
}
in.read(count);
out.written(count);
}
private:
pipereader<T> in;
pipewriter<T> out;
int w;
};
template<typename T, typename Tc>
struct fir_filter : runnable {
fir_filter(scheduler *sch, int _ncoeffs, Tc *_coeffs,
pipebuf<T> &_in, pipebuf<T> &_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<pend; pin+=decim,++pout ) {
T *pc = shifted_coeffs;
T *pi = pin;
T x = 0;
for ( unsigned int i=ncoeffs; i--; ++pc,--pi )
x = x + (*pc)*(*pi);
*pout = x;
}
in.read(count*decim);
out.written(count);
}
private:
unsigned int ncoeffs;
Tc *coeffs;
pipereader<T> in;
pipewriter<T> out;
unsigned int decim;
T *shifted_coeffs;
float current_freq;
void set_freq(float f) {
for ( int i=0; i<ncoeffs; ++i ) {
float a = 2*M_PI * f * (i-ncoeffs/2);
float c=cosf(a), s=sinf(a);
// TBD Support T=complex
shifted_coeffs[i].re = coeffs[i] * c;
shifted_coeffs[i].im = coeffs[i] * s;
}
current_freq = f;
}
public:
float *freq_tap;
float tap_multiplier;
float freq_tol;
}; // fir_filter
// FIR FILTER WITH INTERPOLATION AND DECIMATION
template<typename T, typename Tc>
struct fir_resampler : runnable {
fir_resampler(scheduler *sch, int _ncoeffs, Tc *_coeffs,
pipebuf<T> &_in, pipebuf<T> &_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<pend; ++pin ) {
for ( int i=0; i<interp; ++i,++pout ) {
T *pi = pin;
T *pc = shifted_coeffs+i, *pcend=shifted_coeffs+ncoeffs;
T x = 0;
for ( ; pc<pcend; pc+=interp,--pi )
x = x + (*pc)*(*pi);
*pout = x;
}
}
in.read(count);
out.written(count*interp);
}
private:
unsigned int ncoeffs;
Tc *coeffs;
int interp, decim;
pipereader<T> in;
pipewriter<T> 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<ncoeffs; ++i ) {
float a = 2*M_PI * f * i;
float c=cosf(a), s=sinf(a);
// TBD Support T=complex
shifted_coeffs[i].re = coeffs[i] * c;
shifted_coeffs[i].im = coeffs[i] * s;
}
current_freq = f;
}
}; // fir_resampler
} // namespace
#endif // LEANSDR_DSP_H

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,90 @@
#ifndef LEANSDR_FILTERGEN_H
#define LEANSDR_FILTERGEN_H
#include <math.h>
namespace leansdr {
namespace filtergen {
template<typename T>
void normalize_power(int n, T *coeffs, float gain=1) {
float s2 = 0;
for ( int i=0; i<n; ++i ) s2 = s2 + coeffs[i]*coeffs[i]; // TBD complex
if ( s2 ) gain /= gen_sqrt(s2);
for ( int i=0; i<n; ++i ) coeffs[i] = coeffs[i] * gain;
}
template<typename T>
void normalize_dcgain(int n, T *coeffs, float gain=1) {
float s = 0;
for ( int i=0; i<n; ++i ) s = s + coeffs[i];
if ( s ) gain /= s;
for ( int i=0; i<n; ++i ) coeffs[i] = coeffs[i] * gain;
}
// Generate coefficients for a sinc filter.
// https://en.wikipedia.org/wiki/Sinc_filter
template<typename T>
int lowpass(int order, float Fcut, T **coeffs, float gain=1) {
int ncoeffs = order + 1;
*coeffs = new T[ncoeffs];
for ( int i=0; i<ncoeffs; ++i ) {
float t = i - (ncoeffs-1)*0.5;
float sinc = 2*Fcut * (t ? sin(2*M_PI*Fcut*t)/(2*M_PI*Fcut*t) : 1);
#if 0 // Hamming
float alpha = 25.0/46, beta = 21.0/46;
float window = alpha - beta*cos(2*M_PI*i/order);
#else
float window = 1;
#endif
(*coeffs)[i] = sinc * window;
}
normalize_dcgain(ncoeffs, *coeffs, gain);
return ncoeffs;
}
// Generate coefficients for a RRC filter.
// https://en.wikipedia.org/wiki/Root-raised-cosine_filter
template<typename T>
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<ncoeffs; ++i ) {
int t = i - ncoeffs/2;
float c;
if ( t == 0 )
c = sqrt(Fs) * (1-B+4*B/pi);
else {
float tT = t * Fs;
float den = pi*tT*(1-(4*B*tT)*(4*B*tT));
if ( ! den )
c = B*sqrt(Fs/2) * ( (1+2/pi)*sin(pi/(4*B)) +
(1-2/pi)*cos(pi/(4*B)) );
else
c = sqrt(Fs) * ( sin(pi*tT*(1-B)) +
4*B*tT*cos(pi*tT*(1+B)) ) / den;
}
(*coeffs)[i] = c;
}
normalize_dcgain(ncoeffs, *coeffs);
return ncoeffs;
}
// Dump filter coefficients for matlab/octave
inline void dump_filter(const char *name, int ncoeffs, float *coeffs) {
fprintf(stderr, "%s = [", name);
for ( int i=0; i<ncoeffs; ++i )
fprintf(stderr, "%s %f", (i?",":""), coeffs[i]);
fprintf(stderr, " ];\n");
}
} // namespace
} // namespace
#endif // LEANSDR_FILTERGEN_H

View File

@ -0,0 +1,269 @@
#ifndef LEANSDR_FRAMEWORK_H
#define LEANSDR_FRAMEWORK_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
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; i<nrunnables; ++i )
runnables[i]->run();
}
void run() {
unsigned long long prev_hash = 0;
while ( 1 ) {
step();
unsigned long long h = hash();
if ( h == prev_hash ) break;
prev_hash = h;
}
}
void shutdown() {
for ( int i=0; i<nrunnables; ++i )
runnables[i]->shutdown();
}
unsigned long long hash() {
unsigned long long h = 0;
for ( int i=0; i<npipes; ++i ) h += (1+i)*pipes[i]->hash();
return h;
}
void dump() {
fprintf(stderr, "\n");
size_t total_bufs = 0;
for ( int i=0; i<npipes; ++i ) pipes[i]->dump(&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<typename T>
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<nrd; ++i ) if ( rds[i] < rd ) rd = rds[i];
memmove(buf, rd, (wr-rd)*sizeof(T));
wr -= rd - buf;
for ( int i=0; i<nrd; ++i ) rds[i] -= rd - buf;
}
long long hash() {
return total_written + total_read;
}
void dump(size_t *total_bufs) {
if ( total_written < 10000 )
fprintf(stderr, ".%-16s : %4ld/%4ld", name,
total_read, total_written);
else if ( total_written < 1000000 )
fprintf(stderr, ".%-16s : %3ldk/%3ldk", name,
total_read/1000, total_written/1000);
else
fprintf(stderr, ".%-16s : %3ldM/%3ldM", name,
total_read/1000000, total_written/1000000);
*total_bufs += (end-buf) * sizeof(T);
unsigned long nw = end - wr;
fprintf(stderr, " %6ld writable %c,", nw, (nw<min_write)?'!':' ');
T *rd = wr;
for ( int j=0; j<nrd; ++j ) if ( rds[j] < rd ) rd = rds[j];
fprintf(stderr, " %6d unread (", (int)(wr-rd));
for ( int j=0; j<nrd; ++j )
fprintf(stderr, " %d", (int)(wr-rds[j]));
fprintf(stderr, " )\n");
}
unsigned long min_write;
unsigned long total_written, total_read;
#ifdef DEBUG
~pipebuf() { fprintf(stderr, "Deallocating %s !\n", name); }
#endif
};
template<typename T>
struct pipewriter {
pipebuf<T> &buf;
pipewriter(pipebuf<T> &_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<typename T>
pipewriter<T> *opt_writer(pipebuf<T> *buf) {
return buf ? new pipewriter<T>(*buf) : NULL;
}
template<typename T>
bool opt_writable(pipewriter<T> *p, int n=1) {
return (p==NULL) || p->writable()>=n;
}
template<typename T>
void opt_write(pipewriter<T> *p, T val) {
if ( p ) p->write(val);
}
template<typename T>
struct pipereader {
pipebuf<T> &buf;
int id;
pipereader(pipebuf<T> &_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<typename T> 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<typename T> 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<typename T> 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<typename T> 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<typename T>
T min(const T &x, const T &y) { return (x<y) ? x : y; }
template<typename T>
T max(const T &x, const T &y) { return (x<y) ? y : x; }
// Abreviations for integer types
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned long u32;
typedef signed char s8;
typedef signed short s16;
typedef signed long s32;
} // namespace
#endif // LEANSDR_FRAMEWORK_H

View File

@ -0,0 +1,348 @@
#ifndef LEANSDR_GENERIC_H
#define LEANSDR_GENERIC_H
#include <sys/types.h>
#include <unistd.h>
#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<typename T>
struct file_reader : runnable {
file_reader(scheduler *sch, int _fdin, pipebuf<T> &_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<T> out;
};
// [file_writer] writes raw data from a [pipebuf] to a file descriptor.
template<typename T>
struct file_writer : runnable {
file_writer(scheduler *sch, pipebuf<T> &_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<T> in;
int fdout;
};
// [file_printer] writes data from a [pipebuf] to a file descriptor,
// with printf-style formatting and optional scaling.
template<typename T>
struct file_printer : runnable {
file_printer(scheduler *sch, const char *_format,
pipebuf<T> &_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<pend; ++pin ) {
if ( ++phase >= 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<T> 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<typename T>
struct file_carrayprinter : runnable {
file_carrayprinter(scheduler *sch,
const char *_head,
const char *_format,
const char *_sep,
const char *_tail,
pipebuf< complex<T> > &_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<T> *pin = in.rd();
for ( int i=0; i<n; ++i ) {
if ( i ) fprintf(fout, "%s", sep);
fprintf(fout, format, pin[i].re*scale, pin[i].im*scale);
}
fprintf(fout, "%s", tail);
}
fflush(fout);
in.read(n);
}
}
T scale;
int fixed_size; // Number of elements per batch, or 0.
private:
pipereader< complex<T> > in;
const char *head, *format, *sep, *tail;
FILE *fout;
};
template<typename T, int N>
struct file_vectorprinter : runnable {
file_vectorprinter(scheduler *sch,
const char *_head,
const char *_format,
const char *_sep,
const char *_tail,
pipebuf<T[N]> &_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<N; ++i ) {
if ( i ) fprintf(fout, "%s", sep);
fprintf(fout, format, (*pin)[i]*scale);
}
fprintf(fout, "%s", tail);
in.read(1);
}
fflush(fout);
}
T scale;
private:
pipereader<T[N]> 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<typename Tin, typename Tout>
struct itemcounter : runnable {
itemcounter(scheduler *sch, pipebuf<Tin> &_in, pipebuf<Tout> &_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<Tin> in;
pipewriter<Tout> out;
};
// [decimator] forwards 1 in N sample.
template<typename T>
struct decimator : runnable {
unsigned int d;
decimator(scheduler *sch, int _d, pipebuf<T> &_in, pipebuf<T> &_out)
: runnable(sch, "decimator"),
d(_d),
in(_in), out(_out) {
}
void run() {
unsigned long count = min(in.readable()/d, out.writable());
T *pin=in.rd(), *pend=pin+count*d, *pout=out.wr();
for ( ; pin<pend; pin+=d, ++pout )
*pout = *pin;
in.read(count*d);
out.written(count);
}
private:
pipereader<T> in;
pipewriter<T> out;
};
// [rate_estimator] accumulates counts of two quantities
// and periodically outputs their ratio.
template<typename T>
struct rate_estimator : runnable {
int sample_size;
rate_estimator(scheduler *sch,
pipebuf<int> &_num, pipebuf<int> &_den,
pipebuf<float> &_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<int> num, den;
pipewriter<float> rate;
T acc_num, acc_den;
};
// SERIALIZER
template<typename Tin, typename Tout>
struct serializer : runnable {
serializer(scheduler *sch, pipebuf<Tin> &_in, pipebuf<Tout> &_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<Tin> in;
pipewriter<Tout> out;
}; // serializer
// [buffer_reader] reads from a user-supplied buffer.
template<typename T>
struct buffer_reader : runnable {
buffer_reader(scheduler *sch, T *_data, int _count, pipebuf<T> &_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<T> out;
int pos;
}; // buffer_reader
// [buffer_writer] writes to a user-supplied buffer.
template<typename T>
struct buffer_writer : runnable {
buffer_writer(scheduler *sch, pipebuf<T> &_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<T> in;
T *data;
int count;
int pos;
}; // buffer_writer
} // namespace
#endif // LEANSDR_GENERIC_H

View File

@ -0,0 +1,591 @@
#ifndef LEANSDR_GUI_H
#define LEANSDR_GUI_H
#include <sys/time.h>
#include "framework.h"
namespace leansdr {
//////////////////////////////////////////////////////////////////////
// GUI blocks
//////////////////////////////////////////////////////////////////////
#ifdef GUI
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
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<<b;
clicks |= 1<<b;
mx = ev.xbutton.x;
my = ev.xbutton.y;
break;
}
case ButtonRelease: {
int b = ev.xbutton.button;
buttons &= ~(1<<b);
mx = ev.xbutton.x;
my = ev.xbutton.y;
break;
}
case MotionNotify:
mx = ev.xbutton.x;
my = ev.xbutton.y;
mmoved = true;
break;
}
}
}
void setfg(unsigned char r, unsigned char g, unsigned char b) {
XColor c;
c.red = r<<8; c.green = g<<8; c.blue = b<<8;
c.flags = DoRed | DoGreen | DoBlue;
if ( ! XAllocColor(display, DefaultColormap(display,screen), &c) )
fatal("color");
XSetForeground(display, gc, c.pixel);
}
void point(int x, int y) {
XDrawPoint(display, dbuf, gc, x, y);
}
void line(int x0, int y0, int x1, int y1) {
XDrawLine(display, dbuf, gc, x0,y0, x1,y1);
}
void text(int x, int y, const char *s) {
XDrawString(display, dbuf, gc, x,y, s, strlen(s));
}
void transient_text(int x, int y, const char *s) {
XDrawString(display, window, gc, x,y, s, strlen(s));
}
int buttons; // Mask of button states (2|4|8)
int clicks; // Same, accumulated (must be cleared by owner)
int mx, my; // Cursor position
bool mmoved; // Pointer moved (must be cleared by owner)
};
template<typename T>
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<T> > &_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<T> *p = in.rd(), *pend = p+pixels_per_frame;
for ( ; p<pend; ++p )
g.point(g.w*(p->re-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<signed char> *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<T> > 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<typename T>
struct wavescope : runnable {
T ymin, ymax;
unsigned long decimation;
wavescope(scheduler *sch, pipebuf<T> &_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<pend; ++x,++p ) {
T v = *p;
g.point(x, g.h-1 - (g.h-1)*(v-ymin)/(ymax-ymin));
}
g.show();
g.sync();
}
private:
pipereader<T> in;
int phase;
gfx g;
int x;
};
template<typename T>
struct slowmultiscope : runnable {
struct chanspec {
pipebuf<T> *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<nspecs; ++i ) {
if ( specs[i].flags & chanspec::DISABLED ) continue;
chans[nchans].spec = specs[i];
chans[nchans].in = new pipereader<T>(*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; c<chans+nchans; ++c )
if ( ! (c->spec.flags&chanspec::ASYNC) )
count = min(count, c->in->readable());
for ( int n=count; n--; ) {
for ( channel *c=chans; c<chans+nchans; ++c ) {
int nr;
if ( c->spec.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; i<nchans; ++i ) {
channel *c = &chans[i];
g.setfg(c->spec.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<T> *in;
float accum;
int prev_y;
float print_val;
} *chans;
gfx g;
unsigned long t;
int x;
int total_samples;
};
template<typename T>
struct spectrumscope : runnable {
T ymax;
float amax;
unsigned long size;
unsigned long decimation;
spectrumscope(scheduler *sch, pipebuf< complex<T> > & _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<T> > in;
int phase;
gfx g;
cfft_engine<float> *fft;
void do_fft(complex<T> *input) {
draw_begin();
if ( !fft || fft->n!=size ) {
if ( fft ) delete fft;
fft = new cfft_engine<float>(size);
}
complex<T> *pin=input, *pend=pin+size;
complex<float> data[size], *pout=data;
g.setfg(255, 0, 0);
for ( int x=0; pin<pend; ++pin,++pout,++x ) {
pout->re = (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<size; ++i ) {
int x = ((i<size/2)?i+size/2:i-size/2) * g.w / size;
complex<float> 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<typename T>
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<T> > & _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<T> > in;
int phase;
gfx g;
cfft_engine<float> *fft;
float *filtered;
void do_fft(complex<T> *input) {
g.events();
draw_begin();
if ( !fft || fft->n!=size ) {
if ( fft ) delete fft;
fft = new cfft_engine<float>(size);
}
// Convert to complex<float> and transform
complex<T> *pin=input, *pend=pin+size;
complex<float> data[size], *pout=data;
for ( int x=0; pin<pend; ++pin,++pout,++x ) {
pout->re = (float)pin->re;
pout->im = (float)pin->im;
}
fft->inplace(data, true);
float amp2[size];
for ( int i=0; i<size; ++i ) {
complex<float> &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<size; ++i ) filtered[i] = amp2[i];
}
float bwcomp = 1 - bw;
g.setfg(0, 255, 0);
for ( int i=0; i<size; ++i ) {
filtered[i] = amp2[i]*bw + filtered[i]*bwcomp;
float db = filtered[i] ? 10 * logf(filtered[i])/logf(10) : db0;
int is = (i<size/2) ? i : i-size;
int x = g.w/2 + is*hzoom*g.w/size;
int y = g.h-1 - (db-db0)*g.h/dbrange;
g.line(x, g.h-1, x, y);
}
if ( g.buttons ) {
char s[256];
float freq = Fc + Fs*(g.mx-g.w/2)/g.w/hzoom;
float val = db0 + (float)((g.h-1)-g.my)*dbrange/g.h;
sprintf(s, "%f.3 Hz %f.2 dB", freq, val);
g.setfg(255, 255, 255);
g.text(16, 16, s);
}
// Draw cursors
g.setfg(255, 255, 0);
for ( int i=0; i<ncursors; ++i ) {
int x = g.w/2 + (cursors[i]-Fc)*hzoom*g.w/Fs;
g.line(x,0, x,g.h-1);
}
g.show();
g.sync();
}
void draw_begin() {
g.clear();
// dB scale
g.setfg(64, 64, 64);
for ( float db=floorf(db0); db<db0+dbrange; ++db ) {
int y = g.h-1 - (db-db0)*g.h/dbrange;
g.line(0,y, g.w-1,y);
}
// DC line
g.setfg(255, 255, 255);
g.line(g.w/2,0, g.w/2,g.h);
}
};
template<typename T>
struct genscope : runnable {
struct render {
int x, y;
char dir; // 'h'orizontal or 'v'ertical
};
struct chanspec {
pipebuf<T> *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<nchans; ++i ) {
if ( ! specs[i].in ) {
chans[i].in = NULL;
} else {
chans[i].spec = specs[i];
chans[i].in = new pipereader<T>(*specs[i].in);
}
}
g.clear();
gettimeofday(&tv, NULL);
}
struct channel {
chanspec spec;
pipereader<T> *in;
} *chans;
int nchans;
struct timeval tv;
void run() {
g.setfg(0, 255, 0);
for ( channel *pc=chans; pc<chans+nchans; ++pc ) {
if ( ! pc->in ) 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

View File

@ -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<pend; ++pin ) {
u8 byte_in = (*pin) ^ invertmask;
for ( int bits=8; bits--; byte_in<<=1 ) {
u8 bit_in = byte_in & 128;
shiftreg = (shiftreg>>1) | bit_in;
if ( ! inframe ) {
if ( shiftreg == 0x7e ) { // HDLC flag 01111110
inframe = true;
nbits_out = 0;
begin_frame();
}
} else {
if ( (shiftreg&0xfe) == 0x7c ) { // 0111110x HDLC stuffing
// Unstuff this 0
} else if ( shiftreg == 0x7e ) { // 01111110 HDLC flag
if ( nbits_out != 7 ) {
// Not at byte boundary
if ( debug ) fprintf(stderr, "^");
++*hdlc_errors;
} else {
// Checksum
crc16 ^= 0xffff;
if ( framesize<2 || framesize<minframesize ||
crc16!=crc16_check ) {
if ( debug ) fprintf(stderr, "!");
++*hdlc_errors;
// Do not report random noise as FCS errors
if ( framesize >= minframesize ) ++*fcs_errors;
} else {
if ( debug ) fprintf(stderr, "_");
// This will trigger output, but we finish the byte first.
*pdatasize = framesize-2;
}
}
nbits_out = 0;
begin_frame();
// Keep processing up to 7 remaining bits from byte_in.
// Special cases 0111111 and 1111111 cannot affect *pdatasize.
} else if ( shiftreg == 0xfe ) { // 11111110 HDLC invalid
if ( framesize ) {
if ( debug ) fprintf(stderr, "^");
++*hdlc_errors;
}
inframe = false;
} else { // Data bit
byte_out = (byte_out>>1) | bit_in; // HDLC is LSB first
++nbits_out;
if ( nbits_out == 8 ) {
if ( framesize < maxframesize ) {
framebuf[framesize++] = byte_out;
crc16_byte(byte_out);
}
nbits_out = 0;
}
}
} // inframe
} // bits
if ( *pdatasize != -1 ) {
// Found a complete frame
*ppin = pin+1;
return framebuf;
}
}
*ppin = pin;
return NULL;
}
private:
// 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<u8> &_in, // Packed bits
pipebuf<u8> &_out, // Bytes
int _minframesize, // Including CRC, excluding HDLC flags.
int _maxframesize,
// Status
pipebuf<int> *_lock_out=NULL,
pipebuf<int> *_framecount_out=NULL,
pipebuf<int> *_fcserrcount_out=NULL,
pipebuf<int> *_hdlcbytecount_out=NULL,
pipebuf<int> *_databytecount_out=NULL)
: runnable(sch, "hdlc_sync"),
minframesize(_minframesize),
maxframesize(_maxframesize),
chunk_size(maxframesize+2),
in(_in), out(_out, _maxframesize+chunk_size),
lock_out(opt_writer(_lock_out)),
framecount_out(opt_writer(_framecount_out)),
fcserrcount_out(opt_writer(_fcserrcount_out)),
hdlcbytecount_out(opt_writer(_hdlcbytecount_out)),
databytecount_out(opt_writer(_databytecount_out)),
cur_sync(0), resync_phase(0),
lock_state(false),
resync_period(32),
header16(false)
{
for ( int s=0; s<NSYNCS; ++s ) {
syncs[s].dec = new hdlc_dec(minframesize, maxframesize, s!=0);
for ( int h=0; h<NERRHIST; ++h ) syncs[s].errhist[h] = 0;
}
syncs[cur_sync].dec->debug = sch->debug;
errslot = 0;
}
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; s<NSYNCS; ++s ) {
if ( s != cur_sync ) syncs[s].dec->reset();
syncs[s].errhist[errslot] = 0;
for ( u8 *pin=in.rd(), *pend=pin+chunk_size; pin<pend; ) {
int datasize, hdlc_errors, fcs_errors;
u8 *f = syncs[s].dec->decode(&pin, pend-pin, &datasize,
&hdlc_errors, &fcs_errors);
syncs[s].errhist[errslot] += hdlc_errors;
if ( s == cur_sync ) {
if ( f ) {
lock_state = true;
output_frame(f, datasize);
databytecount += datasize;
++framecount;
}
fcserrcount += fcs_errors;
framecount += fcs_errors;
}
}
}
errslot = (errslot+1) % NERRHIST;
// Switch to another sync option ?
// Compare total error counts over about NERRHIST frames.
int total_errors[NSYNCS];
for ( int s=0; s<NSYNCS; ++s ) {
total_errors[s] = 0;
for ( int h=0; h<NERRHIST; ++h )
total_errors[s] += syncs[s].errhist[h];
}
int best = cur_sync;
for ( int s=0; s<NSYNCS; ++s )
if ( total_errors[s] < total_errors[best] ) best = s;
if ( best != cur_sync ) {
lock_state = false;
if ( sch->debug ) fprintf(stderr, "[%d:%d->%d:%d]",
cur_sync, total_errors[cur_sync],
best, total_errors[best]);
// No verbose messages on candidate syncs
syncs[cur_sync].dec->debug = false;
cur_sync = best;
syncs[cur_sync].dec->debug = sch->debug;
}
} else {
// Use only the currently selected decoder
for ( u8 *pin=in.rd(), *pend=pin+chunk_size; pin<pend; ) {
int datasize, hdlc_errors, fcs_errors;
u8 *f = syncs[cur_sync].dec->decode(&pin, pend-pin, &datasize,
&hdlc_errors, &fcs_errors);
if ( f ) {
lock_state = true;
output_frame(f, datasize);
databytecount += datasize;
++framecount;
}
fcserrcount += fcs_errors;
framecount += fcs_errors;
}
} // resync_phase
in.read(chunk_size);
hdlcbytecount += chunk_size;
if ( ++resync_phase >= resync_period ) resync_phase = 0;
} // Work to do
if ( lock_state != previous_lock_state )
opt_write(lock_out, lock_state?1:0);
opt_write(framecount_out, framecount);
opt_write(fcserrcount_out, fcserrcount);
opt_write(hdlcbytecount_out, hdlcbytecount);
opt_write(databytecount_out, databytecount);
}
private:
void output_frame(u8 *f, int size) {
if ( header16 ) {
// Removed 16-bit CRC, add 16-bit prefix -> Still <= maxframesize.
out.write(size >> 8);
out.write(size & 255);
}
memcpy(out.wr(), f, size);
out.written(size);
opt_write(framecount_out, 1);
}
int minframesize, maxframesize;
int chunk_size;
pipereader<u8> in;
pipewriter<u8> out;
pipewriter<int> *lock_out;
pipewriter<int> *framecount_out, *fcserrcount_out;
pipewriter<int> *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

View File

@ -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<u8> &_in, // Packed scrambled bits
pipebuf<u8> &_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<pend; ++pin,++pout) {
u8 byte_in=*pin, byte_out=0;
for ( int b=8; b--; byte_in<<=1 ) {
// Levels before clock transition
int bit_in = (byte_in&128) ? 1 : 0;
int reset_counter = (shiftreg ^ (shiftreg>>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<u8> in;
pipewriter<u8> out;
u32 shiftreg; // 20 bits
u8 counter; // 5 bits
}; // etr192_descrambler
} // namespace
#endif // LEANSDR_IESS_H

View File

@ -0,0 +1,98 @@
#ifndef LEANSDR_MATH_H
#define LEANSDR_MATH_H
#include <math.h>
#include <stdint.h>
namespace leansdr {
template<typename T>
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<T> &x) { re+=x.re; im+=x.im; }
};
template<typename T>
complex<T> operator +(const complex<T> &a, const complex<T> &b) {
return complex<T>(a.re+b.re, a.im+b.im);
}
template<typename T>
complex<T> operator *(const complex<T> &a, const complex<T> &b) {
return complex<T>(a.re*b.re-a.im*b.im, a.re*b.im+a.im*b.re);
}
template<typename T>
complex<T> operator *(const complex<T> &a, const T &k) {
return complex<T>(a.re*k, a.im*k);
}
template<typename T>
complex<T> operator *(const T &k, const complex<T> &a) {
return complex<T>(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<float> 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<float> &expi(uint16_t a) const {
return lut[a];
}
// a must fit in a int32_t, otherwise behaviour is undefined
inline const complex<float> &expi(float a) const {
return expi((uint16_t)(int16_t)(int32_t)a);
}
};
} // namespace
#endif // LEANSDR_MATH_H

View File

@ -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<typename Te, typename Tp, Tp P, int N, Te ALPHA>
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<<N); ++i ) {
lut_exp[i] = alpha_i;
lut_exp[((1<<N)-1)+i] = alpha_i;
lut_log[alpha_i] = i;
alpha_i <<= 1; // Multiply by alpha=[X] i.e. increase degrees
if ( alpha_i & (1<<N) ) alpha_i ^= P; // Modulo P iteratively
}
}
static const Te alpha = ALPHA;
inline Te add(Te x, Te y) { return x ^ y; } // Addition modulo 2
inline Te sub(Te x, Te y) { return x ^ y; } // Subtraction modulo 2
inline Te mul(Te x, Te y) {
if ( !x || !y ) return 0;
return lut_exp[lut_log[x] + lut_log[y]];
}
inline Te div(Te x, Te y) {
//if ( ! y ) fail("div"); // TODO
if ( ! x ) return 0;
return lut_exp[lut_log[x] + ((1<<N)-1) - lut_log[y]];
}
inline Te inv(Te x) {
// if ( ! x ) fail("inv");
return lut_exp[((1<<N)-1) - lut_log[x]];
}
inline Te exp(Te x) { return lut_exp[x]; }
inline Te log(Te x) { return lut_log[x]; }
private:
Te lut_exp[(1<<N)*2]; // Wrap to avoid indexing modulo 2^N-1
Te lut_log[1<<N];
};
// Reed-Solomon for RS(204,188) shortened from RS(255,239).
struct rs_engine {
// EN 300 421, section 4.4.2, Field Generator Polynomial
// p(X) = X^8 + X^4 + X^3 + X^2 + 1
gf2x_p<unsigned char, unsigned short, 0x11d, 8, 2> 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<n; ++i ) acc = gf.add(gf.mul(acc,x), poly[i]);
return acc;
}
// Evaluation with coefficients listed by increasing degree.
u8 eval_poly(const u8 *poly, int deg, u8 x) {
// poly[0]*x^0 + .. + poly[deg]*x^deg with Hörner method.
u8 acc = 0;
for ( ; deg>=0; --deg ) acc = gf.add(gf.mul(acc,x), poly[deg]);
return acc;
}
// Append parity symbols
void encode(u8 msg[204]) {
// TBD Avoid copying
u8 p[204];
memcpy(p, msg, 188);
memset(p+188, 0, 16);
// p = msg*X^16
#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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,282 @@
#ifndef LEANSDR_VITERBI_H
#define LEANSDR_VITERBI_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 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<typename TS, int NSTATES, typename TUS, int NUS, int NCS>
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; s<NSTATES; ++s )
for ( int cs=0; cs<NCS; ++cs )
states[s].branches[cs].pred = NOSTATE;
}
// TBD Polynomial width should be a template parameter ?
void init_convolutional(const uint16_t G[]) {
if ( NCS & (NCS-1) ) {
fprintf(stderr, "NCS must be a power of 2\n");
exit(1);
}
// Derive number of polynomials from NCS.
int nG = log2i(NCS);
for ( TS s=0; s<NSTATES; ++s ) {
for ( TUS us=0; us<NUS; ++us ) {
// Run the convolutional encoder from state s with input us
uint64_t shiftreg = s; // TBD type
// Reverse bits
TUS us_rev = 0;
for ( int b=1; b<NUS; b*=2 ) if ( us & b ) us_rev |= (NUS/2/b);
shiftreg |= us_rev * NSTATES;
uint32_t cs = 0; // TBD type
for ( int g=0; g<nG; ++g )
cs = (cs<<1) | parity(shiftreg&G[g]);
shiftreg /= NUS; // Shift bits for 1 uncoded symbol
// [us] at state [s] emits [cs] and leads to state [shiftreg].
typename state::branch *b = &states[shiftreg].branches[cs];
if ( b->pred != NOSTATE ) {
fprintf(stderr, "Invalid convolutional code\n");
exit(1);
}
b->pred = s;
b->us = us;
}
}
}
void dump() {
for ( int s=0; s<NSTATES; ++s ) {
fprintf(stderr, "State %02x:", s);
for ( int cs=0; cs<NCS; ++cs ) {
typename state::branch *b = &states[s].branches[cs];
if ( b->pred == NOSTATE )
fprintf(stderr, " - ");
else
fprintf(stderr, " %02x+%x", b->pred, b->us);
}
fprintf(stderr, "\n");
}
}
};
// Interface that hides the templated internals.
template<typename TUS,
typename TCS,
typename TBM,
typename TPM>
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<typename TS, int NSTATES,
typename TUS, int NUS,
typename TCS, int NCS,
typename TBM, typename TPM,
typename TP>
struct viterbi_dec : viterbi_dec_interface<TUS,TCS,TBM,TPM> {
trellis<TS, NSTATES, TUS, NUS, NCS> *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<TS, NSTATES, TUS, NUS, NCS> *_trellis) :
trell(_trellis)
{
states = &statebanks[0];
newstates = &statebanks[1];
for ( TS s=0; s<NSTATES; ++s ) (*states)[s].cost = 0;
// Determine max value that can fit in TPM
max_tpm = (TPM)0 - 1;
if ( max_tpm < 0 ) {
// TPM is signed
for ( max_tpm=0; max_tpm*2+1>max_tpm; max_tpm=max_tpm*2+1 ) ;
}
}
// Update with full metric
TUS update(TBM costs[NCS], TPM *quality=NULL) {
TPM best_tpm = max_tpm, best2_tpm = max_tpm;
TS best_state = 0;
// Update all states
for ( int s=0; s<NSTATES; ++s ) {
TPM best_m = max_tpm;
typename trellis<TS,NSTATES,TUS,NUS,NCS>::state::branch *best_b = NULL;
// Select best branch
for ( int cs=0; cs<NCS; ++cs ) {
typename trellis<TS,NSTATES,TUS,NUS,NCS>::state::branch *b =
&trell->states[s].branches[cs];
if ( b->pred == trell->NOSTATE ) continue;
TPM m = (*states)[b->pred].cost + costs[cs];
if ( m <= best_m ) { // <= guarantees one match
best_m = m;
best_b = b;
}
}
(*newstates)[s].path = (*states)[best_b->pred].path;
(*newstates)[s].path.append(best_b->us);
(*newstates)[s].cost = best_m;
// Select best and second-best states
if ( best_m < best_tpm ) {
best_state = s;
best2_tpm = best_tpm;
best_tpm = best_m;
} else if ( best_m < best2_tpm )
best2_tpm = best_m;
}
// Swap banks
{ statebank *tmp=states; states=newstates; newstates=tmp; }
// Prevent overflow of path metrics
for ( TS s=0; s<NSTATES; ++s ) (*states)[s].cost -= best_tpm;
#if 0
// Observe that the min-max range remains bounded
fprintf(stderr,"-%2d = [", best_tpm);
for ( TS s=0; s<NSTATES; ++s ) fprintf(stderr," %d", (*states)[s].cost);
fprintf(stderr," ]\n");
#endif
// Return difference between best and second-best as quality metric.
if ( quality ) *quality = best2_tpm - best_tpm;
// Return uncoded symbol of best path
return (*states)[best_state].path.read();
}
// Update with partial metrics.
// The costs provided must be negative.
// The other symbols will be assigned a cost of 0.
TUS update(int nm, TCS cs[], TBM costs[], TPM *quality=NULL) {
TPM best_tpm = max_tpm, best2_tpm = max_tpm;
TS best_state = 0;
// Update all states
for ( int s=0; s<NSTATES; ++s ) {
// Select best branch among those for with metrics are provided
TPM best_m = max_tpm;
typename trellis<TS,NSTATES,TUS,NUS,NCS>::state::branch *best_b = NULL;
for ( int im=0; im<nm; ++im ) {
typename trellis<TS,NSTATES,TUS,NUS,NCS>::state::branch *b =
&trell->states[s].branches[cs[im]];
if ( b->pred == trell->NOSTATE ) continue;
TPM m = (*states)[b->pred].cost + costs[im];
if ( m <= best_m ) { // <= guarantees one match
best_m = m;
best_b = b;
}
}
if ( nm != NCS ) {
// Also scan the other branches.
// We actually rescan the branches with metrics.
// This works because costs are negative.
for ( int cs=0; cs<NCS; ++cs ) {
typename trellis<TS,NSTATES,TUS,NUS,NCS>::state::branch *b =
&trell->states[s].branches[cs];
if ( b->pred == trell->NOSTATE ) continue;
TPM m = (*states)[b->pred].cost;
if ( m <= best_m ) {
best_m = m;
best_b = b;
}
}
}
(*newstates)[s].path = (*states)[best_b->pred].path;
(*newstates)[s].path.append(best_b->us);
(*newstates)[s].cost = best_m;
// Select best states
if ( best_m < best_tpm ) {
best_state = s;
best2_tpm = best_tpm;
best_tpm = best_m;
} else if ( best_m < best2_tpm )
best2_tpm = best_m;
}
// Swap banks
{ statebank *tmp=states; states=newstates; newstates=tmp; }
// Prevent overflow of path metrics
for ( TS s=0; s<NSTATES; ++s ) (*states)[s].cost -= best_tpm;
#if 0
// Observe that the min-max range remains bounded
fprintf(stderr,"-%2d = [", best_tpm);
for ( TS s=0; s<NSTATES; ++s ) fprintf(stderr," %d", (*states)[s].cost);
fprintf(stderr," ]\n");
#endif
// Return difference between best and second-best as quality metric.
if ( quality ) *quality = best2_tpm - best_tpm;
// Return uncoded symbol of best path
return (*states)[best_state].path.read();
}
// Update with single-symbol metric.
// cost must be negative.
TUS update(TCS cs, TBM cost, TPM *quality=NULL) {
return update(1, &cs, &cost, quality);
}
void dump() {
fprintf(stderr, "[");
for ( TS s=0; s<NSTATES; ++s )
if ( states[s].cost )
fprintf(stderr, " %02x:%d", s, states[s].cost);
fprintf(stderr, "\n");
}
private:
TPM max_tpm;
};
// Paths (sequences of uncoded symbols) represented as bitstreams.
// NBITS is the number of bits per symbol.
// DEPTH is the number of symbols stored in the path.
// T is an unsigned integer type wider than NBITS*DEPTH.
template<typename T, typename TUS, int NBITS, int DEPTH>
struct bitpath {
T val;
bitpath() : val(0) { }
void append(TUS us) { val = (val<<NBITS) | us; }
TUS read() { return (val>>(DEPTH-1)*NBITS) & ((1<<NBITS)-1); }
};
} // namespace
#endif // LEANSDR_VITERBI_H

View File

@ -0,0 +1,76 @@
<h1>DATV demodulator plugin</h1>
<h2>Dependencies</h2>
- ffmpeg
- libavcodec-dev
- libavformat-dev
<h2>Introduction</h2>
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).
<h2>Interface</h2>
![ATV Demodulator plugin GUI](../../../doc/img/ATVDemod_plugin.png)
<h3>1: Image</h3>
This is where the TV image appears.
<h3>2: Modulation</h3>
- 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.
<h3>3: Frames Per Second</h3>
This combo lets you chose between a 25 FPS or 30 FPS standard.
<h3>4: Horizontal sync</h3>
Use this button to toggle horizontal synchronization processing.
<h3>5: Vertical sync</h3>
Use this button to toggle vertical synchronization processing.
<h3>6: Half image</h3>
Use this button to disable (on) or enable interlacing of the two half images (off).
<h3>7: Reset defaults</h3>
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
<h3>8: Synchronization level</h3>
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.
<h3>9: Black level</h3>
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.
<h3>10: Line length</h3>
This is the line length in time units. The value in microseconds appears on the right of the slider. Nominal value: 64 microseconds.
<h3>10: Top length</h3>
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.