├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake ├── FindFFTW.cmake ├── FindFFTWSingle.cmake ├── FindPortAudio.cmake ├── FindRTLSDR.cmake ├── InstallHeadersWithDirectory.cmake ├── LinkResources.cmake ├── create_resources.cmake └── mingw32.cmake ├── cmd ├── CMakeLists.txt ├── aprsapplication.cc ├── aprsapplication.hh ├── main.cc └── shared │ └── index.html ├── dist └── linux │ ├── debian │ ├── changelog │ ├── compat │ ├── control │ ├── libsdr-dev.install │ ├── libsdr1.install │ └── rules │ ├── libsdr.dsc │ └── libsdr.spec ├── doc └── Doxyfile ├── examples ├── CMakeLists.txt ├── sdr_ax25.cc ├── sdr_fm.cc ├── sdr_pocsag.cc ├── sdr_rec.cc ├── sdr_rtty.cc └── sdr_wavplay.cc ├── src ├── CMakeLists.txt ├── aprs.cc ├── aprs.hh ├── autocast.hh ├── ax25.cc ├── ax25.hh ├── baseband.hh ├── baudot.cc ├── baudot.hh ├── bch31_21.cc ├── bch31_21.hh ├── buffer.cc ├── buffer.hh ├── buffernode.hh ├── combine.hh ├── config.hh.in ├── demod.hh ├── exception.cc ├── exception.hh ├── fftplan.hh ├── fftplan_fftw3.hh ├── fftplan_native.hh ├── filternode.hh ├── firfilter.hh ├── freqshift.hh ├── fsk.cc ├── fsk.hh ├── http.cc ├── http.hh ├── interpolate.hh ├── logger.cc ├── logger.hh ├── math.hh ├── node.cc ├── node.hh ├── operators.hh ├── options.cc ├── options.hh ├── pocsag.cc ├── pocsag.hh ├── portaudio.cc ├── portaudio.hh ├── psk31.cc ├── psk31.hh ├── queue.cc ├── queue.hh ├── rtlsource.cc ├── rtlsource.hh ├── sdr.hh ├── sha1.cc ├── sha1.hh ├── siggen.hh ├── streamsource.hh ├── subsample.hh ├── traits.cc ├── traits.hh ├── utils.cc ├── utils.hh ├── wavfile.cc └── wavfile.hh └── test ├── CMakeLists.txt ├── buffertest.cc ├── buffertest.hh ├── coretest.cc ├── coretest.hh ├── coreutilstest.cc ├── coreutilstest.hh ├── cputime.cc ├── cputime.hh ├── main.cc ├── unittest.cc └── unittest.hh /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.8) 2 | project(libsdr) 3 | 4 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) 5 | include(InstallHeadersWithDirectory) 6 | include(LinkResources) 7 | 8 | OPTION(BUILD_EXAMPLES "Build examples" OFF) 9 | OPTION(BUILD_UNIT_TESTS "Build unit tests" OFF) 10 | OPTION(BUILD_COMMANDLINETOOL "Build command line tool" ON) 11 | 12 | SET(libsdr_VERSION_MAJOR "0") 13 | SET(libsdr_VERSION_MINOR "1") 14 | SET(libsdr_VERSION_PATCH "0") 15 | 16 | find_package(FFTW) 17 | find_package(FFTWSingle) 18 | find_package(PortAudio) 19 | find_package(RTLSDR) 20 | 21 | INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/src) 22 | INCLUDE_DIRECTORIES(${PROJECT_BINARY_DIR}/src) 23 | INCLUDE_DIRECTORIES(${PORTAUDIO_INCLUDE_DIRS}) 24 | INCLUDE_DIRECTORIES(${GETOPT_INCLUDE_DIRS}) 25 | 26 | # Set some variables for the configuration file 27 | IF(FFTW_FOUND) 28 | set(SDR_WITH_FFTW ON) 29 | ELSE(FFTW_FOUND) 30 | set(FFTW_LIBRARIES) 31 | set(FFTWSingle_LIBRARIES) 32 | ENDIF(FFTW_FOUND) 33 | 34 | IF(PORTAUDIO_FOUND) 35 | set(SDR_WITH_PORTAUDIO ON) 36 | ELSE(PORTAUDIO_FOUND) 37 | set(PORTAUDIO_LIBRARIES) 38 | ENDIF(PORTAUDIO_FOUND) 39 | 40 | IF(RTLSDR_FOUND) 41 | set(SDR_WITH_RTLSDR ON) 42 | ELSE(TRLSDR_FOUND) 43 | set(RTLSDR_LIBRARIES) 44 | ENDIF(RTLSDR_FOUND) 45 | 46 | 47 | set(LIBS ${FFTW_LIBRARIES} ${FFTWSingle_LIBRARIES} ${PORTAUDIO_LIBRARIES} ${RTLSDR_LIBRARIES} 48 | "pthread") 49 | 50 | # Set compiler flags 51 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fPIC") 52 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -ggdb") 53 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -ggdb") 54 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -ggdb") 55 | 56 | LINK_DIRECTORIES(${PROJECT_BINARY_DIR}/src) 57 | 58 | IF(CMAKE_BUILD_TYPE MATCHES DEBUG) 59 | SET(SDR_DEBUG ON) 60 | ENDIF(CMAKE_BUILD_TYPE MATCHES DEBUG) 61 | 62 | 63 | # Create config.hh 64 | CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/src/config.hh.in 65 | ${CMAKE_CURRENT_BINARY_DIR}/src/config.hh) 66 | 67 | 68 | # 69 | # Get default install directories under Linux 70 | # 71 | IF(UNIX AND NOT APPLE) 72 | INCLUDE(GNUInstallDirs) 73 | ENDIF(UNIX AND NOT APPLE) 74 | IF(UNIX AND APPLE) 75 | SET(CMAKE_INSTALL_LIBDIR "lib") 76 | SET(CMAKE_INSTALL_FULL_LIBDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") 77 | SET(CMAKE_INSTALL_INCLUDEDIR "include") 78 | SET(CMAKE_INSTALL_FULL_INCLUDEDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}") 79 | # Set RPATH under MacOS 80 | SET(CMAKE_SKIP_RPATH FALSE) 81 | SET(CMAKE_SKIP_BUILD_RPATH FALSE) 82 | SET(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}) 83 | SET(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) 84 | SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) 85 | ENDIF(UNIX AND APPLE) 86 | 87 | # Add core library, and unit tests 88 | add_subdirectory(src) 89 | 90 | IF(BUILD_UNIT_TESTS) 91 | add_subdirectory(test) 92 | ENDIF(BUILD_UNIT_TESTS) 93 | 94 | IF(BUILD_EXAMPLES) 95 | add_subdirectory(examples) 96 | ENDIF(BUILD_EXAMPLES) 97 | 98 | IF(BUILD_COMMANDLINETOOL) 99 | add_subdirectory(cmd) 100 | endif(BUILD_COMMANDLINETOOL) 101 | 102 | # Source distribution packages: 103 | set(CPACK_PACKAGE_VERSION_MAJOR ${libsdr_VERSION_MAJOR}) 104 | set(CPACK_PACKAGE_VERSION_MINOR ${libsdr_VERSION_MINOR}) 105 | set(CPACK_PACKAGE_VERSION_PATCH ${libsdr_VERSION_PATCH}) 106 | set(CPACK_SOURCE_GENERATOR "TGZ") 107 | set(CPACK_SOURCE_PACKAGE_FILE_NAME 108 | "${CMAKE_PROJECT_NAME}-${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") 109 | set(CPACK_SOURCE_IGNORE_FILES 110 | "/build/;/doc/;/dist/;/.git/;.dat$;.wav$;~$;.qm$;${CPACK_SOURCE_IGNORE_FILES}") 111 | 112 | include(CPack) 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libsdr - A simple software defined radio (SDR) library 2 | 3 | **First of all:** I assembled this library for my one entertainment and to learn something about 4 | software defined radio. If you are interested into a full-featured, performant SDR framework, 5 | consider using GNU radio (http://gnuradio.org). 6 | 7 | 8 | SRD-RX 9 | 10 | 11 | Although being simple, libsdr is sufficient to write a simple SDR receiver application 12 | (http://github.com/hmatuschek/sdr-rx, above). This RX application supports several input sources 13 | (i.e. sound card, files, RTL2382 dongles etc.) and modes (i.e. AM, FM, SSB, CW, etc.). 14 | 15 | 16 | ## Build 17 | 18 | The only required run-time dependency of `libsdr` is `libpthread`, which is available on all 19 | Unix-like OSs like Linux and MacOS X. It is also available for windows if `mingw` is used 20 | (http://www.mingw.org) of compilation. There are also some optional dependencies, which allow for 21 | the usage of some additional features of the library. 22 | 23 | * `Qt5` (http://qt-project.org) - Enables the `libsdr-gui` library implementing some graphical user 24 | interface elements like a spectrum view. 25 | * `fftw3` (http://www.fftw.org) - Also required by the GUI library and allows for FFT-convolution 26 | filters. 27 | * `PortAudio` (http://www.portaudio.com) - Allows for sound-card input and output. 28 | * `librtlsdr` (http://rtlsdr.org) - Allows to interface RTL2382U based USB dongles. 29 | 30 | For the compilation of the library, `cmake` (http://www.cmake.org) is also required (as well as a 31 | compiler like gcc or clang of cause). 32 | 33 | Compiling the library is the canonical cmake path: 34 | 35 | ``` 36 | mkdir build 37 | cd build 38 | cmake .. -DCMAKE_BUILD_TYPE=RELEASE 39 | make 40 | ``` 41 | 42 | 43 | ## License 44 | 45 | libsdr - A simple software defined radio (SDR) library 46 | Copyright (C) 2014 Hannes Matuschek 47 | 48 | This program is free software; you can redistribute it and/or 49 | modify it under the terms of the GNU General Public License 50 | as published by the Free Software Foundation; either version 2 51 | of the License, or (at your option) any later version. 52 | 53 | This program is distributed in the hope that it will be useful, 54 | but WITHOUT ANY WARRANTY; without even the implied warranty of 55 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 56 | GNU General Public License for more details. 57 | 58 | You should have received a copy of the GNU General Public License 59 | along with this program; if not, write to the Free Software 60 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 61 | -------------------------------------------------------------------------------- /cmake/FindFFTW.cmake: -------------------------------------------------------------------------------- 1 | # - Find FFTW 2 | # Find the native FFTW includes and library 3 | # 4 | # FFTW_INCLUDES - where to find fftw3.h 5 | # FFTW_LIBRARIES - List of libraries when using FFTW. 6 | # FFTW_FOUND - True if FFTW found. 7 | 8 | if (FFTW_INCLUDES) 9 | # Already in cache, be silent 10 | set (FFTW_FIND_QUIETLY TRUE) 11 | endif (FFTW_INCLUDES) 12 | 13 | find_path (FFTW_INCLUDES fftw3.h) 14 | 15 | find_library (FFTW_LIBRARIES NAMES fftw3) 16 | 17 | # handle the QUIETLY and REQUIRED arguments and set FFTW_FOUND to TRUE if 18 | # all listed variables are TRUE 19 | include (FindPackageHandleStandardArgs) 20 | find_package_handle_standard_args (FFTW DEFAULT_MSG FFTW_LIBRARIES FFTW_INCLUDES) 21 | 22 | mark_as_advanced (FFTW_LIBRARIES FFTW_INCLUDES) 23 | -------------------------------------------------------------------------------- /cmake/FindFFTWSingle.cmake: -------------------------------------------------------------------------------- 1 | # - Find FFTW 2 | # Find the native FFTW includes and library 3 | # 4 | # FFTWSingle_INCLUDES - where to find fftw3.h 5 | # FFTWSingle_LIBRARIES - List of libraries when using FFTW. 6 | # FFTWSingle_FOUND - True if FFTW found. 7 | 8 | if (FFTWSingle_INCLUDES) 9 | # Already in cache, be silent 10 | set (FFTWSingle_FIND_QUIETLY TRUE) 11 | endif (FFTWSingle_INCLUDES) 12 | 13 | find_path (FFTWSingle_INCLUDES fftw3.h) 14 | 15 | find_library (FFTWSingle_LIBRARIES NAMES fftw3f) 16 | 17 | # handle the QUIETLY and REQUIRED arguments and set FFTW_FOUND to TRUE if 18 | # all listed variables are TRUE 19 | include (FindPackageHandleStandardArgs) 20 | find_package_handle_standard_args (FFTWSingle DEFAULT_MSG FFTWSingle_LIBRARIES FFTWSingle_INCLUDES) 21 | 22 | mark_as_advanced (FFTWSingle_LIBRARIES FFTWSingle_INCLUDES) 23 | -------------------------------------------------------------------------------- /cmake/FindPortAudio.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find Portaudio 2 | # Once done this will define 3 | # 4 | # PORTAUDIO_FOUND - system has Portaudio 5 | # PORTAUDIO_INCLUDE_DIRS - the Portaudio include directory 6 | # PORTAUDIO_LIBRARIES - Link these to use Portaudio 7 | # PORTAUDIO_DEFINITIONS - Compiler switches required for using Portaudio 8 | # PORTAUDIO_VERSION - Portaudio version 9 | # 10 | # Copyright (c) 2006 Andreas Schneider 11 | # 12 | # Redistribution and use is allowed according to the terms of the New BSD license. 13 | # For details see the accompanying COPYING-CMAKE-SCRIPTS file. 14 | # 15 | 16 | 17 | if(PORTAUDIO_LIBRARIES AND PORTAUDIO_INCLUDE_DIRS) 18 | # in cache already 19 | set(PORTAUDIO_FOUND TRUE) 20 | else(PORTAUDIO_LIBRARIES AND PORTAUDIO_INCLUDE_DIRS) 21 | if (NOT WIN32) 22 | include(FindPkgConfig) 23 | pkg_check_modules(PORTAUDIO2 portaudio-2.0) 24 | endif (NOT WIN32) 25 | 26 | if (PORTAUDIO2_FOUND) 27 | set(PORTAUDIO_INCLUDE_DIRS 28 | ${PORTAUDIO2_INCLUDE_DIRS} 29 | ) 30 | if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") 31 | set(PORTAUDIO_LIBRARIES "${PORTAUDIO2_LIBRARY_DIRS}/lib${PORTAUDIO2_LIBRARIES}.dylib") 32 | else (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") 33 | set(PORTAUDIO_LIBRARIES 34 | ${PORTAUDIO2_LIBRARIES} 35 | ) 36 | endif (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") 37 | set(PORTAUDIO_VERSION 38 | 19 39 | ) 40 | set(PORTAUDIO_FOUND TRUE) 41 | else (PORTAUDIO2_FOUND) 42 | find_path(PORTAUDIO_INCLUDE_DIR 43 | NAMES 44 | portaudio.h 45 | PATHS 46 | /usr/include 47 | /usr/local/include 48 | /opt/local/include 49 | /sw/include 50 | C:/MinGW/include 51 | ) 52 | 53 | find_library(PORTAUDIO_LIBRARY 54 | NAMES 55 | portaudio 56 | PATHS 57 | /usr/lib 58 | /usr/local/lib 59 | /opt/local/lib 60 | /sw/lib 61 | C:/MinGW/lib 62 | ) 63 | 64 | find_path(PORTAUDIO_LIBRARY_DIR 65 | NAMES 66 | portaudio 67 | PATHS 68 | /usr/lib 69 | /usr/local/lib 70 | /opt/local/lib 71 | /sw/lib 72 | C:/MinGW/lib 73 | ) 74 | 75 | set(PORTAUDIO_INCLUDE_DIRS 76 | ${PORTAUDIO_INCLUDE_DIR} 77 | ) 78 | set(PORTAUDIO_LIBRARIES 79 | ${PORTAUDIO_LIBRARY} 80 | ) 81 | 82 | set(PORTAUDIO_LIBRARY_DIRS 83 | ${PORTAUDIO_LIBRARY_DIR} 84 | ) 85 | 86 | set(PORTAUDIO_VERSION 87 | 18 88 | ) 89 | 90 | if (PORTAUDIO_INCLUDE_DIRS AND PORTAUDIO_LIBRARIES) 91 | set(PORTAUDIO_FOUND TRUE) 92 | endif (PORTAUDIO_INCLUDE_DIRS AND PORTAUDIO_LIBRARIES) 93 | 94 | if (PORTAUDIO_FOUND) 95 | if (NOT Portaudio_FIND_QUIETLY) 96 | message(STATUS "Found Portaudio: ${PORTAUDIO_LIBRARIES}") 97 | endif (NOT Portaudio_FIND_QUIETLY) 98 | else (PORTAUDIO_FOUND) 99 | if (Portaudio_FIND_REQUIRED) 100 | message(FATAL_ERROR "Could not find Portaudio") 101 | endif (Portaudio_FIND_REQUIRED) 102 | endif (PORTAUDIO_FOUND) 103 | endif (PORTAUDIO2_FOUND) 104 | 105 | include(FindPackageHandleStandardArgs) 106 | find_package_handle_standard_args(PORTAUDIO DEFAULT_MSG PORTAUDIO_INCLUDE_DIRS PORTAUDIO_LIBRARIES) 107 | 108 | # show the PORTAUDIO_INCLUDE_DIRS and PORTAUDIO_LIBRARIES variables only in the advanced view 109 | mark_as_advanced(PORTAUDIO_INCLUDE_DIRS PORTAUDIO_LIBRARIES) 110 | 111 | endif (PORTAUDIO_LIBRARIES AND PORTAUDIO_INCLUDE_DIRS) 112 | 113 | -------------------------------------------------------------------------------- /cmake/FindRTLSDR.cmake: -------------------------------------------------------------------------------- 1 | # - Find RTL-SDR 2 | # Find the native RTL-SDR includes and library 3 | # 4 | # RTLSDR_INCLUDES - where to find rtl-sdr.h 5 | # RTLSDR_LIBRARIES - List of libraries when using RTL-SDR. 6 | # RTLSDR_FOUND - True if RTL-SDR found. 7 | 8 | if (RTLSDR_INCLUDES) 9 | # Already in cache, be silent 10 | set (RTLSDR_FIND_QUIETLY TRUE) 11 | endif (RTLSDR_INCLUDES) 12 | 13 | find_path (RTLSDR_INCLUDES rtl-sdr.h) 14 | 15 | find_library (RTLSDR_LIBRARIES NAMES rtlsdr) 16 | 17 | # handle the QUIETLY and REQUIRED arguments and set FFTW_FOUND to TRUE if 18 | # all listed variables are TRUE 19 | include (FindPackageHandleStandardArgs) 20 | find_package_handle_standard_args (RTLSDR DEFAULT_MSG RTLSDR_LIBRARIES RTLSDR_INCLUDES) 21 | 22 | mark_as_advanced (RTLSDR_LIBRARIES RTLSDR_INCLUDES) 23 | -------------------------------------------------------------------------------- /cmake/InstallHeadersWithDirectory.cmake: -------------------------------------------------------------------------------- 1 | MACRO(INSTALL_HEADERS_WITH_DIRECTORY HEADER_LIST DESTINATION_DIR) 2 | FOREACH(HEADER ${HEADER_LIST}) 3 | STRING(REGEX MATCH "(.*)[/\\]" DIR ${HEADER}) 4 | INSTALL(FILES ${HEADER} DESTINATION ${DESTINATION_DIR}/${DIR}) 5 | ENDFOREACH(HEADER) 6 | ENDMACRO(INSTALL_HEADERS_WITH_DIRECTORY) 7 | 8 | MACRO(COPY_HEADERS_WITH_DIRECTORY HEADER_LIST DESTINATION_DIR) 9 | FOREACH(HEADER ${HEADER_LIST}) 10 | STRING(REGEX MATCH "(.*)[/\\]" DIR ${HEADER}) 11 | FILE(COPY ${HEADER} DESTINATION ${DESTINATION_DIR}/${DIR}) 12 | ENDFOREACH(HEADER) 13 | ENDMACRO(COPY_HEADERS_WITH_DIRECTORY) 14 | -------------------------------------------------------------------------------- /cmake/LinkResources.cmake: -------------------------------------------------------------------------------- 1 | macro(link_resources HEADER_NAME) 2 | #create_resources("${CMAKE_CURRENT_BINARY_DIR}/${HEADER_NAME}.hh" ${ARGN}) 3 | add_custom_target(${HEADER_NAME} ALL 4 | COMMAND ${CMAKE_COMMAND} -DOUTPUT="${CMAKE_CURRENT_BINARY_DIR}/${HEADER_NAME}.hh" -DRESOURCE_PATH="${CMAKE_CURRENT_SOURCE_DIR}" -DFILES="${ARGN}" -P "${PROJECT_SOURCE_DIR}/cmake/create_resources.cmake" 5 | DEPENDS ${ARGN} SOURCES ${ARGN}) 6 | endmacro(link_resources) 7 | 8 | -------------------------------------------------------------------------------- /cmake/create_resources.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Implements the packing of resource files into a header file 3 | # 4 | get_filename_component(OUTPUT_FILE ${OUTPUT} NAME) 5 | message(STATUS "Generate resource file '${OUTPUT_FILE}' from: ${FILES}") 6 | # Create empty file 7 | file(WRITE ${OUTPUT} "") 8 | # For each resource file 9 | foreach(file ${FILES}) 10 | # Normalize filename 11 | string(REGEX MATCH "([^/]+)$" filename ${file}) 12 | string(REGEX REPLACE "\\.| " "_" filename ${filename}) 13 | # Read and convert file content 14 | file(READ "${RESOURCE_PATH}/${file}" filedata HEX) 15 | string(REGEX REPLACE "([0-9a-fA-F][0-9a-fA-F])" "0x\\1," filedata ${filedata}) 16 | # Update output file 17 | file(APPEND ${OUTPUT} 18 | "extern \"C\" {\n" 19 | " static char ${filename}[] = {${filedata}};\n" 20 | " static unsigned ${filename}_size = sizeof(${filename});\n" 21 | "}\n") 22 | endforeach() 23 | -------------------------------------------------------------------------------- /cmake/mingw32.cmake: -------------------------------------------------------------------------------- 1 | # the name of the target operating system 2 | SET(CMAKE_SYSTEM_NAME Windows) 3 | 4 | # which compilers to use for C and C++ 5 | SET(CMAKE_C_COMPILER i586-mingw32msvc-gcc) 6 | SET(CMAKE_CXX_COMPILER i586-mingw32msvc-g++) 7 | SET(CMAKE_RC_COMPILER i586-mingw32msvc-windres) 8 | 9 | # here is the target environment located 10 | SET(CMAKE_FIND_ROOT_PATH /usr/i586-mingw32msvc /home/hannes/mingw-install ) 11 | 12 | # adjust the default behaviour of the FIND_XXX() commands: 13 | # search headers and libraries in the target environment, search 14 | # programs in the host environment 15 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 16 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 17 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 18 | -------------------------------------------------------------------------------- /cmd/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | # Creates a header file including the shared resources 3 | INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) 4 | link_resources(sdr_cmd_resources shared/index.html) 5 | 6 | add_executable(sdr_cmd main.cc aprsapplication.cc) 7 | add_dependencies(sdr_cmd sdr_cmd_resources) 8 | target_link_libraries(sdr_cmd ${LIBS} libsdr ) 9 | -------------------------------------------------------------------------------- /cmd/aprsapplication.cc: -------------------------------------------------------------------------------- 1 | #include "aprsapplication.hh" 2 | 3 | // This file will be generated at build time 4 | // and contains the static content served. 5 | #include "sdr_cmd_resources.hh" 6 | 7 | 8 | using namespace sdr; 9 | 10 | APRSApplication::APRSApplication(http::Server &server) 11 | : APRS(), _server(server), _messages() 12 | { 13 | // Register callbacks 14 | server.addStatic("/", std::string(index_html, index_html_size), "text/html"); 15 | server.addJSON("/spots", this, &APRSApplication::spots); 16 | server.addHandler("/update", this, &APRSApplication::update); 17 | } 18 | 19 | APRSApplication::~APRSApplication() { 20 | // pass... 21 | } 22 | 23 | 24 | bool 25 | APRSApplication::spots(const http::JSON &request, http::JSON &response) { 26 | std::list msg_list; 27 | for (std::list::iterator msg = _messages.begin(); msg != _messages.end(); msg++) { 28 | std::map message; 29 | message["call"] = http::JSON(msg->from().call()); 30 | if (msg->hasLocation()) { 31 | message["lat"] = http::JSON(msg->latitude()); 32 | message["lon"] = http::JSON(msg->longitude()); 33 | } 34 | time_t time = msg->time(); 35 | message["time"] = http::JSON(ctime(&time)); 36 | msg_list.push_back(message); 37 | } 38 | response = http::JSON(msg_list); 39 | return true; 40 | } 41 | 42 | void 43 | APRSApplication::handleAPRSMessage(const Message &message) { 44 | _messages.push_back(message); 45 | // Serialize JSON message 46 | std::string json_text; 47 | if (_clients.size()) { 48 | std::map msg; 49 | msg["call"] = http::JSON(message.from().call()); 50 | if (message.hasLocation()) { 51 | msg["lat"] = http::JSON(message.latitude()); 52 | msg["lon"] = http::JSON(message.longitude()); 53 | } 54 | time_t time = message.time(); 55 | msg["time"] = http::JSON(ctime(&time)); 56 | http::JSON(msg).serialize(json_text); 57 | } 58 | // a list collecting the closed 59 | // signal all clients connected 60 | std::list::iterator client = _clients.begin(); 61 | while (client != _clients.end()) { 62 | if (client->isClosed()) { 63 | // remove client from list 64 | client = _clients.erase(client); 65 | } else { 66 | // send event 67 | client->send("data: "); 68 | client->send(json_text); 69 | client->send("\n\n"); 70 | } 71 | } 72 | } 73 | 74 | void 75 | APRSApplication::update(const http::Request &request, http::Response &response) { 76 | // This call back implements a server side event stream, means the response will 77 | // be blocked until the connection is closed 78 | response.setHeader("Content-Type", "text/event-stream"); 79 | response.setHeader("Cache-Control", "no-cache"); 80 | response.setStatus(http::Response::STATUS_OK); 81 | response.sendHeaders(); 82 | // Signal connection thread to exit without closing the connection 83 | response.connection().setProtocolUpgrade(); 84 | // Store connection 85 | _clients.push_back(response.connection()); 86 | } 87 | 88 | -------------------------------------------------------------------------------- /cmd/aprsapplication.hh: -------------------------------------------------------------------------------- 1 | #ifndef __SDR_APRS_APRSAPPLICATION_HH__ 2 | #define __SDR_APRS_APRSAPPLICATION_HH__ 3 | 4 | #include "http.hh" 5 | #include "aprs.hh" 6 | 7 | 8 | namespace sdr { 9 | 10 | class APRSApplication: public APRS 11 | { 12 | public: 13 | APRSApplication(http::Server &server); 14 | ~APRSApplication(); 15 | 16 | bool spots(const http::JSON &request, http::JSON &response); 17 | void update(const http::Request &request, http::Response &response); 18 | 19 | void handleAPRSMessage(const Message &message); 20 | 21 | protected: 22 | http::Server &_server; 23 | std::list _messages; 24 | std::list _clients; 25 | }; 26 | 27 | } 28 | 29 | #endif // APRSAPPLICATION_HH 30 | -------------------------------------------------------------------------------- /cmd/main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "queue.hh" 4 | #include "logger.hh" 5 | 6 | #include 7 | #include 8 | 9 | #include "aprsapplication.hh" 10 | 11 | using namespace sdr; 12 | 13 | 14 | static http::Server *server = 0; 15 | 16 | static void __sigint_handler(int signo) { 17 | if (server) { server->stop(true); } 18 | } 19 | 20 | 21 | int main(int argc, char *argv[]) { 22 | server = new http::Server(8080); 23 | APRSApplication app(*server); 24 | 25 | // Install log handler 26 | sdr::Logger::get().addHandler( 27 | new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG)); 28 | 29 | // Register signal handler: 30 | signal(SIGINT, __sigint_handler); 31 | 32 | // start server 33 | server->start(true); 34 | 35 | return 0; 36 | } 37 | -------------------------------------------------------------------------------- /cmd/shared/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | sdr-aprs — An APRS receiver using libsdr. 4 | 5 | 6 | 13 | 14 | 15 | 67 | 68 | 69 | 70 |
71 |

Can not load Google Maps view. Check your internet conenction!

