diff options
38 files changed, 1901 insertions, 1190 deletions
@@ -10,3 +10,10 @@ Matthias P. Braendli <matthias [at] mpb [dot] li> - Improvements in logging (log to file, to syslog) - ZeroMQ ETI input - Telnet remote-control + - ZeroMQ I/Q output + - I/Q conversion to signed 8-bit + - ARM support + +Jörgen Scott + - ZeroMQ remote control + - Static delay offset @@ -1,6 +1,18 @@ This file contains information about the changes done to the ODR-DabMod in this repository +2015-04-10: Matthias P. Braendli <matthias@mpb.li> + (v0.5.2) + * odr-dabmod: + Merge static delay parameter and ZeroMQ remote control. + Add max_frames_queued option for ZeroMQ input. + Restart modulator on FCT discontinuity or ZeroMQ input + buffer overrun. + Improve error messages and documentation. + Add ZeroMQ output REP socket type for interconnection with + GNURadio. + Fix license text in usage view. + 2015-01-24: Matthias P. Braendli <matthias@mpb.li> (v0.5.1) * odr-dabmod: @@ -11,7 +11,7 @@ Simple install procedure: % tar xjf odr-dabmod-X.Y.Z.tar.bz2 # Unpack the source % cd odr-dabmod-X.Y.Z # Change to the source directory - % ./configure --enable-zeromq --enable-output-uhd + % ./configure --enable-zeromq # Run the configure script % make # Build ODR-DabMod [ as root ] @@ -23,7 +23,7 @@ The configure script can be launch with a variety of options: --enable-zeromq Enable ZeroMQ input (to be used with ODR-DabMux), output and remotecontrol. - --enable-output-uhd Includes the binding to the UHD driver for USRPs + --disable-output-uhd Disable the binding to the UHD driver for USRPs You have the choice between two FFT libraries: KISS FFT and FFTW. KISS FFT is a proven library, but it's performance is worse than with the new FFTW. With KISS diff --git a/Makefile.am b/Makefile.am index c0b3024..5c7c60b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,7 +1,10 @@ # Copyright (C) 2007, 2008, 2009, 2010 Her Majesty the Queen in Right # of Canada (Communications Research Center Canada) # -# Copyright (C) 2014 Matthias P. Braendli, http://mpb.li +# Copyright (C) 2014, 2015 +# Matthias P. Braendli, matthias.braendli@mpb.li + +# http://opendigitalradio.org # This file is part of ODR-DabMod. # @@ -18,8 +21,154 @@ # You should have received a copy of the GNU General Public License # along with ODR-DabMod. If not, see <http://www.gnu.org/licenses/>. -SUBDIRS = src lib - ACLOCAL_AMFLAGS = -I m4 EXTRA_DIST = COPYING NEWS README.md AUTHORS ChangeLog TODO doc + +if IS_GIT_REPO +GITVERSION_FLAGS = -DGITVERSION="\"`git describe --dirty`\"" +else +GITVERSION_FLAGS = +endif + +if HAVE_SSE +SIMD_CFLAGS = -msse -msse2 +else +SIMD_CFLAGS = +endif + +bin_PROGRAMS = odr-dabmod + +FFT_DIR=lib/kiss_fft129 +FFT_LDADD= + +if USE_KISS_FFT +FFT_INC=-I$(FFT_DIR) -I$(FFT_DIR)/tools +FFT_FLG=-ffast-math + +.PHONY: lib/kiss_fft129 + +BUILT_SOURCES=lib/kiss_fft129 + +lib/kiss_fft129: + if [ ! -e lib/kiss_fft129/kiss_fft.c ]; then \ + tar xzf lib/kiss_fft129.tar.gz -C lib; \ + fi + +nodist_odr_dabmod_SOURCES = lib/kiss_fft129/kiss_fft.c \ + lib/kiss_fft129/kiss_fft.h \ + lib/kiss_fft129/tools/kiss_fftr.c \ + lib/kiss_fft129/tools/kiss_fftr.h \ + src/kiss_fftsimd.c \ + src/kiss_fftsimd.h + +clean-local: + rm -rf $(FFT_DIR) + +else +FFT_INC= +FFT_FLG= +endif + +odr_dabmod_CPPFLAGS = -Wall -Isrc \ + $(FFT_INC) $(FFT_FLG) $(SIMD_CFLAGS) $(GITVERSION_FLAGS) +odr_dabmod_LDADD = $(FFT_LDADD) +odr_dabmod_SOURCES = src/DabMod.cpp \ + src/PcDebug.h \ + src/porting.c \ + src/porting.h \ + src/DabModulator.cpp \ + src/DabModulator.h \ + src/Buffer.cpp \ + src/Buffer.h \ + src/ModCodec.cpp \ + src/ModCodec.h \ + src/ModPlugin.cpp \ + src/ModPlugin.h \ + src/ModFormat.cpp \ + src/ModFormat.h \ + src/EtiReader.cpp \ + src/EtiReader.h \ + src/Eti.cpp \ + src/Eti.h \ + src/FicSource.cpp \ + src/FicSource.h \ + src/FIRFilter.cpp \ + src/FIRFilter.h \ + src/ModInput.cpp \ + src/ModInput.h \ + src/PuncturingRule.cpp \ + src/PuncturingRule.h \ + src/PuncturingEncoder.cpp \ + src/PuncturingEncoder.h \ + src/SubchannelSource.cpp \ + src/SubchannelSource.h \ + src/Flowgraph.cpp \ + src/Flowgraph.h \ + src/GainControl.cpp \ + src/GainControl.h \ + src/OutputMemory.cpp \ + src/OutputMemory.h \ + src/OutputZeroMQ.cpp \ + src/OutputZeroMQ.h \ + src/TimestampDecoder.h \ + src/TimestampDecoder.cpp \ + src/OutputUHD.cpp \ + src/OutputUHD.h \ + src/ModOutput.cpp \ + src/ModOutput.h \ + src/InputMemory.cpp \ + src/InputMemory.h \ + src/InputFileReader.cpp \ + src/InputZeroMQReader.cpp \ + src/InputReader.h \ + src/OutputFile.cpp \ + src/OutputFile.h \ + src/FrameMultiplexer.cpp \ + src/FrameMultiplexer.h \ + src/ModMux.cpp \ + src/ModMux.h \ + src/PrbsGenerator.cpp \ + src/PrbsGenerator.h \ + src/BlockPartitioner.cpp \ + src/BlockPartitioner.h \ + src/QpskSymbolMapper.cpp \ + src/QpskSymbolMapper.h \ + src/FrequencyInterleaver.cpp \ + src/FrequencyInterleaver.h \ + src/PhaseReference.cpp \ + src/PhaseReference.h \ + src/DifferentialModulator.cpp \ + src/DifferentialModulator.h \ + src/NullSymbol.cpp \ + src/NullSymbol.h \ + src/SignalMultiplexer.cpp \ + src/SignalMultiplexer.h \ + src/CicEqualizer.cpp \ + src/CicEqualizer.h \ + src/OfdmGenerator.cpp \ + src/OfdmGenerator.h \ + src/GuardIntervalInserter.cpp \ + src/GuardIntervalInserter.h \ + src/Resampler.cpp \ + src/Resampler.h \ + src/ConvEncoder.cpp \ + src/ConvEncoder.h \ + src/TimeInterleaver.cpp \ + src/TimeInterleaver.h \ + src/ThreadsafeQueue.h \ + src/Log.cpp \ + src/Log.h \ + src/RemoteControl.cpp \ + src/RemoteControl.h \ + src/FormatConverter.cpp \ + src/FormatConverter.h \ + src/Utils.cpp \ + src/Utils.h \ + src/zmq.hpp + +dist_bin_SCRIPTS = src/crc-dwap.py + +EXTRA_DIST += lib/kiss_fft129.tar.gz +EXTRA_DIST += src/kiss_fftsimd.c src/kiss_fftsimd.h + @@ -1,24 +1,30 @@ OVERVIEW ======== -ODR-DabMod is a fork of CRC-DabMod, which was developed by the Communications -Research Center Canada. It has been forked by the Opendigitalradio project. - ODR-DabMod is a DAB (Digital Audio Broadcasting) modulator compliant to ETSI EN 300 401. -In addition to the features of CRC-DabMod, this fork contains: +ODR-DabMod is a fork of CRC-DabMod, which was developed by the +Communications Research Center Canada and whose development has ceased. +The Opendigitalradio association now continues this project. + +Short list of features: +- Reads ETI, outputs compliant COFDM I/Q +- Supports native DAB sample rate and can also + resample to other rates +- supports all four DAB transmission modes - Configuration file support, see doc/example.ini - Integrated UHD output for USRP devices - Tested for B200, B100, USRP2, USRP1 - With WBX daughterboard (where appropriate) - Timestamping support required for SFN -- A FIR filter (previously done in GNURadio by crc-dwap.py) +- A FIR filter for improved spectrum mask - Improvements in logging (log to file, to syslog) - ETI sources: file (Raw, Framed and Streamed) and ZeroMQ - A Telnet and ZeroMQ remote-control that can be used to change some parameters during runtime -- 8-bit signed I/Q output format +- 8-bit signed I/Q output format, useful for the HackRF +- ZeroMQ PUB and REP output. The src/ directory contains the source code of ODR-DabMod. @@ -41,6 +47,6 @@ CONTACT Matthias P. Braendli <matthias [at] mpb [dot] li> Pascal Charest <pascal [dot] charest [at] crc [dot] ca> +With thanks to other contributors listed in AUTHORS + http://opendigitalradio.org/ -http://mmbtools.crc.ca/ -http://mpb.li/ diff --git a/configure.ac b/configure.ac index 3f21f89..821ba1f 100644 --- a/configure.ac +++ b/configure.ac @@ -19,10 +19,11 @@ # along with ODR-DabMod. If not, see <http://www.gnu.org/licenses/>. AC_PREREQ(2.59) -AC_INIT([ODR-DabMod], [0.5.1], [matthias.braendli@mpb.li]) +AC_INIT([ODR-DabMod], [0.5.2], [matthias.braendli@mpb.li]) AC_CONFIG_AUX_DIR([build-aux]) +AC_CONFIG_MACRO_DIR([m4]) AC_CANONICAL_SYSTEM -AM_INIT_AUTOMAKE([-Wall foreign]) +AM_INIT_AUTOMAKE([-Wall foreign subdir-objects]) AC_CONFIG_SRCDIR([src/DabMod.cpp]) AM_CONFIG_HEADER([config.h]) AM_SILENT_RULES([yes]) @@ -76,7 +77,7 @@ AC_ARG_ENABLE([zeromq], # UHD support control AC_ARG_ENABLE([output_uhd], - [AS_HELP_STRING([--enable-output-uhd], [Enable UHD output])], + [AS_HELP_STRING([--disable-output-uhd], [Disable UHD output])], [], [enable_output_uhd=yes]) AS_IF([test "x$enable_kiss" = "xno"], @@ -186,9 +187,7 @@ AM_CONDITIONAL([HAVE_SSE], [test "x$has_sse" = "xyes"]) AC_TYPE_SIGNAL AC_CHECK_FUNCS([bzero floor ftime gettimeofday memset sqrt strchr strerror strtol]) -AC_CONFIG_FILES([Makefile - lib/Makefile - src/Makefile]) +AC_CONFIG_FILES([Makefile]) AC_OUTPUT echo diff --git a/doc/example.ini b/doc/example.ini index 43adacb..317d652 100644 --- a/doc/example.ini +++ b/doc/example.ini @@ -28,7 +28,7 @@ telnetport=2121 ; ; REQ: ["set"][module name][parameter][value] ; REP: ["ok"] _OR_ ["fail"][error description] -zmqctrl=1 +zmqctrl=0 zmqctrlendpoint=tcp://127.0.0.1:9400 [log] @@ -49,6 +49,9 @@ loop=0 ; When recieving data using ZeroMQ, the source is the URI to be used ;transport=zeromq ;source=tcp://localhost:8080 +; The option max_frames_queued defines the maximum number of ETI frames +; that can be in the input queue +;max_frames_queued=100 [modulator] ; Gain mode: 0=FIX, 1=MAX, 2=VAR @@ -109,7 +112,7 @@ enabled=0 filtertapsfile=simple_taps.txt [output] -; choose output: possible values: uhd, file +; choose output: possible values: uhd, file, zmq output=uhd [fileoutput] @@ -150,7 +153,7 @@ txgain=2.0 ; For the B200 ; More information and measurements available on: -; http://opendigitalradio.org/index.php/USRP_B200_Measurements +; http://wiki.opendigitalradio.org/index.php/USRP_B200_Measurements ; ; Settings: ;device= @@ -179,17 +182,35 @@ txgain=2.0 channel=13C ; The reference clock to use. -; possible values : internal, external, MIMO +; possible values : internal, external, MIMO, gpsdo refclk_source=internal ; The reference one pulse-per second to use -; possible values : none, external, MIMO +; possible values : none, external, MIMO, gpsdo pps_source=none ; Behaviour when external clock reference lock lost ; possible values: ignore, crash behaviour_refclk_lock_lost=ignore +; The maximum accepted holdover time for the gpsdo. +; Valid only if the refclk and pps_source are set to gpsdo. +; Units: seconds +; Set to 0 to disable holdover check +; default value: 0 +max_gps_holdover_time=30 + +; section defining ZeroMQ output properties +[zmqoutput] + +; on which port to listen for connections +; please see the Transports section in man zmq +; for more informat io the syntax +listen=tcp://*:54001 + +; what ZMQ socket type to use. Valid values: PUB, REP +; Please see man zmq_socket for documentation +socket_type=pub ; Used for SFN with the UHD output [delaymanagement] @@ -200,16 +221,11 @@ synchronous=0 ; Whether to mute the TX when incoming frames have no timestamp mutenotimestamps=0 -; Choose between fixed and dynamic offset definition -; fixed defines an offset in this file that cannot be changed while -; the modulator runs. -; -; dynamic reads the offset from a file, and if the value changes, -; the chain does a re-sync. -management=dynamic +; This offset is added to the TIST, and the sum defines the +; TX time of the transmission frame. It can by changed at runtime +; through the remote control. +offset=0.002 -fixedoffset=0.002 +; The previous static vs dynamic offset distinction, and reading the +; modulatoroffset from a file has been removed. -; The file should contain a single floating point value, written -; in ASCII (it's human-readable, not binary) -dynamicoffsetfile=modulator_offset diff --git a/lib/Makefile.am b/lib/Makefile.am deleted file mode 100644 index c37fb46..0000000 --- a/lib/Makefile.am +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (C) 2008, 2009 Her Majesty the Queen in Right of Canada -# (Communications Research Center Canada) - -# This file is part of ODR-DabMod. -# -# ODR-DabMod is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# ODR-DabMod is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with ODR-DabMod. If not, see <http://www.gnu.org/licenses/>. - -EXTRA_DIST =kiss_fft129.tar.gz diff --git a/m4/pkg.m4 b/m4/pkg.m4 new file mode 100644 index 0000000..c5b26b5 --- /dev/null +++ b/m4/pkg.m4 @@ -0,0 +1,214 @@ +# pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*- +# serial 1 (pkg-config-0.24) +# +# Copyright © 2004 Scott James Remnant <scott@netsplit.com>. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# PKG_PROG_PKG_CONFIG([MIN-VERSION]) +# ---------------------------------- +AC_DEFUN([PKG_PROG_PKG_CONFIG], +[m4_pattern_forbid([^_?PKG_[A-Z_]+$]) +m4_pattern_allow([^PKG_CONFIG(_(PATH|LIBDIR|SYSROOT_DIR|ALLOW_SYSTEM_(CFLAGS|LIBS)))?$]) +m4_pattern_allow([^PKG_CONFIG_(DISABLE_UNINSTALLED|TOP_BUILD_DIR|DEBUG_SPEW)$]) +AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility]) +AC_ARG_VAR([PKG_CONFIG_PATH], [directories to add to pkg-config's search path]) +AC_ARG_VAR([PKG_CONFIG_LIBDIR], [path overriding pkg-config's built-in search path]) + +if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then + AC_PATH_TOOL([PKG_CONFIG], [pkg-config]) +fi +if test -n "$PKG_CONFIG"; then + _pkg_min_version=m4_default([$1], [0.9.0]) + AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version]) + if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + PKG_CONFIG="" + fi +fi[]dnl +])# PKG_PROG_PKG_CONFIG + +# PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +# +# Check to see whether a particular set of modules exists. Similar +# to PKG_CHECK_MODULES(), but does not set variables or print errors. +# +# Please remember that m4 expands AC_REQUIRE([PKG_PROG_PKG_CONFIG]) +# only at the first occurence in configure.ac, so if the first place +# it's called might be skipped (such as if it is within an "if", you +# have to call PKG_CHECK_EXISTS manually +# -------------------------------------------------------------- +AC_DEFUN([PKG_CHECK_EXISTS], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +if test -n "$PKG_CONFIG" && \ + AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then + m4_default([$2], [:]) +m4_ifvaln([$3], [else + $3])dnl +fi]) + +# _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES]) +# --------------------------------------------- +m4_define([_PKG_CONFIG], +[if test -n "$$1"; then + pkg_cv_[]$1="$$1" + elif test -n "$PKG_CONFIG"; then + PKG_CHECK_EXISTS([$3], + [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes ], + [pkg_failed=yes]) + else + pkg_failed=untried +fi[]dnl +])# _PKG_CONFIG + +# _PKG_SHORT_ERRORS_SUPPORTED +# ----------------------------- +AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG]) +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi[]dnl +])# _PKG_SHORT_ERRORS_SUPPORTED + + +# PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], +# [ACTION-IF-NOT-FOUND]) +# +# +# Note that if there is a possibility the first call to +# PKG_CHECK_MODULES might not happen, you should be sure to include an +# explicit call to PKG_PROG_PKG_CONFIG in your configure.ac +# +# +# -------------------------------------------------------------- +AC_DEFUN([PKG_CHECK_MODULES], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl +AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl + +pkg_failed=no +AC_MSG_CHECKING([for $1]) + +_PKG_CONFIG([$1][_CFLAGS], [cflags], [$2]) +_PKG_CONFIG([$1][_LIBS], [libs], [$2]) + +m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS +and $1[]_LIBS to avoid the need to call pkg-config. +See the pkg-config man page for more details.]) + +if test $pkg_failed = yes; then + AC_MSG_RESULT([no]) + _PKG_SHORT_ERRORS_SUPPORTED + if test $_pkg_short_errors_supported = yes; then + $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$2" 2>&1` + else + $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$2" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD + + m4_default([$4], [AC_MSG_ERROR( +[Package requirements ($2) were not met: + +$$1_PKG_ERRORS + +Consider adjusting the PKG_CONFIG_PATH environment variable if you +installed software in a non-standard prefix. + +_PKG_TEXT])[]dnl + ]) +elif test $pkg_failed = untried; then + AC_MSG_RESULT([no]) + m4_default([$4], [AC_MSG_FAILURE( +[The pkg-config script could not be found or is too old. Make sure it +is in your PATH or set the PKG_CONFIG environment variable to the full +path to pkg-config. + +_PKG_TEXT + +To get pkg-config, see <http://pkg-config.freedesktop.org/>.])[]dnl + ]) +else + $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS + $1[]_LIBS=$pkg_cv_[]$1[]_LIBS + AC_MSG_RESULT([yes]) + $3 +fi[]dnl +])# PKG_CHECK_MODULES + + +# PKG_INSTALLDIR(DIRECTORY) +# ------------------------- +# Substitutes the variable pkgconfigdir as the location where a module +# should install pkg-config .pc files. By default the directory is +# $libdir/pkgconfig, but the default can be changed by passing +# DIRECTORY. The user can override through the --with-pkgconfigdir +# parameter. +AC_DEFUN([PKG_INSTALLDIR], +[m4_pushdef([pkg_default], [m4_default([$1], ['${libdir}/pkgconfig'])]) +m4_pushdef([pkg_description], + [pkg-config installation directory @<:@]pkg_default[@:>@]) +AC_ARG_WITH([pkgconfigdir], + [AS_HELP_STRING([--with-pkgconfigdir], pkg_description)],, + [with_pkgconfigdir=]pkg_default) +AC_SUBST([pkgconfigdir], [$with_pkgconfigdir]) +m4_popdef([pkg_default]) +m4_popdef([pkg_description]) +]) dnl PKG_INSTALLDIR + + +# PKG_NOARCH_INSTALLDIR(DIRECTORY) +# ------------------------- +# Substitutes the variable noarch_pkgconfigdir as the location where a +# module should install arch-independent pkg-config .pc files. By +# default the directory is $datadir/pkgconfig, but the default can be +# changed by passing DIRECTORY. The user can override through the +# --with-noarch-pkgconfigdir parameter. +AC_DEFUN([PKG_NOARCH_INSTALLDIR], +[m4_pushdef([pkg_default], [m4_default([$1], ['${datadir}/pkgconfig'])]) +m4_pushdef([pkg_description], + [pkg-config arch-independent installation directory @<:@]pkg_default[@:>@]) +AC_ARG_WITH([noarch-pkgconfigdir], + [AS_HELP_STRING([--with-noarch-pkgconfigdir], pkg_description)],, + [with_noarch_pkgconfigdir=]pkg_default) +AC_SUBST([noarch_pkgconfigdir], [$with_noarch_pkgconfigdir]) +m4_popdef([pkg_default]) +m4_popdef([pkg_description]) +]) dnl PKG_NOARCH_INSTALLDIR + + +# PKG_CHECK_VAR(VARIABLE, MODULE, CONFIG-VARIABLE, +# [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +# ------------------------------------------- +# Retrieves the value of the pkg-config variable for the given module. +AC_DEFUN([PKG_CHECK_VAR], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +AC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])dnl + +_PKG_CONFIG([$1], [variable="][$3]["], [$2]) +AS_VAR_COPY([$1], [pkg_cv_][$1]) + +AS_VAR_IF([$1], [""], [$5], [$4])dnl +])# PKG_CHECK_VAR diff --git a/src/Buffer.cpp b/src/Buffer.cpp index aa0ef4c..fa7f52f 100644 --- a/src/Buffer.cpp +++ b/src/Buffer.cpp @@ -47,6 +47,7 @@ Buffer::Buffer(size_t len, const void *data) Buffer::~Buffer() { + PDEBUG("Buffer::~Buffer() len=%zu, data=%p\n", len, data); free(data); } diff --git a/src/CicEqualizer.cpp b/src/CicEqualizer.cpp index d8eb2ee..a9c0dd6 100644 --- a/src/CicEqualizer.cpp +++ b/src/CicEqualizer.cpp @@ -46,11 +46,12 @@ CicEqualizer::CicEqualizer(size_t nbCarriers, size_t spacing, int R) : float angle = pi * k / spacing; if (k == 0) { myFilter[i] = 1.0f; - } else { - myFilter[i] = sinf(angle / R) / sinf(angle * M); - myFilter[i] = fabsf(myFilter[i]) * R * M; - myFilter[i] = powf(myFilter[i], N); - } + } + else { + myFilter[i] = sinf(angle / R) / sinf(angle * M); + myFilter[i] = fabsf(myFilter[i]) * R * M; + myFilter[i] = powf(myFilter[i], N); + } PDEBUG("HCic[%zu -> %i] = %f (%f dB) -> angle: %f\n", i, k,myFilter[i], 20.0 * log10(myFilter[i]), angle); } @@ -93,3 +94,4 @@ int CicEqualizer::process(Buffer* const dataIn, Buffer* dataOut) return sizeOut; } + diff --git a/src/DabMod.cpp b/src/DabMod.cpp index 214231c..1fc7e3c 100644 --- a/src/DabMod.cpp +++ b/src/DabMod.cpp @@ -3,7 +3,7 @@ Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2014 + Copyright (C) 2014, 2015 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -30,7 +30,7 @@ #endif #include "porting.h" - +#include "Utils.h" #include "Log.h" #include "DabModulator.h" #include "InputMemory.h" @@ -46,6 +46,8 @@ #include "FIRFilter.h" #include "RemoteControl.h" +#include <boost/shared_ptr.hpp> +#include <boost/make_shared.hpp> #include <boost/property_tree/ptree.hpp> #include <boost/property_tree/ini_parser.hpp> #include <complex> @@ -68,9 +70,12 @@ # define memalign(a, b) malloc(b) #endif +#define ZMQ_INPUT_MAX_FRAME_QUEUE 500 + typedef std::complex<float> complexf; +using namespace boost; volatile sig_atomic_t running = 1; @@ -81,113 +86,51 @@ void signalHandler(int signalNb) running = 0; } - -void printUsage(char* progName, FILE* out = stderr) +struct modulator_data { - fprintf(out, "Welcome to %s %s, compiled at %s, %s\n\n", - PACKAGE, -#if defined(GITVERSION) - GITVERSION, -#else - VERSION, -#endif - __DATE__, __TIME__); - fprintf(out, "Usage with configuration file:\n"); - fprintf(out, "\t%s [-C] config_file.ini\n\n", progName); - - fprintf(out, "Usage with command line options:\n"); - fprintf(out, "\t%s" - " input" - " (-f filename | -u uhddevice -F frequency) " - " [-G txgain]" - " [-o offset]" - " [-O offsetfile]" - " [-T filter_taps_file]" - " [-a gain]" - " [-c clockrate]" - " [-g gainMode]" - " [-h]" - " [-l]" - " [-m dabMode]" - " [-r samplingRate]" - "\n", progName); - fprintf(out, "Where:\n"); - fprintf(out, "input: ETI input filename (default: stdin).\n"); - fprintf(out, "-f name: Use file output with given filename. (use /dev/stdout for standard output)\n"); - fprintf(out, "-u device: Use UHD output with given device string. (use "" for default device)\n"); - fprintf(out, "-F frequency: Set the transmit frequency when using UHD output. (mandatory option when using UHD)\n"); - fprintf(out, "-G txgain: Set the transmit gain for the UHD driver (default: 0)\n"); - fprintf(out, "-o: (UHD only) Set the timestamp offset added to the timestamp in the ETI. The offset is a double.\n"); - fprintf(out, "-O: (UHD only) Set the file containing the timestamp offset added to the timestamp in the ETI.\n" - "The file is read every six seconds, and must contain a double value.\n"); - fprintf(out, " Specifying either -o or -O has two implications: It enables synchronous transmission,\n" - " requiring an external REFCLK and PPS signal and frames that do not contain a valid timestamp\n" - " get muted.\n\n"); - fprintf(out, "-T taps_file: Enable filtering before the output, using the specified file containing the filter taps.\n"); - fprintf(out, "-a gain: Apply digital amplitude gain.\n"); - fprintf(out, "-c rate: Set the DAC clock rate and enable Cic Equalisation.\n"); - fprintf(out, "-g: Set computation gain mode: " - "%u FIX, %u MAX, %u VAR\n", GAIN_FIX, GAIN_MAX, GAIN_VAR); - fprintf(out, "-h: Print this help.\n"); - fprintf(out, "-l: Loop file when reach end of file.\n"); - fprintf(out, "-m mode: Set DAB mode: (0: auto, 1-4: force).\n"); - fprintf(out, "-r rate: Set output sampling rate (default: 2048000).\n"); -} + modulator_data() : + inputReader(NULL), + framecount(0), + flowgraph(NULL), + rcs(NULL) {} + InputReader* inputReader; + Buffer data; + uint64_t framecount; -void printVersion(FILE *out = stderr) -{ - fprintf(out, "Welcome to %s %s, compiled at %s, %s\n\n", - PACKAGE, VERSION, __DATE__, __TIME__); - fprintf(out, - " ODR-DabMod is copyright (C) Her Majesty the Queen in Right of Canada,\n" - " 2009, 2010, 2011, 2012 Communications Research Centre (CRC),\n" - " and\n" - " Copyright (C) 2014 Matthias P. Braendli, matthias.braendli@mpb.li\n" - "\n" - " http://opendigitalradio.org\n" - "\n" - " This program is available free of charge and is licensed to you on a\n" - " non-exclusive basis; you may not redistribute it.\n" - "\n" - " This program is provided \"AS IS\" in the hope that it will be useful, but\n" - " WITHOUT ANY WARRANTY with respect to its accurancy or usefulness; witout\n" - " even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR\n" - " PURPOSE and NONINFRINGEMENT.\n" - "\n" - " In no event shall CRC be LIABLE for any LOSS, DAMAGE or COST that may be\n" - " incurred in connection with the use of this software.\n" - "\n" -#if USE_KISS_FFT - "ODR-DabMod makes use of the following open source packages:\n" - " Kiss FFT v1.2.9 (Revised BSD) - http://kissfft.sourceforge.net/\n" -#endif - ); + Flowgraph* flowgraph; + RemoteControllers* rcs; +}; -} +enum run_modulator_state { + MOD_FAILURE, + MOD_NORMAL_END, + MOD_AGAIN +}; +run_modulator_state run_modulator(modulator_data& m); -int main(int argc, char* argv[]) +int launch_modulator(int argc, char* argv[]) { int ret = 0; bool loop = false; std::string inputName = ""; std::string inputTransport = "file"; + unsigned inputMaxFramesQueued = ZMQ_INPUT_MAX_FRAME_QUEUE; std::string outputName; int useZeroMQOutput = 0; + std::string zmqOutputSocketType = ""; int useFileOutput = 0; std::string fileOutputFormat = "complexf"; int useUHDOutput = 0; - uint64_t frame = 0; size_t outputRate = 2048000; size_t clockRate = 0; unsigned dabMode = 0; float digitalgain = 1.0f; float normalise = 1.0f; GainMode gainMode = GAIN_VAR; - Buffer data; /* UHD requires the input I and Q samples to be in the interval @@ -211,26 +154,25 @@ int main(int argc, char* argv[]) OutputUHDConfig outputuhd_conf; #endif + modulator_data m; + // To handle the timestamp offset of the modulator - struct modulator_offset_config modconf; - modconf.use_offset_file = false; - modconf.use_offset_fixed = false; - modconf.delay_calculation_pipeline_stages = 0; + unsigned tist_delay_stages = 0; + double tist_offset_s = 0.0; - Flowgraph* flowgraph = NULL; - DabModulator* modulator = NULL; - InputMemory* input = NULL; - FormatConverter* format_converter = NULL; - ModOutput* output = NULL; + shared_ptr<Flowgraph> flowgraph(new Flowgraph()); + shared_ptr<FormatConverter> format_converter; + shared_ptr<ModOutput> output; RemoteControllers rcs; + m.rcs = &rcs; - Logger logger; - InputFileReader inputFileReader(logger); + bool run_again = true; + + InputFileReader inputFileReader; #if defined(HAVE_ZEROMQ) - InputZeroMQReader inputZeroMQReader(logger); + shared_ptr<InputZeroMQReader> inputZeroMQReader(new InputZeroMQReader()); #endif - InputReader* inputReader; struct sigaction sa; memset(&sa, 0, sizeof(struct sigaction)); @@ -271,7 +213,7 @@ int main(int argc, char* argv[]) #if defined(HAVE_OUTPUT_UHD) if (useUHDOutput) { fprintf(stderr, "Options -u and -f are mutually exclusive\n"); - goto END_MAIN; + throw std::invalid_argument("Invalid command line options"); } #endif outputName = optarg; @@ -294,25 +236,7 @@ int main(int argc, char* argv[]) loop = true; break; case 'o': - if (modconf.use_offset_file) - { - fprintf(stderr, "Options -o and -O are mutually exclusive\n"); - goto END_MAIN; - } - modconf.use_offset_fixed = true; - modconf.offset_fixed = strtod(optarg, NULL); -#if defined(HAVE_OUTPUT_UHD) - outputuhd_conf.enableSync = true; -#endif - break; - case 'O': - if (modconf.use_offset_fixed) - { - fprintf(stderr, "Options -o and -O are mutually exclusive\n"); - goto END_MAIN; - } - modconf.use_offset_file = true; - modconf.offset_filename = std::string(optarg); + tist_offset_s = strtod(optarg, NULL); #if defined(HAVE_OUTPUT_UHD) outputuhd_conf.enableSync = true; #endif @@ -330,7 +254,7 @@ int main(int argc, char* argv[]) #if defined(HAVE_OUTPUT_UHD) if (useFileOutput) { fprintf(stderr, "Options -u and -f are mutually exclusive\n"); - goto END_MAIN; + throw std::invalid_argument("Invalid command line options"); } outputuhd_conf.device = optarg; useUHDOutput = 1; @@ -338,17 +262,17 @@ int main(int argc, char* argv[]) break; case 'V': printVersion(); - goto END_MAIN; + throw std::invalid_argument(""); break; case '?': case 'h': printUsage(argv[0]); - goto END_MAIN; + throw std::invalid_argument(""); break; default: fprintf(stderr, "Option '%c' not coded yet!\n", c); ret = -1; - goto END_MAIN; + throw std::invalid_argument("Invalid command line options"); } } @@ -389,7 +313,7 @@ int main(int argc, char* argv[]) // No argument given ? You can't be serious ! Show usage. if (argc == 1) { printUsage(argv[0]); - goto END_MAIN; + throw std::invalid_argument("Invalid command line options"); } // If only one argument is given, interpret as configuration file name @@ -408,8 +332,9 @@ int main(int argc, char* argv[]) } catch (boost::property_tree::ini_parser::ini_parser_error &e) { - fprintf(stderr, "Error, cannot read configuration file '%s'\n", configuration_file.c_str()); - goto END_MAIN; + std::cerr << "Error, cannot read configuration file '" << configuration_file.c_str() << "'" << std::endl; + std::cerr << " " << e.what() << std::endl; + throw std::runtime_error("Cannot read configuration file"); } // remote controller: @@ -422,7 +347,7 @@ int main(int argc, char* argv[]) catch (std::exception &e) { std::cerr << "Error: " << e.what() << "\n"; std::cerr << " telnet remote control enabled, but no telnetport defined.\n"; - goto END_MAIN; + throw std::runtime_error("Configuration error"); } } @@ -437,7 +362,7 @@ int main(int argc, char* argv[]) catch (std::exception &e) { std::cerr << "Error: " << e.what() << "\n"; std::cerr << " zmq remote control enabled, but no endpoint defined.\n"; - goto END_MAIN; + throw std::runtime_error("Configuration error"); } } #endif @@ -448,12 +373,15 @@ int main(int argc, char* argv[]) } inputTransport = pt.get("input.transport", "file"); + inputMaxFramesQueued = pt.get("input.max_frames_queued", + ZMQ_INPUT_MAX_FRAME_QUEUE); + inputName = pt.get("input.source", "/dev/stdin"); // log parameters: if (pt.get("log.syslog", 0) == 1) { LogToSyslog* log_syslog = new LogToSyslog(); - logger.register_backend(log_syslog); + etiLog.register_backend(log_syslog); } if (pt.get("log.filelog", 0) == 1) { @@ -464,11 +392,11 @@ int main(int argc, char* argv[]) catch (std::exception &e) { std::cerr << "Error: " << e.what() << "\n"; std::cerr << " Configuration enables file log, but does not specify log filename\n"; - goto END_MAIN; + throw std::runtime_error("Configuration error"); } LogToFile* log_file = new LogToFile(logfilename); - logger.register_backend(log_file); + etiLog.register_backend(log_file); } @@ -487,7 +415,7 @@ int main(int argc, char* argv[]) catch (std::exception &e) { std::cerr << "Error: " << e.what() << "\n"; std::cerr << " Configuration enables firfilter, but does not specify filter taps file\n"; - goto END_MAIN; + throw std::runtime_error("Configuration error"); } } @@ -499,7 +427,7 @@ int main(int argc, char* argv[]) catch (std::exception &e) { std::cerr << "Error: " << e.what() << "\n"; std::cerr << " Configuration does not specify output\n"; - goto END_MAIN; + throw std::runtime_error("Configuration error"); } if (output_selected == "file") { @@ -509,7 +437,7 @@ int main(int argc, char* argv[]) catch (std::exception &e) { std::cerr << "Error: " << e.what() << "\n"; std::cerr << " Configuration does not specify file name for file output\n"; - goto END_MAIN; + throw std::runtime_error("Configuration error"); } useFileOutput = 1; @@ -535,11 +463,11 @@ int main(int argc, char* argv[]) outputuhd_conf.txgain = pt.get("uhdoutput.txgain", 0.0); outputuhd_conf.frequency = pt.get<double>("uhdoutput.frequency", 0); std::string chan = pt.get<std::string>("uhdoutput.channel", ""); - outputuhd_conf.dabMode = dabMode; + outputuhd_conf.dabMode = dabMode; if (outputuhd_conf.frequency == 0 && chan == "") { std::cerr << " UHD output enabled, but neither frequency nor channel defined.\n"; - goto END_MAIN; + throw std::runtime_error("Configuration error"); } else if (outputuhd_conf.frequency == 0) { double freq; @@ -583,13 +511,13 @@ int main(int argc, char* argv[]) else if (chan == "13F") freq = 239200000; else { std::cerr << " UHD output: channel " << chan << " does not exist in table\n"; - goto END_MAIN; + throw std::out_of_range("UHD channel selection error"); } outputuhd_conf.frequency = freq; } else if (outputuhd_conf.frequency != 0 && chan != "") { std::cerr << " UHD output: cannot define both frequency and channel.\n"; - goto END_MAIN; + throw std::runtime_error("Configuration error"); } @@ -607,44 +535,44 @@ int main(int argc, char* argv[]) } else { std::cerr << "Error: UHD output: behaviour_refclk_lock_lost invalid." << std::endl; - goto END_MAIN; + throw std::runtime_error("Configuration error"); } + outputuhd_conf.maxGPSHoldoverTime = pt.get("uhdoutput.max_gps_holdover_time", 0); + useUHDOutput = 1; } #endif #if defined(HAVE_ZEROMQ) else if (output_selected == "zmq") { outputName = pt.get<std::string>("zmqoutput.listen"); + zmqOutputSocketType = pt.get<std::string>("zmqoutput.socket_type"); useZeroMQOutput = 1; } #endif else { std::cerr << "Error: Invalid output defined.\n"; - goto END_MAIN; + throw std::runtime_error("Configuration error"); } #if defined(HAVE_OUTPUT_UHD) outputuhd_conf.enableSync = (pt.get("delaymanagement.synchronous", 0) == 1); if (outputuhd_conf.enableSync) { + std::string delay_mgmt = pt.get<std::string>("delaymanagement.management", ""); + std::string fixedoffset = pt.get<std::string>("delaymanagement.fixedoffset", ""); + std::string offset_filename = pt.get<std::string>("delaymanagement.dynamicoffsetfile", ""); + + if (not(delay_mgmt.empty() and fixedoffset.empty() and offset_filename.empty())) { + std::cerr << "Warning: you are using the old config syntax for the offset management.\n"; + std::cerr << " Please see the example.ini configuration for the new settings.\n"; + } + try { - std::string delay_mgmt = pt.get<std::string>("delaymanagement.management"); - if (delay_mgmt == "fixed") { - modconf.offset_fixed = pt.get<double>("delaymanagement.fixedoffset"); - modconf.use_offset_fixed = true; - } - else if (delay_mgmt == "dynamic") { - modconf.offset_filename = pt.get<std::string>("delaymanagement.dynamicoffsetfile"); - modconf.use_offset_file = true; - } - else { - throw std::runtime_error("invalid management value"); - } + tist_offset_s = pt.get<double>("delaymanagement.offset"); } catch (std::exception &e) { - std::cerr << "Error: " << e.what() << "\n"; - std::cerr << " Synchronised transmission enabled, but delay management specification is incomplete.\n"; - goto END_MAIN; + std::cerr << "Error: delaymanagement: synchronous is enabled, but no offset defined!\n"; + throw std::runtime_error("Configuration error"); } } @@ -653,23 +581,17 @@ int main(int argc, char* argv[]) } if (rcs.get_no_controllers() == 0) { - logger.level(warn) << "No Remote-Control started"; + etiLog.level(warn) << "No Remote-Control started"; rcs.add_controller(new RemoteControllerDummy()); } - logger.level(info) << "Starting up"; - - if (!(modconf.use_offset_file || modconf.use_offset_fixed)) { - logger.level(debug) << "No Modulator offset defined, setting to 0"; - modconf.use_offset_fixed = true; - modconf.offset_fixed = 0; - } + etiLog.level(info) << "Starting up"; // When using the FIRFilter, increase the modulator offset pipelining delay // by the correct amount if (filterTapsFilename != "") { - modconf.delay_calculation_pipeline_stages += FIRFILTER_PIPELINE_DELAY; + tist_delay_stages += FIRFILTER_PIPELINE_DELAY; } // Setting ETI input filename @@ -697,14 +619,14 @@ int main(int argc, char* argv[]) fprintf(stderr, "\n"); printUsage(argv[0]); ret = -1; - logger.level(error) << "Received invalid command line arguments"; - goto END_MAIN; + etiLog.level(error) << "Received invalid command line arguments"; + throw std::invalid_argument("Invalid command line options"); } if (!useFileOutput && !useUHDOutput && !useZeroMQOutput) { - logger.level(error) << "Output not specified"; + etiLog.level(error) << "Output not specified"; fprintf(stderr, "Must specify output !"); - goto END_MAIN; + throw std::runtime_error("Configuration error"); } // Print settings @@ -721,16 +643,22 @@ int main(int argc, char* argv[]) fprintf(stderr, " UHD\n" " Device: %s\n" " Type: %s\n" - " master_clock_rate: %ld\n", + " master_clock_rate: %ld\n" + " refclk: %s\n" + " pps source: %s\n", outputuhd_conf.device.c_str(), outputuhd_conf.usrpType.c_str(), - outputuhd_conf.masterClockRate); + outputuhd_conf.masterClockRate, + outputuhd_conf.refclk_src.c_str(), + outputuhd_conf.pps_src.c_str()); } #endif else if (useZeroMQOutput) { fprintf(stderr, " ZeroMQ\n" - " Listening on: %s\n", - outputName.c_str()); + " Listening on: %s\n" + " Socket type : %s\n", + outputName.c_str(), + zmqOutputSocketType.c_str()); } fprintf(stderr, " Sampling rate: "); @@ -748,96 +676,147 @@ int main(int argc, char* argv[]) // Opening ETI input file if (inputFileReader.Open(inputName, loop) == -1) { fprintf(stderr, "Unable to open input file!\n"); - logger.level(error) << "Unable to open input file!"; + etiLog.level(error) << "Unable to open input file!"; ret = -1; - goto END_MAIN; + throw std::runtime_error("Unable to open input"); } - inputReader = &inputFileReader; + m.inputReader = &inputFileReader; } else if (inputTransport == "zeromq") { #if !defined(HAVE_ZEROMQ) fprintf(stderr, "Error, ZeroMQ input transport selected, but not compiled in!\n"); ret = -1; - goto END_MAIN; + throw std::runtime_error("Unable to open input"); #else - // The URL might start with zmq+tcp:// - if (inputName.substr(0, 4) == "zmq+") { - inputZeroMQReader.Open(inputName.substr(4)); - } - else { - inputZeroMQReader.Open(inputName); - } - inputReader = &inputZeroMQReader; + inputZeroMQReader->Open(inputName, inputMaxFramesQueued); + m.inputReader = inputZeroMQReader.get(); #endif } else { fprintf(stderr, "Error, invalid input transport %s selected!\n", inputTransport.c_str()); ret = -1; - goto END_MAIN; + throw std::runtime_error("Unable to open input"); } if (useFileOutput) { if (fileOutputFormat == "complexf") { - output = new OutputFile(outputName); + output = make_shared<OutputFile>(outputName); } else if (fileOutputFormat == "s8") { // We must normalise the samples to the interval [-127.0; 127.0] normalise = 127.0f / normalise_factor; - format_converter = new FormatConverter(); + format_converter = make_shared<FormatConverter>(); - output = new OutputFile(outputName); + output = make_shared<OutputFile>(outputName); } } #if defined(HAVE_OUTPUT_UHD) else if (useUHDOutput) { - normalise = 1.0f / normalise_factor; - outputuhd_conf.sampleRate = outputRate; - try { - output = new OutputUHD(outputuhd_conf, logger); - ((OutputUHD*)output)->enrol_at(rcs); - } - catch (std::exception& e) { - logger.level(error) << "UHD initialisation failed:" << e.what(); - goto END_MAIN; - } + output = make_shared<OutputUHD>(outputuhd_conf); + ((OutputUHD*)output.get())->enrol_at(rcs); } #endif #if defined(HAVE_ZEROMQ) else if (useZeroMQOutput) { /* We normalise the same way as for the UHD output */ normalise = 1.0f / normalise_factor; - - output = new OutputZeroMQ(outputName); + if (zmqOutputSocketType == "pub") { + output = make_shared<OutputZeroMQ>(outputName, ZMQ_PUB); + } + else if (zmqOutputSocketType == "rep") { + output = make_shared<OutputZeroMQ>(outputName, ZMQ_REP); + } + else { + std::stringstream ss; + ss << "ZeroMQ output socket type " << zmqOutputSocketType << " invalid"; + throw std::invalid_argument(ss.str()); + } } #endif - flowgraph = new Flowgraph(); - data.setLength(6144); - input = new InputMemory(&data); - modulator = new DabModulator(modconf, &rcs, logger, outputRate, clockRate, - dabMode, gainMode, digitalgain, normalise, filterTapsFilename); - flowgraph->connect(input, modulator); - if (format_converter) { - flowgraph->connect(modulator, format_converter); - flowgraph->connect(format_converter, output); - } - else { - flowgraph->connect(modulator, output); - } + + while (run_again) { + Flowgraph flowgraph; + + m.flowgraph = &flowgraph; + m.data.setLength(6144); + + shared_ptr<InputMemory> input(new InputMemory(&m.data)); + shared_ptr<DabModulator> modulator( + new DabModulator(tist_offset_s, tist_delay_stages, &rcs, + outputRate, clockRate, dabMode, gainMode, digitalgain, + normalise, filterTapsFilename)); + + flowgraph.connect(input, modulator); + if (format_converter) { + flowgraph.connect(modulator, format_converter); + flowgraph.connect(format_converter, output); + } + else { + flowgraph.connect(modulator, output); + } #if defined(HAVE_OUTPUT_UHD) - if (useUHDOutput) { - ((OutputUHD*)output)->setETIReader(modulator->getEtiReader()); - } + if (useUHDOutput) { + ((OutputUHD*)output.get())->setETIReader(modulator->getEtiReader()); + } +#endif + + m.inputReader->PrintInfo(); + + run_modulator_state st = run_modulator(m); + + switch (st) { + case MOD_FAILURE: + etiLog.level(error) << "Modulator failure."; + run_again = false; + ret = 1; + break; +#if defined(HAVE_ZEROMQ) + case MOD_AGAIN: + etiLog.level(warn) << "Restart modulator."; + running = true; + if (inputTransport == "zeromq") { + run_again = true; + + // Create a new input reader + inputZeroMQReader = make_shared<InputZeroMQReader>(); + inputZeroMQReader->Open(inputName, inputMaxFramesQueued); + m.inputReader = inputZeroMQReader.get(); + } + break; #endif + case MOD_NORMAL_END: + default: + etiLog.level(info) << "modulator stopped."; + ret = 0; + run_again = false; + break; + } - inputReader->PrintInfo(); + fprintf(stderr, "\n\n"); + etiLog.level(info) << m.framecount << " DAB frames encoded"; + etiLog.level(info) << ((float)m.framecount * 0.024f) << " seconds encoded"; + + m.data.setLength(0); + } + + //////////////////////////////////////////////////////////////////////// + // Cleaning things + //////////////////////////////////////////////////////////////////////// + etiLog.level(info) << "Terminating"; + return ret; +} + +run_modulator_state run_modulator(modulator_data& m) +{ + run_modulator_state ret = MOD_FAILURE; try { while (running) { @@ -846,57 +825,68 @@ int main(int argc, char* argv[]) PDEBUG("*****************************************\n"); PDEBUG("* Starting main loop\n"); PDEBUG("*****************************************\n"); - while ((framesize = inputReader->GetNextFrame(data.getData())) > 0) { + while ((framesize = m.inputReader->GetNextFrame(m.data.getData())) > 0) { if (!running) { break; } - frame++; + m.framecount++; PDEBUG("*****************************************\n"); - PDEBUG("* Read frame %lu\n", frame); + PDEBUG("* Read frame %lu\n", m.framecount); PDEBUG("*****************************************\n"); //////////////////////////////////////////////////////////////// - // Proccessing data + // Processing data //////////////////////////////////////////////////////////////// - flowgraph->run(); + m.flowgraph->run(); /* Check every once in a while if the remote control * is still working */ - if (rcs.get_no_controllers() > 0 && (frame % 250) == 0) { - rcs.check_faults(); + if (m.rcs->get_no_controllers() > 0 && (m.framecount % 250) == 0) { + m.rcs->check_faults(); } } if (framesize == 0) { - fprintf(stderr, "End of file reached.\n"); + etiLog.level(info) << "End of file reached."; } else { - fprintf(stderr, "Input read error.\n"); + etiLog.level(error) << "Input read error."; } running = 0; + ret = MOD_NORMAL_END; } +#if defined(HAVE_OUTPUT_UHD) + } catch (fct_discontinuity_error& e) { + // The OutputUHD saw a FCT discontinuity + etiLog.level(warn) << e.what(); + ret = MOD_AGAIN; +#endif + } catch (zmq_input_overflow& e) { + // The ZeroMQ input has overflowed its buffer + etiLog.level(warn) << e.what(); + ret = MOD_AGAIN; } catch (std::exception& e) { - fprintf(stderr, "EXCEPTION: %s\n", e.what()); - ret = -1; + etiLog.level(error) << "Exception caught: " << e.what(); + ret = MOD_FAILURE; } -END_MAIN: - //////////////////////////////////////////////////////////////////////// - // Cleaning things - //////////////////////////////////////////////////////////////////////// - fprintf(stderr, "\n\n"); - fprintf(stderr, "%lu DAB frames encoded\n", frame); - fprintf(stderr, "%f seconds encoded\n", (float)frame * 0.024f); - - fprintf(stderr, "\nCleaning flowgraph...\n"); - delete flowgraph; - - // Cif - fprintf(stderr, "\nCleaning buffers...\n"); - - logger.level(info) << "Terminating"; - return ret; } +int main(int argc, char* argv[]) +{ + try { + return launch_modulator(argc, argv); + } + catch (std::invalid_argument& e) { + std::string what(e.what()); + if (not what.empty()) { + std::cerr << "Modulator error: " << what << std::endl; + } + } + catch (std::runtime_error& e) { + std::cerr << "Modulator runtime error: " << e.what() << std::endl; + } +} + diff --git a/src/DabModulator.cpp b/src/DabModulator.cpp index 2664a08..35ef7cb 100644 --- a/src/DabModulator.cpp +++ b/src/DabModulator.cpp @@ -3,8 +3,10 @@ Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Includes modifications for which no copyright is claimed - 2012, Matthias P. Braendli, matthias.braendli@mpb.li + Copyright (C) 2015 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://opendigitalradio.org */ /* This file is part of ODR-DabMod. @@ -50,25 +52,24 @@ #include "RemoteControl.h" #include "Log.h" +using namespace boost; DabModulator::DabModulator( - struct modulator_offset_config& modconf, + double tist_offset_s, unsigned tist_delay_stages, RemoteControllers* rcs, - Logger& logger, unsigned outputRate, unsigned clockRate, unsigned dabMode, GainMode gainMode, float digGain, float normalise, std::string filterTapsFilename ) : ModCodec(ModFormat(1), ModFormat(0)), - myLogger(logger), myOutputRate(outputRate), myClockRate(clockRate), myDabMode(dabMode), myGainMode(gainMode), myDigGain(digGain), myNormalise(normalise), - myEtiReader(EtiReader(modconf, myLogger)), + myEtiReader(EtiReader(tist_offset_s, tist_delay_stages, rcs)), myFlowgraph(NULL), myFilterTapsFilename(filterTapsFilename), myRCs(rcs) @@ -155,62 +156,65 @@ int DabModulator::process(Buffer* const dataIn, Buffer* dataOut) //////////////////////////////////////////////////////////////// // CIF data initialisation //////////////////////////////////////////////////////////////// - FrameMultiplexer* cifMux = NULL; - PrbsGenerator* cifPrbs = NULL; - BlockPartitioner* cifPart = NULL; - QpskSymbolMapper* cifMap = NULL; - FrequencyInterleaver* cifFreq = NULL; - PhaseReference* cifRef = NULL; - DifferentialModulator* cifDiff = NULL; - NullSymbol* cifNull = NULL; - SignalMultiplexer* cifSig = NULL; - CicEqualizer* cifCicEq = NULL; - OfdmGenerator* cifOfdm = NULL; - GainControl* cifGain = NULL; - GuardIntervalInserter* cifGuard = NULL; - FIRFilter* cifFilter = NULL; - Resampler* cifRes = NULL; - - cifPrbs = new PrbsGenerator(864 * 8, 0x110); - cifMux = new FrameMultiplexer(myFicSizeOut + 864 * 8, - &myEtiReader.getSubchannels()); - cifPart = new BlockPartitioner(mode, myEtiReader.getFp()); - cifMap = new QpskSymbolMapper(myNbCarriers); - cifRef = new PhaseReference(mode); - cifFreq = new FrequencyInterleaver(mode); - cifDiff = new DifferentialModulator(myNbCarriers); - cifNull = new NullSymbol(myNbCarriers); - cifSig = new SignalMultiplexer( - (1 + myNbSymbols) * myNbCarriers * sizeof(complexf)); - + shared_ptr<PrbsGenerator> cifPrbs(new PrbsGenerator(864 * 8, 0x110)); + shared_ptr<FrameMultiplexer> cifMux( + new FrameMultiplexer(myFicSizeOut + 864 * 8, + &myEtiReader.getSubchannels())); + + shared_ptr<BlockPartitioner> cifPart( + new BlockPartitioner(mode, myEtiReader.getFp())); + + shared_ptr<QpskSymbolMapper> cifMap(new QpskSymbolMapper(myNbCarriers)); + shared_ptr<PhaseReference> cifRef(new PhaseReference(mode)); + shared_ptr<FrequencyInterleaver> cifFreq(new FrequencyInterleaver(mode)); + shared_ptr<DifferentialModulator> cifDiff( + new DifferentialModulator(myNbCarriers)); + + shared_ptr<NullSymbol> cifNull(new NullSymbol(myNbCarriers)); + shared_ptr<SignalMultiplexer> cifSig(new SignalMultiplexer( + (1 + myNbSymbols) * myNbCarriers * sizeof(complexf))); + + // TODO this needs a review + bool useCicEq = false; + unsigned cic_ratio = 1; if (myClockRate) { - unsigned ratio = myClockRate / myOutputRate; - ratio /= 4; // FPGA DUC + cic_ratio = myClockRate / myOutputRate; + cic_ratio /= 4; // FPGA DUC if (myClockRate == 400000000) { // USRP2 - if (ratio & 1) { // odd - cifCicEq = new CicEqualizer(myNbCarriers, - (float)mySpacing * (float)myOutputRate / 2048000.0f, - ratio); + if (cic_ratio & 1) { // odd + useCicEq = true; } // even, no filter - } else { - cifCicEq = new CicEqualizer(myNbCarriers, - (float)mySpacing * (float)myOutputRate / 2048000.0f, - ratio); + } + else { + useCicEq = true; } } - cifOfdm = new OfdmGenerator((1 + myNbSymbols), myNbCarriers, mySpacing); - cifGain = new GainControl(mySpacing, myGainMode, myDigGain, myNormalise); + shared_ptr<CicEqualizer> cifCicEq(new CicEqualizer(myNbCarriers, + (float)mySpacing * (float)myOutputRate / 2048000.0f, + cic_ratio)); + + + shared_ptr<OfdmGenerator> cifOfdm( + new OfdmGenerator((1 + myNbSymbols), myNbCarriers, mySpacing)); + + shared_ptr<GainControl> cifGain( + new GainControl(mySpacing, myGainMode, myDigGain, myNormalise)); + cifGain->enrol_at(*myRCs); - cifGuard = new GuardIntervalInserter(myNbSymbols, mySpacing, - myNullSize, mySymSize); + shared_ptr<GuardIntervalInserter> cifGuard( + new GuardIntervalInserter(myNbSymbols, mySpacing, + myNullSize, mySymSize)); + + FIRFilter* cifFilter = NULL; if (myFilterTapsFilename != "") { cifFilter = new FIRFilter(myFilterTapsFilename); cifFilter->enrol_at(*myRCs); } - myOutput = new OutputMemory(); + shared_ptr<OutputMemory> myOutput(new OutputMemory(dataOut)); + Resampler* cifRes = NULL; if (myOutputRate != 2048000) { cifRes = new Resampler(2048000, myOutputRate, mySpacing); } else { @@ -222,10 +226,7 @@ int DabModulator::process(Buffer* const dataIn, Buffer* dataOut) //////////////////////////////////////////////////////////////// // Processing FIC //////////////////////////////////////////////////////////////// - FicSource* fic = myEtiReader.getFic(); - PrbsGenerator* ficPrbs = NULL; - ConvEncoder* ficConv = NULL; - PuncturingEncoder* ficPunc = NULL; + shared_ptr<FicSource> fic(myEtiReader.getFic()); //////////////////////////////////////////////////////////////// // Data initialisation //////////////////////////////////////////////////////////////// @@ -241,13 +242,13 @@ int DabModulator::process(Buffer* const dataIn, Buffer* dataOut) PDEBUG(" Framesize: %zu\n", fic->getFramesize()); // Configuring prbs generator - ficPrbs = new PrbsGenerator(myFicSizeIn, 0x110); + shared_ptr<PrbsGenerator> ficPrbs(new PrbsGenerator(myFicSizeIn, 0x110)); // Configuring convolutionnal encoder - ficConv = new ConvEncoder(myFicSizeIn); + shared_ptr<ConvEncoder> ficConv(new ConvEncoder(myFicSizeIn)); // Configuring puncturing encoder - ficPunc = new PuncturingEncoder(); + shared_ptr<PuncturingEncoder> ficPunc(new PuncturingEncoder()); std::vector<PuncturingRule*> rules = fic->get_rules(); std::vector<PuncturingRule*>::const_iterator rule; for (rule = rules.begin(); rule != rules.end(); ++rule) { @@ -267,16 +268,12 @@ int DabModulator::process(Buffer* const dataIn, Buffer* dataOut) //////////////////////////////////////////////////////////////// // Configuring subchannels //////////////////////////////////////////////////////////////// - std::vector<SubchannelSource*> subchannels = + std::vector<shared_ptr<SubchannelSource> > subchannels = myEtiReader.getSubchannels(); - std::vector<SubchannelSource*>::const_iterator subchannel; + std::vector<shared_ptr<SubchannelSource> >::const_iterator subchannel; for (subchannel = subchannels.begin(); subchannel != subchannels.end(); ++subchannel) { - PrbsGenerator* subchPrbs = NULL; - ConvEncoder* subchConv = NULL; - PuncturingEncoder* subchPunc = NULL; - TimeInterleaver* subchInterleaver = NULL; //////////////////////////////////////////////////////////// // Data initialisation @@ -307,13 +304,17 @@ int DabModulator::process(Buffer* const dataIn, Buffer* dataOut) (*subchannel)->protectionOption()); // Configuring prbs genrerator - subchPrbs = new PrbsGenerator(subchSizeIn, 0x110); + shared_ptr<PrbsGenerator> subchPrbs( + new PrbsGenerator(subchSizeIn, 0x110)); // Configuring convolutionnal encoder - subchConv = new ConvEncoder(subchSizeIn); + shared_ptr<ConvEncoder> subchConv( + new ConvEncoder(subchSizeIn)); // Configuring puncturing encoder - subchPunc = new PuncturingEncoder(); + shared_ptr<PuncturingEncoder> subchPunc( + new PuncturingEncoder()); + std::vector<PuncturingRule*> rules = (*subchannel)->get_rules(); std::vector<PuncturingRule*>::const_iterator rule; for (rule = rules.begin(); rule != rules.end(); ++rule) { @@ -326,7 +327,8 @@ int DabModulator::process(Buffer* const dataIn, Buffer* dataOut) subchPunc->append_tail_rule(PuncturingRule(3, 0xcccccc)); // Configuring time interleaver - subchInterleaver = new TimeInterleaver(subchSizeOut); + shared_ptr<TimeInterleaver> subchInterleaver( + new TimeInterleaver(subchSizeOut)); myFlowgraph->connect(*subchannel, subchPrbs); myFlowgraph->connect(subchPrbs, subchConv); @@ -342,7 +344,7 @@ int DabModulator::process(Buffer* const dataIn, Buffer* dataOut) myFlowgraph->connect(cifFreq, cifDiff); myFlowgraph->connect(cifNull, cifSig); myFlowgraph->connect(cifDiff, cifSig); - if (myClockRate) { + if (useCicEq) { myFlowgraph->connect(cifSig, cifCicEq); myFlowgraph->connect(cifCicEq, cifOfdm); } else { @@ -352,18 +354,21 @@ int DabModulator::process(Buffer* const dataIn, Buffer* dataOut) myFlowgraph->connect(cifGain, cifGuard); if (myFilterTapsFilename != "") { - myFlowgraph->connect(cifGuard, cifFilter); + shared_ptr<FIRFilter> cifFilterptr(cifFilter); + myFlowgraph->connect(cifGuard, cifFilterptr); if (cifRes != NULL) { - myFlowgraph->connect(cifFilter, cifRes); - myFlowgraph->connect(cifRes, myOutput); + shared_ptr<Resampler> res(cifRes); + myFlowgraph->connect(cifFilterptr, res); + myFlowgraph->connect(res, myOutput); } else { - myFlowgraph->connect(cifFilter, myOutput); + myFlowgraph->connect(cifFilterptr, myOutput); } } else { //no filtering if (cifRes != NULL) { - myFlowgraph->connect(cifGuard, cifRes); - myFlowgraph->connect(cifRes, myOutput); + shared_ptr<Resampler> res(cifRes); + myFlowgraph->connect(cifGuard, res); + myFlowgraph->connect(res, myOutput); } else { myFlowgraph->connect(cifGuard, myOutput); } @@ -374,6 +379,6 @@ int DabModulator::process(Buffer* const dataIn, Buffer* dataOut) //////////////////////////////////////////////////////////////////// // Proccessing data //////////////////////////////////////////////////////////////////// - myOutput->setOutput(dataOut); return myFlowgraph->run(); } + diff --git a/src/DabModulator.h b/src/DabModulator.h index 84c9926..1a9e477 100644 --- a/src/DabModulator.h +++ b/src/DabModulator.h @@ -3,8 +3,10 @@ Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Includes modifications for which no copyright is claimed - 2012, Matthias P. Braendli, matthias.braendli@mpb.li + Copyright (C) 2015 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://opendigitalradio.org */ /* This file is part of ODR-DabMod. @@ -32,6 +34,7 @@ #include <sys/types.h> #include <string> +#include <boost/shared_ptr.hpp> #include "ModCodec.h" #include "EtiReader.h" @@ -46,9 +49,8 @@ class DabModulator : public ModCodec { public: DabModulator( - struct modulator_offset_config& modconf, + double tist_offset_s, unsigned tist_delay_stages, RemoteControllers* rcs, - Logger& logger, unsigned outputRate = 2048000, unsigned clockRate = 0, unsigned dabMode = 0, GainMode gainMode = GAIN_VAR, float digGain = 1.0, float normalise = 1.0, @@ -63,8 +65,6 @@ public: EtiReader* getEtiReader() { return &myEtiReader; } protected: - Logger& myLogger; - void setMode(unsigned mode); unsigned myOutputRate; @@ -88,5 +88,5 @@ protected: size_t myFicSizeIn; }; - #endif // DAB_MODULATOR_H + diff --git a/src/EtiReader.cpp b/src/EtiReader.cpp index fe54f55..f584275 100644 --- a/src/EtiReader.cpp +++ b/src/EtiReader.cpp @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2014 + Copyright (C) 2014, 2015 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -34,6 +34,7 @@ #include <string.h> #include <arpa/inet.h> +using namespace boost; enum ETI_READER_STATE { EtiReaderStateNbFrame, @@ -50,16 +51,20 @@ enum ETI_READER_STATE { }; -EtiReader::EtiReader(struct modulator_offset_config& modconf, - Logger& logger) : - myLogger(logger), +EtiReader::EtiReader( + double tist_offset_s, + unsigned tist_delay_stages, + RemoteControllers* rcs) : state(EtiReaderStateSync), myFicSource(NULL), - myTimestampDecoder(modconf, myLogger) + myTimestampDecoder(tist_offset_s, tist_delay_stages) { PDEBUG("EtiReader::EtiReader()\n"); + myTimestampDecoder.enrol_at(*rcs); + myCurrentFrame = 0; + eti_fc_valid = false; } EtiReader::~EtiReader() @@ -69,9 +74,6 @@ EtiReader::~EtiReader() // if (myFicSource != NULL) { // delete myFicSource; // } -// for (unsigned i = 0; i < mySources.size(); ++i) { -// delete mySources[i]; -// } } @@ -83,23 +85,29 @@ FicSource* EtiReader::getFic() unsigned EtiReader::getMode() { + if (not eti_fc_valid) { + throw std::runtime_error("Trying to access Mode before it is ready!"); + } return eti_fc.MID; } unsigned EtiReader::getFp() { + if (not eti_fc_valid) { + throw std::runtime_error("Trying to access FP before it is ready!"); + } return eti_fc.FP; } -const std::vector<SubchannelSource*>& EtiReader::getSubchannels() +const std::vector<boost::shared_ptr<SubchannelSource> >& EtiReader::getSubchannels() { return mySources; } -int EtiReader::process(Buffer* dataIn) +int EtiReader::process(const Buffer* dataIn) { PDEBUG("EtiReader::process(dataIn: %p)\n", dataIn); PDEBUG(" state: %u\n", state); @@ -146,6 +154,7 @@ int EtiReader::process(Buffer* dataIn) return dataIn->getLength() - input_size; } memcpy(&eti_fc, in, 4); + eti_fc_valid = true; input_size -= 4; framesize -= 4; in += 4; @@ -171,13 +180,12 @@ int EtiReader::process(Buffer* dataIn) (memcmp(&eti_stc[0], in, 4 * eti_fc.NST))) { PDEBUG("New stc!\n"); eti_stc.resize(eti_fc.NST); - for (unsigned i = 0; i < mySources.size(); ++i) { - delete mySources[i]; - } - mySources.resize(eti_fc.NST); memcpy(&eti_stc[0], in, 4 * eti_fc.NST); + + mySources.clear(); for (unsigned i = 0; i < eti_fc.NST; ++i) { - mySources[i] = new SubchannelSource(eti_stc[i]); + mySources.push_back(shared_ptr<SubchannelSource>( + new SubchannelSource(eti_stc[i]))); PDEBUG("Sstc %u:\n", i); PDEBUG(" Stc%i.scid: %i\n", i, eti_stc[i].SCID); PDEBUG(" Stc%i.sad: %u\n", i, eti_stc[i].getStartAddress()); @@ -281,11 +289,6 @@ int EtiReader::process(Buffer* dataIn) myTimestampDecoder.updateTimestampEti(eti_fc.FP & 0x3, eti_eoh.MNSC, getPPSOffset(), eti_fc.FCT); - if (eti_fc.FCT % 125 == 0) //every 3 seconds is fine enough - { - myTimestampDecoder.updateModulatorOffset(); - } - return dataIn->getLength() - input_size; } diff --git a/src/EtiReader.h b/src/EtiReader.h index 209b208..84ad9b4 100644 --- a/src/EtiReader.h +++ b/src/EtiReader.h @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2014 + Copyright (C) 2014, 2015 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -41,12 +41,16 @@ #include <vector> #include <stdint.h> #include <sys/types.h> +#include <boost/shared_ptr.hpp> class EtiReader { public: - EtiReader(struct modulator_offset_config& modconf, Logger& logger); + EtiReader( + double tist_offset_s, + unsigned tist_delay_stages, + RemoteControllers* rcs); virtual ~EtiReader(); EtiReader(const EtiReader&); EtiReader& operator=(const EtiReader&); @@ -54,8 +58,8 @@ public: FicSource* getFic(); unsigned getMode(); unsigned getFp(); - const std::vector<SubchannelSource*>& getSubchannels(); - int process(Buffer* dataIn); + const std::vector<boost::shared_ptr<SubchannelSource> >& getSubchannels(); + int process(const Buffer* dataIn); void calculateTimestamp(struct frame_timestamp& ts) { @@ -66,9 +70,6 @@ public: bool sourceContainsTimestamp(); protected: - /* Main program logger */ - Logger& myLogger; - /* Transform the ETI TIST to a PPS offset in ms */ double getPPSOffset(); @@ -83,14 +84,14 @@ protected: eti_EOF eti_eof; eti_TIST eti_tist; FicSource* myFicSource; - std::vector<SubchannelSource*> mySources; + std::vector<boost::shared_ptr<SubchannelSource> > mySources; TimestampDecoder myTimestampDecoder; - + private: size_t myCurrentFrame; - bool time_ext_enabled; - unsigned long timestamp_seconds; + bool eti_fc_valid; }; #endif // ETI_READER_H + diff --git a/src/FIRFilter.cpp b/src/FIRFilter.cpp index 805c6d2..b1ce618 100644 --- a/src/FIRFilter.cpp +++ b/src/FIRFilter.cpp @@ -36,6 +36,8 @@ #include <iostream> #include <fstream> +#include <boost/make_shared.hpp> + #ifdef __AVX__ # include <immintrin.h> #else @@ -58,11 +60,10 @@ void FIRFilterWorker::process(struct FIRFilterWorkerData *fwd) // the incoming buffer while(running) { - Buffer* dataIn; + boost::shared_ptr<Buffer> dataIn; fwd->input_queue.wait_and_pop(dataIn); - Buffer* dataOut; - dataOut = new Buffer(); + boost::shared_ptr<Buffer> dataOut = boost::make_shared<Buffer>(); dataOut->setLength(dataIn->getLength()); PDEBUG("FIRFilterWorker: dataIn->getLength() %zu\n", dataIn->getLength()); @@ -91,7 +92,7 @@ void FIRFilterWorker::process(struct FIRFilterWorkerData *fwd) fprintf(stderr, "FIRFilterWorker: out not aligned %p ", out); throw std::runtime_error("FIRFilterWorker: out not aligned"); } - + clock_gettime(CLOCK_THREAD_CPUTIME_ID, &time_start); __m256 AVXout; @@ -141,7 +142,7 @@ void FIRFilterWorker::process(struct FIRFilterWorkerData *fwd) fprintf(stderr, "FIRFilterWorker: out not aligned %p ", out); throw std::runtime_error("FIRFilterWorker: out not aligned"); } - + clock_gettime(CLOCK_THREAD_CPUTIME_ID, &time_start); __m128 SSEout; @@ -290,11 +291,10 @@ void FIRFilterWorker::process(struct FIRFilterWorkerData *fwd) } } #endif - + calculationTime += (time_end.tv_sec - time_start.tv_sec) * 1000000000L + time_end.tv_nsec - time_start.tv_nsec; fwd->output_queue.push(dataOut); - delete dataIn; } } @@ -393,17 +393,16 @@ int FIRFilter::process(Buffer* const dataIn, Buffer* dataOut) // This thread creates the dataIn buffer, and deletes // the outgoing buffer - Buffer* inbuffer = new Buffer(dataIn->getLength(), dataIn->getData()); + boost::shared_ptr<Buffer> inbuffer = + boost::make_shared<Buffer>(dataIn->getLength(), dataIn->getData()); firwd.input_queue.push(inbuffer); if (number_of_runs > 2) { - Buffer* outbuffer; + boost::shared_ptr<Buffer> outbuffer; firwd.output_queue.wait_and_pop(outbuffer); dataOut->setData(outbuffer->getData(), outbuffer->getLength()); - - delete outbuffer; } else { dataOut->setLength(dataIn->getLength()); diff --git a/src/FIRFilter.h b/src/FIRFilter.h index 0ecae3e..751be91 100644 --- a/src/FIRFilter.h +++ b/src/FIRFilter.h @@ -30,7 +30,7 @@ #endif #include <boost/thread.hpp> -#include "ThreadsafeQueue.h" +#include <boost/shared_ptr.hpp> #include "RemoteControl.h" #include "ModCodec.h" @@ -52,8 +52,8 @@ struct FIRFilterWorkerData { /* Thread-safe queues to give data to and get data from * the worker */ - ThreadsafeQueue<Buffer*> input_queue; - ThreadsafeQueue<Buffer*> output_queue; + ThreadsafeQueue<boost::shared_ptr<Buffer> > input_queue; + ThreadsafeQueue<boost::shared_ptr<Buffer> > output_queue; /* Remote-control can change the taps while the filter * runs. This lock makes sure nothing bad happens when @@ -127,5 +127,5 @@ protected: struct FIRFilterWorkerData firwd; }; - #endif //FIRFILTER_H + diff --git a/src/Flowgraph.cpp b/src/Flowgraph.cpp index dd9c68b..3844e86 100644 --- a/src/Flowgraph.cpp +++ b/src/Flowgraph.cpp @@ -1,6 +1,11 @@ /* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) + + Copyright (C) 2015 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://opendigitalradio.org */ /* This file is part of ODR-DabMod. @@ -38,16 +43,18 @@ #include <sys/time.h> #endif +using namespace boost; -typedef std::vector<Node*>::iterator NodeIterator; -typedef std::vector<Edge*>::iterator EdgeIterator; +typedef std::vector<shared_ptr<Node> >::iterator NodeIterator; +typedef std::vector<shared_ptr<Edge> >::iterator EdgeIterator; -Node::Node(ModPlugin* plugin) : +Node::Node(shared_ptr<ModPlugin> plugin) : myPlugin(plugin), myProcessTime(0) { - PDEBUG("Node::Node(plugin(%s): %p) @ %p\n", plugin->name(), plugin, this); + PDEBUG("Node::Node(plugin(%s): %p) @ %p\n", + plugin->name(), plugin.get(), this); } @@ -56,24 +63,21 @@ Node::~Node() { PDEBUG("Node::~Node() @ %p\n", this); - if (myPlugin != NULL) { - delete myPlugin; - } assert(myInputBuffers.size() == 0); assert(myOutputBuffers.size() == 0); } -Edge::Edge(Node* srcNode, Node* dstNode) : +Edge::Edge(shared_ptr<Node>& srcNode, shared_ptr<Node>& dstNode) : mySrcNode(srcNode), myDstNode(dstNode) { PDEBUG("Edge::Edge(srcNode(%s): %p, dstNode(%s): %p) @ %p\n", - srcNode->plugin()->name(), srcNode, - dstNode->plugin()->name(), dstNode, + srcNode->plugin()->name(), srcNode.get(), + dstNode->plugin()->name(), dstNode.get(), this); - myBuffer = new Buffer(); + myBuffer = shared_ptr<Buffer>(new Buffer()); srcNode->myOutputBuffers.push_back(myBuffer); dstNode->myInputBuffers.push_back(myBuffer); } @@ -83,7 +87,7 @@ Edge::~Edge() { PDEBUG("Edge::~Edge() @ %p\n", this); - std::vector<Buffer*>::iterator buffer; + std::vector<shared_ptr<Buffer> >::iterator buffer; if (myBuffer != NULL) { for (buffer = mySrcNode->myOutputBuffers.begin(); buffer != mySrcNode->myOutputBuffers.end(); @@ -102,7 +106,6 @@ Edge::~Edge() break; } } - delete myBuffer; } } @@ -110,9 +113,26 @@ Edge::~Edge() int Node::process() { PDEBUG("Edge::process()\n"); - PDEBUG(" Plugin name: %s (%p)\n", myPlugin->name(), myPlugin); + PDEBUG(" Plugin name: %s (%p)\n", myPlugin->name(), myPlugin.get()); + + // the plugin process() still wants vector<Buffer*> + // arguments. + std::vector<Buffer*> inBuffers; + std::vector<shared_ptr<Buffer> >::iterator buffer; + for (buffer = myInputBuffers.begin(); + buffer != myInputBuffers.end(); + ++buffer) { + inBuffers.push_back(buffer->get()); + } - return myPlugin->process(myInputBuffers, myOutputBuffers); + std::vector<Buffer*> outBuffers; + for (buffer = myOutputBuffers.begin(); + buffer != myOutputBuffers.end(); + ++buffer) { + outBuffers.push_back(buffer->get()); + } + + return myPlugin->process(inBuffers, outBuffers); } @@ -128,35 +148,26 @@ Flowgraph::~Flowgraph() { PDEBUG("Flowgraph::~Flowgraph() @ %p\n", this); - std::vector<Edge*>::const_iterator edge; - for (edge = edges.begin(); edge != edges.end(); ++edge) { - delete *edge; - } - if (myProcessTime) { fprintf(stderr, "Process time:\n"); - } - std::vector<Node*>::const_iterator node; - for (node = nodes.begin(); node != nodes.end(); ++node) { - if (myProcessTime) { + + std::vector<shared_ptr<Node> >::const_iterator node; + for (node = nodes.begin(); node != nodes.end(); ++node) { fprintf(stderr, " %30s: %10u us (%2.2f %%)\n", (*node)->plugin()->name(), (unsigned)(*node)->processTime(), (*node)->processTime() * 100.0 / myProcessTime); } - delete *node; - } - if (myProcessTime) { + fprintf(stderr, " %30s: %10u us (100.00 %%)\n", "total", (unsigned)myProcessTime); } } - -void Flowgraph::connect(ModPlugin* input, ModPlugin* output) +void Flowgraph::connect(shared_ptr<ModPlugin> input, shared_ptr<ModPlugin> output) { PDEBUG("Flowgraph::connect(input(%s): %p, output(%s): %p)\n", - input->name(), input, output->name(), output); + input->name(), input.get(), output->name(), output.get()); NodeIterator inputNode; NodeIterator outputNode; @@ -167,7 +178,7 @@ void Flowgraph::connect(ModPlugin* input, ModPlugin* output) } } if (inputNode == nodes.end()) { - inputNode = nodes.insert(nodes.end(), new Node(input)); + inputNode = nodes.insert(nodes.end(), shared_ptr<Node>(new Node(input))); } for (outputNode = nodes.begin(); outputNode != nodes.end(); ++outputNode) { @@ -176,14 +187,14 @@ void Flowgraph::connect(ModPlugin* input, ModPlugin* output) } } if (outputNode == nodes.end()) { - outputNode = nodes.insert(nodes.end(), new Node(output)); + outputNode = nodes.insert(nodes.end(), shared_ptr<Node>(new Node(output))); for (inputNode = nodes.begin(); inputNode != nodes.end(); ++inputNode) { if ((*inputNode)->plugin() == input) { break; } } } else if (inputNode > outputNode) { - Node* node = *outputNode; + shared_ptr<Node> node = *outputNode; nodes.erase(outputNode); outputNode = nodes.insert(nodes.end(), node); for (inputNode = nodes.begin(); inputNode != nodes.end(); ++inputNode) { @@ -196,7 +207,7 @@ void Flowgraph::connect(ModPlugin* input, ModPlugin* output) assert((*inputNode)->plugin() == input); assert((*outputNode)->plugin() == output); - edges.push_back(new Edge(*inputNode, *outputNode)); + edges.push_back(shared_ptr<Edge>(new Edge(*inputNode, *outputNode))); } @@ -204,7 +215,7 @@ bool Flowgraph::run() { PDEBUG("Flowgraph::run()\n"); - std::vector<Node*>::const_iterator node; + std::vector<shared_ptr<Node> >::const_iterator node; timeval start, stop; time_t diff; @@ -224,3 +235,4 @@ bool Flowgraph::run() } return true; } + diff --git a/src/Flowgraph.h b/src/Flowgraph.h index 178b6a9..1129668 100644 --- a/src/Flowgraph.h +++ b/src/Flowgraph.h @@ -1,6 +1,11 @@ /* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) + + Copyright (C) 2015 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://opendigitalradio.org */ /* This file is part of ODR-DabMod. @@ -32,20 +37,21 @@ #include <sys/types.h> #include <vector> +#include <boost/shared_ptr.hpp> class Node { public: - Node(ModPlugin* plugin); + Node(boost::shared_ptr<ModPlugin> plugin); ~Node(); Node(const Node&); Node& operator=(const Node&); - ModPlugin* plugin() { return myPlugin; } + boost::shared_ptr<ModPlugin> plugin() { return myPlugin; } - std::vector<Buffer*> myInputBuffers; - std::vector<Buffer*> myOutputBuffers; + std::vector<boost::shared_ptr<Buffer> > myInputBuffers; + std::vector<boost::shared_ptr<Buffer> > myOutputBuffers; int process(); time_t processTime() { return myProcessTime; } @@ -54,7 +60,7 @@ public: } protected: - ModPlugin* myPlugin; + boost::shared_ptr<ModPlugin> myPlugin; time_t myProcessTime; }; @@ -62,15 +68,15 @@ protected: class Edge { public: - Edge(Node* src, Node* dst); + Edge(boost::shared_ptr<Node>& src, boost::shared_ptr<Node>& dst); ~Edge(); Edge(const Edge&); Edge& operator=(const Edge&); protected: - Node* mySrcNode; - Node* myDstNode; - Buffer* myBuffer; + boost::shared_ptr<Node> mySrcNode; + boost::shared_ptr<Node> myDstNode; + boost::shared_ptr<Buffer> myBuffer; }; @@ -82,14 +88,16 @@ public: Flowgraph(const Flowgraph&); Flowgraph& operator=(const Flowgraph&); - void connect(ModPlugin* input, ModPlugin* output); + void connect(boost::shared_ptr<ModPlugin> input, + boost::shared_ptr<ModPlugin> output); bool run(); protected: - std::vector<Node*> nodes; - std::vector<Edge*> edges; + std::vector<boost::shared_ptr<Node> > nodes; + std::vector<boost::shared_ptr<Edge> > edges; time_t myProcessTime; }; #endif // FLOWGRAPH_H + diff --git a/src/FrameMultiplexer.cpp b/src/FrameMultiplexer.cpp index c5e58b7..843f72d 100644 --- a/src/FrameMultiplexer.cpp +++ b/src/FrameMultiplexer.cpp @@ -30,8 +30,11 @@ typedef std::complex<float> complexf; +using namespace boost; -FrameMultiplexer::FrameMultiplexer(size_t framesize, const std::vector<SubchannelSource*>* subchannels) : +FrameMultiplexer::FrameMultiplexer( + size_t framesize, + const std::vector<shared_ptr<SubchannelSource> >* subchannels) : ModMux(ModFormat(framesize), ModFormat(framesize)), d_frameSize(framesize), mySubchannels(subchannels) @@ -76,7 +79,7 @@ int FrameMultiplexer::process(std::vector<Buffer*> dataIn, Buffer* dataOut) ++in; // Write subchannel assert(mySubchannels->size() == dataIn.size() - 1); - std::vector<SubchannelSource*>::const_iterator subchannel = + std::vector<shared_ptr<SubchannelSource> >::const_iterator subchannel = mySubchannels->begin(); while (in != dataIn.end()) { assert((*subchannel)->framesizeCu() * 8 == (*in)->getLength()); @@ -88,3 +91,4 @@ int FrameMultiplexer::process(std::vector<Buffer*> dataIn, Buffer* dataOut) return dataOut->getLength(); } + diff --git a/src/FrameMultiplexer.h b/src/FrameMultiplexer.h index f1bd587..ba571f6 100644 --- a/src/FrameMultiplexer.h +++ b/src/FrameMultiplexer.h @@ -29,7 +29,7 @@ #include "ModMux.h" #include "SubchannelSource.h" - +#include <boost/shared_ptr.hpp> #include <sys/types.h> @@ -37,7 +37,8 @@ class FrameMultiplexer : public ModMux { public: - FrameMultiplexer(size_t frameSize, const std::vector<SubchannelSource*>* subchannels); + FrameMultiplexer(size_t frameSize, + const std::vector<boost::shared_ptr<SubchannelSource> >* subchannels); virtual ~FrameMultiplexer(); FrameMultiplexer(const FrameMultiplexer&); FrameMultiplexer& operator=(const FrameMultiplexer&); @@ -48,8 +49,8 @@ public: protected: size_t d_frameSize; - const std::vector<SubchannelSource*>* mySubchannels; + const std::vector<boost::shared_ptr<SubchannelSource> >* mySubchannels; }; - #endif // FRAME_MULTIPLEXER_H + diff --git a/src/InputFileReader.cpp b/src/InputFileReader.cpp index 205fbfa..84f0be4 100644 --- a/src/InputFileReader.cpp +++ b/src/InputFileReader.cpp @@ -49,8 +49,7 @@ int InputFileReader::Open(std::string filename, bool loop) loop_ = loop; inputfile_ = fopen(filename_.c_str(), "r"); if (inputfile_ == NULL) { - fprintf(stderr, "Unable to open input file!\n"); - logger_.level(error) << "Unable to open input file!"; + etiLog.level(error) << "Unable to open input file!"; perror(filename_.c_str()); return -1; } @@ -79,8 +78,7 @@ int InputFileReader::IdentifyType() char discard_buffer[6144]; if (fread(&sync, sizeof(sync), 1, inputfile_) != 1) { - fprintf(stderr, "Unable to read sync in input file!\n"); - logger_.level(error) << "Unable to read sync in input file!"; + etiLog.level(error) << "Unable to read sync in input file!"; perror(filename_.c_str()); return -1; } @@ -96,8 +94,7 @@ int InputFileReader::IdentifyType() // if the seek fails, consume the rest of the frame if (fread(discard_buffer, 6144 - sizeof(sync), 1, inputfile_) != 1) { - fprintf(stderr, "Unable to read from input file!\n"); - logger_.level(error) << "Unable to read from input file!"; + etiLog.level(error) << "Unable to read from input file!"; perror(filename_.c_str()); return -1; } @@ -108,8 +105,7 @@ int InputFileReader::IdentifyType() nbFrames = sync; if (fread(&frameSize, sizeof(frameSize), 1, inputfile_) != 1) { - fprintf(stderr, "Unable to read frame size in input file!\n"); - logger_.level(error) << "Unable to read frame size in input file!"; + etiLog.level(error) << "Unable to read frame size in input file!"; perror(filename_.c_str()); return -1; } @@ -130,8 +126,7 @@ int InputFileReader::IdentifyType() // if the seek fails, consume the rest of the frame if (fread(discard_buffer, frameSize - 4, 1, inputfile_) != 1) { - fprintf(stderr, "Unable to read from input file!\n"); - logger_.level(error) << "Unable to read from input file!"; + etiLog.level(error) << "Unable to read from input file!"; perror(filename_.c_str()); return -1; } @@ -141,8 +136,7 @@ int InputFileReader::IdentifyType() } if (fread(&sync, sizeof(sync), 1, inputfile_) != 1) { - fprintf(stderr, "Unable to read nb frame in input file!\n"); - logger_.level(error) << "Unable to read nb frame in input file!"; + etiLog.level(error) << "Unable to read nb frame in input file!"; perror(filename_.c_str()); return -1; } @@ -152,8 +146,7 @@ int InputFileReader::IdentifyType() // if the seek fails, consume the rest of the frame if (fread(discard_buffer, frameSize - 4, 1, inputfile_) != 1) { - fprintf(stderr, "Unable to read from input file!\n"); - logger_.level(error) << "Unable to read from input file!"; + etiLog.level(error) << "Unable to read from input file!"; perror(filename_.c_str()); return -1; } @@ -168,8 +161,7 @@ int InputFileReader::IdentifyType() sync >>= 8; sync &= 0xffffff; if (fread((uint8_t*)&sync + 3, 1, 1, inputfile_) != 1) { - fprintf(stderr, "Unable to read from input file!\n"); - logger_.level(error) << "Unable to read from input file!"; + etiLog.level(error) << "Unable to read from input file!"; perror(filename_.c_str()); return -1; } @@ -184,8 +176,7 @@ int InputFileReader::IdentifyType() if (fseek(inputfile_, -sizeof(sync), SEEK_CUR) != 0) { if (fread(discard_buffer, 6144 - sizeof(sync), 1, inputfile_) != 1) { - fprintf(stderr, "Unable to read from input file!\n"); - logger_.level(error) << "Unable to read from input file!"; + etiLog.level(error) << "Unable to read from input file!"; perror(filename_.c_str()); return -1; } @@ -195,8 +186,7 @@ int InputFileReader::IdentifyType() } } - fprintf(stderr, "Bad input file format!\n"); - logger_.level(error) << "Bad input file format!"; + etiLog.level(error) << "Bad input file format!"; return -1; } @@ -236,18 +226,18 @@ int InputFileReader::GetNextFrame(void* buffer) } else { if (fread(&frameSize, sizeof(frameSize), 1, inputfile_) != 1) { - logger_.level(error) << "Reached end of file."; + etiLog.level(error) << "Reached end of file."; if (loop_) { if (Rewind() == 0) { if (fread(&frameSize, sizeof(frameSize), 1, inputfile_) != 1) { PDEBUG("Error after rewinding file!\n"); - logger_.level(error) << "Error after rewinding file!"; + etiLog.level(error) << "Error after rewinding file!"; return -1; } } else { PDEBUG("Impossible to rewind file!\n"); - logger_.level(error) << "Impossible to rewind file!"; + etiLog.level(error) << "Impossible to rewind file!"; return -1; } } @@ -257,8 +247,7 @@ int InputFileReader::GetNextFrame(void* buffer) } } if (frameSize > 6144) { // there might be a better limit - logger_.level(error) << "Wrong frame size " << frameSize << " in ETI file!"; - fprintf(stderr, "Wrong frame size %u in ETI file!\n", frameSize); + etiLog.level(error) << "Wrong frame size " << frameSize << " in ETI file!"; return -1; } @@ -275,7 +264,7 @@ int InputFileReader::GetNextFrame(void* buffer) } else { PDEBUG("Impossible to rewind file!\n"); - logger_.level(error) << "Impossible to rewind file!"; + etiLog.level(error) << "Impossible to rewind file!"; return -1; } } @@ -285,12 +274,8 @@ int InputFileReader::GetNextFrame(void* buffer) // A short read of a frame (i.e. reading an incomplete frame) // is not tolerated. Input files must not contain incomplete frames if (read_bytes != 0) { - fprintf(stderr, - "Unable to read a complete frame of %u data bytes from input file!\n", - frameSize); - - perror(filename_.c_str()); - logger_.level(error) << "Unable to read from input file!"; + etiLog.level(error) << + "Unable to read a complete frame of " << frameSize << " data bytes from input file!"; return -1; } else { diff --git a/src/InputReader.h b/src/InputReader.h index 3e0dcab..b262cc9 100644 --- a/src/InputReader.h +++ b/src/InputReader.h @@ -3,7 +3,7 @@ Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyrigth (C) 2013 + Copyrigth (C) 2013, 2015 Matthias P. Braendli, matthias.braendli@mpb.li */ /* @@ -31,6 +31,8 @@ #endif #include <cstdio> +#include <vector> +#include <boost/shared_ptr.hpp> #if defined(HAVE_ZEROMQ) # include "zmq.hpp" # include "ThreadsafeQueue.h" @@ -85,15 +87,15 @@ class InputReader class InputFileReader : public InputReader { public: - InputFileReader(Logger logger) : + InputFileReader() : streamtype_(ETI_STREAM_TYPE_NONE), - inputfile_(NULL), logger_(logger) {}; + inputfile_(NULL) { } ~InputFileReader() { - fprintf(stderr, "\nClosing input file...\n"); - if (inputfile_ != NULL) { + fprintf(stderr, "\nClosing input file...\n"); + fclose(inputfile_); } } @@ -113,6 +115,9 @@ class InputFileReader : public InputReader } private: + InputFileReader(const InputFileReader& other); + InputFileReader& operator=(const InputFileReader& other); + int IdentifyType(); // Rewind the file, and replay anew @@ -123,20 +128,30 @@ class InputFileReader : public InputReader std::string filename_; EtiStreamType streamtype_; FILE* inputfile_; - Logger logger_; size_t inputfilelength_; uint64_t nbframes_; // 64-bit because 32-bit overflow is // after 2**32 * 24ms ~= 3.3 years }; +struct zmq_input_overflow : public std::exception +{ + const char* what () const throw () + { + return "InputZMQ buffer overflow"; + } +}; + #if defined(HAVE_ZEROMQ) /* A ZeroMQ input. See www.zeromq.org for more info */ struct InputZeroMQThreadData { - ThreadsafeQueue<uint8_t*> *in_messages; + ThreadsafeQueue<boost::shared_ptr<std::vector<uint8_t> > > *in_messages; std::string uri; + unsigned max_queued_frames; + + bool running; }; class InputZeroMQWorker @@ -168,10 +183,10 @@ class InputZeroMQWorker class InputZeroMQReader : public InputReader { public: - InputZeroMQReader(Logger logger) : - logger_(logger), in_messages_(10) + InputZeroMQReader() { workerdata_.in_messages = &in_messages_; + workerdata_.running = false; } ~InputZeroMQReader() @@ -179,21 +194,22 @@ class InputZeroMQReader : public InputReader worker_.Stop(); } - int Open(std::string uri); + int Open(const std::string& uri, unsigned max_queued_frames); int GetNextFrame(void* buffer); void PrintInfo(); private: - InputZeroMQReader(const InputZeroMQReader& other) {} - Logger logger_; + InputZeroMQReader(const InputZeroMQReader& other); + InputZeroMQReader& operator=(const InputZeroMQReader& other); std::string uri_; InputZeroMQWorker worker_; - ThreadsafeQueue<uint8_t*> in_messages_; + ThreadsafeQueue<boost::shared_ptr<std::vector<uint8_t> > > in_messages_; struct InputZeroMQThreadData workerdata_; }; #endif #endif + diff --git a/src/InputZeroMQReader.cpp b/src/InputZeroMQReader.cpp index f7f5702..36d4e4b 100644 --- a/src/InputZeroMQReader.cpp +++ b/src/InputZeroMQReader.cpp @@ -3,7 +3,7 @@ Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2013, 2014 + Copyright (C) 2013, 2014, 2015 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -37,12 +37,11 @@ #include <stdint.h> #include "zmq.hpp" #include <boost/thread/thread.hpp> +#include <boost/make_shared.hpp> #include "porting.h" #include "InputReader.h" #include "PcDebug.h" -#define MAX_QUEUE_SIZE 50 - #define NUM_FRAMES_PER_ZMQ_MESSAGE 4 /* A concatenation of four ETI frames, * whose maximal size is 6144. @@ -64,10 +63,18 @@ struct zmq_dab_message_t uint8_t buf[NUM_FRAMES_PER_ZMQ_MESSAGE*6144]; }; -int InputZeroMQReader::Open(std::string uri) +int InputZeroMQReader::Open(const std::string& uri, unsigned max_queued_frames) { - uri_ = uri; + // The URL might start with zmq+tcp:// + if (uri.substr(0, 4) == "zmq+") { + uri_ = uri.substr(4); + } + else { + uri_ = uri; + } + workerdata_.uri = uri; + workerdata_.max_queued_frames = max_queued_frames; // launch receiver thread worker_.Start(&workerdata_); @@ -78,12 +85,25 @@ int InputZeroMQReader::GetNextFrame(void* buffer) { const size_t framesize = 6144; - uint8_t* incoming; - in_messages_.wait_and_pop(incoming); + boost::shared_ptr<std::vector<uint8_t> > incoming; - memcpy(buffer, incoming, framesize); + /* Do some prebuffering because reads will happen in bursts + * (4 ETI frames in TM1) and we should make sure that + * we can serve the data required for a full transmission frame. + */ + if (in_messages_.size() < 4) { + const size_t prebuffering = 10; + in_messages_.wait_and_pop(incoming, prebuffering); + } + else { + in_messages_.wait_and_pop(incoming); + } - delete incoming; + if (! workerdata_.running) { + throw zmq_input_overflow(); + } + + memcpy(buffer, &incoming->front(), framesize); return framesize; } @@ -123,18 +143,16 @@ void InputZeroMQWorker::RecvProcess(struct InputZeroMQThreadData* workerdata) } m_to_drop--; } - else if (queue_size < MAX_QUEUE_SIZE) { + else if (queue_size < workerdata->max_queued_frames) { if (buffer_full) { - fprintf(stderr, "ZeroMQ buffer recovered: %zu elements\n", - queue_size); + etiLog.level(info) << "ZeroMQ buffer recovered: " << queue_size << " elements"; buffer_full = false; } zmq_dab_message_t* dab_msg = (zmq_dab_message_t*)incoming.data(); if (dab_msg->version != 1) { - fprintf(stderr, "ZeroMQ input: wrong packet version %d\n", - dab_msg->version); + etiLog.level(error) << "ZeroMQ wrong packet version " << dab_msg->version; } int offset = sizeof(dab_msg->version) + @@ -145,23 +163,20 @@ void InputZeroMQWorker::RecvProcess(struct InputZeroMQThreadData* workerdata) if (dab_msg->buflen[i] <= 0 || dab_msg->buflen[i] > 6144) { - fprintf(stderr, "ZeroMQ buffer %d: invalid length %d\n", - i, dab_msg->buflen[i]); + etiLog.level(error) << "ZeroMQ buffer " << i << " has invalid length " << + dab_msg->buflen[i]; // TODO error handling } else { - uint8_t* buf = new uint8_t[6144]; + boost::shared_ptr<std::vector<uint8_t> > buf = + boost::make_shared<std::vector<uint8_t> >(6144, 0x55); const int framesize = dab_msg->buflen[i]; - memcpy(buf, + memcpy(&buf->front(), ((uint8_t*)incoming.data()) + offset, framesize); - // pad to 6144 bytes - memset(&((uint8_t*)buf)[framesize], - 0x55, 6144 - framesize); - offset += framesize; queue_size = workerdata->in_messages->push(buf); @@ -172,9 +187,10 @@ void InputZeroMQWorker::RecvProcess(struct InputZeroMQThreadData* workerdata) workerdata->in_messages->notify(); if (!buffer_full) { - fprintf(stderr, "ZeroMQ buffer overfull !\n"); + etiLog.level(warn) << "ZeroMQ buffer overfull !"; buffer_full = true; + throw std::runtime_error("ZMQ input full"); } queue_size = workerdata->in_messages->size(); @@ -188,23 +204,28 @@ void InputZeroMQWorker::RecvProcess(struct InputZeroMQThreadData* workerdata) } if (queue_size < 5) { - fprintf(stderr, "ZeroMQ buffer underfull: %zu elements !\n", - queue_size); + etiLog.level(warn) << "ZeroMQ buffer low: " << queue_size << " elements !"; } } } catch (zmq::error_t& err) { - fprintf(stderr, "ZeroMQ error in RecvProcess: '%s'\n", err.what()); + etiLog.level(error) << "ZeroMQ error in RecvProcess: '" << err.what() << "'"; + } + catch (std::exception& err) { } - fprintf(stderr, "ZeroMQ input worker terminated\n"); + etiLog.level(info) << "ZeroMQ input worker terminated"; subscriber.close(); + + workerdata->running = false; + workerdata->in_messages->notify(); } void InputZeroMQWorker::Start(struct InputZeroMQThreadData* workerdata) { running = true; + workerdata->running = true; recv_thread = boost::thread(&InputZeroMQWorker::RecvProcess, this, workerdata); } diff --git a/src/Makefile.am b/src/Makefile.am deleted file mode 100644 index f8ba7c2..0000000 --- a/src/Makefile.am +++ /dev/null @@ -1,127 +0,0 @@ -# Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the -# Queen in Right of Canada (Communications Research Center Canada) - -# This file is part of ODR-DabMod. -# -# ODR-DabMod is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# ODR-DabMod is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with ODR-DabMod. If not, see <http://www.gnu.org/licenses/>. - -if IS_GIT_REPO -GITVERSION_FLAGS = -DGITVERSION="\"`git describe`\"" -else -GITVERSION_FLAGS = -endif - -if HAVE_SSE -SIMD_CFLAGS = -msse -msse2 -else -SIMD_CFLAGS = -endif - -bin_PROGRAMS = odr-dabmod - -if USE_KISS_FFT -FFT_DIR=$(top_builddir)/lib/kiss_fft129 -FFT_INC=-I$(FFT_DIR) -I$(FFT_DIR)/tools -FFT_SRC=$(FFT_DIR)/kiss_fft.c \ - $(FFT_DIR)/kiss_fft.h \ - $(FFT_DIR)/tools/kiss_fftr.c \ - $(FFT_DIR)/tools/kiss_fftr.h \ - kiss_fftsimd.c \ - kiss_fftsimd.h -FFT_FLG=-ffast-math - -.PHONY: kiss_fft129 reed-solomon-4.0 - -DabModulator.cpp: $(FFT_DIR) - -BUILT_SOURCES: $(FFT_DIR) - -FFT_LDADD= - -$(FFT_DIR): - if [ ! -e $(FFT_DIR) ]; then \ - tar xzf $(top_srcdir)/lib/kiss_fft129.tar.gz -C $(top_builddir)/lib; \ - fi - -else -FFT_LDADD= -FFT_DIR= -FFT_INC= -FFT_SRC= -FFT_FLG= -endif - -odr_dabmod_CPPFLAGS = -Wall \ - $(FFT_INC) $(FFT_FLG) $(SIMD_CFLAGS) $(GITVERSION_FLAGS) -odr_dabmod_LDADD = $(FFT_LDADD) -odr_dabmod_SOURCES = DabMod.cpp \ - PcDebug.h \ - porting.c porting.h \ - DabModulator.cpp DabModulator.h \ - Buffer.cpp Buffer.h \ - ModCodec.cpp ModCodec.h \ - ModPlugin.cpp ModPlugin.h \ - ModFormat.cpp ModFormat.h \ - EtiReader.cpp EtiReader.h \ - Eti.cpp Eti.h \ - FicSource.cpp FicSource.h \ - FIRFilter.cpp FIRFilter.h \ - ModInput.cpp ModInput.h \ - PuncturingRule.cpp PuncturingRule.h \ - PuncturingEncoder.cpp PuncturingEncoder.h \ - SubchannelSource.cpp SubchannelSource.h \ - Flowgraph.cpp Flowgraph.h \ - GainControl.cpp GainControl.h \ - OutputMemory.cpp OutputMemory.h \ - OutputZeroMQ.cpp OutputZeroMQ.h \ - TimestampDecoder.h TimestampDecoder.cpp \ - OutputUHD.cpp OutputUHD.h \ - ModOutput.cpp ModOutput.h \ - InputMemory.cpp InputMemory.h \ - InputFileReader.cpp InputZeroMQReader.cpp InputReader.h \ - OutputFile.cpp OutputFile.h \ - FrameMultiplexer.cpp FrameMultiplexer.h \ - ModMux.cpp ModMux.h \ - PrbsGenerator.cpp PrbsGenerator.h \ - BlockPartitioner.cpp BlockPartitioner.h \ - QpskSymbolMapper.cpp QpskSymbolMapper.h \ - FrequencyInterleaver.cpp FrequencyInterleaver.h \ - PhaseReference.cpp PhaseReference.h \ - DifferentialModulator.cpp DifferentialModulator.h \ - NullSymbol.cpp NullSymbol.h \ - SignalMultiplexer.cpp SignalMultiplexer.h \ - CicEqualizer.cpp CicEqualizer.h \ - OfdmGenerator.cpp OfdmGenerator.h \ - GuardIntervalInserter.cpp GuardIntervalInserter.h \ - Resampler.cpp Resampler.h \ - ConvEncoder.cpp ConvEncoder.h \ - TimeInterleaver.cpp TimeInterleaver.h \ - ThreadsafeQueue.h \ - Log.cpp Log.h \ - RemoteControl.cpp RemoteControl.h \ - FormatConverter.cpp FormatConverter.h \ - zmq.hpp - -nodist_odr_dabmod_SOURCES = $(FFT_SRC) - -dist_bin_SCRIPTS = crc-dwap.py - -if USE_KISS_FFT -EXTRA_DIST = kiss_fftsimd.c kiss_fftsimd.h - -clean-local: - rm -rf $(FFT_DIR) - -endif - diff --git a/src/OutputMemory.h b/src/OutputMemory.h index 2dd49c5..56cbc01 100644 --- a/src/OutputMemory.h +++ b/src/OutputMemory.h @@ -50,7 +50,7 @@ class OutputMemory : public ModOutput { public: - OutputMemory(Buffer* dataOut = NULL); + OutputMemory(Buffer* dataOut); virtual ~OutputMemory(); virtual int process(Buffer* dataIn, Buffer* dataOut); const char* name() { return "OutputMemory"; } diff --git a/src/OutputUHD.cpp b/src/OutputUHD.cpp index bfd24a8..b815a4c 100644 --- a/src/OutputUHD.cpp +++ b/src/OutputUHD.cpp @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2014 + Copyright (C) 2014, 2015 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -31,6 +31,11 @@ #include "PcDebug.h" #include "Log.h" #include "RemoteControl.h" +#include "Utils.h" + +#include <boost/thread/future.hpp> + +#include <uhd/utils/msg.hpp> #include <cmath> #include <iostream> @@ -46,23 +51,61 @@ using namespace std; typedef std::complex<float> complexf; +void uhd_msg_handler(uhd::msg::type_t type, const std::string &msg) +{ + if (type == uhd::msg::warning) { + etiLog.level(warn) << "UHD Warning: " << msg; + } + else if (type == uhd::msg::error) { + etiLog.level(error) << "UHD Error: " << msg; + } +} + +// Check function for GPS fixtype +bool check_gps_fix_ok(uhd::usrp::multi_usrp::sptr usrp) +{ + try { + std::string fixtype( + usrp->get_mboard_sensor("gps_fixtype", 0).to_pp_string()); + + if (fixtype.find("3d fix") == std::string::npos) { + etiLog.level(warn) << "OutputUHD: " << fixtype; + + return false; + } + + return true; + } + catch (uhd::lookup_error &e) { + etiLog.level(warn) << "OutputUHD: no gps_fixtype sensor"; + return false; + } +} + + OutputUHD::OutputUHD( - OutputUHDConfig& config, - Logger& logger) : + const OutputUHDConfig& config) : ModOutput(ModFormat(1), ModFormat(0)), RemoteControllable("uhd"), - myLogger(logger), myConf(config), // Since we don't know the buffer size, we cannot initialise // the buffers at object initialisation. first_run(true), + gps_fix_verified(false), activebuffer(1), myDelayBuf(0) { - myMuting = 0; // is remote-controllable + myMuting = true; // is remote-controllable, and reset by the GPS fix check myStaticDelayUs = 0; // is remote-controllable + // Variables needed for GPS fix check + num_checks_without_gps_fix = 1; + first_gps_fix_check.tv_sec = 0; + last_gps_fix_check.tv_sec = 0; + time_last_frame.tv_sec = 0; + + #if FAKE_UHD MDEBUG("OutputUHD:Using fake UHD output"); #else @@ -91,7 +134,10 @@ OutputUHD::OutputUHD( RC_ADD_PARAMETER(freq, "UHD transmission frequency"); RC_ADD_PARAMETER(muting, "Mute the output by stopping the transmitter"); RC_ADD_PARAMETER(staticdelay, "Set static delay (uS) between 0 and 96000"); - RC_ADD_PARAMETER(iqbalance, "Set I/Q balance between 0 and 1.0"); + + // TODO: find out how to use boost::bind to give the logger to the + // uhd_msg_handler + uhd::msg::register_handler(uhd_msg_handler); uhd::set_thread_priority_safe(); @@ -152,59 +198,7 @@ OutputUHD::OutputUHD( MDEBUG("OutputUHD:Mute on missing timestamps: %s ...\n", myConf.muteNoTimestamps ? "enabled" : "disabled"); - if (myConf.enableSync && (myConf.pps_src == "none")) { - myLogger.level(warn) << - "OutputUHD: WARNING:" - " you are using synchronous transmission without PPS input!"; - - struct timespec now; - if (clock_gettime(CLOCK_REALTIME, &now)) { - perror("OutputUHD:Error: could not get time: "); - myLogger.level(error) << "OutputUHD: could not get time"; - } - else { - myUsrp->set_time_now(uhd::time_spec_t(now.tv_sec)); - myLogger.level(info) << "OutputUHD: Setting USRP time to " << - uhd::time_spec_t(now.tv_sec).get_real_secs(); - } - } - - if (myConf.pps_src != "none") { - /* handling time for synchronisation: wait until the next full - * second, and set the USRP time at next PPS */ - struct timespec now; - time_t seconds; - if (clock_gettime(CLOCK_REALTIME, &now)) { - myLogger.level(error) << "OutputUHD: could not get time :" << - strerror(errno); - throw std::runtime_error("OutputUHD: could not get time."); - } - else { - seconds = now.tv_sec; - - MDEBUG("OutputUHD:sec+1: %ld ; now: %ld ...\n", seconds+1, now.tv_sec); - while (seconds + 1 > now.tv_sec) { - usleep(1); - if (clock_gettime(CLOCK_REALTIME, &now)) { - myLogger.level(error) << "OutputUHD: could not get time :" << - strerror(errno); - throw std::runtime_error("OutputUHD: could not get time."); - } - } - MDEBUG("OutputUHD:sec+1: %ld ; now: %ld ...\n", seconds+1, now.tv_sec); - /* We are now shortly after the second change. */ - - usleep(200000); // 200ms, we want the PPS to be later - myUsrp->set_time_unknown_pps(uhd::time_spec_t(seconds + 2)); - myLogger.level(info) << "OutputUHD: Setting USRP time next pps to " << - uhd::time_spec_t(seconds + 2).get_real_secs(); - } - - usleep(1e6); - myLogger.log(info, "OutputUHD: USRP time %f\n", - myUsrp->get_time_now().get_real_secs()); - } - + set_usrp_time(); // preparing output thread worker data uwd.myUsrp = myUsrp; @@ -215,24 +209,27 @@ OutputUHD::OutputUHD( uwd.sampleRate = myConf.sampleRate; uwd.sourceContainsTimestamp = false; uwd.muteNoTimestamps = myConf.muteNoTimestamps; - uwd.logger = &myLogger; uwd.refclk_lock_loss_behaviour = myConf.refclk_lock_loss_behaviour; if (myConf.refclk_src == "internal") { uwd.check_refclk_loss = false; + uwd.check_gpsfix = false; + } + else if (myConf.refclk_src == "gpsdo") { + uwd.check_refclk_loss = true; + uwd.check_gpsfix = (myConf.maxGPSHoldoverTime != 0); } else { uwd.check_refclk_loss = true; + uwd.check_gpsfix = false; } - SetDelayBuffer(config.dabMode); + SetDelayBuffer(myConf.dabMode); shared_ptr<barrier> b(new barrier(2)); mySyncBarrier = b; uwd.sync_barrier = b; - worker.start(&uwd); - MDEBUG("OutputUHD:UHD ready.\n"); } @@ -247,32 +244,30 @@ OutputUHD::~OutputUHD() } } -void OutputUHD::SetDelayBuffer(unsigned int dabMode) +int transmission_frame_duration_ms(unsigned int dabMode) { - // find out the duration of the transmission frame (Table 2 in ETSI 300 401) switch (dabMode) { - case 0: // could happen when called from constructor and we take the mode from ETI - myTFDurationMs = 0; - break; - case 1: - myTFDurationMs = 96; - break; - case 2: - myTFDurationMs = 24; - break; - case 3: - myTFDurationMs = 24; - break; - case 4: - myTFDurationMs = 48; - break; - default: - throw std::runtime_error("OutPutUHD: invalid DAB mode"); - } - // The buffer size equals the number of samples per transmission frame so - // we calculate it by multiplying the duration of the transmission frame - // with the samplerate. - myDelayBuf.resize(myTFDurationMs * myConf.sampleRate / 1000); + // could happen when called from constructor and we take the mode from ETI + case 0: return 0; + + case 1: return 96; + case 2: return 24; + case 3: return 24; + case 4: return 48; + default: + throw std::runtime_error("OutputUHD: invalid DAB mode"); + } +} + +void OutputUHD::SetDelayBuffer(unsigned int dabMode) +{ + // find out the duration of the transmission frame (Table 2 in ETSI 300 401) + myTFDurationMs = transmission_frame_duration_ms(dabMode); + + // The buffer size equals the number of samples per transmission frame so + // we calculate it by multiplying the duration of the transmission frame + // with the samplerate. + myDelayBuf.resize(myTFDurationMs * myConf.sampleRate / 1000); } int OutputUHD::process(Buffer* dataIn, Buffer* dataOut) @@ -286,8 +281,25 @@ int OutputUHD::process(Buffer* dataIn, Buffer* dataOut) // the first buffer // We will only wait on the barrier on the subsequent calls to // OutputUHD::process - if (first_run) { - myLogger.level(debug) << "OutputUHD: UHD initialising..."; + if (not gps_fix_verified) { + if (uwd.check_gpsfix) { + initial_gps_check(); + + if (num_checks_without_gps_fix == 0) { + set_usrp_time(); + gps_fix_verified = true; + myMuting = false; + } + } + else { + gps_fix_verified = true; + myMuting = false; + } + } + else if (first_run) { + etiLog.level(debug) << "OutputUHD: UHD initialising..."; + + worker.start(&uwd); uwd.bufsize = dataIn->getLength(); uwd.frame0.buf = malloc(uwd.bufsize); @@ -310,28 +322,53 @@ int OutputUHD::process(Buffer* dataIn, Buffer* dataOut) default: break; } - // we only set the delay buffer from the dab mode signaled in ETI if the - // dab mode was not set in contructor - if (myTFDurationMs == 0) - SetDelayBuffer(myEtiReader->getMode()); + // we only set the delay buffer from the dab mode signaled in ETI if the + // dab mode was not set in contructor + if (myTFDurationMs == 0) { + SetDelayBuffer(myEtiReader->getMode()); + } activebuffer = 1; lastLen = uwd.bufsize; first_run = false; - myLogger.level(debug) << "OutputUHD: UHD initialising complete"; + etiLog.level(debug) << "OutputUHD: UHD initialising complete"; } else { if (lastLen != dataIn->getLength()) { // I expect that this never happens. - myLogger.level(emerg) << + etiLog.level(emerg) << "OutputUHD: Fatal error, input length changed from " << lastLen << " to " << dataIn->getLength(); throw std::runtime_error("Non-constant input length!"); } + + if (uwd.check_gpsfix) { + try { + check_gps(); + } + catch (std::runtime_error& e) { + uwd.running = false; + etiLog.level(error) << e.what(); + } + } + mySyncBarrier.get()->wait(); + if (!uwd.running) { + worker.stop(); + first_run = true; + if (uwd.failed_due_to_fct) { + throw fct_discontinuity_error(); + } + else { + etiLog.level(error) << + "OutputUHD: Error, UHD worker failed"; + throw std::runtime_error("UHD worker failed"); + } + } + // write into the our buffer while // the worker sends the other. @@ -371,56 +408,226 @@ int OutputUHD::process(Buffer* dataIn, Buffer* dataOut) } return uwd.bufsize; +} + + +void OutputUHD::set_usrp_time() +{ + if (myConf.enableSync && (myConf.pps_src == "none")) { + etiLog.level(warn) << + "OutputUHD: WARNING:" + " you are using synchronous transmission without PPS input!"; + + struct timespec now; + if (clock_gettime(CLOCK_REALTIME, &now)) { + perror("OutputUHD:Error: could not get time: "); + etiLog.level(error) << "OutputUHD: could not get time"; + } + else { + myUsrp->set_time_now(uhd::time_spec_t(now.tv_sec)); + etiLog.level(info) << "OutputUHD: Setting USRP time to " << + uhd::time_spec_t(now.tv_sec).get_real_secs(); + } + } + + if (myConf.pps_src != "none") { + /* handling time for synchronisation: wait until the next full + * second, and set the USRP time at next PPS */ + struct timespec now; + time_t seconds; + if (clock_gettime(CLOCK_REALTIME, &now)) { + etiLog.level(error) << "OutputUHD: could not get time :" << + strerror(errno); + throw std::runtime_error("OutputUHD: could not get time."); + } + else { + seconds = now.tv_sec; + + MDEBUG("OutputUHD:sec+1: %ld ; now: %ld ...\n", seconds+1, now.tv_sec); + while (seconds + 1 > now.tv_sec) { + usleep(1); + if (clock_gettime(CLOCK_REALTIME, &now)) { + etiLog.level(error) << "OutputUHD: could not get time :" << + strerror(errno); + throw std::runtime_error("OutputUHD: could not get time."); + } + } + MDEBUG("OutputUHD:sec+1: %ld ; now: %ld ...\n", seconds+1, now.tv_sec); + /* We are now shortly after the second change. */ + + usleep(200000); // 200ms, we want the PPS to be later + myUsrp->set_time_unknown_pps(uhd::time_spec_t(seconds + 2)); + etiLog.level(info) << "OutputUHD: Setting USRP time next pps to " << + uhd::time_spec_t(seconds + 2).get_real_secs(); + } + usleep(1e6); + etiLog.log(info, "OutputUHD: USRP time %f\n", + myUsrp->get_time_now().get_real_secs()); + } } -void UHDWorker::process() +void OutputUHD::initial_gps_check() { - int workerbuffer = 0; - time_t tx_second = 0; - double pps_offset = 0; - double last_pps = 2.0; - double usrp_time; + if (first_gps_fix_check.tv_sec == 0) { + etiLog.level(info) << "Waiting for GPS fix"; - //const struct timespec hundred_nano = {0, 100}; + if (clock_gettime(CLOCK_MONOTONIC, &first_gps_fix_check) != 0) { + stringstream ss; + ss << "clock_gettime failure: " << strerror(errno); + throw std::runtime_error(ss.str()); + } + } - size_t sizeIn; - struct UHDWorkerFrameData* frame; + check_gps(); - size_t num_acc_samps; //number of accumulated samples - //int write_fail_count; + if (last_gps_fix_check.tv_sec > + first_gps_fix_check.tv_sec + initial_gps_fix_wait) { + stringstream ss; + ss << "GPS did not fix in " << initial_gps_fix_wait << " seconds"; + throw std::runtime_error(ss.str()); + } - // Transmit timeout - const double timeout = 0.2; + if (time_last_frame.tv_sec == 0) { + if (clock_gettime(CLOCK_MONOTONIC, &time_last_frame) != 0) { + stringstream ss; + ss << "clock_gettime failure: " << strerror(errno); + throw std::runtime_error(ss.str()); + } + } + + struct timespec now; + if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) { + stringstream ss; + ss << "clock_gettime failure: " << strerror(errno); + throw std::runtime_error(ss.str()); + } + + long delta_us = timespecdiff_us(time_last_frame, now); + long wait_time_us = transmission_frame_duration_ms(myConf.dabMode); + + if (wait_time_us - delta_us > 0) { + usleep(wait_time_us - delta_us); + } + + time_last_frame.tv_nsec += wait_time_us * 1000; + if (time_last_frame.tv_nsec >= 1000000000L) { + time_last_frame.tv_nsec -= 1000000000L; + time_last_frame.tv_sec++; + } +} + +void OutputUHD::check_gps() +{ + struct timespec time_now; + if (clock_gettime(CLOCK_MONOTONIC, &time_now) != 0) { + stringstream ss; + ss << "clock_gettime failure: " << strerror(errno); + throw std::runtime_error(ss.str()); + } + + // Divide interval by two because we alternate between + // launch and check + if (uwd.check_gpsfix and + last_gps_fix_check.tv_sec + gps_fix_check_interval/2.0 < + time_now.tv_sec) { + last_gps_fix_check = time_now; + + // Alternate between launching thread and checking the + // result. + if (gps_fix_task.joinable()) { + if (gps_fix_future.has_value()) { + + gps_fix_future.wait(); + + gps_fix_task.join(); + + if (not gps_fix_future.get()) { + if (num_checks_without_gps_fix == 0) { + etiLog.level(alert) << + "OutputUHD: GPS Fix lost"; + } + num_checks_without_gps_fix++; + } + else { + if (num_checks_without_gps_fix) { + etiLog.level(info) << + "OutputUHD: GPS Fix recovered"; + } + num_checks_without_gps_fix = 0; + } + + if (gps_fix_check_interval * num_checks_without_gps_fix > + myConf.maxGPSHoldoverTime) { + std::stringstream ss; + ss << "Lost GPS fix for " << gps_fix_check_interval * + num_checks_without_gps_fix << " seconds"; + throw std::runtime_error(ss.str()); + } + } + } + else { + // Checking the sensor here takes too much + // time, it has to be done in a separate thread. + gps_fix_pt = boost::packaged_task<bool>( + boost::bind(check_gps_fix_ok, myUsrp) ); + + gps_fix_future = gps_fix_pt.get_future(); + + gps_fix_task = boost::thread(boost::move(gps_fix_pt)); + } + } +} + +//============================ UHD Worker ======================== + +void UHDWorker::process_errhandler() +{ + try { + process(); + } + catch (fct_discontinuity_error& e) { + etiLog.level(warn) << e.what(); + uwd->failed_due_to_fct = true; + } + + uwd->running = false; + uwd->sync_barrier.get()->wait(); + etiLog.level(warn) << "UHD worker terminated"; +} + +void UHDWorker::process() +{ + int workerbuffer = 0; + tx_second = 0; + pps_offset = 0.0; + last_pps = 2.0; #if FAKE_UHD == 0 uhd::stream_args_t stream_args("fc32"); //complex floats - uhd::tx_streamer::sptr myTxStream = uwd->myUsrp->get_tx_stream(stream_args); - size_t usrp_max_num_samps = myTxStream->get_max_num_samps(); -#else - size_t usrp_max_num_samps = 2048; // arbitrarily chosen + myTxStream = uwd->myUsrp->get_tx_stream(stream_args); #endif - const complexf* in; - - uhd::tx_metadata_t md; md.start_of_burst = false; - md.end_of_burst = false; + md.end_of_burst = false; + + expected_next_fct = -1; - int expected_next_fct = -1; + num_underflows = 0; + num_late_packets = 0; - while (running) { - bool fct_discontinuity = false; - md.has_time_spec = false; - md.time_spec = uhd::time_spec_t(0.0); - num_acc_samps = 0; - //write_fail_count = 0; + while (uwd->running) { + fct_discontinuity = false; + md.has_time_spec = false; + md.time_spec = uhd::time_spec_t(0.0); /* Wait for barrier */ // this wait will hopefully always be the second one // because modulation should be quicker than transmission uwd->sync_barrier.get()->wait(); + struct UHDWorkerFrameData* frame; + if (workerbuffer == 0) { frame = &(uwd->frame0); } @@ -432,241 +639,240 @@ void UHDWorker::process() "UHDWorker.process: workerbuffer is neither 0 nor 1 !"); } - in = reinterpret_cast<const complexf*>(frame->buf); - pps_offset = frame->ts.timestamp_pps_offset; + handle_frame(frame); - // Tx second from MNSC - tx_second = frame->ts.timestamp_sec; + // swap buffers + workerbuffer = (workerbuffer + 1) % 2; + } +} - sizeIn = uwd->bufsize / sizeof(complexf); +void UHDWorker::handle_frame(const struct UHDWorkerFrameData *frame) +{ + // Transmit timeout + static const double tx_timeout = 20.0; - /* Verify that the FCT value is correct. If we miss one transmission - * frame we must interrupt UHD and resync to the timestamps - */ - if (expected_next_fct != -1) { - if (expected_next_fct != (int)frame->ts.fct) { - uwd->logger->level(warn) << - "OutputUHD: Incorrect expect fct " << frame->ts.fct; + pps_offset = frame->ts.timestamp_pps_offset; - fct_discontinuity = true; - } - } + // Tx second from MNSC + tx_second = frame->ts.timestamp_sec; - expected_next_fct = (frame->ts.fct + uwd->fct_increment) % 250; + /* Verify that the FCT value is correct. If we miss one transmission + * frame we must interrupt UHD and resync to the timestamps + */ + if (frame->ts.fct == -1) { + etiLog.level(info) << + "OutputUHD: dropping one frame with invalid FCT"; + return; + } + if (expected_next_fct != -1) { + if (expected_next_fct != (int)frame->ts.fct) { + etiLog.level(warn) << + "OutputUHD: Incorrect expect fct " << frame->ts.fct << + ", expected " << expected_next_fct; + + fct_discontinuity = true; + throw fct_discontinuity_error(); + } + } - // Check for ref_lock - if (uwd->check_refclk_loss) - { - try { - // TODO: Is this check specific to the B100 and USRP2 ? - if (! uwd->myUsrp->get_mboard_sensor("ref_locked", 0).to_bool()) { - uwd->logger->log(alert, - "OutputUHD: External reference clock lock lost !"); - if (uwd->refclk_lock_loss_behaviour == CRASH) { - throw std::runtime_error( - "OutputUHD: External reference clock lock lost."); - } + expected_next_fct = (frame->ts.fct + uwd->fct_increment) % 250; + + // Check for ref_lock + if (uwd->check_refclk_loss) { + try { + // TODO: Is this check specific to the B100 and USRP2 ? + if (! uwd->myUsrp->get_mboard_sensor("ref_locked", 0).to_bool()) { + etiLog.log(alert, + "OutputUHD: External reference clock lock lost !"); + if (uwd->refclk_lock_loss_behaviour == CRASH) { + throw std::runtime_error( + "OutputUHD: External reference clock lock lost."); } } - catch (uhd::lookup_error &e) { - uwd->check_refclk_loss = false; - uwd->logger->log(warn, - "OutputUHD: This USRP does not have mboard sensor for ext clock loss." - " Check disabled."); - } } + catch (uhd::lookup_error &e) { + uwd->check_refclk_loss = false; + etiLog.log(warn, + "OutputUHD: This USRP does not have mboard sensor for ext clock loss." + " Check disabled."); + } + } - usrp_time = uwd->myUsrp->get_time_now().get_real_secs(); - - if (uwd->sourceContainsTimestamp) { - if (!frame->ts.timestamp_valid) { - /* We have not received a full timestamp through - * MNSC. We sleep through the frame. - */ - uwd->logger->level(info) << - "OutputUHD: Throwing sample " << frame->ts.fct << - " away: incomplete timestamp " << tx_second << - " + " << pps_offset; - usleep(20000); //TODO should this be TM-dependant ? - goto loopend; - } - - md.has_time_spec = true; - md.time_spec = uhd::time_spec_t(tx_second, pps_offset); - - // md is defined, let's do some checks - if (md.time_spec.get_real_secs() + 0.2 < usrp_time) { - uwd->logger->level(warn) << - "OutputUHD: Timestamp in the past! offset: " << - md.time_spec.get_real_secs() - usrp_time << - " (" << usrp_time << ")" - " frame " << frame->ts.fct << - ", tx_second " << tx_second << - ", pps " << pps_offset; - goto loopend; //skip the frame - } + double usrp_time = uwd->myUsrp->get_time_now().get_real_secs(); - if (md.time_spec.get_real_secs() > usrp_time + TIMESTAMP_MARGIN_FUTURE) { - uwd->logger->level(warn) << - "OutputUHD: Timestamp too far in the future! offset: " << - md.time_spec.get_real_secs() - usrp_time; - usleep(20000); //sleep so as to fill buffers - } - if (md.time_spec.get_real_secs() > usrp_time + TIMESTAMP_ABORT_FUTURE) { - uwd->logger->level(error) << - "OutputUHD: Timestamp way too far in the future! offset: " << - md.time_spec.get_real_secs() - usrp_time; - throw std::runtime_error("Timestamp error. Aborted."); - } + if (uwd->sourceContainsTimestamp) { + if (!frame->ts.timestamp_valid) { + /* We have not received a full timestamp through + * MNSC. We sleep through the frame. + */ + etiLog.level(info) << + "OutputUHD: Throwing sample " << frame->ts.fct << + " away: incomplete timestamp " << tx_second << + " + " << pps_offset; + usleep(20000); //TODO should this be TM-dependant ? + return; + } - if (last_pps > pps_offset) { - uwd->logger->log(info, - "OutputUHD (usrp time: %f): frame %d;" - " tx_second %zu; pps %.9f\n", - usrp_time, - frame->ts.fct, tx_second, pps_offset); - } + md.has_time_spec = true; + md.time_spec = uhd::time_spec_t(tx_second, pps_offset); + + // md is defined, let's do some checks + if (md.time_spec.get_real_secs() + tx_timeout < usrp_time) { + etiLog.level(warn) << + "OutputUHD: Timestamp in the past! offset: " << + md.time_spec.get_real_secs() - usrp_time << + " (" << usrp_time << ")" + " frame " << frame->ts.fct << + ", tx_second " << tx_second << + ", pps " << pps_offset; + return; + } + if (md.time_spec.get_real_secs() > usrp_time + TIMESTAMP_ABORT_FUTURE) { + etiLog.level(error) << + "OutputUHD: Timestamp way too far in the future! offset: " << + md.time_spec.get_real_secs() - usrp_time; + throw std::runtime_error("Timestamp error. Aborted."); } - else { // !uwd->sourceContainsTimestamp - if (uwd->muting || uwd->muteNoTimestamps) { - /* There was some error decoding the timestamp - */ - if (uwd->muting) { - uwd->logger->log(info, - "OutputUHD: Muting sample %d requested\n", - frame->ts.fct); - } - else { - uwd->logger->log(info, - "OutputUHD: Muting sample %d : no timestamp\n", - frame->ts.fct); - } - usleep(20000); - goto loopend; + } + else { // !uwd->sourceContainsTimestamp + if (uwd->muting || uwd->muteNoTimestamps) { + /* There was some error decoding the timestamp + */ + if (uwd->muting) { + etiLog.log(info, + "OutputUHD: Muting sample %d requested\n", + frame->ts.fct); } + else { + etiLog.log(info, + "OutputUHD: Muting sample %d : no timestamp\n", + frame->ts.fct); + } + usleep(20000); + return; } + } - PDEBUG("UHDWorker::process:max_num_samps: %zu.\n", - usrp_max_num_samps); + tx_frame(frame); - while (running && !uwd->muting && (num_acc_samps < sizeIn)) { - size_t samps_to_send = std::min(sizeIn - num_acc_samps, usrp_max_num_samps); + if (last_pps > pps_offset) { + if (num_underflows or num_late_packets) { + etiLog.log(info, + "OutputUHD status (usrp time: %f): " + "%d underruns and %d late packets since last status.\n", + usrp_time, + num_underflows, num_late_packets); + } + num_underflows = 0; + num_late_packets = 0; + } - //ensure the the last packet has EOB set if the timestamps has been - //refreshed and need to be reconsidered. - //Also, if we saw that the FCT did not increment as expected, which - //could be due to a lost incoming packet. - md.end_of_burst = ( - uwd->sourceContainsTimestamp && - (frame->ts.timestamp_refresh || fct_discontinuity) && - samps_to_send <= usrp_max_num_samps ); + last_pps = pps_offset; +} +void UHDWorker::tx_frame(const struct UHDWorkerFrameData *frame) +{ + const double tx_timeout = 20.0; + const size_t sizeIn = uwd->bufsize / sizeof(complexf); + const complexf* in_data = reinterpret_cast<const complexf*>(frame->buf); -#if FAKE_UHD - // This is probably very approximate - usleep( (1000000 / uwd->sampleRate) * samps_to_send); - size_t num_tx_samps = samps_to_send; +#if FAKE_UHD == 0 + size_t usrp_max_num_samps = myTxStream->get_max_num_samps(); #else - //send a single packet - size_t num_tx_samps = myTxStream->send( - &in[num_acc_samps], - samps_to_send, md, timeout); + size_t usrp_max_num_samps = 2048; // arbitrarily chosen #endif - num_acc_samps += num_tx_samps; + size_t num_acc_samps = 0; //number of accumulated samples + while (uwd->running && !uwd->muting && (num_acc_samps < sizeIn)) { + size_t samps_to_send = std::min(sizeIn - num_acc_samps, usrp_max_num_samps); - md.time_spec = uhd::time_spec_t(tx_second, pps_offset) - + uhd::time_spec_t(0, num_acc_samps/uwd->sampleRate); + //ensure the the last packet has EOB set if the timestamps has been + //refreshed and need to be reconsidered. + //Also, if we saw that the FCT did not increment as expected, which + //could be due to a lost incoming packet. + md.end_of_burst = ( + uwd->sourceContainsTimestamp && + (frame->ts.timestamp_refresh || fct_discontinuity) && + samps_to_send <= usrp_max_num_samps ); - /* - fprintf(stderr, "*** pps_offset %f, md.time_spec %f, usrp->now %f\n", - pps_offset, - md.time_spec.get_real_secs(), - uwd->myUsrp->get_time_now().get_real_secs()); - // */ - - if (num_tx_samps == 0) { -#if 1 - uwd->logger->log(warn, - "UHDWorker::process() unable to write to device, skipping frame!\n"); - break; +#if FAKE_UHD + // This is probably very approximate + usleep( (1000000 / uwd->sampleRate) * samps_to_send); + size_t num_tx_samps = samps_to_send; #else - // This has been disabled, because if there is a write failure, - // we'd better not insist and try to go on transmitting future - // frames. - // The goal is not to try to send by all means possible. It's - // more important to make sure the SFN is not disturbed. - - fprintf(stderr, "F"); - nanosleep(&hundred_nano, NULL); - write_fail_count++; - if (write_fail_count >= 3) { - double ts = md.time_spec.get_real_secs(); - double t_usrp = uwd->myUsrp->get_time_now().get_real_secs(); - - fprintf(stderr, "*** USRP write fail count %d\n", write_fail_count); - fprintf(stderr, "*** delta %f, md.time_spec %f, usrp->now %f\n", - ts - t_usrp, - ts, t_usrp); - - fprintf(stderr, "UHDWorker::process() unable to write to device, skipping frame!\n"); - break; - } + //send a single packet + size_t num_tx_samps = myTxStream->send( + &in_data[num_acc_samps], + samps_to_send, md, tx_timeout); #endif - } -#if FAKE_UHD == 0 - uhd::async_metadata_t async_md; - if (uwd->myUsrp->get_device()->recv_async_msg(async_md, 0)) { - const char* uhd_async_message = ""; - bool failure = true; - switch (async_md.event_code) { - case uhd::async_metadata_t::EVENT_CODE_BURST_ACK: - failure = false; - break; - case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW: - uhd_async_message = "Underflow"; - break; - case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR: - uhd_async_message = "Packet loss between host and device."; - break; - case uhd::async_metadata_t::EVENT_CODE_TIME_ERROR: - uhd_async_message = "Packet had time that was late."; - break; - case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW_IN_PACKET: - uhd_async_message = "Underflow occurred inside a packet."; - break; - case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR_IN_BURST: - uhd_async_message = "Packet loss within a burst."; - break; - default: - uhd_async_message = "unknown event code"; - break; - } + num_acc_samps += num_tx_samps; - if (failure) { - uwd->logger->level(alert) << "Near frame " << - frame->ts.fct << ": Received Async UHD Message '" << - uhd_async_message << "'"; + md.time_spec = uhd::time_spec_t(tx_second, pps_offset) + + uhd::time_spec_t(0, num_acc_samps/uwd->sampleRate); - } - } -#endif + if (num_tx_samps == 0) { + etiLog.log(warn, + "UHDWorker::process() unable to write to device, skipping frame!\n"); + break; } - last_pps = pps_offset; - -loopend: - // swap buffers - workerbuffer = (workerbuffer + 1) % 2; + print_async_metadata(frame); } +} - uwd->logger->level(warn) << "UHD worker terminated"; +void UHDWorker::print_async_metadata(const struct UHDWorkerFrameData *frame) +{ +#if FAKE_UHD == 0 + uhd::async_metadata_t async_md; + if (uwd->myUsrp->get_device()->recv_async_msg(async_md, 0)) { + const char* uhd_async_message = ""; + bool failure = false; + switch (async_md.event_code) { + case uhd::async_metadata_t::EVENT_CODE_BURST_ACK: + break; + case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW: + uhd_async_message = "Underflow"; + num_underflows++; + break; + case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR: + uhd_async_message = "Packet loss between host and device."; + failure = true; + break; + case uhd::async_metadata_t::EVENT_CODE_TIME_ERROR: + uhd_async_message = "Packet had time that was late."; + num_late_packets++; + break; + case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW_IN_PACKET: + uhd_async_message = "Underflow occurred inside a packet."; + failure = true; + break; + case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR_IN_BURST: + uhd_async_message = "Packet loss within a burst."; + failure = true; + break; + default: + uhd_async_message = "unknown event code"; + failure = true; + break; + } + + if (failure) { + etiLog.level(alert) << "Near frame " << + frame->ts.fct << ": Received Async UHD Message '" << + uhd_async_message << "'"; + + } + } +#endif } +// ======================================= +// Remote Control for UHD +// ======================================= void OutputUHD::set_parameter(const string& parameter, const string& value) { @@ -688,26 +894,21 @@ void OutputUHD::set_parameter(const string& parameter, const string& value) else if (parameter == "staticdelay") { int64_t adjust; ss >> adjust; - if (adjust > (myTFDurationMs * 1000)) - { // reset static delay for values outside range - myStaticDelayUs = 0; - } - else - { // the new adjust value is added to the existing delay and the result - // is wrapped around at TF duration - int newStaticDelayUs = myStaticDelayUs + adjust; - if (newStaticDelayUs > (myTFDurationMs * 1000)) - myStaticDelayUs = newStaticDelayUs - (myTFDurationMs * 1000); - else if (newStaticDelayUs < 0) - myStaticDelayUs = newStaticDelayUs + (myTFDurationMs * 1000); - else - myStaticDelayUs = newStaticDelayUs; - } - } - else if (parameter == "iqbalance") { - ss >> myConf.frequency; - myUsrp->set_tx_freq(myConf.frequency); - myConf.frequency = myUsrp->get_tx_freq(); + if (adjust > (myTFDurationMs * 1000)) + { // reset static delay for values outside range + myStaticDelayUs = 0; + } + else + { // the new adjust value is added to the existing delay and the result + // is wrapped around at TF duration + int newStaticDelayUs = myStaticDelayUs + adjust; + if (newStaticDelayUs > (myTFDurationMs * 1000)) + myStaticDelayUs = newStaticDelayUs - (myTFDurationMs * 1000); + else if (newStaticDelayUs < 0) + myStaticDelayUs = newStaticDelayUs + (myTFDurationMs * 1000); + else + myStaticDelayUs = newStaticDelayUs; + } } else { stringstream ss; @@ -741,3 +942,4 @@ const string OutputUHD::get_parameter(const string& parameter) const } #endif // HAVE_OUTPUT_UHD + diff --git a/src/OutputUHD.h b/src/OutputUHD.h index d002e98..633de04 100644 --- a/src/OutputUHD.h +++ b/src/OutputUHD.h @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2014 + Copyright (C) 2014, 2015 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -50,6 +50,7 @@ DESCRIPTION: #include <boost/thread/thread.hpp> #include <boost/thread/barrier.hpp> #include <boost/shared_ptr.hpp> +#include <boost/atomic.hpp> #include <list> #include <string> @@ -83,9 +84,20 @@ struct UHDWorkerFrameData { struct frame_timestamp ts; }; +struct fct_discontinuity_error : public std::exception +{ + const char* what () const throw () + { + return "FCT discontinuity detected"; + } +}; + enum refclk_lock_loss_behaviour_t { CRASH, IGNORE }; struct UHDWorkerData { + boost::atomic<bool> running; + bool failed_due_to_fct; + #if FAKE_UHD == 0 uhd::usrp::multi_usrp::sptr myUsrp; #endif @@ -109,6 +121,9 @@ struct UHDWorkerData { // If we want to verify loss of refclk bool check_refclk_loss; + // If we want to check for the gps_fixtype sensor + bool check_gpsfix; + // muting set by remote control bool muting; @@ -118,9 +133,6 @@ struct UHDWorkerData { // What to do when the reference clock PLL loses lock refclk_lock_loss_behaviour_t refclk_lock_loss_behaviour; - // The common logger - Logger* logger; - // What transmission mode we're using defines by how // much the FCT should increment for each // transmission frame. @@ -130,31 +142,44 @@ struct UHDWorkerData { class UHDWorker { public: - UHDWorker () { - running = false; - } - void start(struct UHDWorkerData *uhdworkerdata) { - running = true; uwd = uhdworkerdata; - uhd_thread = boost::thread(&UHDWorker::process, this); + + uwd->running = true; + uwd->failed_due_to_fct = false; + uhd_thread = boost::thread(&UHDWorker::process_errhandler, this); } void stop() { - running = false; + uwd->running = false; uhd_thread.interrupt(); uhd_thread.join(); } - void process(); + private: + // Asynchronous message statistics + int num_underflows; + int num_late_packets; + bool fct_discontinuity; + int expected_next_fct; + uhd::tx_metadata_t md; + time_t tx_second; + double pps_offset; + double last_pps; + + void print_async_metadata(const struct UHDWorkerFrameData *frame); + + void handle_frame(const struct UHDWorkerFrameData *frame); + void tx_frame(const struct UHDWorkerFrameData *frame); - private: struct UHDWorkerData *uwd; - bool running; boost::thread uhd_thread; uhd::tx_streamer::sptr myTxStream; + + void process(); + void process_errhandler(); }; /* This structure is used as initial configuration for OutputUHD */ @@ -171,7 +196,8 @@ struct OutputUHDConfig { double txgain; bool enableSync; bool muteNoTimestamps; - unsigned dabMode; + unsigned dabMode; + unsigned maxGPSHoldoverTime; /* allowed values : auto, int, sma, mimo */ std::string refclk_src; @@ -190,9 +216,7 @@ struct OutputUHDConfig { class OutputUHD: public ModOutput, public RemoteControllable { public: - OutputUHD( - OutputUHDConfig& config, - Logger& logger); + OutputUHD(const OutputUHDConfig& config); ~OutputUHD(); int process(Buffer* dataIn, Buffer* dataOut); @@ -218,13 +242,16 @@ class OutputUHD: public ModOutput, public RemoteControllable { protected: - Logger& myLogger; + OutputUHD(const OutputUHD& other); + OutputUHD& operator=(const OutputUHD& other); + EtiReader *myEtiReader; OutputUHDConfig myConf; uhd::usrp::multi_usrp::sptr myUsrp; boost::shared_ptr<boost::barrier> mySyncBarrier; UHDWorker worker; bool first_run; + bool gps_fix_verified; struct UHDWorkerData uwd; int activebuffer; @@ -232,14 +259,36 @@ class OutputUHD: public ModOutput, public RemoteControllable { bool myMuting; private: - // methods - void SetDelayBuffer(unsigned int dabMode); + // Resize the internal delay buffer according to the dabMode and + // the sample rate. + void SetDelayBuffer(unsigned int dabMode); // data int myStaticDelayUs; // static delay in microseconds - int myTFDurationMs; // TF duration in milliseconds + int myTFDurationMs; // TF duration in milliseconds std::vector<complexf> myDelayBuf; size_t lastLen; + + // GPS Fix check variables + int num_checks_without_gps_fix; + struct timespec first_gps_fix_check; + struct timespec last_gps_fix_check; + struct timespec time_last_frame; + boost::packaged_task<bool> gps_fix_pt; + boost::unique_future<bool> gps_fix_future; + boost::thread gps_fix_task; + + // Wait time in seconds to get fix + static const int initial_gps_fix_wait = 60; + + // Interval for checking the GPS at runtime + static const double gps_fix_check_interval = 10.0; // seconds + + void check_gps(); + + void set_usrp_time(); + + void initial_gps_check(); }; #endif // HAVE_OUTPUT_UHD diff --git a/src/OutputZeroMQ.cpp b/src/OutputZeroMQ.cpp index 793e473..da4473e 100644 --- a/src/OutputZeroMQ.cpp +++ b/src/OutputZeroMQ.cpp @@ -32,19 +32,31 @@ #if defined(HAVE_ZEROMQ) -OutputZeroMQ::OutputZeroMQ(std::string endpoint, Buffer* dataOut) +OutputZeroMQ::OutputZeroMQ(std::string endpoint, int type, Buffer* dataOut) : ModOutput(ModFormat(1), ModFormat(0)), + m_type(type), m_zmq_context(1), - m_zmq_pub_sock(m_zmq_context, ZMQ_PUB), + m_zmq_sock(m_zmq_context, type), m_endpoint(endpoint) { PDEBUG("OutputZeroMQ::OutputZeroMQ(%p) @ %p\n", dataOut, this); std::stringstream ss; - ss << "OutputZeroMQ(" << m_endpoint << ")"; + ss << "OutputZeroMQ(" << m_endpoint << " "; + + if (type == ZMQ_PUB) { + ss << "ZMQ_PUB"; + } + else if (type == ZMQ_REP) { + ss << "ZMQ_REP"; + } + else { + throw std::invalid_argument("ZMQ socket type unknown"); + } + ss << ")"; m_name = ss.str(); - m_zmq_pub_sock.bind(m_endpoint.c_str()); + m_zmq_sock.bind(m_endpoint.c_str()); } OutputZeroMQ::~OutputZeroMQ() @@ -58,7 +70,13 @@ int OutputZeroMQ::process(Buffer* dataIn, Buffer* dataOut) "(dataIn: %p, dataOut: %p)\n", dataIn, dataOut); - m_zmq_pub_sock.send(dataIn->getData(), dataIn->getLength()); + if (m_type == ZMQ_REP) { + // A ZMQ_REP socket requires a request first + zmq::message_t msg; + m_zmq_sock.recv(&msg); + } + + m_zmq_sock.send(dataIn->getData(), dataIn->getLength()); return dataIn->getLength(); } diff --git a/src/OutputZeroMQ.h b/src/OutputZeroMQ.h index a80eab4..85f85a7 100644 --- a/src/OutputZeroMQ.h +++ b/src/OutputZeroMQ.h @@ -39,14 +39,15 @@ class OutputZeroMQ : public ModOutput { public: - OutputZeroMQ(std::string endpoint, Buffer* dataOut = NULL); + OutputZeroMQ(std::string endpoint, int type, Buffer* dataOut = NULL); virtual ~OutputZeroMQ(); virtual int process(Buffer* dataIn, Buffer* dataOut); const char* name() { return m_name.c_str(); } protected: + int m_type; // zmq socket type zmq::context_t m_zmq_context; // handle for the zmq context - zmq::socket_t m_zmq_pub_sock; // handle for the zmq publisher socket + zmq::socket_t m_zmq_sock; // handle for the zmq publisher socket std::string m_endpoint; // On which port to listen: e.g. // tcp://*:58300 diff --git a/src/RemoteControl.cpp b/src/RemoteControl.cpp index 7a2af00..21a6c81 100644 --- a/src/RemoteControl.cpp +++ b/src/RemoteControl.cpp @@ -58,8 +58,10 @@ void RemoteControllerTelnet::restart_thread(long) void RemoteControllerTelnet::process(long) { - m_welcome = "ODR-DabMod Remote Control CLI\nWrite 'help' for help.\n**********\n"; - m_prompt = "> "; + std::string m_welcome = "ODR-DabMod Remote Control CLI\n" + "Write 'help' for help.\n" + "**********\n"; + std::string m_prompt = "> "; std::string in_message; size_t length; diff --git a/src/RemoteControl.h b/src/RemoteControl.h index 89a1583..1b5e447 100644 --- a/src/RemoteControl.h +++ b/src/RemoteControl.h @@ -34,7 +34,7 @@ #endif #if defined(HAVE_ZEROMQ) -#include <zmq.hpp> +#include "zmq.hpp" #endif #include <list> @@ -50,6 +50,7 @@ #include <boost/thread.hpp> #include <stdexcept> +#include "Log.h" #define RC_ADD_PARAMETER(p, desc) { \ std::vector<std::string> p; \ @@ -114,8 +115,8 @@ class RemoteControllers { it != m_controllers.end(); ++it) { if ((*it)->fault_detected()) { - fprintf(stderr, - "Detected Remote Control fault, restarting it\n"); + etiLog.level(warn) << + "Detected Remote Control fault, restarting it"; (*it)->restart(); } } @@ -289,9 +290,6 @@ class RemoteControllerTelnet : public BaseRemoteController { /* This controller commands the controllables in the cohort */ std::list<RemoteControllable*> m_cohort; - std::string m_welcome; - std::string m_prompt; - int m_port; }; diff --git a/src/ThreadsafeQueue.h b/src/ThreadsafeQueue.h index 78e9ef0..e5e83ef 100644 --- a/src/ThreadsafeQueue.h +++ b/src/ThreadsafeQueue.h @@ -38,25 +38,14 @@ * that pushes elements into the queue, and one consumer that * retrieves the elements. * - * The queue can make the consumer block until enough elements - * are available. + * The queue can make the consumer block until an element + * is available. */ template<typename T> class ThreadsafeQueue { public: - /* Create a new queue without any minimum required - * fill before it is possible to pop an element - */ - ThreadsafeQueue() : the_required_size(1) {} - - /* Create a queue where it has to contain at least - * required_size elements before pop is possible - */ - ThreadsafeQueue(size_t required_size) : the_required_size(required_size) { - } - /* Push one element into the queue, and notify another thread that * might be waiting. * @@ -87,14 +76,14 @@ public: size_t size() const { + boost::mutex::scoped_lock lock(the_mutex); return the_queue.size(); } bool try_pop(T& popped_value) { boost::mutex::scoped_lock lock(the_mutex); - if(the_queue.size() < the_required_size) - { + if (the_queue.empty()) { return false; } @@ -103,10 +92,10 @@ public: return true; } - void wait_and_pop(T& popped_value) + void wait_and_pop(T& popped_value, size_t prebuffering = 1) { boost::mutex::scoped_lock lock(the_mutex); - while(the_queue.size() < the_required_size) { + while (the_queue.size() < prebuffering) { the_condition_variable.wait(lock); } @@ -118,7 +107,6 @@ private: std::queue<T> the_queue; mutable boost::mutex the_mutex; boost::condition_variable the_condition_variable; - size_t the_required_size; }; #endif diff --git a/src/TimestampDecoder.cpp b/src/TimestampDecoder.cpp index 96c84c0..5044366 100644 --- a/src/TimestampDecoder.cpp +++ b/src/TimestampDecoder.cpp @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2014 + Copyright (C) 2014, 2015 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -29,6 +29,7 @@ #include <fstream> #include <string> #include <boost/lexical_cast.hpp> +#include <boost/make_shared.hpp> #include <sys/types.h> #include "PcDebug.h" #include "TimestampDecoder.h" @@ -41,7 +42,8 @@ void TimestampDecoder::calculateTimestamp(struct frame_timestamp& ts) { - struct frame_timestamp* ts_queued = new struct frame_timestamp; + boost::shared_ptr<struct frame_timestamp> ts_queued = + boost::make_shared<struct frame_timestamp>(); /* Push new timestamp into queue */ ts_queued->timestamp_valid = full_timestamp_received_mnsc; @@ -62,14 +64,14 @@ void TimestampDecoder::calculateTimestamp(struct frame_timestamp& ts) * * Therefore, use <= and not < for comparison */ - if (queue_timestamps.size() <= modconfig.delay_calculation_pipeline_stages) { - //fprintf(stderr, "* %zu %u ", queue_timestamps.size(), modconfig.delay_calculation_pipeline_stages); + if (queue_timestamps.size() <= m_tist_delay_stages) { + //fprintf(stderr, "* %zu %u ", queue_timestamps.size(), m_tist_delay_stages); /* Return invalid timestamp until the queue is full */ ts.timestamp_valid = false; ts.timestamp_sec = 0; ts.timestamp_pps_offset = 0; ts.timestamp_refresh = false; - ts.fct = 0; + ts.fct = -1; } else { //fprintf(stderr, ". %zu ", queue_timestamps.size()); @@ -87,16 +89,14 @@ void TimestampDecoder::calculateTimestamp(struct frame_timestamp& ts) ts.timestamp_sec, ts.timestamp_pps_offset, ts.timestamp_refresh);*/ - - delete ts_queued; } MDEBUG("Timestamp queue size %zu, delay_calc %u\n", queue_timestamps.size(), - modconfig.delay_calculation_pipeline_stages); + m_tist_delay_stages); - if (queue_timestamps.size() > modconfig.delay_calculation_pipeline_stages) { - myLogger.level(error) << "Error: Timestamp queue is too large : size " << + if (queue_timestamps.size() > m_tist_delay_stages) { + etiLog.level(error) << "Error: Timestamp queue is too large : size " << queue_timestamps.size() << "! This should not happen !"; } @@ -191,84 +191,48 @@ void TimestampDecoder::updateTimestampEti( int framephase, uint16_t mnsc, double pps, - uint32_t fct) + int32_t fct) { updateTimestampPPS(pps); pushMNSCData(framephase, mnsc); latestFCT = fct; } - -bool TimestampDecoder::updateModulatorOffset() +void TimestampDecoder::set_parameter( + const std::string& parameter, + const std::string& value) { using namespace std; - using boost::lexical_cast; - using boost::bad_lexical_cast; - if (modconfig.use_offset_fixed) - { - timestamp_offset = modconfig.offset_fixed; - return true; - } - else if (modconfig.use_offset_file) - { - bool r = false; - double newoffset; + stringstream ss(value); + ss.exceptions ( stringstream::failbit | stringstream::badbit ); - std::string filedata; - ifstream filestream; - - try - { - filestream.open(modconfig.offset_filename.c_str()); - if (!filestream.eof()) - { - getline(filestream, filedata); - try - { - newoffset = lexical_cast<double>(filedata); - r = true; - } - catch (bad_lexical_cast& e) - { - myLogger.level(error) << - "Error parsing timestamp offset from file '" << - modconfig.offset_filename << "'"; - r = false; - } - } - else - { - myLogger.level(error) << - "Error reading from timestamp offset file: eof reached\n"; - r = false; - } - filestream.close(); - } - catch (exception& e) - { - myLogger.level(error) << "Error opening timestamp offset file\n"; - r = false; - } - - - if (r) - { - if (timestamp_offset != newoffset) - { - timestamp_offset = newoffset; - myLogger.level(info) << - "TimestampDecoder::updateTimestampOffset: new offset is " << - timestamp_offset; - offset_changed = true; - } + if (parameter == "offset") { + ss >> timestamp_offset; + offset_changed = true; + } + else { + stringstream ss; + ss << "Parameter '" << parameter + << "' is not exported by controllable " << get_rc_name(); + throw ParameterError(ss.str()); + } +} - } +const std::string TimestampDecoder::get_parameter( + const std::string& parameter) const +{ + using namespace std; - return r; + stringstream ss; + if (parameter == "offset") { + ss << timestamp_offset; } else { - return false; + ss << "Parameter '" << parameter << + "' is not exported by controllable " << get_rc_name(); + throw ParameterError(ss.str()); } + return ss.str(); } diff --git a/src/TimestampDecoder.h b/src/TimestampDecoder.h index 0c393e4..d8ab633 100644 --- a/src/TimestampDecoder.h +++ b/src/TimestampDecoder.h @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2014 + Copyright (C) 2014, 2015 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -28,34 +28,19 @@ #define TIMESTAMP_DECODER_H #include <queue> +#include <boost/shared_ptr.hpp> #include <string> #include <time.h> #include <math.h> #include <stdio.h> #include "Eti.h" #include "Log.h" - -struct modulator_offset_config -{ - bool use_offset_fixed; - double offset_fixed; - /* These two fields are used when the modulator is run with a fixed offset */ - - bool use_offset_file; - std::string offset_filename; - /* These two fields are used when the modulator reads the offset from a file */ - - unsigned delay_calculation_pipeline_stages; - /* Specifies by how many stages the timestamp must be delayed. - * (e.g. The FIRFilter is pipelined, therefore we must increase - * delay_calculation_pipeline_stages by one if the filter is used - */ -}; +#include "RemoteControl.h" struct frame_timestamp { // Which frame count does this timestamp apply to - uint32_t fct; + int32_t fct; uint32_t timestamp_sec; double timestamp_pps_offset; @@ -101,21 +86,32 @@ struct frame_timestamp void print(const char* t) { fprintf(stderr, - "%s <struct frame_timestamp(%s, %d, %.9f)>\n", + "%s <struct frame_timestamp(%s, %d, %.9f, %d)>\n", t, this->timestamp_valid ? "valid" : "invalid", - this->timestamp_sec, this->timestamp_pps_offset); + this->timestamp_sec, this->timestamp_pps_offset, + this->fct); } }; /* This module decodes MNSC time information */ -class TimestampDecoder +class TimestampDecoder : public RemoteControllable { public: TimestampDecoder( - struct modulator_offset_config& config, - Logger& logger): - myLogger(logger), modconfig(config) + /* The modulator adds this offset to the TIST to define time of + * frame transmission + */ + double offset_s, + + /* Specifies by how many stages the timestamp must be delayed. + * (e.g. The FIRFilter is pipelined, therefore we must increase + * tist_delay_stages by one if the filter is used + */ + unsigned tist_delay_stages) : + RemoteControllable("tist") { + timestamp_offset = offset_s; + m_tist_delay_stages = tist_delay_stages; inhibit_second_update = 0; time_pps = 0.0; time_secs = 0; @@ -125,10 +121,10 @@ class TimestampDecoder gmtime_r(0, &temp_time); offset_changed = false; - myLogger.level(info) << "Setting up timestamp decoder with " << - (modconfig.use_offset_fixed ? "fixed" : - (modconfig.use_offset_file ? "dynamic" : "none")) << - " offset"; + RC_ADD_PARAMETER(offset, "TIST offset [s]"); + + etiLog.level(info) << "Setting up timestamp decoder with " << + timestamp_offset << " offset"; }; @@ -140,16 +136,25 @@ class TimestampDecoder int framephase, uint16_t mnsc, double pps, - uint32_t fct); + int32_t fct); - /* Update the modulator timestamp offset according to the modconf + /*********** REMOTE CONTROL ***************/ + /* virtual void enrol_at(BaseRemoteController& controller) + * is inherited */ - bool updateModulatorOffset(); - protected: - /* Main program logger */ - Logger& myLogger; + /* Base function to set parameters. */ + virtual void set_parameter(const std::string& parameter, + const std::string& value); + + /* Getting a parameter always returns a string. */ + virtual const std::string get_parameter( + const std::string& parameter) const; + const char* name() { return "TS"; } + + + protected: /* Push a new MNSC field into the decoder */ void pushMNSCData(int framephase, uint16_t mnsc); @@ -167,15 +172,13 @@ class TimestampDecoder struct tm temp_time; uint32_t time_secs; - uint32_t latestFCT; + int32_t latestFCT; double time_pps; double timestamp_offset; + unsigned m_tist_delay_stages; int inhibit_second_update; bool offset_changed; - /* configuration for the offset management */ - struct modulator_offset_config& modconfig; - /* When the type or identifier don't match, the decoder must * be disabled */ @@ -189,8 +192,9 @@ class TimestampDecoder * synchronise two modulators if only one uses (for instance) the * FIRFilter (1 stage pipeline) */ - std::queue<struct frame_timestamp*> queue_timestamps; + std::queue<boost::shared_ptr<struct frame_timestamp> > queue_timestamps; }; #endif + diff --git a/src/Utils.cpp b/src/Utils.cpp new file mode 100644 index 0000000..6c9b0fc --- /dev/null +++ b/src/Utils.cpp @@ -0,0 +1,118 @@ +/* + Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 + Her Majesty the Queen in Right of Canada (Communications Research + Center Canada) + + Copyright (C) 2015 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://opendigitalradio.org + */ +/* + This file is part of ODR-DabMod. + + ODR-DabMod is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + ODR-DabMod is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with ODR-DabMod. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "Utils.h" +#include "GainControl.h" + +void printUsage(char* progName) +{ + FILE* out = stderr; + + fprintf(out, "Welcome to %s %s, compiled at %s, %s\n\n", + PACKAGE, +#if defined(GITVERSION) + GITVERSION, +#else + VERSION, +#endif + __DATE__, __TIME__); + fprintf(out, "Usage with configuration file:\n"); + fprintf(out, "\t%s [-C] config_file.ini\n\n", progName); + + fprintf(out, "Usage with command line options:\n"); + fprintf(out, "\t%s" + " input" + " (-f filename | -u uhddevice -F frequency) " + " [-G txgain]" + " [-o offset]" + " [-T filter_taps_file]" + " [-a gain]" + " [-c clockrate]" + " [-g gainMode]" + " [-h]" + " [-l]" + " [-m dabMode]" + " [-r samplingRate]" + "\n", progName); + fprintf(out, "Where:\n"); + fprintf(out, "input: ETI input filename (default: stdin).\n"); + fprintf(out, "-f name: Use file output with given filename. (use /dev/stdout for standard output)\n"); + fprintf(out, "-u device: Use UHD output with given device string. (use "" for default device)\n"); + fprintf(out, "-F frequency: Set the transmit frequency when using UHD output. (mandatory option when using UHD)\n"); + fprintf(out, "-G txgain: Set the transmit gain for the UHD driver (default: 0)\n"); + fprintf(out, "-o: (UHD only) Set the timestamp offset added to the timestamp in the ETI. The offset is a double.\n"); + fprintf(out, " Specifying this option has two implications: It enables synchronous transmission,\n" + " requiring an external REFCLK and PPS signal and frames that do not contain a valid timestamp\n" + " get muted.\n\n"); + fprintf(out, "-T taps_file: Enable filtering before the output, using the specified file containing the filter taps.\n"); + fprintf(out, "-a gain: Apply digital amplitude gain.\n"); + fprintf(out, "-c rate: Set the DAC clock rate and enable Cic Equalisation.\n"); + fprintf(out, "-g: Set computation gain mode: " + "%u FIX, %u MAX, %u VAR\n", GAIN_FIX, GAIN_MAX, GAIN_VAR); + fprintf(out, "-h: Print this help.\n"); + fprintf(out, "-l: Loop file when reach end of file.\n"); + fprintf(out, "-m mode: Set DAB mode: (0: auto, 1-4: force).\n"); + fprintf(out, "-r rate: Set output sampling rate (default: 2048000).\n\n"); +} + + +void printVersion(void) +{ + FILE *out = stderr; + + fprintf(out, "Welcome to %s %s, compiled at %s, %s\n\n", + PACKAGE, VERSION, __DATE__, __TIME__); + fprintf(out, + " ODR-DabMod is copyright (C) Her Majesty the Queen in Right of Canada,\n" + " 2009, 2010, 2011, 2012 Communications Research Centre (CRC),\n" + " and\n" + " Copyright (C) 2014, 2015 Matthias P. Braendli, matthias.braendli@mpb.li\n" + "\n" + " http://opendigitalradio.org\n" + "\n" + " ODR-DabMod is free software: you can redistribute it and/or modify it\n" + " under the terms of the GNU General Public License as published by the\n" + " Free Software Foundation, either version 3 of the License, or (at your\n" + " option) any later version.\n" + "\n" + " ODR-DabMod is distributed in the hope that it will be useful, but\n" + " WITHOUT ANY WARRANTY; without even the implied warranty of\n" + " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n" + " General Public License for more details.\n" + "\n" + " You should have received a copy of the GNU General Public License along\n" + " with ODR-DabMod. If not, see <http://www.gnu.org/licenses/>.\n" + "\n" +#if USE_KISS_FFT + "ODR-DabMod makes use of the following open source packages:\n" + " Kiss FFT v1.2.9 (Revised BSD) - http://kissfft.sourceforge.net/\n" +#endif + ); + +} + + diff --git a/src/Utils.h b/src/Utils.h new file mode 100644 index 0000000..f023646 --- /dev/null +++ b/src/Utils.h @@ -0,0 +1,62 @@ +/* + Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 + Her Majesty the Queen in Right of Canada (Communications Research + Center Canada) + + Copyright (C) 2015 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://opendigitalradio.org + */ +/* + This file is part of ODR-DabMod. + + ODR-DabMod is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + ODR-DabMod is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with ODR-DabMod. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __UTILS_H_ +#define __UTILS_H_ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <time.h> + +void printUsage(char* progName); + +void printVersion(void); + +inline long timespecdiff_us(struct timespec& oldTime, struct timespec& time) +{ + long tv_sec; + long tv_nsec; + if (time.tv_nsec < oldTime.tv_nsec) { + tv_sec = time.tv_sec - 1 - oldTime.tv_sec; + tv_nsec = 1000000000L + time.tv_nsec - oldTime.tv_nsec; + } + else { + tv_sec = time.tv_sec - oldTime.tv_sec; + tv_nsec = time.tv_nsec - oldTime.tv_nsec; + } + + return tv_sec * 1000 + tv_nsec / 1000; +} + + +#endif + |