aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Transceiver52M/Makefile.am10
-rw-r--r--Transceiver52M/osmo-trx-dec.cpp520
3 files changed, 530 insertions, 1 deletions
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 <Alexander.Chemeris@fairwaves.co>
+ *
+ * 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 <limits.h>
+#include <fstream>
+#include <iomanip>
+
+#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<mPRBS.size(); i++) {
+ mPRBS.processBit(bits[i]);
+ }
+ mSynchronized = true;
+ return mPRBS.size();
+ }
+
+ void process(const BitVector &bits, size_t start_from = 0)
+ {
+ for (size_t i=start_from; i<bits.size(); i++) {
+ mTotalBits++;
+ if (mPRBS.generateBit() != bits.bit(i)) {
+ mErrorBits++;
+ }
+ }
+ }
+
+ void sync_and_process(const BitVector &bits)
+ {
+ unsigned skip = 0;
+ if (!mSynchronized) {
+ skip = synchronize(bits);
+ }
+
+ process(bits, skip);
+ }
+
+ void skip(size_t num)
+ {
+ for (size_t i=0; i<num; i++) {
+ mTotalBits++;
+ mErrorBits++;
+ mPRBS.generateBit();
+ }
+ }
+
+ void reset()
+ {
+ mTotalBits = 0;
+ mErrorBits = 0;
+ }
+
+ unsigned totalBits() const { return mTotalBits; }
+ unsigned errorBits() const { return mErrorBits; }
+ double BER() const { return mErrorBits/(double)mTotalBits; }
+
+ bool isSynchronized() const {return mSynchronized; }
+
+protected:
+ PRBS mPRBS;
+ unsigned mTotalBits;
+ unsigned mErrorBits;
+ bool mSynchronized;
+};
+
+double getBurstRSSI(const signalVector &burst, unsigned sps, double full_scale)
+{
+ /* Calculate average power of the burst */
+ float avg = energyDetect(burst, 20 * sps);
+ return 20.0 * log10(sqrt(avg) / full_scale);
+}
+
+void printDetectionResult(int rc)
+{
+ if (rc > 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;
+}