72 |
73 | 74 | 75 | -------------------------------------------------------------------------------- /dist/linux/debian/changelog: -------------------------------------------------------------------------------- 1 | libsdr (0.1.0-1) unstable; urgency=low 2 | 3 | * Initial official release. 4 | 5 | -- Hannes Matuschek Sun, 07 December 2014 00:00:00 +0000 6 | -------------------------------------------------------------------------------- /dist/linux/debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /dist/linux/debian/control: -------------------------------------------------------------------------------- 1 | Source: libsdr 2 | Priority: extra 3 | Maintainer: Hannes Matuschek 4 | Build-Depends: cdbs (>= 0.4.51), dh-exec, debhelper (>= 9.0.0), cmake, portaudio19-dev, librtlsdr-dev, fftw3-dev 5 | Standards-Version: 3.9.5 6 | Section: hamradio 7 | Homepage: http://github.com/hmatuschek/libsdr 8 | 9 | Package: libsdr1 10 | Section: hamradio 11 | Architecture: any 12 | Multi-Arch: same 13 | Pre-Depends: multiarch-support, ${misc:Pre-Depends} 14 | Depends: ${shlibs:Depends}, ${misc:Depends}, libportaudio2 15 | Description: libsdr 16 | A C++ library for software defined radio. 17 | 18 | Package: libsdr-dev 19 | Section: hamradio 20 | Architecture: any 21 | Multi-Arch: same 22 | Pre-Depends: multiarch-support, ${misc:Pre-Depends} 23 | Depends: libsdr1 (= ${binary:Version}), ${misc:Depends}, portaudio19-dev, librtlsdr-dev, fftw3-dev, libsdr1 24 | Description: libsdr 25 | A C++ library for software defined radio. Development files. 26 | -------------------------------------------------------------------------------- /dist/linux/debian/libsdr-dev.install: -------------------------------------------------------------------------------- 1 | #! /usr/bin/dh-exec 2 | usr/lib/*/libsdr.so 3 | usr/include/* 4 | -------------------------------------------------------------------------------- /dist/linux/debian/libsdr1.install: -------------------------------------------------------------------------------- 1 | #! /usr/bin/dh-exec 2 | usr/lib/*/libsdr.so.* 3 | -------------------------------------------------------------------------------- /dist/linux/debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH) 4 | # Disable version check, will be updated by package manager. 5 | DEB_CMAKE_EXTRA_FLAGS = -DCMAKE_LIBRARY_ARCHITECTURE=${DEB_HOST_MULTIARCH} \ 6 | -DCMAKE_BUILD_TYPE=RELEASE 7 | 8 | include /usr/share/cdbs/1/rules/debhelper.mk 9 | include /usr/share/cdbs/1/class/cmake.mk 10 | -------------------------------------------------------------------------------- /dist/linux/libsdr.dsc: -------------------------------------------------------------------------------- 1 | Format: 3.0 (quilt) 2 | Source: libsdr 3 | Binary: libsdr, libsdr-dev 4 | Architecture: any 5 | Version: 0.1.0-1 6 | Maintainer: Hannes Matuschek 7 | Homepage: http://github.com/hmatuschek/libsdr 8 | Standards-Version: 3.9.2 9 | Build-Depends: cdbs (>= 0.4.51), dh-exec, debhelper (>= 8.0.0), cmake, qtbase5-dev (>= 5.0), portaudio19-dev, librtlsdr-dev, fftw3-dev 10 | Package-List: 11 | libsdr deb hamradio extra 12 | libsdr-dev deb hamradio extra 13 | Checksums-Sha1: 14 | 090c653318e0b28f16186a77e515b3ee6079a7c9 84903 libsdr_0.1.0.orig.tar.gz 15 | 2693f22ff59c8b19a7fd4bdb7c2470652f587929 1986 libsdr_0.1.0-1.debian.tar.gz 16 | Checksums-Sha256: 17 | 1bc05e8862c0307bdde2df66cd48741340b5a6667aecee41854cf96ae0c81aa5 84903 libsdr_0.1.0.orig.tar.gz 18 | 0914fb57b80c1ad775fad3119f10d0b9feb61e4bddfee881ed21abeb2836eb63 1986 libsdr_0.1.0-1.debian.tar.gz 19 | Files: 20 | e2b0e1f226194f64867618b56e32c5fd 84903 libsdr_0.1.0.orig.tar.gz 21 | c6c66c0a6a3397ca696d1c69e9155cdc 1986 libsdr_0.1.0-1.debian.tar.gz 22 | -------------------------------------------------------------------------------- /dist/linux/libsdr.spec: -------------------------------------------------------------------------------- 1 | Summary: A C++ library for software defined radios 2 | 3 | %define version 0.1.0 4 | 5 | License: GPL-2.0+ 6 | Name: libsdr 7 | Group: Development/Libraries/C and C++ 8 | Prefix: /usr 9 | Release: 1 10 | Source: libsdr-%{version}.tar.gz 11 | URL: https://github.com/hmatuschek/libsdr 12 | Version: %{version} 13 | Buildroot: /tmp/libsdrrpm 14 | BuildRequires: gcc-c++, cmake, portaudio-devel, fftw3-devel, rtl-sdr-devel 15 | Requires: portaudio, fftw3, rtl-sdr 16 | %if 0%{?suse_version} 17 | BuildRequires: libqt5-qtbase-devel 18 | Requires: libqt5-qtbase 19 | %endif 20 | %if 0%{?fedora} 21 | BuildRequires: qt5-qtbase-devel 22 | Requires: qt5-qtbase 23 | %endif 24 | 25 | %description 26 | A simple C++ library for software defined radios. 27 | 28 | 29 | %package -n libsdr-devel 30 | Summary: A C++ library for software defined radios 31 | Group: Development/Libraries/C and C++ 32 | BuildRequires: gcc-c++, cmake, portaudio-devel, fftw3-devel, rtl-sdr-devel 33 | Requires: portaudio, fftw3, rtl-sdr 34 | %if 0%{?suse_version} 35 | BuildRequires: libqt5-qtbase-devel 36 | Requires: libqt5-qtbase 37 | %endif 38 | %if 0%{?fedora} 39 | BuildRequires: qt5-qtbase-devel 40 | Requires: qt5-qtbase 41 | %endif 42 | 43 | %description -n libsdr-devel 44 | Provides the runtime library header files for libsdr. 45 | 46 | %prep 47 | %setup -q 48 | 49 | 50 | %build 51 | cmake -DCMAKE_BUILD_TYPE=RELEASE \ 52 | -DCMAKE_INSTALL_PREFIX=$RPM_BUILD_ROOT/usr 53 | make 54 | 55 | 56 | %install 57 | make install 58 | 59 | %clean 60 | rm -rf $RPM_BUILD_ROOT 61 | 62 | %post 63 | /sbin/ldconfig 64 | 65 | %postun 66 | /sbin/ldconfig 67 | 68 | 69 | %files 70 | %{_libdir}/libsdr.so.* 71 | %{_libdir}/libsdr-gui.so.* 72 | 73 | %files -n libsdr-devel 74 | %defattr(-, root, root, -) 75 | %{_libdir}/libsdr.so 76 | %{_libdir}/libsdr-gui.so 77 | %{_includedir}/libsdr/ 78 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | IF(SDR_WITH_PORTAUDIO) 2 | add_executable(sdr_wavplay sdr_wavplay.cc) 3 | target_link_libraries(sdr_wavplay ${LIBS} libsdr) 4 | 5 | add_executable(sdr_fm sdr_fm.cc) 6 | target_link_libraries(sdr_fm ${LIBS} libsdr) 7 | 8 | add_executable(sdr_rec sdr_rec.cc) 9 | target_link_libraries(sdr_rec ${LIBS} libsdr) 10 | 11 | add_executable(sdr_rtty sdr_rtty.cc) 12 | target_link_libraries(sdr_rtty ${LIBS} libsdr) 13 | 14 | add_executable(sdr_pocsag sdr_pocsag.cc) 15 | target_link_libraries(sdr_pocsag ${LIBS} libsdr) 16 | 17 | add_executable(sdr_ax25 sdr_ax25.cc) 18 | target_link_libraries(sdr_ax25 ${LIBS} libsdr) 19 | 20 | ENDIF(SDR_WITH_PORTAUDIO) 21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/sdr_ax25.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * sdr_ax25 -- A AX.25 and APRS receiver using libsdr. 3 | * 4 | * (c) 2015 Hannes Matuschek 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 19 | */ 20 | 21 | #include "options.hh" 22 | #include "autocast.hh" 23 | #include "rtlsource.hh" 24 | #include "baseband.hh" 25 | #include "demod.hh" 26 | #include "portaudio.hh" 27 | #include "wavfile.hh" 28 | #include "fsk.hh" 29 | #include "utils.hh" 30 | #include "aprs.hh" 31 | 32 | #include 33 | #include 34 | #include 35 | 36 | 37 | using namespace sdr; 38 | 39 | // On SIGINT -> stop queue properly 40 | static void __sigint_handler(int signo) { 41 | Queue::get().stop(); 42 | } 43 | 44 | // Command line options 45 | static Options::Definition options[] = { 46 | {"frequency", 'F', Options::FLOAT, 47 | "Selects a RTL2832 as the source and specifies the frequency in Hz."}, 48 | {"correction", 0, Options::FLOAT, 49 | "Specifies the frequency correction for the RTL2832 device in parts-per-million (ppm)."}, 50 | {"audio", 'a', Options::FLAG, "Selects the system audio as the source."}, 51 | {"file", 'f', Options::ANY, "Selects a WAV file as the source."}, 52 | {"monitor", 'M', Options::FLAG, "Enable sound monitor."}, 53 | {"help", 0, Options::FLAG, "Prints this help message."}, 54 | {0,0,Options::FLAG,0} 55 | }; 56 | 57 | 58 | void print_help() { 59 | std::cerr << "USAGE: sdr_ax25 SOURCE [OPTIONS]" << std::endl << std::endl; 60 | Options::print_help(std::cerr, options); 61 | } 62 | 63 | int main(int argc, char *argv[]) 64 | { 65 | // Install log handler 66 | sdr::Logger::get().addHandler( 67 | new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG)); 68 | 69 | // Register signal handler: 70 | signal(SIGINT, __sigint_handler); 71 | 72 | // Parse command line options. 73 | Options opts; 74 | if (! Options::parse(options, argc, argv, opts)) { 75 | print_help(); return -1; 76 | } 77 | 78 | // If help flag is present -> print and done. 79 | if (opts.has("help")) { print_help(); return 0; } 80 | 81 | // If no source has been selected 82 | if (! (opts.has("frequency")|opts.has("audio")|opts.has("file"))) { 83 | print_help(); return -1; 84 | } 85 | 86 | // Init audio system 87 | PortAudio::init(); 88 | 89 | // Get the global queue 90 | Queue &queue = Queue::get(); 91 | 92 | // pointer to the selected source 93 | Source *src = 0; 94 | 95 | // nodes for WAV file input 96 | WavSource *wav_src=0; 97 | AutoCast *wav_cast=0; 98 | 99 | // nodes for PortAudio input 100 | PortSource *audio_src=0; 101 | 102 | // nodes for RTL2832 input 103 | RTLSource *rtl_source=0; 104 | AutoCast< std::complex > *rtl_cast=0; 105 | IQBaseBand *rtl_baseband=0; 106 | FMDemod *rtl_demod=0; 107 | FMDeemph *rtl_deemph=0; 108 | 109 | if (opts.has("frequency")) { 110 | // Assemble processing chain for the RTL2832 intput 111 | rtl_source = new RTLSource(opts.get("frequency").toFloat()); 112 | if (opts.has("correction")) { 113 | // Apply specified frequency correction. 114 | rtl_source->setFreqCorrection(opts.get("correction").toFloat()); 115 | } 116 | rtl_cast = new AutoCast< std::complex >(); 117 | rtl_baseband = new IQBaseBand(0, 15.0e3, 21, 0, 22050.0); 118 | rtl_demod = new FMDemod(); 119 | rtl_deemph = new FMDeemph(); 120 | // Connect nodes 121 | rtl_source->connect(rtl_cast); 122 | rtl_cast->connect(rtl_baseband, true); 123 | rtl_baseband->connect(rtl_demod); 124 | rtl_demod->connect(rtl_deemph); 125 | // FM deemph. is source for decoder 126 | src = rtl_deemph; 127 | // On queue start, start RTL source 128 | Queue::get().addStart(rtl_source, &RTLSource::start); 129 | // On queue stop, stop RTL source 130 | Queue::get().addStop(rtl_source, &RTLSource::stop); 131 | } else if (opts.has("audio")) { 132 | // Configure audio source 133 | audio_src = new PortSource(22010., 1024); 134 | src = audio_src; 135 | // On queue idle, read next chunk from audio source 136 | Queue::get().addIdle(audio_src, &PortSource::next); 137 | } else if (opts.has("file")) { 138 | // Assemble processing chain for WAV file input 139 | wav_src = new WavSource(opts.get("file").toString()); 140 | wav_cast = new AutoCast(); 141 | wav_src->connect(wav_cast); 142 | src = wav_cast; 143 | // On queue idle, read next chunk from file 144 | Queue::get().addIdle(wav_src, &WavSource::next); 145 | // On end of file, stop queue 146 | wav_src->addEOS(&(Queue::get()), &Queue::stop); 147 | } 148 | 149 | /* Common demodulation nodes. */ 150 | 151 | // (A)FSK detector 152 | FSKDetector detector(1200, 1200, 2200); 153 | // Bit decoder 154 | BitStream bits(1200, BitStream::TRANSITION); 155 | // APRS decoder 156 | APRS aprs; 157 | // Audio sink for monitor 158 | PortSink sink; 159 | 160 | // connect source ASK detector 161 | src->connect(&detector); 162 | // detector to bit decoder 163 | detector.connect(&bits); 164 | // and bit decoder to APRS decoder and print 165 | bits.connect(&aprs); 166 | 167 | // If monitor is enabled -> connect to sink 168 | if (opts.has("monitor")) { 169 | src->connect(&sink); 170 | } 171 | 172 | // Start queue 173 | queue.start(); 174 | // wait for queue to exit 175 | queue.wait(); 176 | 177 | // Free allocated nodes 178 | if (rtl_source) { delete rtl_source; } 179 | if (rtl_cast) { delete rtl_cast; } 180 | if (rtl_baseband) { delete rtl_baseband; } 181 | if (rtl_demod) { delete rtl_demod; } 182 | if (rtl_deemph) { delete rtl_deemph; } 183 | if (audio_src) { delete audio_src; } 184 | if (wav_src) { delete wav_src; } 185 | if (wav_cast) { delete wav_cast; } 186 | 187 | // terminate port audio system properly 188 | PortAudio::terminate(); 189 | 190 | // quit. 191 | return 0; 192 | } 193 | -------------------------------------------------------------------------------- /examples/sdr_fm.cc: -------------------------------------------------------------------------------- 1 | #include "demod.hh" 2 | #include "rtlsource.hh" 3 | #include "baseband.hh" 4 | #include "autocast.hh" 5 | #include "portaudio.hh" 6 | #include "wavfile.hh" 7 | 8 | #include 9 | #include 10 | 11 | using namespace sdr; 12 | 13 | static void __sigint_handler(int signo) { 14 | std::cerr << "Stop Queue..." << std::endl; 15 | // On SIGINT -> stop queue properly 16 | Queue::get().stop(); 17 | Queue::get().wait(); 18 | } 19 | 20 | 21 | int main(int argc, char *argv[]) { 22 | if (2 > argc) { std::cout << "USAGE: sdr_fm FREQUENCY [OUTPUT.wav]" << std::endl; return -1; } 23 | 24 | // get frequency 25 | double freq = atof(argv[1]); 26 | // Get output file (if given) 27 | std::string outFile; 28 | if (3 <= argc) { outFile = argv[2]; } 29 | 30 | sdr::Logger::get().addHandler( 31 | new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG)); 32 | 33 | // Register handler: 34 | signal(SIGINT, __sigint_handler); 35 | 36 | PortAudio::init(); 37 | 38 | RTLSource src(freq-100e3, 1e6); 39 | AutoCast< std::complex > cast; 40 | IQBaseBand baseband(100e3, 12.5e3, 21, 1, 8000.0); 41 | baseband.setCenterFrequency(100e3); 42 | baseband.setFilterFrequency(100e3); 43 | FMDemod demod; 44 | FMDeemph deemph; 45 | PortSink audio; 46 | WavSink *wav_sink = 0; 47 | 48 | // Assemble processing chain: 49 | src.connect(&cast, true); 50 | cast.connect(&baseband); 51 | baseband.connect(&demod, true); 52 | demod.connect(&deemph, true); 53 | deemph.connect(&audio); 54 | 55 | if (outFile.size()) { 56 | wav_sink = new WavSink(outFile); 57 | deemph.connect(wav_sink); 58 | } 59 | 60 | Queue::get().addStart(&src, &RTLSource::start); 61 | Queue::get().addStop(&src, &RTLSource::stop); 62 | 63 | Queue::get().start(); 64 | Queue::get().wait(); 65 | 66 | if (wav_sink) { 67 | delete wav_sink; 68 | } 69 | 70 | PortAudio::terminate(); 71 | 72 | return 0; 73 | } 74 | -------------------------------------------------------------------------------- /examples/sdr_pocsag.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * sdr_pocsag -- A POCSAG receiver using libsdr. 3 | * 4 | * (c) 2015 Hannes Matuschek 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 19 | */ 20 | 21 | #include "autocast.hh" 22 | #include "portaudio.hh" 23 | #include "wavfile.hh" 24 | #include "fsk.hh" 25 | #include "utils.hh" 26 | #include "pocsag.hh" 27 | #include "options.hh" 28 | #include "rtlsource.hh" 29 | #include "demod.hh" 30 | #include "baseband.hh" 31 | 32 | #include 33 | #include 34 | #include 35 | 36 | using namespace sdr; 37 | 38 | // On SIGINT -> stop queue properly 39 | static void __sigint_handler(int signo) { 40 | Queue::get().stop(); 41 | } 42 | 43 | // Command line options 44 | static Options::Definition options[] = { 45 | {"frequency", 'F', Options::FLOAT, 46 | "Selects a RTL2832 as the source and specifies the frequency in Hz."}, 47 | {"correction", 0, Options::FLOAT, 48 | "Specifies the frequency correction for the RTL2832 device in parts-per-million (ppm)."}, 49 | {"audio", 'a', Options::FLAG, "Selects the system audio as the source."}, 50 | {"file", 'f', Options::ANY, "Selects a WAV file as the source."}, 51 | {"monitor", 'M', Options::FLAG, "Enable sound monitor."}, 52 | {"invert", 0, Options::FLAG, "Inverts mark/space logic."}, 53 | {"help", 0, Options::FLAG, "Prints this help message."}, 54 | {0,0,Options::FLAG,0} 55 | }; 56 | 57 | void print_help() { 58 | std::cerr << "USAGE: sdr_pocsag SOURCE [OPTIONS]" << std::endl << std::endl; 59 | Options::print_help(std::cerr, options); 60 | } 61 | 62 | 63 | int main(int argc, char *argv[]) 64 | { 65 | // Install log handler 66 | sdr::Logger::get().addHandler( 67 | new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG)); 68 | 69 | // Register signal handler: 70 | signal(SIGINT, __sigint_handler); 71 | 72 | // Parse command line options. 73 | Options opts; 74 | if (! Options::parse(options, argc, argv, opts)) { 75 | print_help(); return -1; 76 | } 77 | 78 | if (opts.has("help")) { 79 | print_help(); return 0; 80 | } 81 | 82 | // If no source has been selected 83 | if (! (opts.has("frequency")|opts.has("audio")|opts.has("file"))) { 84 | print_help(); return -1; 85 | } 86 | 87 | // Init audio system 88 | PortAudio::init(); 89 | 90 | // Get the global queue 91 | Queue &queue = Queue::get(); 92 | 93 | // pointer to the selected source 94 | Source *src = 0; 95 | 96 | // Nodes for WAV file input 97 | WavSource *wav_src=0; 98 | AutoCast *wav_cast=0; 99 | 100 | // Nodes for PortAudio input 101 | PortSource *audio_src=0; 102 | 103 | // Nodes for RTL2832 input 104 | RTLSource *rtl_source=0; 105 | AutoCast< std::complex > *rtl_cast=0; 106 | IQBaseBand *rtl_baseband=0; 107 | FMDemod *rtl_demod=0; 108 | FMDeemph *rtl_deemph=0; 109 | 110 | if (opts.has("frequency")) { 111 | // Assemble processing chain for the RTL2832 intput 112 | rtl_source = new RTLSource(opts.get("frequency").toFloat()); 113 | if (opts.has("correction")) { 114 | rtl_source->setFreqCorrection(opts.get("correction").toFloat()); 115 | } 116 | rtl_cast = new AutoCast< std::complex >(); 117 | rtl_baseband = new IQBaseBand(0, 12.5e3, 21, 0, 22050.0); 118 | rtl_demod = new FMDemod(); 119 | rtl_deemph = new FMDeemph(); 120 | rtl_source->connect(rtl_cast); 121 | rtl_cast->connect(rtl_baseband, true); 122 | rtl_baseband->connect(rtl_demod); 123 | rtl_demod->connect(rtl_deemph); 124 | // FM deemph. is source for decoder 125 | src = rtl_deemph; 126 | // On queue start, start RTL source 127 | Queue::get().addStart(rtl_source, &RTLSource::start); 128 | // On queue stop, stop RTL source 129 | Queue::get().addStop(rtl_source, &RTLSource::stop); 130 | } else if (opts.has("audio")) { 131 | // Configure audio source 132 | audio_src = new PortSource(22010., 1024); 133 | src = audio_src; 134 | // On queue idle, read next chunk from audio source 135 | Queue::get().addIdle(audio_src, &PortSource::next); 136 | } else if (opts.has("file")) { 137 | // Assemble processing chain for WAV file input 138 | wav_src = new WavSource(opts.get("file").toString()); 139 | wav_cast = new AutoCast(); 140 | wav_src->connect(wav_cast); 141 | src = wav_cast; 142 | // On queue idle, read next chunk from file 143 | Queue::get().addIdle(wav_src, &WavSource::next); 144 | // On end of file, stop queue 145 | wav_src->addEOS(&(Queue::get()), &Queue::stop); 146 | } 147 | 148 | /* Common demodulation nodes. */ 149 | // amplitude detector 150 | ASKDetector detector(opts.has("invert")); 151 | // Bit decoder 152 | BitStream bits(1200, BitStream::NORMAL); 153 | // POCSAG decoder 154 | POCSAGDump pocsag(std::cout); 155 | // Audio sink for monitor 156 | PortSink sink; 157 | 158 | 159 | // connect source ASK detector 160 | src->connect(&detector); 161 | // detector to bit decoder 162 | detector.connect(&bits); 163 | // and bit decoder to POCSAG decoder and print 164 | bits.connect(&pocsag); 165 | 166 | // If monitor is enabled -> connect to sink 167 | if (opts.has("monitor")) { 168 | src->connect(&sink); 169 | } 170 | 171 | // Start queue 172 | queue.start(); 173 | // wait for queue to exit 174 | queue.wait(); 175 | 176 | // Free allocated nodes 177 | if (rtl_source) { delete rtl_source; } 178 | if (rtl_cast) { delete rtl_cast; } 179 | if (rtl_baseband) { delete rtl_baseband; } 180 | if (rtl_demod) { delete rtl_demod; } 181 | if (rtl_deemph) { delete rtl_deemph; } 182 | if (audio_src) { delete audio_src; } 183 | if (wav_src) { delete wav_src; } 184 | if (wav_cast) { delete wav_cast; } 185 | 186 | // terminate port audio system properly 187 | PortAudio::terminate(); 188 | 189 | // quit. 190 | return 0; 191 | } 192 | -------------------------------------------------------------------------------- /examples/sdr_rec.cc: -------------------------------------------------------------------------------- 1 | #include "demod.hh" 2 | #include "rtlsource.hh" 3 | #include "baseband.hh" 4 | #include "autocast.hh" 5 | #include "portaudio.hh" 6 | #include "wavfile.hh" 7 | 8 | #include 9 | #include 10 | 11 | using namespace sdr; 12 | 13 | static void __sigint_handler(int signo) { 14 | std::cerr << "Stop Queue..." << std::endl; 15 | // On SIGINT -> stop queue properly 16 | Queue::get().stop(); 17 | Queue::get().wait(); 18 | } 19 | 20 | 21 | int main(int argc, char *argv[]) { 22 | if (3 > argc) { 23 | std::cout << "USAGE: sdr_rec FREQUENCY MODE [OUTPUT.wav]" << std::endl; return -1; 24 | } 25 | 26 | // get frequency 27 | double freq = atof(argv[1]); 28 | std::string mode = argv[2]; 29 | // Get output file (if given) 30 | std::string outFile; 31 | if (4 <= argc) { outFile = argv[3]; } 32 | 33 | sdr::Logger::get().addHandler( 34 | new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG)); 35 | 36 | // Register handler: 37 | signal(SIGINT, __sigint_handler); 38 | 39 | PortAudio::init(); 40 | 41 | // obtain base-band config 42 | double f_center = 0, f_filter = 0, flt_width = 0; 43 | int sub_sample = 1; double out_f_sample = 12e3; 44 | if (mode == "WFM") { 45 | f_center = 0; f_filter = 0; flt_width = 50e3; 46 | out_f_sample = 48e3; 47 | } else if (mode == "NFM") { 48 | f_center = 0; f_filter = 0; flt_width = 12.5e3; 49 | out_f_sample = 12e3; 50 | } else if (mode == "AM") { 51 | f_center = 0; f_filter = 0; flt_width = 15e3; 52 | out_f_sample = 12e3; 53 | } else if (mode == "USB") { 54 | f_center = 0; f_filter = 1500; flt_width = 3e3; 55 | out_f_sample = 12e3; 56 | } else if (mode == "LSB") { 57 | f_center = 0; f_filter = -1500; flt_width = 3e3; 58 | out_f_sample = 12e3; 59 | } else { 60 | std::cerr << "Unknown mode '" << mode 61 | << "': Possible values are WFM, NFM, AM, USB, LSB." << std::endl; 62 | return -1; 63 | } 64 | 65 | // Create nodes 66 | RTLSource src(freq, 1e6); 67 | AutoCast< std::complex > cast; 68 | IQBaseBand baseband(f_center, f_filter, flt_width, 16, sub_sample, out_f_sample); 69 | FMDemod *fm_demod = 0; 70 | FMDeemph *fm_deemph = 0; 71 | AMDemod *am_demod = 0; 72 | USBDemod *usb_demod = 0; 73 | PortSink audio; 74 | WavSink *wav_sink = 0; 75 | 76 | if (outFile.size()) { 77 | wav_sink = new WavSink(outFile); 78 | } 79 | 80 | // Assemble processing chain: 81 | src.connect(&cast, true); 82 | cast.connect(&baseband); 83 | 84 | if (mode == "WFM") { 85 | fm_demod = new FMDemod(); 86 | fm_deemph= new FMDeemph(); 87 | baseband.connect(fm_demod, true); 88 | fm_demod->connect(fm_deemph, true); 89 | fm_deemph->connect(&audio); 90 | if (wav_sink) { fm_deemph->connect(wav_sink); } 91 | } else if (mode == "NFM") { 92 | fm_demod = new FMDemod(); 93 | fm_deemph= new FMDeemph(); 94 | fm_demod->connect(fm_deemph, true); 95 | baseband.connect(fm_demod, true); 96 | fm_demod->connect(fm_deemph, true); 97 | fm_deemph->connect(&audio); 98 | if (wav_sink) { fm_deemph->connect(wav_sink); } 99 | } else if (mode == "AM") { 100 | am_demod = new AMDemod(); 101 | baseband.connect(am_demod); 102 | am_demod->connect(&audio); 103 | if (wav_sink) { am_demod->connect(wav_sink); } 104 | } else if ((mode == "USB") || (mode == "LSB")){ 105 | usb_demod = new USBDemod(); 106 | baseband.connect(usb_demod); 107 | usb_demod->connect(&audio); 108 | if (wav_sink) { usb_demod->connect(wav_sink); } 109 | } 110 | 111 | Queue::get().addStart(&src, &RTLSource::start); 112 | Queue::get().addStop(&src, &RTLSource::stop); 113 | 114 | std::cerr << "Start recording at " << src.frequency() 115 | << "Hz in mode " << mode << ". Press CTRL-C to stop recoding." << std::endl; 116 | 117 | Queue::get().start(); 118 | Queue::get().wait(); 119 | 120 | if (fm_demod) { delete fm_demod; } 121 | if (fm_deemph) { delete fm_deemph; } 122 | if (usb_demod) { delete usb_demod; } 123 | if (wav_sink) { delete wav_sink; } 124 | 125 | PortAudio::terminate(); 126 | 127 | std::cerr << "Recording stopped." << std::endl; 128 | 129 | return 0; 130 | } 131 | -------------------------------------------------------------------------------- /examples/sdr_rtty.cc: -------------------------------------------------------------------------------- 1 | #include "autocast.hh" 2 | #include "portaudio.hh" 3 | #include "wavfile.hh" 4 | #include "fsk.hh" 5 | #include "baudot.hh" 6 | #include "utils.hh" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace sdr; 13 | 14 | static void __sigint_handler(int signo) { 15 | // On SIGINT -> stop queue properly 16 | Queue::get().stop(); 17 | } 18 | 19 | 20 | int main(int argc, char *argv[]) 21 | { 22 | if (2 != argc) { 23 | std::cerr << "Usage: sdr_rtty FILENAME" << std::endl; 24 | return -1; 25 | } 26 | 27 | sdr::Logger::get().addHandler( 28 | new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG)); 29 | 30 | // Register handler: 31 | signal(SIGINT, __sigint_handler); 32 | 33 | PortAudio::init(); 34 | 35 | Queue &queue = Queue::get(); 36 | 37 | WavSource src(argv[1]); 38 | PortSink sink; 39 | AutoCast cast; 40 | FSKDetector fsk(90.90, 930., 1100.); 41 | BitStream bits(90.90, BitStream::NORMAL); 42 | Baudot decoder; 43 | TextDump dump; 44 | 45 | // Playback 46 | src.connect(&sink); 47 | // Cast to int16 48 | src.connect(&cast); 49 | // FSK demod 50 | cast.connect(&fsk); 51 | fsk.connect(&bits); 52 | // Baudot decoder 53 | bits.connect(&decoder); 54 | // dump to std::cerr 55 | decoder.connect(&dump); 56 | 57 | // on idle -> read next buffer from input file 58 | queue.addIdle(&src, &WavSource::next); 59 | // on end-of-file -> stop queue 60 | src.addEOS(&queue, &Queue::stop); 61 | 62 | // Start queue 63 | queue.start(); 64 | // wait for queue to exit 65 | queue.wait(); 66 | 67 | // terminate port audio system properly 68 | PortAudio::terminate(); 69 | 70 | // quit. 71 | return 0; 72 | } 73 | -------------------------------------------------------------------------------- /examples/sdr_wavplay.cc: -------------------------------------------------------------------------------- 1 | #include "sdr.hh" 2 | #include 3 | 4 | using namespace sdr; 5 | 6 | 7 | int main(int argc, char *argv[]) 8 | { 9 | if (argc < 2) { 10 | std::cerr << "USAGE: sdr_wavplay FILENAME" << std::endl; 11 | return -1; 12 | } 13 | 14 | Queue &queue = Queue::get(); 15 | 16 | PortAudio::init(); 17 | 18 | WavSource src(argv[1]); 19 | if (! src.isOpen() ) { 20 | std::cerr << "Can not open file " << argv[1] << std::endl; 21 | return -1; 22 | } 23 | queue.addIdle(&src, &WavSource::next); 24 | 25 | RealPart to_real; 26 | PortSink sink; 27 | 28 | if (src.isReal()) { 29 | src.connect(&sink, true); 30 | } else { 31 | src.connect(&to_real, true); 32 | to_real.connect(&sink, true); 33 | } 34 | 35 | // run... 36 | queue.start(); 37 | queue.wait(); 38 | 39 | PortAudio::terminate(); 40 | return 0; 41 | } 42 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Sources of libsdr 2 | set(LIBSDR_SOURCES 3 | buffer.cc node.cc queue.cc traits.cc portaudio.cc utils.cc wavfile.cc exception.cc logger.cc 4 | psk31.cc options.cc fsk.cc ax25.cc aprs.cc baudot.cc pocsag.cc bch31_21.cc http.cc sha1.cc) 5 | set(LIBSDR_HEADERS sdr.hh math.hh 6 | buffer.hh node.hh queue.hh buffernode.hh filternode.hh traits.hh autocast.hh 7 | siggen.hh portaudio.hh utils.hh wavfile.hh demod.hh firfilter.hh 8 | fftplan.hh fftplan_native.hh exception.hh baseband.hh freqshift.hh subsample.hh 9 | combine.hh logger.hh psk31.hh interpolate.hh operators.hh options.hh fsk.hh ax25.hh 10 | aprs.hh baudot.hh pocsag.hh bch31_21.hh http.hh sha1.hh) 11 | 12 | if(SDR_WITH_PORTAUDIO) 13 | set(LIBSDR_SOURCES ${LIBSDR_SOURCES} portaudio.cc) 14 | set(LIBSDR_HEADERS ${LIBSDR_HEADERS} portaudio.hh) 15 | endif(SDR_WITH_PORTAUDIO) 16 | 17 | if(SDR_WITH_FFTW) 18 | set(LIBSDR_SOURCES ${LIBSDR_SOURCES}) 19 | set(LIBSDR_HEADERS ${LIBSDR_HEADERS} fftplan_fftw3.hh) 20 | endif(SDR_WITH_FFTW) 21 | 22 | if(SDR_WITH_RTLSDR) 23 | set(LIBSDR_SOURCES ${LIBSDR_SOURCES} rtlsource.cc) 24 | set(LIBSDR_HEADERS ${LIBSDR_HEADERS} rtlsource.hh) 25 | endif(SDR_WITH_RTLSDR) 26 | 27 | add_custom_target(libsdr_hdrs SOURCES ${LIBSDR_HEADERS}) 28 | 29 | add_library(libsdr SHARED ${LIBSDR_SOURCES}) 30 | set_target_properties(libsdr PROPERTIES OUTPUT_NAME sdr) 31 | set_target_properties(libsdr PROPERTIES DEPENDS libsdr_hdrs) 32 | set_target_properties(libsdr PROPERTIES VERSION 33 | "${libsdr_VERSION_MAJOR}.${libsdr_VERSION_MINOR}.${libsdr_VERSION_PATCH}") 34 | set_target_properties(libsdr PROPERTIES SOVERION "${libsdr_VERSION_MAJOR}") 35 | set_target_properties(libsdr PROPERTIES MACOSX_RPATH "${CMAKE_INSTALL_RPATH}") 36 | set_target_properties(libsdr PROPERTIES INSTALL_NAME_DIR ${CMAKE_INSTALL_FULL_LIBDIR}) 37 | target_link_libraries(libsdr ${LIBS}) 38 | 39 | install(TARGETS libsdr DESTINATION ${CMAKE_INSTALL_LIBDIR}) 40 | INSTALL_HEADERS_WITH_DIRECTORY("${LIBSDR_HEADERS}" "${CMAKE_INSTALL_INCLUDEDIR}/libsdr") 41 | install(FILES "${CMAKE_CURRENT_BINARY_DIR}/config.hh" 42 | DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/libsdr") 43 | 44 | -------------------------------------------------------------------------------- /src/aprs.hh: -------------------------------------------------------------------------------- 1 | #ifndef __SDR_APRS_HH__ 2 | #define __SDR_APRS_HH__ 3 | 4 | #include "ax25.hh" 5 | #include 6 | 7 | 8 | namespace sdr { 9 | 10 | class APRS: public AX25 11 | { 12 | public: 13 | class Message: public AX25::Message 14 | { 15 | public: 16 | /** A small selection of possible symbols to display the station. */ 17 | typedef enum { 18 | NONE, POLICE, DIGI, PHONE, AIRCRAFT, HOUSE, MOTORCYCLE, CAR, BBS, BALLOON, BUS, 19 | BOAT, JOGGER, WX 20 | } Symbol; 21 | 22 | public: 23 | Message(); 24 | Message(const AX25::Message &msg); 25 | Message(const Message &msg); 26 | 27 | Message &operator=(const Message &other); 28 | 29 | inline bool hasLocation() const { return _hasLocation; } 30 | inline double latitude() const { return _latitude; } 31 | inline double longitude() const { return _longitude; } 32 | inline Symbol symbol() const { return _symbol; } 33 | 34 | inline const time_t &time() const { return _time; } 35 | 36 | inline bool hasComment() const { return (0 != _comment.size()); } 37 | inline const std::string &comment() const { return _comment; } 38 | 39 | protected: 40 | bool _readLocation(size_t &offset); 41 | bool _readLatitude(size_t &offset); 42 | bool _readLongitude(size_t &offset); 43 | bool _readTime(size_t &offset); 44 | 45 | protected: 46 | bool _hasLocation; 47 | double _latitude; 48 | double _longitude; 49 | Symbol _symbol; 50 | bool _hasTime; 51 | time_t _time; 52 | std::string _comment; 53 | }; 54 | 55 | public: 56 | APRS(); 57 | 58 | void handleAX25Message(const AX25::Message &message); 59 | virtual void handleAPRSMessage(const Message &message); 60 | }; 61 | 62 | std::ostream& operator<<(std::ostream &stream, const APRS::Message &msg); 63 | 64 | } 65 | 66 | #endif // __SDR_APRS_HH__ 67 | -------------------------------------------------------------------------------- /src/ax25.hh: -------------------------------------------------------------------------------- 1 | #ifndef __SDR_AX25_HH__ 2 | #define __SDR_AX25_HH__ 3 | 4 | #include "node.hh" 5 | 6 | namespace sdr { 7 | 8 | /** Decodes AX25 (PacketRadio) messages from a bit stream. 9 | * 10 | * In conjecture with the (A)FSK demodulator, the AX25 can be used to receive packet radio or APRS 11 | * messages. AX25 is usually transmitted as FSK in transition mode, means the bits aren't 12 | * encoded by mark & space tones but rather as a transition from mark to space or in reverse. Hence 13 | * the FSK node needs to be configured in transition mode. 14 | * 15 | * The node does not process the actual AX.25 packages, it only checks the frame check sequence and 16 | * forwards the AX.25 datagram to all connected sinks on success. The receiving node is responsible 17 | * for unpacking and handling the received datagram. 18 | * @ingroup datanodes */ 19 | class AX25: public Sink 20 | { 21 | public: 22 | class Address 23 | { 24 | public: 25 | Address(); 26 | Address(const std::string &call, size_t ssid); 27 | Address(const Address &other); 28 | 29 | Address &operator=(const Address &other); 30 | 31 | inline bool isEmpty() const { return 0 == _call.size(); } 32 | inline const std::string &call() const { return _call; } 33 | inline size_t ssid() const { return _ssid; } 34 | 35 | protected: 36 | std::string _call; 37 | size_t _ssid; 38 | }; 39 | 40 | class Message 41 | { 42 | public: 43 | Message(); 44 | Message(uint8_t *buffer, size_t length); 45 | Message(const Message &other); 46 | 47 | Message &operator=(const Message &other); 48 | 49 | inline const Address &from() const { return _from; } 50 | inline const Address &to() const { return _to; } 51 | inline const std::vector
&via() const { return _via; } 52 | 53 | inline const std::string &payload() const { return _payload; } 54 | 55 | protected: 56 | Address _from; 57 | Address _to; 58 | std::vector
_via; 59 | std::string _payload; 60 | }; 61 | 62 | public: 63 | /** Constructor. */ 64 | AX25(); 65 | /** Destructor. */ 66 | virtual ~AX25(); 67 | 68 | /** Configures the node. */ 69 | virtual void config(const Config &src_cfg); 70 | /** Processes the bit stream. */ 71 | virtual void process(const Buffer &buffer, bool allow_overwrite); 72 | 73 | virtual void handleAX25Message(const Message &message); 74 | 75 | protected: 76 | /** The last bits. */ 77 | uint32_t _bitstream; 78 | /** A buffer of received bits. */ 79 | uint32_t _bitbuffer; 80 | /** The current state. */ 81 | uint32_t _state; 82 | 83 | /** Message buffer. */ 84 | uint8_t _rxbuffer[512]; 85 | /** Insert-pointer to the buffer. */ 86 | uint8_t *_ptr; 87 | }; 88 | 89 | 90 | /** Prints received AX25 messages to the specified stream. */ 91 | class AX25Dump: public AX25 92 | { 93 | public: 94 | /** Constructor. 95 | * @param stream The output stream. */ 96 | AX25Dump(std::ostream &stream); 97 | 98 | /** Implements AX25 interface. */ 99 | void handleAX25Message(const Message &message); 100 | 101 | protected: 102 | /** The output stream. */ 103 | std::ostream &_stream; 104 | }; 105 | 106 | 107 | /** Serialization of AX25 address. */ 108 | std::ostream& operator<<(std::ostream &stream, const sdr::AX25::Address &addr); 109 | /** Serialization of AX25 message. */ 110 | std::ostream& operator<<(std::ostream &stream, const sdr::AX25::Message &msg); 111 | 112 | } // namespace sdr 113 | 114 | 115 | #endif // __SDR_AX25_HH__ 116 | -------------------------------------------------------------------------------- /src/baudot.cc: -------------------------------------------------------------------------------- 1 | #include "baudot.hh" 2 | #include "traits.hh" 3 | #include "logger.hh" 4 | 5 | 6 | using namespace sdr; 7 | 8 | // Baudot code tables 9 | char Baudot::_letter[32] = { 0, 'E','\n', 'A', ' ', 'S', 'I', 'U','\n', 'D', 'R', 'J', 'N', 'F', 10 | 'C', 'K', 'T', 'Z', 'L', 'W', 'H', 'Y', 'P', 'Q', 'O', 'B', 'G', 0, 11 | 'M', 'X', 'V', 0}; 12 | char Baudot::_figure[32] = { 0, '3','\n', '-', ' ','\a', '8', '7','\n', '?', '4','\'', ',', '!', 13 | ':', '(', '5', '"', ')', '2', '#', '6', '0', '1', '9', '?', '&', 0, 14 | '.', '/', ';', 0}; 15 | 16 | // Some special codes 17 | #define CHAR_NUL 0 18 | #define CHAR_STF 27 19 | #define CHAR_STL 31 20 | #define CHAR_SPA 4 21 | 22 | 23 | Baudot::Baudot(StopBits stopBits) 24 | : Sink(), Source(), _mode(LETTERS) 25 | { 26 | switch (stopBits) { 27 | case STOP1: 28 | // Pattern xx11 xxxx xxxx xx00 29 | // Mask 0011 0000 0000 0011 30 | _stopHBits = 2; 31 | _bitsPerSymbol = 14; 32 | _pattern = 0x3000; 33 | _mask = 0x3003; 34 | break; 35 | case STOP15: 36 | // Pattern x11x xxxx xxxx x000 37 | // Mask 0110 0000 0000 0111 38 | _stopHBits = 3; 39 | _bitsPerSymbol = 15; 40 | _pattern = 0x6000; 41 | _mask = 0x6007; 42 | break; 43 | case STOP2: 44 | // Pattern 11xx xxxx xxxx 0000 45 | // Mask 1100 0000 0000 1111 46 | _stopHBits = 4; 47 | _bitsPerSymbol = 16; 48 | _pattern = 0xC000; 49 | _mask = 0xC00F; 50 | break; 51 | } 52 | } 53 | 54 | void 55 | Baudot::config(const Config &src_cfg) { 56 | if (! src_cfg.hasType()) { return; } 57 | // Check if buffer type matches 58 | if (Config::typeId() != src_cfg.type()) { 59 | ConfigError err; 60 | err << "Can not configure Baudot: Invalid type " << src_cfg.type() 61 | << ", expected " << Config::typeId(); 62 | throw err; 63 | } 64 | 65 | // Init (half) bit stream and counter 66 | _bitstream = 0; 67 | _bitcount = 0; 68 | 69 | // Compute buffer size. 70 | size_t buffer_size = (src_cfg.bufferSize()/(2*_bitsPerSymbol))+1; 71 | _buffer = Buffer(buffer_size); 72 | 73 | LogMessage msg(LOG_DEBUG); 74 | msg << "Config Baudot node: " << std::endl 75 | << " input sample rate: " << src_cfg.sampleRate() << " half-bits/s" << std::endl 76 | << " start bits: " << 1 << std::endl 77 | << " stop bits: " << float(_stopHBits)/2 << std::endl; 78 | Logger::get().log(msg); 79 | 80 | // propergate config 81 | this->setConfig(Config(Traits::scalarId, 0, buffer_size, 1)); 82 | } 83 | 84 | 85 | void 86 | Baudot::process(const Buffer &buffer, bool allow_overwrite) 87 | { 88 | size_t o=0; 89 | for (size_t i=0; i>shift)&0x01)<send(_buffer.head(o)); } 111 | } 112 | -------------------------------------------------------------------------------- /src/baudot.hh: -------------------------------------------------------------------------------- 1 | #ifndef __SDR_BAUDOT_HH__ 2 | #define __SDR_BAUDOT_HH__ 3 | 4 | #include "node.hh" 5 | #include 6 | 7 | namespace sdr { 8 | 9 | /** Implements a Baudot decoder. Inconjecture with the (A)FSK demodulator, it enables the 10 | * reception of radio teletype (RTTY) messages. 11 | * 12 | * Please note that a baudot encoded char is usually transmitted in a frame with one start bit and 13 | * 1, 1.5 or 2 stop bits. Hence this node expects to receive two bits for one decoded bit in order 14 | * to detect the 1.5 stop bits reliably. 15 | * 16 | * I.e. to receive a 45.45 baud RTTY signal, the (A)FSK demodulator need to be configured for 17 | * 90.90 baud (= 2*45.45 baud). 18 | * @ingroup datanodes */ 19 | class Baudot: public Sink, public Source 20 | { 21 | public: 22 | /** Specifies the current code-tables. */ 23 | typedef enum { 24 | LETTERS, ///< Letters. 25 | FIGURES ///< Numbers, symbols etc. 26 | } Mode; 27 | 28 | /** Specifies the number of stop bits. */ 29 | typedef enum { 30 | STOP1, ///< 1 stop bit. 31 | STOP15, ///< 1.5 stop bits. 32 | STOP2 ///< 2 stop bits. 33 | } StopBits; 34 | 35 | public: 36 | /** Constructor. */ 37 | Baudot(StopBits stopBits = STOP15); 38 | 39 | /** Configures the node. */ 40 | virtual void config(const Config &src_cfg); 41 | /** Processes the bit-stream. */ 42 | virtual void process(const Buffer &buffer, bool allow_overwrite); 43 | 44 | protected: 45 | /** Code table for letters. */ 46 | static char _letter[32]; 47 | /** Code table for symbols or figure (i.e. numbers). */ 48 | static char _figure[32]; 49 | /** The last bits received. */ 50 | uint16_t _bitstream; 51 | /** The number of bits received. */ 52 | size_t _bitcount; 53 | 54 | /** The currently selected table. */ 55 | Mode _mode; 56 | 57 | /** Specifies the number of half bits per symbol. */ 58 | size_t _bitsPerSymbol; 59 | /** Specifies the frame pattern. */ 60 | uint16_t _pattern; 61 | /** Specifies the frame mask. */ 62 | uint16_t _mask; 63 | /** Number of half bits forming the stop bit. */ 64 | uint16_t _stopHBits; 65 | 66 | /** The output buffer. */ 67 | Buffer _buffer; 68 | }; 69 | 70 | } 71 | 72 | #endif // BAUDOT_HH 73 | -------------------------------------------------------------------------------- /src/bch31_21.cc: -------------------------------------------------------------------------------- 1 | #include "bch31_21.hh" 2 | #include "stdlib.h" 3 | #include "string.h" 4 | 5 | using namespace sdr; 6 | 7 | /* 8 | * the code used by POCSAG is a (n=31,k=21) BCH Code with dmin=5, 9 | * thus it could correct two bit errors in a 31-Bit codeword. 10 | * It is a systematic code. 11 | * The generator polynomial is: 12 | * g(x) = x^10+x^9+x^8+x^6+x^5+x^3+1 13 | * The parity check polynomial is: 14 | * h(x) = x^21+x^20+x^18+x^16+x^14+x^13+x^12+x^11+x^8+x^5+x^3+1 15 | * g(x) * h(x) = x^n+1 16 | */ 17 | #define BCH_POLY 03551 /* octal */ 18 | #define BCH_N 31 19 | #define BCH_K 21 20 | 21 | static inline unsigned char even_parity(uint32_t data) 22 | { 23 | unsigned int temp = data ^ (data >> 16); 24 | 25 | temp = temp ^ (temp >> 8); 26 | temp = temp ^ (temp >> 4); 27 | temp = temp ^ (temp >> 2); 28 | temp = temp ^ (temp >> 1); 29 | return temp & 1; 30 | } 31 | 32 | static unsigned int 33 | pocsag_syndrome(uint32_t data) 34 | { 35 | uint32_t shreg = data >> 1; /* throw away parity bit */ 36 | uint32_t mask = 1L << (BCH_N-1), coeff = BCH_POLY << (BCH_K-1); 37 | int n = BCH_K; 38 | 39 | for(; n > 0; mask >>= 1, coeff >>= 1, n--) { 40 | if (shreg & mask) { shreg ^= coeff; } 41 | } 42 | if (even_parity(data)) { 43 | shreg |= (1 << (BCH_N - BCH_K)); 44 | } 45 | return shreg; 46 | } 47 | 48 | static void 49 | bitslice_syndrome(uint32_t *slices) 50 | { 51 | const int firstBit = BCH_N - 1; 52 | int i, n; 53 | uint32_t paritymask = slices[0]; 54 | 55 | // do the parity and shift together 56 | for (i = 1; i < 32; ++i) { 57 | paritymask ^= slices[i]; 58 | slices[i-1] = slices[i]; 59 | } 60 | slices[31] = 0; 61 | 62 | // BCH_POLY << (BCH_K - 1) is 63 | // 20 21 22 23 64 | // 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ONE, 0, 0, ONE, 65 | // 24 25 26 27 28 29 30 31 66 | // 0, ONE, ONE, 0, ONE, ONE, ONE, 0 67 | 68 | for (n = 0; n < BCH_K; ++n) { 69 | // one line here for every '1' bit in coeff (above) 70 | const int bit = firstBit - n; 71 | slices[20 - n] ^= slices[bit]; 72 | slices[23 - n] ^= slices[bit]; 73 | slices[25 - n] ^= slices[bit]; 74 | slices[26 - n] ^= slices[bit]; 75 | slices[28 - n] ^= slices[bit]; 76 | slices[29 - n] ^= slices[bit]; 77 | slices[30 - n] ^= slices[bit]; 78 | slices[31 - n] ^= slices[bit]; 79 | } 80 | 81 | // apply the parity mask we built up 82 | slices[BCH_N - BCH_K] |= paritymask; 83 | } 84 | 85 | static uint32_t 86 | transpose_n(int n, uint32_t *matrix) 87 | { 88 | uint32_t out = 0; 89 | int j; 90 | 91 | for (j = 0; j < 32; ++j) { 92 | if (matrix[j] & (1<>= 1; } 148 | --n; 149 | data ^= (1<>= 1; } 171 | --n; 172 | 173 | data = transpose_n(n, in); 174 | goto returnfree; 175 | } 176 | 177 | transpose_clone(data, xpose); 178 | n = 0; 179 | } 180 | } 181 | } 182 | 183 | if (n > 0) { 184 | memcpy(in, xpose, sizeof(uint32_t)*32); 185 | 186 | bitslice_syndrome(xpose); 187 | 188 | res = 0; 189 | for (i = 0; i < 32; ++i) { res |= xpose[i]; } 190 | res = ~res; 191 | 192 | if (res) { 193 | int n = 0; 194 | while (res) { ++n; res >>= 1; } 195 | --n; 196 | 197 | data = transpose_n(n, in); 198 | goto returnfree; 199 | } 200 | } 201 | 202 | if (xpose) { free(xpose); } 203 | if (in) { free(in); } 204 | return 1; 205 | 206 | returnfree: 207 | if (xpose) 208 | free(xpose); 209 | if (in) 210 | free(in); 211 | return 0; 212 | } 213 | 214 | -------------------------------------------------------------------------------- /src/bch31_21.hh: -------------------------------------------------------------------------------- 1 | #ifndef __SDR_BCH31_21_HH__ 2 | #define __SDR_BCH31_21_HH__ 3 | 4 | #include 5 | 6 | namespace sdr { 7 | 8 | /** Checks and repairs a POCSAG message with its 9 | * BCH(31,21) ECC. */ 10 | int pocsag_repair(uint32_t &data); 11 | 12 | } 13 | 14 | #endif // __SDR_BCH31_21_HH__ 15 | -------------------------------------------------------------------------------- /src/buffer.cc: -------------------------------------------------------------------------------- 1 | #include "buffer.hh" 2 | #include 3 | 4 | using namespace sdr; 5 | 6 | 7 | /* ********************************************************************************************* * 8 | * Implementation of RawBuffer 9 | * ********************************************************************************************* */ 10 | RawBuffer::RawBuffer() 11 | : _ptr(0), _storage_size(0), _b_offset(0), _b_length(0), _refcount(0), _owner(0) 12 | { 13 | // pass... 14 | } 15 | 16 | RawBuffer::RawBuffer(char *data, size_t offset, size_t len) 17 | : _ptr(data), _storage_size(offset+len), _b_offset(offset), _b_length(len), 18 | _refcount(0), _owner(0) 19 | { 20 | // pass... 21 | } 22 | 23 | RawBuffer::RawBuffer(size_t N, BufferOwner *owner) 24 | : _ptr((char *)malloc(N)), _storage_size(N), _b_offset(0), _b_length(N), 25 | _refcount((int *)malloc(sizeof(int))), _owner(owner) 26 | { 27 | // Check if data could be allocated 28 | if ((0 == _ptr) && (0 != _refcount)) { 29 | free(_refcount); _refcount = 0; _storage_size = 0; return; 30 | } 31 | // Set refcount, done... 32 | if (_refcount) { (*_refcount) = 1; } 33 | } 34 | 35 | RawBuffer::RawBuffer(const RawBuffer &other) 36 | : _ptr(other._ptr), _storage_size(other._storage_size), 37 | _b_offset(other._b_offset), _b_length(other._b_length), 38 | _refcount(other._refcount), _owner(other._owner) 39 | { 40 | // pass... 41 | } 42 | 43 | RawBuffer::RawBuffer(const RawBuffer &other, size_t offset, size_t len) 44 | : _ptr(other._ptr), _storage_size(other._storage_size), 45 | _b_offset(other._b_offset+offset), _b_length(len), 46 | _refcount(other._refcount), _owner(other._owner) 47 | { 48 | // pass... 49 | } 50 | 51 | RawBuffer::~RawBuffer() 52 | { 53 | // pass... 54 | } 55 | 56 | /** Increment reference counter. */ 57 | void RawBuffer::ref() const { 58 | if (0 != _refcount) { (*_refcount)++; } 59 | } 60 | 61 | 62 | /** Dereferences the buffer. */ 63 | void RawBuffer::unref() { 64 | // If empty -> skip... 65 | if ((0 == _ptr) || (0 == _refcount)) { return; } 66 | // Decrement refcount 67 | (*_refcount)--; 68 | // If there is only one reference left and the buffer is owned -> notify owner, who holds the last 69 | // reference. 70 | if ((1 == (*_refcount)) && (_owner)) { _owner->bufferUnused(*this); } 71 | // If the buffer is unreachable -> free 72 | if (0 == (*_refcount)) { 73 | free(_ptr); free(_refcount); 74 | // mark as empty 75 | _ptr = 0; _refcount=0; 76 | } 77 | } 78 | 79 | 80 | 81 | /* ********************************************************************************************* * 82 | * Implementation of RawRingBuffer 83 | * ********************************************************************************************* */ 84 | RawRingBuffer::RawRingBuffer() 85 | : RawBuffer(), _take_idx(0), _b_stored(0) 86 | { 87 | // pass... 88 | } 89 | 90 | RawRingBuffer::RawRingBuffer(size_t size) 91 | : RawBuffer(size), _take_idx(0), _b_stored(0) 92 | { 93 | // pass... 94 | } 95 | 96 | RawRingBuffer::RawRingBuffer(const RawRingBuffer &other) 97 | : RawBuffer(other), _take_idx(other._take_idx), _b_stored(other._b_stored) 98 | { 99 | // pass... 100 | } 101 | 102 | RawRingBuffer::~RawRingBuffer() { 103 | // pass... 104 | } 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /src/buffernode.hh: -------------------------------------------------------------------------------- 1 | #ifndef __SDR_BUFFERNODE_HH__ 2 | #define __SDR_BUFFERNODE_HH__ 3 | 4 | #include "node.hh" 5 | #include "config.hh" 6 | #include "logger.hh" 7 | 8 | #include 9 | #include 10 | 11 | 12 | namespace sdr { 13 | 14 | /** A simple buffering node, that ensures a fixed buffer size. This node is useful, expecially in 15 | * front of a node that performs a FFT transform, which requires a certain buffer size. 16 | * @ingroup datanodes */ 17 | template 18 | class BufferNode : public Sink, public Source 19 | { 20 | public: 21 | /** Constructs a new buffer node. 22 | * @param bufferSize Specifies the desired size of the output buffers. */ 23 | BufferNode(size_t bufferSize) 24 | : _bufferSize(bufferSize), _bufferSet(0, _bufferSize), _temp(bufferSize), _samplesLeft(0) 25 | { 26 | // pass... 27 | } 28 | 29 | /** Configures the buffer node. */ 30 | virtual void config(const Config &src_cfg) 31 | { 32 | // Check if source config is complete 33 | if (Config::Type_UNDEFINED == src_cfg.type()) { return; } 34 | if (0 == src_cfg.bufferSize()) { return; } 35 | if (0 == src_cfg.numBuffers()) { return; } 36 | // Check source type 37 | if (src_cfg.type() != Config::typeId()) { 38 | ConfigError err; 39 | err << "Can not configure BufferNode sink. Source type is " << src_cfg.type() 40 | << " expected " << Config::typeId() << std::endl; 41 | throw err; 42 | } 43 | // Estimate number of buffers needed: 44 | size_t totSize = src_cfg.bufferSize()*src_cfg.numBuffers(); 45 | size_t numBuffers = std::max(size_t(2), totSize/_bufferSize); 46 | _bufferSet.resize(numBuffers); 47 | 48 | LogMessage msg(LOG_DEBUG); 49 | msg << "Configure BufferNode: " << std::endl 50 | << " type: " << src_cfg.type() << std::endl 51 | << " sample-rate: " << src_cfg.sampleRate() << std::endl 52 | << " buffer-size: " << src_cfg.bufferSize() 53 | << " -> " << _bufferSize << std::endl 54 | << " # buffers: " << src_cfg.numBuffers(); 55 | 56 | // Propergate source config 57 | this->setConfig(Config(src_cfg.type(), src_cfg.sampleRate(), _bufferSize, numBuffers)); 58 | } 59 | 60 | /** Process the incomming data. */ 61 | virtual void process(const Buffer &buffer, bool allow_overwrite) 62 | { 63 | // If the current buffer buffer does not contain enough smaples to fill an output buffer: 64 | if ((_samplesLeft+buffer.size()) < _bufferSize) { 65 | memcpy(_temp.data()+_samplesLeft*sizeof(Scalar), buffer.data(), sizeof(Scalar)*buffer.size()); 66 | _samplesLeft += buffer.size(); 67 | return; 68 | } 69 | // There are enough samples collected to fill an ouput buffer, 70 | // fill first out buffer and send it 71 | Buffer out = _bufferSet.getBuffer(); 72 | memcpy(out.data(), _temp.data(), sizeof(Scalar)*_samplesLeft); 73 | memcpy(out.data()+_samplesLeft*sizeof(Scalar), buffer.data(), sizeof(Scalar)*(_bufferSize-_samplesLeft)); 74 | size_t in_offset = (_bufferSize-_samplesLeft); 75 | 76 | // Determine the number of samples left 77 | _samplesLeft = buffer.size()+_samplesLeft-_bufferSize; 78 | this->send(out); 79 | 80 | // Process remaining data 81 | while (_samplesLeft >= _bufferSize) { 82 | Buffer out = _bufferSet.getBuffer(); 83 | memcpy(out.data(), buffer.data()+in_offset*sizeof(Scalar), _bufferSize*sizeof(Scalar)); 84 | in_offset += _bufferSize; 85 | _samplesLeft -= _bufferSize; 86 | this->send(out); 87 | } 88 | 89 | // Store data left over into temp 90 | memcpy(_temp.data(), buffer.data(), _samplesLeft*sizeof(Scalar)); 91 | } 92 | 93 | protected: 94 | /** The desired buffer size. */ 95 | size_t _bufferSize; 96 | /** A set of output buffers. */ 97 | BufferSet _bufferSet; 98 | /** An intermediate buffer to hold left-over samples from the previous buffers. */ 99 | Buffer _temp; 100 | /** Number of samples left. */ 101 | size_t _samplesLeft; 102 | }; 103 | 104 | } 105 | 106 | #endif // __SDR_BUFFERNODE_HH__ 107 | -------------------------------------------------------------------------------- /src/combine.hh: -------------------------------------------------------------------------------- 1 | #ifndef __SDR_COMBINE_HH__ 2 | #define __SDR_COMBINE_HH__ 3 | 4 | #include "config.hh" 5 | #include "traits.hh" 6 | #include "node.hh" 7 | #include "buffer.hh" 8 | #include "logger.hh" 9 | #include 10 | 11 | namespace sdr { 12 | 13 | template class Combine; 14 | 15 | /** A single sink of a Combine node. Do not use this node explicitly, consider using @c Combine. */ 16 | template 17 | class CombineSink: public Sink 18 | { 19 | public: 20 | /** Constructor. */ 21 | CombineSink(Combine *combine, size_t index, RingBuffer &buffer) 22 | : Sink(), _index(index), _parent(combine), _buffer(buffer) 23 | { 24 | // Pass... 25 | } 26 | /** Destructor. */ 27 | virtual ~CombineSink() { 28 | // pass... 29 | } 30 | 31 | /** Configures the sink. */ 32 | virtual void config(const Config &src_cfg) { 33 | // Requires sample rate and type 34 | if (!src_cfg.hasType() || !src_cfg.hasSampleRate()) { return; } 35 | // Check type 36 | if (Config::typeId() != src_cfg.type()) { 37 | ConfigError err; 38 | err << "Can not configure CombinSink: Invalid source type " << src_cfg.type() 39 | << ", expected " << Config::typeId(); 40 | throw err; 41 | } 42 | // Notify parent 43 | _parent->notifyConfig(_index, src_cfg); 44 | } 45 | 46 | /** Handles the given buffer. */ 47 | virtual void process(const Buffer &buffer, bool allow_overwrite) { 48 | // Copy data into ring buffer and notify parent 49 | _buffer.put(buffer); 50 | _parent->notifyData(_index); 51 | } 52 | 53 | protected: 54 | /** The index of the sink within the combine node. */ 55 | size_t _index; 56 | /** A reference to the combine node. */ 57 | Combine *_parent; 58 | /** The input ring-buffer. */ 59 | RingBuffer &_buffer; 60 | }; 61 | 62 | 63 | /** A combine node. This node allows to combine several streams into one. 64 | * @ingroup datanodes */ 65 | template 66 | class Combine 67 | { 68 | public: 69 | /** Constructor, @n N specifies the number of sinks. */ 70 | Combine(size_t N) { 71 | _buffers.reserve(N); _sinks.reserve(N); 72 | for (size_t i=0; i()); 74 | _sinks.push_back(new CombineSink(this, i, _buffers.back())); 75 | } 76 | } 77 | 78 | /** Destructor. */ 79 | virtual ~Combine() { 80 | // Unref all buffers and free sinks 81 | for (size_t i=0; i<_sinks.size(); i++) { 82 | delete _sinks[i]; 83 | _buffers[i].unref(); 84 | } 85 | _buffers.clear(); 86 | _sinks.clear(); 87 | } 88 | 89 | /** Needs to be overridden. */ 90 | virtual void config(const Config &cfg) = 0; 91 | /** Needs to be overridden. */ 92 | virtual void process(std::vector< RingBuffer > &buffers, size_t N) = 0; 93 | 94 | 95 | protected: 96 | /** Unifies the configuration of all sinks. */ 97 | void notifyConfig(size_t idx, const Config &cfg) 98 | { 99 | // Requires type, sampleRate and buffer size 100 | if (!cfg.hasType() || !cfg.hasSampleRate() || !cfg.hasBufferSize()) { return; } 101 | // check or unify type 102 | if (!_config.hasType()) { _config.setType(cfg.type()); } 103 | else if (_config.type() != cfg.type()) { 104 | ConfigError err; 105 | err << "Can not configure Combine node: Invalid type of sink #" << idx 106 | << " " << cfg.type() << ", expected " << _config.type(); 107 | throw err; 108 | } 109 | // Check sample rate 110 | if (!_config.hasSampleRate()) { _config.setSampleRate(cfg.sampleRate()); } 111 | else if (_config.sampleRate() != cfg.sampleRate()) { 112 | ConfigError err; 113 | err << "Can ont configure Combine node: Invalid sample rate of sink #" << idx 114 | << " " << cfg.sampleRate() << ", expected " << _config.sampleRate(); 115 | throw err; 116 | } 117 | // Determine max buffer size 118 | if (!_config.hasBufferSize()) { _config.setBufferSize(cfg.bufferSize()); } 119 | else { 120 | // Take maximum: 121 | _config.setBufferSize(std::max(_config.bufferSize(), cfg.bufferSize())); 122 | } 123 | // Reallocate buffers 124 | for (size_t i=0; i<_sinks.size(); i++) { 125 | _buffers[i] = RingBuffer(_config.bufferSize()); 126 | } 127 | // Propergate config 128 | this->config(_config); 129 | } 130 | 131 | /** Determines the minimum amount of data that is available on all ring buffers. */ 132 | void notifyData(size_t idx) { 133 | // Determine minimum size of available data 134 | size_t N = std::numeric_limits::max(); 135 | for (size_t i=0; i<_sinks.size(); i++) { 136 | N = std::min(N, _buffers[i].stored()); 137 | } 138 | if (N > 0) { this->process(_buffers, N); } 139 | } 140 | 141 | protected: 142 | /** The ring buffers of all combine sinks. */ 143 | std::vector< RingBuffer > _buffers; 144 | /** The combine sinks. */ 145 | std::vector< CombineSink *> _sinks; 146 | /** The output configuration. */ 147 | Config _config; 148 | 149 | friend class CombineSink; 150 | }; 151 | 152 | 153 | /** Interleaves several input streams. 154 | * @ingroup datanodes */ 155 | template 156 | class Interleave : public Combine, public Source 157 | { 158 | public: 159 | /** Constructor. */ 160 | Interleave(size_t N) 161 | : Combine(N), Source(), _N(N), _buffer() 162 | { 163 | // pass... 164 | } 165 | 166 | /** Retunrs the i-th sink. */ 167 | Sink *sink(size_t i) { 168 | if (i >= _N) { 169 | RuntimeError err; 170 | err << "Interleave: Sink index " << i << " out of range [0," << _N << ")"; 171 | throw err; 172 | } 173 | return Combine::_sinks[i]; 174 | } 175 | 176 | /** Configures the interleave node. */ 177 | virtual void config(const Config &cfg) { 178 | //Requres type & buffer size 179 | if (!cfg.hasType() || !cfg.hasBufferSize()) { return; } 180 | // Check type 181 | if (Config::typeId() != cfg.type()) { 182 | ConfigError err; 183 | err << "Can not configure Interleave node: Invalid source type " << cfg.type() 184 | << ", expected " << Config::typeId(); 185 | throw err; 186 | } 187 | // Allocate buffer: 188 | _buffer = Buffer(_N*cfg.bufferSize()); 189 | // Propergate config 190 | this->setConfig(Config(Config::typeId(), cfg.sampleRate(), _buffer.size(), 1)); 191 | } 192 | 193 | /** Processes the data from all sinks. */ 194 | virtual void process(std::vector > &buffers, size_t N) { 195 | if (0 == N) { return; } 196 | if (! _buffer.isUnused()) { 197 | #ifdef SDR_DEBUG 198 | LogMessage msg(LOG_WARNING); 199 | msg << "Interleave: Output buffer in use: Drop " << _N << "x" << N 200 | << " input values"; 201 | Logger::get().log(msg); 202 | #endif 203 | for (size_t i=0; isend(_buffer.head(num*_N)); 220 | } 221 | 222 | protected: 223 | /** The number of sinks. */ 224 | size_t _N; 225 | /** The putput buffer. */ 226 | Buffer _buffer; 227 | }; 228 | 229 | } 230 | #endif // __SDR_COMBINE_HH__ 231 | -------------------------------------------------------------------------------- /src/config.hh.in: -------------------------------------------------------------------------------- 1 | #cmakedefine SDR_DEBUG 1 2 | 3 | #cmakedefine SDR_WITH_FFTW 1 4 | #cmakedefine SDR_WITH_PORTAUDIO 1 5 | #cmakedefine SDR_WITH_RTLSDR 1 6 | 7 | #define SDR_VERSION_MAJOR ${libsdr_VERSION_MAJOR} 8 | #define SDR_VERSION_MINOR ${libsdr_VERSION_MINOR} 9 | #define SDR_VERSION_PATCH ${libsdr_VERSION_PATCH} 10 | #define SDR_VERSION_STRING "${libsdr_VERSION_MAJOR}.${libsdr_VERSION_MINOR}.${libsdr_VERSION_PATCH}" 11 | -------------------------------------------------------------------------------- /src/exception.cc: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/exception.hh: -------------------------------------------------------------------------------- 1 | #ifndef __SDR_EXCEPTION_HH__ 2 | #define __SDR_EXCEPTION_HH__ 3 | 4 | #include 5 | #include 6 | 7 | namespace sdr { 8 | 9 | /** Base class of all SDR exceptions. */ 10 | class SDRError : public std::exception, public std::stringstream { 11 | public: 12 | /** Constructor. */ 13 | SDRError(): std::exception(), std::stringstream() { } 14 | /** Copy constructor. */ 15 | SDRError(const SDRError &other) 16 | : std::exception(), std::stringstream() { this->str(other.str()); } 17 | /** Destructor. */ 18 | virtual ~SDRError() throw() { } 19 | /** Implements the @c std::exception interface. */ 20 | virtual const char *what() const throw() { return this->str().c_str(); } 21 | }; 22 | 23 | /** The configuration error class. */ 24 | class ConfigError : public SDRError { 25 | public: 26 | /** Constructor. */ 27 | ConfigError(): SDRError() {} 28 | /** Copy constructor. */ 29 | ConfigError(const ConfigError &other): SDRError(other) {} 30 | /** Destructor. */ 31 | virtual ~ConfigError() throw() { } 32 | }; 33 | 34 | 35 | /** The runtime error class. */ 36 | class RuntimeError: public SDRError { 37 | public: 38 | /** Constructor. */ 39 | RuntimeError(): SDRError() {} 40 | /** Copy constructor. */ 41 | RuntimeError(const RuntimeError &other): SDRError(other) {} 42 | /** Destructor. */ 43 | virtual ~RuntimeError() throw() { } 44 | }; 45 | 46 | 47 | 48 | } 49 | #endif // __SDR_EXCEPTION_HH__ 50 | -------------------------------------------------------------------------------- /src/fftplan.hh: -------------------------------------------------------------------------------- 1 | #ifndef __SDR_FFTPLAN_HH__ 2 | #define __SDR_FFTPLAN_HH__ 3 | 4 | #include "buffer.hh" 5 | #include "node.hh" 6 | #include "config.hh" 7 | 8 | namespace sdr { 9 | 10 | // Forward declaration of FFTPlan 11 | template class FFTPlan { }; 12 | 13 | /** FFT module class, provides static methods to perfrom a FFT directly. */ 14 | class FFT { 15 | public: 16 | /** Direction type. */ 17 | typedef enum { 18 | FORWARD, BACKWARD 19 | } Direction; 20 | 21 | /** Performs a FFT transform. */ 22 | template 23 | static void exec(const Buffer< std::complex > &in, 24 | const Buffer< std::complex > &out, FFT::Direction dir) 25 | { 26 | FFTPlan plan(in, out, dir); plan(); 27 | } 28 | 29 | /** Performs an in-place FFT transform. */ 30 | template 31 | static void exec(const Buffer< std::complex > &inplace, FFT::Direction dir) 32 | { 33 | FFTPlan plan(inplace, dir); plan(); 34 | } 35 | 36 | }; 37 | 38 | } 39 | 40 | 41 | #ifdef SDR_WITH_FFTW 42 | #include "fftplan_fftw3.hh" 43 | #endif 44 | 45 | 46 | #endif // __SDR_FFTPLAN_HH__ 47 | -------------------------------------------------------------------------------- /src/fftplan_fftw3.hh: -------------------------------------------------------------------------------- 1 | #ifndef __SDR_FFTPLAN_FFTW3_HH__ 2 | #define __SDR_FFTPLAN_FFTW3_HH__ 3 | 4 | #include "fftplan.hh" 5 | #include 6 | 7 | 8 | namespace sdr { 9 | 10 | /** Template specialization for a FFT transform on std::complex values. */ 11 | template<> 12 | class FFTPlan 13 | { 14 | public: 15 | /** Constructor. */ 16 | FFTPlan(const Buffer< std::complex > &in, const Buffer< std::complex > &out, 17 | FFT::Direction dir) 18 | : _in(in), _out(out) 19 | { 20 | if (in.size() != out.size()) { 21 | ConfigError err; 22 | err << "Can not construct FFT plan: input & output buffers are of different size!"; 23 | throw err; 24 | } 25 | if (in.isEmpty() || out.isEmpty()) { 26 | ConfigError err; 27 | err << "Can not construct FFT plan: input or output buffer is empty!"; 28 | throw err; 29 | } 30 | 31 | int fftw_dir = FFTW_FORWARD; 32 | if (FFT::BACKWARD == dir) { fftw_dir = FFTW_BACKWARD; } 33 | 34 | _plan = fftw_plan_dft_1d( 35 | in.size(), (fftw_complex *)in.data(), (fftw_complex *)out.data(), 36 | fftw_dir, FFTW_ESTIMATE); 37 | } 38 | 39 | /** Constructor. */ 40 | FFTPlan(const Buffer< std::complex > &inplace, FFT::Direction dir) 41 | : _in(inplace), _out(inplace) 42 | { 43 | if (inplace.isEmpty()) { 44 | ConfigError err; 45 | err << "Can not construct FFT plan: Buffer is empty!"; 46 | throw err; 47 | } 48 | 49 | int fftw_dir = FFTW_FORWARD; 50 | if (FFT::BACKWARD == dir) { fftw_dir = FFTW_BACKWARD; } 51 | 52 | _plan = fftw_plan_dft_1d( 53 | inplace.size(), (fftw_complex *)inplace.data(), (fftw_complex *)inplace.data(), 54 | fftw_dir, FFTW_ESTIMATE); 55 | } 56 | 57 | /** Destructor. */ 58 | virtual ~FFTPlan() { 59 | fftw_destroy_plan(_plan); 60 | } 61 | 62 | /** Performs the transformation. */ 63 | void operator() () { 64 | fftw_execute(_plan); 65 | } 66 | 67 | protected: 68 | /** Input buffer. */ 69 | Buffer< std::complex > _in; 70 | /** Output buffer. */ 71 | Buffer< std::complex > _out; 72 | /** The FFT plan. */ 73 | fftw_plan _plan; 74 | }; 75 | 76 | 77 | /** Template specialization for a FFT transform on std::complex values. */ 78 | template<> 79 | class FFTPlan 80 | { 81 | public: 82 | /** Constructor. */ 83 | FFTPlan(const Buffer< std::complex > &in, const Buffer< std::complex > &out, 84 | FFT::Direction dir) 85 | : _in(in), _out(out) 86 | { 87 | if (in.size() != out.size()) { 88 | ConfigError err; 89 | err << "Can not construct FFT plan: input & output buffers are of different size!"; 90 | throw err; 91 | } 92 | 93 | if (in.isEmpty() || out.isEmpty()) { 94 | ConfigError err; 95 | err << "Can not construct FFT plan: input or output buffer is empty!"; 96 | throw err; 97 | } 98 | 99 | int fftw_dir = FFTW_FORWARD; 100 | if (FFT::BACKWARD == dir) { fftw_dir = FFTW_BACKWARD; } 101 | 102 | _plan = fftwf_plan_dft_1d( 103 | in.size(), (fftwf_complex *)in.data(), (fftwf_complex *)out.data(), 104 | fftw_dir, FFTW_ESTIMATE); 105 | } 106 | 107 | /** Constructor. */ 108 | FFTPlan(const Buffer< std::complex > &inplace, FFT::Direction dir) 109 | : _in(inplace), _out(inplace) 110 | { 111 | if (inplace.isEmpty()) { 112 | ConfigError err; 113 | err << "Can not construct FFT plan: Buffer is empty!"; 114 | throw err; 115 | } 116 | 117 | int fftw_dir = FFTW_FORWARD; 118 | if (FFT::BACKWARD == dir) { fftw_dir = FFTW_BACKWARD; } 119 | 120 | _plan = fftwf_plan_dft_1d( 121 | inplace.size(), (fftwf_complex *)inplace.data(), (fftwf_complex *)inplace.data(), 122 | fftw_dir, FFTW_ESTIMATE); 123 | } 124 | 125 | /** Destructor. */ 126 | virtual ~FFTPlan() { 127 | fftwf_destroy_plan(_plan); 128 | } 129 | 130 | /** Performs the FFT transform. */ 131 | void operator() () { 132 | fftwf_execute(_plan); 133 | } 134 | 135 | protected: 136 | /** Input buffer. */ 137 | Buffer< std::complex > _in; 138 | /** Output buffer. */ 139 | Buffer< std::complex > _out; 140 | /** The fft plan. */ 141 | fftwf_plan _plan; 142 | }; 143 | 144 | 145 | 146 | 147 | } 148 | 149 | #endif // __SDR_FFTPLAN_FFTW3_HH__ 150 | -------------------------------------------------------------------------------- /src/fftplan_native.hh: -------------------------------------------------------------------------------- 1 | #ifndef __SDR_FFTPLAN_NATIVE_HH__ 2 | #define __SDR_FFTPLAN_NATIVE_HH__ 3 | 4 | #include "traits.hh" 5 | #include "fftplan.hh" 6 | 7 | namespace sdr { 8 | 9 | /** Trivial FFT implementation for buffer sizes of N=2**K. */ 10 | template 11 | class FFTPlan 12 | { 13 | public: 14 | /** The super-scalar of the input type. */ 15 | typedef typename Traits::SScalar SScalar; 16 | 17 | public: 18 | /** Constructs a FFT plan for the input and output buffers. */ 19 | FFTPlan(const Buffer< std::complex > &in, const Buffer< std::complex > &out, 20 | FFT::Direction dir) 21 | : _N(in.size()), _in(in), _out(out) 22 | { 23 | // Check dimensions 24 | if (_in.size() != _out.size()) { 25 | ConfigError err; 26 | err << "Can not construct FFT plan: input & output buffers are of different size!"; 27 | throw err; 28 | } 29 | if (_in.isEmpty() || _out.isEmpty()) { 30 | ConfigError err; 31 | err << "Can not construct FFT plan: input or output buffer is empty!"; 32 | throw err; 33 | } 34 | // Check if N is power of two 35 | if (_N != std::pow(2.0, std::log2(_N))) { 36 | err << "Can not construct FFT plan: input and output buffer length must be a power of 2!"; 37 | throw err; 38 | } 39 | 40 | // Assemble lookuptable 41 | int dir_fac = (dir==FFT::FORWARD) ? 1 : -1; 42 | for (size_t i=0; i<_N; i++) { 43 | _lut[i] = (std::exp(std::complex(0,(-2*M_PI*i*dir_fac)/N)) * (1<::shift)); 44 | } 45 | } 46 | 47 | /** Destructor. */ 48 | virtual ~FFTPlan() { 49 | _lut.unref(); 50 | } 51 | 52 | /** Performs the FFT. */ 53 | void operator() () { 54 | _calc(reinterpret_cast< std::complex >(_in.data()), 55 | reinterpret_cast< std::complex >(_out.data()), _N, 1); 56 | } 57 | 58 | protected: 59 | /** Actual FFT implmenetation. */ 60 | void _calc(std::complex *a, std::complex *b, size_t N, size_t stride) { 61 | // Recursion exit 62 | if (1 == N) { b[0] = a[0]; return; } 63 | // Calc FFT recursive 64 | _calc(a, b, N/2, 2*stride); 65 | _calc(a+stride, b+N/2, N/2, 2*stride); 66 | // Radix-2... 67 | for (size_t i=0; i tmp = b[i]; 69 | // I hope that the compiler will turn the (x/(1<> L) if x is an integer... 70 | b[i] = tmp + (_lut[i]*std::complex(b[i+N/2])) / (1<::shift); 71 | b[i+N/2] = tmp - (_lut[i]*std::complex(b[i+N/2])) / (1<::shift); 72 | } 73 | } 74 | 75 | protected: 76 | /** FFT size, needs to be a power of 2. */ 77 | size_t _N; 78 | /** The input buffer. */ 79 | Buffer< std::complex > _in; 80 | /** The output buffer. */ 81 | Buffer< std::complex > _out; 82 | /** The exp(-i 2 pi k / N) look-up table. */ 83 | Buffer< std::complex > _lut; 84 | }; 85 | 86 | } 87 | 88 | #endif // __SDR_FFTPLAN_NATIVE_HH__ 89 | -------------------------------------------------------------------------------- /src/freqshift.hh: -------------------------------------------------------------------------------- 1 | #ifndef __SDR_FREQSHIFT_HH__ 2 | #define __SDR_FREQSHIFT_HH__ 3 | 4 | #include "config.hh" 5 | #include "traits.hh" 6 | #include "node.hh" 7 | #include "operators.hh" 8 | 9 | namespace sdr { 10 | 11 | /** A performant implementation of a frequency-shift operation on integer signals. 12 | * @ingroup filters */ 13 | template 14 | class FreqShiftBase 15 | { 16 | public: 17 | /** The complex input signal. */ 18 | typedef std::complex CScalar; 19 | /** The compute (super) scalar of the input type. */ 20 | typedef typename Traits::SScalar SScalar; 21 | /** The complex compute (super) scalar of the input type. */ 22 | typedef std::complex CSScalar; 23 | 24 | public: 25 | /** Constructor. */ 26 | FreqShiftBase(double F, double Fs) 27 | : _freq_shift(F), _Fs(Fs), _lut_inc(0), _lut_count(0), _lut(_lut_size) 28 | { 29 | // Allocate and assemble LUT 30 | // Allocate LUT for (neg) frequency shift 31 | _lut = Buffer(_lut_size); 32 | for (size_t i=0; i<_lut_size; i++) { 33 | _lut[i] = double(1 << Traits::shift) * 34 | std::exp(std::complex(0,-(2*M_PI*i)/_lut_size)); 35 | } 36 | } 37 | 38 | /** Destructor. */ 39 | virtual ~FreqShiftBase() { 40 | _lut.unref(); 41 | } 42 | 43 | /** Returns the sample rate. */ 44 | inline double sampleRate() const { return _Fs; } 45 | /** Sets the sample rate and updates the LUT. */ 46 | virtual void setSampleRate(double Fs) { 47 | _Fs = Fs; _update_lut_incr(); 48 | } 49 | 50 | /** Returns the frequency shift. */ 51 | inline double frequencyShift() const { return _freq_shift; } 52 | /** Sets the frequency shift and updates the LUT. */ 53 | virtual void setFrequencyShift(double F) { 54 | _freq_shift = F; _update_lut_incr(); 55 | } 56 | 57 | /** Performs the frequency shift on a single sample. */ 58 | inline CSScalar applyFrequencyShift(CSScalar value) 59 | { 60 | // If frequency shift is actually 0 -> return 61 | if (0 == _lut_inc) { return value; } 62 | // Get index, idx = (_lut_count/256) 63 | size_t idx = (_lut_count>>8); 64 | // Handle negative frequency shifts 65 | if (0 > _freq_shift) { idx = _lut_size - idx - 1; } 66 | // Apply 67 | value = ((_lut[idx] * value) >> Traits::shift); 68 | // Incement _lut_count 69 | _lut_count += _lut_inc; 70 | // _lut_count modulo (_lut_size*256) 71 | while (_lut_count >= (_lut_size<<8)) { _lut_count -= (_lut_size<<8); } 72 | // Done. 73 | return value; 74 | } 75 | 76 | protected: 77 | /** Updates the multiplier LUT. */ 78 | void _update_lut_incr() { 79 | // Every sample increments the LUT index by lut_inc/256. 80 | // The multiple is needed as ratio between the frequency shift _Fc and the sample rate _Fs 81 | // may not result in an integer increment. By simply flooring _lut_size*_Fc/_Fs, the actual 82 | // down conversion may be much smaller than actual reuqired. Hence, the counter is therefore 83 | // incremented by the integer (256*_lut_size*_Fc/_Fs) and the index is then obtained by 84 | // dividing _lut_count by 256 (right shift 8 bits). 85 | _lut_inc = (_lut_size*(1<<8)*std::abs(_freq_shift))/_Fs; 86 | _lut_count = 0; 87 | } 88 | 89 | protected: 90 | /** The current frequency shift. */ 91 | double _freq_shift; 92 | /** The current sample rate. */ 93 | double _Fs; 94 | /** The LUT increment. */ 95 | size_t _lut_inc; 96 | /** The LUT index counter. */ 97 | size_t _lut_count; 98 | /** The LUT. */ 99 | Buffer _lut; 100 | 101 | protected: 102 | /** The size of the LUT. */ 103 | static const size_t _lut_size = 128; 104 | }; 105 | 106 | 107 | } 108 | 109 | #endif // FREQSHIFT_HH 110 | -------------------------------------------------------------------------------- /src/fsk.hh: -------------------------------------------------------------------------------- 1 | #ifndef __SDR_FSK_HH__ 2 | #define __SDR_FSK_HH__ 3 | 4 | #include "node.hh" 5 | #include "traits.hh" 6 | #include "logger.hh" 7 | 8 | 9 | namespace sdr { 10 | 11 | 12 | /** Implements the basic FSK/AFSK symbol detection. 13 | * This node contains two FIR filters for the detection of the mark and space frequencies. The node 14 | * returns a sequence of symbols (i.e. sub-bits) which need to be processed to obtain a sequenc of 15 | * transmitted bits (i.e. by the @c BitStream node). 16 | * 17 | * @ingroup demods */ 18 | class FSKDetector: public Sink, public Source 19 | { 20 | public: 21 | /** Constructor. 22 | * @param baud Specifies the baud-rate of the signal. 23 | * @param Fmark Specifies the mark frequency in Hz. 24 | * @param Fspace Specifies the space frequency in Hz. */ 25 | FSKDetector(float baud, float Fmark, float Fspace); 26 | 27 | void config(const Config &src_cfg); 28 | void process(const Buffer &buffer, bool allow_overwrite); 29 | 30 | protected: 31 | /** Updates the mark/space FIR filter and returns the sampled symbol. */ 32 | uint8_t _process(int16_t sample); 33 | 34 | protected: 35 | /** Baudrate of the transmission. Needed to compute the filter length of the FIR mark/space 36 | * filters. */ 37 | float _baud; 38 | /** The filter lenght. */ 39 | size_t _corrLen; 40 | /** The current FIR filter LUT index. */ 41 | size_t _lutIdx; 42 | /** Mark "tone" frequency. */ 43 | float _Fmark; 44 | /** Space "tone" frequency. */ 45 | float _Fspace; 46 | /** Mark frequency FIR filter LUT. */ 47 | Buffer< std::complex > _markLUT; 48 | /** Space frequency FIR filter LUT. */ 49 | Buffer< std::complex > _spaceLUT; 50 | /** FIR filter buffer. */ 51 | Buffer< std::complex > _markHist; 52 | /** FIR filter buffer. */ 53 | Buffer< std::complex > _spaceHist; 54 | /** Output buffer. */ 55 | Buffer _buffer; 56 | }; 57 | 58 | 59 | /** Rather trivial node to detect mark/space symbols by the amplitude. 60 | * For low baud rates (i.e. <= 1200 baud) a FSK signal can be "demodulated" using a 61 | * simple FM demodulator. The result will be a series of decaying exponentials. Hence the 62 | * mark/space symbols can be determined by the means of the input amplitude (positive/negative). 63 | * 64 | * This node implements such a simple symbol detection by the means of the amplitude. The node 65 | * returns a sequence of symbols (sub-bits) that need to be processed to obtain the sequence of 66 | * received bits (i.e. @c BitStream). 67 | * 68 | * @ingroup demods */ 69 | template 70 | class ASKDetector: public Sink, public Source 71 | { 72 | public: 73 | /** Constructor. */ 74 | ASKDetector(bool invert=false) 75 | : Sink(), Source(), _invert(invert) 76 | { 77 | // pass... 78 | } 79 | 80 | void config(const Config &src_cfg) { 81 | // Check if config is complete 82 | if (!src_cfg.hasType() || !src_cfg.hasSampleRate()) { return; } 83 | 84 | // Check if buffer type matches 85 | if (Config::typeId() != src_cfg.type()) { 86 | ConfigError err; 87 | err << "Can not configure ASKDetector: Invalid type " << src_cfg.type() 88 | << ", expected " << Config::typeId(); 89 | throw err; 90 | } 91 | 92 | // Allocate output buffer 93 | _buffer = Buffer(src_cfg.bufferSize()); 94 | 95 | LogMessage msg(LOG_DEBUG); 96 | msg << "Config ASKDetector node: " << std::endl 97 | << " threshold: " << 0 << std::endl 98 | << " invert: " << ( _invert ? "yes" : "no" ) << std::endl 99 | << " symbol rate: " << src_cfg.sampleRate() << " Hz"; 100 | Logger::get().log(msg); 101 | 102 | // Forward config. 103 | this->setConfig(Config(Traits::scalarId, src_cfg.sampleRate(), _buffer.size(), 1)); 104 | } 105 | 106 | void process(const Buffer &buffer, bool allow_overwrite) { 107 | for (size_t i=0; i0)^_invert); 109 | } 110 | this->send(_buffer.head(buffer.size()), false); 111 | } 112 | 113 | protected: 114 | /** If true the symbol logic is inverted. */ 115 | bool _invert; 116 | /** The output buffer. */ 117 | Buffer _buffer; 118 | }; 119 | 120 | 121 | /** Decodes a bitstream with the desired baud rate. 122 | * This node implements a simple PLL to syncronize the bit sampling with the transitions 123 | * of the input symbol sequence. */ 124 | class BitStream: public Sink, public Source 125 | { 126 | public: 127 | /** Possible bit decoding modes. */ 128 | typedef enum { 129 | NORMAL, ///< Normal mode (i.e. mark -> 1, space -> 0). 130 | TRANSITION ///< Transition mode (i.e. transition -> 0, no transition -> 1). 131 | } Mode; 132 | 133 | public: 134 | /** Constructor. 135 | * @param baud Specifies the baud-rate of the input signal. 136 | * @param mode Specifies the bit detection mode. */ 137 | BitStream(float baud, Mode mode = TRANSITION); 138 | 139 | void config(const Config &src_cfg); 140 | void process(const Buffer &buffer, bool allow_overwrite); 141 | 142 | protected: 143 | /** The baud rate. */ 144 | float _baud; 145 | /** The bit detection mode. */ 146 | Mode _mode; 147 | /** The approximative bit length in samples. */ 148 | size_t _corrLen; 149 | /** Last received symbols. */ 150 | Buffer _symbols; 151 | /** Insertion index for the next symbol. */ 152 | size_t _symIdx; 153 | /** Sum over all received symbol (encoded as -1 & 1). */ 154 | int32_t _symSum; 155 | /** Last sum over all received symbol (encoded as -1 & 1). */ 156 | int32_t _lastSymSum; 157 | /** Current bit "phase". */ 158 | float _phase; 159 | /** Phase velocity. */ 160 | float _omega; 161 | /** Minimum phase velocity. */ 162 | float _omegaMin; 163 | /** Maximum phase velocity. */ 164 | float _omegaMax; 165 | /** PLL gain. */ 166 | float _pllGain; 167 | /** The last decoded bits (needed for transition mode). */ 168 | uint8_t _lastBits; 169 | /** Output buffer. */ 170 | Buffer _buffer; 171 | }; 172 | 173 | 174 | /** Trivial node to dump a bit-stream to a std::ostream. 175 | * @ingroup sinks */ 176 | class BitDump : public Sink 177 | { 178 | public: 179 | /** Constructor. 180 | * @param stream Specifies the output stream. */ 181 | BitDump(std::ostream &stream); 182 | 183 | void config(const Config &src_cfg); 184 | void process(const Buffer &buffer, bool allow_overwrite); 185 | 186 | protected: 187 | /** The output stream. */ 188 | std::ostream &_stream; 189 | }; 190 | 191 | } 192 | #endif // __SDR_FSK_HH__ 193 | -------------------------------------------------------------------------------- /src/logger.cc: -------------------------------------------------------------------------------- 1 | #include "logger.hh" 2 | 3 | using namespace sdr; 4 | 5 | 6 | /* ********************************************************************************************* * 7 | * LogMessage 8 | * ********************************************************************************************* */ 9 | LogMessage::LogMessage(LogLevel level, const std::string &msg) 10 | : std::stringstream(), _level(level) 11 | { 12 | (*this) << msg; 13 | } 14 | 15 | LogMessage::LogMessage(const LogMessage &other) 16 | : std::stringstream(), _level(other._level) 17 | { 18 | (*this) << other.message(); 19 | } 20 | 21 | LogMessage::~LogMessage() { 22 | // pass... 23 | } 24 | 25 | LogLevel 26 | LogMessage::level() const { 27 | return _level; 28 | } 29 | 30 | 31 | /* ********************************************************************************************* * 32 | * LogHandler 33 | * ********************************************************************************************* */ 34 | LogHandler::LogHandler() { 35 | // pass... 36 | } 37 | 38 | LogHandler::~LogHandler() { 39 | // pass... 40 | } 41 | 42 | 43 | /* ********************************************************************************************* * 44 | * StreamLogHandler 45 | * ********************************************************************************************* */ 46 | StreamLogHandler::StreamLogHandler(std::ostream &stream, LogLevel level) 47 | : LogHandler(), _stream(stream), _level(level) 48 | { 49 | // pass... 50 | } 51 | 52 | StreamLogHandler::~StreamLogHandler() { 53 | // pass... 54 | } 55 | 56 | void 57 | StreamLogHandler::handle(const LogMessage &msg) { 58 | if (msg.level() < _level) { return; } 59 | switch (msg.level()) { 60 | case LOG_DEBUG: _stream << "DEBUG: "; break; 61 | case LOG_INFO: _stream << "INFO: "; break; 62 | case LOG_WARNING: _stream << "WARN: "; break; 63 | case LOG_ERROR: _stream << "ERROR: "; break; 64 | } 65 | _stream << msg.message() << std::endl; 66 | } 67 | 68 | 69 | /* ********************************************************************************************* * 70 | * Logger 71 | * ********************************************************************************************* */ 72 | Logger *Logger::_instance = 0; 73 | 74 | Logger::Logger() 75 | : _handler() 76 | { 77 | // pass... 78 | } 79 | 80 | Logger::~Logger() { 81 | std::list::iterator item = _handler.begin(); 82 | for (; item != _handler.end(); item++) { 83 | delete (*item); 84 | } 85 | _handler.clear(); 86 | } 87 | 88 | Logger & 89 | Logger::get() { 90 | if (0 == _instance) { _instance = new Logger(); } 91 | return *_instance; 92 | } 93 | 94 | void 95 | Logger::addHandler(LogHandler *handler) { 96 | _handler.push_back(handler); 97 | } 98 | 99 | void 100 | Logger::log(const LogMessage &message) { 101 | std::list::iterator item = _handler.begin(); 102 | for (; item != _handler.end(); item++) { 103 | (*item)->handle(message); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/logger.hh: -------------------------------------------------------------------------------- 1 | #ifndef __SDR_LOGGER_HH__ 2 | #define __SDR_LOGGER_HH__ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | 9 | namespace sdr { 10 | 11 | /** Specifies the possible log levels. */ 12 | typedef enum { 13 | LOG_DEBUG = 0, ///< Every thing that may be of interest. 14 | LOG_INFO, ///< Messages about state changes. 15 | LOG_WARNING, ///< Non critical errors (i.e. data loss). 16 | LOG_ERROR ///< Critical errors. 17 | } LogLevel; 18 | 19 | 20 | /** A log message. 21 | * Bundles a message with a level. */ 22 | class LogMessage: public std::stringstream 23 | { 24 | public: 25 | /** Constructor. 26 | * @param level Specified the log-level of the message. 27 | * @param msg An optional message. */ 28 | LogMessage(LogLevel level, const std::string &msg=""); 29 | /** Copy constructor. */ 30 | LogMessage(const LogMessage &other); 31 | /** Destructor. */ 32 | virtual ~LogMessage(); 33 | 34 | /** Returns the level of the message. */ 35 | LogLevel level() const; 36 | /** Returns the message. */ 37 | inline std::string message() const { return this->str(); } 38 | 39 | protected: 40 | /** The level of the message. */ 41 | LogLevel _level; 42 | }; 43 | 44 | 45 | /** Base class of all log-message handlers. */ 46 | class LogHandler 47 | { 48 | protected: 49 | /** Hidden constructor. */ 50 | LogHandler(); 51 | 52 | public: 53 | /** Destructor. */ 54 | virtual ~LogHandler(); 55 | /** Needs to be implemented by sub-classes to handle log messages. */ 56 | virtual void handle(const LogMessage &msg) = 0; 57 | }; 58 | 59 | 60 | 61 | /** Serializes log message into the specified stream. */ 62 | class StreamLogHandler: public LogHandler 63 | { 64 | public: 65 | /** Constructor. 66 | * @param stream Specifies the stream, the messages are serialized into. 67 | * @param level Specifies the minimum log level of the messages being serialized. 68 | */ 69 | StreamLogHandler(std::ostream &stream, LogLevel level); 70 | /** Destructor. */ 71 | virtual ~StreamLogHandler(); 72 | 73 | /** Handles the message. */ 74 | virtual void handle(const LogMessage &msg); 75 | 76 | protected: 77 | /** The output stream. */ 78 | std::ostream &_stream; 79 | /** The minimum log-level. */ 80 | LogLevel _level; 81 | }; 82 | 83 | 84 | 85 | /** The logger class (singleton). */ 86 | class Logger 87 | { 88 | protected: 89 | /** Hidden constructor. Use @c get to obtain an instance. */ 90 | Logger(); 91 | 92 | public: 93 | /** Destructor. */ 94 | virtual ~Logger(); 95 | 96 | /** Returns the singleton instance of the logger. */ 97 | static Logger &get(); 98 | 99 | /** Logs a message. */ 100 | void log(const LogMessage &message); 101 | 102 | /** Adds a message handler. The ownership of the hander is transferred to the logger 103 | * instance. */ 104 | void addHandler(LogHandler *handler); 105 | 106 | protected: 107 | /** The singleton instance. */ 108 | static Logger *_instance; 109 | /** All registered handlers. */ 110 | std::list _handler; 111 | }; 112 | 113 | } 114 | 115 | #endif // __SDR_LOGGER_HH__ 116 | -------------------------------------------------------------------------------- /src/math.hh: -------------------------------------------------------------------------------- 1 | #ifndef __SDR_MATH_HH__ 2 | #define __SDR_MATH_HH__ 3 | 4 | #include 5 | 6 | namespace sdr { 7 | 8 | /** Template prototype for @c fast_atan2. */ 9 | template oScalar fast_atan2(iScalar a, iScalar b); 10 | 11 | /** Implementation of atan2 approximation using integers. */ 12 | template <> inline int16_t fast_atan2(int8_t a, int8_t b) { 13 | const int32_t pi4 = (1<<12); 14 | const int32_t pi34 = 3*(1<<12); 15 | int32_t aabs, angle; 16 | if ((0 == a) && (0 == b)) { return 0; } 17 | aabs = (a >= 0) ? a : -a; 18 | if (b >= 0) { angle = pi4 - pi4*(b-aabs) / (b+aabs); } 19 | else { angle = pi34 - pi4*(b+aabs) / (aabs-b); } 20 | return (a >= 0) ? angle : -angle; 21 | } 22 | 23 | /** Implementation of atan2 approximation using integers. */ 24 | template <> inline int16_t fast_atan2(uint8_t ua, uint8_t ub) { 25 | int8_t a = (int16_t(ua)-(1<<7)); 26 | int8_t b = (int16_t(ub)-(1<<7)); 27 | return fast_atan2(a,b); 28 | } 29 | 30 | /** Implementation of atan2 approximation using integers. */ 31 | template <> inline int16_t fast_atan2(int16_t a, int16_t b) { 32 | const int32_t pi4 = (1<<12); 33 | const int32_t pi34 = 3*(1<<12); 34 | int32_t aabs, angle; 35 | if ((0 == a) && (0 == b)) { return 0; } 36 | aabs = (a >= 0) ? a : -a; 37 | if (b >= 0) { angle = pi4 - pi4*(b-aabs) / (b+aabs); } 38 | else { angle = pi34 - pi4*(b+aabs) / (aabs-b); } 39 | return (a >= 0) ? angle : -angle; 40 | } 41 | 42 | 43 | } 44 | #endif // MATH_HH 45 | -------------------------------------------------------------------------------- /src/node.cc: -------------------------------------------------------------------------------- 1 | #include "node.hh" 2 | 3 | using namespace sdr; 4 | 5 | 6 | /* ********************************************************************************************* * 7 | * Implementation of Config class 8 | * ********************************************************************************************* */ 9 | Config::Config() 10 | : _type(Type_UNDEFINED), _sampleRate(0), _bufferSize(0), _numBuffers(0) 11 | { 12 | // pass... 13 | } 14 | 15 | Config::Config(Type type, double sampleRate, size_t bufferSize, size_t numBuffers) 16 | : _type(type), _sampleRate(sampleRate), _bufferSize(bufferSize), _numBuffers(numBuffers) 17 | { 18 | // pass... 19 | } 20 | 21 | Config::Config(const Config &other) 22 | : _type(other._type), _sampleRate(other._sampleRate), 23 | _bufferSize(other._bufferSize), _numBuffers(other._numBuffers) 24 | { 25 | // pass... 26 | } 27 | 28 | const Config & 29 | Config::operator =(const Config &other) { 30 | _type = other._type; _sampleRate = other._sampleRate; 31 | _bufferSize = other._bufferSize; _numBuffers = other._numBuffers; 32 | return *this; 33 | } 34 | 35 | bool 36 | Config::operator ==(const Config &other) const { 37 | return (other._type == _type) && (other._sampleRate == _sampleRate) && 38 | (other._bufferSize == _bufferSize) && (other._numBuffers == _numBuffers); 39 | } 40 | 41 | 42 | /* ********************************************************************************************* * 43 | * Implementation of SinkBase 44 | * ********************************************************************************************* */ 45 | SinkBase::SinkBase() { 46 | // pass... 47 | } 48 | 49 | SinkBase::~SinkBase() { 50 | // pass... 51 | } 52 | 53 | /* ********************************************************************************************* * 54 | * Implementation of Source class 55 | * ********************************************************************************************* */ 56 | Source::Source() 57 | : _config(), _sinks() 58 | { 59 | // pass.. 60 | } 61 | 62 | Source::~Source() { 63 | // pass... 64 | } 65 | 66 | void 67 | Source::send(const RawBuffer &buffer, bool allow_overwrite) { 68 | std::map::iterator item = _sinks.begin(); 69 | for (; item != _sinks.end(); item++) { 70 | // If connected directly, call directly 71 | if (item->second) { 72 | // The source allows the sink to manipulate the buffer directly, 73 | // iff, the sink is the only one receiving this buffer, the source allows it and the 74 | // connection is direct. 75 | allow_overwrite = allow_overwrite && (1 == _sinks.size()); 76 | // Call sink directly 77 | item->first->handleBuffer(buffer, allow_overwrite); 78 | } else { 79 | // otherwise, queue buffer 80 | allow_overwrite = allow_overwrite && (1 == _sinks.size()); 81 | Queue::get().send(buffer, item->first, allow_overwrite); 82 | } 83 | } 84 | } 85 | 86 | void 87 | Source::connect(SinkBase *sink, bool direct) { 88 | _sinks[sink] = direct; 89 | sink->config(_config); 90 | } 91 | 92 | void 93 | Source::disconnect(SinkBase *sink) { 94 | _sinks.erase(sink); 95 | } 96 | 97 | void 98 | Source::setConfig(const Config &config) { 99 | // If the config did not changed -> skip 100 | if (config == _config) { return; } 101 | // Store config 102 | _config = config; 103 | // Propergate config 104 | propagateConfig(_config); 105 | } 106 | 107 | void 108 | Source::propagateConfig(const Config &config) { 109 | // propagate config to all connected sinks 110 | std::map::iterator item = _sinks.begin(); 111 | for (; item != _sinks.end(); item++) { 112 | item->first->config(_config); 113 | } 114 | } 115 | 116 | Config::Type 117 | Source::type() const { 118 | return _config.type(); 119 | } 120 | 121 | double 122 | Source::sampleRate() const { 123 | return _config.sampleRate(); 124 | } 125 | 126 | void 127 | Source::signalEOS() { 128 | std::list::iterator item = _eos.begin(); 129 | for (; item != _eos.end(); item++) { (**item)(); } 130 | } 131 | 132 | 133 | 134 | /* ********************************************************************************************* * 135 | * Implementation of BlockingSource 136 | * ********************************************************************************************* */ 137 | BlockingSource::BlockingSource(bool parallel, bool connect_idle, bool stop_queue_on_eos) 138 | : Source(), _is_active(false), _is_parallel(parallel) 139 | { 140 | // If the source is not autonomous and shall be triggered by the idle event of the Queue 141 | // -> connect to it. 142 | if (!parallel && connect_idle) { 143 | Queue::get().addIdle(this, &BlockingSource::_nonvirt_idle_cb); 144 | } 145 | // If the EOS signal of the source shall stop the queue 146 | if (stop_queue_on_eos) { this->addEOS(&Queue::get(), &Queue::stop); } 147 | } 148 | 149 | BlockingSource::~BlockingSource() { 150 | if (isActive()) { stop(); } 151 | } 152 | 153 | void 154 | BlockingSource::start() { 155 | if (_is_active) { return; } 156 | if (_is_parallel) { 157 | pthread_create(&_thread, 0, BlockingSource::_pthread_main_wrapper, this); 158 | } 159 | } 160 | 161 | void 162 | BlockingSource::stop() { 163 | if (! _is_active) { return; } 164 | _is_active = false; 165 | if (_is_parallel) { 166 | void *ret_ptr; 167 | pthread_join(_thread, &ret_ptr); 168 | } 169 | } 170 | 171 | void 172 | BlockingSource::_parallel_main() { 173 | while (_is_active && Queue::get().isRunning()) { 174 | this->next(); 175 | } 176 | } 177 | 178 | void 179 | BlockingSource::_nonvirt_idle_cb() { 180 | if (_is_active && Queue::get().isRunning()) { 181 | this->next(); 182 | } 183 | } 184 | 185 | void * 186 | BlockingSource::_pthread_main_wrapper(void *ptr) { 187 | BlockingSource *obj = reinterpret_cast(ptr); 188 | obj->_parallel_main(); 189 | return 0; 190 | } 191 | 192 | 193 | 194 | /* ********************************************************************************************* * 195 | * Implementation of Proxy 196 | * ********************************************************************************************* */ 197 | Proxy::Proxy() 198 | : SinkBase(), Source() 199 | { 200 | // pass... 201 | } 202 | 203 | Proxy::~Proxy() { 204 | // pass... 205 | } 206 | 207 | void 208 | Proxy::config(const Config &src_cfg) { 209 | this->setConfig(src_cfg); 210 | } 211 | 212 | void 213 | Proxy::handleBuffer(const RawBuffer &buffer, bool allow_overwrite) { 214 | this->send(buffer); 215 | } 216 | -------------------------------------------------------------------------------- /src/operators.hh: -------------------------------------------------------------------------------- 1 | #ifndef __SDR_OPERATORS_HH__ 2 | #define __SDR_OPERATORS_HH__ 3 | 4 | #include 5 | #include 6 | 7 | 8 | // Tiny helper functions to handle std::real & std::conj on real integers 9 | namespace std { 10 | inline int16_t conj(int16_t value) { return value; } 11 | inline int16_t real(int16_t value) { return value; } 12 | inline int16_t imag(int16_t value) { return 0; } 13 | } 14 | 15 | 16 | inline std::complex operator*(const double &a, const std::complex &b) { 17 | return std::complex(a*b.real(), a*b.imag()); 18 | } 19 | 20 | inline std::complex operator*(const double &a, const std::complex &b) { 21 | return std::complex(a*b.real(), a*b.imag()); 22 | } 23 | 24 | inline std::complex operator*(const double &a, const std::complex &b) { 25 | return std::complex(a*b.real(), a*b.imag()); 26 | } 27 | 28 | inline std::complex operator*(const double &a, const std::complex &b) { 29 | return std::complex(a*b.real(), a*b.imag()); 30 | } 31 | 32 | inline std::complex operator*(const double &a, const std::complex &b) { 33 | return std::complex(a*b.real(), a*b.imag()); 34 | } 35 | 36 | inline std::complex operator* (const std::complex &a, const std::complex &b) { 37 | return a*std::complex(std::real(b), std::imag(b)); 38 | } 39 | 40 | inline std::complex operator* (const std::complex &a, const std::complex &b) { 41 | return a*std::complex(std::real(b), std::imag(b)); 42 | } 43 | 44 | inline std::complex operator<< (const std::complex &a, int b) { 45 | return std::complex(std::real(a)< operator>> (const std::complex &a, int b) { 49 | return std::complex(std::real(a)>>b, std::imag(a)>>b); 50 | } 51 | 52 | 53 | namespace sdr { 54 | 55 | template 56 | inline oScalar cast(const iScalar &a) { return oScalar(a); } 57 | 58 | template<> 59 | inline std::complex cast >(const int8_t &a) { 60 | return std::complex(std::real(a), std::imag(a)); 61 | } 62 | 63 | template<> 64 | inline std::complex cast, std::complex >(const std::complex &a) { 65 | return std::complex(std::real(a), std::imag(a)); 66 | } 67 | 68 | template<> 69 | inline std::complex cast >(const int16_t &a) { 70 | return std::complex(std::real(a), std::imag(a)); 71 | } 72 | 73 | template<> 74 | inline std::complex cast, std::complex >(const std::complex &a) { 75 | return std::complex(std::real(a), std::imag(a)); 76 | } 77 | 78 | 79 | /** Mulitplication by a power of two. */ 80 | inline uint8_t mul2(uint8_t a, int n) { 81 | if (n < 0) { return a >> -n; } 82 | else { return a << n; } 83 | } 84 | 85 | /** Division by a power of two. */ 86 | inline uint8_t div2(uint8_t a, int n) { 87 | if (n < 0) { return a << -n; } 88 | else { return a >> n; } 89 | } 90 | 91 | /** Mulitplication by a power of two. */ 92 | inline std::complex mul2(const std::complex &a, int n) { 93 | if (n < 0) { 94 | return std::complex(std::real(a)>>(-n), std::imag(a)>>(-n)); 95 | } else { 96 | return std::complex(std::real(a)<<(n), std::imag(a)<<(n)); 97 | } 98 | } 99 | 100 | /** Division by a power of two. */ 101 | inline std::complex div2(const std::complex &a, int n) { 102 | if (n < 0) { 103 | return std::complex(std::real(a)<<(-n), std::imag(a)<<(-n)); 104 | } else { 105 | return std::complex(std::real(a)>>(n), std::imag(a)>>(n)); 106 | } 107 | } 108 | 109 | /** Mulitplication by a power of two. */ 110 | inline int8_t mul2(int8_t a, int n) { 111 | if (n < 0) { return a >> -n; } 112 | else { return a << n; } 113 | } 114 | 115 | /** Mulitplication by a power of two. */ 116 | inline std::complex mul2(const std::complex &a, int n) { 117 | if (n < 0) { 118 | return std::complex(std::real(a)>>(-n), std::imag(a)>>(-n)); 119 | } else { 120 | return std::complex(std::real(a)>>(-n), std::imag(a)>>(-n)); 121 | } 122 | } 123 | 124 | /** Mulitplication by a power of two. */ 125 | inline uint16_t mul2(uint16_t a, int n) { 126 | if (n < 0) { return a >> -n; } 127 | else { return a << n; } 128 | } 129 | 130 | /** Mulitplication by a power of two. */ 131 | inline std::complex mul2(const std::complex &a, int n) { 132 | if (n < 0) { 133 | return std::complex(std::real(a)>>(-n), std::imag(a)>>(-n)); 134 | } else { 135 | return std::complex(std::real(a)>>(-n), std::imag(a)>>(-n)); 136 | } 137 | } 138 | 139 | /** Mulitplication by a power of two. */ 140 | inline int16_t mul2(int16_t a, int n) { 141 | if (n < 0) { return a >> -n; } 142 | else { return a << n; } 143 | } 144 | 145 | /** Mulitplication by a power of two. */ 146 | inline std::complex mul2(const std::complex &a, int n) { 147 | if (n < 0) { 148 | return std::complex(std::real(a)>>(-n), std::imag(a)>>(-n)); 149 | } else { 150 | return std::complex(std::real(a)>>(-n), std::imag(a)>>(-n)); 151 | } 152 | } 153 | 154 | /** Mulitplication by a power of two. */ 155 | inline float mul2(float a, int n) { 156 | if (n < 0) { return a/(1<<(-n)); } 157 | else { return a*(1< mul2(const std::complex &a, int n) { 162 | if (n < 0) { return a/float(1<<(-n)); } 163 | else { return a*float(1< mul2(const std::complex &a, int n) { 168 | if (n < 0) { return a/double(1<<(-n)); } 169 | else { return a*double(1< 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | using namespace sdr; 10 | 11 | 12 | /* ********************************************************************************************* * 13 | * Implementation of Options 14 | * ********************************************************************************************* */ 15 | Options::Options() 16 | : _options() 17 | { 18 | // pass... 19 | } 20 | 21 | bool 22 | Options::has(const char *name) { 23 | return _options.end() != _options.find(name); 24 | } 25 | 26 | const Options::Value & 27 | Options::get(const char *name) { 28 | return _options[name]; 29 | } 30 | 31 | bool 32 | Options::parse(const Definition defs[], int argc, char *argv[], Options &options) 33 | { 34 | // Get number of definitions 35 | const Definition *def = defs; 36 | int nopts = 0; 37 | while (def->name) { nopts++; def++; } 38 | 39 | struct option *getopt_options = (struct option *)malloc((nopts+1)*sizeof(struct option)); 40 | std::map long_options; 41 | std::map short_options; 42 | 43 | def = defs; struct option *getopt_opt = getopt_options; 44 | std::string short_opt_str; 45 | for (int i=0; iname = def->name; 47 | if (FLAG == def->type) { getopt_opt->has_arg = no_argument; } 48 | else { getopt_opt->has_arg = required_argument; } 49 | getopt_opt->flag = 0; 50 | getopt_opt->val = def->short_name; 51 | long_options[def->name] = *def; 52 | if (def->short_name) { 53 | short_options[def->short_name] = *def; 54 | short_opt_str += def->short_name; 55 | if (FLAG != def->type) { short_opt_str += ':'; } 56 | } 57 | } 58 | // add sentinel 59 | getopt_opt->name = 0; getopt_opt->has_arg = 0; getopt_opt->flag = 0; getopt_opt->val = 0; 60 | 61 | // Parse options using getopt 62 | while (true) { 63 | int option_index = 0; 64 | int c = getopt_long(argc, argv, short_opt_str.c_str(), getopt_options, &option_index); 65 | if (-1 == c) { break; } 66 | 67 | // Handle long option: 68 | if (0 == c) { 69 | const char *name = getopt_options[option_index].name; 70 | if (long_options.end() == long_options.find(name)) { 71 | free(getopt_options); 72 | return false; 73 | } 74 | const Definition &opt = long_options[name]; 75 | if (FLAG == opt.type) { options._options[name] = Value(); } 76 | else if (INTEGER == opt.type) { options._options[name] = atol(optarg); } 77 | else if (FLOAT == opt.type) { options._options[name] = atof(optarg); } 78 | else if (ANY == opt.type) { options._options[name] = Value(optarg); } 79 | } else { 80 | if (short_options.end() == short_options.find(c)) { 81 | free(getopt_options); 82 | return false; 83 | } 84 | const Definition &opt = short_options[c]; 85 | const char *name = opt.name; 86 | if (FLAG == opt.type) { options._options[name] = Value(); } 87 | else if (INTEGER == opt.type) { options._options[name] = atol(optarg); } 88 | else if (FLOAT == opt.type) { options._options[name] = atof(optarg); } 89 | else if (ANY == opt.type) { options._options[name] = Value(optarg); } 90 | } 91 | } 92 | 93 | free(getopt_options); 94 | return true; 95 | } 96 | 97 | 98 | void 99 | Options::print_help(std::ostream &stream, const Definition defs[]) 100 | { 101 | for (const Definition *def = defs; def->name; def++) { 102 | stream << "--" << def->name; 103 | if (def->short_name) { stream << ", -" << def->short_name; } 104 | if (INTEGER == def->type) { stream << " INTEGER"; } 105 | else if (FLOAT == def->type) { stream << " FLOAT"; } 106 | else if (ANY == def->type) { stream << " VALUE"; } 107 | stream << std::endl; 108 | if (def->help) { 109 | std::istringstream iss(def->help); 110 | std::string line(" "); 111 | do { 112 | std::string word; iss >> word; 113 | if ((line.length()+word.length()) > 78) { 114 | stream << line << std::endl; 115 | line = " "; 116 | } 117 | line += word + " "; 118 | } while (iss); 119 | if (! line.empty()) { stream << line << std::endl; } 120 | } 121 | stream << std::endl; 122 | } 123 | } 124 | 125 | 126 | 127 | /* ********************************************************************************************* * 128 | * Implementation of Value 129 | * ********************************************************************************************* */ 130 | Options::Value::Value() 131 | : _type(NONE) 132 | { 133 | // pass... 134 | } 135 | 136 | Options::Value::Value(long value) 137 | : _type(INTEGER) 138 | { 139 | _value.as_int = value; 140 | } 141 | 142 | Options::Value::Value(double value) 143 | : _type(FLOAT) 144 | { 145 | _value.as_float = value; 146 | } 147 | 148 | Options::Value::Value(const std::string &value) 149 | : _type(STRING) 150 | { 151 | _value.as_string = strdup(value.c_str()); 152 | } 153 | 154 | Options::Value::~Value() { 155 | if (STRING == _type) { free(_value.as_string); } 156 | } 157 | 158 | Options::Value::Value(const Value &other) 159 | : _type(other._type), _value(other._value) 160 | { 161 | if (STRING == _type) { _value.as_string = strdup(_value.as_string); } 162 | } 163 | 164 | const Options::Value & 165 | Options::Value::operator =(const Value &other) { 166 | if (STRING == _type) { free(_value.as_string); } 167 | _type = other._type; 168 | if (NONE == _type) { /* pass...*/ } 169 | else if (INTEGER == _type) { _value.as_int = other._value.as_int; } 170 | else if (FLOAT == _type) { _value.as_float = other._value.as_float; } 171 | else if (STRING == _type) { _value.as_string = strdup(other._value.as_string); } 172 | return *this; 173 | } 174 | 175 | bool 176 | Options::Value::isNone() const { 177 | return NONE == _type; 178 | } 179 | 180 | bool 181 | Options::Value::isInteger() const { 182 | return INTEGER == _type; 183 | } 184 | 185 | bool 186 | Options::Value::isFloat() const { 187 | return FLOAT == _type; 188 | } 189 | 190 | bool 191 | Options::Value::isString() const { 192 | return STRING == _type; 193 | } 194 | 195 | long 196 | Options::Value::toInteger() const { 197 | return _value.as_int; 198 | } 199 | 200 | double 201 | Options::Value::toFloat() const { 202 | return _value.as_float; 203 | } 204 | 205 | std::string 206 | Options::Value::toString() const { 207 | return _value.as_string; 208 | } 209 | -------------------------------------------------------------------------------- /src/options.hh: -------------------------------------------------------------------------------- 1 | #ifndef __SDR_OPTIONS_HH__ 2 | #define __SDR_OPTIONS_HH__ 3 | 4 | #include 5 | #include 6 | 7 | namespace sdr { 8 | 9 | /** Convenience functions for command line arguments. */ 10 | class Options 11 | { 12 | public: 13 | /** The argument value. */ 14 | class Value { 15 | protected: 16 | /** Value type. */ 17 | typedef enum { 18 | NONE, ///< Empty or invalid value. 19 | INTEGER, ///< An integer (long int). 20 | FLOAT, ///< A floating point number (double). 21 | STRING ///< An ASCII string. 22 | } Type; 23 | 24 | public: 25 | /** Empty constructor. */ 26 | Value(); 27 | /** Integer constructor. */ 28 | Value(long value); 29 | /** Floating point constructor. */ 30 | Value(double value); 31 | /** String constructor. */ 32 | Value(const std::string &value); 33 | /** Copy constructor. */ 34 | Value(const Value &other); 35 | /** Destructor. */ 36 | ~Value(); 37 | /** Assignment. */ 38 | const Value &operator=(const Value &other); 39 | /** Returns @c true if the value is empty. */ 40 | bool isNone() const; 41 | /** Returns @c true if the value is an integer. */ 42 | bool isInteger() const; 43 | /** Returns @c true if the value is a floating point number. */ 44 | bool isFloat() const; 45 | /** Returns @c true if the value is a string. */ 46 | bool isString() const; 47 | /** Turns the value into an integer. */ 48 | long toInteger() const; 49 | /** Turns the value into a floating point number. */ 50 | double toFloat() const; 51 | /** Turns the value into a string. */ 52 | std::string toString() const; 53 | 54 | protected: 55 | /** The type of the value. */ 56 | Type _type; 57 | /** Values. */ 58 | union { 59 | long as_int; 60 | double as_float; 61 | char *as_string; 62 | } _value; 63 | }; 64 | 65 | /** Possible argument types. */ 66 | typedef enum { 67 | FLAG, ///< No argument (flag). 68 | INTEGER, ///< Integer argument. 69 | FLOAT, ///< Floating point argument. 70 | ANY ///< Any argument (string). 71 | } ArgType; 72 | 73 | /** Argument definition. */ 74 | typedef struct { 75 | const char *name; ///< Argument name (long). 76 | char short_name; ///< Argument name (short). 77 | ArgType type; ///< Argument type. 78 | const char *help; ///< Help string. 79 | } Definition; 80 | 81 | /** Parse the given arguments (@c argc, @c argv) using the definitions @c defs and stores 82 | * the results into @c options. Returns @c false on error. */ 83 | static bool parse(const Definition defs[], int argc, char *argv[], Options &options); 84 | /** Serializes a help text derived from given the definitions into @c stream. */ 85 | static void print_help(std::ostream &stream, const Definition defs[]); 86 | 87 | public: 88 | /** Empty constructor. */ 89 | Options(); 90 | /** Returns @c true if the specified option was found (long name). */ 91 | bool has(const char *name); 92 | /** Returns the argument of the specified option (long name). */ 93 | const Value &get(const char *name); 94 | 95 | protected: 96 | /** The table of option names and argument values. */ 97 | std::map _options; 98 | }; 99 | 100 | } 101 | 102 | #endif // __SDR_OPTIONS_HH__ 103 | -------------------------------------------------------------------------------- /src/pocsag.hh: -------------------------------------------------------------------------------- 1 | #ifndef __SDR_POSAG_HH__ 2 | #define __SDR_POSAG_HH__ 3 | 4 | #include "node.hh" 5 | 6 | namespace sdr { 7 | 8 | /** Implements a POCSAG decoder. 9 | * In conjecture with the @c FSKDetector or @c AFSDetector and the @c BitStream nodes, this node 10 | * can be used to receive and process POCSAG (pages) messages. 11 | * 12 | * The POCSAG protocol is defined as followig: 13 | * 14 | * 1. at least 576 bits of alternating value (1 0 1 0 ...) 15 | * 2. a 32-bit sync word (0x7CD215D8) 16 | * 3. 2x8 data words (each 32 bit) 17 | * 4. If data left to send -> continue with step 2 18 | * 19 | * Unused data words are send as 0x7A89C197. Each dataword is either a address word (bit 31 = 0) 20 | * or message word (bit 31 = 1). 21 | * 22 | * In order to process the received message you need to override the @c handleMessages() method 23 | * which gets called once a batch of messages has been received. 24 | * 25 | * @ingroup datanodes */ 26 | class POCSAG: public Sink 27 | { 28 | public: 29 | /** A pocsag message. 30 | * A pocsag message can be either a numeric message (i.e. phone numbers) or a text message. 31 | * The transmitter knows which type a certain receiver expects, hence there is no information 32 | * embedded into the actual message that determines the type. Hence a heuristic needs to be 33 | * used in order to select the message type by the contents of the message. This is done using 34 | * the @c estimateText and @c estimateNumeric functions. They return a weight in favor of one of 35 | * the types. I.e. if estimateText() > estimateNumeric(), the message is likely to be a text 36 | * message. Like any heuristic, this approach may fail. 37 | * The message text is returned by @c asText() and the numeric data is returned by 38 | * @c asNumeric(). Both methods return a @c std::string as the numeric message may also contain 39 | * a very limited set of non-number symbols. */ 40 | class Message { 41 | public: 42 | /** Empty constructor. */ 43 | Message(); 44 | /** Constructor from address and function. */ 45 | Message(uint32_t addr, uint8_t func); 46 | /** Copy constructor. */ 47 | Message(const Message &other); 48 | 49 | /** Assignment operator. */ 50 | Message &operator=(const Message &other); 51 | 52 | /** Retruns @c true if the message is empty (has no address). */ 53 | inline bool isEmpty() const { return _empty; } 54 | /** Returns the address of the message. */ 55 | inline uint32_t address() const { return _address; } 56 | /** Returns the function of the message. */ 57 | inline uint8_t function() const { return _function; } 58 | /** Returns the number of data bits. */ 59 | inline uint32_t bits() const { return _bits; } 60 | 61 | /** Adds some payload from the given POGSAC word. */ 62 | void addPayload(uint32_t word); 63 | 64 | /** Retruns the "likelihood" that the message is a text message (actually a log-likelihood). */ 65 | int estimateText() const; 66 | /** Retruns the "likelihood" that the message is a numeric message 67 | * (actually a log-likelihood). */ 68 | int estimateNumeric() const; 69 | 70 | /** Decodes the message as a text message. */ 71 | std::string asText() const; 72 | /** Decodes the message as a numeric message. */ 73 | std::string asNumeric() const; 74 | /** Dumps the payload. */ 75 | std::string asHex() const; 76 | 77 | protected: 78 | /** The address of the message. */ 79 | uint32_t _address; 80 | /** The function of the message. */ 81 | uint8_t _function; 82 | /** If @c true the message is empty. */ 83 | bool _empty; 84 | /** The number of payload bits in the message. */ 85 | uint32_t _bits; 86 | /** The actual payload. */ 87 | std::vector _payload; 88 | }; 89 | 90 | protected: 91 | /** The possible states of the POGSAC receiver. */ 92 | typedef enum { 93 | WAIT, ///< Wait for a sync word. 94 | RECEIVE, ///< Receive data. 95 | CHECK_CONTINUE ///< Wait for the sync word for continuation. 96 | } State; 97 | 98 | public: 99 | /** Constructor. */ 100 | POCSAG(); 101 | 102 | void config(const Config &src_cfg); 103 | void process(const Buffer &buffer, bool allow_overwrite); 104 | 105 | /** Can be overwritten by any other implementation to process the received messages 106 | * stored in @c _queue. */ 107 | virtual void handleMessages(); 108 | 109 | protected: 110 | /** Process a POGSAC word. */ 111 | void _process_word(uint32_t word); 112 | /** Clear the message. */ 113 | void _reset_message(); 114 | /** Add the (non-empty) message to the queue. */ 115 | void _finish_message(); 116 | 117 | protected: 118 | /** The current state. */ 119 | State _state; 120 | /** The last received bits. */ 121 | uint64_t _bits; 122 | /** The number of received bits. */ 123 | uint8_t _bitcount; 124 | /** The current slot. */ 125 | uint8_t _slot; 126 | /** The current message. */ 127 | Message _message; 128 | /** The completed messages. */ 129 | std::list _queue; 130 | }; 131 | 132 | 133 | /** A simple extention of the @c POCSAG node that prints the received messages to a 134 | * @c std::ostream. 135 | * @ingroup datanodes */ 136 | class POCSAGDump: public POCSAG 137 | { 138 | public: 139 | /** Constructor. 140 | * @param stream Specifies the stream, the received messages are serialized into. */ 141 | POCSAGDump(std::ostream &stream); 142 | 143 | /** Dumps the received messages. */ 144 | void handleMessages(); 145 | 146 | protected: 147 | /** The output stream. */ 148 | std::ostream &_stream; 149 | }; 150 | 151 | 152 | } 153 | 154 | #endif // __SDR_POSAG_HH__ 155 | -------------------------------------------------------------------------------- /src/portaudio.cc: -------------------------------------------------------------------------------- 1 | #include "portaudio.hh" 2 | 3 | using namespace sdr; 4 | 5 | 6 | /* ******************************************************************************************* * 7 | * PortAudio interface 8 | * ******************************************************************************************* */ 9 | void 10 | PortAudio::init() { 11 | Pa_Initialize(); 12 | } 13 | 14 | void 15 | PortAudio::terminate() { 16 | Pa_Terminate(); 17 | } 18 | 19 | int 20 | PortAudio::numDevices() { 21 | return Pa_GetDeviceCount(); 22 | } 23 | 24 | int 25 | PortAudio::defaultInputDevice() { 26 | return Pa_GetDefaultInputDevice(); 27 | } 28 | 29 | int 30 | PortAudio::defaultOutputDevice() { 31 | return Pa_GetDefaultOutputDevice(); 32 | } 33 | 34 | bool 35 | PortAudio::hasInputStream(int idx) { 36 | const PaDeviceInfo *info = Pa_GetDeviceInfo(idx); 37 | return 0 != info->maxInputChannels; 38 | } 39 | 40 | bool 41 | PortAudio::hasOutputStream(int idx) { 42 | const PaDeviceInfo *info = Pa_GetDeviceInfo(idx); 43 | return 0 != info->maxOutputChannels; 44 | } 45 | 46 | std::string 47 | PortAudio::deviceName(int idx) { 48 | const PaDeviceInfo *info = Pa_GetDeviceInfo(idx); 49 | return info->name; 50 | } 51 | 52 | 53 | 54 | /* ******************************************************************************************* * 55 | * PortSink implementation 56 | * ******************************************************************************************* */ 57 | PortSink::PortSink() 58 | : SinkBase(), _stream(0), _frame_size(0) 59 | { 60 | // pass... 61 | } 62 | 63 | PortSink::~PortSink() { 64 | if (0 != _stream) { 65 | Pa_CloseStream(_stream); 66 | } 67 | } 68 | 69 | void 70 | PortSink::config(const Config &src_cfg) { 71 | // Skip if config is incomplete 72 | if (!src_cfg.hasType() || !src_cfg.hasSampleRate() || !src_cfg.bufferSize()) { 73 | return; 74 | } 75 | // Check type: 76 | PaSampleFormat fmt; 77 | size_t nChanels; 78 | switch (src_cfg.type()) { 79 | case Config::Type_u8: 80 | case Config::Type_s8: 81 | fmt = paInt8; 82 | nChanels = 1; 83 | _frame_size = nChanels*1; 84 | break; 85 | case Config::Type_cu8: 86 | case Config::Type_cs8: 87 | fmt = paInt8; 88 | nChanels = 2; 89 | _frame_size = nChanels*1; 90 | break; 91 | case Config::Type_u16: 92 | case Config::Type_s16: 93 | fmt = paInt16; 94 | nChanels = 1; 95 | _frame_size = nChanels*2; 96 | break; 97 | case Config::Type_cu16: 98 | case Config::Type_cs16: 99 | fmt = paInt16; 100 | nChanels = 2; 101 | _frame_size = nChanels*2; 102 | break; 103 | case Config::Type_f32: 104 | fmt = paFloat32; 105 | nChanels = 1; 106 | _frame_size = nChanels*4; 107 | break; 108 | case Config::Type_cf32: 109 | fmt = paFloat32; 110 | nChanels = 2; 111 | _frame_size = nChanels*4; 112 | break; 113 | default: 114 | ConfigError err; 115 | err << "Can not configure PortAudio sink: Unsupported format " << src_cfg.type() 116 | << " must be one of " << Config::Type_u8 << ", " << Config::Type_s8 << ", " 117 | << Config::Type_cu8 << ", " << Config::Type_cs8 << ", " << Config::Type_u16 << ", " 118 | << Config::Type_s16 << ", " << Config::Type_cu16 << ", " << Config::Type_cs16 << ", " 119 | << Config::Type_f32 << " or " << Config::Type_cf32; 120 | throw err; 121 | } 122 | /* Configure PortAudio stream */ 123 | 124 | // Close stream if already open 125 | if (0 != _stream) { Pa_StopStream(_stream); Pa_CloseStream(_stream); } 126 | 127 | PaError err; 128 | // One output chanel (mono), format == float, samplerate and buffer size as provided by src_cfg 129 | if ((err = Pa_OpenDefaultStream(&_stream, 0, nChanels, fmt, 130 | (unsigned int)src_cfg.sampleRate(), src_cfg.bufferSize(), 0, 0))) 131 | { 132 | ConfigError ex; 133 | ex << "Can not configure PortAudio sink: " 134 | << ((const char *)Pa_GetErrorText(err)); 135 | throw ex; 136 | } 137 | 138 | LogMessage msg(LOG_DEBUG); 139 | msg << "Configure PortAudio sink: " << std::endl 140 | << " sample rate " << (int)src_cfg.sampleRate() << std::endl 141 | << " buffer size " << src_cfg.bufferSize() << std::endl 142 | << " format " << src_cfg.type() << std::endl 143 | << " # chanels " << nChanels; 144 | Logger::get().log(msg); 145 | 146 | // start 147 | Pa_StartStream(_stream); 148 | } 149 | 150 | 151 | void 152 | PortSink::handleBuffer(const RawBuffer &buffer, bool allow_overwrite) { 153 | // Bug, check if data was send properly 154 | Pa_WriteStream(_stream, buffer.data(), buffer.bytesLen()/_frame_size); 155 | } 156 | -------------------------------------------------------------------------------- /src/portaudio.hh: -------------------------------------------------------------------------------- 1 | #ifndef __SDR_PORTAUDIO_HH__ 2 | #define __SDR_PORTAUDIO_HH__ 3 | 4 | #include 5 | 6 | #include "buffer.hh" 7 | #include "node.hh" 8 | #include "config.hh" 9 | #include "logger.hh" 10 | 11 | namespace sdr { 12 | 13 | /** "Namespace" to collect all static, PortAudio related functions. */ 14 | class PortAudio { 15 | public: 16 | /** Initializes the PortAudio system, must be called first. */ 17 | static void init(); 18 | /** Shutdown. */ 19 | static void terminate(); 20 | 21 | /** Returns the number of devices available. */ 22 | static int numDevices(); 23 | /** Returns the index of the default input device. */ 24 | static int defaultInputDevice(); 25 | /** Returns the index of the default output device. */ 26 | static int defaultOutputDevice(); 27 | /** Returns @c true of the given device provides an input chanel. */ 28 | static bool hasInputStream(int idx); 29 | /** Returns @c true of the given device provides an output chanel. */ 30 | static bool hasOutputStream(int idx); 31 | /** Returns the device name. */ 32 | static std::string deviceName(int idx); 33 | }; 34 | 35 | 36 | /** PortAudio playback node. 37 | * @ingroup sinks */ 38 | class PortSink: public SinkBase 39 | { 40 | public: 41 | /** Constructor. */ 42 | PortSink(); 43 | /** Destructor. */ 44 | virtual ~PortSink(); 45 | 46 | /** Configures the PortAudio output. */ 47 | virtual void config(const Config &src_cfg); 48 | /** Playback. */ 49 | virtual void handleBuffer(const RawBuffer &buffer, bool allow_overwrite); 50 | 51 | protected: 52 | /** The PortAudio stream. */ 53 | PaStream *_stream; 54 | /** The frame-size. */ 55 | size_t _frame_size; 56 | }; 57 | 58 | 59 | /** PortAudio input stream as a @c Source. 60 | * @ingroup sources */ 61 | template 62 | class PortSource: public Source 63 | { 64 | public: 65 | /** Constructor. */ 66 | PortSource(double sampleRate, size_t bufferSize, int dev=-1): 67 | Source(), _streamIsOpen(false), _stream(0), _sampleRate(sampleRate), _is_real(true) 68 | { 69 | // Allocate buffer 70 | _buffer = Buffer(bufferSize); 71 | _initializeStream(dev); 72 | } 73 | 74 | /** Destructor. */ 75 | virtual ~PortSource() { 76 | // close stream 77 | if (0 != _stream) { Pa_CloseStream(_stream); } 78 | // unref buffer 79 | _buffer.unref(); 80 | } 81 | 82 | /** Reads (blocking) the next buffer from the PortAudio stream. This function can be 83 | * connected to the idle event of the @c Queue. */ 84 | void next() { 85 | /// @todo Signal loss of samples in debug mode. 86 | /// @bug Drop data if output buffer is in use. 87 | Pa_ReadStream(_stream, _buffer.ptr(), _buffer.size()); 88 | this->send(_buffer); 89 | } 90 | 91 | /** Returns the currently selected input device. */ 92 | int deviceIndex() const { return _deviceIndex; } 93 | 94 | /** Selects the input device, throws a @c ConfigError exception if the device can not 95 | * be setup as an input device. Do not call this function, while the @c Queue is running. */ 96 | void setDeviceIndex(int idx=-1) { 97 | _initializeStream(idx); 98 | } 99 | 100 | /** Checks if the given sample rate is supported by the device. */ 101 | bool hasSampleRate(double sampleRate) { 102 | PaStreamParameters params; 103 | params.device = _deviceIndex; 104 | params.channelCount = _is_real ? 1 : 2; 105 | params.sampleFormat = _fmt; 106 | params.hostApiSpecificStreamInfo = 0; 107 | return paFormatIsSupported == Pa_IsFormatSupported(¶ms, 0, sampleRate); 108 | } 109 | 110 | /** Resets the sample rate of the input device. Do not call this function, while the 111 | * @c Queue is running. */ 112 | void setSampleRate(double sampleRate) { 113 | _sampleRate = sampleRate; 114 | _initializeStream(_deviceIndex); 115 | } 116 | 117 | protected: 118 | /** Device setup. */ 119 | void _initializeStream(int idx=-1) { 120 | // Stop and close stream if running 121 | if (_streamIsOpen) { 122 | Pa_StopStream(_stream); Pa_CloseStream(_stream); _streamIsOpen = false; 123 | } 124 | 125 | // Determine input stream format by scalar type 126 | switch (Config::typeId()) { 127 | case Config::Type_f32: 128 | _fmt = paFloat32; 129 | _is_real = true; 130 | break; 131 | case Config::Type_u16: 132 | case Config::Type_s16: 133 | _fmt = paInt16; 134 | _is_real = true; 135 | break; 136 | case Config::Type_cf32: 137 | _fmt = paFloat32; 138 | _is_real = false; 139 | break; 140 | case Config::Type_cu16: 141 | case Config::Type_cs16: 142 | _fmt = paInt16; 143 | _is_real = false; 144 | break; 145 | default: 146 | ConfigError err; 147 | err << "Can not configure PortAudio sink: Unsupported format " << Config::typeId() 148 | << " must be one of " << Config::Type_u16 << ", " << Config::Type_s16 149 | << ", " << Config::Type_f32 << ", " << Config::Type_cu16 << ", " << Config::Type_cs16 150 | << " or " << Config::Type_cf32 << "."; 151 | throw err; 152 | } 153 | 154 | // Assemble config: 155 | int numCh = _is_real ? 1 : 2; 156 | _deviceIndex = idx < 0 ? Pa_GetDefaultInputDevice() : idx; 157 | PaStreamParameters params; 158 | params.device = _deviceIndex; 159 | params.channelCount = numCh; 160 | params.sampleFormat = _fmt; 161 | params.suggestedLatency = Pa_GetDeviceInfo(_deviceIndex)->defaultHighInputLatency; 162 | params.hostApiSpecificStreamInfo = 0; 163 | 164 | // open stream 165 | PaError err = Pa_OpenStream(&_stream, ¶ms, 0, _sampleRate, _buffer.size(), 0, 0, 0); 166 | 167 | // Check for errors 168 | if (0 > err) { 169 | ConfigError err; 170 | err << "Can not open PortAudio input stream!"; throw err; 171 | } 172 | // Mark stream as open 173 | _streamIsOpen = true; 174 | // Start stream 175 | Pa_StartStream(_stream); 176 | 177 | LogMessage msg(LOG_DEBUG); 178 | msg << "Configure PortAudio source: " << std::endl 179 | << " sample rate " << _sampleRate << std::endl 180 | << " buffer size " << _buffer.size() << std::endl 181 | << " format " << Config::typeId() << std::endl 182 | << " num chanels " << numCh; 183 | Logger::get().log(msg); 184 | 185 | // Set config 186 | this->setConfig(Config(Config::typeId(), _sampleRate, _buffer.size(), 1)); 187 | } 188 | 189 | 190 | protected: 191 | /** If true, the PortAudio stream, @c _stream is open. */ 192 | bool _streamIsOpen; 193 | /** The PortAudio input stream. */ 194 | PaStream *_stream; 195 | /** The current format of the PortAudio stream. */ 196 | PaSampleFormat _fmt; 197 | /** The current sample rate. */ 198 | double _sampleRate; 199 | /** The current input device index. */ 200 | int _deviceIndex; 201 | /** If @c true, the input stream is real (1 chanel), otherwise complex (2 chanels). */ 202 | bool _is_real; 203 | /** The output buffer. */ 204 | Buffer _buffer; 205 | }; 206 | 207 | } 208 | 209 | #endif // __SDR_PORTAUDIO_HH__ 210 | -------------------------------------------------------------------------------- /src/psk31.cc: -------------------------------------------------------------------------------- 1 | #include "psk31.hh" 2 | #include "logger.hh" 3 | 4 | using namespace sdr; 5 | 6 | 7 | Varicode::Varicode() 8 | : Sink(), Source() 9 | { 10 | // Fill code table 11 | _code_table[1023] = '!'; _code_table[87] = '.'; _code_table[895] = '\''; 12 | _code_table[367] = '*'; _code_table[495] = '\\'; _code_table[687] = '?'; 13 | _code_table[475] = '$'; _code_table[701] = '@'; _code_table[365] = '_'; 14 | _code_table[735] = '`'; _code_table[351] = '"'; _code_table[493] = '<'; 15 | _code_table[727] = '~'; _code_table[699] = '&'; _code_table[703] = '^'; 16 | _code_table[507] = ']'; _code_table[117] = '-'; _code_table[445] = ';'; 17 | _code_table[1013] = '#'; _code_table[695] = '{'; _code_table[245] = ':'; 18 | _code_table[693] = '}'; _code_table[503] = ')'; _code_table[1749] = '%'; 19 | _code_table[471] = '>'; _code_table[991] = '+'; _code_table[251] = '['; 20 | _code_table[85] = '='; _code_table[943] = '/'; _code_table[29] = '\n'; 21 | _code_table[31] = '\r'; _code_table[747] = '\n'; 22 | // ^- Encode EOT as LN 23 | _code_table[443] = '|'; _code_table[1] = ' '; _code_table[125] = 'A'; 24 | _code_table[235] = 'B'; _code_table[173] = 'C'; _code_table[181] = 'D'; 25 | _code_table[119] = 'E'; _code_table[219] = 'F'; _code_table[253] = 'G'; 26 | _code_table[341] = 'H'; _code_table[127] = 'I'; _code_table[509] = 'J'; 27 | _code_table[381] = 'K'; _code_table[215] = 'L'; _code_table[187] = 'M'; 28 | _code_table[221] = 'N'; _code_table[171] = 'O'; _code_table[213] = 'P'; 29 | _code_table[477] = 'Q'; _code_table[175] = 'R'; _code_table[111] = 'S'; 30 | _code_table[109] = 'T'; _code_table[343] = 'U'; _code_table[437] = 'V'; 31 | _code_table[349] = 'W'; _code_table[373] = 'X'; _code_table[379] = 'Y'; 32 | _code_table[685] = 'Z'; _code_table[11] = 'a'; _code_table[95] = 'b'; 33 | _code_table[47] = 'c'; _code_table[45] = 'd'; _code_table[3] = 'e'; 34 | _code_table[61] = 'f'; _code_table[91] = 'g'; _code_table[43] = 'h'; 35 | _code_table[13] = 'i'; _code_table[491] = 'j'; _code_table[191] = 'k'; 36 | _code_table[27] = 'l'; _code_table[59] = 'm'; _code_table[15] = 'n'; 37 | _code_table[7] = 'o'; _code_table[63] = 'p'; _code_table[447] = 'q'; 38 | _code_table[21] = 'r'; _code_table[23] = 's'; _code_table[5] = 't'; 39 | _code_table[55] = 'u'; _code_table[123] = 'v'; _code_table[107] = 'w'; 40 | _code_table[223] = 'x'; _code_table[93] = 'y'; _code_table[469] = 'z'; 41 | _code_table[183] = '0'; _code_table[189] = '1'; _code_table[237] = '2'; 42 | _code_table[511] = '3'; _code_table[375] = '4'; _code_table[859] = '5'; 43 | _code_table[363] = '6'; _code_table[941] = '7'; _code_table[427] = '8'; 44 | _code_table[951] = '9'; 45 | } 46 | 47 | Varicode::~Varicode() { 48 | // pass... 49 | } 50 | 51 | void 52 | Varicode::config(const Config &src_cfg) { 53 | // Requires type, sample rate & buffer size 54 | if (!src_cfg.hasType() || !src_cfg.hasBufferSize()) { return; } 55 | // Check buffer type 56 | if (Config::typeId() != src_cfg.type()) { 57 | ConfigError err; 58 | err << "Can not configure Varicode: Invalid type " << src_cfg.type() 59 | << ", expected " << Config::typeId(); 60 | throw err; 61 | } 62 | 63 | _value = 0; 64 | _buffer = Buffer(18); 65 | this->setConfig(Config(Traits::scalarId, 0, 18, 1)); 66 | } 67 | 68 | void 69 | Varicode::process(const Buffer &buffer, bool allow_overwrite) { 70 | size_t oidx = 0; 71 | for (size_t i=0; i>= 2; 75 | if (_value) { 76 | std::map::iterator item = _code_table.find(_value); 77 | if (item != _code_table.end()) { 78 | _buffer[oidx++] = item->second; 79 | } else { 80 | LogMessage msg(LOG_INFO); 81 | msg << "Can not decode varicode " << _value << ": Unkown symbol."; 82 | Logger::get().log(msg); 83 | } 84 | } 85 | _value = 0; 86 | } 87 | } 88 | if (oidx) { 89 | this->send(_buffer.head(oidx)); 90 | } 91 | } 92 | 93 | -------------------------------------------------------------------------------- /src/queue.cc: -------------------------------------------------------------------------------- 1 | #include "queue.hh" 2 | #include "node.hh" 3 | #include "config.hh" 4 | #include "logger.hh" 5 | 6 | using namespace sdr; 7 | 8 | 9 | /* ********************************************************************************************* * 10 | * Implementation of Queue class 11 | * ********************************************************************************************* */ 12 | Queue *Queue::_instance = 0; 13 | 14 | Queue & 15 | Queue::get() { 16 | if (0 == Queue::_instance) { 17 | Queue::_instance = new Queue(); 18 | } 19 | return *Queue::_instance; 20 | } 21 | 22 | Queue::Queue() 23 | : _running(false) 24 | { 25 | // allocate queue lock and condition 26 | pthread_mutex_init(&_queue_lock, NULL); 27 | pthread_cond_init(&_queue_cond, NULL); 28 | } 29 | 30 | Queue::~Queue() { 31 | pthread_mutex_destroy(&_queue_lock); 32 | pthread_cond_destroy(&_queue_cond); 33 | } 34 | 35 | void 36 | Queue::send(const RawBuffer &buffer, SinkBase *sink, bool allow_overwrite) { 37 | // Refrerence buffer 38 | pthread_mutex_lock(&_queue_lock); 39 | buffer.ref(); 40 | _queue.push_back(Message(buffer, sink, allow_overwrite)); 41 | pthread_cond_signal(&_queue_cond); 42 | pthread_mutex_unlock(&_queue_lock); 43 | } 44 | 45 | bool 46 | Queue::isStopped() const { 47 | return !_running; 48 | } 49 | 50 | bool 51 | Queue::isRunning() const { 52 | return _running; 53 | } 54 | 55 | 56 | void 57 | Queue::start() { 58 | if (_running) { return; } 59 | pthread_create(&_thread, 0, Queue::__thread_start, this); 60 | } 61 | 62 | 63 | void 64 | Queue::stop() { 65 | _running = false; 66 | pthread_cond_signal(&_queue_cond); 67 | } 68 | 69 | void 70 | Queue::wait() { 71 | // Wait for the queue to quit 72 | void *p; pthread_join(_thread, &p); 73 | 74 | // Clear queue. 75 | std::list::iterator item = _queue.begin(); 76 | for (; item != _queue.end(); item++) { 77 | item->buffer().unref(); 78 | } 79 | _queue.clear(); 80 | } 81 | 82 | 83 | void 84 | Queue::_main() 85 | { 86 | // set state 87 | _running = true; 88 | 89 | Logger::get().log(LogMessage(LOG_DEBUG, "Queue started.")); 90 | 91 | // Call all start signal handlers... 92 | _signalStart(); 93 | 94 | // As long as the queue runs or there are any buffers left to be processed 95 | while (_running || (_queue.size() > 0)) { 96 | // Process all messages in queue 97 | while (_queue.size() > 0) { 98 | // Get a Message from the queue 99 | pthread_mutex_lock(&_queue_lock); 100 | Message msg(_queue.front()); _queue.pop_front(); 101 | pthread_mutex_unlock(&_queue_lock); 102 | // Process message 103 | msg.sink()->handleBuffer(msg.buffer(), msg.allowOverwrite()); 104 | // Mark buffer unused 105 | msg.buffer().unref(); 106 | } 107 | 108 | // If there are no buffer in the queue and the queue is still running: 109 | if ((0 == _queue.size()) && _running) { 110 | // Signal idle handlers 111 | _signalIdle(); 112 | // -> wait until a buffer gets available 113 | pthread_mutex_lock(&_queue_lock); 114 | while( (0 == _queue.size()) && _running ) { pthread_cond_wait(&_queue_cond, &_queue_lock); } 115 | pthread_mutex_unlock(&_queue_lock); 116 | } 117 | } 118 | // Call all stop-signal handlers 119 | _signalStop(); 120 | { 121 | LogMessage msg(LOG_DEBUG, "Queue stopped."); 122 | msg << " Messages left in queue: " << _queue.size(); 123 | Logger::get().log(msg); 124 | } 125 | } 126 | 127 | void 128 | Queue::_signalIdle() { 129 | std::list::iterator item = _idle.begin(); 130 | for (; item != _idle.end(); item++) { 131 | (**item)(); 132 | } 133 | } 134 | 135 | void 136 | Queue::_signalStart() { 137 | std::list::iterator item = _onStart.begin(); 138 | for (; item != _onStart.end(); item++) { 139 | (**item)(); 140 | } 141 | } 142 | 143 | void 144 | Queue::_signalStop() { 145 | std::list::iterator item = _onStop.begin(); 146 | for (; item != _onStop.end(); item++) { 147 | (**item)(); 148 | } 149 | } 150 | 151 | void * 152 | Queue::__thread_start(void *ptr) { 153 | Queue *queue = reinterpret_cast(ptr); 154 | try { 155 | queue->_main(); 156 | } catch (std::exception &err) { 157 | LogMessage msg(LOG_ERROR); 158 | msg << "Caught exception in thread: " << err.what() 159 | << " -> Stop thread."; 160 | Logger::get().log(msg); 161 | } catch (...) { 162 | LogMessage msg(LOG_ERROR); 163 | msg << "Caught (known) exception in thread -> Stop thread."; 164 | Logger::get().log(msg); 165 | } 166 | queue->_running = false; 167 | pthread_exit(0); 168 | return 0; 169 | } 170 | -------------------------------------------------------------------------------- /src/rtlsource.cc: -------------------------------------------------------------------------------- 1 | #include "rtlsource.hh" 2 | #include "logger.hh" 3 | 4 | using namespace sdr; 5 | 6 | 7 | RTLSource::RTLSource(double frequency, double sample_rate, size_t device_idx) 8 | : Source(), _frequency(frequency), _sample_rate(sample_rate), _agc_enabled(true), _gains(), 9 | _buffer_size(131072), _device(0) 10 | { 11 | { 12 | LogMessage msg(LOG_DEBUG); 13 | msg << "Found " << rtlsdr_get_device_count() 14 | << " RTL2832 devices, using No. " << device_idx << "."; 15 | Logger::get().log(msg); 16 | } 17 | 18 | // Open device 19 | if (0 < rtlsdr_get_device_count()) { 20 | if (rtlsdr_open(&_device, device_idx)) { 21 | ConfigError err; err << "Can not open RTL2832 USB device " << device_idx; 22 | throw err; 23 | } 24 | } else { 25 | ConfigError err; err << "Can not open RTL2832 USB device: No device with index " 26 | << device_idx << " found."; 27 | throw err; 28 | } 29 | 30 | { 31 | LogMessage msg(LOG_DEBUG); 32 | msg << "Using device: " << rtlsdr_get_device_name(device_idx); 33 | Logger::get().log(msg); 34 | } 35 | 36 | // Try to set center frequency 37 | if (0 < _frequency) { setFrequency(_frequency); } 38 | // Set sample rate 39 | if (_sample_rate > 0) { setSampleRate(sample_rate); } 40 | 41 | // Query gain factors: 42 | int num_gains = rtlsdr_get_tuner_gains(_device, 0); 43 | if (num_gains > 0) { 44 | int gains[num_gains]; rtlsdr_get_tuner_gains(_device, gains); 45 | _gains.reserve(num_gains); 46 | for (int i=0; isetConfig(Config(Config::typeId< std::complex >(), _sample_rate, _buffer_size, 15)); 57 | } 58 | 59 | 60 | RTLSource::~RTLSource() { 61 | rtlsdr_close(_device); 62 | } 63 | 64 | 65 | void 66 | RTLSource::setFrequency(double frequency) { 67 | rtlsdr_set_center_freq(_device, uint32_t(frequency)); 68 | // Update center frequency 69 | _frequency = double (rtlsdr_get_center_freq(_device)); 70 | } 71 | 72 | void 73 | RTLSource::setFreqCorrection(double ppm) { 74 | rtlsdr_set_freq_correction(_device, ppm); 75 | } 76 | 77 | void 78 | RTLSource::setSampleRate(double sample_rate) { 79 | uint32_t sr = sample_rate; 80 | if (sr < 225001) { sr = 225001; } 81 | else if ((sr>300000) && (sr<900001)) { sr = 900001; } 82 | else if (sr>2400000) { sr = 2400000; } 83 | 84 | rtlsdr_set_sample_rate(_device, sr); 85 | rtlsdr_reset_buffer(_device); 86 | _sample_rate = rtlsdr_get_sample_rate(_device); 87 | 88 | this->setConfig(Config(Config::Type_cu8, _sample_rate, _buffer_size, 15)); 89 | } 90 | 91 | void 92 | RTLSource::enableAGC(bool enable) { 93 | _agc_enabled = enable; 94 | rtlsdr_set_tuner_gain_mode(_device, !_agc_enabled); 95 | rtlsdr_set_agc_mode(_device, _agc_enabled); 96 | } 97 | 98 | void 99 | RTLSource::setGain(double gain) { 100 | if (!_agc_enabled) { 101 | rtlsdr_set_tuner_gain(_device, gain); 102 | } 103 | } 104 | 105 | 106 | void 107 | RTLSource::start() { 108 | pthread_create(&_thread, 0, RTLSource::__rtl_sdr_parallel_main, this); 109 | } 110 | 111 | void 112 | RTLSource::stop() { 113 | void *p; 114 | // stop async reading of RTL device 115 | rtlsdr_cancel_async(_device); 116 | // Wait for blocked thread to exit: 117 | // This is important to ensure that no new messages are added to the 118 | // queue after this function returned. 119 | pthread_join(_thread, &p); 120 | } 121 | 122 | size_t 123 | RTLSource::numDevices() { 124 | return rtlsdr_get_device_count(); 125 | } 126 | 127 | std::string 128 | RTLSource::deviceName(size_t idx) { 129 | return rtlsdr_get_device_name(idx); 130 | } 131 | 132 | 133 | void * 134 | RTLSource::__rtl_sdr_parallel_main(void *ctx) { 135 | RTLSource *self = reinterpret_cast(ctx); 136 | rtlsdr_read_async(self->_device, &RTLSource::__rtl_sdr_callback, self, 137 | 15, self->_buffer_size*2); 138 | return 0; 139 | } 140 | 141 | void 142 | RTLSource::__rtl_sdr_callback(unsigned char *buffer, uint32_t len, void *ctx) { 143 | RTLSource *self = reinterpret_cast(ctx); 144 | self->send(Buffer< std::complex >((std::complex *)buffer, len/2)); 145 | } 146 | -------------------------------------------------------------------------------- /src/rtlsource.hh: -------------------------------------------------------------------------------- 1 | #ifndef __SDR_RTLSOURCE_HH__ 2 | #define __SDR_RTLSOURCE_HH__ 3 | 4 | #include 5 | #include 6 | #include "node.hh" 7 | 8 | 9 | namespace sdr { 10 | 11 | /** Implements a @c uint_8 I/Q source for RTL2832 based TV dongles. 12 | * This source runs in its own thread, hence the user does not need to trigger the reception of 13 | * the next data chunk explicitly. The reception is started by calling the @c start method and 14 | * stopped by calling the @c stop method. 15 | * @ingroup sources */ 16 | class RTLSource: public Source 17 | { 18 | public: 19 | /** Constructor. 20 | * 21 | * By default the gain is set to be automatically determined, this can be changed with the 22 | * @c enableAGC and @c setGain methods. 23 | * 24 | * @param frequency Specifies the tuner frequency. 25 | * @param sample_rate Specifies the sample rate in Hz. 26 | * @param device_idx Specifies the device to be used. The @c numDevices 27 | * and @c deviceName static method can be used to select the desired device index. */ 28 | RTLSource(double frequency, double sample_rate=1e6, size_t device_idx=0); 29 | 30 | /** Destructor. */ 31 | virtual ~RTLSource(); 32 | 33 | /** Returns the tuner frequency. */ 34 | inline double frequency() const { return _frequency; } 35 | /** (Re-) Sets the tuner frequency. */ 36 | void setFrequency(double frequency); 37 | 38 | /** Returns the frequency correction in parts per million (ppm). */ 39 | inline double freqCorrection() const { return rtlsdr_get_freq_correction(_device); } 40 | /** (Re-) Sets the frequency correction in ppm. */ 41 | void setFreqCorrection(double ppm); 42 | 43 | /** Returns the sample rate. */ 44 | inline double sampleRate() const { return _sample_rate; } 45 | /** (Re-) sets the sample rate. This method also triggers the reconfiguration of all 46 | * connected sinks. */ 47 | void setSampleRate(double sample_rate); 48 | 49 | /** Returns true if AGC is enabled. */ 50 | inline bool agcEnabled() const { return _agc_enabled; } 51 | /** Enable/Disable AGC. */ 52 | void enableAGC(bool enable); 53 | 54 | /** Returns the tuner gain. */ 55 | inline double gain() const { return rtlsdr_get_tuner_gain(_device); } 56 | /** (Re-) Sets the tuner gain. Has no effect in AGC mode. */ 57 | void setGain(double gain); 58 | /** Retunrs a vector of supported gain factors. */ 59 | inline const std::vector & gainFactors() const { return _gains; } 60 | 61 | /** Starts the reception. */ 62 | void start(); 63 | /** Stops the reception. */ 64 | void stop(); 65 | 66 | /** Returns the number of compatible devices found. */ 67 | static size_t numDevices(); 68 | /** Returns the name of the specified device. */ 69 | static std::string deviceName(size_t idx); 70 | 71 | protected: 72 | /** Parallel routine to receive some data from the device. */ 73 | static void *__rtl_sdr_parallel_main(void *ctx); 74 | /** Callback to process received data. */ 75 | static void __rtl_sdr_callback(unsigned char *buffer, uint32_t len, void *ctx); 76 | 77 | protected: 78 | /** The current tuner frequency. */ 79 | double _frequency; 80 | /** The current sample rate. */ 81 | double _sample_rate; 82 | /** If true, the AGC is enabled. */ 83 | bool _agc_enabled; 84 | /** A vector of gain factors supported by the device. */ 85 | std::vector _gains; 86 | /** The buffer size. */ 87 | size_t _buffer_size; 88 | /** The RTL2832 device object. */ 89 | rtlsdr_dev_t *_device; 90 | /** The thread object. */ 91 | pthread_t _thread; 92 | }; 93 | 94 | 95 | } 96 | 97 | #endif // __SDR_RTLSOURCE_HH__ 98 | -------------------------------------------------------------------------------- /src/sha1.cc: -------------------------------------------------------------------------------- 1 | /* This code is public-domain - it is based on libcrypt 2 | * placed in the public domain by Wei Dai and other contributors. 3 | */ 4 | // gcc -Wall -DSHA1TEST -o sha1test sha1.c && ./sha1test 5 | 6 | #include "sha1.hh" 7 | #include 8 | #include 9 | 10 | using namespace sdr; 11 | 12 | #ifdef __BIG_ENDIAN__ 13 | # define SHA_BIG_ENDIAN 14 | #elif defined __LITTLE_ENDIAN__ 15 | /* override */ 16 | #elif defined __BYTE_ORDER 17 | # if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ 18 | # define SHA_BIG_ENDIAN 19 | # endif 20 | #else // ! defined __LITTLE_ENDIAN__ 21 | # include // machine/endian.h 22 | # if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ 23 | # define SHA_BIG_ENDIAN 24 | # endif 25 | #endif 26 | 27 | 28 | 29 | 30 | /* code */ 31 | #define SHA1_K0 0x5a827999 32 | #define SHA1_K20 0x6ed9eba1 33 | #define SHA1_K40 0x8f1bbcdc 34 | #define SHA1_K60 0xca62c1d6 35 | 36 | void 37 | sdr::sha1_init(sha1 *s) { 38 | s->state[0] = 0x67452301; 39 | s->state[1] = 0xefcdab89; 40 | s->state[2] = 0x98badcfe; 41 | s->state[3] = 0x10325476; 42 | s->state[4] = 0xc3d2e1f0; 43 | s->byteCount = 0; 44 | s->bufferOffset = 0; 45 | } 46 | 47 | uint32_t 48 | sha1_rol32(uint32_t number, uint8_t bits) { 49 | return ((number << bits) | (number >> (32-bits))); 50 | } 51 | 52 | void 53 | sha1_hashBlock(sha1 *s) { 54 | uint8_t i; 55 | uint32_t a,b,c,d,e,t; 56 | 57 | a=s->state[0]; 58 | b=s->state[1]; 59 | c=s->state[2]; 60 | d=s->state[3]; 61 | e=s->state[4]; 62 | for (i=0; i<80; i++) { 63 | if (i>=16) { 64 | t = s->buffer[(i+13)&15] ^ s->buffer[(i+8)&15] ^ s->buffer[(i+2)&15] ^ s->buffer[i&15]; 65 | s->buffer[i&15] = sha1_rol32(t,1); 66 | } 67 | if (i<20) { 68 | t = (d ^ (b & (c ^ d))) + SHA1_K0; 69 | } else if (i<40) { 70 | t = (b ^ c ^ d) + SHA1_K20; 71 | } else if (i<60) { 72 | t = ((b & c) | (d & (b | c))) + SHA1_K40; 73 | } else { 74 | t = (b ^ c ^ d) + SHA1_K60; 75 | } 76 | t+=sha1_rol32(a,5) + e + s->buffer[i&15]; 77 | e=d; 78 | d=c; 79 | c=sha1_rol32(b,30); 80 | b=a; 81 | a=t; 82 | } 83 | s->state[0] += a; 84 | s->state[1] += b; 85 | s->state[2] += c; 86 | s->state[3] += d; 87 | s->state[4] += e; 88 | } 89 | 90 | void 91 | sha1_addUncounted(sha1 *s, uint8_t data) { 92 | uint8_t * const b = (uint8_t*) s->buffer; 93 | #ifdef SHA_BIG_ENDIAN 94 | b[s->bufferOffset] = data; 95 | #else 96 | b[s->bufferOffset ^ 3] = data; 97 | #endif 98 | s->bufferOffset++; 99 | if (s->bufferOffset == SDR_SHA1_BLOCK_LENGTH) { 100 | sha1_hashBlock(s); 101 | s->bufferOffset = 0; 102 | } 103 | } 104 | 105 | void 106 | sdr::sha1_writebyte(sha1 *s, uint8_t data) { 107 | ++s->byteCount; 108 | sha1_addUncounted(s, data); 109 | } 110 | 111 | void 112 | sdr::sha1_write(sha1 *s, const char *data, size_t len) { 113 | for (;len--;) sha1_writebyte(s, (uint8_t) *data++); 114 | } 115 | 116 | void 117 | sha1_pad(sha1 *s) { 118 | // Implement SHA-1 padding (fips180-2 §5.1.1) 119 | 120 | // Pad with 0x80 followed by 0x00 until the end of the block 121 | sha1_addUncounted(s, 0x80); 122 | while (s->bufferOffset != 56) sha1_addUncounted(s, 0x00); 123 | 124 | // Append length in the last 8 bytes 125 | sha1_addUncounted(s, 0); // We're only using 32 bit lengths 126 | sha1_addUncounted(s, 0); // But SHA-1 supports 64 bit lengths 127 | sha1_addUncounted(s, 0); // So zero pad the top bits 128 | sha1_addUncounted(s, s->byteCount >> 29); // Shifting to multiply by 8 129 | sha1_addUncounted(s, s->byteCount >> 21); // as SHA-1 supports bitstreams as well as 130 | sha1_addUncounted(s, s->byteCount >> 13); // byte. 131 | sha1_addUncounted(s, s->byteCount >> 5); 132 | sha1_addUncounted(s, s->byteCount << 3); 133 | } 134 | 135 | uint8_t* 136 | sdr::sha1_result(sha1 *s) { 137 | // Pad to complete the last block 138 | sha1_pad(s); 139 | 140 | #ifndef SHA_BIG_ENDIAN 141 | // Swap byte order back 142 | int i; 143 | for (i=0; i<5; i++) { 144 | s->state[i]= 145 | (((s->state[i])<<24)& 0xff000000) 146 | | (((s->state[i])<<8) & 0x00ff0000) 147 | | (((s->state[i])>>8) & 0x0000ff00) 148 | | (((s->state[i])>>24)& 0x000000ff); 149 | } 150 | #endif 151 | 152 | // Return pointer to hash (20 characters) 153 | return (uint8_t*) s->state; 154 | } 155 | 156 | #define HMAC_IPAD 0x36 157 | #define HMAC_OPAD 0x5c 158 | 159 | void 160 | sdr::sha1_initHmac(sha1 *s, const uint8_t* key, int keyLength) { 161 | uint8_t i; 162 | memset(s->keyBuffer, 0,SDR_SHA1_BLOCK_LENGTH); 163 | if (keyLength >SDR_SHA1_BLOCK_LENGTH) { 164 | // Hash long keys 165 | sha1_init(s); 166 | for (;keyLength--;) sha1_writebyte(s, *key++); 167 | memcpy(s->keyBuffer, sha1_result(s),SDR_SHA1_HASH_LENGTH); 168 | } else { 169 | // Block length keys are used as is 170 | memcpy(s->keyBuffer, key, keyLength); 171 | } 172 | // Start inner hash 173 | sha1_init(s); 174 | for (i=0; ikeyBuffer[i] ^ HMAC_IPAD); 176 | } 177 | } 178 | 179 | uint8_t* 180 | sdr::sha1_resultHmac(sha1 *s) { 181 | uint8_t i; 182 | // Complete inner hash 183 | memcpy(s->innerHash,sha1_result(s), SDR_SHA1_HASH_LENGTH); 184 | // Calculate outer hash 185 | sha1_init(s); 186 | for (i=0; ikeyBuffer[i] ^ HMAC_OPAD); 187 | for (i=0; iinnerHash[i]); 188 | return sha1_result(s); 189 | } 190 | -------------------------------------------------------------------------------- /src/sha1.hh: -------------------------------------------------------------------------------- 1 | #ifndef __SDR_SHA1_HH__ 2 | #define __SDR_SHA1_HH__ 3 | 4 | #include 5 | #include 6 | 7 | namespace sdr { 8 | 9 | /* header */ 10 | 11 | #define SDR_SHA1_HASH_LENGTH 20 12 | #define SDR_SHA1_BLOCK_LENGTH 64 13 | 14 | typedef struct { 15 | uint32_t buffer[SDR_SHA1_BLOCK_LENGTH/4]; 16 | uint32_t state[SDR_SHA1_HASH_LENGTH/4]; 17 | uint32_t byteCount; 18 | uint8_t bufferOffset; 19 | uint8_t keyBuffer[SDR_SHA1_BLOCK_LENGTH]; 20 | uint8_t innerHash[SDR_SHA1_HASH_LENGTH]; 21 | } sha1; 22 | 23 | /** 24 | */ 25 | void sha1_init(sha1 *s); 26 | /** 27 | */ 28 | void sha1_writebyte(sha1 *s, uint8_t data); 29 | /** 30 | */ 31 | void sha1_write(sha1 *s, const char *data, size_t len); 32 | /** 33 | */ 34 | uint8_t* sha1_result(sha1 *s); 35 | /** 36 | */ 37 | void sha1_initHmac(sha1 *s, const uint8_t* key, int keyLength); 38 | /** 39 | */ 40 | uint8_t* sha1_resultHmac(sha1 *s); 41 | 42 | } 43 | 44 | #endif // __SDR_SHA1_HH__ 45 | -------------------------------------------------------------------------------- /src/siggen.hh: -------------------------------------------------------------------------------- 1 | #ifndef __SDR_SIGGEN_HH__ 2 | #define __SDR_SIGGEN_HH__ 3 | 4 | #include "node.hh" 5 | 6 | 7 | namespace sdr { 8 | 9 | /** Arbitrary function generator. 10 | * @ingroup sources */ 11 | template 12 | class SigGen: public Source 13 | { 14 | public: 15 | /** Constructs the function generator. */ 16 | SigGen(double samplerate, size_t buffersize, double tmax=-1) 17 | : Source(), _sampleRate(samplerate), _dt(1./_sampleRate), _t(0), _tMax(tmax), _scale(1), 18 | _bufferSize(buffersize), _buffer(buffersize) 19 | { 20 | switch (Config::typeId()) { 21 | case Config::Type_u8: 22 | case Config::Type_s8: 23 | case Config::Type_cu8: 24 | case Config::Type_cs8: 25 | _scale = 1<<6; break; 26 | case Config::Type_u16: 27 | case Config::Type_s16: 28 | case Config::Type_cu16: 29 | case Config::Type_cs16: 30 | _scale = 1<<14; break; 31 | default: 32 | _scale = 1; break; 33 | } 34 | 35 | this->setConfig(Config(Config::typeId(), samplerate, buffersize, 1)); 36 | } 37 | 38 | /** Destructor. */ 39 | virtual ~SigGen() {} 40 | 41 | /** Computes the next buffer. This function can be connected to the idle signal of the @c Queue. */ 42 | void next() { 43 | // Stop processing once the max time elapsed 44 | if ((_tMax>0) && (_t >= _tMax)) { Queue::get().stop(); return; } 45 | // Assemble signal 46 | for (size_t i=0; i<_bufferSize; i++) { 47 | _buffer[i] = 0; 48 | if (_signals.size() > 0) { 49 | std::list< std::vector >::iterator item = _signals.begin(); 50 | for (; item != _signals.end(); item++) { 51 | _buffer[i] += (_scale*((*item)[1] * sin(2*M_PI*(*item)[0]*_t + (*item)[2]))/_signals.size()); 52 | } 53 | } 54 | _t += _dt; 55 | } 56 | // Send buffer 57 | this->send(_buffer); 58 | } 59 | 60 | /** Add a sine function to the function generator. */ 61 | void addSine(double freq, double ampl=1, double phase=0) { 62 | std::vector tmp(3); tmp[0] = freq; tmp[1] = ampl; tmp[2] = phase; 63 | _signals.push_back(tmp); 64 | } 65 | 66 | protected: 67 | /** The sample rate of the function generator. */ 68 | double _sampleRate; 69 | /** The sample period. */ 70 | double _dt; 71 | /** The current time. */ 72 | double _t; 73 | /** The maximum time. */ 74 | double _tMax; 75 | /** The scaling of the signal. */ 76 | double _scale; 77 | /** A list of functions. */ 78 | std::list< std::vector > _signals; 79 | /** The size of the output buffer. */ 80 | size_t _bufferSize; 81 | /** The output buffer. */ 82 | Buffer _buffer; 83 | }; 84 | 85 | 86 | 87 | /** Arbitrary function generator. 88 | * @ingroup sources */ 89 | template 90 | class IQSigGen: public Source 91 | { 92 | public: 93 | /** Constructs the function generator. */ 94 | IQSigGen(double samplerate, size_t buffersize, double tmax=-1) 95 | : Source(), _sampleRate(samplerate), _dt(1./_sampleRate), _t(0), _tMax(tmax), _scale(1), 96 | _bufferSize(buffersize), _buffer(buffersize) 97 | { 98 | switch (Config::typeId()) { 99 | case Config::Type_cu8: 100 | case Config::Type_cs8: 101 | _scale = 1<<6; break; 102 | case Config::Type_cu16: 103 | case Config::Type_cs16: 104 | _scale = 1<<14; break; 105 | default: 106 | _scale = 1; break; 107 | } 108 | 109 | this->setConfig(Config(Config::typeId< std::complex >(), samplerate, buffersize, 1)); 110 | } 111 | 112 | /** Destructor. */ 113 | virtual ~IQSigGen() {} 114 | 115 | /** Computes the next buffer. This function can be connected to the idle signal of the @c Queue. */ 116 | void next() { 117 | // Stop processing once the max time elapsed 118 | if ((_tMax>0) && (_t >= _tMax)) { Queue::get().stop(); return; } 119 | // Assemble signal 120 | for (size_t i=0; i<_bufferSize; i++) { 121 | _buffer[i] = 0; 122 | if (_signals.size() > 0) { 123 | std::list< std::vector >::iterator item = _signals.begin(); 124 | for (; item != _signals.end(); item++) { 125 | _buffer[i] += (_scale*((*item)[1] * std::exp(std::complex(0, 2*M_PI*(*item)[0]*_t + (*item)[2])))/double(_signals.size())); 126 | } 127 | } 128 | _t += _dt; 129 | } 130 | // Send buffer 131 | this->send(_buffer); 132 | } 133 | 134 | /** Add a sine function to the function generator. */ 135 | void addSine(double freq, double ampl=1, double phase=0) { 136 | std::vector tmp(3); tmp[0] = freq; tmp[1] = ampl; tmp[2] = phase; 137 | _signals.push_back(tmp); 138 | } 139 | 140 | protected: 141 | /** The sample rate of the function generator. */ 142 | double _sampleRate; 143 | /** The sample period. */ 144 | double _dt; 145 | /** The current time. */ 146 | double _t; 147 | /** The maximum time. */ 148 | double _tMax; 149 | /** The scaling of the signal. */ 150 | double _scale; 151 | /** A list of functions. */ 152 | std::list< std::vector > _signals; 153 | /** The size of the output buffer. */ 154 | size_t _bufferSize; 155 | /** The output buffer. */ 156 | Buffer< std::complex > _buffer; 157 | }; 158 | 159 | } 160 | 161 | 162 | #endif // __SDR_SIGGEN_HH__ 163 | -------------------------------------------------------------------------------- /src/streamsource.hh: -------------------------------------------------------------------------------- 1 | #ifndef __SDR_STREAMSOURCE_HH__ 2 | #define __SDR_STREAMSOURCE_HH__ 3 | 4 | #include 5 | #include "node.hh" 6 | #include "queue.hh" 7 | 8 | namespace sdr { 9 | 10 | } 11 | 12 | #endif // __SDR_STREAMSOURCE_HH__ 13 | -------------------------------------------------------------------------------- /src/traits.cc: -------------------------------------------------------------------------------- 1 | #include "traits.hh" 2 | 3 | using namespace sdr; 4 | 5 | 6 | const float Traits::scale = 127; 7 | const size_t Traits::shift = 8; 8 | const float Traits< std::complex >::scale = 127; 9 | const size_t Traits< std::complex >::shift = 8; 10 | 11 | const float Traits::scale = 127; 12 | const size_t Traits::shift = 8; 13 | const float Traits< std::complex >::scale = 127; 14 | const size_t Traits< std::complex >::shift = 8; 15 | 16 | const float Traits::scale = 32767; 17 | const size_t Traits::shift = 16; 18 | const float Traits< std::complex >::scale = 32767; 19 | const size_t Traits< std::complex >::shift = 16; 20 | 21 | const float Traits::scale = 32767; 22 | const size_t Traits::shift = 16; 23 | const float Traits< std::complex >::scale = 32767; 24 | const size_t Traits< std::complex >::shift = 16; 25 | 26 | const float Traits::scale = 1; 27 | const size_t Traits::shift = 0; 28 | const float Traits< std::complex >::scale = 1; 29 | const size_t Traits< std::complex >::shift = 0; 30 | 31 | const float Traits::scale = 1; 32 | const size_t Traits::shift = 0; 33 | const float Traits< std::complex >::scale = 1; 34 | const size_t Traits< std::complex >::shift = 0; 35 | 36 | -------------------------------------------------------------------------------- /src/utils.cc: -------------------------------------------------------------------------------- 1 | #include "utils.hh" 2 | 3 | using namespace sdr; 4 | 5 | 6 | /* ********************************************************************************************* * 7 | * Implementation of UnsignedToSinged 8 | * ********************************************************************************************* */ 9 | UnsignedToSigned::UnsignedToSigned(float scale) 10 | : SinkBase(), Source(), _scale(scale) 11 | { 12 | // pass... 13 | } 14 | 15 | UnsignedToSigned::~UnsignedToSigned() { 16 | // pass... 17 | } 18 | 19 | void 20 | UnsignedToSigned::config(const Config &src_cfg) { 21 | // Requires type 22 | if (!src_cfg.hasType()) { return; } 23 | 24 | size_t scalar_size; 25 | Config::Type out_type; 26 | // Requires a unsigned integer or complex unsigned integer type as input 27 | switch (src_cfg.type()) { 28 | case Config::Type_u8: 29 | scalar_size = 1; out_type = Config::Type_s8; 30 | _process = &UnsignedToSigned::_process_int8; 31 | break; 32 | case Config::Type_cu8: 33 | scalar_size = 2; out_type = Config::Type_cs8; 34 | _process = &UnsignedToSigned::_process_int8; 35 | break; 36 | case Config::Type_u16: 37 | scalar_size = 2; out_type = Config::Type_s16; 38 | _process = &UnsignedToSigned::_process_int16; 39 | break; 40 | case Config::Type_cu16: 41 | scalar_size = 4; out_type = Config::Type_cs16; 42 | _process = &UnsignedToSigned::_process_int16; 43 | break; 44 | 45 | default: { 46 | ConfigError err; 47 | err << "Can not configure Unsigned2Signed node: Invalid input type " << src_cfg.type() 48 | << ", expected " << Config::Type_u8 << ", " << Config::Type_cu8 << ", " << Config::Type_u16 49 | << " or " << Config::Type_cu8; 50 | throw err; 51 | } 52 | } 53 | 54 | // unreference buffer if non-empty 55 | if (!_buffer.isEmpty()) { _buffer.unref(); } 56 | // Allocate buffer 57 | _buffer = RawBuffer(scalar_size*src_cfg.bufferSize()); 58 | // Propergate config 59 | this->setConfig(Config( 60 | out_type, src_cfg.sampleRate(), src_cfg.bufferSize(), 1)); 61 | } 62 | 63 | void 64 | UnsignedToSigned::handleBuffer(const RawBuffer &buffer, bool allow_overwrite) { 65 | if (allow_overwrite) { 66 | (this->*_process)(buffer, buffer); 67 | } 68 | else if (_buffer.isUnused()) { 69 | (this->*_process)(buffer, _buffer); 70 | } else { 71 | #ifdef SDR_DEBUG 72 | std::cerr << "Unsigned2Signed: Drop buffer: Output buffer still in use." << std::endl; 73 | #endif 74 | } 75 | } 76 | 77 | void 78 | UnsignedToSigned::_process_int8(const RawBuffer &in, const RawBuffer &out) { 79 | size_t num = in.bytesLen(); 80 | uint8_t *in_ptr = (uint8_t *) in.data(); 81 | int8_t *out_ptr = (int8_t *) out.data(); 82 | //std::cerr << "UnsingedToSigned: Process " << Buffer(in) << std::endl; 83 | for (size_t i=0; i " << Buffer(RawBuffer(out, 0, num)) << std::endl; 87 | this->send(RawBuffer(out, 0, num), true); 88 | } 89 | 90 | void 91 | UnsignedToSigned::_process_int16(const RawBuffer &in, const RawBuffer &out) { 92 | size_t num = in.bytesLen()/2; 93 | uint16_t *in_ptr = (uint16_t *) in.data(); 94 | int16_t *out_ptr = (int16_t *) out.data(); 95 | for (size_t i=0; isend(RawBuffer(out, 0, num), true); 99 | } 100 | 101 | 102 | 103 | /* ********************************************************************************************* * 104 | * Implementation of SignedToUnsinged 105 | * ********************************************************************************************* */ 106 | SignedToUnsigned::SignedToUnsigned() 107 | : SinkBase(), Source() 108 | { 109 | // pass... 110 | } 111 | 112 | SignedToUnsigned::~SignedToUnsigned() { 113 | // pass... 114 | } 115 | 116 | void 117 | SignedToUnsigned::config(const Config &src_cfg) { 118 | // Requires type 119 | if (!src_cfg.hasType()) { return; } 120 | 121 | size_t scalar_size; 122 | Config::Type out_type; 123 | // Requires a unsigned integer or complex unsigned integer type as input 124 | switch (src_cfg.type()) { 125 | case Config::Type_s8: 126 | scalar_size = 1; out_type = Config::Type_u8; 127 | _process = &SignedToUnsigned::_process_int8; 128 | break; 129 | case Config::Type_cs8: 130 | scalar_size = 2; out_type = Config::Type_cu8; 131 | _process = &SignedToUnsigned::_process_int8; 132 | break; 133 | case Config::Type_s16: 134 | scalar_size = 2; out_type = Config::Type_u16; 135 | _process = &SignedToUnsigned::_process_int16; 136 | break; 137 | case Config::Type_cs16: 138 | scalar_size = 4; out_type = Config::Type_cu16; 139 | _process = &SignedToUnsigned::_process_int16; 140 | break; 141 | 142 | default: { 143 | ConfigError err; 144 | err << "Can not configure SignedToUnsigned node: Invalid input type " << src_cfg.type() 145 | << ", expected " << Config::Type_s8 << ", " << Config::Type_cs8 << ", " << Config::Type_s16 146 | << " or " << Config::Type_cs8; 147 | throw err; 148 | } 149 | } 150 | 151 | // Allocate buffer 152 | _buffer = RawBuffer(scalar_size*src_cfg.bufferSize()); 153 | // Propergate config 154 | this->setConfig(Config(out_type, src_cfg.sampleRate(), src_cfg.bufferSize(), 1)); 155 | } 156 | 157 | void 158 | SignedToUnsigned::handleBuffer(const RawBuffer &buffer, bool allow_overwrite) { 159 | if (allow_overwrite) { 160 | (this->*_process)(buffer, buffer); 161 | } 162 | else if (_buffer.isUnused()) { 163 | (this->*_process)(buffer, _buffer); 164 | } else { 165 | #ifdef SDR_DEBUG 166 | std::cerr << "SignedToUnsigned: Drop buffer: Output buffer still in use." << std::endl; 167 | #endif 168 | } 169 | } 170 | 171 | void 172 | SignedToUnsigned::_process_int8(const RawBuffer &in, const RawBuffer &out) { 173 | size_t num = in.bytesLen(); 174 | for (size_t i=0; isend(RawBuffer(out, 0, num), true); 178 | } 179 | 180 | void 181 | SignedToUnsigned::_process_int16(const RawBuffer &in, const RawBuffer &out) { 182 | size_t num = in.bytesLen()/2; 183 | for (size_t i=0; isend(RawBuffer(out, 0, num), true); 187 | } 188 | 189 | 190 | /* ********************************************************************************************* * 191 | * Implementation of TextDump 192 | * ********************************************************************************************* */ 193 | TextDump::TextDump(std::ostream &stream) 194 | : Sink(), _stream(stream) 195 | { 196 | // pass... 197 | } 198 | 199 | void 200 | TextDump::config(const Config &src_cfg) { 201 | // Requires type 202 | if (!src_cfg.hasType()) { return; } 203 | if (src_cfg.type() != Traits::scalarId) { 204 | ConfigError err; 205 | err << "Can not configure TextDump node: Invalid input type " << src_cfg.type() 206 | << ", expected " << Config::Type_u8 << "."; 207 | throw err; 208 | } 209 | // done 210 | } 211 | 212 | void 213 | TextDump::process(const Buffer &buffer, bool allow_overwrite) { 214 | for (size_t i=0; i 5 | 6 | 7 | using namespace sdr; 8 | 9 | WavSource::WavSource(size_t buffer_size) 10 | : Source(), _file(), _buffer(), _buffer_size(buffer_size), 11 | _frame_count(0), _type(Config::Type_UNDEFINED), _sample_rate(0), _frames_left(0) 12 | { 13 | // pass.. 14 | } 15 | 16 | WavSource::WavSource(const std::string &filename, size_t buffer_size) 17 | : Source(), _file(), _buffer(), _buffer_size(buffer_size), 18 | _frame_count(0), _type(Config::Type_UNDEFINED), _sample_rate(0), _frames_left(0) 19 | { 20 | open(filename); 21 | } 22 | 23 | WavSource::~WavSource() { 24 | _file.close(); 25 | } 26 | 27 | bool 28 | WavSource::isOpen() const { 29 | return _file.is_open(); 30 | } 31 | 32 | void 33 | WavSource::open(const std::string &filename) 34 | { 35 | if (_file.is_open()) { _file.close(); } 36 | _file.open(filename.c_str(), std::ios_base::in); 37 | if (! _file.is_open()) { return; } 38 | 39 | // Read header and configure source 40 | char str[5]; str[4] = 0; 41 | uint16_t val2; uint32_t val4; 42 | uint16_t n_chanels; 43 | uint32_t sample_rate; 44 | uint16_t block_align, bits_per_sample; 45 | uint32_t chunk_offset, chunk_size; 46 | 47 | /* 48 | * Read Header 49 | */ 50 | chunk_offset = 0; 51 | _file.read(str, 4); 52 | if (0 != strncmp(str, "RIFF", 4)) { 53 | RuntimeError err; 54 | err << "File '" << filename << "' is not a WAV file."; 55 | throw err; 56 | } 57 | 58 | _file.read((char *)&chunk_size, 4); // Read file-size (unused) 59 | _file.read(str, 4); // Read "WAVE" (unused) 60 | if (0 != strncmp(str, "WAVE", 4)) { 61 | RuntimeError err; 62 | err << "File '" << filename << "' is not a WAV file."; 63 | throw err; 64 | } 65 | 66 | 67 | /* 68 | * Read Format part 69 | */ 70 | chunk_offset = 12; 71 | _file.read(str, 4); // Read "fmt " (unused) 72 | if (0 != strncmp(str, "fmt ", 4)) { 73 | RuntimeError err; 74 | err << "'File 'fmt' header missing in file " << filename << "' @" << _file.tellg(); 75 | throw err; 76 | } 77 | _file.read((char *)&chunk_size, 4); // Subheader size (unused) 78 | 79 | _file.read((char *)&val2, 2); // Format 80 | if (1 != val2) { 81 | RuntimeError err; 82 | err << "Unsupported WAV data format: " << val2 83 | << " of file " << filename << ". Expected " << 1; 84 | throw err; 85 | } 86 | 87 | _file.read((char *)&n_chanels, 2); // Read # chanels 88 | if ((1 != n_chanels) && (2 != n_chanels)) { 89 | RuntimeError err; 90 | err << "Unsupported number of chanels: " << n_chanels 91 | << " of file " << filename << ". Expected 1 or 2."; 92 | throw err; 93 | } 94 | 95 | _file.read((char *)&sample_rate, 4); // Read sample-rate 96 | _file.read((char *)&val4, 4); // Read byte-rate (unused) 97 | _file.read((char *)&block_align, 2); 98 | _file.read((char *)&bits_per_sample, 2); 99 | 100 | // Check sample format 101 | if ((16 != bits_per_sample) && (8 != bits_per_sample)){ 102 | RuntimeError err; 103 | err << "Unsupported sample format: " << bits_per_sample 104 | << "b of file " << filename << ". Expected 16b or 8b."; 105 | throw err; 106 | } 107 | if (block_align != n_chanels*(bits_per_sample/8)) { 108 | RuntimeError err; 109 | err << "Unsupported alignment: " << block_align 110 | << "byte of file " << filename << ". Expected " << (bits_per_sample/8) << "byte."; 111 | throw err; 112 | } 113 | // Seek to end of header 114 | chunk_offset += 8+chunk_size; 115 | _file.seekg(chunk_offset); 116 | 117 | // Search for data chunk 118 | _file.read(str, 4); 119 | while ((0 != strncmp(str, "data", 4)) || _file.eof()) { 120 | // read chunk size 121 | _file.read((char*)&chunk_size, 4); 122 | chunk_offset += (8+chunk_size); 123 | _file.seekg(chunk_offset); 124 | _file.read(str, 4); 125 | } 126 | if (_file.eof()) { 127 | RuntimeError err; 128 | err << "WAV file '" << filename << "' contains no 'data' chunk."; 129 | throw err; 130 | } 131 | 132 | /* 133 | * Read data part. 134 | */ 135 | _file.read((char *)&chunk_size, 4); // read frame count 136 | 137 | // Configure source 138 | _frame_count = chunk_size/(n_chanels*(bits_per_sample/8)); 139 | if ((1 == n_chanels) && (8 == bits_per_sample)) { _type = Config::Type_u8; } 140 | else if ((1==n_chanels) && (16 == bits_per_sample)) { _type = Config::Type_s16; } 141 | else if ((2==n_chanels) && ( 8 == bits_per_sample)) { _type = Config::Type_cu8; } 142 | else if ((2==n_chanels) && (16 == bits_per_sample)) { _type = Config::Type_cs16; } 143 | else { 144 | ConfigError err; err << "Can not configure WavSource: Unsupported PCM type."; throw err; 145 | } 146 | 147 | _sample_rate = sample_rate; 148 | _frames_left = _frame_count; 149 | 150 | LogMessage msg(LOG_DEBUG); 151 | msg << "Configured WavSource:" << std::endl 152 | << " file: " << filename << std::endl 153 | << " type: " << _type << std::endl 154 | << " sample-rate: " << _sample_rate << std::endl 155 | << " frame-count: " << _frame_count << std::endl 156 | << " duration: " << _frame_count/_sample_rate << "s" << std::endl 157 | << " buffer-size: " << _buffer_size; 158 | Logger::get().log(msg); 159 | 160 | // unreference buffer if not empty 161 | if (! _buffer.isEmpty()) { _buffer.unref(); } 162 | 163 | // Allocate buffer and propergate config 164 | switch (_type) { 165 | case Config::Type_u8: 166 | _buffer = Buffer(_buffer_size); 167 | this->setConfig(Config(Config::Type_u8, _sample_rate, _buffer_size, 1)); 168 | break; 169 | case Config::Type_s16: 170 | _buffer = Buffer(_buffer_size); 171 | this->setConfig(Config(Config::Type_s16, _sample_rate, _buffer_size, 1)); 172 | break; 173 | case Config::Type_cu8: 174 | _buffer = Buffer< std::complex >(_buffer_size); 175 | this->setConfig(Config(Config::Type_cu8, _sample_rate, _buffer_size, 1)); 176 | break; 177 | case Config::Type_cs16: 178 | _buffer = Buffer< std::complex >(_buffer_size); 179 | this->setConfig(Config(Config::Type_cs16, _sample_rate, _buffer_size, 1)); 180 | break; 181 | default: { 182 | ConfigError err; err << "Can not configure WavSource: Unsupported PCM type."; throw err; 183 | } 184 | } 185 | } 186 | 187 | void 188 | WavSource::close() { 189 | _file.close(); _frames_left = 0; 190 | } 191 | 192 | 193 | bool 194 | WavSource::isReal() const { 195 | return (Config::Type_u8 == _type) || (Config::Type_s16 == _type); 196 | } 197 | 198 | void 199 | WavSource::next() 200 | { 201 | //Logger::get().log(LogMessage(LOG_DEBUG, "WavSource: Read next buffer")); 202 | 203 | if ((0 == _frames_left)) { 204 | // Close file 205 | _file.close(); 206 | signalEOS(); 207 | return; 208 | } 209 | 210 | // Determine the number of frames to read 211 | size_t n_frames = std::min(_frames_left, _buffer_size); 212 | 213 | switch (_type) { 214 | case Config::Type_u8: 215 | _file.read(_buffer.ptr(), n_frames*sizeof(uint8_t)); 216 | _frames_left -= n_frames; 217 | this->send(RawBuffer(_buffer, 0, n_frames*sizeof(uint8_t)), true); 218 | break; 219 | case Config::Type_s16: 220 | _file.read(_buffer.ptr(), n_frames*sizeof(int16_t)); 221 | _frames_left -= n_frames; 222 | this->send(RawBuffer(_buffer, 0, n_frames*sizeof(int16_t)), true); 223 | break; 224 | case Config::Type_cu8: 225 | _file.read(_buffer.ptr(), 2*n_frames*sizeof(uint8_t)); 226 | _frames_left -= n_frames; 227 | this->send(RawBuffer(_buffer, 0, 2*n_frames*sizeof(uint8_t)), true); 228 | break; 229 | case Config::Type_cs16: 230 | _file.read(_buffer.ptr(), 2*n_frames*sizeof(int16_t)); 231 | _frames_left -= n_frames; 232 | this->send(RawBuffer(_buffer, 0, 2*n_frames*sizeof(int16_t)), true); 233 | break; 234 | default: 235 | break; 236 | } 237 | } 238 | 239 | -------------------------------------------------------------------------------- /src/wavfile.hh: -------------------------------------------------------------------------------- 1 | #ifndef __SDR_WAVFILE_HH__ 2 | #define __SDR_WAVFILE_HH__ 3 | 4 | #include "node.hh" 5 | #include 6 | 7 | namespace sdr { 8 | 9 | /** Stores the received buffers into a WAV file. 10 | * @ingroup sinks */ 11 | template 12 | class WavSink: public Sink 13 | { 14 | public: 15 | /** Constructor, @c filename specifies the file name, the WAV data is stored into. 16 | * @throws ConfigError If the specified file can not be opened for output. */ 17 | WavSink(const std::string &filename) 18 | : Sink(), _file(filename.c_str(), std::ios_base::out|std::ios_base::binary), 19 | _frameCount(0), _sampleRate(0) 20 | { 21 | if (!_file.is_open()) { 22 | ConfigError err; 23 | err << "Can not open wav file for output: " << filename; 24 | throw err; 25 | } 26 | // Fill first 36+8 bytes with 0, the headers are written once close() gets called. 27 | for (size_t i=0; i<44; i++) { _file.write("\x00", 1); } 28 | 29 | // check format 30 | switch (Config::typeId()) { 31 | case Config::Type_u8: 32 | case Config::Type_s8: 33 | _bitsPerSample = 8; 34 | _numChanels = 1; 35 | break; 36 | case Config::Type_cu8: 37 | case Config::Type_cs8: 38 | _bitsPerSample = 8; 39 | _numChanels = 2; 40 | break; 41 | case Config::Type_u16: 42 | case Config::Type_s16: 43 | _bitsPerSample = 16; 44 | _numChanels = 1; 45 | break; 46 | case Config::Type_cu16: 47 | case Config::Type_cs16: 48 | _bitsPerSample = 16; 49 | _numChanels = 2; 50 | break; 51 | default: 52 | ConfigError err; 53 | err << "WAV format only allows (real) integer typed data."; 54 | throw err; 55 | } 56 | } 57 | 58 | /** Destructor, closes the file if not done yet. */ 59 | virtual ~WavSink() { 60 | if (_file.is_open()) { 61 | this->close(); 62 | } 63 | } 64 | 65 | /** Configures the sink. */ 66 | virtual void config(const Config &src_cfg) { 67 | // Requires type, samplerate 68 | if (!src_cfg.hasType() || !src_cfg.hasSampleRate()) { return; } 69 | // Check if type matches 70 | if (Config::typeId() != src_cfg.type()) { 71 | ConfigError err; 72 | err << "Can not configure WavSink: Invalid buffer type " << src_cfg.type() 73 | << ", expected " << Config::typeId(); 74 | throw err; 75 | } 76 | // Store sample rate 77 | _sampleRate = src_cfg.sampleRate(); 78 | } 79 | 80 | /** Completes the WAV header and closes the file. */ 81 | void close() { 82 | if (! _file.is_open()) { return; } 83 | uint32_t val4; 84 | uint16_t val2; 85 | 86 | _file.seekp(0); 87 | _file.write("RIFF", 4); 88 | val4 = (uint32_t)(36u+2u*_frameCount); _file.write((char *)&val4, 4); 89 | _file.write("WAVE", 4); 90 | 91 | _file.write("fmt ", 4); 92 | val4 = 16; _file.write((char *)&val4, 4); // sub header size = 16 93 | val2 = 1; _file.write((char *)&val2, 2); // format PCM = 1 94 | _file.write((char *)&_numChanels, 2); // num chanels = 1 95 | _file.write((char *)&_sampleRate, 4); 96 | val4 = _numChanels*_sampleRate*(_bitsPerSample/8); 97 | _file.write((char *)&val4, 4); // byte rate 98 | val2 = _numChanels*(_bitsPerSample/8); 99 | _file.write((char *)&val2, 2); // block align 100 | _file.write((char *)&_bitsPerSample, 2); // bits per sample 101 | 102 | _file.write("data", 4); 103 | val4 = _numChanels*_frameCount*(_bitsPerSample/8); _file.write((char *)&val4, 4); 104 | _file.close(); 105 | } 106 | 107 | /** Writes some data into the WAV file. */ 108 | virtual void process(const Buffer &buffer, bool allow_overwrite) { 109 | if (! _file.is_open()) { return; } 110 | _file.write(buffer.data(), buffer.size()*sizeof(Scalar)); 111 | _frameCount += buffer.size(); 112 | } 113 | 114 | 115 | protected: 116 | /** The file output stream. */ 117 | std::fstream _file; 118 | /** The number of bits per sample (depends on the template type). */ 119 | uint16_t _bitsPerSample; 120 | /** The total number of frame counts. */ 121 | uint32_t _frameCount; 122 | /** The sample rate. */ 123 | uint32_t _sampleRate; 124 | /** The number of chanels. */ 125 | uint16_t _numChanels; 126 | }; 127 | 128 | 129 | 130 | /** A simple imput source that reads from a wav file. Some data is read from the file on every call 131 | * to @c next until the end of file is reached. 132 | * @ingroup sources */ 133 | class WavSource: public Source 134 | { 135 | public: 136 | /** Constructor, @c buffer_size specified the output buffer size. */ 137 | WavSource(size_t buffer_size=1024); 138 | /** Constructor with file name, @c buffer_size specified the output buffer size. */ 139 | WavSource(const std::string &filename, size_t buffer_size=1024); 140 | /** Destructor. */ 141 | virtual ~WavSource(); 142 | 143 | /** Returns @c true if the file is open. */ 144 | bool isOpen() const; 145 | /** Open a new file. */ 146 | void open(const std::string &filename); 147 | /** Close the current file. */ 148 | void close(); 149 | 150 | /** Returns true, if the input is real (stereo files are handled as I/Q signals). */ 151 | bool isReal() const; 152 | 153 | /** Read the next data. */ 154 | void next(); 155 | 156 | protected: 157 | /** The input file stream. */ 158 | std::fstream _file; 159 | /** The output buffer. */ 160 | RawBuffer _buffer; 161 | /** The current buffer size. */ 162 | size_t _buffer_size; 163 | 164 | /** The number of available frames. */ 165 | size_t _frame_count; 166 | /** The type of the data in the WAV file. */ 167 | Config::Type _type; 168 | /** The sample rate. */ 169 | double _sample_rate; 170 | /** The number of frames left to be read. */ 171 | size_t _frames_left; 172 | }; 173 | 174 | } 175 | 176 | #endif // __SDR_WAVFILE_HH__ 177 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(test_SOURCES main.cc 2 | cputime.cc unittest.cc buffertest.cc coreutilstest.cc coretest.cc) 3 | set(test_HEADERS 4 | cputime.hh unittest.hh buffertest.hh coreutilstest.hh coretest.hh) 5 | 6 | add_executable(sdr_test ${test_SOURCES}) 7 | target_link_libraries(sdr_test ${LIBS} libsdr) 8 | -------------------------------------------------------------------------------- /test/buffertest.cc: -------------------------------------------------------------------------------- 1 | #include "buffertest.hh" 2 | #include 3 | using namespace sdr; 4 | using namespace UnitTest; 5 | 6 | BufferTest::~BufferTest() { } 7 | 8 | 9 | void 10 | BufferTest::testRefcount() { 11 | Buffer a(3); 12 | 13 | // Test Direct reference counting 14 | UT_ASSERT_EQUAL(a.refCount(), 1); 15 | UT_ASSERT(a.isUnused()); 16 | 17 | { 18 | Buffer b(a); 19 | UT_ASSERT_EQUAL(a.refCount(), 1); 20 | UT_ASSERT_EQUAL(b.refCount(), 1); 21 | UT_ASSERT(a.isUnused()); 22 | UT_ASSERT(b.isUnused()); 23 | } 24 | 25 | { 26 | Buffer b(a); 27 | b.ref(); 28 | UT_ASSERT_EQUAL(a.refCount(), 2); 29 | UT_ASSERT_EQUAL(b.refCount(), 2); 30 | UT_ASSERT(!a.isUnused()); 31 | UT_ASSERT(!b.isUnused()); 32 | b.unref(); 33 | } 34 | 35 | UT_ASSERT_EQUAL(a.refCount(), 1); 36 | UT_ASSERT(a.isUnused()); 37 | 38 | // Test indirect reference counting 39 | std::list buffers; 40 | buffers.push_back(a); 41 | 42 | UT_ASSERT_EQUAL(a.refCount(), 1); 43 | UT_ASSERT(a.isUnused()); 44 | 45 | // check direct referenceing 46 | UT_ASSERT_EQUAL(buffers.back().refCount(), 1); 47 | UT_ASSERT(buffers.back().isUnused()); 48 | 49 | buffers.pop_back(); 50 | UT_ASSERT_EQUAL(a.refCount(), 1); 51 | UT_ASSERT(a.isUnused()); 52 | } 53 | 54 | 55 | void 56 | BufferTest::testReinterprete() { 57 | // Check handle interleaved numbers as real & imag part 58 | Buffer real_buffer(4); 59 | 60 | real_buffer[0] = 1; 61 | real_buffer[1] = 2; 62 | real_buffer[2] = 3; 63 | real_buffer[3] = 4; 64 | 65 | // Cast to complex char 66 | Buffer< std::complex > cmplx_buffer(real_buffer); 67 | // Check size 68 | UT_ASSERT_EQUAL(real_buffer.size()/2, cmplx_buffer.size()); 69 | // Check content 70 | UT_ASSERT_EQUAL(cmplx_buffer[0], std::complex(1,2)); 71 | UT_ASSERT_EQUAL(cmplx_buffer[1], std::complex(3,4)); 72 | } 73 | 74 | 75 | void 76 | BufferTest::testRawRingBuffer() { 77 | RawBuffer a(3), b(3); 78 | RawRingBuffer ring(3); 79 | 80 | memcpy(a.data(), "abc", 3); 81 | 82 | // Check if ring is empty 83 | UT_ASSERT_EQUAL(ring.bytesLen(), size_t(0)); 84 | UT_ASSERT_EQUAL(ring.bytesFree(), size_t(3)); 85 | 86 | // Put a byte 87 | UT_ASSERT(ring.put(RawBuffer(a, 0, 1))); 88 | UT_ASSERT_EQUAL(ring.bytesLen(), size_t(1)); 89 | UT_ASSERT_EQUAL(ring.bytesFree(), size_t(2)); 90 | 91 | // Put two more bytes 92 | UT_ASSERT(ring.put(RawBuffer(a, 1, 2))); 93 | UT_ASSERT_EQUAL(ring.bytesLen(), size_t(3)); 94 | UT_ASSERT_EQUAL(ring.bytesFree(), size_t(0)); 95 | 96 | // Now, the ring is full, any further put should fail 97 | UT_ASSERT(!ring.put(a)); 98 | 99 | // Take a byte from ring 100 | UT_ASSERT(ring.take(b, 1)); 101 | UT_ASSERT_EQUAL(ring.bytesLen(), size_t(2)); 102 | UT_ASSERT_EQUAL(ring.bytesFree(), size_t(1)); 103 | UT_ASSERT_EQUAL(*(b.data()), 'a'); 104 | 105 | // Take another byte 106 | UT_ASSERT(ring.take(b, 1)); 107 | UT_ASSERT_EQUAL(ring.bytesLen(), size_t(1)); 108 | UT_ASSERT_EQUAL(ring.bytesFree(), size_t(2)); 109 | UT_ASSERT_EQUAL(*(b.data()), 'b'); 110 | 111 | // Put two more back 112 | UT_ASSERT(ring.put(RawBuffer(a, 0, 2))); 113 | UT_ASSERT_EQUAL(ring.bytesLen(), size_t(3)); 114 | UT_ASSERT_EQUAL(ring.bytesFree(), size_t(0)); 115 | 116 | // Take all 117 | UT_ASSERT(ring.take(b, 3)); 118 | UT_ASSERT_EQUAL(ring.bytesLen(), size_t(0)); 119 | UT_ASSERT_EQUAL(ring.bytesFree(), size_t(3)); 120 | UT_ASSERT(0 == memcmp(b.data(), "cab", 3)); 121 | 122 | } 123 | 124 | 125 | TestSuite * 126 | BufferTest::suite() { 127 | TestSuite *suite = new TestSuite("Buffer Tests"); 128 | 129 | suite->addTest(new TestCaller( 130 | "reference counter", &BufferTest::testRefcount)); 131 | suite->addTest(new TestCaller( 132 | "re-interprete case", &BufferTest::testReinterprete)); 133 | suite->addTest(new TestCaller( 134 | "raw ring buffer", &BufferTest::testRawRingBuffer)); 135 | return suite; 136 | } 137 | -------------------------------------------------------------------------------- /test/buffertest.hh: -------------------------------------------------------------------------------- 1 | #ifndef __SDR_TEST_BUFFERTEST_HH__ 2 | #define __SDR_TEST_BUFFERTEST_HH__ 3 | 4 | #include "buffer.hh" 5 | #include "unittest.hh" 6 | 7 | 8 | class BufferTest : public UnitTest::TestCase 9 | { 10 | public: 11 | virtual ~BufferTest(); 12 | 13 | void testRefcount(); 14 | void testReinterprete(); 15 | void testRawRingBuffer(); 16 | 17 | 18 | public: 19 | static UnitTest::TestSuite *suite(); 20 | }; 21 | 22 | #endif // BUFFERTEST_HH 23 | -------------------------------------------------------------------------------- /test/coretest.cc: -------------------------------------------------------------------------------- 1 | #include "coretest.hh" 2 | #include "sdr.hh" 3 | 4 | using namespace sdr; 5 | 6 | 7 | CoreTest::~CoreTest() { /* pass... */ } 8 | 9 | 10 | void 11 | CoreTest::testShiftOperators() { 12 | // Test if shift can be used as multiplication or division by a power of two 13 | // (even on negative integers) 14 | int a=128, b=-128; 15 | // On positive integers (should work always) 16 | UT_ASSERT_EQUAL(a>>1, 64); 17 | UT_ASSERT_EQUAL(a<<1, 256); 18 | UT_ASSERT_EQUAL(a>>0, 128); 19 | UT_ASSERT_EQUAL(a<<0, 128); 20 | 21 | UT_ASSERT_EQUAL(b>>1, -64); 22 | UT_ASSERT_EQUAL(b<<1, -256); 23 | UT_ASSERT_EQUAL(b>>0, -128); 24 | UT_ASSERT_EQUAL(b<<0, -128); 25 | } 26 | 27 | 28 | 29 | UnitTest::TestSuite * 30 | CoreTest::suite() { 31 | UnitTest::TestSuite *suite = new UnitTest::TestSuite("Core operations"); 32 | 33 | suite->addTest(new UnitTest::TestCaller( 34 | "shift operators", &CoreTest::testShiftOperators)); 35 | 36 | return suite; 37 | } 38 | -------------------------------------------------------------------------------- /test/coretest.hh: -------------------------------------------------------------------------------- 1 | #ifndef __SDT_TEST_CORETEST_HH__ 2 | #define __SDT_TEST_CORETEST_HH__ 3 | 4 | #include "unittest.hh" 5 | 6 | class CoreTest : public UnitTest::TestCase 7 | { 8 | public: 9 | virtual ~CoreTest(); 10 | 11 | void testShiftOperators(); 12 | 13 | 14 | public: 15 | static UnitTest::TestSuite *suite(); 16 | }; 17 | 18 | #endif // __SDT_TEST_CORETEST_HH__ 19 | -------------------------------------------------------------------------------- /test/coreutilstest.cc: -------------------------------------------------------------------------------- 1 | #include "coreutilstest.hh" 2 | #include "config.hh" 3 | #include "utils.hh" 4 | #include "combine.hh" 5 | 6 | using namespace sdr; 7 | using namespace UnitTest; 8 | 9 | CoreUtilsTest::~CoreUtilsTest() { } 10 | 11 | void 12 | CoreUtilsTest::testUChar2Char() { 13 | Buffer uchar_buffer(3); 14 | uchar_buffer[0] = 0u; 15 | uchar_buffer[1] = 128u; 16 | uchar_buffer[2] = 255u; 17 | 18 | // Assemble cast instance and configure it 19 | UnsignedToSigned cast; 20 | cast.config(Config(Config::Type_u8, 1, 3, 1)); 21 | // Perform in-place operation 22 | cast.handleBuffer(uchar_buffer, true); 23 | 24 | // Reinterprete uchar buffer as char buffer 25 | Buffer char_buffer(uchar_buffer); 26 | // Check values 27 | UT_ASSERT_EQUAL(char_buffer[0], (signed char)-128); 28 | UT_ASSERT_EQUAL(char_buffer[1], (signed char)0); 29 | UT_ASSERT_EQUAL(char_buffer[2], (signed char)127); 30 | } 31 | 32 | void 33 | CoreUtilsTest::testUShort2Short() { 34 | Buffer uchar_buffer(3); 35 | uchar_buffer[0] = 0u; 36 | uchar_buffer[1] = 128u; 37 | uchar_buffer[2] = 255u; 38 | 39 | // Assemble cast instance and configure it 40 | UnsignedToSigned cast; 41 | cast.config(Config(Config::Type_u16, 1, 3, 1)); 42 | // Perform in-place operation 43 | cast.handleBuffer(uchar_buffer, true); 44 | 45 | // Reinterprete uchar buffer as char buffer 46 | Buffer char_buffer(uchar_buffer); 47 | // Check values 48 | UT_ASSERT_EQUAL(char_buffer[0], (int16_t)-128); 49 | UT_ASSERT_EQUAL(char_buffer[1], (int16_t)0); 50 | UT_ASSERT_EQUAL(char_buffer[2], (int16_t)127); 51 | } 52 | 53 | 54 | void 55 | CoreUtilsTest::testInterleave() { 56 | Interleave interl(2); 57 | DebugStore sink; 58 | Buffer a(3); 59 | 60 | interl.sink(0)->config(Config(Config::Type_s16, 1, a.size(), 1)); 61 | interl.sink(1)->config(Config(Config::Type_s16, 1, a.size(), 1)); 62 | interl.connect(&sink, true); 63 | 64 | // Send some data 65 | a[0] = 1; a[1] = 2; a[2] = 3; interl.sink(0)->process(a, false); 66 | a[0] = 4; a[1] = 5; a[2] = 6; interl.sink(1)->process(a, false); 67 | 68 | // Check content of sink 69 | UT_ASSERT_EQUAL(sink.buffer()[0], (int16_t)1); 70 | UT_ASSERT_EQUAL(sink.buffer()[1], (int16_t)4); 71 | UT_ASSERT_EQUAL(sink.buffer()[2], (int16_t)2); 72 | UT_ASSERT_EQUAL(sink.buffer()[3], (int16_t)5); 73 | UT_ASSERT_EQUAL(sink.buffer()[4], (int16_t)3); 74 | UT_ASSERT_EQUAL(sink.buffer()[5], (int16_t)6); 75 | } 76 | 77 | 78 | TestSuite * 79 | CoreUtilsTest::suite() { 80 | TestSuite *suite = new TestSuite("Core Utils"); 81 | 82 | suite->addTest(new TestCaller( 83 | "cast uint8_t -> int8_t", &CoreUtilsTest::testUChar2Char)); 84 | suite->addTest(new TestCaller( 85 | "cast uint16_t -> int16_t", &CoreUtilsTest::testUChar2Char)); 86 | suite->addTest(new TestCaller( 87 | "Interleave", &CoreUtilsTest::testInterleave)); 88 | 89 | return suite; 90 | } 91 | -------------------------------------------------------------------------------- /test/coreutilstest.hh: -------------------------------------------------------------------------------- 1 | #ifndef __SDR_TEST_COREUTILSTEST_HH__ 2 | #define __SDR_TEST_COREUTILSTEST_HH__ 3 | 4 | #include "unittest.hh" 5 | 6 | class CoreUtilsTest : public UnitTest::TestCase 7 | { 8 | public: 9 | virtual ~CoreUtilsTest(); 10 | 11 | void testUChar2Char(); 12 | void testUShort2Short(); 13 | void testInterleave(); 14 | 15 | public: 16 | static UnitTest::TestSuite *suite(); 17 | }; 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /test/cputime.cc: -------------------------------------------------------------------------------- 1 | #include "cputime.hh" 2 | 3 | using namespace UnitTest; 4 | 5 | 6 | CpuTime::CpuTime() 7 | { 8 | } 9 | 10 | 11 | void 12 | CpuTime::start() 13 | { 14 | this->_clocks.push_back(clock()); 15 | } 16 | 17 | 18 | double 19 | CpuTime::stop() 20 | { 21 | // measure time. 22 | clock_t end = clock(); 23 | 24 | // Get time-diff since start: 25 | double dt = end-this->_clocks.back(); 26 | dt /= CLOCKS_PER_SEC; 27 | 28 | // Remove start time from stack: 29 | this->_clocks.pop_back(); 30 | 31 | // Return delta t: 32 | return dt; 33 | } 34 | 35 | 36 | double 37 | CpuTime::getTime() 38 | { 39 | clock_t end = clock(); 40 | 41 | // get diff: 42 | double dt = end - this->_clocks.back(); 43 | dt /= CLOCKS_PER_SEC; 44 | 45 | return dt; 46 | } 47 | -------------------------------------------------------------------------------- /test/cputime.hh: -------------------------------------------------------------------------------- 1 | #ifndef __SDR_CPUTIME_HH__ 2 | #define __SDR_CPUTIME_HH__ 3 | 4 | #include 5 | #include 6 | 7 | 8 | namespace UnitTest { 9 | 10 | /** A utility class to measure the CPU time used by some algorithms. */ 11 | class CpuTime 12 | { 13 | public: 14 | /** Constructs a new CPU time clock. */ 15 | CpuTime(); 16 | 17 | /** Start the clock. */ 18 | void start(); 19 | /** Stops the clock and returns the time in seconds. */ 20 | double stop(); 21 | /** Retruns the current time of the current clock. */ 22 | double getTime(); 23 | 24 | protected: 25 | /** The stack of start times. */ 26 | std::list< clock_t > _clocks; 27 | }; 28 | 29 | } 30 | 31 | #endif // CPUTIME_HH 32 | -------------------------------------------------------------------------------- /test/main.cc: -------------------------------------------------------------------------------- 1 | #include "coretest.hh" 2 | #include "coreutilstest.hh" 3 | #include "unittest.hh" 4 | #include "buffertest.hh" 5 | #include 6 | 7 | using namespace sdr; 8 | 9 | 10 | int main(int argc, char *argv[]) { 11 | 12 | UnitTest::TestRunner runner(std::cout); 13 | 14 | runner.addSuite(CoreTest::suite()); 15 | runner.addSuite(BufferTest::suite()); 16 | runner.addSuite(CoreUtilsTest::suite()); 17 | 18 | runner(); 19 | 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /test/unittest.cc: -------------------------------------------------------------------------------- 1 | #include "unittest.hh" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "cputime.hh" 7 | 8 | using namespace UnitTest; 9 | 10 | 11 | 12 | /* ********************************************************************************************* * 13 | * Implementation of TestFailure 14 | * ********************************************************************************************* */ 15 | TestFailure::TestFailure(const std::string &message) throw() 16 | : message(message) 17 | { 18 | // pass... 19 | } 20 | 21 | TestFailure::~TestFailure() throw() 22 | { 23 | // Pass... 24 | } 25 | 26 | const char * 27 | TestFailure::what() const throw () 28 | { 29 | return this->message.c_str(); 30 | } 31 | 32 | 33 | 34 | 35 | /* ********************************************************************************************* * 36 | * Implementation of TestCase 37 | * ********************************************************************************************* */ 38 | void 39 | TestCase::setUp() 40 | { 41 | // Pass... 42 | } 43 | 44 | 45 | void 46 | TestCase::tearDown() 47 | { 48 | // Pass... 49 | } 50 | 51 | 52 | void 53 | TestCase::assertTrue(bool test, const std::string &file, size_t line) 54 | { 55 | if (! test) 56 | { 57 | std::stringstream str; 58 | str << "Assert failed in " << file << " in line " << line; 59 | throw TestFailure(str.str()); 60 | } 61 | } 62 | 63 | 64 | 65 | /* ********************************************************************************************* * 66 | * Implementation of TestSuite 67 | * ********************************************************************************************* */ 68 | TestSuite::TestSuite(const std::string &desc) 69 | : description(desc) 70 | { 71 | // pass... 72 | } 73 | 74 | 75 | TestSuite::~TestSuite() 76 | { 77 | // Free callers: 78 | for (std::list::iterator caller=this->tests.begin(); 79 | caller != this->tests.end(); caller++) { 80 | delete *caller; 81 | } 82 | } 83 | 84 | 85 | void 86 | TestSuite::addTest(TestCallerInterface *test) 87 | { 88 | this->tests.push_back(test); 89 | } 90 | 91 | 92 | const std::string & 93 | TestSuite::getDescription() 94 | { 95 | return this->description; 96 | } 97 | 98 | 99 | TestSuite::iterator 100 | TestSuite::begin() 101 | { 102 | return this->tests.begin(); 103 | } 104 | 105 | 106 | TestSuite::iterator 107 | TestSuite::end() 108 | { 109 | return this->tests.end(); 110 | } 111 | 112 | 113 | 114 | 115 | /* ********************************************************************************************* * 116 | * Implementation of TestSuite 117 | * ********************************************************************************************* */ 118 | TestRunner::TestRunner(std::ostream &stream) 119 | : stream(stream) 120 | { 121 | // Pass... 122 | } 123 | 124 | 125 | TestRunner::~TestRunner() 126 | { 127 | // Free suites: 128 | for (std::list::iterator suite = this->suites.begin(); 129 | suite != this->suites.end(); suite++) 130 | { 131 | delete *suite; 132 | } 133 | } 134 | 135 | 136 | void 137 | TestRunner::addSuite(TestSuite *suite) 138 | { 139 | this->suites.push_back(suite); 140 | } 141 | 142 | 143 | void 144 | TestRunner::operator ()() 145 | { 146 | size_t tests_run = 0; 147 | size_t tests_failed = 0; 148 | size_t tests_error = 0; 149 | 150 | for (std::list::iterator suite = this->suites.begin(); 151 | suite != this->suites.end(); suite++) 152 | { 153 | // Dump Suite description 154 | this->stream << "Suite: " << (*suite)->getDescription() << std::endl; 155 | 156 | // For each test in suite: 157 | for (TestSuite::iterator test = (*suite)->begin(); test != (*suite)->end(); test++) 158 | { 159 | this->stream << " test: " << (*test)->getDescription() << ": "; 160 | 161 | try 162 | { 163 | tests_run++; 164 | CpuTime clock; clock.start(); 165 | // Run test 166 | (**test)(); 167 | this->stream << " ok (" << clock.stop() << "s)" << std::endl; 168 | } 169 | catch (TestFailure &fail) 170 | { 171 | this->stream << " fail" << std::endl; 172 | this->stream << " reason: " << fail.what() << std::endl; 173 | tests_failed++; 174 | } 175 | catch (std::exception &err) 176 | { 177 | this->stream << " exception" << std::endl; 178 | this->stream << " what(): " << err.what() << std::endl; 179 | tests_error++; 180 | } 181 | } 182 | 183 | this->stream << std::endl; 184 | } 185 | 186 | this->stream << "Summary: " << tests_failed << " tests failed out of " 187 | << tests_run - tests_error 188 | << " (" << 100. * float((tests_run-tests_failed-tests_error))/(tests_run-tests_error) 189 | << "% passed)." << std::endl << " Where " 190 | << tests_error << " tests produced errors." << std::endl; 191 | } 192 | 193 | -------------------------------------------------------------------------------- /test/unittest.hh: -------------------------------------------------------------------------------- 1 | #ifndef __SDR_UNITTEST_HH__ 2 | #define __SDR_UNITTEST_HH__ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | namespace UnitTest { 11 | 12 | class TestFailure : public std::exception 13 | { 14 | protected: 15 | std::string message; 16 | 17 | public: 18 | TestFailure(const std::string &message) throw(); 19 | virtual ~TestFailure() throw(); 20 | 21 | const char *what() const throw(); 22 | }; 23 | 24 | 25 | 26 | class TestCase 27 | { 28 | public: 29 | virtual void setUp(); 30 | virtual void tearDown(); 31 | 32 | void assertTrue(bool test, const std::string &file, size_t line); 33 | 34 | template 35 | void assertEqual(Scalar t, Scalar e, const std::string &file, size_t line) { 36 | if (e != t) { 37 | std::stringstream str; 38 | str << "Expected: " << +e << " but got: " << +t 39 | << " in file "<< file << " in line " << line; 40 | throw TestFailure(str.str()); 41 | } 42 | } 43 | 44 | template 45 | void assertNear(Scalar t, Scalar e, const std::string &file, size_t line, 46 | Scalar err_abs=Scalar(1e-8), Scalar err_rel=Scalar(1e-6)) 47 | { 48 | if (std::abs(e-t) > (err_abs + err_rel*std::abs(e))) { 49 | std::stringstream str; 50 | str << "Expected: " << +e << " but got: " << +t 51 | << " in file "<< file << " in line " << line; 52 | throw TestFailure(str.str()); 53 | } 54 | } 55 | }; 56 | 57 | 58 | 59 | class TestCallerInterface 60 | { 61 | protected: 62 | std::string description; 63 | 64 | public: 65 | TestCallerInterface(const std::string &desc) 66 | : description(desc) 67 | { 68 | // Pass... 69 | } 70 | 71 | virtual ~TestCallerInterface() { /* pass... */ } 72 | 73 | virtual const std::string &getDescription() 74 | { 75 | return this->description; 76 | } 77 | 78 | virtual void operator() () = 0; 79 | }; 80 | 81 | 82 | template 83 | class TestCaller : public TestCallerInterface 84 | { 85 | protected: 86 | void (T::*function)(void); 87 | 88 | public: 89 | TestCaller(const std::string &desc, void (T::*func)(void)) 90 | : TestCallerInterface(desc), function(func) 91 | { 92 | // Pass... 93 | } 94 | 95 | virtual ~TestCaller() { /* pass... */ } 96 | 97 | virtual void operator() () 98 | { 99 | // Create new test: 100 | T *instance = new T(); 101 | 102 | // Call test 103 | instance->setUp(); 104 | (instance->*function)(); 105 | instance->tearDown(); 106 | 107 | // free instance: 108 | delete instance; 109 | } 110 | }; 111 | 112 | 113 | class TestSuite 114 | { 115 | public: 116 | typedef std::list::iterator iterator; 117 | 118 | protected: 119 | std::string description; 120 | std::list tests; 121 | 122 | public: 123 | TestSuite(const std::string &desc); 124 | virtual ~TestSuite(); 125 | 126 | void addTest(TestCallerInterface *test); 127 | 128 | const std::string &getDescription(); 129 | 130 | iterator begin(); 131 | iterator end(); 132 | }; 133 | 134 | 135 | class TestRunner 136 | { 137 | protected: 138 | std::ostream &stream; 139 | std::list suites; 140 | 141 | public: 142 | TestRunner(std::ostream &stream); 143 | virtual ~TestRunner(); 144 | 145 | void addSuite(TestSuite *suite); 146 | 147 | void operator() (); 148 | }; 149 | 150 | 151 | #define UT_ASSERT(t) this->assertTrue(t, __FILE__, __LINE__) 152 | #define UT_ASSERT_EQUAL(t, e) this->assertEqual(t, e, __FILE__, __LINE__) 153 | #define UT_ASSERT_NEAR(t, e) this->assertNear(t, e, __FILE__, __LINE__) 154 | #define UT_ASSERT_THROW(t, e) \ 155 | try { t; throw UnitTest::TestFailure("No exception thrown!"); } catch (e &err) {} 156 | } 157 | 158 | 159 | #endif // UNITTEST_HH 160 | --------------------------------------------------------------------------------