diff options
| -rw-r--r-- | INSTALL | 4 | ||||
| -rw-r--r-- | Makefile.am | 12 | ||||
| -rw-r--r-- | configure.ac | 19 | ||||
| -rw-r--r-- | doc/example.ini | 42 | ||||
| -rw-r--r-- | doc/zmq-ctrl/cpp/OdrModCtrl.cpp | 1 | ||||
| -rw-r--r-- | src/DabMod.cpp | 153 | ||||
| -rw-r--r-- | src/DabModulator.cpp | 22 | ||||
| -rw-r--r-- | src/DabModulator.h | 8 | ||||
| -rw-r--r-- | src/EtiReader.cpp | 16 | ||||
| -rw-r--r-- | src/EtiReader.h | 10 | ||||
| -rw-r--r-- | src/FIRFilter.cpp | 21 | ||||
| -rw-r--r-- | src/FIRFilter.h | 8 | ||||
| -rw-r--r-- | src/InputFileReader.cpp | 49 | ||||
| -rw-r--r-- | src/InputReader.h | 21 | ||||
| -rw-r--r-- | src/InputZeroMQReader.cpp | 48 | ||||
| -rw-r--r-- | src/OutputUHD.cpp | 808 | ||||
| -rw-r--r-- | src/OutputUHD.h | 58 | ||||
| -rw-r--r-- | src/RemoteControl.cpp | 15 | ||||
| -rw-r--r-- | src/RemoteControl.h | 10 | ||||
| -rw-r--r-- | src/SignalMultiplexer.cpp | 14 | ||||
| -rw-r--r-- | src/TII.cpp | 367 | ||||
| -rw-r--r-- | src/TII.h | 105 | ||||
| -rw-r--r-- | src/ThreadsafeQueue.h | 24 | ||||
| -rw-r--r-- | src/TimestampDecoder.cpp | 108 | ||||
| -rw-r--r-- | src/TimestampDecoder.h | 73 | ||||
| -rw-r--r-- | src/Utils.cpp | 5 | ||||
| -rw-r--r-- | src/Utils.h | 18 | 
27 files changed, 1351 insertions, 688 deletions
| @@ -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 fa8afd0..65c48c5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -26,17 +26,11 @@ ACLOCAL_AMFLAGS = -I m4  EXTRA_DIST = COPYING NEWS README.md AUTHORS ChangeLog TODO doc  if IS_GIT_REPO -GITVERSION_FLAGS = -DGITVERSION="\"`git describe`\"" +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 @@ -71,7 +65,7 @@ FFT_FLG=  endif  odr_dabmod_CPPFLAGS = -Wall -Isrc \ -					  $(FFT_INC) $(FFT_FLG) $(SIMD_CFLAGS) $(GITVERSION_FLAGS) +					  $(FFT_INC) $(FFT_FLG) $(GITVERSION_FLAGS)  odr_dabmod_LDADD    = $(FFT_LDADD)  odr_dabmod_SOURCES  = src/DabMod.cpp \  					  src/PcDebug.h \ @@ -165,6 +159,8 @@ odr_dabmod_SOURCES  = src/DabMod.cpp \  					  src/FormatConverter.h \  					  src/Utils.cpp \  					  src/Utils.h \ +					  src/TII.cpp \ +					  src/TII.h \  					  src/zmq.hpp  dist_bin_SCRIPTS = src/crc-dwap.py diff --git a/configure.ac b/configure.ac index 9841ac9..0de0b22 100644 --- a/configure.ac +++ b/configure.ac @@ -164,21 +164,24 @@ AC_LANG_POP([C++])  # Check for SSE  AC_MSG_CHECKING(for SSE in current arch/CFLAGS) -AC_LINK_IFELSE([ -AC_LANG_PROGRAM([[ + +save_CXXFLAGS="$CXXFLAGS" +CXXFLAGS="$CXXFLAGS -msse" +AC_MSG_CHECKING([whether we need to add -msse]) +AC_COMPILE_IFELSE(  [AC_LANG_PROGRAM([[  #include <xmmintrin.h>  __m128 testfunc(float *a, float *b) {    return _mm_add_ps(_mm_loadu_ps(a), _mm_loadu_ps(b));  }  ]])], -[ -has_sse=yes -], -[ -has_sse=no -] +[has_sse=yes], +[has_sse=no]  )  AC_MSG_RESULT($has_sse) +if test x"$has_sse" = xno; then +    CXXFLAGS="$save_CXXFLAGS" +fi +  AM_CONDITIONAL([HAVE_SSE], [test "x$has_sse" = "xyes"]) diff --git a/doc/example.ini b/doc/example.ini index ee9d567..417cbf3 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] @@ -182,17 +182,27 @@ 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. +; This value is also used for the initial lock check, and must +; be at least a minute so that the GPSOD has enough time to lock +; and to start disciplining its oscillator. +; Units: seconds +; Set to 0 to disable holdover check +; default value: 0 +max_gps_holdover_time=600 +  ; section defining ZeroMQ output properties  [zmqoutput] @@ -214,16 +224,20 @@ 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 + +; The previous static vs dynamic offset distinction, and reading the +; modulatoroffset from a file has been removed. -fixedoffset=0.002 +[tii] +; (experimental) +; If these options are set, TII transmission is enabled. +; DAB modes I and II are supported, and must be set explicitly in +; this file. Reading DAB mode from ETI is not supported. +enable=0 +comb=16 +pattern=3 -; The file should contain a single floating point value, written -; in ASCII (it's human-readable, not binary) -dynamicoffsetfile=modulator_offset diff --git a/doc/zmq-ctrl/cpp/OdrModCtrl.cpp b/doc/zmq-ctrl/cpp/OdrModCtrl.cpp index 731a9af..416ffcd 100644 --- a/doc/zmq-ctrl/cpp/OdrModCtrl.cpp +++ b/doc/zmq-ctrl/cpp/OdrModCtrl.cpp @@ -51,6 +51,7 @@ COdrModCtrl::COdrModCtrl(zmq::context_t *pContext, std::string odrEndpoint,  	m_pContext = pContext;  	m_odrEndpoint = odrEndpoint;  	m_timeoutMs = (uint32_t) timeoutMs; +	m_pReqSocket = NULL;  }  COdrModCtrl::~COdrModCtrl() diff --git a/src/DabMod.cpp b/src/DabMod.cpp index ec1a4cd..3ed5e40 100644 --- a/src/DabMod.cpp +++ b/src/DabMod.cpp @@ -70,7 +70,7 @@  #   define memalign(a, b)   malloc(b)  #endif -#define ZMQ_INPUT_MAX_FRAME_QUEUE 50 +#define ZMQ_INPUT_MAX_FRAME_QUEUE 500  typedef std::complex<float> complexf; @@ -108,7 +108,7 @@ enum run_modulator_state {      MOD_AGAIN  }; -run_modulator_state run_modulator(Logger& logger, modulator_data& m); +run_modulator_state run_modulator(modulator_data& m);  int launch_modulator(int argc, char* argv[])  { @@ -132,6 +132,7 @@ int launch_modulator(int argc, char* argv[])      float normalise = 1.0f;      GainMode gainMode = GAIN_VAR; +    tii_config_t tiiConfig;      /* UHD requires the input I and Q samples to be in the interval       * [-1.0,1.0], otherwise they get truncated, which creates very @@ -157,10 +158,8 @@ int launch_modulator(int argc, char* argv[])      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;      shared_ptr<Flowgraph> flowgraph(new Flowgraph());      shared_ptr<FormatConverter> format_converter; @@ -171,10 +170,9 @@ int launch_modulator(int argc, char* argv[])      bool run_again = true; -    Logger logger; -    InputFileReader inputFileReader(logger); +    InputFileReader inputFileReader;  #if defined(HAVE_ZEROMQ) -    shared_ptr<InputZeroMQReader> inputZeroMQReader(new InputZeroMQReader(logger)); +    shared_ptr<InputZeroMQReader> inputZeroMQReader(new InputZeroMQReader());  #endif      struct sigaction sa; @@ -239,25 +237,7 @@ int launch_modulator(int argc, char* argv[])              loop = true;              break;          case 'o': -            if (modconf.use_offset_file) -            { -                fprintf(stderr, "Options -o and -O are mutually exclusive\n"); -                throw std::invalid_argument("Invalid command line options"); -            } -            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"); -                throw std::invalid_argument("Invalid command line options"); -            } -            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 @@ -402,7 +382,7 @@ int launch_modulator(int argc, char* argv[])          // 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) { @@ -417,7 +397,7 @@ int launch_modulator(int argc, char* argv[])              }              LogToFile* log_file = new LogToFile(logfilename); -            logger.register_backend(log_file); +            etiLog.register_backend(log_file);          } @@ -559,6 +539,8 @@ int launch_modulator(int argc, char* argv[])                  throw std::runtime_error("Configuration error");              } +            outputuhd_conf.maxGPSHoldoverTime = pt.get("uhdoutput.max_gps_holdover_time", 0); +              useUHDOutput = 1;          }  #endif @@ -577,49 +559,45 @@ int launch_modulator(int argc, char* argv[])  #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"; +                std::cerr << "Error: delaymanagement: synchronous is enabled, but no offset defined!\n";                  throw std::runtime_error("Configuration error");              }          }          outputuhd_conf.muteNoTimestamps = (pt.get("delaymanagement.mutenotimestamps", 0) == 1);  #endif + +        /* Read TII parameters from config file */ +        tiiConfig.enable  = pt.get("tii.enable", 0); +        tiiConfig.comb    = pt.get("tii.comb", 0); +        tiiConfig.pattern = pt.get("tii.pattern", 0);      }      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 @@ -647,12 +625,12 @@ int launch_modulator(int argc, char* argv[])          fprintf(stderr, "\n");          printUsage(argv[0]);          ret = -1; -        logger.level(error) << "Received invalid command line arguments"; +        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 !");          throw std::runtime_error("Configuration error");      } @@ -671,10 +649,14 @@ int launch_modulator(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) { @@ -700,7 +682,7 @@ int launch_modulator(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;              throw std::runtime_error("Unable to open input");          } @@ -741,7 +723,7 @@ int launch_modulator(int argc, char* argv[])      else if (useUHDOutput) {          normalise = 1.0f / normalise_factor;          outputuhd_conf.sampleRate = outputRate; -        output = make_shared<OutputUHD>(outputuhd_conf, &logger); +        output = make_shared<OutputUHD>(outputuhd_conf);          ((OutputUHD*)output.get())->enrol_at(rcs);      }  #endif @@ -772,8 +754,9 @@ int launch_modulator(int argc, char* argv[])          shared_ptr<InputMemory> input(new InputMemory(&m.data));          shared_ptr<DabModulator> modulator( -                new DabModulator(modconf, &rcs, logger, outputRate, clockRate, -                    dabMode, gainMode, digitalgain, normalise, filterTapsFilename)); +                new DabModulator(tist_offset_s, tist_delay_stages, &rcs, +                    tiiConfig, outputRate, clockRate, dabMode, gainMode, +                    digitalgain, normalise, filterTapsFilename));          flowgraph.connect(input, modulator);          if (format_converter) { @@ -792,41 +775,47 @@ int launch_modulator(int argc, char* argv[])          m.inputReader->PrintInfo(); -        run_modulator_state st = run_modulator(logger, m); +        run_modulator_state st = run_modulator(m);          switch (st) {              case MOD_FAILURE: -                fprintf(stderr, "\nModulator failure.\n"); +                etiLog.level(error) << "Modulator failure.";                  run_again = false;                  ret = 1;                  break; -#if defined(HAVE_ZEROMQ)              case MOD_AGAIN: -                fprintf(stderr, "\nRestart modulator\n"); -                running = true; -                if (inputTransport == "zeromq") { +                etiLog.level(warn) << "Restart modulator."; +                run_again = false; +                if (inputTransport == "file") { +                    if (inputFileReader.Open(inputName, loop) == -1) { +                        etiLog.level(error) << "Unable to open input file!"; +                        ret = 1; +                    } +                    else { +                        run_again = true; +                    } +                } +                else if (inputTransport == "zeromq") { +#if defined(HAVE_ZEROMQ)                      run_again = true; -                      // Create a new input reader -                    inputZeroMQReader = make_shared<InputZeroMQReader>(logger); +                    inputZeroMQReader = make_shared<InputZeroMQReader>();                      inputZeroMQReader->Open(inputName, inputMaxFramesQueued);                      m.inputReader = inputZeroMQReader.get(); +#endif                  }                  break; -#endif              case MOD_NORMAL_END:              default: -                fprintf(stderr, "\nModulator stopped.\n"); +                etiLog.level(info) << "modulator stopped.";                  ret = 0;                  run_again = false;                  break;          }          fprintf(stderr, "\n\n"); -        fprintf(stderr, "%lu DAB frames encoded\n", m.framecount); -        fprintf(stderr, "%f seconds encoded\n", (float)m.framecount * 0.024f); - -        fprintf(stderr, "\nCleaning flowgraph...\n"); +        etiLog.level(info) << m.framecount << " DAB frames encoded"; +        etiLog.level(info) << ((float)m.framecount * 0.024f) << " seconds encoded";          m.data.setLength(0);      } @@ -835,11 +824,11 @@ int launch_modulator(int argc, char* argv[])      // Cleaning things      //////////////////////////////////////////////////////////////////////// -    logger.level(info) << "Terminating"; +    etiLog.level(info) << "Terminating";      return ret;  } -run_modulator_state run_modulator(Logger& logger, modulator_data& m) +run_modulator_state run_modulator(modulator_data& m)  {      run_modulator_state ret = MOD_FAILURE;      try { @@ -873,10 +862,10 @@ run_modulator_state run_modulator(Logger& logger, modulator_data& m)                  }              }              if (framesize == 0) { -                logger.level(info) << "End of file reached."; +                etiLog.level(info) << "End of file reached.";              }              else { -                logger.level(error) << "Input read error."; +                etiLog.level(error) << "Input read error.";              }              running = 0;              ret = MOD_NORMAL_END; @@ -884,15 +873,15 @@ run_modulator_state run_modulator(Logger& logger, modulator_data& m)  #if defined(HAVE_OUTPUT_UHD)      } catch (fct_discontinuity_error& e) {          // The OutputUHD saw a FCT discontinuity -        logger.level(warn) << e.what(); +        etiLog.level(warn) << e.what();          ret = MOD_AGAIN;  #endif      } catch (zmq_input_overflow& e) {          // The ZeroMQ input has overflowed its buffer -        logger.level(warn) << e.what(); +        etiLog.level(warn) << e.what();          ret = MOD_AGAIN;      } catch (std::exception& e) { -        logger.level(error) << "Exception caught: " << e.what(); +        etiLog.level(error) << "Exception caught: " << e.what();          ret = MOD_FAILURE;      } diff --git a/src/DabModulator.cpp b/src/DabModulator.cpp index 667d885..8a52401 100644 --- a/src/DabModulator.cpp +++ b/src/DabModulator.cpp @@ -26,6 +26,7 @@   */  #include <string> +#include <boost/make_shared.hpp>  #include "DabModulator.h"  #include "PcDebug.h" @@ -46,6 +47,7 @@  #include "Resampler.h"  #include "ConvEncoder.h"  #include "FIRFilter.h" +#include "TII.h"  #include "PuncturingEncoder.h"  #include "TimeInterleaver.h"  #include "TimestampDecoder.h" @@ -55,25 +57,25 @@  using namespace boost;  DabModulator::DabModulator( -        struct modulator_offset_config& modconf, +        double tist_offset_s, unsigned tist_delay_stages,          RemoteControllers* rcs, -        Logger& logger, +        const tii_config_t& tiiConfig,          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), +    myTiiConfig(tiiConfig),      myRCs(rcs)  {      PDEBUG("DabModulator::DabModulator(%u, %u, %u, %u) @ %p\n", @@ -196,6 +198,14 @@ int DabModulator::process(Buffer* const dataIn, Buffer* dataOut)                  (float)mySpacing * (float)myOutputRate / 2048000.0f,                  cic_ratio)); +        shared_ptr<TII> tii; +        try { +            tii = make_shared<TII>(myDabMode, myTiiConfig); +            tii->enrol_at(*myRCs); +        } +        catch (std::runtime_error& e) { +            etiLog.level(error) << "Could not initialise TII, skipping!"; +        }          shared_ptr<OfdmGenerator> cifOfdm(                  new OfdmGenerator((1 + myNbSymbols), myNbCarriers, mySpacing)); @@ -346,6 +356,10 @@ int DabModulator::process(Buffer* const dataIn, Buffer* dataOut)          myFlowgraph->connect(cifFreq, cifDiff);          myFlowgraph->connect(cifNull, cifSig);          myFlowgraph->connect(cifDiff, cifSig); +        if (tii) { +            myFlowgraph->connect(tii, cifSig); +        } +          if (useCicEq) {              myFlowgraph->connect(cifSig, cifCicEq);              myFlowgraph->connect(cifCicEq, cifOfdm); diff --git a/src/DabModulator.h b/src/DabModulator.h index 89ddd7c..cee066a 100644 --- a/src/DabModulator.h +++ b/src/DabModulator.h @@ -43,15 +43,16 @@  #include "OutputMemory.h"  #include "RemoteControl.h"  #include "Log.h" +#include "TII.h"  class DabModulator : public ModCodec  {  public:      DabModulator( -            struct modulator_offset_config& modconf, +            double tist_offset_s, unsigned tist_delay_stages,              RemoteControllers* rcs, -            Logger& logger, +            const tii_config_t& tiiConfig,              unsigned outputRate = 2048000, unsigned clockRate = 0,              unsigned dabMode = 0, GainMode gainMode = GAIN_VAR,              float digGain = 1.0, float normalise = 1.0, @@ -66,8 +67,6 @@ public:      EtiReader* getEtiReader() { return &myEtiReader; }  protected: -    Logger& myLogger; -      void setMode(unsigned mode);      unsigned myOutputRate; @@ -80,6 +79,7 @@ protected:      Flowgraph* myFlowgraph;      OutputMemory* myOutput;      std::string myFilterTapsFilename; +    tii_config_t myTiiConfig;      RemoteControllers* myRCs;      size_t myNbSymbols; diff --git a/src/EtiReader.cpp b/src/EtiReader.cpp index 0e4182d..f584275 100644 --- a/src/EtiReader.cpp +++ b/src/EtiReader.cpp @@ -51,15 +51,18 @@ 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;  } @@ -286,11 +289,6 @@ int EtiReader::process(const 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 b893f01..84ad9b4 100644 --- a/src/EtiReader.h +++ b/src/EtiReader.h @@ -47,7 +47,10 @@  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&); @@ -67,9 +70,6 @@ public:      bool sourceContainsTimestamp();  protected: -    /* Main program logger */ -    Logger& myLogger; -      /* Transform the ETI TIST to a PPS offset in ms */      double getPPSOffset(); @@ -89,8 +89,6 @@ protected:  private:      size_t myCurrentFrame; -    bool time_ext_enabled; -    unsigned long timestamp_seconds;      bool eti_fc_valid;  }; 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/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 13d49b8..b262cc9 100644 --- a/src/InputReader.h +++ b/src/InputReader.h @@ -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,9 +87,9 @@ class InputReader  class InputFileReader : public InputReader  {      public: -        InputFileReader(Logger logger) : +        InputFileReader() :              streamtype_(ETI_STREAM_TYPE_NONE), -            inputfile_(NULL), logger_(logger) {}; +            inputfile_(NULL) { }          ~InputFileReader()          { @@ -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,7 +128,6 @@ 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 @@ -143,7 +147,7 @@ struct zmq_input_overflow : public std::exception  struct InputZeroMQThreadData  { -    ThreadsafeQueue<uint8_t*> *in_messages; +    ThreadsafeQueue<boost::shared_ptr<std::vector<uint8_t> > > *in_messages;      std::string uri;      unsigned max_queued_frames; @@ -179,8 +183,7 @@ class InputZeroMQWorker  class InputZeroMQReader : public InputReader  {      public: -        InputZeroMQReader(Logger logger) : -            logger_(logger), in_messages_(10) +        InputZeroMQReader()          {              workerdata_.in_messages = &in_messages_;              workerdata_.running     = false; @@ -198,12 +201,12 @@ class InputZeroMQReader : public InputReader          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_;  }; diff --git a/src/InputZeroMQReader.cpp b/src/InputZeroMQReader.cpp index f8c15c4..36d4e4b 100644 --- a/src/InputZeroMQReader.cpp +++ b/src/InputZeroMQReader.cpp @@ -37,6 +37,7 @@  #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" @@ -84,16 +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; + +    /* 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); +    }      if (! workerdata_.running) {          throw zmq_input_overflow();      } -    memcpy(buffer, incoming, framesize); - -    delete incoming; +    memcpy(buffer, &incoming->front(), framesize);      return framesize;  } @@ -135,16 +145,14 @@ void InputZeroMQWorker::RecvProcess(struct InputZeroMQThreadData* workerdata)              }              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) + @@ -155,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); @@ -182,7 +187,7 @@ 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"); @@ -199,18 +204,17 @@ void InputZeroMQWorker::RecvProcess(struct InputZeroMQThreadData* workerdata)              }              if (queue_size < 5) { -                fprintf(stderr, "ZeroMQ buffer low: %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(); diff --git a/src/OutputUHD.cpp b/src/OutputUHD.cpp index dbf8b9d..adc7b9a 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,63 @@ 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; +    } +    else { +        etiLog.level(debug) << "UHD Message: " << msg; +    } +} + +// Check function for GPS TIMELOCK sensor +bool check_gps_timelock(uhd::usrp::multi_usrp::sptr usrp) +{ +    try { +        std::string sensor_value( +                usrp->get_mboard_sensor("gps_timelock", 0).to_pp_string()); + +        if (sensor_value.find("TIME LOCKED") == std::string::npos) { +            etiLog.level(warn) << "OutputUHD: gps_timelock " << sensor_value; +            return false; +        } + +        return true; +    } +    catch (uhd::lookup_error &e) { +        etiLog.level(warn) << "OutputUHD: no gps_timelock sensor"; +        return false; +    } +} + +  OutputUHD::OutputUHD( -        const 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 @@ -92,6 +137,10 @@ OutputUHD::OutputUHD(      RC_ADD_PARAMETER(muting, "Mute the output by stopping the transmitter");      RC_ADD_PARAMETER(staticdelay, "Set static delay (uS) between 0 and 96000"); +    // 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();      //create a usrp device @@ -151,60 +200,6 @@ 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()); -    } - -      // preparing output thread worker data      uwd.myUsrp = myUsrp;  #endif @@ -214,17 +209,22 @@ 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; @@ -244,28 +244,26 @@ 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; +        // 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. @@ -283,8 +281,23 @@ 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); @@ -319,17 +332,28 @@ int OutputUHD::process(Buffer* dataIn, Buffer* dataOut)          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) { @@ -339,7 +363,7 @@ int OutputUHD::process(Buffer* dataIn, Buffer* dataOut)                  throw fct_discontinuity_error();              }              else { -                myLogger->level(error) << +                etiLog.level(error) <<                      "OutputUHD: Error, UHD worker failed";                  throw std::runtime_error("UHD worker failed");              } @@ -384,71 +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 OutputUHD::initial_gps_check() +{ +    if (first_gps_fix_check.tv_sec == 0) { +        etiLog.level(info) << "Waiting for GPS fix"; + +        if (clock_gettime(CLOCK_MONOTONIC, &first_gps_fix_check) != 0) { +            stringstream ss; +            ss << "clock_gettime failure: " << strerror(errno); +            throw std::runtime_error(ss.str()); +        } +    } + +    check_gps(); + +    if (last_gps_fix_check.tv_sec > +            first_gps_fix_check.tv_sec + initial_gps_fix_wait) { +        stringstream ss; +        ss << "GPS did not show time lock in " << initial_gps_fix_wait << " seconds"; +        throw std::runtime_error(ss.str()); +    } + +    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 Time Lock lost"; +                    } +                    num_checks_without_gps_fix++; +                } +                else { +                    if (num_checks_without_gps_fix) { +                        etiLog.level(info) << +                            "OutputUHD: GPS Time Lock 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 Time Lock 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_timelock, 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) { -        uwd->logger->level(warn) << e.what(); +        etiLog.level(warn) << e.what();          uwd->failed_due_to_fct = true;      }      uwd->running = false;      uwd->sync_barrier.get()->wait(); -    uwd->logger->level(warn) << "UHD worker terminated"; +    etiLog.level(warn) << "UHD worker terminated";  }  void UHDWorker::process()  { -    int workerbuffer  = 0; -    time_t tx_second = 0; -    double pps_offset = 0; -    double last_pps   = 2.0; -    double usrp_time; - -    //const struct timespec hundred_nano = {0, 100}; - -    size_t sizeIn; -    struct UHDWorkerFrameData* frame; - -    size_t num_acc_samps; //number of accumulated samples -    //int write_fail_count; - -    // Transmit timeout -    const double timeout = 0.2; +    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; -    int expected_next_fct = -1; +    expected_next_fct = -1; + +    num_underflows   = 0; +    num_late_packets = 0;      while (uwd->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; +        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);          } @@ -460,248 +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 (frame->ts.fct == -1) { -            uwd->logger->level(info) << -                "OutputUHD: dropping one frame with invalid FCT"; -            goto loopend; -        } -        if (expected_next_fct != -1) { -            if (expected_next_fct != (int)frame->ts.fct) { -                uwd->logger->level(warn) << -                    "OutputUHD: Incorrect expect fct " << frame->ts.fct << -                    ", expected " << expected_next_fct; +    pps_offset = frame->ts.timestamp_pps_offset; -                fct_discontinuity = true; -                throw fct_discontinuity_error(); -            } -        } +    // 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() + timeout < 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 0 // Let uhd handle this -            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 -            } -#endif -            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 (uwd->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; +        print_async_metadata(frame); +    } +} -loopend: -        // swap buffers -        workerbuffer = (workerbuffer + 1) % 2; +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)  { @@ -771,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 aed80f6..8234340 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 @@ -120,6 +120,9 @@ struct UHDWorkerData {      // If we want to verify loss of refclk      bool check_refclk_loss; +    // If we want to check for the gps_timelock sensor +    bool check_gpsfix; +      // muting set by remote control      bool muting; @@ -129,9 +132,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. @@ -156,14 +156,29 @@ class UHDWorker {          }      private: -        void process(); -        void process_errhandler(); +        // 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);          struct UHDWorkerData *uwd;          boost::thread uhd_thread;          uhd::tx_streamer::sptr myTxStream; + +        void process(); +        void process_errhandler();  };  /* This structure is used as initial configuration for OutputUHD */ @@ -181,6 +196,7 @@ struct OutputUHDConfig {      bool enableSync;      bool muteNoTimestamps;      unsigned dabMode; +    unsigned maxGPSHoldoverTime;      /* allowed values : auto, int, sma, mimo */      std::string refclk_src; @@ -199,9 +215,7 @@ struct OutputUHDConfig {  class OutputUHD: public ModOutput, public RemoteControllable {      public: -        OutputUHD( -                const OutputUHDConfig& config, -                Logger *logger); +        OutputUHD(const OutputUHDConfig& config);          ~OutputUHD();          int process(Buffer* dataIn, Buffer* dataOut); @@ -227,13 +241,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; @@ -250,6 +267,27 @@ class OutputUHD: public ModOutput, public RemoteControllable {          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 = 180; + +        // 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/RemoteControl.cpp b/src/RemoteControl.cpp index 65da3b7..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; @@ -308,7 +310,7 @@ void RemoteControllerZmq::process()  {      // create zmq reply socket for receiving ctrl parameters      zmq::socket_t repSocket(m_zmqContext, ZMQ_REP); -    std::cout << "Starting zmq remote control thread" << std::endl; +    std::cerr << "Starting zmq remote control thread" << std::endl;      try      {          // connect the socket @@ -342,10 +344,9 @@ void RemoteControllerZmq::process()                      try                      {                          std::string value = get_param_(module, parameter); -                        zmq::message_t *pMsg = new zmq::message_t(value.size()); -                        memcpy ((void*) pMsg->data(), value.data(), value.size()); -                        repSocket.send(*pMsg, 0); -                        delete pMsg; +                        zmq::message_t msg(value.size()); +                        memcpy ((void*) msg.data(), value.data(), value.size()); +                        repSocket.send(&msg, 0);                      }                      catch (ParameterError &err)                      { 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/SignalMultiplexer.cpp b/src/SignalMultiplexer.cpp index c5be552..8edcdc2 100644 --- a/src/SignalMultiplexer.cpp +++ b/src/SignalMultiplexer.cpp @@ -46,6 +46,7 @@ SignalMultiplexer::~SignalMultiplexer()  // dataIn[0] -> null symbol  // dataIn[1] -> MSC symbols +// dataIn[2] -> (optional) TII symbol  int SignalMultiplexer::process(std::vector<Buffer*> dataIn, Buffer* dataOut)  {  #ifdef DEBUG @@ -60,10 +61,17 @@ int SignalMultiplexer::process(std::vector<Buffer*> dataIn, Buffer* dataOut)      fprintf(stderr, ", dataOut: %p, sizeOut: %zu)\n", dataOut, dataOut->getLength());  #endif -    assert(dataIn.size() == 2); +    assert(dataIn.size() == 2 or dataIn.size() == 3); -    *dataOut = *dataIn[0]; -    *dataOut += *dataIn[1]; +    if (dataIn.size() == 2) { +        *dataOut = *dataIn[0]; +        *dataOut += *dataIn[1]; +    } +    else if (dataIn.size() == 3) { +        *dataOut = *dataIn[2]; +        *dataOut += *dataIn[1]; +    }      return dataOut->getLength();  } + diff --git a/src/TII.cpp b/src/TII.cpp new file mode 100644 index 0000000..6d969a5 --- /dev/null +++ b/src/TII.cpp @@ -0,0 +1,367 @@ +/* +   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. + +   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 "TII.h" +#include "PcDebug.h" + +#include <stdio.h> +#include <stdexcept> +#include <string.h> + +typedef std::complex<float> complexf; + +/* TII pattern for TM I, II, IV */ +const int pattern_tm1_2_4[][8] = { // {{{ +    {0,0,0,0,1,1,1,1}, +    {0,0,0,1,0,1,1,1}, +    {0,0,0,1,1,0,1,1}, +    {0,0,0,1,1,1,0,1}, +    {0,0,0,1,1,1,1,0}, +    {0,0,1,0,0,1,1,1}, +    {0,0,1,0,1,0,1,1}, +    {0,0,1,0,1,1,0,1}, +    {0,0,1,0,1,1,1,0}, +    {0,0,1,1,0,0,1,1}, +    {0,0,1,1,0,1,0,1}, +    {0,0,1,1,0,1,1,0}, +    {0,0,1,1,1,0,0,1}, +    {0,0,1,1,1,0,1,0}, +    {0,0,1,1,1,1,0,0}, +    {0,1,0,0,0,1,1,1}, +    {0,1,0,0,1,0,1,1}, +    {0,1,0,0,1,1,0,1}, +    {0,1,0,0,1,1,1,0}, +    {0,1,0,1,0,0,1,1}, +    {0,1,0,1,0,1,0,1}, +    {0,1,0,1,0,1,1,0}, +    {0,1,0,1,1,0,0,1}, +    {0,1,0,1,1,0,1,0}, +    {0,1,0,1,1,1,0,0}, +    {0,1,1,0,0,0,1,1}, +    {0,1,1,0,0,1,0,1}, +    {0,1,1,0,0,1,1,0}, +    {0,1,1,0,1,0,0,1}, +    {0,1,1,0,1,0,1,0}, +    {0,1,1,0,1,1,0,0}, +    {0,1,1,1,0,0,0,1}, +    {0,1,1,1,0,0,1,0}, +    {0,1,1,1,0,1,0,0}, +    {0,1,1,1,1,0,0,0}, +    {1,0,0,0,0,1,1,1}, +    {1,0,0,0,1,0,1,1}, +    {1,0,0,0,1,1,0,1}, +    {1,0,0,0,1,1,1,0}, +    {1,0,0,1,0,0,1,1}, +    {1,0,0,1,0,1,0,1}, +    {1,0,0,1,0,1,1,0}, +    {1,0,0,1,1,0,0,1}, +    {1,0,0,1,1,0,1,0}, +    {1,0,0,1,1,1,0,0}, +    {1,0,1,0,0,0,1,1}, +    {1,0,1,0,0,1,0,1}, +    {1,0,1,0,0,1,1,0}, +    {1,0,1,0,1,0,0,1}, +    {1,0,1,0,1,0,1,0}, +    {1,0,1,0,1,1,0,0}, +    {1,0,1,1,0,0,0,1}, +    {1,0,1,1,0,0,1,0}, +    {1,0,1,1,0,1,0,0}, +    {1,0,1,1,1,0,0,0}, +    {1,1,0,0,0,0,1,1}, +    {1,1,0,0,0,1,0,1}, +    {1,1,0,0,0,1,1,0}, +    {1,1,0,0,1,0,0,1}, +    {1,1,0,0,1,0,1,0}, +    {1,1,0,0,1,1,0,0}, +    {1,1,0,1,0,0,0,1}, +    {1,1,0,1,0,0,1,0}, +    {1,1,0,1,0,1,0,0}, +    {1,1,0,1,1,0,0,0}, +    {1,1,1,0,0,0,0,1}, +    {1,1,1,0,0,0,1,0}, +    {1,1,1,0,0,1,0,0}, +    {1,1,1,0,1,0,0,0}, +    {1,1,1,1,0,0,0,0} }; // }}} + +TII::TII(unsigned int dabmode, const tii_config_t& tii_config) : +    ModCodec(ModFormat(0), ModFormat(0)), +    RemoteControllable("tii"), +    m_dabmode(dabmode), +    m_enable(tii_config.enable), +    m_comb(tii_config.comb), +    m_pattern(tii_config.pattern), +    m_insert(true) +{ +    PDEBUG("TII::TII(%u) @ %p\n", dabmode, this); + +    RC_ADD_PARAMETER(enable, "enable TII [0-1]"); +    RC_ADD_PARAMETER(comb, "TII comb number [0-23]"); +    RC_ADD_PARAMETER(pattern, "TII pattern number [0-69]"); + +    switch (m_dabmode) { +        case 1: +            m_carriers = 1536; + +            if (not(0 <= m_pattern and m_pattern <= 69) ) { +                throw std::runtime_error( +                        "TII::TII pattern not valid!"); +            } +            break; +        case 2: +            m_carriers = 384; + +            if (not(0 <= m_pattern and m_pattern <= 69) ) { +                throw std::runtime_error( +                        "TII::TII pattern not valid!"); +            } +            break; +        /* unsupported +        case 3: +            m_carriers = 192; +            break; +        case 4: +            d_dabmode = 0; +        case 0: +        */ +        default: +            std::stringstream ss_exception; +            ss_exception << +                    "TII::TII DAB mode " << m_dabmode << " not valid!"; +            throw std::runtime_error(ss_exception.str()); +    } + +    if (not(0 <= m_comb and m_comb <= 23) ) { +        throw std::runtime_error( +                "TII::TII comb not valid!"); +    } + +    m_dataIn.clear(); +    m_dataIn.resize(m_carriers); +    prepare_pattern(); + +    myOutputFormat.size(m_carriers * sizeof(complexf)); +} + + +TII::~TII() +{ +    PDEBUG("TII::~TII() @ %p\n", this); +} + +const char* TII::name() +{ +    // Calculate name on demand because comb and pattern are +    // modifiable through RC +    std::stringstream ss; +    ss << "TII(comb:" << m_comb << ", pattern:" << m_pattern << ")"; +    m_name = ss.str(); + +    return m_name.c_str(); +} + + +int TII::process(Buffer* const dataIn, Buffer* dataOut) +{ +    PDEBUG("TII::process(dataIn: %p, dataOut: %p)\n", +            dataIn, dataOut); + +    if ((dataIn != NULL) && (dataIn->getLength() != 0)) { +        throw std::runtime_error( +                "TII::process input size not valid!"); +    } + +    if (m_enable and m_insert) { +        boost::mutex::scoped_lock lock(m_dataIn_mutex); +        dataOut->setData(&m_dataIn[0], m_carriers * sizeof(complexf)); +    } +    else { +        dataOut->setLength(m_carriers * sizeof(complexf)); +        bzero(dataOut->getData(), dataOut->getLength()); +    } + +    // TODO wrong! Must align with frames containing the right data +    m_insert = not m_insert; + +    return 1; +} + +void TII::enable_carrier(int k) { +    int ix = m_carriers/2 + k; + +    if (ix < 0 or ix+1 >= (ssize_t)m_dataIn.size()) { +        throw std::runtime_error( +                "TII::enable_carrier invalid k!"); +    } + +    // TODO power of the carrier ? +    m_dataIn.at(ix) = 1.0; +    m_dataIn.at(ix+1) = 1.0; // TODO verify if +1 is really correct +} + +void TII::prepare_pattern() { +    int comb = m_comb; // Convert from unsigned to signed + +    boost::mutex::scoped_lock lock(m_dataIn_mutex); + +    // Clear previous pattern +    for (size_t i = 0; i < m_dataIn.size(); i++) { +        m_dataIn[i] = 0.0; +    } + +    // This could be written more efficiently, but since it is +    // not performance-critial, it makes sense to write it +    // in the same way as the specification in +    // ETSI EN 300 401 Clause 14.8 +    if (m_dabmode == 1) { +        for (int k = -768; k < -384; k++) { +            for (int b = 0; b < 8; b++) { +                if (    k == -768 + 2 * comb + 48 * b and +                        pattern_tm1_2_4[m_pattern][b]) { +                    enable_carrier(k); +                } +            } +        } + +        for (int k = -384; k < -0; k++) { +            for (int b = 0; b < 8; b++) { +                if (    k == -384 + 2 * comb + 48 * b and +                        pattern_tm1_2_4[m_pattern][b]) { +                    enable_carrier(k); +                } +            } +        } + +        for (int k = 1; k <= 384; k++) { +            for (int b = 0; b < 8; b++) { +                if (    k == 1 + 2 * comb + 48 * b and +                        pattern_tm1_2_4[m_pattern][b]) { +                    enable_carrier(k); +                } +            } +        } + +        for (int k = 384; k <= 768; k++) { +            for (int b = 0; b < 8; b++) { +                if (    k == 385 + 2 * comb + 48 * b and +                        pattern_tm1_2_4[m_pattern][b]) { +                    enable_carrier(k); +                } +            } +        } +    } +    else if (m_dabmode == 2) { +        for (int k = -192; k <= 192; k++) { +            for (int b = 0; b < 4; b++) { +                if (    k == -192 + 2 * comb + 48 * b and +                        pattern_tm1_2_4[m_pattern][b]) { +                    enable_carrier(k); +                } +            } + +            for (int b = 4; b < 8; b++) { +                if (    k == -191 + 2 * comb + 48 * b and +                        pattern_tm1_2_4[m_pattern][b]) { +                    enable_carrier(k); +                } +            } +        } +    } +    else { +        throw std::runtime_error( +                "TII::TII DAB mode not valid!"); +    } +} + +void TII::set_parameter(const std::string& parameter, const std::string& value) +{ +    using namespace std; +    stringstream ss(value); +    ss.exceptions ( stringstream::failbit | stringstream::badbit ); + +    if (parameter == "enable") { +        ss >> m_enable; +    } +    else if (parameter == "pattern") { +        int new_pattern; +        ss >> new_pattern; +        if (    (m_dabmode == 1 or m_dabmode == 2) and +                not(0 <= new_pattern and new_pattern <= 69) ) { +            throw std::runtime_error( +                    "TII pattern not valid!"); +        } +        m_pattern = new_pattern; +        prepare_pattern(); +    } +    else if (parameter == "comb") { +        int new_comb; +        ss >> new_comb; +        if (not(0 <= new_comb and new_comb <= 23) ) { +            throw std::runtime_error( +                    "TII comb not valid!"); +        } +        m_comb = new_comb; +        prepare_pattern(); +    } +    else { +        stringstream ss; +        ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); +        throw ParameterError(ss.str()); +    } +} + +const std::string TII::get_parameter(const std::string& parameter) const +{ +    using namespace std; +    stringstream ss; +    if (parameter == "enable") { +        ss << (m_enable ? 1 : 0); +    } +    else if (parameter == "pattern") { +        ss << m_pattern; +    } +    else if (parameter == "comb") { +        ss << m_comb; +    } +    else { +        ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); +        throw ParameterError(ss.str()); +    } +    return ss.str(); +} + + +#ifdef TII_TEST +int main(int argc, char** argv) +{ +    const unsigned int mode = 2; +    const unsigned int comb = 4; +    const unsigned int pattern = 16; +    TII tii(mode, comb, pattern); + +    return 0; +} +#endif + diff --git a/src/TII.h b/src/TII.h new file mode 100644 index 0000000..b241bed --- /dev/null +++ b/src/TII.h @@ -0,0 +1,105 @@ +/* +   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 + +   TII generation according to ETSI EN 300 401 Clause 14.8 + */ +/* +   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 TII_H +#define TII_H + +#ifdef HAVE_CONFIG_H +#   include <config.h> +#endif + +#include "ModCodec.h" +#include "RemoteControl.h" + +#include <boost/thread.hpp> +#include <sys/types.h> +#include <complex> +#include <vector> +#include <string> + +struct tii_config_t +{ +    tii_config_t() : enable(false), comb(0), pattern(0) {} + +    bool enable; +    int comb; +    int pattern; +}; + +class TII : public ModCodec, public RemoteControllable +{ +    public: +        TII(unsigned int dabmode, const tii_config_t& tii_config); +        virtual ~TII(); + +        int process(Buffer* const dataIn, Buffer* dataOut); +        const char* name(); + +        /******* REMOTE CONTROL ********/ +        virtual void set_parameter(const std::string& parameter, +                const std::string& value); + +        virtual const std::string get_parameter( +                const std::string& parameter) const; + + +    protected: +        // Fill m_dataIn with the correct carriers for the pattern/comb +        // combination +        void prepare_pattern(void); + +        // prerequisites: calling thread must hold m_dataIn mutex +        void enable_carrier(int k); + +        // Configuration settings +        unsigned int m_dabmode; + +        // Remote-controllable settings +        bool         m_enable; +        unsigned int m_comb; +        unsigned int m_pattern; + +        // Internal flag when to insert TII +        bool m_insert; + +        size_t m_carriers; + +        std::string m_name; + +        // m_dataIn is read by modulator thread, and written +        // to by RC thread. +        mutable boost::mutex m_dataIn_mutex; +        std::vector<std::complex<float> > m_dataIn; + +    private: +        TII(const TII&); +        TII& operator=(const TII&); +}; + +#endif // TII_H + 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 6063048..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,8 +64,8 @@ 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; @@ -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 !";      } @@ -198,77 +198,41 @@ void TimestampDecoder::updateTimestampEti(      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 8c6b362..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,29 +28,14 @@  #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  { @@ -109,14 +94,24 @@ struct frame_timestamp  };  /* 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; @@ -126,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";          }; @@ -143,14 +138,23 @@ class TimestampDecoder                  double pps,                  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); @@ -171,12 +175,10 @@ class TimestampDecoder          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           */ @@ -190,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 index 8b97602..6c9b0fc 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -49,7 +49,6 @@ void printUsage(char* progName)              " (-f filename | -u uhddevice -F frequency) "              " [-G txgain]"              " [-o offset]" -            " [-O offsetfile]"              " [-T filter_taps_file]"              " [-a gain]"              " [-c clockrate]" @@ -66,9 +65,7 @@ void printUsage(char* progName)      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" +    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"); diff --git a/src/Utils.h b/src/Utils.h index 7c3129c..f023646 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -35,10 +35,28 @@  #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 | 
