From 2061843c3d66d386009f0c90123a832085c0ad54 Mon Sep 17 00:00:00 2001 From: Alexander Chemeris Date: Wed, 20 Apr 2016 08:49:38 +0300 Subject: osmo-trx-dec: Offline demodulation tool. Change-Id: Ic5b59c7fe1a0c02d962b36de2fd5d7fc9a02f266 --- .gitignore | 1 + Transceiver52M/Makefile.am | 10 +- Transceiver52M/osmo-trx-dec.cpp | 520 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 530 insertions(+), 1 deletion(-) create mode 100644 Transceiver52M/osmo-trx-dec.cpp diff --git a/.gitignore b/.gitignore index ee86991..9ccc8e8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ *.la Transceiver52M/osmo-trx Transceiver52M/osmo-trx-gen +Transceiver52M/osmo-trx-dec # tests CommonLibs/BitVectorTest diff --git a/Transceiver52M/Makefile.am b/Transceiver52M/Makefile.am index 76a64e8..cfceff8 100644 --- a/Transceiver52M/Makefile.am +++ b/Transceiver52M/Makefile.am @@ -71,7 +71,8 @@ libtransceiver_la_SOURCES = \ bin_PROGRAMS = \ osmo-trx \ - osmo-trx-gen + osmo-trx-gen \ + osmo-trx-dec noinst_HEADERS = \ Complex.h \ @@ -108,6 +109,13 @@ osmo_trx_gen_LDADD = \ $(GSM_LA) \ $(COMMON_LA) $(SQLITE_LA) +osmo_trx_dec_SOURCES = osmo-trx-dec.cpp +osmo_trx_dec_LDADD = \ + libtransceiver.la \ + $(ARCH_LA) \ + $(GSM_LA) \ + $(COMMON_LA) $(SQLITE_LA) + if USRP1 libtransceiver_la_SOURCES += USRPDevice.cpp osmo_trx_LDADD += $(USRP_LIBS) diff --git a/Transceiver52M/osmo-trx-dec.cpp b/Transceiver52M/osmo-trx-dec.cpp new file mode 100644 index 0000000..977061b --- /dev/null +++ b/Transceiver52M/osmo-trx-dec.cpp @@ -0,0 +1,520 @@ +/* + * Copyright (C) 2016-2017 Alexander Chemeris + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "Logger.h" +#include "sigProcLib.h" +#include "signalVector.h" +#include "Transceiver.h" +#include "Configuration.h" + +extern "C" { +#include "convolve.h" +#include "convert.h" +} + +#define DEFAULT_RX_SPS 1 +#define DEFAULT_SEARCH_WINDOW 30 + +// Tail + data + stealing + midamble + guard (without the last 0.25) +#define BURST_LEN_FULL 156 +// Tail + data + stealing + midamble +#define BURST_LEN_ACTIVE 148 +// Tail + data + stealing + midamble - 2*0.5 +#define BURST_LEN_USEFUL 147 + +// Size of a sample in bytes as stores in a file +#define SAMPLE_SIZE_BYTES (2 * sizeof(float)) +// Burst length in bytes as stored in a file +#define BURST_LEN_BYTES (BURST_LEN_FULL * SAMPLE_SIZE_BYTES) + +ConfigurationTable gConfig; + +struct trx_config { + std::string log_level; + unsigned sps; + unsigned tsc; + unsigned max_expected_delay_nb; + unsigned max_expected_delay_ab; + double full_scale; + bool edge; + CorrType type; + std::string filename; + unsigned ber_burst_avg; ///< Average BER over this many bursts. + ///< Set to 0 to average for the whole duration. +}; + +class NormalBurstSoftbitMask { +public: + NormalBurstSoftbitMask(SoftVector &softBits) + : mSoftBits(softBits) + { + } + + SoftVector &bits() { return mSoftBits; } + SoftVector tailBitsL() { return mSoftBits.segment(0,3); } + SoftVector dataBitsL() { return mSoftBits.segment(3,57); } + SoftVector stealingBitsL() { return mSoftBits.segment(60, 1); } + SoftVector midambleBits() { return mSoftBits.segment(61, 26); } + SoftVector stealingBitsR() { return mSoftBits.segment(87, 1); } + SoftVector dataBitsR() { return mSoftBits.segment(88,57); } + SoftVector tailBitsR() { return mSoftBits.segment(145,3); } + SoftVector guardBits() { return mSoftBits.segment(148,8); } + +protected: + SoftVector &mSoftBits; +}; + +class SoftBurst { +public: + SoftBurst(SoftVector *softBits, double toa=0) + : mSoftBits(softBits), mTOA(toa) + { + assert(mSoftBits != NULL); + } + + ~SoftBurst() + { + delete mSoftBits; + } + + void TOA(double TOA) { mTOA = TOA; } + double TOA() { return mTOA; } + + NormalBurstSoftbitMask normalBurstMask() { return NormalBurstSoftbitMask(*mSoftBits); } + +protected: + SoftVector *mSoftBits; + double mTOA; +}; + +class BEREstimator { +public: + BEREstimator(const PRBS& prbs) + : mPRBS(prbs), mTotalBits(0), mErrorBits(0), mSynchronized(false) + {} + + unsigned synchronize(const BitVector &bits) + { + for (unsigned i=0; i 0) { + std::cout << "Detected correlation type: " << (CorrType)rc << std::endl; + } else { + if (rc == -SIGERR_CLIP) { + std::cout << "Clipping detected on received RACH or Normal Burst" << std::endl; + } else if (rc != SIGERR_NONE) { + std::cout << "Unhandled RACH or Normal Burst detection error" << std::endl; + } else { +// std::cout << "No burst detected" << std::endl; + } + } +} + +SoftVector *demodulateBurst(const signalVector &burst, + CorrType expected_type, + unsigned sps, unsigned tsc, + unsigned max_expected_delay, + double &timingOffset) +{ + complex amp; + float toa; + int rc; + CorrType detected_type; + + /* Detect normal or RACH bursts */ + rc = detectAnyBurst(burst, tsc, BURST_THRESH, sps, expected_type, amp, toa, + max_expected_delay); + printDetectionResult(rc); + if (rc <= 0) { + return NULL; + } + + // Convert samples to symbols + timingOffset = toa / sps; + // rc > 0 means it's a detected CorrType + detected_type = (CorrType)rc; + + return demodAnyBurst(burst, sps, amp, toa, detected_type); +} + +static bool processBurst(const trx_config &config, signalVector &burst, + unsigned max_expected_delay, + double &RSSI, + double &timingOffset, + BEREstimator &berEstimator) +{ + RSSI = getBurstRSSI(burst, config.sps, config.full_scale); + SoftVector *softBits = demodulateBurst(burst, config.type, config.sps,config.tsc, + max_expected_delay, timingOffset); + + /* Print burst information and content */ + if (softBits == NULL) { + std::cout << "Skipped frame" << std::endl; + // TODO: This is different for EDGE + berEstimator.skip(57*2); + return false; + } + + SoftBurst softBurst(softBits, timingOffset); + NormalBurstSoftbitMask nb = softBurst.normalBurstMask(); + + berEstimator.sync_and_process(nb.dataBitsL().sliced()); + berEstimator.sync_and_process(nb.dataBitsR().sliced()); + + std::cout << "TOA: " << softBurst.TOA() << " symbols" << std::endl; + // Exclude tail and guard bits from the energy calculation + std::cout << "Energy: " << softBits->segment(3,142).getEnergy() << std::endl; + //std::cout << "Demodulated burst: " << *softBits << std::endl; + std::cout << " tail|--------------------------data---------------------------|f|--------midamble----------|f|--------------------------data---------------------------|tai|-guard--" << std::endl; + // " 000 010001011011110011101001100100000001010001011000100100010 0 11101111000100101110111100 0 011010111011101010011010111000101100001110101011011001011 000 1''..---" + std::cout << "Demodulated burst:" + << " " << nb.tailBitsL() + << " " << nb.dataBitsL() + << " " << nb.stealingBitsL() + << " " << nb.midambleBits() + << " " << nb.stealingBitsR() + << " " << nb.dataBitsR() + << " " << nb.tailBitsR() + << " " << nb.guardBits() + << std::endl; + + return true; +} + +// Setup configuration values +static void print_config(struct trx_config *config) +{ + std::ostringstream ost(""); + ost << "Config Settings" << std::endl; + ost << " Source file name............. " << config->filename << std::endl; + ost << " Log Level.................... " << config->log_level << std::endl; + ost << " Rx Samples-per-Symbol........ " << config->sps << std::endl; + ost << " EDGE support................. " << (config->edge ? "Enabled" : "Disabled") << std::endl; + ost << " Burst type................... " << config->type << std::endl; + ost << " Burst TSC.................... " << config->tsc << std::endl; + ost << " Normal Burst search window... " << config->max_expected_delay_nb << std::endl; + ost << " Access Burst search window... " << config->max_expected_delay_ab << std::endl; + ost << " Signal full scale............ " << config->full_scale << std::endl; + ost << " BER average window (bursts).. " << config->ber_burst_avg << std::endl; + std::cout << ost << std::endl; +} + +static void print_help() +{ + fprintf(stdout, "Options:\n" + " -h This text\n" + " -l LEVEL Logging level (%s)\n" + " -e Enable EDGE receiver\n" + " -s SPS Samples-per-symbol (1 or 4, default: %d)\n" + " -t TSC Burst training sequence (0 to 7, default: 0)\n" + " -f FILE File to read\n" + " -w SYMBOLS Normal Burst search window (0 to 156, default: %d)\n" + " -W SYMBOLS Access Burst search window (0 to 156, default: %d)\n" + " -b BURSTS BER average window. Set to 0 to average over the whole file (default: 1)\n", + "EMERG, ALERT, CRT, ERR, WARNING, NOTICE, INFO, DEBUG", + DEFAULT_RX_SPS, + DEFAULT_SEARCH_WINDOW, DEFAULT_SEARCH_WINDOW); +} + +static bool handle_options(int argc, char **argv, struct trx_config *config) +{ + int option; + + config->log_level = "NOTICE"; + config->sps = DEFAULT_RX_SPS; + config->tsc = 0; + config->max_expected_delay_nb = DEFAULT_SEARCH_WINDOW; + config->max_expected_delay_ab = DEFAULT_SEARCH_WINDOW; + config->full_scale = SHRT_MAX; + config->edge = false; + config->type = TSC; + config->ber_burst_avg = 1; + + while ((option = getopt(argc, argv, "ls:et:f:w:W:b:h")) != -1) { + switch (option) { + case 'l': + config->log_level = optarg; + break; + case 's': + config->sps = atoi(optarg); + break; + case 'e': + config->edge = true; + break; + case 't': + config->tsc = atoi(optarg); + break; + case 'f': + config->filename = optarg; + break; + case 'w': + config->max_expected_delay_nb = atoi(optarg); + break; + case 'W': + config->max_expected_delay_ab = atoi(optarg); + break; + case 'b': + config->ber_burst_avg = atoi(optarg); + break; + case 'h': + default: + print_help(); + exit(0); + } + } + + if ((config->sps != 1) && (config->sps != 4)) { + printf("ERROR: Unsupported samples-per-symbol %i\n\n", config->sps); + return false; + } + + if (config->edge && (config->sps != 4)) { + printf("ERROR: EDGE only supported at 4 samples per symbol\n\n"); + return false; + } + + if (config->tsc > 7) { + printf("ERROR: Invalid training sequence %i\n\n", config->tsc); + return false; + } + + if (config->filename.length() == 0) { + printf("ERROR: No input file specified\n\n"); + return false; + } + + if (config->max_expected_delay_nb > 156 || config->max_expected_delay_nb < 0 || + config->max_expected_delay_ab > 156 || config->max_expected_delay_ab < 0) { + printf("ERROR: Invalid search window size, must be withit [1..156] range\n\n"); + return false; + } + + return true; +} + +int main(int argc, char *argv[]) +{ + struct trx_config config; + +#ifdef HAVE_SSE3 + printf("Info: SSE3 support compiled in"); + if (__builtin_cpu_supports("sse3")) + printf(" and supported by CPU\n"); + else + printf(", but not supported by CPU\n"); +#endif + +#ifdef HAVE_SSE4_1 + printf("Info: SSE4.1 support compiled in"); + if (__builtin_cpu_supports("sse4.1")) + printf(" and supported by CPU\n"); + else + printf(", but not supported by CPU\n"); +#endif + + convolve_init(); + convert_init(); + + // Process command line options and print config to screen + if (!handle_options(argc, argv, &config)) { + print_help(); + exit(0); + } + print_config(&config); + + gLogInit("transceiver", config.log_level.c_str(), LOG_LOCAL7); + + if (!sigProcLibSetup()) { + LOG(ALERT) << "Failed to initialize signal processing library"; + return -1; + } + + double RSSI; + double timingOffset, timingOffsetPrev = 0.0; + signalVector burst(2*BURST_LEN_FULL); + GSM::Time gsmTime; + bool syncedTo157bits = false; // We should syncronize to 156-157 frame structure only once + bool burst156_157 = false; // Set to true to enable 156-156-156-157 frame + int bitsReadExtra = 0; // set to 1 every 4 bursts and when TOA>1.0 + int bitsToSkip = 0; // set to 1 when TOA<0.0 + unsigned berBurstsAveraged = 0; + PRBS9 prbs; + BEREstimator berEstimator(prbs); + + // Configure output stream + std::cout << std::fixed; + std::cout << std::setprecision(2); + + std::ifstream file (config.filename.c_str(), std::ifstream::binary); + + // Read the first burst, but do not process it, because we need at least two bursts + // worth of data for reliable initial detection. + file.read((char*)burst.begin(), config.sps * BURST_LEN_BYTES); + {signalVector t = burst.segment(0, BURST_LEN_FULL); scaleVector(t, complex(SHRT_MAX)); } + +#if 0 + /* Distort signal */ + { + signalVector burst_read = burst.segment(85,156); + std::ifstream file (config.filename.c_str(), std::ifstream::binary); + file.read((char*)burst_read.begin(), burst_read.size() * 2 * sizeof(float)); + file.close(); + } +#endif +#if 1 + // Read more data and try burst detection until successful + while(file.read((char*)(burst.begin()+config.sps*BURST_LEN_FULL), config.sps*BURST_LEN_BYTES)) + { + {signalVector t = burst.segment(BURST_LEN_FULL, BURST_LEN_FULL); scaleVector(t, complex(SHRT_MAX)); } + bool found = processBurst(config, burst, BURST_LEN_FULL, RSSI, timingOffset, berEstimator); + std::cout << "RSSI: " << RSSI << " dBFS" << std::endl; + if (found) { + gsmTime.incTN(); + berBurstsAveraged++; + break; + } + burst.segmentMove(config.sps*BURST_LEN_FULL, 0, config.sps*BURST_LEN_FULL); + } + + // Align stream to burst + int offsetInt = (int)timingOffset; + burst.segmentMove(config.sps*(BURST_LEN_FULL+offsetInt), 0, config.sps*(BURST_LEN_FULL-offsetInt)); + {signalVector t = burst.segment(0, BURST_LEN_FULL-offsetInt); scaleVector(t, complex(1.0/SHRT_MAX)); } + file.read((char*)(burst.begin()+config.sps*(BURST_LEN_FULL-offsetInt)), config.sps*offsetInt*SAMPLE_SIZE_BYTES); +#endif + + // Resize burst vector to hold only one burst, because demodulation code + // always decode the full vector size. + burst.shrink(BURST_LEN_FULL+1); + + // Process the rest of the stream + do { + {signalVector t = burst.segment(0, BURST_LEN_FULL); scaleVector(t, complex(SHRT_MAX)); } + processBurst(config, burst, (config.type==RACH)?config.max_expected_delay_ab:config.max_expected_delay_ab, + RSSI, timingOffset, berEstimator); + if (burst156_157 && !syncedTo157bits && timingOffset - timingOffsetPrev > .75) { + std::cout << "TOA adjust: Found a 157-bit burst, reset TN to mark it" << std::endl; + gsmTime.TN(2); + timingOffset -= 1.0; + // Make sure we do this adjustment only once. + syncedTo157bits = true; + } else { + gsmTime.incTN(); + } + bitsToSkip = 0; + bitsReadExtra = 0; + if (timingOffset < 0.0) { + std::cout << "TOA adjust: skip a bit" << std::endl; + burst[0] = 0; + bitsToSkip = 1; + bitsReadExtra--; + } + bitsReadExtra += (gsmTime.TN()%4 == 0); + if (timingOffset > 1.1) { + std::cout << "TOA adjust: add extra bit" << std::endl; + bitsReadExtra++; + } + std::cout << "Clock: " << gsmTime; + std::cout << " RSSI: " << RSSI << " dBFS"; + std::cout << " Error bits: " << berEstimator.errorBits() << " Total bits: " << berEstimator.totalBits() + << " BER: " << 100.0*berEstimator.errorBits() / berEstimator.totalBits() << "%" << std::endl; + berBurstsAveraged++; + // Never reset if config.ber_burst_avg is 0 + if (config.ber_burst_avg > 0 && berBurstsAveraged >= config.ber_burst_avg) { + berBurstsAveraged = 0; + berEstimator.reset(); + } + std::cout << "bitsReadExtra: " << bitsReadExtra << " bitsToSkip: " << bitsToSkip << std::endl; + timingOffsetPrev = timingOffset; + } while(file.read((char*)(burst.begin()+bitsToSkip), config.sps*(BURST_LEN_BYTES+SAMPLE_SIZE_BYTES*bitsReadExtra))); + + std::cout << "End of file reached" << std::endl; + file.close(); + + return 0; +} -- cgit v1.2.3