diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/BlockPartitioner.cpp | 12 | ||||
| -rw-r--r-- | src/BlockPartitioner.h | 4 | ||||
| -rw-r--r-- | src/CicEqualizer.cpp | 19 | ||||
| -rw-r--r-- | src/DabMod.cpp | 156 | ||||
| -rw-r--r-- | src/DabModulator.cpp | 62 | ||||
| -rw-r--r-- | src/DabModulator.h | 14 | ||||
| -rw-r--r-- | src/Eti.h | 40 | ||||
| -rw-r--r-- | src/EtiReader.cpp | 48 | ||||
| -rw-r--r-- | src/EtiReader.h | 26 | ||||
| -rw-r--r-- | src/FIRFilter.cpp | 311 | ||||
| -rw-r--r-- | src/FIRFilter.h | 157 | ||||
| -rw-r--r-- | src/Makefile.am | 3 | ||||
| -rw-r--r-- | src/Makefile.in | 54 | ||||
| -rw-r--r-- | src/OutputUHD.cpp | 466 | ||||
| -rw-r--r-- | src/OutputUHD.h | 167 | ||||
| -rw-r--r-- | src/PcDebug.h | 4 | ||||
| -rw-r--r-- | src/TimestampDecoder.cpp | 210 | ||||
| -rw-r--r-- | src/TimestampDecoder.h | 154 | 
18 files changed, 1839 insertions, 68 deletions
| diff --git a/src/BlockPartitioner.cpp b/src/BlockPartitioner.cpp index bb16ae2..2ae2e7a 100644 --- a/src/BlockPartitioner.cpp +++ b/src/BlockPartitioner.cpp @@ -29,7 +29,7 @@  #include <assert.h> -BlockPartitioner::BlockPartitioner(unsigned mode, unsigned fct) : +BlockPartitioner::BlockPartitioner(unsigned mode, unsigned phase) :      ModMux(ModFormat(0), ModFormat(0)),      d_mode(mode)  { @@ -67,7 +67,7 @@ BlockPartitioner::BlockPartitioner(unsigned mode, unsigned fct) :      }      d_cifNb = 0;      // For Synchronisation purpose, count nb of CIF to drop -    d_cifInit = fct % d_cifCount; +    d_cifPhase = phase % d_cifCount;      d_cifSize = 864 * 8;      myInputFormat.size(d_cifSize); @@ -110,10 +110,10 @@ int BlockPartitioner::process(std::vector<Buffer*> dataIn, Buffer* dataOut)                  "BlockPartitioner::process input size not valid!");      } -    // Synchronize first CIF -    if (d_cifInit != 0) { -        if (++d_cifInit == d_cifCount) { -            d_cifInit = 0; +    // Synchronize CIF phase +    if (d_cifPhase != 0) { +        if (++d_cifPhase == d_cifCount) { +            d_cifPhase = 0;          }          // Drop CIF          return 0; diff --git a/src/BlockPartitioner.h b/src/BlockPartitioner.h index ec788bf..021a1a6 100644 --- a/src/BlockPartitioner.h +++ b/src/BlockPartitioner.h @@ -35,7 +35,7 @@  class BlockPartitioner : public ModMux  {  public: -    BlockPartitioner(unsigned mode, unsigned fct); +    BlockPartitioner(unsigned mode, unsigned phase);      virtual ~BlockPartitioner();      BlockPartitioner(const BlockPartitioner&);      BlockPartitioner& operator=(const BlockPartitioner&); @@ -48,7 +48,7 @@ protected:      size_t d_ficSize;      size_t d_cifCount;      size_t d_cifNb; -    size_t d_cifInit; +    size_t d_cifPhase;      size_t d_cifSize;      size_t d_outputFramesize;      size_t d_outputFramecount; diff --git a/src/CicEqualizer.cpp b/src/CicEqualizer.cpp index 06f4550..52da55e 100644 --- a/src/CicEqualizer.cpp +++ b/src/CicEqualizer.cpp @@ -36,22 +36,21 @@ CicEqualizer::CicEqualizer(size_t nbCarriers, size_t spacing, int R) :              nbCarriers, spacing, R, this);      myFilter = new float[nbCarriers]; -    int M = 1; -    int N = 4; -    float pi = 4.0f * atanf(1.0f); +    const int M = 1; +    const int N = 4; +    const float pi = 4.0f * atanf(1.0f);      for (size_t i = 0; i < nbCarriers; ++i) {          int k = i < (nbCarriers + 1) / 2              ? i + ((nbCarriers & 1) ^ 1)              : i - (int)nbCarriers;          float angle = pi * k / spacing;          if (k == 0) { -            myFilter[i] = R; -        } else { -            myFilter[i] = sinf(angle / R) / sinf(angle * M); -        } -        myFilter[i] = fabsf(myFilter[i]) * R; -        myFilter[i] = powf(myFilter[i], N); -        myFilter[i] *= 0.5f; +            myFilter[i] = 1.0f; +	} else { +		myFilter[i] = sinf(angle / R) / sinf(angle * M); +		myFilter[i] = fabsf(myFilter[i]) * R * M; +		myFilter[i] = powf(myFilter[i], N); +	}          PDEBUG("HCic[%zu -> %i] = %f (%f dB) -> angle: %f\n",                  i, k,myFilter[i], 20.0 * log10(myFilter[i]), angle);      } diff --git a/src/DabMod.cpp b/src/DabMod.cpp index db0c712..f9bc3c9 100644 --- a/src/DabMod.cpp +++ b/src/DabMod.cpp @@ -2,6 +2,9 @@     Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012     Her Majesty the Queen in Right of Canada (Communications Research     Center Canada) + +   Includes modifications for which no copyright is claimed +   2012, Matthias P. Braendli, matthias.braendli@mpb.li   */  /*     This file is part of CRC-DADMOD. @@ -29,7 +32,9 @@  #include "DabModulator.h"  #include "InputMemory.h"  #include "OutputFile.h" +#include "OutputUHD.h"  #include "PcDebug.h" +#include "TimestampDecoder.h"  #include <complex>  #include <stdlib.h> @@ -69,10 +74,14 @@ void printUsage(char* progName, FILE* out = stderr)  {      fprintf(out, "Usage:\n");      fprintf(out, "\t%s" -            " [input [output]]" +            " [input]" +            " (-f filename | -u uhddevice -F frequency) " +            " [-G txgain]" +            " [-o offset]" +            " [-O offsetfile]" +            " [-T filter_taps_file]"              " [-a amplitude]"              " [-c clockrate]" -            " [-f]"              " [-g gainMode]"              " [-h]"              " [-l]" @@ -80,17 +89,23 @@ void printUsage(char* progName, FILE* out = stderr)              " [-r samplingRate]"              "\n", progName);      fprintf(out, "Where:\n"); -    fprintf(out, "input:    ETI input filename (default: stdin).\n"); -    fprintf(out, "output:   COFDM output filename (default: stdout).\n"); -    fprintf(out, "-a:       Apply amplitude gain.\n"); -    fprintf(out, "-c:       Set the DAC clock rate.\n"); -    fprintf(out, "-f:       (deprecated) Set fifo input.\n"); -    fprintf(out, "-g:       Set computation gain mode: " +    fprintf(out, "input:         ETI input filename (default: stdin).\n"); +    fprintf(out, "-f name:       Use file output with given filename. (use /dev/stdout for standard output)\n"); +    fprintf(out, "-u device:     Use UHD output with given device string. (use "" for default device)\n"); +    fprintf(out, "-F frequency:  Set the transmit frequency when using UHD output. (mandatory option when using UHD)\n"); +    fprintf(out, "-G txgain:     Set the transmit gain for the UHD driver (default: 0)\n"); +    fprintf(out, "-o:            (UHD only) Set the timestamp offset added to the timestamp in the ETI. The offset is a double.\n"); +    fprintf(out, "-O:            (UHD only) Set the file containing the timestamp offset added to the timestamp in the ETI. The file is read every six seconds, and must contain a double value.\n"); +    fprintf(out, "                  Specifying either -o or -O make DABMOD mute frames that do not contain a valid timestamp.\n"); +    fprintf(out, "-T taps_file:  Enable filtering before the output, using the specified file containing the filter taps.\n"); +    fprintf(out, "-a gain:       Apply digital amplitude gain.\n"); +    fprintf(out, "-c rate:       Set the DAC clock rate and enable Cic Equalisation.\n"); +    fprintf(out, "-g:            Set computation gain mode: "              "%u FIX, %u MAX, %u VAR\n", GAIN_FIX, GAIN_MAX, GAIN_VAR); -    fprintf(out, "-h:       Print this help.\n"); -    fprintf(out, "-l:       Loop file when reach end of file.\n"); -    fprintf(out, "-m:       Set DAB mode: (0: auto, 1-4: force).\n"); -    fprintf(out, "-r:       Set output sampling rate (default: 2048000).\n"); +    fprintf(out, "-h:            Print this help.\n"); +    fprintf(out, "-l:            Loop file when reach end of file.\n"); +    fprintf(out, "-m mode:       Set DAB mode: (0: auto, 1-4: force).\n"); +    fprintf(out, "-r rate:       Set output sampling rate (default: 2048000).\n");  } @@ -124,7 +139,15 @@ int main(int argc, char* argv[])      int ret = 0;      bool loop = false;      char* inputName; +      char* outputName; +    char* outputDevice; +    int useFileOutput = 0; +    int useUHDOutput = 0; +    double uhdFrequency = 0.0; +    int uhdTxGain = 0; +    bool uhd_mute_no_timestamps = false; +      FILE* inputFile = NULL;      uint32_t sync = 0;      uint32_t nbFrames = 0; @@ -137,15 +160,26 @@ int main(int argc, char* argv[])      GainMode gainMode = GAIN_VAR;      Buffer data; +    char* filterTapsFilename = NULL; + +    // To handle the timestamp offset of the modulator +    struct modulator_offset_config modconf; +    modconf.use_offset_file = false; +    modconf.use_offset_fixed = false; +      Flowgraph* flowgraph = NULL;      DabModulator* modulator = NULL;      InputMemory* input = NULL; -    OutputFile* output = NULL; +    ModOutput* output = NULL;      signal(SIGINT, signalHandler); +    // Set timezone to UTC +    setenv("TZ", "", 1); +    tzset(); +      while (true) { -        int c = getopt(argc, argv, "a:c:fg:hlm:r:V"); +        int c = getopt(argc, argv, "a:c:f:F:g:G:hlm:o:O:r:T:u:V");          if (c == -1) {              break;          } @@ -157,20 +191,60 @@ int main(int argc, char* argv[])              clockRate = strtol(optarg, NULL, 0);              break;          case 'f': -            fprintf(stderr, "Option -f deprecated!\n"); +            if (useUHDOutput) { +                fprintf(stderr, "Options -u and -f are mutually exclusive\n"); +                goto END_MAIN; +            } +            outputName = optarg; +            useFileOutput = 1; +            break; +        case 'F': +            uhdFrequency = strtof(optarg, NULL);              break;          case 'g':              gainMode = (GainMode)strtol(optarg, NULL, 0);              break; +        case 'G': +            uhdTxGain = (int)strtol(optarg, NULL, 10); +            break;          case 'l':              loop = true;              break; +        case 'o': +            if (modconf.use_offset_file) +            { +                fprintf(stderr, "Options -o and -O are mutually exclusive\n"); +                goto END_MAIN; +            } +            modconf.use_offset_fixed = true; +            modconf.offset_fixed = strtod(optarg, NULL); +            break; +        case 'O': +            if (modconf.use_offset_fixed) +            { +                fprintf(stderr, "Options -o and -O are mutually exclusive\n"); +                goto END_MAIN; +            } +            modconf.use_offset_file = true; +            modconf.offset_filename = optarg; +            break;          case 'm':              dabMode = strtol(optarg, NULL, 0);              break;          case 'r':              outputRate = strtol(optarg, NULL, 0);              break; +        case 'T': +            filterTapsFilename = optarg; +            break; +        case 'u': +            if (useFileOutput) { +                fprintf(stderr, "Options -u and -f are mutually exclusive\n"); +                goto END_MAIN; +            } +            outputDevice = optarg; +            useUHDOutput = 1; +            break;          case 'V':              printVersion();              goto END_MAIN; @@ -186,18 +260,22 @@ int main(int argc, char* argv[])              goto END_MAIN;          }      } + +    // When using offset, enable frame muting +    uhd_mute_no_timestamps = (modconf.use_offset_file || modconf.use_offset_fixed); + +    if (!(modconf.use_offset_file || modconf.use_offset_fixed)) { +        fprintf(stderr, "No Modulator offset defined, setting to 0\n"); +        modconf.use_offset_fixed = true; +        modconf.offset_fixed = 0; +    } +      // Setting ETI input filename      if (optind < argc) {          inputName = argv[optind++];      } else {          inputName = (char*)"/dev/stdin";      } -    // Setting COFDM output filename -    if (optind < argc) { -        outputName = argv[optind++]; -    } else { -        outputName = (char*)"/dev/stdout"; -    }      // Checking unused arguments      if (optind != argc) {          fprintf(stderr, "Invalid arguments:"); @@ -210,15 +288,25 @@ int main(int argc, char* argv[])          goto END_MAIN;      } +    if (!useFileOutput && !useUHDOutput) { +        fprintf(stderr, "Must specify output !"); +        goto END_MAIN; +    } +      // Print settings      fprintf(stderr, "Input\n");      fprintf(stderr, "  Name: %s\n", inputName);      fprintf(stderr, "Output\n"); -    fprintf(stderr, "  Name: %s\n", outputName); +    if (useUHDOutput) { +        fprintf(stderr, " UHD, Device: %s\n", outputDevice); +    } +    else if (useFileOutput) { +        fprintf(stderr, "  Name: %s\n", outputName); +    }      fprintf(stderr, "  Sampling rate: ");      if (outputRate > 1000) {          if (outputRate > 1000000) { -            fprintf(stderr, "%.3g mHz\n", outputRate / 1000000.0f); +            fprintf(stderr, "%.3g MHz\n", outputRate / 1000000.0f);          } else {              fprintf(stderr, "%.3g kHz\n", outputRate / 1000.0f);          } @@ -234,18 +322,32 @@ int main(int argc, char* argv[])          ret = -1;          goto END_MAIN;      } -    // Opening COFDM output file -    if (outputName != NULL) { -        output = new OutputFile(outputName); + +    if (useFileOutput) { +        // Opening COFDM output file +        if (outputName != NULL) { +            fprintf(stderr, "Using file output\n"); +            output = new OutputFile(outputName); +        } +    } +    else if (useUHDOutput) { +        fprintf(stderr, "Using UHD output\n"); +        amplitude /= 32000.0f; +        output = new OutputUHD(outputDevice, outputRate, uhdFrequency, uhdTxGain, uhd_mute_no_timestamps);      }      flowgraph = new Flowgraph();      data.setLength(6144);      input = new InputMemory(&data); -    modulator = new DabModulator(outputRate, clockRate, dabMode, gainMode, amplitude); +    modulator = new DabModulator(modconf, outputRate, clockRate, +            dabMode, gainMode, amplitude, filterTapsFilename);      flowgraph->connect(input, modulator);      flowgraph->connect(modulator, output); +    if (useUHDOutput) { +        ((OutputUHD*)output)->setETIReader(modulator->getEtiReader()); +    } +      try {          while (running) {              enum EtiStreamType { diff --git a/src/DabModulator.cpp b/src/DabModulator.cpp index 6396a4d..e186fd3 100644 --- a/src/DabModulator.cpp +++ b/src/DabModulator.cpp @@ -2,6 +2,9 @@     Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012     Her Majesty the Queen in Right of Canada (Communications Research     Center Canada) + +   Includes modifications for which no copyright is claimed +   2012, Matthias P. Braendli, matthias.braendli@mpb.li   */  /*     This file is part of CRC-DADMOD. @@ -38,19 +41,27 @@  #include "GuardIntervalInserter.h"  #include "Resampler.h"  #include "ConvEncoder.h" +#include "FIRFilter.h"  #include "PuncturingEncoder.h"  #include "TimeInterleaver.h" +#include "TimestampDecoder.h" -DabModulator::DabModulator(unsigned outputRate, unsigned clockRate, -        unsigned dabMode, GainMode gainMode, float factor) : +DabModulator::DabModulator( +        struct modulator_offset_config& modconf, +        unsigned outputRate, unsigned clockRate, +        unsigned dabMode, GainMode gainMode, float factor, +        char* filterTapsFilename +        ) :      ModCodec(ModFormat(1), ModFormat(0)),      myOutputRate(outputRate),      myClockRate(clockRate),      myDabMode(dabMode),      myGainMode(gainMode),      myFactor(factor), -    myFlowgraph(NULL) +    myEtiReader(EtiReader(modconf)), +    myFlowgraph(NULL), +    myFilterTapsFilename(filterTapsFilename)  {      PDEBUG("DabModulator::DabModulator(%u, %u, %u, %u) @ %p\n",              outputRate, clockRate, dabMode, gainMode, this); @@ -147,12 +158,13 @@ int DabModulator::process(Buffer* const dataIn, Buffer* dataOut)          OfdmGenerator* cifOfdm = NULL;          GainControl* cifGain = NULL;          GuardIntervalInserter* cifGuard = NULL; +        FIRFilter* cifFilter = NULL;          Resampler* cifRes = NULL;          cifPrbs = new PrbsGenerator(864 * 8, 0x110);          cifMux = new FrameMultiplexer(myFicSizeOut + 864 * 8,                  &myEtiReader.getSubchannels()); -        cifPart = new BlockPartitioner(mode, myEtiReader.getFct()); +        cifPart = new BlockPartitioner(mode, myEtiReader.getFp());          cifMap = new QpskSymbolMapper(myNbCarriers);          cifRef = new PhaseReference(mode);          cifFreq = new FrequencyInterleaver(mode); @@ -162,15 +174,28 @@ int DabModulator::process(Buffer* const dataIn, Buffer* dataOut)                  (1 + myNbSymbols) * myNbCarriers * sizeof(complexf));          if (myClockRate) { -            cifCicEq = new CicEqualizer(myNbCarriers, -                    (float)mySpacing * (float)myOutputRate / 2048000.0f, -                    myClockRate / myOutputRate); +            unsigned ratio = myClockRate / myOutputRate; +            ratio /= 4; // FPGA DUC +            if (myClockRate == 400000000) { // USRP2 +                if (ratio & 1) { // odd +                    cifCicEq = new CicEqualizer(myNbCarriers, +                            (float)mySpacing * (float)myOutputRate / 2048000.0f, +                            ratio); +                } // even, no filter +            } else { +                cifCicEq = new CicEqualizer(myNbCarriers, +                        (float)mySpacing * (float)myOutputRate / 2048000.0f, +                        ratio); +            }          }          cifOfdm = new OfdmGenerator((1 + myNbSymbols), myNbCarriers, mySpacing);          cifGain = new GainControl(mySpacing, myGainMode, myFactor);          cifGuard = new GuardIntervalInserter(myNbSymbols, mySpacing,                  myNullSize, mySymSize); +        if (myFilterTapsFilename != NULL) { +            cifFilter = new FIRFilter(myFilterTapsFilename); +        }          myOutput = new OutputMemory();          if (myOutputRate != 2048000) { @@ -312,11 +337,24 @@ int DabModulator::process(Buffer* const dataIn, Buffer* dataOut)          }          myFlowgraph->connect(cifOfdm, cifGain);          myFlowgraph->connect(cifGain, cifGuard); -        if (cifRes != NULL) { -            myFlowgraph->connect(cifGuard, cifRes); -            myFlowgraph->connect(cifRes, myOutput); -        } else { -            myFlowgraph->connect(cifGuard, myOutput); + +        if (myFilterTapsFilename != NULL) { +            myFlowgraph->connect(cifGuard, cifFilter); +            if (cifRes != NULL) { +                myFlowgraph->connect(cifFilter, cifRes); +                myFlowgraph->connect(cifRes, myOutput); +            } else { +                myFlowgraph->connect(cifFilter, myOutput); +            } +        } +        else { //no filtering +            if (cifRes != NULL) { +                myFlowgraph->connect(cifGuard, cifRes); +                myFlowgraph->connect(cifRes, myOutput); +            } else { +                myFlowgraph->connect(cifGuard, myOutput); +            } +          }      } diff --git a/src/DabModulator.h b/src/DabModulator.h index 9112960..0eecfa4 100644 --- a/src/DabModulator.h +++ b/src/DabModulator.h @@ -2,6 +2,9 @@     Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012     Her Majesty the Queen in Right of Canada (Communications Research     Center Canada) + +   Includes modifications for which no copyright is claimed +   2012, Matthias P. Braendli, matthias.braendli@mpb.li   */  /*     This file is part of CRC-DADMOD. @@ -39,14 +42,20 @@  class DabModulator : public ModCodec  {  public: -    DabModulator(unsigned outputRate = 2048000, unsigned clockRate = 0, -            unsigned dabMode = 0, GainMode gainMode = GAIN_VAR, float factor = 1.0); +    DabModulator( +            struct modulator_offset_config& modconf, +            unsigned outputRate = 2048000, unsigned clockRate = 0, +            unsigned dabMode = 0, GainMode gainMode = GAIN_VAR, +            float factor = 1.0, char* filterTapsFilename = NULL);      DabModulator(const DabModulator& copy);      virtual ~DabModulator();      int process(Buffer* const dataIn, Buffer* dataOut);      const char* name() { return "DabModulator"; } +    /* Required to get the timestamp */ +    EtiReader* getEtiReader() { return &myEtiReader; } +  protected:      void setMode(unsigned mode); @@ -58,6 +67,7 @@ protected:      EtiReader myEtiReader;      Flowgraph* myFlowgraph;      OutputMemory* myOutput; +    char* myFilterTapsFilename;      size_t myNbSymbols;      size_t myNbCarriers; @@ -1,6 +1,9 @@  /*     Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the     Queen in Right of Canada (Communications Research Center Canada) + +   Includes modifications for which no copyright is claimed +   2012, Matthias P. Braendli, matthias.braendli@mpb.li   */  /*     This file is part of CRC-DADMOD. @@ -39,6 +42,7 @@ typedef DWORD32 uint32_t;  #   define PACKED __attribute__ ((packed))  #endif +#include <time.h>  //definitions des structures des champs du ETI(NI, G703) @@ -92,5 +96,41 @@ struct eti_TIST {      uint32_t TIST;  } PACKED; +struct eti_MNSC_TIME_0 { +    uint32_t type:4; +    uint32_t identifier:4; +    uint32_t rfa:8; +} PACKED; + +struct eti_MNSC_TIME_1 { +    uint32_t second_unit:4; +    uint32_t second_tens:3; +    uint32_t accuracy:1; +     +    uint32_t minute_unit:4; +    uint32_t minute_tens:3; +    uint32_t sync_to_frame:1; +} PACKED; + +struct eti_MNSC_TIME_2 { +    uint32_t hour_unit:4; +    uint32_t hour_tens:4; +     +    uint32_t day_unit:4; +    uint32_t day_tens:4; +} PACKED; + +struct eti_MNSC_TIME_3 { +    uint32_t month_unit:4; +    uint32_t month_tens:4; +     +    uint32_t year_unit:4; +    uint32_t year_tens:4; +} PACKED; + +struct eti_extension_TIME { +    uint32_t TIME_SECONDS; +} PACKED; +  #endif // ETI_H diff --git a/src/EtiReader.cpp b/src/EtiReader.cpp index d65dbe0..7e7d4af 100644 --- a/src/EtiReader.cpp +++ b/src/EtiReader.cpp @@ -1,6 +1,9 @@  /*     Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty     the Queen in Right of Canada (Communications Research Center Canada) + +   Includes modifications for which no copyright is claimed +   2012, Matthias P. Braendli, matthias.braendli@mpb.li   */  /*     This file is part of CRC-DADMOD. @@ -21,10 +24,12 @@  #include "EtiReader.h"  #include "PcDebug.h" +#include "TimestampDecoder.h"  #include <stdexcept>  #include <sys/types.h>  #include <string.h> +#include <arpa/inet.h>  enum ETI_READER_STATE { @@ -42,15 +47,15 @@ enum ETI_READER_STATE {  }; -EtiReader::EtiReader() : state(EtiReaderStateSync), -    myFicSource(NULL) +EtiReader::EtiReader(struct modulator_offset_config& modconf) : +    state(EtiReaderStateSync), myFicSource(NULL)  {      PDEBUG("EtiReader::EtiReader()\n");      myCurrentFrame = 0; +    myTimestampDecoder = new TimestampDecoder(modconf);  } -  EtiReader::~EtiReader()  {      PDEBUG("EtiReader::~EtiReader()\n"); @@ -76,9 +81,9 @@ unsigned EtiReader::getMode()  } -unsigned EtiReader::getFct() +unsigned EtiReader::getFp()  { -    return eti_fc.FCT; +    return eti_fc.FP;  } @@ -266,5 +271,38 @@ int EtiReader::process(Buffer* dataIn)          }      } +    // Update timestamps +    myTimestampDecoder->updateTimestampEti(eti_fc.FP & 0x3, +            eti_eoh.MNSC,  +            getPPSOffset()); + +    if (getFCT() % 125 == 0) //every 3 seconds is fine enough +    { +        myTimestampDecoder->updateModulatorOffset(); +    } +      return dataIn->getLength() - input_size;  } + +bool EtiReader::sourceContainsTimestamp() +{ +    return (ntohl(eti_tist.TIST) & 0xFFFFFF) != 0xFFFFFF; +    /* See ETS 300 799, Annex C.2.2 */ +} + +double EtiReader::getPPSOffset() +{ +    if (!sourceContainsTimestamp()) +        return 0.0; + +    uint32_t timestamp = ntohl(eti_tist.TIST) & 0xFFFFFF; +    //fprintf(stderr, "TIST 0x%x\n", timestamp); +    double pps = timestamp / 16384000.0; // seconds + +    return pps; +} + +uint32_t EtiReader::getFCT() +{ +    return eti_fc.FCT; +} diff --git a/src/EtiReader.h b/src/EtiReader.h index 7e172db..5f4897b 100644 --- a/src/EtiReader.h +++ b/src/EtiReader.h @@ -1,6 +1,9 @@  /*     Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty     the Queen in Right of Canada (Communications Research Center Canada) + +   Includes modifications for which no copyright is claimed +   2012, Matthias P. Braendli, matthias.braendli@mpb.li   */  /*     This file is part of CRC-DADMOD. @@ -30,6 +33,7 @@  #include "Eti.h"  #include "FicSource.h"  #include "SubchannelSource.h" +#include "TimestampDecoder.h"  #include <vector>  #include <stdint.h> @@ -51,21 +55,39 @@ protected:      eti_TIST eti_tist;      FicSource* myFicSource;      std::vector<SubchannelSource*> mySources; +    TimestampDecoder* myTimestampDecoder;  public: -    EtiReader(); +    EtiReader(struct modulator_offset_config& modconf);      virtual ~EtiReader();      EtiReader(const EtiReader&);      EtiReader& operator=(const EtiReader&);      FicSource* getFic();      unsigned getMode(); -    unsigned getFct(); +    unsigned getFp();      const std::vector<SubchannelSource*>& getSubchannels();      int process(Buffer* dataIn); +    void calculateTimestamp(struct frame_timestamp& ts) +    { +        myTimestampDecoder->calculateTimestamp(ts); +    } + +    /* Return the frame counter */ +    uint32_t getFCT(); + +    /* Returns true if we have valid time stamps in the ETI*/ +    bool sourceContainsTimestamp(); + +protected: +    /* Transform the ETI TIST to a PPS offset in ms */ +    double getPPSOffset(); +  private:      size_t myCurrentFrame; +    bool time_ext_enabled; +    unsigned long timestamp_seconds;  }; diff --git a/src/FIRFilter.cpp b/src/FIRFilter.cpp new file mode 100644 index 0000000..2ba4294 --- /dev/null +++ b/src/FIRFilter.cpp @@ -0,0 +1,311 @@ +/* +   Copyright (C) 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in +   Right of Canada (Communications Research Center Canada) + +   Written by +   2012, Matthias P. Braendli, matthias.braendli@mpb.li + +   This block implements a FIR filter. The real filter taps are given +   as floats, and the block can take advantage of SSE. +   For better performance, filtering is done in another thread, leading +   to a pipeline delay of two calls to FIRFilter::process + */ +/* +   This file is part of CRC-DADMOD. + +   CRC-DADMOD 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. + +   CRC-DADMOD 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 CRC-DADMOD.  If not, see <http://www.gnu.org/licenses/>. + */ + +#include "FIRFilter.h" +#include "PcDebug.h" + +#include <stdio.h> +#include <stdexcept> + +#include <iostream> +#include <fstream> + +#ifdef __SSE__ +#   include <xmmintrin.h> +#endif + + +#include <sys/time.h> + +void FIRFilterWorker::process(struct FIRFilterWorkerData *fwd) +{ +    size_t i; +    struct timespec time_start; +    struct timespec time_end; + +    // This thread creates the dataOut buffer, and deletes +    // the incoming buffer + +    while(running) { +        Buffer* dataIn; +        fwd->input_queue.wait_and_pop(dataIn); + +        Buffer* dataOut; +        dataOut = new Buffer(); +        dataOut->setLength(dataIn->getLength()); + +        PDEBUG("FIRFilterWorker: dataIn->getLength() %d\n", dataIn->getLength()); + +#if __SSE__ +        // The SSE accelerated version cannot work on the complex values, +        // it is necessary to do the convolution on the real and imaginary +        // parts separately. Thankfully, the taps are real, simplifying the +        // procedure. + +        const float* in = reinterpret_cast<const float*>(dataIn->getData()); +        float* out      = reinterpret_cast<float*>(dataOut->getData()); +        size_t sizeIn   = dataIn->getLength() / sizeof(float); + +        if ((uintptr_t)(&out[0]) % 16 != 0) { +            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; +        __m128 SSEtaps; +        __m128 SSEin; +        for (i = 0; i < sizeIn - 2*fwd->n_taps; i += 4) { +            SSEout = _mm_setr_ps(0,0,0,0); + +            for (int j = 0; j < fwd->n_taps; j++) { +                if ((uintptr_t)(&in[i+2*j]) % 16 == 0) { +                    SSEin = _mm_load_ps(&in[i+2*j]); //faster when aligned +                } +                else { +                    SSEin = _mm_loadu_ps(&in[i+2*j]); +                } + +                SSEtaps = _mm_load1_ps(&fwd->taps[j]); + +                SSEout = _mm_add_ps(SSEout, _mm_mul_ps(SSEin, SSEtaps)); +            } +            _mm_store_ps(&out[i], SSEout); +        } + +        for (; i < sizeIn; i++) { +            out[i] = 0.0; +            for (int j = 0; i+2*j < sizeIn; j++) { +                out[i] += in[i+2*j] * fwd->taps[j]; +            } +        } +        clock_gettime(CLOCK_THREAD_CPUTIME_ID, &time_end); + +#else +        // No SSE ? Loop unrolling should make this faster. As for the SSE, +        // the real and imaginary parts are calculated separately. +        const float* in = reinterpret_cast<const float*>(dataIn->getData()); +        float* out      = reinterpret_cast<float*>(dataOut->getData()); +        size_t sizeIn   = dataIn->getLength() / sizeof(float); + +        clock_gettime(CLOCK_THREAD_CPUTIME_ID, &time_start); + +        // Convolve by aligning both frame and taps at zero. +        for (i = 0; i < sizeIn - 2*fwd->n_taps; i += 4) { +            out[i]    = 0.0; +            out[i+1]  = 0.0; +            out[i+2]  = 0.0; +            out[i+3]  = 0.0; + +            for (int j = 0; j < fwd->n_taps; j++) { +                out[i]   += in[i   + 2*j] * fwd->taps[j]; +                out[i+1] += in[i+1 + 2*j] * fwd->taps[j]; +                out[i+2] += in[i+2 + 2*j] * fwd->taps[j]; +                out[i+3] += in[i+3 + 2*j] * fwd->taps[j]; +            } +        } + +        // At the end of the frame, we cut the convolution off. +        // The beginning of the next frame starts with a NULL symbol +        // anyway. +        for (; i < sizeIn; i++) { +            out[i] = 0.0; +            for (int j = 0; i+2*j < sizeIn; j++) { +                out[i] += in[i+2*j] * fwd->taps[j]; +            } +        } + +        clock_gettime(CLOCK_THREAD_CPUTIME_ID, &time_end); + + +#endif + +        // The following implementations are for debugging only. +#if 0 +        // Same thing as above, without loop unrolling. For debugging. +        const float* in = reinterpret_cast<const float*>(dataIn->getData()); +        float* out      = reinterpret_cast<float*>(dataOut->getData()); +        size_t sizeIn   = dataIn->getLength() / sizeof(float); + +        for (i = 0; i < sizeIn - 2*fwd->n_taps; i += 1) { +            out[i]  = 0.0; + +            for (int j = 0; j < fwd->n_taps; j++) { +                out[i]  += in[i+2*j] * fwd->taps[j]; +            } +        } + +        for (; i < sizeIn; i++) { +            out[i] = 0.0; +            for (int j = 0; i+2*j < sizeIn; j++) { +                out[i] += in[i+2*j] * fwd->taps[j]; +            } +        } + +#elif 0 +        // An unrolled loop, but this time, the input data is cast to complex float. +        // Makes indices more natural. For debugging. +        const complexf* in = reinterpret_cast<const complexf*>(dataIn->getData()); +        complexf* out      = reinterpret_cast<complexf*>(dataOut->getData()); +        size_t sizeIn      = dataIn->getLength() / sizeof(complexf); + +        for (i = 0; i < sizeIn - fwd->n_taps; i += 4) { +            out[i]   = 0.0; +            out[i+1] = 0.0; +            out[i+2] = 0.0; +            out[i+3] = 0.0; + +            for (int j = 0; j < fwd->n_taps; j++) { +                out[i]   += in[i+j  ] * fwd->taps[j]; +                out[i+1] += in[i+1+j] * fwd->taps[j]; +                out[i+2] += in[i+2+j] * fwd->taps[j]; +                out[i+3] += in[i+3+j] * fwd->taps[j]; +            } +        } + +        for (; i < sizeIn; i++) { +            out[i] = 0.0; +            for (int j = 0; j+i < sizeIn; j++) { +                out[i] += in[i+j] * fwd->taps[j]; +            } +        } + +#elif 0 +        // Simple implementation. Slow. For debugging. +        const complexf* in = reinterpret_cast<const complexf*>(dataIn->getData()); +        complexf* out      = reinterpret_cast<complexf*>(dataOut->getData()); +        size_t sizeIn      = dataIn->getLength() / sizeof(complexf); + +        for (i = 0; i < sizeIn - fwd->n_taps; i += 1) { +            out[i]   = 0.0; + +            for (int j = 0; j < fwd->n_taps; j++) { +                out[i]  += in[i+j  ] * fwd->taps[j]; +            } +        } + +        for (; i < sizeIn; i++) { +            out[i] = 0.0; +            for (int j = 0; j+i < sizeIn; j++) { +                out[i] += in[i+j] * fwd->taps[j]; +            } +        } +#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; +    } +} + + +FIRFilter::FIRFilter(char* taps_file) : +    ModCodec(ModFormat(sizeof(complexf)), ModFormat(sizeof(complexf))) +{ +    PDEBUG("FIRFilter::FIRFilter(%s) @ %p\n", +            taps_file, this); + +    number_of_runs = 0; + +    std::ifstream taps_fstream(taps_file); +    if(!taps_fstream) {  +        fprintf(stderr, "FIRFilter: file %s could not be opened !\n", taps_file); +        throw std::runtime_error("FIRFilter: Could not open file with taps! "); +    } +    int n_taps; +    taps_fstream >> n_taps; + +    my_Ntaps = n_taps; + +    fprintf(stderr, "FIRFilter: Reading %d taps...\n", my_Ntaps); + +    myFilter = new float[my_Ntaps]; + +    int n; +    for (n = 0; n < n_taps; n++) { +        taps_fstream >> myFilter[n]; +        PDEBUG("FIRFilter: tap: %f\n",  myFilter[n] ); +        if (taps_fstream.eof()) { +            fprintf(stderr, "FIRFilter: file %s should contains %d taps, but EOF reached "\ +                    "after %d taps !\n", taps_file, n_taps, n); +            throw std::runtime_error("FIRFilter: filtertaps file invalid ! "); +        } +    } + +    firwd.taps = myFilter; +    firwd.n_taps = my_Ntaps; + +    PDEBUG("FIRFilter: Starting worker\n" ); +    worker.start(&firwd); +} + + +FIRFilter::~FIRFilter() +{ +    PDEBUG("FIRFilter::~FIRFilter() @ %p\n", this); + +    worker.stop(); + +    if (myFilter != NULL) { +        delete[] myFilter; +    } +} + + +int FIRFilter::process(Buffer* const dataIn, Buffer* dataOut) +{ +    PDEBUG("FIRFilter::process(dataIn: %p, dataOut: %p)\n", +            dataIn, dataOut); + +    // This thread creates the dataIn buffer, and deletes +    // the outgoing buffer + +    Buffer* inbuffer = new Buffer(dataIn->getLength(), dataIn->getData()); + +    firwd.input_queue.push(inbuffer); + +    if (number_of_runs > 2) { +        Buffer* outbuffer; +        firwd.output_queue.wait_and_pop(outbuffer); + +        dataOut->setData(outbuffer->getData(), outbuffer->getLength()); + +        delete outbuffer; +    } +    else { +        dataOut->setLength(dataIn->getLength()); +        memset(dataOut->getData(), 0, dataOut->getLength()); +        number_of_runs++; +    } + +    return dataOut->getLength(); + +} diff --git a/src/FIRFilter.h b/src/FIRFilter.h new file mode 100644 index 0000000..397301d --- /dev/null +++ b/src/FIRFilter.h @@ -0,0 +1,157 @@ +/* +   Copyright (C) 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in +   Right of Canada (Communications Research Center Canada) + +   Written by +   2012, Matthias P. Braendli, matthias.braendli@mpb.li + */ +/* +   This file is part of CRC-DADMOD. + +   CRC-DADMOD 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. + +   CRC-DADMOD 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 CRC-DADMOD.  If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef FIRFILTER_H +#define FIRFILTER_H + +#ifdef HAVE_CONFIG_H +#   include <config.h> +#endif + +#include <boost/thread.hpp> +#include <queue> + +#include "ModCodec.h" + +#include <sys/types.h> +#include <complex> + +#include <time.h> +#include <cstdio> + +//#define MDEBUG(fmt, args...) fprintf (stderr, fmt , ## args)  +#define MDEBUG(fmt, args...)  + +typedef std::complex<float> complexf; + +template<typename T> +class ThreadsafeQueue +{ +private: +    std::queue<T> the_queue; +    mutable boost::mutex the_mutex; +    boost::condition_variable the_condition_variable; +public: +    void push(T const& val) +    { +        boost::mutex::scoped_lock lock(the_mutex); +        the_queue.push(val); +        lock.unlock(); +        the_condition_variable.notify_one(); +    } + +    bool empty() const +    { +        boost::mutex::scoped_lock lock(the_mutex); +        return the_queue.empty(); +    } + +    bool try_pop(T& popped_value) +    { +        boost::mutex::scoped_lock lock(the_mutex); +        if(the_queue.empty()) +        { +            return false; +        } + +        popped_value = the_queue.front(); +        the_queue.pop(); +        return true; +    } + +    void wait_and_pop(T& popped_value) +    { +        boost::mutex::scoped_lock lock(the_mutex); +        while(the_queue.empty()) +        { +            the_condition_variable.wait(lock); +        } +         +        popped_value = the_queue.front(); +        the_queue.pop(); +    } +}; + +struct FIRFilterWorkerData { +    ThreadsafeQueue<Buffer*> input_queue; +    ThreadsafeQueue<Buffer*> output_queue; +    float* taps; +    int n_taps; +}; + +class FIRFilterWorker { +    public: +        FIRFilterWorker () { +            running = false; +            calculationTime = 0; +        } + +        ~FIRFilterWorker() { +            MDEBUG("~FIRFilterWorker: Total elapsed thread time filtering: %zu\n", calculationTime); +        } + +        void start(struct FIRFilterWorkerData *firworkerdata) { +            running = true; +            fir_thread = boost::thread(&FIRFilterWorker::process, this, firworkerdata); +        } + +        void stop() { +            running = false; +            fir_thread.interrupt(); +            fir_thread.join(); +        } + +        void process(struct FIRFilterWorkerData *fwd); + + +    private: +        time_t calculationTime;  +        struct FIRFilterWorkerData *workerdata; +        bool running; +        boost::thread fir_thread; +}; + + +class FIRFilter : public ModCodec +{ +public: +    FIRFilter(char* taps_file); +    virtual ~FIRFilter(); +    FIRFilter(const FIRFilter&); +    FIRFilter& operator=(const FIRFilter&); + +    int process(Buffer* const dataIn, Buffer* dataOut); +    const char* name() { return "FIRFilter"; } + +protected: +    int my_Ntaps; +    float* myFilter; + +    FIRFilterWorker worker; +    int number_of_runs; +    struct FIRFilterWorkerData firwd; +}; + + +#endif //FIRFILTER_H diff --git a/src/Makefile.am b/src/Makefile.am index a46bec5..c82842c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -47,6 +47,7 @@ crc_dabmod_SOURCES =  DabMod.cpp \                        EtiReader.cpp EtiReader.h \                        Eti.cpp Eti.h \                        FicSource.cpp FicSource.h \ +					  FIRFilter.cpp FIRFilter.h \                        ModInput.cpp ModInput.h \                        PuncturingRule.cpp PuncturingRule.h \                        PuncturingEncoder.cpp PuncturingEncoder.h \ @@ -54,6 +55,8 @@ crc_dabmod_SOURCES =  DabMod.cpp \                        Flowgraph.cpp Flowgraph.h \                        GainControl.cpp GainControl.h \                        OutputMemory.cpp OutputMemory.h \ +					  TimestampDecoder.h TimestampDecoder.cpp \ +                      OutputUHD.cpp OutputUHD.h \                        ModOutput.cpp ModOutput.h \                        InputMemory.cpp InputMemory.h \                        OutputFile.cpp OutputFile.h \ diff --git a/src/Makefile.in b/src/Makefile.in index 5a95e45..6620494 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -73,14 +73,16 @@ am_crc_dabmod_OBJECTS = crc_dabmod-DabMod.$(OBJEXT) \  	crc_dabmod-Buffer.$(OBJEXT) crc_dabmod-ModCodec.$(OBJEXT) \  	crc_dabmod-ModPlugin.$(OBJEXT) crc_dabmod-ModFormat.$(OBJEXT) \  	crc_dabmod-EtiReader.$(OBJEXT) crc_dabmod-Eti.$(OBJEXT) \ -	crc_dabmod-FicSource.$(OBJEXT) crc_dabmod-ModInput.$(OBJEXT) \ +	crc_dabmod-FicSource.$(OBJEXT) crc_dabmod-FIRFilter.$(OBJEXT) \ +	crc_dabmod-ModInput.$(OBJEXT) \  	crc_dabmod-PuncturingRule.$(OBJEXT) \  	crc_dabmod-PuncturingEncoder.$(OBJEXT) \  	crc_dabmod-SubchannelSource.$(OBJEXT) \  	crc_dabmod-Flowgraph.$(OBJEXT) \  	crc_dabmod-GainControl.$(OBJEXT) \  	crc_dabmod-OutputMemory.$(OBJEXT) \ -	crc_dabmod-ModOutput.$(OBJEXT) \ +	crc_dabmod-TimestampDecoder.$(OBJEXT) \ +	crc_dabmod-OutputUHD.$(OBJEXT) crc_dabmod-ModOutput.$(OBJEXT) \  	crc_dabmod-InputMemory.$(OBJEXT) \  	crc_dabmod-OutputFile.$(OBJEXT) \  	crc_dabmod-FrameMultiplexer.$(OBJEXT) \ @@ -263,6 +265,7 @@ crc_dabmod_SOURCES = DabMod.cpp \                        EtiReader.cpp EtiReader.h \                        Eti.cpp Eti.h \                        FicSource.cpp FicSource.h \ +					  FIRFilter.cpp FIRFilter.h \                        ModInput.cpp ModInput.h \                        PuncturingRule.cpp PuncturingRule.h \                        PuncturingEncoder.cpp PuncturingEncoder.h \ @@ -270,6 +273,8 @@ crc_dabmod_SOURCES = DabMod.cpp \                        Flowgraph.cpp Flowgraph.h \                        GainControl.cpp GainControl.h \                        OutputMemory.cpp OutputMemory.h \ +					  TimestampDecoder.h TimestampDecoder.cpp \ +                      OutputUHD.cpp OutputUHD.h \                        ModOutput.cpp ModOutput.h \                        InputMemory.cpp InputMemory.h \                        OutputFile.cpp OutputFile.h \ @@ -417,6 +422,7 @@ distclean-compile:  @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc_dabmod-DifferentialModulator.Po@am__quote@  @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc_dabmod-Eti.Po@am__quote@  @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc_dabmod-EtiReader.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc_dabmod-FIRFilter.Po@am__quote@  @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc_dabmod-FicSource.Po@am__quote@  @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc_dabmod-Flowgraph.Po@am__quote@  @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc_dabmod-FrameMultiplexer.Po@am__quote@ @@ -434,6 +440,7 @@ distclean-compile:  @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc_dabmod-OfdmGenerator.Po@am__quote@  @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc_dabmod-OutputFile.Po@am__quote@  @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc_dabmod-OutputMemory.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc_dabmod-OutputUHD.Po@am__quote@  @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc_dabmod-PhaseReference.Po@am__quote@  @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc_dabmod-PrbsGenerator.Po@am__quote@  @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc_dabmod-PuncturingEncoder.Po@am__quote@ @@ -443,6 +450,7 @@ distclean-compile:  @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc_dabmod-SignalMultiplexer.Po@am__quote@  @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc_dabmod-SubchannelSource.Po@am__quote@  @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc_dabmod-TimeInterleaver.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc_dabmod-TimestampDecoder.Po@am__quote@  @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc_dabmod-kiss_fft.Po@am__quote@  @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc_dabmod-kiss_fftr.Po@am__quote@  @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc_dabmod-kiss_fftsimd.Po@am__quote@ @@ -658,6 +666,20 @@ crc_dabmod-FicSource.obj: FicSource.cpp  @AMDEP_TRUE@@am__fastdepCXX_FALSE@	DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@  @am__fastdepCXX_FALSE@	$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(crc_dabmod_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o crc_dabmod-FicSource.obj `if test -f 'FicSource.cpp'; then $(CYGPATH_W) 'FicSource.cpp'; else $(CYGPATH_W) '$(srcdir)/FicSource.cpp'; fi` +crc_dabmod-FIRFilter.o: FIRFilter.cpp +@am__fastdepCXX_TRUE@	$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(crc_dabmod_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT crc_dabmod-FIRFilter.o -MD -MP -MF $(DEPDIR)/crc_dabmod-FIRFilter.Tpo -c -o crc_dabmod-FIRFilter.o `test -f 'FIRFilter.cpp' || echo '$(srcdir)/'`FIRFilter.cpp +@am__fastdepCXX_TRUE@	$(am__mv) $(DEPDIR)/crc_dabmod-FIRFilter.Tpo $(DEPDIR)/crc_dabmod-FIRFilter.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@	source='FIRFilter.cpp' object='crc_dabmod-FIRFilter.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@	DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@	$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(crc_dabmod_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o crc_dabmod-FIRFilter.o `test -f 'FIRFilter.cpp' || echo '$(srcdir)/'`FIRFilter.cpp + +crc_dabmod-FIRFilter.obj: FIRFilter.cpp +@am__fastdepCXX_TRUE@	$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(crc_dabmod_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT crc_dabmod-FIRFilter.obj -MD -MP -MF $(DEPDIR)/crc_dabmod-FIRFilter.Tpo -c -o crc_dabmod-FIRFilter.obj `if test -f 'FIRFilter.cpp'; then $(CYGPATH_W) 'FIRFilter.cpp'; else $(CYGPATH_W) '$(srcdir)/FIRFilter.cpp'; fi` +@am__fastdepCXX_TRUE@	$(am__mv) $(DEPDIR)/crc_dabmod-FIRFilter.Tpo $(DEPDIR)/crc_dabmod-FIRFilter.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@	source='FIRFilter.cpp' object='crc_dabmod-FIRFilter.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@	DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@	$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(crc_dabmod_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o crc_dabmod-FIRFilter.obj `if test -f 'FIRFilter.cpp'; then $(CYGPATH_W) 'FIRFilter.cpp'; else $(CYGPATH_W) '$(srcdir)/FIRFilter.cpp'; fi` +  crc_dabmod-ModInput.o: ModInput.cpp  @am__fastdepCXX_TRUE@	$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(crc_dabmod_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT crc_dabmod-ModInput.o -MD -MP -MF $(DEPDIR)/crc_dabmod-ModInput.Tpo -c -o crc_dabmod-ModInput.o `test -f 'ModInput.cpp' || echo '$(srcdir)/'`ModInput.cpp  @am__fastdepCXX_TRUE@	$(am__mv) $(DEPDIR)/crc_dabmod-ModInput.Tpo $(DEPDIR)/crc_dabmod-ModInput.Po @@ -756,6 +778,34 @@ crc_dabmod-OutputMemory.obj: OutputMemory.cpp  @AMDEP_TRUE@@am__fastdepCXX_FALSE@	DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@  @am__fastdepCXX_FALSE@	$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(crc_dabmod_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o crc_dabmod-OutputMemory.obj `if test -f 'OutputMemory.cpp'; then $(CYGPATH_W) 'OutputMemory.cpp'; else $(CYGPATH_W) '$(srcdir)/OutputMemory.cpp'; fi` +crc_dabmod-TimestampDecoder.o: TimestampDecoder.cpp +@am__fastdepCXX_TRUE@	$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(crc_dabmod_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT crc_dabmod-TimestampDecoder.o -MD -MP -MF $(DEPDIR)/crc_dabmod-TimestampDecoder.Tpo -c -o crc_dabmod-TimestampDecoder.o `test -f 'TimestampDecoder.cpp' || echo '$(srcdir)/'`TimestampDecoder.cpp +@am__fastdepCXX_TRUE@	$(am__mv) $(DEPDIR)/crc_dabmod-TimestampDecoder.Tpo $(DEPDIR)/crc_dabmod-TimestampDecoder.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@	source='TimestampDecoder.cpp' object='crc_dabmod-TimestampDecoder.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@	DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@	$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(crc_dabmod_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o crc_dabmod-TimestampDecoder.o `test -f 'TimestampDecoder.cpp' || echo '$(srcdir)/'`TimestampDecoder.cpp + +crc_dabmod-TimestampDecoder.obj: TimestampDecoder.cpp +@am__fastdepCXX_TRUE@	$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(crc_dabmod_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT crc_dabmod-TimestampDecoder.obj -MD -MP -MF $(DEPDIR)/crc_dabmod-TimestampDecoder.Tpo -c -o crc_dabmod-TimestampDecoder.obj `if test -f 'TimestampDecoder.cpp'; then $(CYGPATH_W) 'TimestampDecoder.cpp'; else $(CYGPATH_W) '$(srcdir)/TimestampDecoder.cpp'; fi` +@am__fastdepCXX_TRUE@	$(am__mv) $(DEPDIR)/crc_dabmod-TimestampDecoder.Tpo $(DEPDIR)/crc_dabmod-TimestampDecoder.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@	source='TimestampDecoder.cpp' object='crc_dabmod-TimestampDecoder.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@	DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@	$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(crc_dabmod_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o crc_dabmod-TimestampDecoder.obj `if test -f 'TimestampDecoder.cpp'; then $(CYGPATH_W) 'TimestampDecoder.cpp'; else $(CYGPATH_W) '$(srcdir)/TimestampDecoder.cpp'; fi` + +crc_dabmod-OutputUHD.o: OutputUHD.cpp +@am__fastdepCXX_TRUE@	$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(crc_dabmod_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT crc_dabmod-OutputUHD.o -MD -MP -MF $(DEPDIR)/crc_dabmod-OutputUHD.Tpo -c -o crc_dabmod-OutputUHD.o `test -f 'OutputUHD.cpp' || echo '$(srcdir)/'`OutputUHD.cpp +@am__fastdepCXX_TRUE@	$(am__mv) $(DEPDIR)/crc_dabmod-OutputUHD.Tpo $(DEPDIR)/crc_dabmod-OutputUHD.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@	source='OutputUHD.cpp' object='crc_dabmod-OutputUHD.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@	DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@	$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(crc_dabmod_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o crc_dabmod-OutputUHD.o `test -f 'OutputUHD.cpp' || echo '$(srcdir)/'`OutputUHD.cpp + +crc_dabmod-OutputUHD.obj: OutputUHD.cpp +@am__fastdepCXX_TRUE@	$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(crc_dabmod_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT crc_dabmod-OutputUHD.obj -MD -MP -MF $(DEPDIR)/crc_dabmod-OutputUHD.Tpo -c -o crc_dabmod-OutputUHD.obj `if test -f 'OutputUHD.cpp'; then $(CYGPATH_W) 'OutputUHD.cpp'; else $(CYGPATH_W) '$(srcdir)/OutputUHD.cpp'; fi` +@am__fastdepCXX_TRUE@	$(am__mv) $(DEPDIR)/crc_dabmod-OutputUHD.Tpo $(DEPDIR)/crc_dabmod-OutputUHD.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@	source='OutputUHD.cpp' object='crc_dabmod-OutputUHD.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@	DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@	$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(crc_dabmod_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o crc_dabmod-OutputUHD.obj `if test -f 'OutputUHD.cpp'; then $(CYGPATH_W) 'OutputUHD.cpp'; else $(CYGPATH_W) '$(srcdir)/OutputUHD.cpp'; fi` +  crc_dabmod-ModOutput.o: ModOutput.cpp  @am__fastdepCXX_TRUE@	$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(crc_dabmod_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT crc_dabmod-ModOutput.o -MD -MP -MF $(DEPDIR)/crc_dabmod-ModOutput.Tpo -c -o crc_dabmod-ModOutput.o `test -f 'ModOutput.cpp' || echo '$(srcdir)/'`ModOutput.cpp  @am__fastdepCXX_TRUE@	$(am__mv) $(DEPDIR)/crc_dabmod-ModOutput.Tpo $(DEPDIR)/crc_dabmod-ModOutput.Po diff --git a/src/OutputUHD.cpp b/src/OutputUHD.cpp new file mode 100644 index 0000000..9e14a4f --- /dev/null +++ b/src/OutputUHD.cpp @@ -0,0 +1,466 @@ +/* +   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the +   Queen in Right of Canada (Communications Research Center Canada) + +   Includes modifications for which no copyright is claimed +   2012, Matthias P. Braendli, matthias.braendli@mpb.li + */ +/* +   This file is part of CRC-DADMOD. + +   CRC-DADMOD 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. + +   CRC-DADMOD 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 CRC-DADMOD.  If not, see <http://www.gnu.org/licenses/>. + */ + +#define ENABLE_UHD 1 + +#include "OutputUHD.h" +#include "PcDebug.h" + +#include <iostream> +#include <assert.h> +#include <stdexcept> +#include <stdio.h> +#include <time.h> +#include <errno.h> +#include <unistd.h> + +typedef std::complex<float> complexf; + +OutputUHD::OutputUHD(char* device, unsigned sampleRate, +        double frequency, int txgain, bool muteNoTimestamps) : +    ModOutput(ModFormat(1), ModFormat(0)), +    mySampleRate(sampleRate), myTxGain(txgain), +    myFrequency(frequency), mute_no_timestamps(muteNoTimestamps) +{ +    MDEBUG("OutputUHD::OutputUHD(device: %s) @ %p\n", +            device, this); + +    myDevice = device; +     +#if ENABLE_UHD +    uhd::set_thread_priority_safe(); + +    //create a usrp device +    MDEBUG("OutputUHD:Creating the usrp device with: %s...\n", +            myDevice.c_str()); +    myUsrp = uhd::usrp::multi_usrp::make(myDevice); +    MDEBUG("OutputUHD:Using device: %s...\n", myUsrp->get_pp_string().c_str()); + +    MDEBUG("OutputUHD:Setting REFCLK and PPS input...\n"); +    uhd::clock_config_t clock_config; +    clock_config.ref_source = uhd::clock_config_t::REF_SMA; +    clock_config.pps_source = uhd::clock_config_t::PPS_SMA; +    clock_config.pps_polarity = uhd::clock_config_t::PPS_POS; +    myUsrp->set_clock_config(clock_config, uhd::usrp::multi_usrp::ALL_MBOARDS); + +    std::cerr << "UHD clock source is " <<  +            myUsrp->get_clock_source(0) << std::endl; + +    std::cerr << "UHD time source is " << +            myUsrp->get_time_source(0) << std::endl; + +    //set the tx sample rate +    MDEBUG("OutputUHD:Setting rate to %d...\n", mySampleRate); +    myUsrp->set_tx_rate(mySampleRate); +    MDEBUG("OutputUHD:Actual TX Rate: %f Msps...\n", myUsrp->get_tx_rate()); +     +    //set the centre frequency +    MDEBUG("OutputUHD:Setting freq to %f...\n", myFrequency); +    myUsrp->set_tx_freq(myFrequency); +    MDEBUG("OutputUHD:Actual frequency: %f\n", myUsrp->get_tx_freq()); + +    myUsrp->set_tx_gain(myTxGain); +    MDEBUG("OutputUHD:Actual TX Gain: %f ...\n", myUsrp->get_tx_gain()); + + +    /* 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)) { +        fprintf(stderr, "errno: %d\n", errno); +        perror("OutputUHD:Error: 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)) { +                fprintf(stderr, "errno: %d\n", errno); +                perror("OutputUHD:Error: could not get time: "); +                break; +            } +        } +        MDEBUG("OutputUHD:sec+1: %ld ; now: %ld ...\n", seconds+1, now.tv_sec); +        /* We are now shortly after the second change. */ + +#warning "TODO usleep for USRP time setting !" +        //usleep(200000); // 200ms, we want the PPS to be later +        myUsrp->set_time_unknown_pps(uhd::time_spec_t(seconds + 2)); +        fprintf(stderr, "OutputUHD: Setting USRP time next pps to %f\n", +                uhd::time_spec_t(seconds + 2).get_real_secs()); +    } + +    usleep(1e6); +    fprintf(stderr, "OutputUHD: USRP time %f\n", +            myUsrp->get_time_now().get_real_secs()); +     +     +    // preparing output thread worker data +    uwd.myUsrp = myUsrp; +#else +    fprintf(stderr, "OutputUHD: UHD initialisation disabled at compile-time\n"); +#endif + +    uwd.frame0.ts.timestamp_valid = false; +    uwd.frame1.ts.timestamp_valid = false; +    uwd.sampleRate = mySampleRate; +    uwd.sourceContainsTimestamp = false; +    uwd.muteNoTimestamps = mute_no_timestamps; + +    // Since we don't know the buffer size, we cannot initialise +    // the buffers here +    first_run = true; + +    shared_ptr<barrier> b(new barrier(2)); +    my_sync_barrier = b; +    uwd.sync_barrier = b; + +    worker.start(&uwd); + +    MDEBUG("OutputUHD:UHD ready.\n"); +} + + +OutputUHD::~OutputUHD() +{ +    MDEBUG("OutputUHD::~OutputUHD() @ %p\n", this); +    worker.stop(); +} + +int OutputUHD::process(Buffer* dataIn, Buffer* dataOut) +{ +    struct frame_timestamp ts; + +    // On the first call, we must do some allocation and we must fill +    // the first buffer +    // We will only wait on the barrier on the subsequent calls to  +    // OutputUHD::process +    if (first_run) { +        fprintf(stderr, "OutUHD.process:Initialising...\n"); + +        uwd.bufsize = dataIn->getLength(); +        uwd.frame0.buf = malloc(uwd.bufsize); +        uwd.frame1.buf = malloc(uwd.bufsize); + +        uwd.sourceContainsTimestamp = myEtiReader->sourceContainsTimestamp(); + +        // The worker begins by transmitting buf0 +        memcpy(uwd.frame0.buf, dataIn->getData(), uwd.bufsize); + +        myEtiReader->calculateTimestamp(ts); +        uwd.frame0.ts = ts; +        uwd.frame0.fct = myEtiReader->getFCT(); + +        activebuffer = 1; + +        lastLen = uwd.bufsize; +        first_run = false; +        fprintf(stderr, "OutUHD.process:Initialising complete.\n"); +    } +    else { + +        if (lastLen != dataIn->getLength()) { +            // I expect that this never happens. +            fprintf(stderr, +                    "OutUHD.process:AAAAH PANIC input length changed from %zu to %zu !\n", +                    lastLen, dataIn->getLength()); +            throw std::runtime_error("Non-constant input length!"); +        } +        //fprintf(stderr, "OutUHD.process:Waiting for barrier\n"); +        my_sync_barrier.get()->wait(); +         +        // write into the our buffer while +        // the worker sends the other. + +        myEtiReader->calculateTimestamp(ts); +        uwd.sourceContainsTimestamp = myEtiReader->sourceContainsTimestamp(); + +        if (activebuffer == 0) { +            memcpy(uwd.frame0.buf, dataIn->getData(), uwd.bufsize); + +            uwd.frame0.ts = ts; +            uwd.frame0.fct = myEtiReader->getFCT(); +        } +        else if (activebuffer == 1) { +            memcpy(uwd.frame1.buf, dataIn->getData(), uwd.bufsize); + +            uwd.frame1.ts = ts; +            uwd.frame1.fct = myEtiReader->getFCT(); +        } + +        activebuffer = (activebuffer + 1) % 2; +    } + +    return uwd.bufsize; + +} + +void UHDWorker::process(struct UHDWorkerData *uwd) +{ +    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; + +#if ENABLE_UHD +    // Transmit timeout +    const double timeout = 0.2; + +    uhd::stream_args_t stream_args("fc32"); //complex floats +    uhd::tx_streamer::sptr myTxStream = uwd->myUsrp->get_tx_stream(stream_args); +    size_t bufsize = myTxStream->get_max_num_samps(); +#endif + +    const complexf* in; + +    uhd::tx_metadata_t md; +    md.start_of_burst = false; +    md.end_of_burst = false; + +    while (running) { +        md.has_time_spec = false; +        md.time_spec = uhd::time_spec_t(0.0); +        num_acc_samps = 0; +        write_fail_count = 0; + +        /* Wait for barrier */ +        // this wait will hopefully always be the second one +        // because modulation should be quicker than transmission +        //fprintf(stderr, "Worker:Waiting for barrier\n"); +        uwd->sync_barrier.get()->wait(); + +        if (workerbuffer == 0) { +            frame = &(uwd->frame0); +        } +        else if (workerbuffer == 1) { +            frame = &(uwd->frame1); +        } +        else { +            fprintf(stderr, "UHDWorker.process: workerbuffer: %d\n", workerbuffer); +            perror("UHDWorker.process: workerbuffer is neither 0 nor 1!\n"); +        } + +        in = reinterpret_cast<const complexf*>(frame->buf); +        pps_offset = frame->ts.timestamp_pps_offset; +        // +        // Tx second from MNSC +        tx_second = frame->ts.timestamp_sec; + +        sizeIn = uwd->bufsize / sizeof(complexf); +         +#if ENABLE_UHD +        // Check for ref_lock  +        if (! uwd->myUsrp->get_mboard_sensor("ref_locked", 0).to_bool()) { +            fprintf(stderr, "UHDWorker: RefLock lost !\n"); +        } + +        usrp_time = uwd->myUsrp->get_time_now().get_real_secs(); +#else +        usrp_time = 0; +#endif + +        if (uwd->sourceContainsTimestamp) { +            if (!frame->ts.timestamp_valid) { +                /* We have not received a full timestamp through +                 * MNSC. We sleep through the frame. +                 */ +                fprintf(stderr, "UHDOut: Throwing sample %d away: incomplete timestamp %zu + %f\n", +                        frame->fct, tx_second, pps_offset); +                usleep(20000); +                goto loopend; +            } + +            md.has_time_spec = true; +            md.time_spec = uhd::time_spec_t(tx_second, pps_offset); +             +            // md is defined, let's do some checks +            if (md.time_spec.get_real_secs() + 0.2 < usrp_time) { +                fprintf(stderr, +                    "* Timestamp in the past! offset: %f" +                    "  (%f) frame %d tx_second %zu; pps %f\n", +                    md.time_spec.get_real_secs() - usrp_time, +                    usrp_time, frame->fct, tx_second, pps_offset); +                goto loopend; //skip the frame +            } + +#if ENABLE_UHD +            if (md.time_spec.get_real_secs() > usrp_time + TIMESTAMP_MARGIN_FUTURE) { +                fprintf(stderr, +                    "* Timestamp too far in the future! offset: %f\n", +                    md.time_spec.get_real_secs() - usrp_time); +                usleep(20000); //sleep so as to fill buffers +            } + +            if (md.time_spec.get_real_secs() > usrp_time + TIMESTAMP_ABORT_FUTURE) { +                fprintf(stderr, +                    "* Timestamp way too far in the future! offset: %f\n", +                    md.time_spec.get_real_secs() - usrp_time); +                fprintf(stderr, "* Aborting\n"); +                throw std::runtime_error("Timestamp error. Aborted."); +            } +#endif + +            if (frame->fct % 50 < 4) { +                fprintf(stderr, "UHDOut (%f): frame %d tx_second %zu; pps %.9f\n", +                        usrp_time, +                        frame->fct, tx_second, pps_offset); +            } + +        } +        else { // !uwd->sourceContainsTimestamp +            if (uwd->muteNoTimestamps) { +                /* There was some error decoding the timestamp +                 */ +                fprintf(stderr, "UHDOut: Muting sample %d : no timestamp\n", +                        frame->fct); +                usleep(20000); +                goto loopend; +            } +        } + +#if ENABLE_UHD +        PDEBUG("UHDWorker::process:max_num_samps: %zu.\n", +                myTxStream->get_max_num_samps()); + +        /* +        size_t num_tx_samps = myTxStream->send( +                dataIn, sizeIn, md, timeout); + +        MDEBUG("UHDWorker::process:num_tx_samps: %zu.\n", num_tx_samps); +        */ +        while (running && (num_acc_samps < sizeIn)) { +            size_t samps_to_send = std::min(sizeIn - num_acc_samps, bufsize); + +            //ensure the the last packet has EOB set if the timestamps has been refreshed +            //and needs to be reconsidered. +            md.end_of_burst = (frame->ts.timestamp_refresh && (samps_to_send <= bufsize)); + +            //send a single packet +            size_t num_tx_samps = myTxStream->send( +                    &in[num_acc_samps], +                    samps_to_send, md, timeout); + +            num_acc_samps += num_tx_samps; + +            md.time_spec = uhd::time_spec_t(tx_second, pps_offset) +                         + uhd::time_spec_t(0, num_acc_samps/uwd->sampleRate); + +            /* +            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 +                fprintf(stderr, +                        "UHDWorker::process() unable to write to device, skipping frame!\n"); +                break; +#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; +                } +#endif +            } + +            //std::cerr << std::endl << "Waiting for async burst ACK... " << std::flush; +            uhd::async_metadata_t async_md; +            if (uwd->myUsrp->get_device()->recv_async_msg(async_md, 0)) { +                std::string PREFIX = "### asyncronous UHD message : "; +                switch (async_md.event_code) { +                    case uhd::async_metadata_t::EVENT_CODE_BURST_ACK: +                        break; +                    case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW: +                        std::cerr << PREFIX << "Underflow" << std::endl; +                        break; +                    case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR: +                        std::cerr << PREFIX << "Packet loss between host and device." << std::endl; +                        break; +                    case uhd::async_metadata_t::EVENT_CODE_TIME_ERROR: +                        std::cerr << PREFIX << "Packet had time that was late." << std::endl; +                        break; +                    case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW_IN_PACKET: +                        std::cerr << PREFIX << "Underflow occurred inside a packet." << std::endl; +                        break; +                    case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR_IN_BURST: +                        std::cerr << PREFIX << "Packet loss within a burst." << std::endl; +                        break; +                    default: +                        std::cerr << PREFIX << "unknown event code" << std::endl; +                        break; +                } +            } + +            /* +            bool got_async_burst_ack = false; +            //loop through all messages for the ACK packet (may have underflow messages in queue) +            while (not got_async_burst_ack and uwd->myUsrp->get_device()->recv_async_msg(async_md, 0.2)){ +                got_async_burst_ack = (async_md.event_code == uhd::async_metadata_t::EVENT_CODE_BURST_ACK); +            } +            //std::cerr << (got_async_burst_ack? "success" : "fail") << std::endl; +            // */ + + +        } +#endif + +        last_pps = pps_offset; + +loopend: +        // swap buffers +        workerbuffer = (workerbuffer + 1) % 2; +    } +} diff --git a/src/OutputUHD.h b/src/OutputUHD.h new file mode 100644 index 0000000..eea594f --- /dev/null +++ b/src/OutputUHD.h @@ -0,0 +1,167 @@ +/* +   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the +   Queen in Right of Canada (Communications Research Center Canada) + +   Includes modifications for which no copyright is claimed +   2012, Matthias P. Braendli, matthias.braendli@mpb.li + +DESCRIPTION: +   It is an output driver for the USRP family of devices, and uses +   the UHD library. This version is multi-threaded. One thread runs +   the whole modulator, and the second threads sends the data to the +   device. The two threads are synchronised using a barrier. Communication +   between the two threads is implemented using double buffering. At the +   barrier, they exchange the buffers. +*/ + +/* +   This file is part of CRC-DADMOD. + +   CRC-DADMOD 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. + +   CRC-DADMOD 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 CRC-DADMOD.  If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef OUTPUT_UHD_H +#define OUTPUT_UHD_H + +#ifdef HAVE_CONFIG_H +#   include <config.h> +#endif + +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <boost/thread/thread.hpp> +#include <boost/thread/barrier.hpp> +#include <boost/shared_ptr.hpp> + +#include "ModOutput.h" +#include "EtiReader.h" +#include "TimestampDecoder.h" + +#include <stdio.h> +#include <sys/types.h> + +#define MDEBUG(fmt, args...) fprintf (LOG, fmt , ## args)  +//#define MDEBUG(fmt, args...) + +// If the timestamp is further in the future than +// 100 seconds, abort +#define TIMESTAMP_ABORT_FUTURE 100 + +// Add a delay to increase buffers when +// frames are too far in the future +#define TIMESTAMP_MARGIN_FUTURE 0.5 + +typedef std::complex<float> complexf; + +using namespace boost; + +struct UHDWorkerFrameData { +    // Buffer holding frame data +    void* buf; + +    // Full timestamp +    struct frame_timestamp ts; + +    // Frame counter +    uint32_t fct; +}; + + +struct UHDWorkerData { +    uhd::usrp::multi_usrp::sptr myUsrp; +    unsigned sampleRate; + +    // Double buffering between the two threads +    // Each buffer contains one OFDM frame, and it's +    // associated timestamp +    // A full timestamp contains a TIST according to standard +    // and time information within MNSC with tx_second. +    bool sourceContainsTimestamp; + +    // When working with timestamps, mute the frames that +    // do not have a timestamp +    bool muteNoTimestamps; + +    struct UHDWorkerFrameData frame0; +    struct UHDWorkerFrameData frame1; +    size_t bufsize; // in bytes + +    // A barrier to synchronise the two threads +    shared_ptr<barrier> sync_barrier; +};  + + +class UHDWorker { +    public: +        UHDWorker () { +            running = false; +        } + +        void start(struct UHDWorkerData *uhdworkerdata) { +            running = true; +            uhd_thread = boost::thread(&UHDWorker::process, this, uhdworkerdata); +        } + +        void stop() { +            running = false; +            uhd_thread.interrupt(); +            uhd_thread.join(); +        } + +        void process(struct UHDWorkerData *uhdworkerdata); + + +    private: +        struct UHDWorkerData *workerdata; +        bool running; +        boost::thread uhd_thread; + +        uhd::tx_streamer::sptr myTxStream; +}; + + +class OutputUHD: public ModOutput { +    public: +        OutputUHD(char* device, unsigned sampleRate, double frequency, int txgain, +                bool muteNoTimestamps); +        ~OutputUHD(); + +        int process(Buffer* dataIn, Buffer* dataOut); + +        const char* name() { return "OutputUHD"; } + +        void setETIReader(EtiReader *etiReader) { +            myEtiReader = etiReader; +        } + +    protected: +        EtiReader *myEtiReader; +        std::string myDevice; +        unsigned mySampleRate; +        int myTxGain; +        double myFrequency; +        uhd::usrp::multi_usrp::sptr myUsrp; +        shared_ptr<barrier> my_sync_barrier; +        UHDWorker worker; +        bool first_run; +        struct UHDWorkerData uwd; +        int activebuffer; +        bool mute_no_timestamps; + +        size_t lastLen; +}; + + +#endif // OUTPUT_UHD_H diff --git a/src/PcDebug.h b/src/PcDebug.h index 6c6d30c..e4bfba3 100644 --- a/src/PcDebug.h +++ b/src/PcDebug.h @@ -74,4 +74,8 @@  #  endif  #endif +void mpbdebugopen(); +void mpbdebug(const char* message, int val); + +  #endif // PC_DEBUG_H diff --git a/src/TimestampDecoder.cpp b/src/TimestampDecoder.cpp new file mode 100644 index 0000000..3dd64bf --- /dev/null +++ b/src/TimestampDecoder.cpp @@ -0,0 +1,210 @@ +/* +   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the +   Queen in Right of Canada (Communications Research Center Canada) + +   Includes modifications for which no copyright is claimed +   2012, Matthias P. Braendli, matthias.braendli@mpb.li + */ +/* +   This file is part of CRC-DADMOD. + +   CRC-DADMOD 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. + +   CRC-DADMOD 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 CRC-DADMOD.  If not, see <http://www.gnu.org/licenses/>. + */ + +#include "TimestampDecoder.h" +#include "Eti.h" +#include <sys/types.h> +#include "PcDebug.h" +#include <iostream> +#include <fstream> +#include <string> +#include <boost/lexical_cast.hpp> + +//#define MDEBUG(fmt, args...) fprintf (LOG, fmt , ## args)  +#define MDEBUG(fmt, args...) PDEBUG(fmt, ## args)  + + +void TimestampDecoder::calculateTimestamp(struct frame_timestamp& ts) +{ +    ts.timestamp_valid = full_timestamp_received_mnsc; +    ts.timestamp_sec = time_secs; +    ts.timestamp_pps_offset = time_pps; + +    ts.timestamp_refresh = offset_changed; +    offset_changed = false; + +    ts += timestamp_offset; +    //ts.print("calc2 "); +} + +void TimestampDecoder::pushMNSCData(int framephase, uint16_t mnsc) +{ +    struct eti_MNSC_TIME_0 *mnsc0; +    struct eti_MNSC_TIME_1 *mnsc1; +    struct eti_MNSC_TIME_2 *mnsc2; +    struct eti_MNSC_TIME_3 *mnsc3; + +    switch (framephase) +    { +        case 0: +            mnsc0 = (struct eti_MNSC_TIME_0*)&mnsc; +            enableDecode = (mnsc0->type == 0) && +                (mnsc0->identifier == 0); +            gmtime_r(0, &temp_time); +            break; + +        case 1: +            mnsc1 = (struct eti_MNSC_TIME_1*)&mnsc; +            temp_time.tm_sec = mnsc1->second_tens * 10 + mnsc1->second_unit; +            temp_time.tm_min = mnsc1->minute_tens * 10 + mnsc1->minute_unit; + +            if (!mnsc1->sync_to_frame) +            { +                enableDecode = false; +                PDEBUG("TimestampDecoder: MNSC time info is not synchronised to frame\n"); +            } + +            break; + +        case 2: +            mnsc2 = (struct eti_MNSC_TIME_2*)&mnsc; +            temp_time.tm_hour = mnsc2->hour_tens * 10 + mnsc2->hour_unit; +            temp_time.tm_mday = mnsc2->day_tens * 10 + mnsc2->day_unit; +            break; + +        case 3: +            mnsc3 = (struct eti_MNSC_TIME_3*)&mnsc; +            temp_time.tm_mon = (mnsc3->month_tens * 10 + mnsc3->month_unit) - 1; +            temp_time.tm_year = (mnsc3->year_tens * 10 + mnsc3->year_unit) + 100; + +            if (enableDecode) +            { +                full_timestamp_received_mnsc = true; +                updateTimestampSeconds(mktime(&temp_time)); +            } +            break; +    } + +    MDEBUG("TimestampDecoder::pushMNSCData(%d, 0x%x)\n", framephase, mnsc); +    MDEBUG("                            -> %s\n", asctime(&temp_time)); +    MDEBUG("                            -> %zu\n", mktime(&temp_time)); +} + +void TimestampDecoder::updateTimestampSeconds(uint32_t secs) +{ +    MDEBUG("TimestampDecoder::updateTimestampSeconds(%d)\n", secs); +    if (inhibit_second_update > 0) +    { +        inhibit_second_update--; +    } +    else +    { +        time_secs = secs; +    } +} + +void TimestampDecoder::updateTimestampPPS(double pps) +{ +    MDEBUG("TimestampDecoder::updateTimestampPPS(%f)\n", pps); + +    if (time_pps > pps) // Second boundary crossed +    { +        MDEBUG("TimestampDecoder::updateTimestampPPS crossed second\n", pps); + +        // The second for the next eight frames will not +        // be defined by the MNSC +        inhibit_second_update = 2; +        time_secs += 1; +    } + +    time_pps = pps; + +} + +void TimestampDecoder::updateTimestampEti(int framephase, uint16_t mnsc, double pps) +{ +    updateTimestampPPS(pps); +    pushMNSCData(framephase, mnsc); +} + + +bool TimestampDecoder::updateModulatorOffset() +{ +    using namespace std; +    using boost::lexical_cast; +    using boost::bad_lexical_cast; + +    if (modconfig.use_offset_fixed) +    { +        timestamp_offset = modconfig.offset_fixed; +        PDEBUG("Setting timestamp offset to %f\n", timestamp_offset); +        return true; +    } +    else if (modconfig.use_offset_file) +    { +        bool r = false; +        double newoffset; + +        std::string filedata; +        ifstream filestream; + +        try +        { +            filestream.open(modconfig.offset_filename); +            if (!filestream.eof()) +            { +                getline(filestream, filedata); +                try +                { +                    newoffset = lexical_cast<double>(filedata); +                    r = true; +                } +                catch (bad_lexical_cast& e) +                { +                    fprintf(stderr, "Error parsing timestamp offset from file\n"); +                    r = false; +                } +            } +            else +            { +                fprintf(stderr, "Error reading from timestamp offset file: eof reached\n"); +                r = false; +            } +            filestream.close(); +        } +        catch (exception& e) +        { +            fprintf(stderr, "Error opening timestamp offset file\n"); +            r = false; +        } + + +        if (r) +        { +            if (timestamp_offset != newoffset) +            { +                timestamp_offset = newoffset; +                fprintf(stderr, "TimestampDecoder::updateTimestampOffset:" \ +                        "new offset is %f\n", timestamp_offset); +                offset_changed = true; +            } + +        } + +        return r; +    } +    else { +        return false; +    } +} diff --git a/src/TimestampDecoder.h b/src/TimestampDecoder.h new file mode 100644 index 0000000..3a1fb1b --- /dev/null +++ b/src/TimestampDecoder.h @@ -0,0 +1,154 @@ +/* +   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the +   Queen in Right of Canada (Communications Research Center Canada) + +   Includes modifications for which no copyright is claimed +   2012, Matthias P. Braendli, matthias.braendli@mpb.li + */ +/* +   This file is part of CRC-DADMOD. + +   CRC-DADMOD 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. + +   CRC-DADMOD 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 CRC-DADMOD.  If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TIMESTAMP_DECODER_H +#define TIMESTAMP_DECODER_H + +#include "Eti.h" +#include <time.h> +#include <math.h> +#include <stdio.h> + +struct modulator_offset_config +{ +    bool use_offset_fixed; +    double offset_fixed; + +    bool use_offset_file; +    char* offset_filename; +}; + +struct frame_timestamp +{ +    uint32_t timestamp_sec; +    double timestamp_pps_offset; +    bool timestamp_valid; +    bool timestamp_refresh; + +    struct frame_timestamp operator=(const struct frame_timestamp &rhs) +    { +        if (this != &rhs) { +            this->timestamp_sec = rhs.timestamp_sec; +            this->timestamp_pps_offset = rhs.timestamp_pps_offset; +            this->timestamp_valid = rhs.timestamp_valid; +            this->timestamp_refresh = rhs.timestamp_refresh; +        } + +        return *this; +    } + +    struct frame_timestamp& operator+=(const double& diff) +    { +        double offset_pps, offset_secs; +        offset_pps = modf(diff, &offset_secs); + +        this->timestamp_sec += lrintf(offset_secs); +        this->timestamp_pps_offset += offset_pps; + +        while (this->timestamp_pps_offset > 1) +        { +            this->timestamp_pps_offset -= 1.0; +            this->timestamp_sec += 1; +        }; +        return *this; +    } + +    const struct frame_timestamp operator+(const double diff) +    { +        struct frame_timestamp ts = *this; +        ts += diff; +        return ts; +    } + +    void print(const char* t) +    { +        fprintf(stderr, +                "%s <struct frame_timestamp(%s, %d, %.9f)>\n",  +                t, this->timestamp_valid ? "valid" : "invalid", +                 this->timestamp_sec, this->timestamp_pps_offset); +    } +}; + +/* This module decodes MNSC time information */ +class TimestampDecoder +{ +    public:  +        TimestampDecoder(struct modulator_offset_config& config): +            modconfig(config) +        { +            inhibit_second_update = 0; +            time_pps = 0.0; +            time_secs = 0; +            enableDecode = false; +            full_timestamp_received_mnsc = false; +            gmtime_r(0, &temp_time); +            offset_changed = false; +        }; + +        /* Calculate the timestamp for the current frame. */ +        void calculateTimestamp(struct frame_timestamp& ts); + +        /* Update timestamp data from data in ETI */ +        void updateTimestampEti(int framephase, uint16_t mnsc, double pps); + +        /* Update the modulator timestamp offset according to the modconf +         */ +        bool updateModulatorOffset(); + +    protected: +        /* Push a new MNSC field into the decoder */ +        void pushMNSCData(int framephase, uint16_t mnsc); + +        /* Each frame contains the TIST field with the PPS offset. +         * For each frame, this function must be called to update +         * the timestamp +         */ +        void updateTimestampPPS(double pps); +         +        /* Update the timestamp when a full set of MNSC data is +         * known. This function can be called at most every four +         * frames when the data is transferred using the MNSC. +         */ +        void updateTimestampSeconds(uint32_t secs); + +        struct tm temp_time; +        uint32_t time_secs; +        double time_pps; +        double timestamp_offset; +        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 +         */ +        bool enableDecode; + +        /* Disable timstamps until full time has been received in mnsc */ +        bool full_timestamp_received_mnsc; +}; + +#endif | 
