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 |