diff --git a/plugins/channelrx/demoddatv/CMakeLists.txt b/plugins/channelrx/demoddatv/CMakeLists.txt index b1eedcbcb..5e9171a81 100644 --- a/plugins/channelrx/demoddatv/CMakeLists.txt +++ b/plugins/channelrx/demoddatv/CMakeLists.txt @@ -7,7 +7,12 @@ set(datv_SOURCES datvdemodgui.cpp datvdemodplugin.cpp datvideostream.cpp - datvideorender.cpp + datvideorender.cpp + leansdr/dvb.cpp + leansdr/filtergen.cpp + leansdr/framework.cpp + leansdr/math.cpp + leansdr/sdr.cpp ) set(datv_HEADERS @@ -15,7 +20,12 @@ set(datv_HEADERS datvdemodgui.h datvdemodplugin.h datvideostream.h - datvideorender.h + datvideorender.h + leansdr/dvb.h + leansdr/filtergen.h + leansdr/framework.h + leansdr/math.h + leansdr/sdr.h ) set(datv_FORMS diff --git a/plugins/channelrx/demoddatv/datvconstellation.h b/plugins/channelrx/demoddatv/datvconstellation.h index 04d7fa0f7..749ea2ca0 100644 --- a/plugins/channelrx/demoddatv/datvconstellation.h +++ b/plugins/channelrx/demoddatv/datvconstellation.h @@ -28,12 +28,87 @@ namespace leansdr { static const int DEFAULT_GUI_DECIMATION = 64; +static inline cstln_lut * make_dvbs2_constellation(cstln_lut::predef c, + code_rate r) +{ + float gamma1 = 1, gamma2 = 1, gamma3 = 1; + switch (c) + { + case cstln_lut::APSK16: + // EN 302 307, section 5.4.3, Table 9 + switch (r) + { + case FEC23: + case FEC46: + gamma1 = 3.15; + break; + case FEC34: + gamma1 = 2.85; + break; + case FEC45: + gamma1 = 2.75; + break; + case FEC56: + gamma1 = 2.70; + break; + case FEC89: + gamma1 = 2.60; + break; + case FEC910: + gamma1 = 2.57; + break; + default: + fail("cstln_lut<256>::make_dvbs2_constellation: Code rate not supported with APSK16"); + return 0; + } + break; + case cstln_lut::APSK32: + // EN 302 307, section 5.4.4, Table 10 + switch (r) + { + case FEC34: + gamma1 = 2.84; + gamma2 = 5.27; + break; + case FEC45: + gamma1 = 2.72; + gamma2 = 4.87; + break; + case FEC56: + gamma1 = 2.64; + gamma2 = 4.64; + break; + case FEC89: + gamma1 = 2.54; + gamma2 = 4.33; + break; + case FEC910: + gamma1 = 2.53; + gamma2 = 4.30; + break; + default: + fail("cstln_lut::make_dvbs2_constellation: Code rate not supported with APSK32"); + return 0; + } + break; + case cstln_lut::APSK64E: + // EN 302 307-2, section 5.4.5, Table 13f + gamma1 = 2.4; + gamma2 = 4.3; + gamma3 = 7; + break; + default: + break; + } + return new cstln_lut(c, gamma1, gamma2, gamma3); +} + template struct datvconstellation: runnable { T xymin, xymax; unsigned long decimation; - unsigned long pixels_per_frame; - cstln_lut<256> **cstln; // Optional ptr to optional constellation + long pixels_per_frame; + cstln_lut **cstln; // Optional ptr to optional constellation TVScreen *m_objDATVScreen; pipereader > in; unsigned long phase; diff --git a/plugins/channelrx/demoddatv/datvdemod.cpp b/plugins/channelrx/demoddatv/datvdemod.cpp index 6b3e51646..8941efec9 100644 --- a/plugins/channelrx/demoddatv/datvdemod.cpp +++ b/plugins/channelrx/demoddatv/datvdemod.cpp @@ -531,34 +531,34 @@ void DATVDemod::InitDATVFramework() switch(m_objRunning.enmModulation) { case BPSK: - m_objCfg.constellation = leansdr::cstln_lut<256>::BPSK; + m_objCfg.constellation = leansdr::cstln_lut::BPSK; break; case QPSK: - m_objCfg.constellation = leansdr::cstln_lut<256>::QPSK; + m_objCfg.constellation = leansdr::cstln_lut::QPSK; break; case PSK8: - m_objCfg.constellation = leansdr::cstln_lut<256>::PSK8; + m_objCfg.constellation = leansdr::cstln_lut::PSK8; break; case APSK16: - m_objCfg.constellation = leansdr::cstln_lut<256>::APSK16; + m_objCfg.constellation = leansdr::cstln_lut::APSK16; break; case APSK32: - m_objCfg.constellation = leansdr::cstln_lut<256>::APSK32; + m_objCfg.constellation = leansdr::cstln_lut::APSK32; break; case APSK64E: - m_objCfg.constellation = leansdr::cstln_lut<256>::APSK64E; + m_objCfg.constellation = leansdr::cstln_lut::APSK64E; break; case QAM16: - m_objCfg.constellation = leansdr::cstln_lut<256>::QAM16; + m_objCfg.constellation = leansdr::cstln_lut::QAM16; break; case QAM64: - m_objCfg.constellation = leansdr::cstln_lut<256>::QAM64; + m_objCfg.constellation = leansdr::cstln_lut::QAM64; break; case QAM256: - m_objCfg.constellation = leansdr::cstln_lut<256>::QAM256; + m_objCfg.constellation = leansdr::cstln_lut::QAM256; break; default: - m_objCfg.constellation = leansdr::cstln_lut<256>::BPSK; + m_objCfg.constellation = leansdr::cstln_lut::BPSK; break; } @@ -648,7 +648,7 @@ void DATVDemod::InitDATVFramework() // Generic constellation receiver - p_symbols = new leansdr::pipebuf(m_objScheduler, "PSK soft-symbols", BUF_SYMBOLS); + p_symbols = new leansdr::pipebuf(m_objScheduler, "PSK soft-symbols", BUF_SYMBOLS); p_freq = new leansdr::pipebuf (m_objScheduler, "freq", BUF_SLOW); p_ss = new leansdr::pipebuf (m_objScheduler, "SS", BUF_SLOW); p_mer = new leansdr::pipebuf (m_objScheduler, "MER", BUF_SLOW); @@ -682,7 +682,7 @@ void DATVDemod::InitDATVFramework() return; } - m_objDemodulator = new leansdr::cstln_receiver( + m_objDemodulator = new leansdr::cstln_receiver( m_objScheduler, sampler, *p_preprocessed, @@ -694,8 +694,8 @@ void DATVDemod::InitDATVFramework() if (m_objCfg.standard == DVB_S) { - if ( m_objCfg.constellation != leansdr::cstln_lut<256>::QPSK - && m_objCfg.constellation != leansdr::cstln_lut<256>::BPSK ) + if ( m_objCfg.constellation != leansdr::cstln_lut::QPSK + && m_objCfg.constellation != leansdr::cstln_lut::BPSK ) { qWarning("DATVDemod::InitDATVFramework: non-standard constellation for DVB-S"); } diff --git a/plugins/channelrx/demoddatv/datvdemod.h b/plugins/channelrx/demoddatv/datvdemod.h index 5473fab04..4b3dc885e 100644 --- a/plugins/channelrx/demoddatv/datvdemod.h +++ b/plugins/channelrx/demoddatv/datvdemod.h @@ -25,11 +25,7 @@ class DownChannelizer; #define rfFilterFftLength 1024 -#ifndef LEANSDR_FRAMEWORK -#define LEANSDR_FRAMEWORK - //LeanSDR - #include "leansdr/framework.h" #include "leansdr/generic.h" #include "leansdr/dsp.h" @@ -41,10 +37,10 @@ class DownChannelizer; #include "leansdr/hdlc.h" #include "leansdr/iess.h" -#endif - #include "datvconstellation.h" #include "datvvideoplayer.h" +#include "datvideostream.h" +#include "datvideorender.h" #include "channel/channelsinkapi.h" #include "dsp/basebandsamplesink.h" @@ -60,9 +56,6 @@ class DownChannelizer; #include "util/message.h" #include "util/movingaverage.h" -#include "datvideostream.h" -#include "datvideorender.h" - #include enum DATVModulation { BPSK, QPSK, PSK8, APSK16, APSK32, APSK64E, QAM16, QAM64, QAM256 }; @@ -83,7 +76,7 @@ struct config bool cnr; // Measure CNR unsigned int decim; // Decimation, 0=auto float Fm; // QPSK symbol rate (Hz) - leansdr::cstln_lut<256>::predef constellation; + leansdr::cstln_lut::predef constellation; leansdr::code_rate fec; float Ftune; // Bias frequency for the QPSK demodulator (Hz) bool allow_drift; @@ -109,7 +102,7 @@ struct config cnr(false), decim(0), Fm(2e6), - constellation(leansdr::cstln_lut<256>::QPSK), + constellation(leansdr::cstln_lut::QPSK), fec(leansdr::FEC12), Ftune(0), allow_drift(false), @@ -323,7 +316,7 @@ private: }; unsigned long m_lngExpectedReadIQ; - unsigned long m_lngReadIQ; + long m_lngReadIQ; //************** LEANDBV Parameters ************** @@ -372,7 +365,7 @@ private: float *coeffs_sampler; int ncoeffs_sampler; - leansdr::pipebuf *p_symbols; + leansdr::pipebuf *p_symbols; leansdr::pipebuf *p_freq; leansdr::pipebuf *p_ss; leansdr::pipebuf *p_mer; @@ -386,7 +379,7 @@ private: leansdr::file_writer *r_ppout; //GENERIC CONSTELLATION RECEIVER - leansdr::cstln_receiver *m_objDemodulator; + leansdr::cstln_receiver *m_objDemodulator; // DECONVOLUTION AND SYNCHRONIZATION leansdr::pipebuf *p_bytes; diff --git a/plugins/channelrx/demoddatv/datvdemodgui.cpp b/plugins/channelrx/demoddatv/datvdemodgui.cpp index fd91cc2de..9a332e719 100644 --- a/plugins/channelrx/demoddatv/datvdemodgui.cpp +++ b/plugins/channelrx/demoddatv/datvdemodgui.cpp @@ -21,7 +21,7 @@ #include #include "datvdemodgui.h" -#include "datvideostream.h" +//#include "datvideostream.h" #include "device/devicesourceapi.h" #include "device/deviceuiset.h" diff --git a/plugins/channelrx/demoddatv/leansdr/bch.h b/plugins/channelrx/demoddatv/leansdr/bch.h new file mode 100644 index 000000000..aab86547a --- /dev/null +++ b/plugins/channelrx/demoddatv/leansdr/bch.h @@ -0,0 +1,250 @@ +// This file is part of LeanSDR Copyright (C) 2016-2018 . +// See the toplevel README for more information. +// +// This program 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. +// +// This program 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 this program. If not, see . + +#ifndef LEANSDR_BCH_H +#define LEANSDR_BCH_H + +#include "leansdr/discrmath.h" + +namespace leansdr +{ + +// Interface to hide the template parameters + +struct bch_interface +{ + virtual void encode(const uint8_t *msg, size_t msgbytes, uint8_t *out) = 0; + virtual int decode(uint8_t *cw, size_t cwbytes) = 0; +}; // bch_interface + +// BCH error correction. +// T: Unsigned type for packing binary polynomials. +// N: Number of parity bits. +// NP: Width of the polynomials supplied. +// DP: Actual degree of the minimum polynomials (all must be same). +// TGF: Unsigned type for syndromes (must be wider than DP). +// GFTRUNCGEN: Generator polynomial for GF(2^DP), with X^DP omitted. + +template +struct bch_engine : bch_interface +{ + bch_engine(const bitvect *polys, int _npolys) + : npolys(_npolys) + { + // Build the generator polynomial (product of polys[]). + g = 1; + for (int i = 0; i < npolys; ++i) + g = g * polys[i]; + // Convert the polynomials to truncated representation + // (with X^DP omitted) for use with divmod(). + truncpolys = new bitvect[npolys]; + for (int i = 0; i < npolys; ++i) + truncpolys[i].copy(polys[i]); + // Check which polynomial contains each root. + // Note: The DVB-S2 polynomials are numbered so that + // syndpoly[2*i]==i, but we don't use that property. + syndpolys = new int[2 * npolys]; + for (int i = 0; i < 2 * npolys; ++i) + { + int j; + for (j = 0; j < npolys; ++j) + if (!eval_poly(truncpolys[j], true, 1 + i)) + break; + if (j == npolys) + fail("Bad polynomials/root"); + syndpolys[i] = j; + } + } + + // Generate BCH parity bits. + + void encode(const uint8_t *msg, size_t msgbytes, uint8_t *out) + { + bitvect parity = shiftdivmod(msg, msgbytes, g); + // Output as bytes, coefficient of highest degree first + for (int i = N / 8; i--; ++out) + *out = parity.v[i / sizeof(T)] >> ((i & (sizeof(T) - 1)) * 8); + } + + // Decode BCH. + // Return number of bits corrected, or -1 on failure. + + int decode(uint8_t *cw, size_t cwbytes) + { + //again: + bool corrupted = false; + // Divide by individual polynomials. + // TBD Maybe do in parallel, scanning cw only once. + bitvect rem[npolys]; + for (int j = 0; j < npolys; ++j) + { + rem[j] = divmod(cw, cwbytes, truncpolys[j]); + } + // Compute syndromes. + TGF S[2 * npolys]; + for (int i = 0; i < 2 * npolys; ++i) + { + // Compute R(alpha^(1+i)), exploiting the fact that + // R(x)=Q(x)g_j(X)+rem_j(X) and g_j(alpha^(1+i))=0 + // for some j that we already determined. + // TBD Compute even exponents using conjugates. + S[i] = eval_poly(rem[syndpolys[i]], false, 1 + i); + if (S[i]) + corrupted = true; + } + if (!corrupted) + return 0; +#if 0 + fprintf(stderr, "synd:"); + for ( int i=0; i<2*npolys; ++i ) fprintf(stderr, " %04x", S[i]); + fprintf(stderr, "\n"); +#endif + + // S_j = R(alpha_j) = 0+E(alpha_j) = sum(l=1..L)((alpha^j)^i_l) + // where i_1 .. i_L are the degrees of the non-zero coefficients of E. + // S_j = sum(l=1..L)((alpha^i_l)^j) = sum(l=1..L)(X_l^j) + + // Berlekamp - Massey + // http://en.wikipedia.org/wiki/Berlekamp%E2%80%93Massey_algorithm + // TBD More efficient to work with logs of syndromes ? + + int NN = 2 * npolys; + TGF C[NN] = { + 1, + }, + B[NN] = { + 1, + }; + int L = 0, m = 1; + TGF b = 1; + for (int n = 0; n < NN; ++n) + { + TGF d = S[n]; + for (int i = 1; i <= L; ++i) + d = GF.add(d, GF.mul(C[i], S[n - i])); + if (d == 0) + ++m; + else + { + TGF d_div_b = GF.mul(d, GF.inv(b)); + if (2 * L <= n) + { + TGF tmp[NN]; + memcpy(tmp, C, sizeof(tmp)); + for (int i = 0; i < NN - m; ++i) + C[m + i] = GF.sub(C[m + i], GF.mul(d_div_b, B[i])); + L = n + 1 - L; + memcpy(B, tmp, sizeof(B)); + b = d; + m = 1; + } + else + { + for (int i = 0; i < NN - m; ++i) + C[m + i] = GF.sub(C[m + i], GF.mul(d_div_b, B[i])); + ++m; + } + } + } + // L is the number of errors. + // C of degree L is the error locator polynomial (Lambda). + // C(X) = sum(l=1..L)(1-X_l*X). +#if 0 + fprintf(stderr, "C[%d]=", L); + for ( int i=0; i> (rloc & 7); + ++roots_found; + if (roots_found == L) + break; + } + } + if (roots_found != L) + return -1; + return L; + } + + private: + // Eval a GF(2)[X] polynomial at a power of ALPHA. + + TGF eval_poly(const bitvect &poly, bool is_trunc, int rootexp) + { + TGF acc = 0; + int re = 0; + for (int i = 0; i < DP; ++i) + { + if (poly[i]) + acc = GF.add(acc, GF.exp(re)); + re += rootexp; + if (re >= (1 << DP) - 1) + re -= (1 << DP) - 1; // mod 2^DP-1 incrementally + } + if (is_trunc) + acc = GF.add(acc, GF.exp(re)); + return acc; + } + + // Eval a GF(2^16)[X] polynomial at a power of ALPHA. + + TGF eval_poly(const TGF *poly, int deg, int rootexp) + { + TGF acc = 0; + int re = 0; + for (int i = 0; i <= deg; ++i) + { + acc = GF.add(acc, GF.mul(poly[i], GF.exp(re))); + re += rootexp; + if (re >= (1 << DP) - 1) + re -= (1 << DP) - 1; // mod 2^DP-1 incrementally + } + return acc; + } + + bitvect *truncpolys; + int npolys; + int *syndpolys; + bitvect g; + // Finite group for syndrome calculations + gf2n GF; +}; // bch_engine + +} // namespace leansdr + +#endif // LEANSDR_BCH_H diff --git a/plugins/channelrx/demoddatv/leansdr/convolutional.h b/plugins/channelrx/demoddatv/leansdr/convolutional.h index f103fc420..387163873 100644 --- a/plugins/channelrx/demoddatv/leansdr/convolutional.h +++ b/plugins/channelrx/demoddatv/leansdr/convolutional.h @@ -1,6 +1,25 @@ +// This file is part of LeanSDR Copyright (C) 2016-2018 . +// See the toplevel README for more information. +// +// This program 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. +// +// This program 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 this program. If not, see . + #ifndef LEANSDR_CONVOLUTIONAL_H #define LEANSDR_CONVOLUTIONAL_H +#include "framework.h" +#include "sdr.h" + namespace leansdr { @@ -10,30 +29,21 @@ namespace leansdr // This is a straightforward implementation, provided for reference. // deconvol_poly2 is functionally equivalent and much faster. -template +template struct deconvol_poly { typedef u8 hardsymbol; // Support soft of float input - inline u8 SYMVAL(const hardsymbol *s) - { - return *s; - } - inline u8 SYMVAL(const softsymbol *s) - { - return s->symbol; - } + inline u8 SYMVAL(const hardsymbol *s) { return *s; } + inline u8 SYMVAL(const softsymbol *s) { return s->symbol; } typedef u8 decoded_byte; - deconvol_poly() : - hist(0) - { - } + deconvol_poly() : hist(0) {} // Remap and deconvolve [nb*8] symbols into [nb] bytes. // Return estimated number of bit errors. @@ -42,9 +52,11 @@ struct deconvol_poly { int nerrors = 0; int halfway = nb / 2; + for (; nb--; ++pout) { decoded_byte byte = 0; + for (int bit = 8; bit--; ++pin) { hist = (hist << 2) | remap[SYMVAL(pin)]; @@ -52,16 +64,17 @@ struct deconvol_poly if (nb < halfway) nerrors += parity(hist & POLY_ERRORS); } + *pout = byte; } + return nerrors; } -private: + private: Thist hist; -}; -// deconvol_poly +}; // deconvol_poly // ALGEBRAIC DECONVOLUTION, OPTIMIZED @@ -69,223 +82,186 @@ private: // Functionally equivalent to deconvol_poly, // but processing 32 bits in parallel. -template +template struct deconvol_poly2 { typedef u8 hardsymbol; // Support instanciation of template with soft of float input - inline u8 SYMVAL(const hardsymbol *s) - { - return *s; - } - inline u8 SYMVAL(const softsymbol *s) - { - return s->symbol; - } + inline u8 SYMVAL(const hardsymbol *s) { return *s; } + inline u8 SYMVAL(const softsymbol *s) { return s->symbol; } typedef u8 decoded_byte; - deconvol_poly2() : - inI(0), inQ(0) - { - } + deconvol_poly2() : inI(0), inQ(0) {} // Remap and deconvolve [nb*8] symbols into [nb] bytes. - // Return estimated number of bit errors or -1 if error. + // Return estimated number of bit errors. int run(const Tin *pin, const u8 remap[], decoded_byte *pout, int nb) { if (nb & (sizeof(Thist) - 1)) { - fail("deconvol_poly2::run", "Must deconvolve sizeof(Thist) bytes at a time"); - return -1; + fail("Must deconvolve sizeof(Thist) bytes at a time"); } + nb /= sizeof(Thist); unsigned long nerrors = 0; int halfway = nb / 2; Thist histI = inI, histQ = inQ; + for (; nb--;) { // This is where we convolve bits in parallel. - Thist wd = 0; // decoded bits - Thist we = 0; // error bits (should be 0) + Thist wd = 0; // decoded bits + Thist we = 0; // error bits (should be 0) #if 0 - // Trust gcc to unroll and evaluate the bit tests at compile-time. - for ( int bit=sizeof(Thist)*8; bit--; ++pin ) - { - u8 iq = remap[SYMVAL(pin)]; - histI = (histI<<1) | (iq>>1); - histQ = (histQ<<1) | (iq&1); - if ( POLY_DECONVOL & ((Tpoly)2<<(2*bit)) ) wd ^= histI; - if ( POLY_DECONVOL & ((Tpoly)1<<(2*bit)) ) wd ^= histQ; - if ( POLY_ERRORS & ((Tpoly)2<<(2*bit)) ) we ^= histI; - if ( POLY_ERRORS & ((Tpoly)1<<(2*bit)) ) we ^= histQ; - } + // Trust gcc to unroll and evaluate the bit tests at compile-time. + for ( int bit=sizeof(Thist)*8; bit--; ++pin ) { + u8 iq = remap[SYMVAL(pin)]; + histI = (histI<<1) | (iq>>1); + histQ = (histQ<<1) | (iq&1); + if ( POLY_DECONVOL & ((Tpoly)2<<(2*bit)) ) wd ^= histI; + if ( POLY_DECONVOL & ((Tpoly)1<<(2*bit)) ) wd ^= histQ; + if ( POLY_ERRORS & ((Tpoly)2<<(2*bit)) ) we ^= histI; + if ( POLY_ERRORS & ((Tpoly)1<<(2*bit)) ) we ^= histQ; + } #else // Unroll manually. -#define LOOP(bit) { \ - u8 iq = remap[SYMVAL(pin)]; \ - histI = (histI<<1) | (iq>>1); \ - histQ = (histQ<<1) | (iq&1); \ - if ( POLY_DECONVOL & ((Tpoly)2<<(2*bit)) ) wd ^= histI; \ - if ( POLY_DECONVOL & ((Tpoly)1<<(2*bit)) ) wd ^= histQ; \ - if ( POLY_ERRORS & ((Tpoly)2<<(2*bit)) ) we ^= histI; \ - if ( POLY_ERRORS & ((Tpoly)1<<(2*bit)) ) we ^= histQ; \ - ++pin; \ - } +#define LOOP(bit) \ + { \ + u8 iq = remap[SYMVAL(pin)]; \ + histI = (histI << 1) | (iq >> 1); \ + histQ = (histQ << 1) | (iq & 1); \ + if (POLY_DECONVOL & ((Tpoly)2 << (2 * bit))) \ + wd ^= histI; \ + if (POLY_DECONVOL & ((Tpoly)1 << (2 * bit))) \ + wd ^= histQ; \ + if (POLY_ERRORS & ((Tpoly)2 << (2 * bit))) \ + we ^= histI; \ + if (POLY_ERRORS & ((Tpoly)1 << (2 * bit))) \ + we ^= histQ; \ + ++pin; \ + } // Don't shift by more than the operand width - switch (sizeof(Thist) /* 8*/) + switch (sizeof(Thist) * 8) { -#if 0 // Not needed yet - avoid compiler warnings - case 8: - LOOP(63); LOOP(62); LOOP(61); LOOP(60); - LOOP(59); LOOP(58); LOOP(57); LOOP(56); - LOOP(55); LOOP(54); LOOP(53); LOOP(52); - LOOP(51); LOOP(50); LOOP(49); LOOP(48); - LOOP(47); LOOP(46); LOOP(45); LOOP(44); - LOOP(43); LOOP(42); LOOP(41); LOOP(40); - LOOP(39); LOOP(38); LOOP(37); LOOP(36); - LOOP(35); LOOP(34); LOOP(33); LOOP(32); - // Fall-through +#if 0 // Not needed yet - avoid compiler warnings + case 64: + LOOP(63); LOOP(62); LOOP(61); LOOP(60); + LOOP(59); LOOP(58); LOOP(57); LOOP(56); + LOOP(55); LOOP(54); LOOP(53); LOOP(52); + LOOP(51); LOOP(50); LOOP(49); LOOP(48); + LOOP(47); LOOP(46); LOOP(45); LOOP(44); + LOOP(43); LOOP(42); LOOP(41); LOOP(40); + LOOP(39); LOOP(38); LOOP(37); LOOP(36); + LOOP(35); LOOP(34); LOOP(33); LOOP(32); + // Fall-through #endif - case 4: - LOOP(31) - ; - LOOP(30) - ; - LOOP(29) - ; - LOOP(28) - ; - LOOP(27) - ; - LOOP(26) - ; - LOOP(25) - ; - LOOP(24) - ; - LOOP(23) - ; - LOOP(22) - ; - LOOP(21) - ; - LOOP(20) - ; - LOOP(19) - ; - LOOP(18) - ; - LOOP(17) - ; - LOOP(16) - ; + case 32: + LOOP(31); + LOOP(30); + LOOP(29); + LOOP(28); + LOOP(27); + LOOP(26); + LOOP(25); + LOOP(24); + LOOP(23); + LOOP(22); + LOOP(21); + LOOP(20); + LOOP(19); + LOOP(18); + LOOP(17); + LOOP(16); // Fall-through - case 2: - LOOP(15) - ; - LOOP(14) - ; - LOOP(13) - ; - LOOP(12) - ; - LOOP(11) - ; - LOOP(10) - ; - LOOP(9) - ; - LOOP(8) - ; + case 16: + LOOP(15); + LOOP(14); + LOOP(13); + LOOP(12); + LOOP(11); + LOOP(10); + LOOP(9); + LOOP(8); // Fall-through - case 1: - LOOP(7) - ; - LOOP(6) - ; - LOOP(5) - ; - LOOP(4) - ; - LOOP(3) - ; - LOOP(2) - ; - LOOP(1) - ; - LOOP(0) - ; + case 8: + LOOP(7); + LOOP(6); + LOOP(5); + LOOP(4); + LOOP(3); + LOOP(2); + LOOP(1); + LOOP(0); break; default: - fail("deconvol_poly2::run", "Thist not supported (1)"); - return -1; + fail("Thist not supported"); } #undef LOOP #endif - switch (sizeof(Thist) /* 8*/) + switch (sizeof(Thist) * 8) { -#if 0 // Not needed yet - avoid compiler warnings - case 8: - *pout++ = wd >> 56; - *pout++ = wd >> 48; - *pout++ = wd >> 40; - *pout++ = wd >> 32; - // Fall-through +#if 0 // Not needed yet - avoid compiler warnings + case 64: + *pout++ = wd >> 56; + *pout++ = wd >> 48; + *pout++ = wd >> 40; + *pout++ = wd >> 32; + // Fall-through #endif - case 4: + case 32: *pout++ = wd >> 24; *pout++ = wd >> 16; // Fall-through - case 2: + case 16: *pout++ = wd >> 8; // Fall-through - case 1: + case 8: *pout++ = wd; break; default: - fail("deconvol_poly2::run", "Thist not supported (2)"); - return -1; + fail("Thist not supported"); } // Count errors when the shift registers are full if (nb < halfway) nerrors += hamming_weight(we); } + inI = histI; inQ = histQ; return nerrors; } -private: + + private: Thist inI, inQ; -}; -// deconvol_poly2 +}; // deconvol_poly2 // CONVOLUTIONAL ENCODER // QPSK 1/2 only. -template +template struct convol_poly2 { typedef u8 uncoded_byte; typedef u8 hardsymbol; - convol_poly2() : - hist(0) - { - } + convol_poly2() : hist(0) {} // Convolve [count] bytes into [count*8] symbols, and remap. - void run(const uncoded_byte *pin, const u8 remap[], hardsymbol *pout, int count) + void run(const uncoded_byte *pin, const u8 remap[], + hardsymbol *pout, int count) { for (; count--; ++pin) { uncoded_byte b = *pin; + for (int bit = 8; bit--; ++pout) { hist = (hist >> 1) | (((b >> bit) & 1) << 6); @@ -294,50 +270,55 @@ struct convol_poly2 } } } -private: + + private: Thist hist; -}; -// convol_poly2 +}; // convol_poly2 // Generic BPSK..256QAM and puncturing -template +template struct convol_multipoly { typedef u8 uncoded_byte; typedef u8 hardsymbol; int bits_in, bits_out, bps; - const Thist *polys; // [bits_out] + const Thist *polys; // [bits_out] - convol_multipoly() : - bits_in(0), bits_out(0), bps(0), polys(0), hist(0), nhist(0), sersymb(0), nsersymb(0) + convol_multipoly() + : bits_in(0), bits_out(0), bps(0), + hist(0), nhist(0), sersymb(0), nsersymb(0) { } void encode(const uncoded_byte *pin, hardsymbol *pout, int count) { - if (!bits_in || !bits_out || !bps) - { - fatal("leansdr::convol_multipoly::encode: convol_multipoly not configured"); - return; + if (!bits_in || !bits_out || !bps) { + fatal("convol_multipoly not configured"); } + hardsymbol symbmask = (1 << bps) - 1; + for (; count--; ++pin) { uncoded_byte b = *pin; + for (int bit = 8; bit--;) { - hist = (hist >> 1) | ((Thist) ((b >> bit) & 1) << (HISTSIZE - 1)); + hist = (hist >> 1) | ((Thist)((b >> bit) & 1) << (HISTSIZE - 1)); ++nhist; + if (nhist == bits_in) { for (int p = 0; p < bits_out; ++p) { - int b = parity((Thist) (hist & polys[p])); + int b = parity((Thist)(hist & polys[p])); sersymb = (sersymb << 1) | b; } + nhist = 0; nsersymb += bits_out; + while (nsersymb >= bps) { hardsymbol s = (sersymb >> (nsersymb - bps)) & symbmask; @@ -350,17 +331,17 @@ struct convol_multipoly // Ensure deterministic output size // TBD We can relax this if (nhist || nsersymb) { - fatal("leansdr::convol_multipoly::encode: partial run"); + fatal("partial run"); } } -private: + + private: Thist hist; int nhist; Thist sersymb; int nsersymb; -}; -// convol_multipoly +}; // convol_multipoly -}// namespace +} // namespace leansdr -#endif // LEANSDR_CONVOLUTIONAL_H +#endif // LEANSDR_CONVOLUTIONAL_H diff --git a/plugins/channelrx/demoddatv/leansdr/crc.h b/plugins/channelrx/demoddatv/leansdr/crc.h new file mode 100644 index 000000000..0b1d751d3 --- /dev/null +++ b/plugins/channelrx/demoddatv/leansdr/crc.h @@ -0,0 +1,73 @@ +// This file is part of LeanSDR Copyright (C) 2016-2018 . +// See the toplevel README for more information. +// +// This program 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. +// +// This program 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 this program. If not, see . + +#ifndef LEANSDR_CRC_H +#define LEANSDR_CRC_H + +#include + +#include "leansdr/discrmath.h" + +namespace leansdr +{ + +// EN 302 307-1 section 5.1.4 CRC-8 encoder + +struct crc8_engine +{ + crc8_engine() + { + // Precompute + // EN 302 307-1 5.1.4 Figure 2 + bitvect g = POLY_DVBS2_CRC8; + for (int u = 0; u < 256; ++u) + { + uint8_t u8 = u; + bitvect c = shiftdivmod(&u8, 1, g); + table[u] = c.v[0]; + } + } + uint8_t compute(const uint8_t *buf, int len) + { + uint8_t c = 0; + for (; len--; ++buf) + c = table[c ^ *buf]; + return c; + } + + private: + static const uint8_t POLY_DVBS2_CRC8 = 0xd5; // (1)11010101 + uint8_t table[256]; +}; + +// CRC-32 ITU V.42 for FINGERPRINT + +uint32_t crc32(const uint8_t *buf, int len) +{ + static const uint32_t poly = 0xedb88320; + uint32_t c = 0xffffffff; + for (int i = 0; i < len; ++i) + { + c ^= buf[i]; + for (int bit = 8; bit--;) + c = (c & 1) ? (c >> 1) ^ poly : (c >> 1); + } + return c ^ 0xffffffff; +} + +} // namespace leansdr + +#endif // LEANSDR_CRC_H diff --git a/plugins/channelrx/demoddatv/leansdr/discrmath.h b/plugins/channelrx/demoddatv/leansdr/discrmath.h new file mode 100644 index 000000000..ec25568fc --- /dev/null +++ b/plugins/channelrx/demoddatv/leansdr/discrmath.h @@ -0,0 +1,268 @@ +// This file is part of LeanSDR Copyright (C) 2018 . +// See the toplevel README for more information. +// +// This program 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. +// +// This program 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 this program. If not, see . + +#ifndef LEANSDR_DISCRMATH_H +#define LEANSDR_DISCRMATH_H + +#include + +namespace leansdr +{ + +// Polynomials with N binary coefficients. +// This is designed to compile into trivial inline code. + +// Bits are packed into words of type T. +// Unused most-significant bits of the last word are 0. +// T must be an unsigned type. + +template +struct bitvect +{ + static const size_t WSIZE = sizeof(T) * 8; + static const size_t NW = (N + WSIZE - 1) / WSIZE; + T v[NW]; + + bitvect() {} + bitvect(T val) + { + v[0] = val; + for (int i = 1; i < NW; ++i) + v[i] = 0; + } + + // Assign from another bitvect, with truncation or padding + template + bitvect ©(const bitvect &a) + { + int nw = (a.NW < NW) ? a.NW : NW; + for (int i = 0; i < nw; ++i) + v[i] = a.v[i]; + if (M < N) + for (int i = a.NW; i < NW; ++i) + v[i] = 0; + if (M > N) + truncate_to_N(); + return *this; + } + + bool operator[](unsigned int i) const + { + return v[i / WSIZE] & ((T)1 << (i & (WSIZE - 1))); + } + + // Add (in GF(2)) + + template + bitvect &operator+=(const bitvect &a) + { + int nw = (a.NW < NW) ? a.NW : NW; + for (int i = 0; i < nw; ++i) + v[i] ^= a.v[i]; + if (M > N) + truncate_to_N(); + return *this; + } + + // Multiply by X^n + + bitvect &operator<<=(unsigned int n) + { + if (n >= WSIZE) + fail("shift exceeds word width"); + T mask = ~(((T)-1) << n); + for (int i = NW - 1; i > 0; --i) + v[i] = (v[i] << n) | ((v[i - 1] >> (WSIZE - n)) & mask); + v[0] <<= n; + if (N != NW * WSIZE) + truncate_to_N(); + return *this; + } + + private: + // Call this whenever bits N .. NW*WSIZE-1 may have been set. + inline void truncate_to_N() + { + v[NW - 1] &= ((T)-1) >> (NW * WSIZE - N); + } + +}; // bitvect + +// Return m modulo (X^N+p). +// p is typically a generator polynomial of degree N +// with the highest-coefficient monomial ommitted. +// m is packed into nm words of type Tm. +// The MSB of m[0] is the highest-order coefficient of m. + +template +bitvect divmod(const Tm *m, size_t nm, const bitvect &p) +{ + bitvect res = 0; + const Tm bitmask = (Tm)1 << (sizeof(Tm) * 8 - 1); + for (; nm--; ++m) + { + Tm mi = *m; + for (int bit = sizeof(Tm) * 8; bit--; mi <<= 1) + { + // Multiply by X, save outgoing coeff of degree N + bool resN = res[N - 1]; + res <<= 1; + // Add m[i] + if (mi & bitmask) + res.v[0] ^= 1; + // Modulo X^N+p + if (resN) + res += p; + } + } + return res; +} + +// Return (m*X^N) modulo (X^N+p). +// Same as divmod(), slightly faster than appending N zeroes. + +template +bitvect shiftdivmod(const Tm *m, size_t nm, const bitvect &p, + T init = 0) +{ + bitvect res; + for (int i = 0; i < res.NW; ++i) + res.v[i] = init; + const Tm bitmask = (Tm)1 << (sizeof(Tm) * 8 - 1); + for (; nm--; ++m) + { + Tm mi = *m; + for (int bit = sizeof(Tm) * 8; bit--; mi <<= 1) + { + // Multiply by X, save outgoing coeff of degree N + bool resN = res[N - 1]; + res <<= 1; + // Add m[i]*X^N + resN ^= (bool)(mi & bitmask); + // Modulo X^N+p + if (resN) + res += p; + } + } + return res; +} + +template +bool operator==(const bitvect &a, const bitvect &b) +{ + for (int i = 0; i < a.NW; ++i) + if (a.v[i] != b.v[i]) + return false; + return true; +} + +// Add (in GF(2)) + +template +bitvect operator+(const bitvect &a, const bitvect &b) +{ + bitvect res; + for (int i = 0; i < a.NW; ++i) + res.v[i] = a.v[i] ^ b.v[i]; + return res; +} + +// Polynomial multiplication. + +template +bitvect operator*(bitvect a, const bitvect &b) +{ + bitvect res = 0; + for (int i = 0; i < NB; ++i, a <<= 1) + if (b[i]) + res += a; + // TBD If truncation is needed, do it only once at the end. + return res; +} + +// Finite group GF(2^N), for small N. + +// GF(2) is the ring ({0,1},+,*). +// GF(2)[X] is the ring of polynomials with coefficients in GF(2). +// P(X) is an irreducible polynomial of GF(2)[X]. +// N is the degree of P(x). +// GF(2)[X]/(P) is GF(2)[X] modulo P(X). +// (GF(2)[X]/(P), +) is a group with 2^N elements. +// (GF(2)[X]/(P)*, *) is a group with 2^N-1 elements. +// (GF(2)[X]/(P), +, *) is a field with 2^N elements, noted GF(2^N). + +// Te is a C++ integer type for encoding elements of GF(2^N). +// Binary coefficients are packed, with degree 0 at LSB. +// (Te)0 is 0 +// (Te)1 is 1 +// (Te)2 is X +// (Te)3 is X+1 +// (Te)4 is X^2 +// TRUNCP is the encoding of the generator, with highest monomial omitted. +// ALPHA is a primitive element of GF(2^N). Usually "2"=[X] is chosen. + +template +struct gf2n +{ + typedef Te element; + static const Te alpha = ALPHA; + gf2n() + { + if (ALPHA != 2) + fail("alpha!=2 not implemented"); + // Precompute log and exp tables. + Te alpha_i = 1; // ALPHA^0 + for (int i = 0; i < (1 << N); ++i) + { + lut_exp[i] = alpha_i; // ALPHA^i + lut_exp[((1 << N) - 1) + i] = alpha_i; // Wrap to avoid modulo 2^N-1 + lut_log[alpha_i] = i; + bool overflow = alpha_i & (1 << (N - 1)); + alpha_i <<= 1; // Multiply by alpha=[X] i.e. increase degrees + alpha_i &= ~((~(Te)0) << N); // In case Te is wider than N bits + if (overflow) + alpha_i ^= TRUNCP; // Modulo P iteratively + } + } + inline Te add(Te x, Te y) { return x ^ y; } // Addition modulo 2 + inline Te sub(Te x, Te y) { return x ^ y; } // Subtraction modulo 2 + inline Te mul(Te x, Te y) + { + if (!x || !y) + return 0; + return lut_exp[lut_log[x] + lut_log[y]]; + } + inline Te div(Te x, Te y) + { + if (!x) + return 0; + return lut_exp[lut_log[x] + ((1 << N) - 1) - lut_log[y]]; + } + inline Te inv(Te x) + { + return lut_exp[((1 << N) - 1) - lut_log[x]]; + } + inline Te exp(Te x) { return lut_exp[x]; } + inline Te log(Te x) { return lut_log[x]; } + + private: + Te lut_exp[(1 << N) * 2]; // Extra room for wrapping modulo 2^N-1 + Te lut_log[1 << N]; +}; // gf2n + +} // namespace leansdr + +#endif // LEANSDR_DISCRMATH_H diff --git a/plugins/channelrx/demoddatv/leansdr/dsp.h b/plugins/channelrx/demoddatv/leansdr/dsp.h index 6067ee7a0..9a51f2c9b 100644 --- a/plugins/channelrx/demoddatv/leansdr/dsp.h +++ b/plugins/channelrx/demoddatv/leansdr/dsp.h @@ -1,9 +1,25 @@ +// This file is part of LeanSDR Copyright (C) 2016-2018 . +// See the toplevel README for more information. +// +// This program 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. +// +// This program 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 this program. If not, see . + #ifndef LEANSDR_DSP_H #define LEANSDR_DSP_H -#include #include "leansdr/framework.h" #include "leansdr/math.h" +#include namespace leansdr { @@ -14,15 +30,15 @@ namespace leansdr // [cconverter] converts complex streams between numric types, // with optional ofsetting and rational scaling. -template -struct cconverter: runnable +template +struct cconverter : runnable { - cconverter(scheduler *sch, pipebuf > &_in, - pipebuf > &_out) : - runnable(sch, "cconverter"), in(_in), out(_out) + cconverter(scheduler *sch, pipebuf> &_in, + pipebuf> &_out) + : runnable(sch, "cconverter"), + in(_in), out(_out) { } - void run() { unsigned long count = min(in.readable(), out.writable()); @@ -30,25 +46,23 @@ struct cconverter: runnable complex *pout = out.wr(); for (; pin < pend; ++pin, ++pout) { - pout->re = Zout + (pin->re - (Tin) Zin) * Gn / Gd; - pout->im = Zout + (pin->im - (Tin) Zin) * Gn / Gd; + pout->re = Zout + (pin->re - (Tin)Zin) * Gn / Gd; + pout->im = Zout + (pin->im - (Tin)Zin) * Gn / Gd; } in.read(count); out.written(count); } -private: - pipereader > in; - pipewriter > out; + private: + pipereader> in; + pipewriter> out; }; -template +template struct cfft_engine { - const unsigned int n; - - cfft_engine(unsigned int _n) : - n(_n), invsqrtn(1.0 / sqrt(n)) + const int n; + cfft_engine(int _n) : n(_n), invsqrtn(1.0 / sqrt(n)) { // Compute log2(n) logn = 0; @@ -56,29 +70,28 @@ struct cfft_engine ++logn; // Bit reversal bitrev = new int[n]; - for (unsigned int i = 0; i < n; ++i) + for (int i = 0; i < n; ++i) { bitrev[i] = 0; for (int b = 0; b < logn; ++b) bitrev[i] = (bitrev[i] << 1) | ((i >> b) & 1); } // Float constants - omega = new complex [n]; - omega_rev = new complex [n]; - for (unsigned int i = 0; i < n; ++i) + omega = new complex[n]; + omega_rev = new complex[n]; + for (int i = 0; i < n; ++i) { float a = 2.0 * M_PI * i / n; omega_rev[i].re = (omega[i].re = cosf(a)); omega_rev[i].im = -(omega[i].im = sinf(a)); } } - void inplace(complex *data, bool reverse = false) { // Bit-reversal permutation - for (unsigned int i = 0; i < n; ++i) + for (int i = 0; i < n; ++i) { - unsigned int r = bitrev[i]; + int r = bitrev[i]; if (r < i) { complex tmp = data[i]; @@ -100,7 +113,7 @@ struct cfft_engine complex &w = om[k * dom]; complex &dqk = data[q + k]; complex x(w.re * dqk.re - w.im * dqk.im, - w.re * dqk.im + w.im * dqk.re); + w.re * dqk.im + w.im * dqk.re); data[q + k].re = data[p + k].re - x.re; data[q + k].im = data[p + k].im - x.im; data[p + k].re = data[p + k].re + x.re; @@ -111,7 +124,7 @@ struct cfft_engine if (reverse) { float invn = 1.0 / n; - for (unsigned int i = 0; i < n; ++i) + for (int i = 0; i < n; ++i) { data[i].re *= invn; data[i].im *= invn; @@ -119,24 +132,22 @@ struct cfft_engine } } -private: + private: int logn; int *bitrev; complex *omega, *omega_rev; float invsqrtn; }; -template -struct adder: runnable +template +struct adder : runnable { - adder(scheduler *sch, pipebuf &_in1, pipebuf &_in2, pipebuf &_out) : - runnable(sch, "adder"), - in1(_in1), - in2(_in2), - out(_out) + adder(scheduler *sch, + pipebuf &_in1, pipebuf &_in2, pipebuf &_out) + : runnable(sch, "adder"), + in1(_in1), in2(_in2), out(_out) { } - void run() { int n = out.writable(); @@ -144,8 +155,7 @@ struct adder: runnable n = in1.readable(); if (in2.readable() < n) n = in2.readable(); - T *pin1 = in1.rd(), *pin2 = in2.rd(), *pout = out.wr(), *pend = pout - + n; + T *pin1 = in1.rd(), *pin2 = in2.rd(), *pout = out.wr(), *pend = pout + n; while (pout < pend) *pout++ = *pin1++ + *pin2++; in1.read(n); @@ -153,24 +163,22 @@ struct adder: runnable out.written(n); } -private: + private: pipereader in1, in2; pipewriter out; }; -template -struct scaler: runnable +template +struct scaler : runnable { Tscale scale; - - scaler(scheduler *sch, Tscale _scale, pipebuf &_in, pipebuf &_out) : - runnable(sch, "scaler"), - scale(_scale), - in(_in), - out(_out) + scaler(scheduler *sch, Tscale _scale, + pipebuf &_in, pipebuf &_out) + : runnable(sch, "scaler"), + scale(_scale), + in(_in), out(_out) { } - void run() { unsigned long count = min(in.readable(), out.writable()); @@ -182,23 +190,20 @@ struct scaler: runnable out.written(count); } -private: + private: pipereader in; pipewriter out; }; // [awgb_c] generates complex white gaussian noise. -template -struct wgn_c: runnable +template +struct wgn_c : runnable { - wgn_c(scheduler *sch, pipebuf > &_out) : - runnable(sch, "awgn"), - stddev(1.0), - out(_out) + wgn_c(scheduler *sch, pipebuf> &_out) + : runnable(sch, "awgn"), stddev(1.0), out(_out) { } - void run() { int n = out.writable(); @@ -220,21 +225,17 @@ struct wgn_c: runnable } out.written(n); } - float stddev; -private: - pipewriter > out; + private: + pipewriter> out; }; -template -struct naive_lowpass: runnable +template +struct naive_lowpass : runnable { - naive_lowpass(scheduler *sch, pipebuf &_in, pipebuf &_out, int _w) : - runnable(sch, "lowpass"), - in(_in), - out(_out), - w(_w) + naive_lowpass(scheduler *sch, pipebuf &_in, pipebuf &_out, int _w) + : runnable(sch, "lowpass"), in(_in), out(_out), w(_w) { } @@ -257,25 +258,23 @@ struct naive_lowpass: runnable out.written(count); } -private: + private: pipereader in; pipewriter out; int w; }; -template -struct fir_filter: runnable +template +struct fir_filter : runnable { - fir_filter(scheduler *sch, int _ncoeffs, Tc *_coeffs, pipebuf &_in, pipebuf &_out, unsigned int _decim = 1) : - runnable(sch, "fir_filter"), - freq_tap(NULL), - tap_multiplier(1), - freq_tol(0.1), - ncoeffs(_ncoeffs), - coeffs(_coeffs), - in(_in), - out(_out), - decim(_decim) + fir_filter(scheduler *sch, int _ncoeffs, Tc *_coeffs, + pipebuf &_in, pipebuf &_out, + unsigned int _decim = 1) + : runnable(sch, "fir_filter"), + ncoeffs(_ncoeffs), coeffs(_coeffs), + in(_in), out(_out), + decim(_decim), + freq_tap(NULL), tap_multiplier(1), freq_tol(0.1) { shifted_coeffs = new T[ncoeffs]; set_freq(0); @@ -292,16 +291,15 @@ struct fir_filter: runnable if (fabs(current_freq - new_freq) > freq_tol) { if (sch->verbose) - fprintf(stderr, "Shifting filter %f -> %f\n", current_freq, - new_freq); + fprintf(stderr, "Shifting filter %f -> %f\n", + current_freq, new_freq); set_freq(new_freq); } } unsigned long count = min((in.readable() - ncoeffs) / decim, - out.writable()); - T *pin = in.rd() + ncoeffs, *pend = pin + count * decim, *pout = - out.wr(); + out.writable()); + T *pin = in.rd() + ncoeffs, *pend = pin + count * decim, *pout = out.wr(); // TBD use coeffs when current_freq=0 (fewer mults if float) for (; pin < pend; pin += decim, ++pout) { @@ -316,25 +314,20 @@ struct fir_filter: runnable out.written(count); } -public: - float *freq_tap; - float tap_multiplier; - float freq_tol; - -private: + private: unsigned int ncoeffs; Tc *coeffs; pipereader in; pipewriter out; unsigned int decim; + T *shifted_coeffs; float current_freq; - void set_freq(float f) { for (unsigned int i = 0; i < ncoeffs; ++i) { - float a = 2 * M_PI * f * (i - (ncoeffs / 2.0f)); + float a = 2 * M_PI * f * (i - ncoeffs / 2); float c = cosf(a), s = sinf(a); // TBD Support T=complex shifted_coeffs[i].re = coeffs[i] * c; @@ -342,30 +335,29 @@ private: } current_freq = f; } -}; -// fir_filter + + public: + float *freq_tap; + float tap_multiplier; + float freq_tol; +}; // fir_filter // FIR FILTER WITH INTERPOLATION AND DECIMATION -template -struct fir_resampler: runnable +template +struct fir_resampler : runnable { - fir_resampler(scheduler *sch, int _ncoeffs, Tc *_coeffs, pipebuf &_in, pipebuf &_out, int _interp = 1, int _decim = 1) : - runnable(sch, "fir_resampler"), - ncoeffs(_ncoeffs), - coeffs(_coeffs), - interp(_interp), - decim(_decim), - in(_in), - out(_out, interp), - freq_tap(NULL), - tap_multiplier(1), - freq_tol(0.1) + fir_resampler(scheduler *sch, int _ncoeffs, Tc *_coeffs, + pipebuf &_in, pipebuf &_out, + int _interp = 1, int _decim = 1) + : runnable(sch, "fir_resampler"), + ncoeffs(_ncoeffs), coeffs(_coeffs), + interp(_interp), decim(_decim), + in(_in), out(_out, interp), + freq_tap(NULL), tap_multiplier(1), freq_tol(0.1) { - if (decim != 1) { - fail("fir_resampler::fir_resampler", "decim not implemented"); // TBD - return; - } + if (decim != 1) + fail("fir_resampler: decim not implemented"); // TBD shifted_coeffs = new T[ncoeffs]; set_freq(0); } @@ -381,8 +373,8 @@ struct fir_resampler: runnable if (fabs(current_freq - new_freq) > freq_tol) { if (sch->verbose) - fprintf(stderr, "Shifting filter %f -> %f\n", current_freq, - new_freq); + fprintf(stderr, "Shifting filter %f -> %f\n", + current_freq, new_freq); set_freq(new_freq); } } @@ -390,7 +382,7 @@ struct fir_resampler: runnable if (in.readable() * interp < ncoeffs) return; unsigned long count = min((in.readable() * interp - ncoeffs) / interp, - out.writable() / interp); + out.writable() / interp); int latency = (ncoeffs + interp) / interp; T *pin = in.rd() + latency, *pend = pin + count, *pout = out.wr(); // TBD use coeffs when current_freq=0 (fewer mults if float) @@ -410,20 +402,21 @@ struct fir_resampler: runnable out.written(count * interp); } -public: - float *freq_tap; - float tap_multiplier; - float freq_tol; - -private: + private: unsigned int ncoeffs; Tc *coeffs; int interp, decim; pipereader in; pipewriter out; + + public: + float *freq_tap; + float tap_multiplier; + float freq_tol; + + private: T *shifted_coeffs; float current_freq; - void set_freq(float f) { for (int i = 0; i < ncoeffs; ++i) @@ -436,9 +429,8 @@ private: } current_freq = f; } -}; -// fir_resampler +}; // fir_resampler -}// namespace +} // namespace leansdr -#endif // LEANSDR_DSP_H +#endif // LEANSDR_DSP_H diff --git a/plugins/channelrx/demoddatv/leansdr/dvb.cpp b/plugins/channelrx/demoddatv/leansdr/dvb.cpp new file mode 100644 index 000000000..6a5fd7a7b --- /dev/null +++ b/plugins/channelrx/demoddatv/leansdr/dvb.cpp @@ -0,0 +1,46 @@ +#include "dvb.h" + +namespace leansdr +{ + +deconvol_sync_simple *make_deconvol_sync_simple(scheduler *sch, + pipebuf &_in, + pipebuf &_out, + enum code_rate rate) +{ + // EN 300 421, section 4.4.3 Inner coding + uint32_t pX, pY; + switch (rate) + { + case FEC12: + pX = 0x1; // 1 + pY = 0x1; // 1 + break; + case FEC23: + case FEC46: + pX = 0xa; // 1010 (Handle as FEC4/6, no half-symbols) + pY = 0xf; // 1111 + break; + case FEC34: + pX = 0x5; // 101 + pY = 0x6; // 110 + break; + case FEC56: + pX = 0x15; // 10101 + pY = 0x1a; // 11010 + break; + case FEC78: + pX = 0x45; // 1000101 + pY = 0x7a; // 1111010 + break; + default: + //fail("Code rate not implemented"); + // For testing DVB-S2 constellations. + fprintf(stderr, "Code rate not implemented; proceeding anyway\n"); + pX = pY = 1; + } + + return new deconvol_sync_simple(sch, _in, _out, DVBS_G1, DVBS_G2, pX, pY); +} + +} // leansdr \ No newline at end of file diff --git a/plugins/channelrx/demoddatv/leansdr/dvb.h b/plugins/channelrx/demoddatv/leansdr/dvb.h index 8cde82b79..f5735ef7a 100644 --- a/plugins/channelrx/demoddatv/leansdr/dvb.h +++ b/plugins/channelrx/demoddatv/leansdr/dvb.h @@ -1,13 +1,28 @@ +// This file is part of LeanSDR Copyright (C) 2016-2019 . +// See the toplevel README for more information. +// +// This program 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. +// +// This program 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 this program. If not, see . + #ifndef LEANSDR_DVB_H #define LEANSDR_DVB_H #include -#include "leansdr/viterbi.h" #include "leansdr/convolutional.h" -#include "leansdr/sdr.h" #include "leansdr/rs.h" -#include "leansdr/incrementalarray.h" +#include "leansdr/sdr.h" +#include "leansdr/viterbi.h" namespace leansdr { @@ -21,93 +36,41 @@ static const int MPEG_SYNC_CORRUPTED = 0x55; enum code_rate { - FEC12, FEC23, FEC46, FEC34, FEC56, FEC78, // DVB-S + FEC12, + FEC23, + FEC46, + FEC34, + FEC56, + FEC78, // DVB-S FEC45, FEC89, - FEC910, // DVB-S2 - FEC_MAX + FEC910, // DVB-S2 + FEC14, + FEC13, + FEC25, + FEC35, // DVB-S2 + FEC_COUNT }; -// Customize APSK radii according to code rate - -inline cstln_lut<256> * make_dvbs2_constellation(cstln_lut<256>::predef c, - code_rate r) -{ - float gamma1 = 1, gamma2 = 1, gamma3 = 1; - switch (c) - { - case cstln_lut<256>::APSK16: - // EN 302 307, section 5.4.3, Table 9 - switch (r) - { - case FEC23: - case FEC46: - gamma1 = 3.15; - break; - case FEC34: - gamma1 = 2.85; - break; - case FEC45: - gamma1 = 2.75; - break; - case FEC56: - gamma1 = 2.70; - break; - case FEC89: - gamma1 = 2.60; - break; - case FEC910: - gamma1 = 2.57; - break; - default: - fail("cstln_lut<256>::make_dvbs2_constellation", "Code rate not supported with APSK16"); - return 0; - } - break; - case cstln_lut<256>::APSK32: - // EN 302 307, section 5.4.4, Table 10 - switch (r) - { - case FEC34: - gamma1 = 2.84; - gamma2 = 5.27; - break; - case FEC45: - gamma1 = 2.72; - gamma2 = 4.87; - break; - case FEC56: - gamma1 = 2.64; - gamma2 = 4.64; - break; - case FEC89: - gamma1 = 2.54; - gamma2 = 4.33; - break; - case FEC910: - gamma1 = 2.53; - gamma2 = 4.30; - break; - default: - fail("cstln_lut<256>::make_dvbs2_constellation", "Code rate not supported with APSK32"); - return 0; - } - break; - case cstln_lut<256>::APSK64E: - // EN 302 307-2, section 5.4.5, Table 13f - gamma1 = 2.4; - gamma2 = 4.3; - gamma3 = 7; - break; - default: - break; - } - return new cstln_lut<256>(c, gamma1, gamma2, gamma3); -} +// static const char *fec_names[] = { +// [FEC12] = "1/2", +// [FEC23] = "2/3", +// [FEC46] = "4/6", +// [FEC34] = "3/4", +// [FEC56] = "5/6", +// [FEC78] = "7/8", +// [FEC45] = "4/5", +// [FEC89] = "8/9", +// [FEC910] = "9/10", +// [FEC14] = "1/4", +// [FEC13] = "1/3", +// [FEC25] = "2/5", +// [FEC35] = "3/5", +// }; // EN 300 421, section 4.4.3, table 2 Punctured code, G1=0171, G2=0133 -static const int DVBS_G1 = 121; -static const int DVBS_G2 = 91; +static const int DVBS_G1 = 0171; +static const int DVBS_G2 = 0133; // G1 = 0b1111001 // G2 = 0b1011011 @@ -144,14 +107,20 @@ static const int DVBS_G2 = 91; // D = [ 0 1 0 1 1 1 0 1 1 1 0 0 0 0] // D = 0x3ba -template -struct deconvol_sync: runnable +template +struct deconvol_sync : runnable { - deconvol_sync(scheduler *sch, pipebuf &_in, - pipebuf &_out, uint32_t gX, uint32_t gY, uint32_t pX, - uint32_t pY) : - runnable(sch, "deconvol_sync"), fastlock(false), in(_in), out(_out, - SIZE_RSPACKET), skip(0) + deconvol_sync(scheduler *sch, + pipebuf &_in, + pipebuf &_out, + uint32_t gX, + uint32_t gY, + uint32_t pX, + uint32_t pY) : runnable(sch, "deconvol_sync"), + fastlock(false), + in(_in), + out(_out, SIZE_RSPACKET), + skip(0) { conv = new uint32_t[2]; conv[0] = gX; @@ -162,6 +131,7 @@ struct deconvol_sync: runnable punct[1] = pY; punctperiod = 0; punctweight = 0; + for (int i = 0; i < 2; ++i) { int nbits = log2(punct[i]) + 1; @@ -169,8 +139,10 @@ struct deconvol_sync: runnable punctperiod = nbits; punctweight += hamming_weight(punct[i]); } + if (sch->verbose) fprintf(stderr, "puncturing %d/%d\n", punctperiod, punctweight); + deconv = new iq_t[punctperiod]; deconv2 = new iq_t[punctperiod]; inverse_convolution(); @@ -194,17 +166,20 @@ struct deconvol_sync: runnable int sbits = log2(s) + 1; iq_t iq = 0; unsigned char state = 0; + for (int b = sbits - 1; b >= 0; --b) - { // Feed into convolver, MSB first + { // Feed into convolver, MSB first unsigned char bit = (s >> b) & 1; - state = (state >> 1) | (bit << 6); // Shift register + state = (state >> 1) | (bit << 6); // Shift register + for (int j = 0; j < nG; ++j) { - unsigned char xy = parity(state & conv[j]); // Taps + unsigned char xy = parity(state & conv[j]); // Taps if (punct[j] & (1 << (b % punctperiod))) iq = (iq << 1) | xy; } } + return iq; } @@ -216,11 +191,10 @@ struct deconvol_sync: runnable void next_sync() { if (fastlock) - { - fail("deconvol_sync::next_sync", "Bug: next_sync() called with fastlock"); - return; - } + fail("Bug: next_sync() called with fastlock"); + ++locked; + if (locked == &syncs[NSYNCS]) { locked = &syncs[0]; @@ -231,13 +205,12 @@ struct deconvol_sync: runnable bool fastlock; -private: - + private: static const int maxsbits = 64; iq_t response[maxsbits]; //static const int traceback = 48; // For code rate 7/8 - static const int traceback = 64; // For code rate 7/8 with fastlock + static const int traceback = 64; // For code rate 7/8 with fastlock void solve_rec(iq_t prefix, unsigned int nprefix, signal_t exp, iq_t *best) { @@ -245,7 +218,9 @@ private: return; if (nprefix > sizeof(prefix) * 8) return; + int solved = 1; + for (int b = 0; b < maxsbits; ++b) { if (parity(prefix & response[b]) != ((exp >> b) & 1)) @@ -257,13 +232,15 @@ private: solved = 0; } } + if (solved) { *best = prefix; return; } + solve_rec(prefix, nprefix + 1, exp, best); - solve_rec(prefix | ((iq_t) 1 << nprefix), nprefix + 1, exp, best); + solve_rec(prefix | ((iq_t)1 << nprefix), nprefix + 1, exp, best); } static const int LATENCY = 0; @@ -272,12 +249,12 @@ private: { for (int sbit = 0; sbit < maxsbits; ++sbit) { - response[sbit] = convolve((iq_t) 1 << sbit); + response[sbit] = convolve((iq_t)1 << sbit); //fprintf(stderr, "response %d = %x\n", sbit, response[sbit]); } for (int b = 0; b < punctperiod; ++b) { - deconv[b] = -(iq_t) 1; + deconv[b] = -(iq_t)1; solve_rec(0, 0, 1 << (LATENCY + b), &deconv[b]); } @@ -331,19 +308,17 @@ private: if (d == 0x00000000fb11d640LL) d2 = 0x00fbea3c8679c980LL; if (d2 == d) - { - fail("deconvol_sync::inverse_convolution", "Alt polynomial not provided"); - return; - } + fail("Alt polynomial not provided"); deconv2[b] = d2; } if (sch->debug) { for (int b = 0; b < punctperiod; ++b) - fprintf(stderr, "deconv[%d]=0x%016llx %d taps / %d bits\n", b, - (unsigned long long) deconv[b], - hamming_weight(deconv[b]), log2(deconv[b]) + 1); + { + fprintf(stderr, "deconv[%d]=0x%016llx %d taps / %d bits\n", + b, (unsigned long long)deconv[b], hamming_weight(deconv[b]), log2(deconv[b]) + 1); + } } // Sanity check @@ -351,31 +326,22 @@ private: { for (int i = 0; i < maxsbits; ++i) { - iq_t iq = convolve((iq_t) 1 << (LATENCY + i)); + iq_t iq = convolve((iq_t)1 << (LATENCY + i)); int expect = (b == i) ? 1 : 0; int d = parity(iq & deconv[b]); if (d != expect) - { - fail("deconvol_sync::inverse_convolution", "Failed to inverse convolutional coding"); - } + fail("Failed to inverse convolutional coding"); int d2 = parity(iq & deconv2[b]); if (d2 != expect) - { - fail("deconvol_sync::inverse_convolution", "Failed to inverse convolutional coding (alt)"); - } + fail("Failed to inverse convolutional coding (alt)"); } + if (traceback > sizeof(iq_t) * 8) - { - fail("deconvol_sync::inverse_convolution", "Bug: traceback exceeds register size"); - } + fail("Bug: traceback exceeds register size"); if (log2(deconv[b]) + 1 > traceback) - { - fail("deconvol_sync::inverse_convolution", "traceback insufficient for deconvolution"); - } + fail("traceback insufficient for deconvolution"); if (log2(deconv2[b]) + 1 > traceback) - { - fail("deconvol_sync::inverse_convolution", "traceback insufficient for deconvolution (alt)"); - } + fail("traceback insufficient for deconvolution (alt)"); } } @@ -383,7 +349,7 @@ private: struct sync_t { - u8 lut[2][2]; // lut[(re>0)?1:0][(im>0)?1:0] = 0b000000IQ + u8 lut[2][2]; // lut[(re>0)?1:0][(im>0)?1:0] = 0b000000IQ iq_t in; int n_in; signal_t out; @@ -401,28 +367,26 @@ private: for (int sync_id = 0; sync_id < NSYNCS; ++sync_id) { for (int re_pos = 0; re_pos <= 1; ++re_pos) + { for (int im_pos = 0; im_pos <= 1; ++im_pos) { - int re_neg = !re_pos; -#if 0 - int im_neg __attribute__((unused)) = !im_pos; -#endif + int re_neg = !re_pos; //int im_neg = !im_pos; int I, Q; switch (sync_id) { - case 0: // Direct 0° + case 0: // Direct 0° I = re_pos ? 0 : 1; Q = im_pos ? 0 : 1; break; - case 1: // Direct 90° + case 1: // Direct 90° I = im_pos ? 0 : 1; Q = re_neg ? 0 : 1; break; - case 2: // Conj 0° + case 2: // Conj 0° I = re_pos ? 0 : 1; Q = im_pos ? 1 : 0; break; - case 3: // Conj 90° + case 3: // Conj 90° I = im_pos ? 1 : 0; Q = re_neg ? 0 : 1; break; @@ -445,8 +409,11 @@ private: break; #endif } + syncs[sync_id].lut[re_pos][im_pos] = (I << 1) | Q; } + } + syncs[sync_id].n_in = 0; syncs[sync_id].n_out = 0; syncs[sync_id].n_in2 = 0; @@ -461,46 +428,55 @@ private: // 5/6 24 symbols -> 5 bytes // 7/8 32 symbols -> 7 bytes - inline Tbyte readbyte(sync_t *s, softsymbol *&p) + inline Tbyte readbyte(sync_t *s, eucl_ss *&p) { while (s->n_out < 8) { iq_t iq = s->in; + while (s->n_in < traceback) { - u8 iqbits = s->lut[(p->symbol & 2) ? 1 : 0][p->symbol & 1]; + u8 iqbits = s->lut[(p->nearest & 2) ? 1 : 0][p->nearest & 1]; ++p; iq = (iq << 2) | iqbits; s->n_in += 2; } + s->in = iq; + for (int b = punctperiod - 1; b >= 0; --b) { u8 bit = parity(iq & deconv[b]); s->out = (s->out << 1) | bit; } + s->n_out += punctperiod; s->n_in -= punctweight; } + Tbyte res = (s->out >> (s->n_out - 8)) & 255; s->n_out -= 8; return res; } - inline unsigned long readerrors(sync_t *s, softsymbol *&p) + inline unsigned long readerrors(sync_t *s, eucl_ss *&p) { unsigned long res = 0; + while (s->n_out2 < 8) { iq_t iq = s->in2; + while (s->n_in2 < traceback) { - u8 iqbits = s->lut[(p->symbol & 2) ? 1 : 0][p->symbol & 1]; + u8 iqbits = s->lut[(p->nearest & 2) ? 1 : 0][p->nearest & 1]; ++p; iq = (iq << 2) | iqbits; s->n_in2 += 2; } + s->in2 = iq; + for (int b = punctperiod - 1; b >= 0; --b) { u8 bit = parity(iq & deconv[b]); @@ -508,9 +484,11 @@ private: if (bit2 != bit) ++res; } + s->n_out2 += punctperiod; s->n_in2 -= punctweight; } + s->n_out2 -= 8; return res; } @@ -523,41 +501,51 @@ private: // 8 byte margin to fill the deconvolver if (in.readable() < 64) return; + int maxrd = (in.readable() - 64) / (punctweight / 2) * punctperiod / 8; int maxwr = out.writable(); - unsigned int n = (maxrd < maxwr) ? maxrd : maxwr; + int n = (maxrd < maxwr) ? maxrd : maxwr; + if (!n) return; // Require enough symbols to discriminate in fastlock mode // (threshold must be less than size of rspacket) + if (n < 32) return; if (fastlock) { // Try all sync alignments - unsigned long errors_best = 1 << 30; + long errors_best = 1 << 30; sync_t *best = &syncs[0]; + for (sync_t *s = syncs; s < syncs + NSYNCS; ++s) { - softsymbol *pin = in.rd(); - unsigned long errors = 0; + eucl_ss *pin = in.rd(); + long errors = 0; + for (int c = n; c--;) errors += readerrors(s, pin); + if (errors < errors_best) { errors_best = errors; best = s; } } + if (best != locked) { // Another alignment produces fewer bit errors if (sch->debug) - fprintf(stderr, "{%d->%d}\n", (int) (locked - syncs), - (int) (best - syncs)); + { + fprintf(stderr, "{%d->%d}\n", (int)(locked - syncs), (int)(best - syncs)); + } + locked = best; } + // If deconvolution bit error rate > 33%, try next sample alignment if (errors_best > n * 8 / 3) { @@ -566,151 +554,142 @@ private: } } - softsymbol *pin = in.rd(), *pin0 = pin; + eucl_ss *pin = in.rd(), *pin0 = pin; Tbyte *pout = out.wr(), *pout0 = pout; + while (n--) + { *pout++ = readbyte(locked, pin); + } + in.read(pin - pin0); out.written(pout - pout0); } - pipereader in; + pipereader in; pipewriter out; // DECONVOL int nG; uint32_t *conv; // [nG] Convolution polynomials; MSB is newest - uint32_t *punct; // [nG] Puncturing pattern + uint32_t *punct; // [nG] Puncturing pattern int punctperiod, punctweight; iq_t *deconv; // [punctperiod] Deconvolution polynomials - iq_t *deconv2; // [punctperiod] Alternate polynomials (for fastlock) + iq_t *deconv2; // [punctperiod] Alternate polynomials (for fastlock) sync_t *locked; int skip; - }; typedef deconvol_sync deconvol_sync_simple; -inline deconvol_sync_simple *make_deconvol_sync_simple(scheduler *sch, - pipebuf &_in, pipebuf &_out, enum code_rate rate) -{ - // EN 300 421, section 4.4.3 Inner coding - uint32_t pX, pY; - switch (rate) - { - case FEC12: - pX = 0x1; // 1 - pY = 0x1; // 1 - break; - case FEC23: - case FEC46: - pX = 0xa; // 1010 (Handle as FEC4/6, no half-symbols) - pY = 0xf; // 1111 - break; - case FEC34: - pX = 0x5; // 101 - pY = 0x6; // 110 - break; - case FEC56: - pX = 0x15; // 10101 - pY = 0x1a; // 11010 - break; - case FEC78: - pX = 0x45; // 1000101 - pY = 0x7a; // 1111010 - break; - default: - //fail("Code rate not implemented"); - // For testing DVB-S2 constellations. - fprintf(stderr, "Code rate not implemented; proceeding anyway\n"); - pX = pY = 1; - } - return new deconvol_sync_simple(sch, _in, _out, DVBS_G1, DVBS_G2, pX, pY); -} +deconvol_sync_simple *make_deconvol_sync_simple(scheduler *sch, + pipebuf &_in, + pipebuf &_out, + enum code_rate rate); // CONVOLUTIONAL ENCODER -static const uint16_t polys_fec12[] = -{ DVBS_G1, DVBS_G2 // X1Y1 - }; -static const uint16_t polys_fec23[] = -{ DVBS_G1, DVBS_G2, DVBS_G2 << 1 // X1Y1Y2 +static const uint16_t polys_fec12[] = { + DVBS_G1, + DVBS_G2 // X1Y1 }; + +static const uint16_t polys_fec23[] = { + DVBS_G1, DVBS_G2, + DVBS_G2 << 1 // X1Y1Y2 +}; + // Same code rate as 2/3, usable with QPSK -static const uint16_t polys_fec46[] = -{ DVBS_G1, DVBS_G2, DVBS_G2 << 1, // X1Y1Y2 -DVBS_G1 << 2, DVBS_G2 << 2, DVBS_G2 << 3 // X3Y3Y4 +static const uint16_t polys_fec46[] = { + DVBS_G1, DVBS_G2, + DVBS_G2 << 1, // X1Y1Y2 + DVBS_G1 << 2, DVBS_G2 << 2, + DVBS_G2 << 3 // X3Y3Y4 }; -static const uint16_t polys_fec34[] = -{ DVBS_G1, DVBS_G2, // X1Y1 - DVBS_G2 << 1, DVBS_G1 << 2 // Y2X3 + +static const uint16_t polys_fec34[] = { + DVBS_G1, + DVBS_G2, // X1Y1 + DVBS_G2 << 1, + DVBS_G1 << 2 // Y2X3 }; -static const uint16_t polys_fec45[] = -{ // Non standard - DVBS_G1, DVBS_G2, // X1Y1 - DVBS_G2 << 1, DVBS_G1 << 2, // Y2X3 - DVBS_G1 << 3 // X4 - }; -static const uint16_t polys_fec56[] = -{ DVBS_G1, DVBS_G2, // X1Y1 - DVBS_G2 << 1, DVBS_G1 << 2, // Y2X3 - DVBS_G2 << 3, DVBS_G1 << 4 // Y4X5 + +static const uint16_t polys_fec45[] = { + // Non standard + DVBS_G1, + DVBS_G2, // X1Y1 + DVBS_G2 << 1, + DVBS_G1 << 2, // Y2X3 + DVBS_G1 << 3 // X4 }; -static const uint16_t polys_fec78[] = -{ DVBS_G1, DVBS_G2, // X1Y1 - DVBS_G2 << 1, DVBS_G2 << 2, // Y2Y3 - DVBS_G2 << 3, DVBS_G1 << 4, // Y4X5 - DVBS_G2 << 5, DVBS_G1 << 6 // Y6X7 + +static const uint16_t polys_fec56[] = { + DVBS_G1, + DVBS_G2, // X1Y1 + DVBS_G2 << 1, + DVBS_G1 << 2, // Y2X3 + DVBS_G2 << 3, + DVBS_G1 << 4 // Y4X5 +}; + +static const uint16_t polys_fec78[] = { + DVBS_G1, + DVBS_G2, // X1Y1 + DVBS_G2 << 1, + DVBS_G2 << 2, // Y2Y3 + DVBS_G2 << 3, + DVBS_G1 << 4, // Y4X5 + DVBS_G2 << 5, + DVBS_G1 << 6 // Y6X7 }; // FEC parameters, for convolutional coding only (not S2). static struct fec_spec { - int bits_in; // Entering the convolutional coder - int bits_out; // Exiting the convolutional coder - const uint16_t *polys; // [bits_out] -} - -fec_specs[FEC_MAX] = { - { 1, 2, polys_fec12 }, - { 2, 3, polys_fec23 }, - { 4, 6, polys_fec46 }, - { 3, 4, polys_fec34 }, - { 5, 6, polys_fec56 }, - { 7, 8, polys_fec78 }, - { 4, 5, polys_fec45 }, // Non-standard + int bits_in; // Entering the convolutional coder + int bits_out; // Exiting the convolutional coder + const uint16_t *polys; // [bits_out] +} fec_specs[FEC_COUNT] = + { + [FEC12] = {1, 2, polys_fec12}, + [FEC23] = {2, 3, polys_fec23}, + [FEC46] = {4, 6, polys_fec46}, + [FEC34] = {3, 4, polys_fec34}, + [FEC56] = {5, 6, polys_fec56}, + [FEC78] = {7, 8, polys_fec78}, + [FEC45] = {4, 5, polys_fec45}, // Non-standard }; -struct dvb_convol: runnable +struct dvb_convol : runnable { typedef u8 uncoded_byte; typedef u8 hardsymbol; - dvb_convol(scheduler *sch, pipebuf &_in, - pipebuf &_out, code_rate fec, int bits_per_symbol) : - runnable(sch, "dvb_convol"), in(_in), out(_out, 64) // BPSK 7/8: 7 bytes in, 64 symbols out + + dvb_convol(scheduler *sch, + pipebuf &_in, + pipebuf &_out, + code_rate fec, + int bits_per_symbol) : runnable(sch, "dvb_convol"), + in(_in), + out(_out, 64) // BPSK 7/8: 7 bytes in, 64 symbols out { fec_spec *fs = &fec_specs[fec]; + if (!fs->bits_in) - { - fail("dvb_convol::dvb_convol", "Unexpected FEC"); - return; - } + fail("Unexpected FEC"); + convol.bits_in = fs->bits_in; convol.bits_out = fs->bits_out; convol.polys = fs->polys; convol.bps = bits_per_symbol; + // FEC must output a whole number of IQ symbols if (convol.bits_out % convol.bps) - { - fail("dvb_convol::dvb_convol", "Code rate not suitable for this constellation"); - return; - } + fail("Code rate not suitable for this constellation"); } void run() { - int count = min(in.readable(), - out.writable() * convol.bps / convol.bits_out * convol.bits_in - / 8); + int count = min(in.readable(), out.writable() * convol.bps / convol.bits_out * convol.bits_in / 8); // Process in multiples of the puncturing period and of 8 bits. int chunk = convol.bits_in; count = (count / chunk) * chunk; @@ -719,7 +698,8 @@ struct dvb_convol: runnable int nout = count * 8 / convol.bits_in * convol.bits_out / convol.bps; out.written(nout); } -private: + + private: pipereader in; pipewriter out; convol_multipoly convol; @@ -731,19 +711,20 @@ private: // QPSK 1/2 only; // With DVB-S polynomials hardcoded. -template -struct dvb_deconvol_sync: runnable +template +struct dvb_deconvol_sync : runnable { typedef u8 decoded_byte; - int resync_period; + static const int chunk_size = 64; // At least 2*sizeof(Thist)/8 - static const int chunk_size = 64; // At least 2*sizeof(Thist)/8 - - dvb_deconvol_sync(scheduler *sch, pipebuf &_in, - pipebuf &_out) : - runnable(sch, "deconvol_sync_multipoly"), resync_period(32), in( - _in), out(_out, chunk_size), resync_phase(0) + dvb_deconvol_sync(scheduler *sch, + pipebuf &_in, + pipebuf &_out) : runnable(sch, "deconvol_sync_multipoly"), + resync_period(32), + in(_in), + out(_out, chunk_size), + resync_phase(0) { init_syncs(); locked = &syncs[0]; @@ -751,41 +732,45 @@ struct dvb_deconvol_sync: runnable void run() { - while (in.readable() >= chunk_size * 8 && out.writable() >= chunk_size) { int errors_best = 1 << 30; sync_t *best = NULL; + for (sync_t *s = syncs; s < syncs + NSYNCS; ++s) { if (resync_phase != 0 && s != locked) // Decode only the currently-locked alignment continue; + Tin *pin = in.rd(); static decoded_byte dummy[chunk_size]; decoded_byte *pout = (s == locked) ? out.wr() : dummy; int nerrors = s->deconv.run(pin, s->lut, pout, chunk_size); + if (nerrors < errors_best) { errors_best = nerrors; best = s; } } + in.read(chunk_size * 8); out.written(chunk_size); + if (best != locked) { if (sch->debug) - fprintf(stderr, "%%%d", (int) (best - syncs)); + fprintf(stderr, "%%%d", (int)(best - syncs)); locked = best; } + if (++resync_phase >= resync_period) resync_phase = 0; } // Work to do + } // run() - } // run() - -private: + private: pipereader in; pipewriter out; int resync_phase; @@ -795,7 +780,7 @@ private: struct sync_t { deconvol_poly2 deconv; - u8 lut[4]; // TBD Swap and flip bits in the polynomials instead. + u8 lut[4]; // TBD Swap and flip bits in the polynomials instead. } syncs[NSYNCS]; sync_t *locked; @@ -811,6 +796,7 @@ private: // 3 | 1 // 0° syncs[0].lut[0] = 0; + syncs[0].lut[1] = 1; syncs[0].lut[2] = 2; syncs[0].lut[3] = 3; @@ -830,51 +816,45 @@ private: syncs[3].lut[2] = 1; syncs[3].lut[3] = 3; } - }; // dvb_deconvol_sync -typedef dvb_deconvol_sync dvb_deconvol_sync_soft; +typedef dvb_deconvol_sync dvb_deconvol_sync_soft; typedef dvb_deconvol_sync dvb_deconvol_sync_hard; // BIT ALIGNMENT AND MPEG SYNC DETECTION -template -struct mpeg_sync: runnable +template +struct mpeg_sync : runnable { int scan_syncs, want_syncs; unsigned long lock_timeout; bool fastlock; int resync_period; - mpeg_sync( - scheduler *sch, - pipebuf &_in, - pipebuf &_out, - deconvol_sync *_deconv, - pipebuf *_state_out = NULL, - pipebuf *_locktime_out = NULL) : - runnable(sch, "sync_detect"), - scan_syncs(8), - want_syncs(4), - lock_timeout(4), - fastlock(false), - resync_period(1), - in(_in), - out(_out, SIZE_RSPACKET * (scan_syncs + 1)), - deconv(_deconv), - polarity(0), - resync_phase(0), - bitphase(0), - synchronized(false), - next_sync_count(0), - phase8(-1), - lock_timeleft(0), - locktime(0), - report_state(true) + mpeg_sync(scheduler *sch, + pipebuf &_in, + pipebuf &_out, + deconvol_sync *_deconv, + pipebuf *_state_out = NULL, + pipebuf *_locktime_out = NULL) : runnable(sch, "sync_detect"), + scan_syncs(8), + want_syncs(4), + lock_timeout(4), + fastlock(false), + resync_period(1), + in(_in), + out(_out, SIZE_RSPACKET * (scan_syncs + 1)), + deconv(_deconv), + polarity(0), + resync_phase(0), + bitphase(0), + synchronized(false), + next_sync_count(0), + report_state(true) { state_out = _state_out ? new pipewriter(*_state_out) : NULL; - locktime_out = locktime_out ? new pipewriter(*_locktime_out) : NULL; + locktime_out = _locktime_out ? new pipewriter(*_locktime_out) : NULL; } void run() @@ -885,8 +865,11 @@ struct mpeg_sync: runnable state_out->write(0); report_state = false; } + if (synchronized) + { run_decoding(); + } else { if (fastlock) @@ -899,17 +882,19 @@ struct mpeg_sync: runnable void run_searching() { bool next_sync = false; - unsigned int chunk = SIZE_RSPACKET * scan_syncs; + int chunk = SIZE_RSPACKET * scan_syncs; - while (in.readable() >= chunk + 1 && // Need 1 ahead for bit shifting - out.writable() >= chunk && // Use as temp buffer - (!state_out || state_out->writable() >= 1)) + while (in.readable() >= chunk + 1 // Need 1 ahead for bit shifting + && out.writable() >= chunk // Use as temp buffer + && (!state_out || state_out->writable() >= 1)) { if (search_sync()) return; + in.read(chunk); // Switch to next bit alignment ++bitphase; + if (bitphase == 8) { bitphase = 0; @@ -921,6 +906,7 @@ struct mpeg_sync: runnable { // No lock this time ++next_sync_count; + if (next_sync_count >= 3) { // After a few cycles without a lock, resync the deconvolver. @@ -933,11 +919,11 @@ struct mpeg_sync: runnable void run_searching_fast() { - unsigned int chunk = SIZE_RSPACKET * scan_syncs; + int chunk = SIZE_RSPACKET * scan_syncs; - while (in.readable() >= chunk + 1 && // Need 1 ahead for bit shifting - out.writable() >= chunk && // Use as temp buffer - (!state_out || state_out->writable() >= 1)) + while (in.readable() >= chunk + 1 // Need 1 ahead for bit shifting + && out.writable() >= chunk // Use as temp buffer + && (!state_out || state_out->writable() >= 1)) { if (resync_phase == 0) { @@ -948,7 +934,9 @@ struct mpeg_sync: runnable return; } } + in.read(SIZE_RSPACKET); + if (++resync_phase >= resync_period) resync_phase = 0; } @@ -961,33 +949,40 @@ struct mpeg_sync: runnable Tbyte *pin = in.rd(), *pend = pin + chunk; Tbyte *pout = out.wr(); unsigned short w = *pin++; + for (; pin <= pend; ++pin, ++pout) { w = (w << 8) | *pin; *pout = w >> bitphase; } + // Search for [want_sync] start codes at all 204 offsets for (int i = 0; i < SIZE_RSPACKET; ++i) { - int nsyncs_p = 0, nsyncs_n = 0; // # start codes assuming pos/neg polarity + int nsyncs_p = 0, nsyncs_n = 0; // # start codes assuming pos/neg polarity int phase8_p = -1, phase8_n = -1; // Position in sequence of 8 packets Tbyte *p = &out.wr()[i]; + for (int j = 0; j < scan_syncs; ++j, p += SIZE_RSPACKET) { Tbyte b = *p; + if (b == MPEG_SYNC) { ++nsyncs_p; phase8_n = (8 - j) & 7; } + if (b == MPEG_SYNC_INV) { ++nsyncs_n; phase8_p = (8 - j) & 7; } } + // Detect most likely polarity int nsyncs; + if (nsyncs_p > nsyncs_n) { polarity = 0; @@ -1000,21 +995,26 @@ struct mpeg_sync: runnable nsyncs = nsyncs_n; phase8 = phase8_n; } + if (nsyncs >= want_syncs && phase8 >= 0) { if (sch->debug) fprintf(stderr, "Locked\n"); + if (!i) - { // Avoid fixpoint detection in scheduler + { // Avoid fixpoint detection in scheduler i = SIZE_RSPACKET; phase8 = (phase8 + 1) & 7; } - in.read(i); // Skip to first start code + + in.read(i); // Skip to first start code synchronized = true; lock_timeleft = lock_timeout; locktime = 0; + if (state_out) state_out->write(1); + return true; } } @@ -1023,55 +1023,61 @@ struct mpeg_sync: runnable void run_decoding() { - while (in.readable() >= SIZE_RSPACKET + 1 - && // +1 for bit shifting - out.writable() >= SIZE_RSPACKET - && (!state_out || state_out->writable() >= 1) - && (!locktime_out || locktime_out->writable() >= 1)) + while (in.readable() >= SIZE_RSPACKET + 1 && out.writable() >= SIZE_RSPACKET && (!state_out || state_out->writable() >= 1) && (!locktime_out || locktime_out->writable() >= 1)) { Tbyte *pin = in.rd(), *pend = pin + SIZE_RSPACKET; Tbyte *pout = out.wr(); unsigned short w = *pin++; + for (; pin <= pend; ++pin, ++pout) { w = (w << 8) | *pin; *pout = (w >> bitphase) ^ polarity; } + in.read(SIZE_RSPACKET); Tbyte syncbyte = *out.wr(); out.written(SIZE_RSPACKET); ++locktime; + if (locktime_out) locktime_out->write(locktime); + // Reset timer if sync byte is correct Tbyte expected = phase8 ? MPEG_SYNC : MPEG_SYNC_INV; + if (syncbyte == expected) lock_timeleft = lock_timeout; + phase8 = (phase8 + 1) & 7; --lock_timeleft; + if (!lock_timeleft) { if (sch->debug) fprintf(stderr, "Unlocked\n"); + synchronized = false; next_sync_count = 0; + if (state_out) state_out->write(0); + return; } } } -private: + private: pipereader in; pipewriter out; deconvol_sync *deconv; - unsigned char polarity; // XOR mask, 0 or 0xff + unsigned char polarity; // XOR mask, 0 or 0xff int resync_phase; int bitphase; bool synchronized; int next_sync_count; - int phase8; // Position in 8-packet cycle, -1 if not synchronized + int phase8; // Position in 8-packet cycle, -1 if not synchronized unsigned long lock_timeleft; unsigned long locktime; pipewriter *state_out; @@ -1079,7 +1085,7 @@ private: bool report_state; }; -template +template struct rspacket { Tbyte data[SIZE_RSPACKET]; @@ -1087,12 +1093,16 @@ struct rspacket // INTERLEAVER -struct interleaver: runnable +struct interleaver : runnable { - interleaver(scheduler *sch, pipebuf > &_in, pipebuf &_out) : - runnable(sch, "interleaver"), in(_in), out(_out, SIZE_RSPACKET) + interleaver(scheduler *sch, + pipebuf> &_in, + pipebuf &_out) : runnable(sch, "interleaver"), + in(_in), + out(_out, SIZE_RSPACKET) { } + void run() { while (in.readable() >= 12 && out.writable() >= SIZE_RSPACKET) @@ -1100,62 +1110,75 @@ struct interleaver: runnable rspacket *pin = in.rd(); u8 *pout = out.wr(); int delay = 0; - for (int i = 0; i < SIZE_RSPACKET; - ++i, ++pout, delay = (delay + 1) % 12) + + for (int i = 0; i < SIZE_RSPACKET; ++i, ++pout, delay = (delay + 1) % 12) + { *pout = pin[11 - delay].data[i]; + } + in.read(1); out.written(SIZE_RSPACKET); } } -private: - pipereader > in; + + private: + pipereader> in; pipewriter out; }; // interleaver // DEINTERLEAVER -template -struct deinterleaver: runnable +template +struct deinterleaver : runnable { - deinterleaver(scheduler *sch, pipebuf &_in, - pipebuf > &_out) : - runnable(sch, "deinterleaver"), in(_in), out(_out) + deinterleaver(scheduler *sch, + pipebuf &_in, + pipebuf> &_out) : runnable(sch, "deinterleaver"), + in(_in), + out(_out) { } + void run() { - while (in.readable() >= 17 * 11 * 12 + SIZE_RSPACKET - && out.writable() >= 1) + while (in.readable() >= 17 * 11 * 12 + SIZE_RSPACKET && out.writable() >= 1) { Tbyte *pin = in.rd() + 17 * 11 * 12, *pend = pin + SIZE_RSPACKET; Tbyte *pout = out.wr()->data; - for (int delay = 17 * 11; pin < pend; - ++pin, ++pout, delay = (delay - 17 + 17 * 12) % (17 * 12)) + + for (int delay = 17 * 11; pin < pend; ++pin, ++pout, delay = (delay - 17 + 17 * 12) % (17 * 12)) + { *pout = pin[-delay * 12]; + } + in.read(SIZE_RSPACKET); out.written(1); } } -private: + + private: pipereader in; - pipewriter > out; + pipewriter> out; }; // deinterleaver -static const int SIZE_TSPACKET = 188; +static const int SIZE_TSPACKET = 188; // TBD remove struct tspacket { + static const int SIZE = 188; u8 data[SIZE_TSPACKET]; }; // RS ENCODER -struct rs_encoder: runnable +struct rs_encoder : runnable { - rs_encoder(scheduler *sch, pipebuf &_in, - pipebuf > &_out) : - runnable(sch, "RS encoder"), in(_in), out(_out) + rs_encoder(scheduler *sch, + pipebuf &_in, + pipebuf> &_out) : runnable(sch, "RS encoder"), + in(_in), + out(_out) { } @@ -1174,27 +1197,33 @@ struct rs_encoder: runnable out.written(1); } } -private: + + private: rs_engine rs; pipereader in; - pipewriter > out; + pipewriter> out; }; // rs_encoder // RS DECODER -template -struct rs_decoder: runnable +template +struct rs_decoder : runnable { rs_engine rs; - rs_decoder(scheduler *sch, pipebuf > &_in, - pipebuf &_out, pipebuf *_bitcount = NULL, - pipebuf *_errcount = NULL) : - runnable(sch, "RS decoder"), in(_in), out(_out) + + rs_decoder(scheduler *sch, + pipebuf> &_in, + pipebuf &_out, + pipebuf *_bitcount = NULL, + pipebuf *_errcount = NULL) : runnable(sch, "RS decoder"), + in(_in), + out(_out) { bitcount = _bitcount ? new pipewriter(*_bitcount) : NULL; errcount = _errcount ? new pipewriter(*_errcount) : NULL; } + void run() { if (bitcount && bitcount->writable() < 1) @@ -1215,17 +1244,13 @@ struct rs_decoder: runnable if (sizeof(Tbyte) == 1) memcpy(pout, pin, SIZE_TSPACKET); else - { - fail("rs_decoder::run", "Erasures not implemented"); - return; - } + fail("Erasures not implemented"); u8 synd[16]; bool corrupted = rs.syndromes(pin, synd); #if 0 - if ( ! corrupted ) - { + if ( ! corrupted ) { // Test BM fprintf(stderr, "Simulating errors\n"); pin[203] ^= 42; @@ -1236,17 +1261,18 @@ struct rs_decoder: runnable if (!corrupted) { if (sch->debug) - fprintf(stderr, "_"); // Packet received without errors. + fprintf(stderr, "_"); // Packet received without errors. } else { corrupted = rs.correct(synd, pout, pin, &nerrs); + if (sch->debug) { if (!corrupted) - fprintf(stderr, "."); // Errors were corrected. + fprintf(stderr, "."); // Errors were corrected. else - fprintf(stderr, "!"); // Packet still corrupted. + fprintf(stderr, "!"); // Packet still corrupted. } } @@ -1256,9 +1282,10 @@ struct rs_decoder: runnable // otherwise the derandomizer will lose synchronization. if (corrupted) pout[0] ^= MPEG_SYNC_CORRUPTED; - out.written(1); + out.written(1); } + if (nbits) { if (bitcount) @@ -1267,8 +1294,9 @@ struct rs_decoder: runnable errcount->write(nerrs); } } -private: - pipereader > in; + + private: + pipereader> in; pipewriter out; pipewriter *bitcount, *errcount; }; @@ -1276,32 +1304,40 @@ private: // RANDOMIZER -struct randomizer: runnable +struct randomizer : runnable { - randomizer(scheduler *sch, pipebuf &_in, pipebuf &_out) : - runnable(sch, "derandomizer"), in(_in), out(_out) + randomizer(scheduler *sch, + pipebuf &_in, + pipebuf &_out) : runnable(sch, "derandomizer"), + in(_in), + out(_out) { precompute_pattern(); pos = pattern; pattern_end = pattern + sizeof(pattern) / sizeof(pattern[0]); } + void precompute_pattern() { // EN 300 421, section 4.4.1 Transport multiplex adaptation - pattern[0] = 0xff; // Invert one in eight sync bytes - unsigned short st = 169; // 0b 000 000 010 101 001 (Fig 2 reversed) + pattern[0] = 0xff; // Invert one in eight sync bytes + unsigned short st = 000251; // 0b 000 000 010 101 001 (Fig 2 reversed) + for (int i = 1; i < 188 * 8; ++i) { u8 out = 0; + for (int n = 8; n--;) { - int bit = ((st >> 13) ^ (st >> 14)) & 1; // Taps - out = (out << 1) | bit; // MSB first - st = (st << 1) | bit; // Feedback + int bit = ((st >> 13) ^ (st >> 14)) & 1; // Taps + out = (out << 1) | bit; // MSB first + st = (st << 1) | bit; // Feedback } - pattern[i] = (i % 188) ? out : 0; // Inhibit on sync bytes + + pattern[i] = (i % 188) ? out : 0; // Inhibit on sync bytes } } + void run() { while (in.readable() >= 1 && out.writable() >= 1) @@ -1318,7 +1354,8 @@ struct randomizer: runnable out.written(1); } } -private: + + private: u8 pattern[188 * 8], *pattern_end, *pos; pipereader in; pipewriter out; @@ -1327,41 +1364,48 @@ private: // DERANDOMIZER -struct derandomizer: runnable +struct derandomizer : runnable { - derandomizer(scheduler *sch, pipebuf &_in, - pipebuf &_out) : - runnable(sch, "derandomizer"), in(_in), out(_out) + derandomizer(scheduler *sch, + pipebuf &_in, + pipebuf &_out) : runnable(sch, "derandomizer"), + in(_in), + out(_out) { precompute_pattern(); pos = pattern; pattern_end = pattern + sizeof(pattern) / sizeof(pattern[0]); } + void precompute_pattern() { // EN 300 421, section 4.4.1 Transport multiplex adaptation - pattern[0] = 0xff; // Restore the inverted sync byte - unsigned short st = 169; // 0b 000 000 010 101 001 (Fig 2 reversed) + pattern[0] = 0xff; // Restore the inverted sync byte + unsigned short st = 000251; // 0b 000 000 010 101 001 (Fig 2 reversed) + for (int i = 1; i < 188 * 8; ++i) { u8 out = 0; + for (int n = 8; n--;) { - int bit = ((st >> 13) ^ (st >> 14)) & 1; // Taps - out = (out << 1) | bit; // MSB first - st = (st << 1) | bit; // Feedback + int bit = ((st >> 13) ^ (st >> 14)) & 1; // Taps + out = (out << 1) | bit; // MSB first + st = (st << 1) | bit; // Feedback } - pattern[i] = (i % 188) ? out : 0; // Inhibit on sync bytes + + pattern[i] = (i % 188) ? out : 0; // Inhibit on sync bytes } } + void run() { while (in.readable() >= 1 && out.writable() >= 1) { u8 *pin = in.rd()->data, *pend = pin + SIZE_TSPACKET; u8 *pout = out.wr()->data; - if (pin[0] == MPEG_SYNC_INV - || pin[0] == (MPEG_SYNC_INV ^ MPEG_SYNC_CORRUPTED)) + + if (pin[0] == MPEG_SYNC_INV || pin[0] == (MPEG_SYNC_INV ^ MPEG_SYNC_CORRUPTED)) { if (pos != pattern) { @@ -1370,13 +1414,19 @@ struct derandomizer: runnable pos = pattern; } } + for (; pin < pend; ++pin, ++pout, ++pos) + { *pout = *pin ^ *pos; + } + if (pos == pattern_end) pos = pattern; + in.read(1); u8 sync = out.wr()->data[0]; + if (sync == MPEG_SYNC) { out.written(1); @@ -1386,6 +1436,7 @@ struct derandomizer: runnable if (sync != (MPEG_SYNC ^ MPEG_SYNC_CORRUPTED)) if (sch->debug) fprintf(stderr, "(%02x)", sync); + out.wr()->data[1] |= 0x80; // Set the Transport Error Indicator bit // We could output corrupted packets here, in case the // MPEG decoder can use them somehow. @@ -1393,7 +1444,8 @@ struct derandomizer: runnable } } } -private: + + private: u8 pattern[188 * 8], *pattern_end, *pos; pipereader in; pipewriter out; @@ -1407,10 +1459,10 @@ private: // This version implements puncturing by expanding the trellis. // TBD Compare performance vs skipping updates in a 1/2 trellis. -struct viterbi_sync: runnable +struct viterbi_sync : runnable { typedef uint8_t TS, TCS, TUS; - typedef int32_t TBM; // Only 16 bits per IQ, but several IQ per Viterbi CS + typedef int32_t TBM; // Only 16 bits per IQ, but several IQ per Viterbi CS typedef int32_t TPM; typedef viterbi_dec_interface dvb_dec_interface; @@ -1444,59 +1496,69 @@ struct viterbi_sync: runnable typedef trellis trellis_56; typedef viterbi_dec dvb_dec_56; - // QPSK 7/8: 6 bits of state, 7 bits in, 8 bits out + // 7/8: 6 bits of state, 7 bits in, 8 bits out typedef bitpath path_78; typedef trellis trellis_78; typedef viterbi_dec dvb_dec_78; -private: - pipereader in; + private: + pipereader in; pipewriter out; - cstln_lut<256> *cstln; + cstln_lut *cstln; fec_spec *fec; - int bits_per_symbol; // Bits per IQ symbol (not per coded symbol) + int bits_per_symbol; // Bits per IQ symbol (not per coded symbol) int nsyncs; int nshifts; + struct sync { int shift; dvb_dec_interface *dec; - TCS *map; // [nsymbols] - }*syncs; // [nsyncs] - IncrementalArray m_totaldiscr; + TCS *map; // [nsymbols] + } * syncs; // [nsyncs] + int current_sync; static const int chunk_size = 128; int resync_phase; -public: + + public: int resync_period; - viterbi_sync(scheduler *sch, pipebuf &_in, - pipebuf &_out, cstln_lut<256> *_cstln, code_rate cr) : - runnable(sch, "viterbi_sync"), in(_in), out(_out, chunk_size), cstln( - _cstln), current_sync(0), resync_phase(0), resync_period(32) // 1/32 = 9% synchronization overhead TBD + viterbi_sync(scheduler *sch, + pipebuf &_in, + pipebuf &_out, + cstln_lut *_cstln, + code_rate cr) : runnable(sch, "viterbi_sync"), + in(_in), + out(_out, chunk_size), + cstln(_cstln), + current_sync(0), + resync_phase(0), + resync_period(32) // 1/32 = 9% synchronization overhead TBD { bits_per_symbol = log2i(cstln->nsymbols); fec = &fec_specs[cr]; + { // Sanity check: FEC block size must be a multiple of label size. int symbols_per_block = fec->bits_out / bits_per_symbol; if (bits_per_symbol * symbols_per_block != fec->bits_out) - { - fail("viterbi_sync::viterbi_sync", "Code rate not suitable for this constellation"); - return; - } + fail("Code rate not suitable for this constellation"); } + int nconj; + switch (cstln->nsymbols) { case 2: nconj = 1; - break; // Conjugation is not relevant for BPSK + break; // Conjugation is not relevant for BPSK default: nconj = 2; break; } int nrotations; + switch (cstln->nsymbols) { case 2: @@ -1509,6 +1571,7 @@ public: nrotations = cstln->nrotations; break; } + nshifts = fec->bits_out / bits_per_symbol; nsyncs = nconj * nrotations * nshifts; @@ -1517,7 +1580,6 @@ public: // polarity inversion. We could reduce nsyncs. syncs = new sync[nsyncs]; - m_totaldiscr.allocate(nsyncs); for (int s = 0; s < nsyncs; ++s) { @@ -1526,11 +1588,12 @@ public: int conj = (s / nrotations) % nconj; int shift = s / nrotations / nconj; syncs[s].shift = shift; + if (shift) // Reuse identical map syncs[s].map = syncs[conj * nrotations + rot].map; else syncs[s].map = init_map(conj, - 2 * M_PI * rot / cstln->nrotations); + 2 * M_PI * rot / cstln->nrotations); #if 0 fprintf(stderr, "sync %3d: conj%d offs%d rot%d/%d map:", s, conj, syncs[s].shift, rot, cstln->nrotations); @@ -1591,10 +1654,8 @@ public: } else { - fail("viterbi_sync::viterbi_sync", "CR not supported"); - return; + fail("CR not supported"); } - } TCS *init_map(bool conj, float angle) @@ -1603,31 +1664,37 @@ public: // Here we simply tabulate systematically. TCS *map = new TCS[cstln->nsymbols]; float ca = cosf(angle), sa = sinf(angle); + for (int i = 0; i < cstln->nsymbols; ++i) { int8_t I = cstln->symbols[i].re; int8_t Q = cstln->symbols[i].im; + if (conj) Q = -Q; + int8_t RI = I * ca - Q * sa; int8_t RQ = I * sa + Q * ca; - cstln_lut<256>::result *pr = cstln->lookup(RI, RQ); - map[i] = pr->ss.symbol; + cstln_lut::result *pr = cstln->lookup(RI, RQ); + map[i] = pr->ss.nearest; } + return map; } - inline TUS update_sync(int s, softsymbol *pin, TPM *discr) + inline TUS update_sync(int s, eucl_ss *pin, TPM *discr) { // Read one FEC ouput block pin += syncs[s].shift; TCS cs = 0; TBM cost = 0; + for (int i = 0; i < nshifts; ++i, ++pin) { - cs = (cs << bits_per_symbol) | syncs[s].map[pin->symbol]; - cost += pin->cost; + cs = (cs << bits_per_symbol) | syncs[s].map[pin->nearest]; + cost -= pin->discr2; } + return syncs[s].dec->update(cs, cost, discr); } @@ -1639,26 +1706,27 @@ public: // Process [chunk_size] FEC blocks at a time - while ((long) in.readable() >= nshifts * chunk_size + (nshifts - 1) - && ((long) out.writable() * 8) >= fec->bits_in * chunk_size) + TPM *totaldiscr = new TPM[nsyncs]; + + while ((long)in.readable() >= nshifts * chunk_size + (nshifts - 1) && (long)out.writable() * 8 >= fec->bits_in * chunk_size) { - //TPM totaldiscr[nsyncs]; - TPM *totaldiscr = m_totaldiscr.m_array; for (int s = 0; s < nsyncs; ++s) totaldiscr[s] = 0; uint64_t outstream = 0; int nout = 0; - softsymbol *pin = in.rd(); - for (int blocknum = 0; blocknum < chunk_size; ++blocknum, pin += - nshifts) + eucl_ss *pin = in.rd(); + + for (int blocknum = 0; blocknum < chunk_size; ++blocknum, pin += nshifts) { TPM discr; TUS result = update_sync(current_sync, pin, &discr); outstream = (outstream << fec->bits_in) | result; nout += fec->bits_in; + if (blocknum >= discr_delay) totaldiscr[current_sync] += discr; + if (!resync_phase) { // Every [resync_period] chunks, also run the other decoders. @@ -1666,31 +1734,36 @@ public: { if (s == current_sync) continue; + TPM discr; - (void) update_sync(s, pin, &discr); + (void)update_sync(s, pin, &discr); + if (blocknum >= discr_delay) totaldiscr[s] += discr; } } + while (nout >= 8) { out.write(outstream >> (nout - 8)); nout -= 8; } - } // chunk_size + } // chunk_size + in.read(chunk_size * nshifts); + if (nout) - { - fail("viterbi_sync::run", "overlapping out"); - return; - } + fail("overlapping out"); + if (!resync_phase) { // Switch to another decoder ? int best = current_sync; + for (int s = 0; s < nsyncs; ++s) if (totaldiscr[s] > totaldiscr[best]) best = s; + if (best != current_sync) { if (sch->debug) @@ -1698,14 +1771,16 @@ public: current_sync = best; } } + if (++resync_phase >= resync_period) resync_phase = 0; } - } + delete[] totaldiscr; + } }; // viterbi_sync -}// namespace +} // namespace leansdr -#endif // LEANSDR_DVB_H +#endif // LEANSDR_DVB_H diff --git a/plugins/channelrx/demoddatv/leansdr/dvbs2.h b/plugins/channelrx/demoddatv/leansdr/dvbs2.h new file mode 100644 index 000000000..c8d0a3e5e --- /dev/null +++ b/plugins/channelrx/demoddatv/leansdr/dvbs2.h @@ -0,0 +1,2640 @@ +// This file is part of LeanSDR Copyright (C) 2016-2019 . +// See the toplevel README for more information. +// +// This program 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. +// +// This program 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 this program. If not, see . + +#ifndef LEANSDR_DVBS2_H +#define LEANSDR_DVBS2_H + +#include "leansdr/bch.h" +#include "leansdr/crc.h" +#include "leansdr/dvb.h" +#include "leansdr/ldpc.h" +#include "leansdr/sdr.h" +#include "leansdr/softword.h" + +namespace leansdr +{ + +// S2 THRESHOLDS (for comparing demodulators) + +static const int S2_MAX_ERR_SOF_INITIAL = 1; // 26 bits +static const int S2_MAX_ERR_SOF = 13; // 26 bits +static const int S2_MAX_ERR_PLSCODE = 8; // 64 bits, dmin=32 +static const int S2_MAX_ERR_PILOT = 10; // 36 bits + +static const int pilot_length = 36; + +// S2 SOF +// EN 302 307-1 section 5.5.2.1 SOF field + +template +struct s2_sof +{ + static const uint32_t VALUE = 0x18d2e82; + static const uint32_t MASK = 0x3ffffff; + static const int LENGTH = 26; + complex symbols[LENGTH]; + s2_sof() + { + for (int s = 0; s < LENGTH; ++s) + { + int angle = ((VALUE >> (LENGTH - 1 - s)) & 1) * 2 + (s & 1); // pi/2-BPSK + symbols[s].re = cstln_amp * cosf(M_PI / 4 + 2 * M_PI * angle / 4); + symbols[s].im = cstln_amp * sinf(M_PI / 4 + 2 * M_PI * angle / 4); + } + } +}; // s2_sof + +// S2 PLS CODES +// Precomputes the PLS code sequences. +// EN 302 307-1 section 5.5.2.4 PLS code + +template +struct s2_plscodes +{ + // PLS index format MODCOD[4:0]|SHORTFRAME|PILOTS + static const int COUNT = 128; + static const int LENGTH = 64; + uint64_t codewords[COUNT]; + complex symbols[COUNT][LENGTH]; + s2_plscodes() + { + uint32_t G[6] = {0x55555555, + 0x33333333, + 0x0f0f0f0f, + 0x00ff00ff, + 0x0000ffff, + 0xffffffff}; + for (int index = 0; index < COUNT; ++index) + { + uint32_t y = 0; + for (int row = 0; row < 6; ++row) + if ((index >> (6 - row)) & 1) + y ^= G[row]; + uint64_t code = 0; + for (int bit = 31; bit >= 0; --bit) + { + int yi = (y >> bit) & 1; + if (index & 1) + code = (code << 2) | (yi << 1) | (yi ^ 1); + else + code = (code << 2) | (yi << 1) | yi; + } + // Scrambling + code ^= SCRAMBLING; + // Store precomputed codeword. + codewords[index] = code; + // Also store as symbols. + for (int i = 0; i < LENGTH; ++i) + { + int yi = (code >> (LENGTH - 1 - i)) & 1; + int nyi = yi ^ (i & 1); + symbols[index][i].re = cstln_amp * (1 - 2 * nyi) / sqrtf(2); + symbols[index][i].im = cstln_amp * (1 - 2 * yi) / sqrtf(2); + } + } + } + static const uint64_t SCRAMBLING = 0x719d83c953422dfa; +}; // s2_plscodes + +// S2 SCRAMBLING +// Precomputes the symbol rotations for PL scrambling. +// EN 302 307-1 section 5.5.4 Physical layer scrambling + +struct s2_scrambling +{ + uint8_t Rn[131072]; // 0..3 (* 2pi/4) + s2_scrambling(int codenum = 0) + { + uint32_t stx = 0x00001, sty = 0x3ffff; + // x starts at codenum, wraps at index 2^18-1 by design + for (int i = 0; i < codenum; ++i) + stx = lfsr_x(stx); + // First half of sequence is LSB of scrambling angle + for (int i = 0; i < 131072; ++i) + { + int zn = (stx ^ sty) & 1; + Rn[i] = zn; + stx = lfsr_x(stx); + sty = lfsr_y(sty); + } + // Second half is MSB + for (int i = 0; i < 131072; ++i) + { + int zn = (stx ^ sty) & 1; + Rn[i] |= zn << 1; + stx = lfsr_x(stx); + sty = lfsr_y(sty); + } + } + uint32_t lfsr_x(uint32_t X) + { + int bit = ((X >> 7) ^ X) & 1; + return ((bit << 18) | X) >> 1; + } + uint32_t lfsr_y(uint32_t Y) + { + int bit = ((Y >> 10) ^ (Y >> 7) ^ (Y >> 5) ^ Y) & 1; + return ((bit << 18) | Y) >> 1; + } +}; // s2_scrambling + +// S2 BBSCRAMBLING +// Precomputes the xor pattern for baseband scrambling. +// EN 302 307-1 section 5.2.2 BB scrambling + +struct s2_bbscrambling +{ + s2_bbscrambling() + { + uint16_t st = 0x00a9; // 000 0000 1010 1001 (Fig 5 reversed) + for (int i = 0; i < sizeof(pattern); ++i) + { + uint8_t out = 0; + for (int n = 8; n--;) + { + int bit = ((st >> 13) ^ (st >> 14)) & 1; // Taps + out = (out << 1) | bit; // MSB first + st = (st << 1) | bit; // Feedback + } + pattern[i] = out; + } + } + void transform(const uint8_t *in, int bbsize, uint8_t *out) + { + for (int i = 0; i < bbsize; ++i) + out[i] = in[i] ^ pattern[i]; + } + + private: + uint8_t pattern[58192]; // Values 0..3 +}; // s2_bbscrambling + +// S2 PHYSICAL LAYER SIGNALLING + +struct s2_pls +{ + int modcod; // 0..31 + bool sf; + bool pilots; + int framebits() const { return sf ? 16200 : 64800; } +}; + +template +struct plslot +{ + static const int LENGTH = 90; + bool is_pls; + union { + s2_pls pls; + SOFTSYMB symbols[LENGTH]; + }; +}; + +// EN 302 307-1 section 5.5.2.2 MODCOD field +// EN 302 307-1 section 6 Error performance + +const struct modcod_info +{ + static const int MAX_SLOTS_PER_FRAME = 360; + static const int MAX_SYMBOLS_PER_FRAME = + (1 + MAX_SLOTS_PER_FRAME) * plslot::LENGTH + + ((MAX_SLOTS_PER_FRAME - 1) / 16) * pilot_length; + int nslots_nf; // Number of 90-symbol slots per normal frame + int nsymbols; // Symbols in the constellation + cstln_base::predef c; + code_rate rate; + // Ideal Es/N0 for normal frames + // EN 302 307 section 6 Error performance + float esn0_nf; + // Radii for APSK + // EN 302 307, section 5.4.3, Table 9 + // EN 302 307, section 5.4.4, Table 10 + float g1, g2, g3; +} modcod_infos[32] = { + { + 0, + }, + // 1 - 11 + {360, 4, cstln_base::QPSK, FEC14, -2.35}, + {360, 4, cstln_base::QPSK, FEC13, -1.24}, + {360, 4, cstln_base::QPSK, FEC25, -0.30}, + {360, 4, cstln_base::QPSK, FEC12, 1.00}, + {360, 4, cstln_base::QPSK, FEC35, 2.23}, + {360, 4, cstln_base::QPSK, FEC23, 3.10}, + {360, 4, cstln_base::QPSK, FEC34, 4.03}, + {360, 4, cstln_base::QPSK, FEC45, 4.68}, + {360, 4, cstln_base::QPSK, FEC56, 5.18}, + {360, 4, cstln_base::QPSK, FEC89, 6.20}, + {360, 4, cstln_base::QPSK, FEC910, 6.42}, + // 12 - 17 + {240, 8, cstln_base::PSK8, FEC35, 5.50}, + {240, 8, cstln_base::PSK8, FEC23, 6.62}, + {240, 8, cstln_base::PSK8, FEC34, 7.91}, + {240, 8, cstln_base::PSK8, FEC56, 9.35}, + {240, 8, cstln_base::PSK8, FEC89, 10.69}, + {240, 8, cstln_base::PSK8, FEC910, 10.98}, + // 18 - 23 + {180, 16, cstln_base::APSK16, FEC23, 8.97, 3.15}, + {180, 16, cstln_base::APSK16, FEC34, 10.21, 2.85}, + {180, 16, cstln_base::APSK16, FEC45, 11.03, 2.75}, + {180, 16, cstln_base::APSK16, FEC56, 11.61, 2.70}, + {180, 16, cstln_base::APSK16, FEC89, 12.89, 2.60}, + {180, 16, cstln_base::APSK16, FEC910, 13.13, 2.57}, + // 24 - 28 + {144, 32, cstln_base::APSK32, FEC34, 12.73, 2.84, 5.27}, + {144, 32, cstln_base::APSK32, FEC45, 13.64, 2.72, 4.87}, + {144, 32, cstln_base::APSK32, FEC56, 14.28, 2.64, 4.64}, + {144, 32, cstln_base::APSK32, FEC89, 15.69, 2.54, 4.33}, + {144, 32, cstln_base::APSK32, FEC910, 16.05, 2.53, 4.30}, + // 29 - 31 + { + 0, + }, + { + 0, + }, + { + 0, + }}; + +// Assert that a MODCOD number is valid +const modcod_info *check_modcod(int m) +{ + if (m < 0 || m > 31) + fail("Invalid MODCOD number"); + const modcod_info *r = &modcod_infos[m]; + if (!r->nslots_nf) + fail("Unsupported MODCOD"); + return r; +} + +// S2 FRAME TRANSMITTER + +template +struct s2_frame_transmitter : runnable +{ + s2_frame_transmitter(scheduler *sch, + pipebuf> &_in, + pipebuf> &_out) + : runnable(sch, "S2 frame transmitter"), + in(_in), out(_out, modcod_info::MAX_SYMBOLS_PER_FRAME) + { + float amp = cstln_amp / sqrtf(2); + qsymbols[0].re = +amp; + qsymbols[0].im = +amp; + qsymbols[1].re = +amp; + qsymbols[1].im = -amp; + qsymbols[2].re = -amp; + qsymbols[2].im = +amp; + qsymbols[3].re = -amp; + qsymbols[3].im = -amp; + } + void run() + { + while (in.readable() >= 1) + { + plslot *pin = in.rd(); + if (!pin->is_pls) + fail("Expected PLS pseudo-slot"); + s2_pls *pls = &pin->pls; + const modcod_info *mcinfo = check_modcod(pls->modcod); + int nslots = (pls->sf ? mcinfo->nslots_nf / 4 : mcinfo->nslots_nf); + if (in.readable() < 1 + nslots) + break; + // Require room for BBHEADER + slots + optional pilots. + int nsymbols = ((1 + nslots) * plslot::LENGTH + + (pls->pilots ? ((nslots - 1) / 16) * pilot_length : 0)); + if (out.writable() < nsymbols) + break; + update_cstln(mcinfo); + int nw = run_frame(pls, mcinfo, pin + 1, nslots, out.wr()); + if (nw != nsymbols) + fail("Bug: s2_frame_transmitter overflow"); + in.read(1 + nslots); + out.written(nsymbols); + } + } + int run_frame(s2_pls *pls, const modcod_info *mcinfo, + const plslot *pin, int nslots, + complex *pout) + { + complex *pout0 = pout; // For sanity check + // PLHEADER: SOF AND PLSCODE + // EN 302 307-1 section 5.5.2 PL signalling + memcpy(pout, sof.symbols, sof.LENGTH * sizeof(*pout)); + pout += sof.LENGTH; + int pls_index = (pls->modcod << 2) | (pls->sf << 1) | pls->pilots; + memcpy(pout, plscodes.symbols[pls_index], plscodes.LENGTH * sizeof(*pout)); + pout += plscodes.LENGTH; + // Slots and pilots + int till_next_pilot = pls->pilots ? 16 : nslots; + uint8_t *scr = &scrambling.Rn[0]; + for (int S = 0; S < nslots; ++S, ++pin, --till_next_pilot) + { + if (till_next_pilot == 0) + { + // Send pilot + for (int s = 0; s < pilot_length; ++s, ++scr, ++pout) + scramble(&qsymbols[0], *scr, pout); + till_next_pilot = 16; + } + // Send slot + if (pin->is_pls) + fail("s2_frame_transmitter: bad input sequence"); + const hard_ss *ps = pin->symbols; + for (int s = 0; s < pin->LENGTH; ++s, ++ps, ++scr, ++pout) + scramble(&csymbols[*ps], *scr, pout); + } + return pout - pout0; + } + inline void scramble(const complex *src, uint8_t r, complex *dst) + { + switch (r) + { + case 3: + dst->re = src->im; + dst->im = -src->re; + break; + case 2: + dst->re = -src->re; + dst->im = -src->im; + break; + case 1: + dst->re = -src->im; + dst->im = src->re; + break; + default: + *dst = *src; + } + } + + private: + pipereader> in; + pipewriter> out; + cstln_lut *cstln; // NULL initially + complex *csymbols; // Valid iff cstln is valid. RMS cstln_amp. + void update_cstln(const modcod_info *mcinfo) + { + if (!cstln || cstln->nsymbols != mcinfo->nsymbols) + { + if (cstln) + { + fprintf(stderr, "Warning: Variable MODCOD is inefficient\n"); + delete cstln; + delete csymbols; + } + if (sch->debug) + fprintf(stderr, "Building constellation %d\n", mcinfo->nsymbols); + // TBD Different Es/N0 for short frames ? + cstln = new cstln_lut(mcinfo->c, mcinfo->esn0_nf, + mcinfo->g1, mcinfo->g2, mcinfo->g3); + csymbols = new complex[cstln->nsymbols]; + for (int s = 0; s < cstln->nsymbols; ++s) + { + csymbols[s].re = cstln->symbols[s].re; + csymbols[s].im = cstln->symbols[s].im; + } + } + } + complex qsymbols[4]; // RMS cstln_amp + s2_sof sof; + s2_plscodes plscodes; + s2_scrambling scrambling; +}; // s2_frame_transmitter + +// S2 FRAME RECEIVER + +static int pl_errors = 0, pl_symbols = 0; + +#define TEST_DIVERSITY 0 + +template +struct s2_frame_receiver : runnable +{ + sampler_interface *sampler; + int meas_decimation; + float Ftune; // Tuning bias in cycles per symbol + float Fm; // Baud rate in Hz, for debug messages only. TBD remove. + bool strongpls; + static const int MAX_SYMBOLS_PER_FRAME = + (1 + modcod_info::MAX_SLOTS_PER_FRAME) * plslot::LENGTH + + ((modcod_info::MAX_SLOTS_PER_FRAME - 1) / 16) * pilot_length; + s2_frame_receiver(scheduler *sch, + sampler_interface *_sampler, + pipebuf> &_in, + pipebuf> &_out, + pipebuf *_freq_out = NULL, + pipebuf *_ss_out = NULL, + pipebuf *_mer_out = NULL, + pipebuf> *_cstln_out = NULL, + pipebuf> *_cstln_pls_out = NULL, + pipebuf> *_symbols_out = NULL, + pipebuf *_state_out = NULL) + : runnable(sch, "S2 frame receiver"), + sampler(_sampler), + meas_decimation(1048576), + Ftune(0), Fm(0), + strongpls(false), + in_power(0), ev_power(0), agc_gain(1), agc_bw(1e-3), + nsyncs(0), + cstln(NULL), + in(_in), out(_out, 1 + modcod_info::MAX_SLOTS_PER_FRAME), + meas_count(0), + freq_out(opt_writer(_freq_out)), + ss_out(opt_writer(_ss_out)), + mer_out(opt_writer(_mer_out)), + cstln_out(opt_writer(_cstln_out, 1024)), + cstln_pls_out(opt_writer(_cstln_pls_out, 1024)), + symbols_out(opt_writer(_symbols_out, MAX_SYMBOLS_PER_FRAME)), + state_out(opt_writer(_state_out)), + report_state(false), + scrambling(0) + { + // Constellation for PLS + qpsk = new cstln_lut(cstln_base::QPSK); + add_syncs(qpsk); + + init_coarse_freq(); + +#if TEST_DIVERSITY + fprintf(stderr, "** DEBUG: Diversity test mode (slower)\n"); +#endif + } + + enum + { + COARSE_FREQ, + FRAME_SEARCH, + FRAME_LOCKED, + } state; + + float min_freqw16, max_freqw16; + + // State during COARSE_FREQ + complex diffcorr; + int coarse_count; + + // State during FRAME_SEARCH and FRAME_LOCKED + float freqw16; // Carrier frequency initialized by COARSE_FREQ + float phase16; // Estimated phase of carrier at next symbol + + float mu; // Time to next symbol, in samples + float omega; // Samples per symbol + + void run() + { + // Require enough samples to detect one plheader, + // TBD margin ? + int min_samples = (1 + MAX_SYMBOLS_PER_FRAME + + plslot::LENGTH) * + omega * 2; + while (in.readable() >= min_samples + sampler->readahead() && + out.writable() >= 1 + modcod_info::MAX_SLOTS_PER_FRAME && + opt_writable(freq_out, 1) && + opt_writable(ss_out, 1) && + opt_writable(mer_out, 1) && + opt_writable(symbols_out, MAX_SYMBOLS_PER_FRAME) && + opt_writable(state_out, 1)) + { + if (report_state) + { + // Report unlocked state on first invocation. + opt_write(state_out, 0); + report_state = false; + } + switch (state) + { + case COARSE_FREQ: + run_frame_coarse(); + break; + case FRAME_SEARCH: + run_frame_search(); + break; + case FRAME_LOCKED: + run_frame_locked(); + break; + } + } + } + + // Initial state + void init_coarse_freq() + { + diffcorr = 0; + coarse_count = 0; + memset(hist, 0, sizeof(hist)); + state = COARSE_FREQ; + } + + // State transtion + void enter_coarse_freq() + { + opt_write(state_out, 0); + init_coarse_freq(); + } + + void run_frame_coarse() + { + freqw16 = 65536 * Ftune; + min_freqw16 = freqw16 - 65536.0 / 9; + max_freqw16 = freqw16 + 65536.0 / 9; + + complex *pin = in.rd(); + complex p = *pin++; + int nsamples = MAX_SYMBOLS_PER_FRAME * omega; + for (int s = nsamples; s--; ++pin) + { + complex n = *pin; + diffcorr.re += p.re * n.re + p.im * n.im; + diffcorr.im += p.re * n.im - p.im * n.re; + p = n; + } + in.read(nsamples); + ++coarse_count; + if (coarse_count == 50) + { + float freqw = atan2f(diffcorr.im, diffcorr.re) * omega; + fprintf(stderr, "COARSE(%d): %f rad/symb (%.0f Hz at %.0f baud)\n", + coarse_count, freqw, freqw * Fm / (2 * M_PI), Fm); +#if 0 + freqw16 = freqw * 65536 / (2*M_PI); +#else + fprintf(stderr, "Ignoring coarse det, using %f\n", freqw16 * Fm / 65536); +#endif + enter_frame_search(); + } + } + + // State transtion + void enter_frame_search() + { + opt_write(state_out, 0); + mu = 0; + phase16 = 0; + if (sch->debug) + fprintf(stderr, "ACQ\n"); + state = FRAME_SEARCH; + } + + void run_frame_search() + { + complex *psampled; + if (cstln_out && cstln_out->writable() >= 1024) + psampled = cstln_out->wr(); + else + psampled = NULL; + + // Preserve float precision + phase16 -= 65536 * floor(phase16 / 65536); + + int nsymbols = MAX_SYMBOLS_PER_FRAME; // TBD Adjust after PLS decoding + + sampler_state ss = {in.rd(), mu, phase16, freqw16}; + sampler->update_freq(ss.fw16 / omega); + + if (!in_power) + init_agc(ss.p, 64); + update_agc(); + + for (int s = 0; s < nsymbols; ++s) + { + complex p0 = interp_next(&ss); + track_agc(p0); + complex p = p0 * agc_gain; + + // Constellation plot + if (psampled && s < 1024) + *psampled++ = p; + + // Demodulate everything as QPSK. + // Occasionally it locks onto 8PSK at offet 2pi/16. + uint8_t symb = track_symbol(&ss, p, qpsk, 1); + + // Feed symbol into all synchronizers. + for (sync *ps = syncs; ps < syncs + nsyncs; ++ps) + { + ps->hist = (ps->hist << 1) | ((ps->tobpsk >> symb) & 1); + int errors = hamming_weight((ps->hist & sof.MASK) ^ sof.VALUE); + if (errors <= S2_MAX_ERR_SOF_INITIAL) + { + if (sch->debug2) + fprintf(stderr, "Found SOF+%d at %d offset %f\n", + errors, s, ps->offset16); + ss.ph16 += ps->offset16; + in.read(ss.p - in.rd()); + mu = ss.mu; + phase16 = ss.ph16; + freqw16 = ss.fw16; + if (psampled) + cstln_out->written(psampled - cstln_out->wr()); + enter_frame_locked(); + return; + } + } + ss.normalize(); + } + + // Write back sampler progress + in.read(ss.p - in.rd()); + mu = ss.mu; + phase16 = ss.ph16; + freqw16 = ss.fw16; + if (psampled) + cstln_out->written(psampled - cstln_out->wr()); + } + + // State transtion + void enter_frame_locked() + { + opt_write(state_out, 1); + if (sch->debug) + fprintf(stderr, "LOCKED\n"); + state = FRAME_LOCKED; + } + + // Note: Starts after SOF + + struct sampler_state + { + complex *p; // Pointer to samples + float mu; // Time of next symbol, counted from p + float ph16; // Carrier phase at next symbol, cycles*65536 + float fw16; // Carrier frequency, cycles per symbol * 65536 + uint8_t *scr; // Position in scrambling sequeence + void skip_symbols(int ns, float omega) + { + mu += omega * ns; + ph16 += fw16 * ns; + scr += ns; + } + void normalize() + { + ph16 = fmodf(ph16, 65536.0f); // Rounding direction irrelevant + } + }; + +#define xfprintf(...) \ + { \ + } + //#define xfprintf fprintf + + void run_frame_locked() + { + complex *psampled; + if (cstln_out && cstln_out->writable() >= 1024) + psampled = cstln_out->wr(); + else + psampled = NULL; + complex *psampled_pls; + if (cstln_pls_out && cstln_pls_out->writable() >= 1024) + psampled_pls = cstln_pls_out->wr(); + else + psampled_pls = NULL; + +#if TEST_DIVERSITY + complex *psymbols = symbols_out ? symbols_out->wr() : NULL; + float scale_symbols = 1.0 / cstln_amp; +#endif + + xfprintf(stderr, "lock0step fw= %f (%.0f Hz) mu=%f\n", + freqw16, freqw16 * Fm / 65536, mu); + + sampler_state ss = {in.rd(), mu, phase16, freqw16, scrambling.Rn}; + sampler->update_freq(ss.fw16 / omega); + + update_agc(); + + // Read PLSCODE + + uint64_t plscode = 0; + complex pls_symbols[plscodes.LENGTH]; + for (int s = 0; s < plscodes.LENGTH; ++s) + { + complex p = interp_next(&ss) * agc_gain; +#if TEST_DIVERSITY + if (psymbols) + *psymbols++ = p * scale_symbols; +#endif + pls_symbols[s] = p; + if (psampled_pls) + *psampled_pls++ = p; + int bit = (p.im < 1); // TBD suboptimal + plscode = (plscode << 1) | bit; + } + int pls_index = -1; + int pls_errors = S2_MAX_ERR_PLSCODE + 1; // dmin=32 + // TBD: Optimiser + for (int i = 0; i < plscodes.COUNT; ++i) + { + int e = hamming_weight(plscode ^ plscodes.codewords[i]); + if (e < pls_errors) + { + pls_errors = e; + pls_index = i; + } + } + if (pls_index < 0) + { + if (sch->debug2) + fprintf(stderr, "Too many errors in plheader (%d)\n", pls_errors); + in.read(ss.p - in.rd()); + enter_frame_search(); + return; + } + + // Adjust phase with PLS + complex pls_corr = conjprod(plscodes.symbols[pls_index], + pls_symbols, plscodes.LENGTH); + ss.normalize(); + align_phase(&ss, pls_corr); + + s2_pls pls; + pls.modcod = pls_index >> 2; // Guaranteed 0..31 + pls.sf = pls_index & 2; + pls.pilots = pls_index & 1; + xfprintf(stderr, "PLS: modcod %d, short=%d, pilots=%d (%d errors)\n", + pls.modcod, pls.sf, pls.pilots, pls_errors); + const modcod_info *mcinfo = &modcod_infos[pls.modcod]; + if (!mcinfo->nslots_nf) + { + fprintf(stderr, "Unsupported or corrupted MODCOD\n"); + in.read(ss.p - in.rd()); + enter_frame_search(); + return; + } +#if 1 // TBD use fec_infos + if (pls.sf && mcinfo->rate == FEC910) + { + fprintf(stderr, "Unsupported or corrupted FEC\n"); + in.read(ss.p - in.rd()); + enter_frame_search(); + return; + } +#endif + + // TBD Comparison of nsymbols is insufficient for DVB-S2X. + if (!cstln || cstln->nsymbols != mcinfo->nsymbols) + { + if (cstln) + { + fprintf(stderr, "Warning: Variable MODCOD is inefficient\n"); + delete cstln; + } + fprintf(stderr, "Creating LUT for %s ratecode %d\n", + cstln_base::names[mcinfo->c], mcinfo->rate); + cstln = new cstln_lut(mcinfo->c, mcinfo->esn0_nf, + mcinfo->g1, mcinfo->g2, mcinfo->g3); +#if 0 + fprintf(stderr, "Dumping constellation LUT to stdout.\n"); + cstln->dump(stdout); +#endif + } + + int S = pls.sf ? mcinfo->nslots_nf / 4 : mcinfo->nslots_nf; + + plslot *pout = out.wr(), *pout0 = pout; + + // Output special slot with PLS information + pout->is_pls = true; + pout->pls = pls; + ++pout; + + // Read slots and pilots + + int pilot_errors = 0; + + // Slots to skip until next PL slot (pilot or sof) + int till_next_pls = pls.pilots ? 16 : S; + + for (int slots = S; slots--; ++pout, --till_next_pls) + { + if (till_next_pls == 0) + { + // Read pilot + int errors = 0; + complex corr = 0; + for (int s = 0; s < pilot_length; ++s) + { + complex p0 = interp_next(&ss); + track_agc(p0); + complex p = p0 * agc_gain; +#if TEST_DIVERSITY + if (psymbols) + *psymbols++ = p * scale_symbols; +#endif + (void)track_symbol(&ss, p, qpsk, 1); + if (psampled_pls) + *psampled_pls++ = p; + complex d = descramble(&ss, p); + if (d.im < 0 || d.re < 0) + ++errors; + corr.re += d.re + d.im; + corr.im += d.im - d.re; + } + if (errors > S2_MAX_ERR_PILOT) + { + if (sch->debug2) + fprintf(stderr, "Too many errors in pilot (%d/36)\n", errors); + in.read(ss.p - in.rd()); + enter_frame_search(); + return; + } + pilot_errors += errors; + ss.normalize(); + align_phase(&ss, corr); + till_next_pls = 16; + } + // Read slot + pout->is_pls = false; + complex p; // Export last symbols for cstln_out + for (int s = 0; s < pout->LENGTH; ++s) + { + p = interp_next(&ss) * agc_gain; +#if TEST_DIVERSITY + if (psymbols) + *psymbols++ = p * scale_symbols; +#endif +#if 1 || TEST_DIVERSITY + (void)track_symbol(&ss, p, cstln, 0); // SLOW +#endif + complex d = descramble(&ss, p); +#if 0 // Slow + SOFTSYMB *symb = &cstln->lookup(d.re, d.im)->ss; +#else // Avoid scaling floats. May wrap at very low SNR. + SOFTSYMB *symb = &cstln->lookup((int)d.re, (int)d.im)->ss; +#endif + pout->symbols[s] = *symb; + } + if (psampled) + *psampled++ = p; + } // slots + + // Read SOF + + memset(hist, 0, sizeof(hist)); + complex sof_corr = 0; + uint32_t sofbits = 0; + for (int s = 0; s < sof.LENGTH; ++s) + { + complex p0 = interp_next(&ss); + track_agc(p0); + complex p = p0 * agc_gain; +#if TEST_DIVERSITY + if (psymbols) + *psymbols++ = p * scale_symbols; +#endif + uint8_t symb = track_symbol(&ss, p, qpsk, 1); + if (psampled_pls) + *psampled_pls++ = p; + int bit = (p.im < 0); // suboptimal + sofbits = (sofbits << 1) | bit; + sof_corr += conjprod(sof.symbols[s], p); + } + int sof_errors = hamming_weight(sofbits ^ sof.VALUE); + if (sof_errors >= S2_MAX_ERR_SOF) + { + if (sch->debug2) + fprintf(stderr, "Too many errors in SOF (%d/26)\n", sof_errors); + in.read(ss.p - in.rd()); + enter_coarse_freq(); + return; + } + ss.normalize(); + align_phase(&ss, sof_corr); + + // Commit whole frame after final SOF. + out.written(pout - pout0); + + // Write back sampler progress + meas_count += ss.p - in.rd(); + in.read(ss.p - in.rd()); + mu = ss.mu; + phase16 = ss.ph16; + freqw16 = ss.fw16; + + // Measurements + if (psampled) + cstln_out->written(psampled - cstln_out->wr()); + if (psampled_pls) + cstln_pls_out->written(psampled_pls - cstln_pls_out->wr()); +#if TEST_DIVERSITY + if (psymbols) + symbols_out->written(psymbols - symbols_out->wr()); +#endif + if (meas_count >= meas_decimation) + { + opt_write(freq_out, freqw16 / 65536 / omega); + opt_write(ss_out, in_power); + // TBD Adjust if cfg.strongpls + float mer = ev_power ? (float)cstln_amp * cstln_amp / ev_power : 1; + opt_write(mer_out, 10 * logf(mer) / logf(10)); + meas_count -= meas_decimation; + } + + int all_errors = pls_errors + pilot_errors + sof_errors; + int max_errors = plscodes.LENGTH + sof.LENGTH; + if (pls.pilots) + max_errors += ((S - 1) / 16) * pilot_length; + + xfprintf(stderr, "success fw= %f (%.0f Hz) mu= %f " + "errors=%d/64+%d+%d/26 = %2d/%d\n", + freqw16, freqw16 * Fm / 65536, mu, + pls_errors, pilot_errors, sof_errors, all_errors, max_errors); + pl_errors += all_errors; + pl_symbols += max_errors; + } + + void shutdown() + { + fprintf(stderr, "PL SER: %f ppm\n", pl_errors / (pl_symbols + 1e-6) * 1e6); + } + + void init_agc(const complex *buf, int n) + { + in_power = 0; + for (int i = 0; i < n; ++i) + in_power += cnorm2(buf[i]); + in_power /= n; + } + void track_agc(const complex &p) + { + float in_p = p.re * p.re + p.im * p.im; + in_power = in_p * agc_bw + in_power * (1.0f - agc_bw); + } + + void update_agc() + { + float in_amp = gen_sqrt(in_power); + if (!in_amp) + return; + if (!strongpls || !cstln) + { + // Match RMS amplitude + agc_gain = cstln_amp / in_amp; + } + else + { + // Match peak amplitude + agc_gain = cstln_amp / cstln->amp_max / in_amp; + } + } + + complex descramble(sampler_state *ss, const complex &p) + { + int r = *ss->scr++; + complex res; + switch (r) + { + case 3: + res.re = -p.im; + res.im = p.re; + break; + case 2: + res.re = -p.re; + res.im = -p.im; + break; + case 1: + res.re = p.im; + res.im = -p.re; + break; + default: + res = p; + } + return res; + } + + // Interpolator + + inline complex interp_next(sampler_state *ss) + { + // Skip to next sample + while (ss->mu >= 1) + { + ++ss->p; + ss->mu -= 1.0f; + } + // Interpolate +#if 0 + // Interpolate linearly then derotate. + // This will fail with large carrier offsets (e.g. --tune). + float cmu = 1.0f - ss->mu; + complex s(ss->p[0].re*cmu + ss->p[1].re*ss->mu, + ss->p[0].im*cmu + ss->p[1].im*ss->mu); + ss->mu += omega; + // Derotate + const complex &rot = trig.expi(-ss->ph16); + ss->ph16 += ss->fw16; + return rot * s; +#else + // Use generic interpolator + complex s = sampler->interp(ss->p, ss->mu, ss->ph16); + ss->mu += omega; + ss->ph16 += ss->fw16; + return s; +#endif + } + + void align_phase(sampler_state *ss, const complex &c) + { +#if 0 + // Reference implementation + float err = atan2f(c.im,c.re) * (65536/(2*M_PI)); +#else + // Same performance as atan2f, faster + if (!c.re) + return; + float err = c.im / c.re * (65536 / (2 * M_PI)); +#endif + ss->ph16 += err; + } + + inline uint8_t track_symbol(sampler_state *ss, const complex &p, + cstln_lut *c, int mode) + { + static struct + { + float kph, kfw, kmu; + } gains[2] = { + {4e-2, 1e-4, 0.001 / (cstln_amp * cstln_amp)}, + {4e-2, 1e-4, 0.001 / (cstln_amp * cstln_amp)}}; + // Decision + typename cstln_lut::result *cr = c->lookup(p.re, p.im); + // Carrier tracking + ss->ph16 += cr->phase_error * gains[mode].kph; + ss->fw16 += cr->phase_error * gains[mode].kfw; + if (ss->fw16 < min_freqw16) + ss->fw16 = min_freqw16; + if (ss->fw16 > max_freqw16) + ss->fw16 = max_freqw16; + // Phase tracking + hist[2] = hist[1]; + hist[1] = hist[0]; + hist[0].p = p; + complex *cp = &c->symbols[cr->symbol]; + hist[0].c.re = cp->re; + hist[0].c.im = cp->im; + float muerr = + ((hist[0].p.re - hist[2].p.re) * hist[1].c.re + + (hist[0].p.im - hist[2].p.im) * hist[1].c.im) - + ((hist[0].c.re - hist[2].c.re) * hist[1].p.re + + (hist[0].c.im - hist[2].c.im) * hist[1].p.im); + float mucorr = muerr * gains[mode].kmu; + const float max_mucorr = 0.1; + // TBD Optimize out statically + if (mucorr < -max_mucorr) + mucorr = -max_mucorr; + if (mucorr > max_mucorr) + mucorr = max_mucorr; + ss->mu += mucorr; + // Error vector for MER + complex ev(p.re - cp->re, p.im - cp->im); + float ev_p = ev.re * ev.re + ev.im * ev.im; + ev_power = ev_p * agc_bw + ev_power * (1.0f - agc_bw); + return cr->symbol; + } + + struct + { + complex p; // Received symbol + complex c; // Matched constellation point + } hist[3]; + + public: + float in_power, ev_power; + float agc_gain; + float agc_bw; + cstln_lut *qpsk; + static const int MAXSYNCS = 8; + struct sync + { + uint16_t nsmask; // bitmask of cstln orders for which this sync is used + uint64_t tobpsk; // Bitmask from cstln symbols to pi/2-BPSK bits + float offset16; // Phase offset 0..65536 + uint32_t hist; // For SOF detection + } syncs[MAXSYNCS], *current_sync; + int nsyncs; + s2_plscodes plscodes; + cstln_lut *cstln; + // Initialize synchronizers for an arbitrary constellation. + void add_syncs(cstln_lut *c) + { + int random_decision = 0; + int nrot = c->nrotations; +#if 0 + if ( nrot == 4 ) { + fprintf(stderr, "Special case for 8PSK locking as QPSK pi/8\n"); + nrot = 8; + } +#endif + for (int r = 0; r < nrot; ++r) + { + if (nsyncs == MAXSYNCS) + fail("Bug: too many syncs"); + sync *s = &syncs[nsyncs++]; + s->offset16 = 65536.0 * r / nrot; + float angle = -2 * M_PI * r / nrot; + s->tobpsk = 0; + for (int i = c->nsymbols; i--;) + { + complex p = c->symbols[i]; + float re = p.re * cosf(angle) - p.im * sinf(angle); + float im = p.re * sinf(angle) + p.im * cosf(angle); + int bit; + if (im > 1) + bit = 0; + else if (im < -1) + bit = 1; + else + { + bit = random_decision; + random_decision ^= 1; + } // Near 0 + s->tobpsk = (s->tobpsk << 1) | bit; + } + s->hist = 0; + } + } + + trig16 trig; + modcod_info *mcinfo; + pipereader> in; + pipewriter> out; + int meas_count; + pipewriter *freq_out, *ss_out, *mer_out; + pipewriter> *cstln_out; + pipewriter> *cstln_pls_out; + pipewriter> *symbols_out; + pipewriter *state_out; + bool report_state; + // S2 constants + s2_scrambling scrambling; + s2_sof sof; + // Max size of one frame + // static const int MAX_SLOTS = 360; + static const int MAX_SLOTS = 240; // DEBUG match test signal + static const int MAX_SYMBOLS = + (1 + MAX_SLOTS) * plslot::LENGTH + ((MAX_SLOTS - 1) / 16) * pilot_length; +}; // s2_frame_receiver + +template +struct fecframe +{ + s2_pls pls; + SOFTBYTE bytes[64800 / 8]; // Contains 16200/8 or 64800/8 bytes. +}; + +// S2 INTERLEAVER +// EN 302 307-1 section 5.3.3 Bit Interleaver + +struct s2_interleaver : runnable +{ + s2_interleaver(scheduler *sch, + pipebuf> &_in, + pipebuf> &_out) + : runnable(sch, "S2 interleaver"), + in(_in), out(_out, 1 + 360) + { + } + void run() + { + while (in.readable() >= 1) + { + const s2_pls *pls = &in.rd()->pls; + const modcod_info *mcinfo = check_modcod(pls->modcod); + int nslots = pls->sf ? mcinfo->nslots_nf / 4 : mcinfo->nslots_nf; + if (out.writable() < 1 + nslots) + return; + const hard_sb *pbytes = in.rd()->bytes; + // Output pseudo slot with PLS. + plslot *ppls = out.wr(); + ppls->is_pls = true; + ppls->pls = *pls; + out.written(1); + // Interleave + plslot *pout = out.wr(); + if (mcinfo->nsymbols == 4) + serialize_qpsk(pbytes, nslots, pout); + else + { + int bps = log2(mcinfo->nsymbols); + int rows = pls->framebits() / bps; + if (mcinfo->nsymbols == 8 && mcinfo->rate == FEC35) + interleave(bps, rows, pbytes, nslots, false, pout); + else + interleave(bps, rows, pbytes, nslots, true, pout); + } + in.read(1); + out.written(nslots); + } + } + + private: + // Fill slots with serialized QPSK symbols, MSB first. + static void serialize_qpsk(const hard_sb *pin, int nslots, + plslot *pout) + { +#if 0 // For reference + hard_sb acc; + int nacc = 0; + for ( ; nslots; --nslots,++pout ) { + pout->is_pls = false; + hard_ss *ps = pout->symbols; + for ( int ns=pout->LENGTH; ns--; ++ps ) { + if ( nacc < 2 ) { acc=*pin++; nacc=8; } + *ps = acc>>6; + acc <<= 2; + nacc -= 2; + } + } + if ( nacc ) fail("Bug: s2_interleaver"); +#else + if (nslots % 2) + fatal("Bug: Truncated byte"); + for (; nslots; nslots -= 2) + { + hard_sb b; + hard_ss *ps; + // Slot 0 (mod 2) + pout->is_pls = false; + ps = pout->symbols; + for (int i = 0; i < 22; ++i) + { + b = *pin++; + *ps++ = (b >> 6); + *ps++ = (b >> 4) & 3; + *ps++ = (b >> 2) & 3; + *ps++ = (b)&3; + } + b = *pin++; + *ps++ = (b >> 6); + *ps++ = (b >> 4) & 3; + // Slot 1 (mod 2) + ++pout; + pout->is_pls = false; + ps = pout->symbols; + *ps++ = (b >> 2) & 3; + *ps++ = (b)&3; + for (int i = 0; i < 22; ++i) + { + b = *pin++; + *ps++ = (b >> 6); + *ps++ = (b >> 4) & 3; + *ps++ = (b >> 2) & 3; + *ps++ = (b)&3; + } + ++pout; + } +#endif + } + + // Fill slots with interleaved symbols. + // EN 302 307-1 figures 7 and 8 +#if 0 // For reference + static void interleave(int bps, int rows, + const hard_sb *pin, int nslots, + bool msb_first, plslot *pout) { + if ( bps==4 && rows==4050 && msb_first ) + return interleave4050(pin, nslots, pout); + if ( rows % 8 ) fatal("modcod/framesize combination not supported\n"); + int stride = rows/8; // Offset to next column, in bytes + hard_sb accs[bps]; // One accumulator per column + int nacc = 0; // Bits in each column accumulator + for ( ; nslots; --nslots,++pout ) { + pout->is_pls = false; + hard_ss *ps = pout->symbols; + for ( int ns=pout->LENGTH; ns--; ++ps ) { + if ( ! nacc ) { + const hard_sb *pi = pin; + for ( int b=0; b>7); + accs[b] <<= 1; + } + else + for ( int b=bps; b--; ) { + symb = (symb<<1) | (accs[b]>>7); + accs[b] <<= 1; + } + --nacc; + *ps = symb; + } + } + if ( nacc ) fail("Bug: s2_interleaver"); + } +#else // reference + static void interleave(int bps, int rows, + const hard_sb *pin, int nslots, + bool msb_first, plslot *pout) + { + void (*func)(int rows, const hard_sb *pin, int nslots, + plslot *pout) = 0; + if (msb_first) + switch (bps) + { + case 2: + func = interleave<1, 2>; + break; + case 3: + func = interleave<1, 3>; + break; + case 4: + func = interleave<1, 4>; + break; + case 5: + func = interleave<1, 5>; + break; + default: + fail("Bad bps"); + } + else + switch (bps) + { + case 2: + func = interleave<0, 2>; + break; + case 3: + func = interleave<0, 3>; + break; + case 4: + func = interleave<0, 4>; + break; + case 5: + func = interleave<0, 5>; + break; + default: + fail("Bad bps"); + } + (*func)(rows, pin, nslots, pout); + } + template + static void interleave(int rows, const hard_sb *pin, int nslots, + plslot *pout) + { + if (BPS == 4 && rows == 4050 && MSB_FIRST) + return interleave4050(pin, nslots, pout); + if (rows % 8) + fatal("modcod/framesize combination not supported\n"); + int stride = rows / 8; // Offset to next column, in bytes + if (nslots % 4) + fatal("Bug: Truncated byte"); + // plslot::symbols[] are not packed across slots, + // so we need tos split bytes at boundaries. + for (; nslots; nslots -= 4) + { + const hard_sb *pi; + hard_sb accs[BPS]; // One accumulator per column + hard_ss *ps; + // Slot 0 (mod 4): 88+2 + pout->is_pls = false; + ps = pout->symbols; + for (int i = 0; i < 11; ++i) + { + split_byte(pin++, stride, accs); + pop_symbols(accs, &ps, 8); + } + split_byte(pin++, stride, accs); + pop_symbols(accs, &ps, 2); + ++pout; + // Slot 1 (mod 4): 6+80+4 + pout->is_pls = false; + ps = pout->symbols; + pop_symbols(accs, &ps, 6); + for (int i = 0; i < 10; ++i) + { + split_byte(pin++, stride, accs); + pop_symbols(accs, &ps, 8); + } + split_byte(pin++, stride, accs); + pop_symbols(accs, &ps, 4); + ++pout; + // Slot 2 (mod 4): 4+80+6 + pout->is_pls = false; + ps = pout->symbols; + pop_symbols(accs, &ps, 4); + for (int i = 0; i < 10; ++i) + { + split_byte(pin++, stride, accs); + pop_symbols(accs, &ps, 8); + } + split_byte(pin++, stride, accs); + pop_symbols(accs, &ps, 6); + ++pout; + // Slot 3 (mod 4): 2+88 + pout->is_pls = false; + ps = pout->symbols; + pop_symbols(accs, &ps, 2); + for (int i = 0; i < 11; ++i) + { + split_byte(pin++, stride, accs); + pop_symbols(accs, &ps, 8); + } + ++pout; + } + } + template + static inline void split_byte(const hard_sb *pi, int stride, + hard_sb accs[BPS]) + { + // TBD Pass stride as template parameter. + for (int b = 0; b < BPS; ++b, pi += stride) + accs[b] = *pi; + } + template + static void pop_symbols(hard_sb accs[BPS], hard_ss **ps, int ns) + { + for (int i = 0; i < ns; ++i) + { + hard_ss symb = 0; + // Check unrolling and constant propagation. + for (int b = 0; b < BPS; ++b) + if (MSB_FIRST) + symb = (symb << 1) | (accs[b] >> 7); + else + symb = (symb << 1) | (accs[BPS - 1 - b] >> 7); + for (int b = 0; b < BPS; ++b) + accs[b] <<= 1; + *(*ps)++ = symb; + } + } +#endif // reference + + // Special case for 16APSK short frames. + // 4050 rows is not a multiple of 8. + static void interleave4050(const hard_sb *pin, int nslots, + plslot *pout) + { + const hard_sb *pin0 = pin; + int rows = 4050; + hard_sb accs[4]; // One accumulator per column + int nacc = 0; // Bits in each column accumulator + for (; nslots; --nslots, ++pout) + { + pout->is_pls = false; + hard_ss *ps = pout->symbols; + for (int ns = pout->LENGTH; ns--; ++ps) + { + if (!nacc) + { + if (nslots == 1 && ns == 1) + { + // Special case just to avoid reading beyond end of buffer + accs[0] = pin[0]; + accs[1] = (pin[506] << 2) | (pin[507] >> 6); + accs[2] = (pin[1012] << 4) | (pin[1013] >> 4); + accs[3] = (pin[1518] << 6); + } + else + { + accs[0] = pin[0]; + accs[1] = (pin[506] << 2) | (pin[507] >> 6); + accs[2] = (pin[1012] << 4) | (pin[1013] >> 4); + accs[3] = (pin[1518] << 6) | (pin[1519] >> 2); + } + ++pin; + nacc = 8; + } + hard_ss symb = 0; + for (int b = 0; b < 4; ++b) + { + symb = (symb << 1) | (accs[b] >> 7); + accs[b] <<= 1; + } + --nacc; + *ps = symb; + } + } + } + + pipereader> in; + pipewriter> out; +}; // s2_interleaver + +// S2 DEINTERLEAVER +// EN 302 307-1 section 5.3.3 Bit Interleaver + +template +struct s2_deinterleaver : runnable +{ + s2_deinterleaver(scheduler *sch, + pipebuf> &_in, + pipebuf> &_out) + : runnable(sch, "S2 deinterleaver"), + in(_in), out(_out) + { + } + void run() + { + while (in.readable() >= 1 && out.writable() >= 1) + { + plslot *pin = in.rd(); + if (!pin->is_pls) + fail("s2_deinterleaver: bad input sequence"); + s2_pls *pls = &pin->pls; + const modcod_info *mcinfo = check_modcod(pls->modcod); + int nslots = pls->sf ? mcinfo->nslots_nf / 4 : mcinfo->nslots_nf; + if (in.readable() < 1 + nslots) + return; + fecframe *pout = out.wr(); + pout->pls = *pls; + SOFTBYTE *pbytes = pout->bytes; + if (mcinfo->nsymbols == 4) + deserialize_qpsk(pin + 1, nslots, pbytes); + else + { + int bps = log2(mcinfo->nsymbols); + int rows = pls->framebits() / bps; + if (mcinfo->nsymbols == 8 && mcinfo->rate == FEC35) + deinterleave(bps, rows, pin + 1, nslots, false, pbytes); + else + deinterleave(bps, rows, pin + 1, nslots, true, pbytes); + } + in.read(1 + nslots); + out.written(1); + } + } + + private: + // Deserialize slots of QPSK symbols, MSB first. + static void deserialize_qpsk(plslot *pin, int nslots, + SOFTBYTE *pout) + { + SOFTBYTE acc; + softword_clear(&acc); // gcc warning + int nacc = 0; + for (; nslots; --nslots, ++pin) + { + SOFTSYMB *ps = pin->symbols; + for (int ns = pin->LENGTH; ns--; ++ps) + { + pack_qpsk_symbol(*ps, &acc, nacc); + nacc += 2; + if (nacc == 8) + { // TBD unroll + *pout++ = acc; + nacc = 0; + } + } + } + } + + // Deinterleave slots of symbols. + // EN 302 307-1 figures 7 and 8 +#if 0 // For reference + static void deinterleave(int bps, int rows, + const plslot *pin, int nslots, + bool msb_first, SOFTBYTE *pout) { + if ( bps==4 && rows==4050 && msb_first ) + return deinterleave4050(pin, nslots, pout); + if ( rows % 8 ) fatal("modcod/framesize combination not supported\n"); + int stride = rows/8; // Offset to next column, in bytes + SOFTBYTE accs[bps]; + for ( int b=0; bsymbols; + for ( int ns=pin->LENGTH; ns--; ++ps ) { + split_symbol(*ps, bps, accs, nacc, msb_first); + ++nacc; + if ( nacc == 8 ) { + SOFTBYTE *po = pout; + for ( int b=0; b *pin, int nslots, + bool msb_first, SOFTBYTE *pout) + { + void (*func)(int rows, const plslot *pin, int nslots, + SOFTBYTE *pout) = 0; + if (msb_first) + switch (bps) + { + case 2: + func = deinterleave<1, 2>; + break; + case 3: + func = deinterleave<1, 3>; + break; + case 4: + func = deinterleave<1, 4>; + break; + case 5: + func = deinterleave<1, 5>; + break; + default: + fail("Bad bps"); + } + else + switch (bps) + { + case 2: + func = deinterleave<0, 2>; + break; + case 3: + func = deinterleave<0, 3>; + break; + case 4: + func = deinterleave<0, 4>; + break; + case 5: + func = deinterleave<0, 5>; + break; + default: + fail("Bad bps"); + } + (*func)(rows, pin, nslots, pout); + } + + template + static void deinterleave(int rows, const plslot *pin, int nslots, + SOFTBYTE *pout) + { + if (BPS == 4 && rows == 4050 && MSB_FIRST) + return deinterleave4050(pin, nslots, pout); + if (rows % 8) + fatal("modcod/framesize combination not supported\n"); + int stride = rows / 8; // Offset to next column, in bytes + SOFTBYTE accs[BPS]; + for (int b = 0; b < BPS; ++b) + softword_clear(&accs[b]); // gcc warning + int nacc = 0; + for (; nslots; --nslots, ++pin) + { + const SOFTSYMB *ps = pin->symbols; + for (int ns = pin->LENGTH; ns--; ++ps) + { + split_symbol(*ps, BPS, accs, nacc, MSB_FIRST); + ++nacc; + if (nacc == 8) + { // TBD Unroll, same as interleave() + SOFTBYTE *po = pout; + // TBD Pass stride as template parameter. + for (int b = 0; b < BPS; ++b, po += stride) + *po = accs[b]; + ++pout; + nacc = 0; + } + } + } + if (nacc) + fail("Bug: s2_deinterleaver"); + } +#endif // reference + + // Special case for 16APSK short frames. + // 4050 rows is not a multiple of 8 + // so we process rows one at a time rather than in chunks of 8. + static void deinterleave4050(const plslot *pin, int nslots, + SOFTBYTE *pout) + { + const int rows = 4050; + SOFTBYTE accs[4]; + for (int b = 0; b < 4; ++b) + softword_clear(&accs[b]); // gcc warning + int nacc = 0; + for (; nslots; --nslots, ++pin) + { + const SOFTSYMB *ps = pin->symbols; + for (int ns = pin->LENGTH; ns--; ++ps) + { + split_symbol(*ps, 4, accs, nacc, true); + ++nacc; + if (nacc == 8) + { + for (int b = 0; b < 8; ++b) + { + softwords_set(pout, rows * 0 + b, softword_get(accs[0], b)); + softwords_set(pout, rows * 1 + b, softword_get(accs[1], b)); + softwords_set(pout, rows * 2 + b, softword_get(accs[2], b)); + softwords_set(pout, rows * 3 + b, softword_get(accs[3], b)); + } + ++pout; + nacc = 0; + } + } + } + if (nacc != 2) + fatal("Bug: Expected 2 leftover rows\n"); + // Pad with random symbol so that we can use accs[]. + for (int b = nacc; b < 8; ++b) + split_symbol(pin->symbols[0], 4, accs, b, true); + for (int b = 0; b < nacc; ++b) + { + softwords_set(pout, rows * 0 + b, softword_get(accs[0], b)); + softwords_set(pout, rows * 1 + b, softword_get(accs[1], b)); + softwords_set(pout, rows * 2 + b, softword_get(accs[2], b)); + softwords_set(pout, rows * 3 + b, softword_get(accs[3], b)); + } + } + + // Spread LLR symbol across hard columns. + // Must call 8 times before using result because we use bit shifts. + static inline void split_symbol(const llr_ss &ps, int bps, + hard_sb accs[/*bps*/], int nacc, + bool msb_first) + { + if (msb_first) + { + for (int b = 0; b < bps; ++b) + accs[b] = (accs[b] << 1) | llr_harden(ps.bits[bps - 1 - b]); + } + else + { + for (int b = 0; b < bps; ++b) + accs[b] = (accs[b] << 1) | llr_harden(ps.bits[b]); + } + } + // Fast variant + template + static inline void split_symbol(const llr_ss &ps, + hard_sb accs[/*bps*/], int nacc) + { + if (MSB_FIRST) + { + for (int b = 0; b < BPS; ++b) + accs[b] = (accs[b] << 1) | llr_harden(ps.bits[BPS - 1 - b]); + } + else + { + for (int b = 0; b < BPS; ++b) + accs[b] = (accs[b] << 1) | llr_harden(ps.bits[b]); + } + } + + // Spread LLR symbol across LLR columns. + static inline void split_symbol(const llr_ss &ps, int bps, + llr_sb accs[/*bps*/], int nacc, + bool msb_first) + { + if (msb_first) + { + for (int b = 0; b < bps; ++b) + accs[b].bits[nacc] = ps.bits[bps - 1 - b]; + } + else + { + for (int b = 0; b < bps; ++b) + accs[b].bits[nacc] = ps.bits[b]; + } + } + // Fast variant + template + static inline void split_symbol(const llr_ss &ps, + llr_sb accs[/*bps*/], int nacc) + { + if (MSB_FIRST) + { + for (int b = 0; b < BPS; ++b) + accs[b].bits[nacc] = ps.bits[BPS - 1 - b]; + } + else + { + for (int b = 0; b < BPS; ++b) + accs[b].bits[nacc] = ps.bits[b]; + } + } + + // Merge QPSK LLR symbol into hard byte. + static inline void pack_qpsk_symbol(const llr_ss &ps, + hard_sb *acc, int nacc) + { + // TBD Must match LLR law, see softsymb_harden. + uint8_t s = llr_harden(ps.bits[0]) | (llr_harden(ps.bits[1]) << 1); + *acc = (*acc << 2) | s; + } + + // Merge QPSK LLR symbol into LLR byte. + static inline void pack_qpsk_symbol(const llr_ss &ps, + llr_sb *acc, int nacc) + { + acc->bits[nacc] = ps.bits[1]; + acc->bits[nacc + 1] = ps.bits[0]; + } + + pipereader> in; + pipewriter> out; +}; // s2_deinterleaver + +typedef ldpc_table s2_ldpc_table; +typedef ldpc_engine s2_ldpc_engine; + +#include "dvbs2_data.h" + +static const struct fec_info +{ + static const int KBCH_MAX = 58192; + int Kbch; // BCH message size (bits) + int kldpc; // LDPC message size (= BCH codeword size) (bits) + int t; // BCH error correction + const s2_ldpc_table *ldpc; +} fec_infos[2][FEC_COUNT] = { + { + // Normal frames + [FEC12] = {32208, 32400, 12, &ldpc_nf_fec12}, + [FEC23] = {43040, 43200, 10, &ldpc_nf_fec23}, + [FEC46] = {0}, + [FEC34] = {48408, 48600, 12, &ldpc_nf_fec34}, + [FEC56] = {53840, 54000, 10, &ldpc_nf_fec56}, + [FEC78] = {0}, + [FEC45] = {51648, 51840, 12, &ldpc_nf_fec45}, + [FEC89] = {57472, 57600, 8, &ldpc_nf_fec89}, + [FEC910] = {58192, 58320, 8, &ldpc_nf_fec910}, + [FEC14] = {16008, 16200, 12, &ldpc_nf_fec14}, + [FEC13] = {21408, 21600, 12, &ldpc_nf_fec13}, + [FEC25] = {25728, 25920, 12, &ldpc_nf_fec25}, + [FEC35] = {38688, 38880, 12, &ldpc_nf_fec35}, + }, + { + // Short frames + [FEC12] = {7032, 7200, 12, &ldpc_sf_fec12}, + [FEC23] = {10632, 10800, 12, &ldpc_sf_fec23}, + [FEC46] = {}, + [FEC34] = {11712, 11880, 12, &ldpc_sf_fec34}, + [FEC56] = {13152, 13320, 12, &ldpc_sf_fec56}, + [FEC78] = {}, + [FEC45] = {12432, 12600, 12, &ldpc_sf_fec45}, + [FEC89] = {14232, 14400, 12, &ldpc_sf_fec89}, + [FEC910] = {}, + [FEC14] = {3072, 3240, 12, &ldpc_sf_fec14}, + [FEC13] = {5232, 5400, 12, &ldpc_sf_fec13}, + [FEC25] = {6312, 6480, 12, &ldpc_sf_fec25}, + [FEC35] = {9552, 9720, 12, &ldpc_sf_fec35}, + }, +}; + +struct bbframe +{ + s2_pls pls; + uint8_t bytes[58192 / 8]; // Kbch/8 max +}; + +// S2_LDPC_ENGINES +// Initializes LDPC engines for all DVB-S2 FEC settings. + +template +struct s2_ldpc_engines +{ + typedef ldpc_engine s2_ldpc_engine; + s2_ldpc_engine *ldpcs[2][FEC_COUNT]; // [shortframes][fec] + s2_ldpc_engines() + { + memset(ldpcs, 0, sizeof(ldpcs)); + for (int sf = 0; sf <= 1; ++sf) + { + for (int fec = 0; fec < FEC_COUNT; ++fec) + { + const fec_info *fi = &fec_infos[sf][fec]; + if (!fi->ldpc) + { + ldpcs[sf][fec] = NULL; + } + else + { + int n = (sf ? 64800 / 4 : 64800); + int k = fi->kldpc; + ldpcs[sf][fec] = new s2_ldpc_engine(fi->ldpc, k, n); + } + } + } + } + void print_node_stats() + { + for (int sf = 0; sf <= 1; ++sf) + for (int fec = 0; fec < FEC_COUNT; ++fec) + { + s2_ldpc_engine *ldpc = ldpcs[sf][fec]; + if (ldpc) + ldpc->print_node_stats(); + } + } +}; // s2_ldpc_engines + +// S2_BCH_ENGINES +// Initializes BCH engines for all DVB-S2 FEC settings. + +struct s2_bch_engines +{ + bch_interface *bchs[2][FEC_COUNT]; + // N=t*m + // The generator of GF(2^m) is always g1. + // Normal frames with 8, 10 or 12 polynomials. + typedef bch_engine s2_bch_engine_nf12; + typedef bch_engine s2_bch_engine_nf10; + typedef bch_engine s2_bch_engine_nf8; + // Short frames with 12 polynomials. + typedef bch_engine s2_bch_engine_sf12; + s2_bch_engines() + { + bitvect bch_polys[2][12]; // [shortframes][polyindex] + // EN 302 307-1 5.3.1 Table 6a (polynomials for normal frames) + bch_polys[0][0] = bitvect(0x1002d); // g1 + bch_polys[0][1] = bitvect(0x10173); // g2 + bch_polys[0][2] = bitvect(0x10fbd); // g3 + bch_polys[0][3] = bitvect(0x15a55); // g4 + bch_polys[0][4] = bitvect(0x11f2f); // g5 + bch_polys[0][5] = bitvect(0x1f7b5); // g6 + bch_polys[0][6] = bitvect(0x1af65); // g7 + bch_polys[0][7] = bitvect(0x17367); // g8 + bch_polys[0][8] = bitvect(0x10ea1); // g9 + bch_polys[0][9] = bitvect(0x175a7); // g10 + bch_polys[0][10] = bitvect(0x13a2d); // g11 + bch_polys[0][11] = bitvect(0x11ae3); // g12 + // EN 302 307-1 5.3.1 Table 6b (polynomials for short frames) + bch_polys[1][0] = bitvect(0x402b); // g1 + bch_polys[1][1] = bitvect(0x4941); // g2 + bch_polys[1][2] = bitvect(0x4647); // g3 + bch_polys[1][3] = bitvect(0x5591); // g4 + bch_polys[1][4] = bitvect(0x6b55); // g5 + bch_polys[1][5] = bitvect(0x6389); // g6 + bch_polys[1][6] = bitvect(0x6ce5); // g7 + bch_polys[1][7] = bitvect(0x4f21); // g8 + bch_polys[1][8] = bitvect(0x460f); // g9 + bch_polys[1][9] = bitvect(0x5a49); // g10 + bch_polys[1][10] = bitvect(0x5811); // g11 + bch_polys[1][11] = bitvect(0x65ef); // g12 + // Redundant with fec_infos[], but needs static template argument. + memset(bchs, 0, sizeof(bchs)); + bchs[0][FEC12] = new s2_bch_engine_nf12(bch_polys[0], 12); + bchs[0][FEC23] = new s2_bch_engine_nf10(bch_polys[0], 10); + bchs[0][FEC34] = new s2_bch_engine_nf12(bch_polys[0], 12); + bchs[0][FEC56] = new s2_bch_engine_nf10(bch_polys[0], 10); + bchs[0][FEC45] = new s2_bch_engine_nf12(bch_polys[0], 12); + bchs[0][FEC89] = new s2_bch_engine_nf8(bch_polys[0], 8); + bchs[0][FEC910] = new s2_bch_engine_nf8(bch_polys[0], 8); + bchs[0][FEC14] = new s2_bch_engine_nf12(bch_polys[0], 12); + bchs[0][FEC13] = new s2_bch_engine_nf12(bch_polys[0], 12); + bchs[0][FEC25] = new s2_bch_engine_nf12(bch_polys[0], 12); + bchs[0][FEC35] = new s2_bch_engine_nf12(bch_polys[0], 12); + bchs[1][FEC12] = new s2_bch_engine_sf12(bch_polys[1], 12); + bchs[1][FEC23] = new s2_bch_engine_sf12(bch_polys[1], 12); + bchs[1][FEC34] = new s2_bch_engine_sf12(bch_polys[1], 12); + bchs[1][FEC56] = new s2_bch_engine_sf12(bch_polys[1], 12); + bchs[1][FEC45] = new s2_bch_engine_sf12(bch_polys[1], 12); + bchs[1][FEC89] = new s2_bch_engine_sf12(bch_polys[1], 12); + bchs[1][FEC14] = new s2_bch_engine_sf12(bch_polys[1], 12); + bchs[1][FEC13] = new s2_bch_engine_sf12(bch_polys[1], 12); + bchs[1][FEC25] = new s2_bch_engine_sf12(bch_polys[1], 12); + bchs[1][FEC35] = new s2_bch_engine_sf12(bch_polys[1], 12); + } +}; // s2_bch_engines + +// S2 BASEBAND DESCRAMBLER AND FEC ENCODER +// EN 302 307-1 section 5.2.2 +// EN 302 307-1 section 5.3 + +struct s2_fecenc : runnable +{ + typedef ldpc_engine s2_ldpc_engine; + s2_fecenc(scheduler *sch, + pipebuf &_in, pipebuf> &_out) + : runnable(sch, "S2 fecenc"), + in(_in), out(_out) + { + if (sch->debug) + s2ldpc.print_node_stats(); + } + void run() + { + while (in.readable() >= 1 && out.writable() >= 1) + { + bbframe *pin = in.rd(); + fecframe *pout = out.wr(); + run_frame(in.rd(), out.wr()); + in.read(1); + out.written(1); + } + } + + private: + void run_frame(const bbframe *pin, fecframe *pout) + { + const modcod_info *mcinfo = check_modcod(pin->pls.modcod); + const fec_info *fi = &fec_infos[pin->pls.sf][mcinfo->rate]; + pout->pls = pin->pls; + hard_sb *pbytes = pout->bytes; + bbscrambling.transform(pin->bytes, fi->Kbch / 8, pbytes); + { // BCH + size_t msgbytes = fi->Kbch / 8; + size_t cwbytes = fi->kldpc / 8; + bch_interface *bch = s2bch.bchs[pin->pls.sf][mcinfo->rate]; + bch->encode(pbytes, msgbytes, pbytes + msgbytes); + } + { // LDPC + size_t msgbits = fi->kldpc; + size_t cwbits = pin->pls.framebits(); + s2_ldpc_engine *ldpc = s2ldpc.ldpcs[pin->pls.sf][mcinfo->rate]; + ldpc->encode(fi->ldpc, pbytes, msgbits, cwbits, pbytes + msgbits / 8); + } + } + pipereader in; + pipewriter> out; + s2_bbscrambling bbscrambling; + s2_bch_engines s2bch; + s2_ldpc_engines s2ldpc; +}; // s2_fecenc + +// S2 FEC DECODER AND BASEBAND DESCRAMBLER +// EN 302 307-1 section 5.3 +// EN 302 307-1 section 5.2.2 + +template +struct s2_fecdec : runnable +{ + int bitflips; + s2_fecdec(scheduler *sch, + pipebuf> &_in, pipebuf &_out, + pipebuf *_bitcount = NULL, + pipebuf *_errcount = NULL) + : runnable(sch, "S2 fecdec"), + bitflips(0), + in(_in), out(_out), + bitcount(opt_writer(_bitcount, 1)), + errcount(opt_writer(_errcount, 1)) + { + if (sch->debug) + s2ldpc.print_node_stats(); + } + void run() + { + while (in.readable() >= 1 && out.writable() >= 1 && + opt_writable(bitcount, 1) && opt_writable(errcount, 1)) + { + fecframe *pin = in.rd(); + const modcod_info *mcinfo = check_modcod(pin->pls.modcod); + const fec_info *fi = &fec_infos[pin->pls.sf][mcinfo->rate]; + bool corrupted = false; + bool residual_errors; + if (true) + { + // LDPC decode + size_t cwbits = pin->pls.framebits(); + size_t msgbits = fi->kldpc; + size_t chkbits = cwbits - msgbits; + s2_ldpc_engine *ldpc = s2ldpc.ldpcs[pin->pls.sf][mcinfo->rate]; + int ncorr = ldpc->decode_bitflip(fi->ldpc, pin->bytes, msgbits, cwbits, bitflips); + if (sch->debug2) + fprintf(stderr, "LDPCCORR = %d\n", ncorr); + } + uint8_t *hardbytes = softbytes_harden(pin->bytes, fi->kldpc / 8, bch_buf); + if (true) + { + // BCH decode + size_t cwbytes = fi->kldpc / 8; + size_t msgbytes = fi->Kbch / 8; + size_t chkbytes = cwbytes - msgbytes; + // Decode with suitable BCH decoder for this MODCOD + bch_interface *bch = s2bch.bchs[pin->pls.sf][mcinfo->rate]; + int ncorr = bch->decode(hardbytes, cwbytes); + if (sch->debug2) + fprintf(stderr, "BCHCORR = %d\n", ncorr); + corrupted = (ncorr < 0); + residual_errors = (ncorr != 0); + // Report VER + opt_write(bitcount, fi->Kbch); + opt_write(errcount, (ncorr >= 0) ? ncorr : fi->Kbch); + } + int bbsize = fi->Kbch / 8; + // TBD Some decoders want the bad packets. +#if 0 + if ( corrupted ) { + fprintf(stderr, "Passing bad frame\n"); + corrupted = false; + } +#endif + if (!corrupted) + { + // Descramble and output + bbframe *pout = out.wr(); + pout->pls = pin->pls; + bbscrambling.transform(hardbytes, bbsize, pout->bytes); + out.written(1); + } + if (sch->debug) + fprintf(stderr, "%c", corrupted ? ':' : residual_errors ? '.' : '_'); + in.read(1); + } + } + + private: + s2_ldpc_engines s2ldpc; + uint8_t bch_buf[64800 / 8]; // Temp storage for hardening before BCH + s2_bch_engines s2bch; + s2_bbscrambling bbscrambling; + pipereader> in; + pipewriter out; + pipewriter *bitcount, *errcount; +}; // s2_fecdec + +// External LDPC decoder +// Spawns a user-specified command, FEC frames on stdin/stdout. + +template +struct simplequeue +{ + static const int SIZE = _SIZE; + simplequeue() { rd = wr = count = 0; } + bool full() { return count == SIZE; } + T *put() + { + T *res = &q[wr]; + wr = (wr + 1) % SIZE; + ++count; + return res; + } + bool empty() { return count == 0; } + const T *peek() { return &q[rd]; } + const T *get() + { + const T *res = &q[rd]; + rd = (rd + 1) % SIZE; + --count; + return res; + } + // private: + int rd, wr, count; + T q[SIZE]; +}; + +template +struct s2_fecdec_helper : runnable +{ + int batch_size; + int nhelpers; + bool must_buffer; + s2_fecdec_helper(scheduler *sch, + pipebuf> &_in, + pipebuf &_out, + const char *_command, + pipebuf *_bitcount = NULL, + pipebuf *_errcount = NULL) + : runnable(sch, "S2 fecdec io"), + batch_size(32), + nhelpers(1), + must_buffer(false), + in(_in), out(_out), + command(_command), + bitcount(opt_writer(_bitcount, 1)), + errcount(opt_writer(_errcount, 1)) + { + for (int mc = 0; mc < 32; ++mc) + for (int sf = 0; sf < 2; ++sf) + pools[mc][sf].procs = NULL; + } + void run() + { + bool work_done = false; + // Send work until all helpers block. + bool all_blocked = false; + while (in.readable() >= 1 && !jobs.full()) + { + if (!send_frame(in.rd())) + { + all_blocked = true; + break; + } + in.read(1); + work_done = true; + } + // Risk blocking on read() only when we have nothing else to do + // and we know a result is coming. + while ((all_blocked || !work_done || jobs.full()) && + !jobs.empty() && + jobs.peek()->h->b_out && + out.writable() >= 1 && + opt_writable(bitcount, 1) && opt_writable(errcount, 1)) + { + receive_frame(jobs.get()); + } + } + + private: + struct helper_instance + { + int fd_tx; // To helper + int fd_rx; // From helper + int batch_size; // Latency + int b_in; // Jobs in input queue + int b_out; // Jobs in output queue + }; + struct pool + { + helper_instance *procs; // NULL or [nprocs] + int nprocs; + } pools[32][2]; // [modcod][sf] + struct helper_job + { + s2_pls pls; + helper_instance *h; + }; + simplequeue jobs; + // Try to send a frame. Return false if helper was busy. + bool send_frame(fecframe *pin) + { + pool *p = get_pool(&pin->pls); + for (int i = 0; i < p->nprocs; ++i) + { + helper_instance *h = &p->procs[i]; + size_t iosize = (pin->pls.framebits() / 8) * sizeof(SOFTBYTE); + // fprintf(stderr, "Writing %lu to fd %d\n", iosize, h->fd_tx); + int nw = write(h->fd_tx, pin->bytes, iosize); + if (nw < 0 && errno == EWOULDBLOCK) + continue; + if (nw < 0) + fatal("write(LDPC helper"); + if (nw != iosize) + fatal("partial write(LDPC helper)"); + helper_job *job = jobs.put(); + job->pls = pin->pls; + job->h = h; + ++h->b_in; + if (h->b_in >= h->batch_size) + { + h->b_in -= h->batch_size; + h->b_out += h->batch_size; + } + return true; + } + return false; + } + // Return a pool of running helpers for a given modcod. + pool *get_pool(const s2_pls *pls) + { + pool *p = &pools[pls->modcod][pls->sf]; + if (!p->procs) + { + p->procs = new helper_instance[nhelpers]; + for (int i = 0; i < nhelpers; ++i) + spawn_helper(&p->procs[i], pls); + p->nprocs = nhelpers; + } + return p; + } + // Spawn a helper process. + void spawn_helper(helper_instance *h, const s2_pls *pls) + { + if (sch->debug) + fprintf(stderr, "Spawning LDPC helper: modcod=%d sf=%d\n", + pls->modcod, pls->sf); + int tx[2], rx[2]; + if (pipe(tx) || pipe(rx)) + fatal("pipe"); + // Size the pipes so that the helper never runs out of work to do. + int pipesize = 64800 * batch_size; + if (fcntl(tx[0], F_SETPIPE_SZ, pipesize) < 0 || + fcntl(rx[0], F_SETPIPE_SZ, pipesize) < 0 || + fcntl(tx[1], F_SETPIPE_SZ, pipesize) < 0 || + fcntl(rx[1], F_SETPIPE_SZ, pipesize) < 0) + { + fprintf(stderr, + "*** Failed to increase pipe size.\n" + "*** Try echo %d > /proc/sys/fs/pipe-max-size\n", + pipesize); + if (must_buffer) + fatal("F_SETPIPE_SZ"); + else + fprintf(stderr, "*** Throughput will be suboptimal.\n"); + } + int child = vfork(); + if (!child) + { + // Child process + close(tx[1]); + dup2(tx[0], 0); + close(rx[0]); + dup2(rx[1], 1); + char mc_arg[16]; + sprintf(mc_arg, "%d", pls->modcod); + const char *sf_arg = pls->sf ? "--shortframes" : NULL; + const char *argv[] = {command, "--modcod", mc_arg, sf_arg, NULL}; + execve(command, (char *const *)argv, NULL); + fatal(command); + } + h->fd_tx = tx[1]; + close(tx[0]); + h->fd_rx = rx[0]; + close(rx[1]); + h->batch_size = 32; // TBD + h->b_in = h->b_out = 0; + int flags = fcntl(h->fd_tx, F_GETFL); + if (fcntl(h->fd_tx, F_SETFL, flags | O_NONBLOCK)) + fatal("fcntl(helper)"); + } + + // Receive a finished job. + void receive_frame(const helper_job *job) + { + // Read corrected frame from helper + const s2_pls *pls = &job->pls; + size_t iosize = (pls->framebits() / 8) * sizeof(ldpc_buf[0]); + int nr = read(job->h->fd_rx, ldpc_buf, iosize); + if (nr < 0) + fatal("read(LDPC helper)"); + if (nr != iosize) + fatal("partial read(LDPC helper)"); + --job->h->b_out; + // Decode BCH. + const modcod_info *mcinfo = check_modcod(job->pls.modcod); + const fec_info *fi = &fec_infos[job->pls.sf][mcinfo->rate]; + uint8_t *hardbytes = softbytes_harden(ldpc_buf, fi->kldpc / 8, bch_buf); + size_t cwbytes = fi->kldpc / 8; + size_t msgbytes = fi->Kbch / 8; + size_t chkbytes = cwbytes - msgbytes; + bch_interface *bch = s2bch.bchs[job->pls.sf][mcinfo->rate]; + int ncorr = bch->decode(hardbytes, cwbytes); + if (sch->debug2) + fprintf(stderr, "BCHCORR = %d\n", ncorr); + bool corrupted = (ncorr < 0); + // Report VBER + opt_write(bitcount, fi->Kbch); + opt_write(errcount, (ncorr >= 0) ? ncorr : fi->Kbch); +#if 0 + // TBD Some decoders want the bad packets. + if ( corrupted ) { + fprintf(stderr, "Passing bad frame\n"); + corrupted = false; + } +#endif + if (!corrupted) + { + // Descramble and output + bbframe *pout = out.wr(); + pout->pls = job->pls; + bbscrambling.transform(hardbytes, fi->Kbch / 8, pout->bytes); + out.written(1); + } + if (sch->debug) + fprintf(stderr, "%c", corrupted ? '!' : ncorr ? '.' : '_'); + } + pipereader> in; + pipewriter out; + const char *command; + SOFTBYTE ldpc_buf[64800 / 8]; + uint8_t bch_buf[64800 / 8]; // Temp storage for hardening before BCH + s2_bch_engines s2bch; + s2_bbscrambling bbscrambling; + pipewriter *bitcount, *errcount; +}; // s2_fecdec_helper + +// S2 FRAMER +// EN 302 307-1 section 5.1 Mode adaptation + +struct s2_framer : runnable +{ + uint8_t rolloff_code; // 0=0.35, 1=0.25, 2=0.20, 3=reserved + s2_pls pls; + s2_framer(scheduler *sch, pipebuf &_in, pipebuf &_out) + : runnable(sch, "S2 framer"), + in(_in), out(_out) + { + pls.modcod = 4; + pls.sf = false; + pls.pilots = true; + nremain = 0; + remcrc = 0; // CRC for nonexistent previous packet + } + void run() + { + while (out.writable() >= 1) + { + const modcod_info *mcinfo = check_modcod(pls.modcod); + const fec_info *fi = &fec_infos[pls.sf][mcinfo->rate]; + int framebytes = fi->Kbch / 8; + if (!framebytes) + fail("MODCOD/framesize combination not allowed"); + if (10 + nremain + 188 * in.readable() < framebytes) + break; // Not enough data to fill a frame + bbframe *pout = out.wr(); + pout->pls = pls; + uint8_t *buf = pout->bytes; + uint8_t *end = buf + framebytes; + // EN 302 307-1 section 5.1.6 Base-Band Header insertion + uint8_t *bbheader = buf; + *buf++ = 0x30 | rolloff_code; // MATYPE-1: SIS, CCM + *buf++ = 0; // MATYPE-2 + uint16_t upl = 188 * 8; + *buf++ = upl >> 8; // UPL MSB + *buf++ = upl; // UPL LSB + uint16_t dfl = (framebytes - 10) * 8; + *buf++ = dfl >> 8; // DFL MSB + *buf++ = dfl; // DFL LSB + *buf++ = 0x47; // SYNC + uint16_t syncd = nremain * 8; + *buf++ = syncd >> 8; // SYNCD MSB + *buf++ = syncd; // SYNCD LSB + *buf++ = crc8.compute(bbheader, 9); + // Data field + memcpy(buf, rembuf, nremain); // Leftover from previous runs + buf += nremain; + while (buf < end) + { + tspacket *tsp = in.rd(); + if (tsp->data[0] != MPEG_SYNC) + fail("Invalid TS"); + *buf++ = remcrc; // Replace SYNC with CRC of previous. + remcrc = crc8.compute(tsp->data + 1, tspacket::SIZE - 1); + int nused = end - buf; + if (nused > tspacket::SIZE - 1) + nused = tspacket::SIZE - 1; + memcpy(buf, tsp->data + 1, nused); + buf += nused; + if (buf == end) + { + nremain = (tspacket::SIZE - 1) - nused; + memcpy(rembuf, tsp->data + 1 + nused, nremain); + } + in.read(1); + } + if (buf != end) + fail("Bug: s2_framer"); + out.written(1); + } + } + + private: + pipereader in; + pipewriter out; + crc8_engine crc8; + int nremain; + uint8_t rembuf[tspacket::SIZE]; + uint8_t remcrc; +}; // s2_framer + +// S2 DEFRAMER +// EN 302 307-1 section 5.1 Mode adaptation + +struct s2_deframer : runnable +{ + s2_deframer(scheduler *sch, pipebuf &_in, pipebuf &_out, + pipebuf *_state_out = NULL, + pipebuf *_locktime_out = NULL) + : runnable(sch, "S2 deframer"), + missing(-1), + in(_in), out(_out, MAX_TS_PER_BBFRAME), + current_state(false), + state_out(opt_writer(_state_out, 2)), + report_state(true), + locktime(0), + locktime_out(opt_writer(_locktime_out, MAX_TS_PER_BBFRAME)) + { + } + void run() + { + while (in.readable() >= 1 && out.writable() >= MAX_TS_PER_BBFRAME && + opt_writable(state_out, 2) && + opt_writable(locktime_out, MAX_TS_PER_BBFRAME)) + { + if (report_state) + { + // Report unlocked state on first invocation. + opt_write(state_out, 0); + report_state = false; + } + run_bbframe(in.rd()); + in.read(1); + } + } + + private: + void run_bbframe(bbframe *pin) + { + uint8_t *bbh = pin->bytes; + uint16_t upl = (bbh[2] << 8) | bbh[3]; + uint16_t dfl = (bbh[4] << 8) | bbh[5]; + uint8_t sync = bbh[6]; + uint16_t syncd = (bbh[7] << 8) | bbh[8]; + uint8_t crcexp = crc8.compute(bbh, 9); + uint8_t crc = bbh[9]; + uint8_t *data = bbh + 10; + int ro_code = bbh[0] & 3; + if (sch->debug2) + { + static float ro_values[] = {0.35, 0.25, 0.20, 0}; + fprintf(stderr, "BBH: crc %02x/%02x %s ma=%02x%02x ro=%.2f" + " upl=%d dfl=%d sync=%02x syncd=%d\n", + crc, crcexp, (crc == crcexp) ? "OK" : "KO", + bbh[0], bbh[1], ro_values[ro_code], upl, dfl, sync, syncd); + } + if (crc != crcexp || upl != 188 * 8 || sync != 0x47 || dfl > fec_info::KBCH_MAX || + syncd > dfl || (dfl & 7) || (syncd & 7)) + { + // Note: Maybe accept syncd=65535 + fprintf(stderr, "Bad bbframe\n"); + missing = -1; + info_unlocked(); + return; + } + // TBD Handle packets as payload+finalCRC and do crc8 before pout + int pos; // Start of useful data in this bbframe + if (missing < 0) + { + // Skip unusable data at beginning of bbframe + pos = syncd / 8; + fprintf(stderr, "Start TS at %d\n", pos); + missing = 0; + } + else + { + // Sanity check + if (syncd / 8 != missing) + { + fprintf(stderr, "Lost a bbframe ?\n"); + missing = -1; + info_unlocked(); + return; + } + pos = 0; + } + if (missing) + { + // Complete and output the partial TS packet in leftover[]. + tspacket *pout = out.wr(); + memcpy(pout->data, leftover, 188 - missing); + memcpy(pout->data + (188 - missing), data + pos, missing); + out.written(1); + info_good_packet(); + ++pout; + // Skip to beginning of next TS packet + pos += missing; + missing = 0; + } + while (pos + 188 <= dfl / 8) + { + tspacket *pout = out.wr(); + memcpy(pout->data, data + pos, 188); + pout->data[0] = sync; // Replace CRC + out.written(1); + info_good_packet(); + pos += 188; + } + int remain = dfl / 8 - pos; + if (remain) + { + memcpy(leftover, data + pos, remain); + leftover[0] = sync; // Replace CRC + missing = 188 - remain; + } + } + + void info_unlocked() + { + info_is_locked(false); + locktime = 0; + } + void info_good_packet() + { + info_is_locked(true); + ++locktime; + opt_write(locktime_out, locktime); + } + void info_is_locked(bool newstate) + { + if (newstate != current_state) + { + opt_write(state_out, newstate ? 1 : 0); + current_state = newstate; + } + } + + crc8_engine crc8; + int missing; // Bytes needed to complete leftover[], + // or 0 if no leftover data, + // or -1 if not synced. + uint8_t leftover[188]; + static const int MAX_TS_PER_BBFRAME = fec_info::KBCH_MAX / 8 / 188 + 1; + bool locked; + pipereader in; + pipewriter out; + int current_state; + pipewriter *state_out; + bool report_state; + unsigned long locktime; + pipewriter *locktime_out; +}; // s2_deframer + +} // namespace leansdr + +#endif // LEANSDR_DVBS2_H diff --git a/plugins/channelrx/demoddatv/leansdr/dvbs2_data.h b/plugins/channelrx/demoddatv/leansdr/dvbs2_data.h new file mode 100644 index 000000000..a69d7440a --- /dev/null +++ b/plugins/channelrx/demoddatv/leansdr/dvbs2_data.h @@ -0,0 +1,1635 @@ + +/* + (defun ldpcformat () + (interactive) + (save-restriction + (narrow-to-region (region-beginning) (region-end)) + (goto-char (point-min)) + (while (re-search-forward "\\([^ 0-9]\\)\\([0-9]\\)\\([^0-9]\\)" nil t) (replace-match "\\1 \\2\\3")) + (goto-char (point-min)) + (while (re-search-forward "\\([^ 0-9]\\)\\([0-9][0-9]\\)\\([^0-9]\\)" nil t) (replace-match "\\1 \\2\\3")) + (goto-char (point-min)) + (while (re-search-forward "\\([^ 0-9]\\)\\([0-9][0-9][0-9]\\)\\([^0-9]\\)" nil t) (replace-match "\\1 \\2\\3")) + (goto-char (point-min)) + (while (re-search-forward "\\([^ 0-9]\\)\\([0-9][0-9][0-9][0-9]\\)\\([^0-9]\\)" nil t) (replace-match "\\1 \\2\\3")) + ) + ) + */ +// (defun align2 () (interactive) (while (re-search-forward "\\([^ 0-9]\\)\\([0-9][0-9]\\)\\([^0-9]\\)" (region-end)) (replace-match "\\1 \\2\\3"))) +// (defun align3 () (interactive) (while (re-search-forward "\\([^ 0-9]\\)\\([0-9][0-9][0-9]\\)\\([^0-9]\\)" (region-end)) (replace-match "\\1 \\2\\3"))) +// (defun align4 () (interactive) (while (re-search-forward "\\([^ 0-9]\\)\\([0-9][0-9][0-9][0-9]\\)\\([^0-9]\\)" (region-end)) (replace-match "\\1 \\2\\3"))) + +// EN 302 307-1 Table B.1 +static const s2_ldpc_table ldpc_nf_fec14 = + {135, 45, { + {12, {23606, 36098, 1140, 28859, 18148, 18510, 6226, 540, 42014, 20879, 23802, 47088}}, + {12, {16419, 24928, 16609, 17248, 7693, 24997, 42587, 16858, 34921, 21042, 37024, 20692}}, + {12, {1874, 40094, 18704, 14474, 14004, 11519, 13106, 28826, 38669, 22363, 30255, 31105}}, + {12, {22254, 40564, 22645, 22532, 6134, 9176, 39998, 23892, 8937, 15608, 16854, 31009}}, + {12, {8037, 40401, 13550, 19526, 41902, 28782, 13304, 32796, 24679, 27140, 45980, 10021}}, + {12, {40540, 44498, 13911, 22435, 32701, 18405, 39929, 25521, 12497, 9851, 39223, 34823}}, + {12, {15233, 45333, 5041, 44979, 45710, 42150, 19416, 1892, 23121, 15860, 8832, 10308}}, + {12, {10468, 44296, 3611, 1480, 37581, 32254, 13817, 6883, 32892, 40258, 46538, 11940}}, + {12, {6705, 21634, 28150, 43757, 895, 6547, 20970, 28914, 30117, 25736, 41734, 11392}}, + {12, {22002, 5739, 27210, 27828, 34192, 37992, 10915, 6998, 3824, 42130, 4494, 35739}}, + {12, {8515, 1191, 13642, 30950, 25943, 12673, 16726, 34261, 31828, 3340, 8747, 39225}}, + {12, {18979, 17058, 43130, 4246, 4793, 44030, 19454, 29511, 47929, 15174, 24333, 19354}}, + {12, {16694, 8381, 29642, 46516, 32224, 26344, 9405, 18292, 12437, 27316, 35466, 41992}}, + {12, {15642, 5871, 46489, 26723, 23396, 7257, 8974, 3156, 37420, 44823, 35423, 13541}}, + {12, {42858, 32008, 41282, 38773, 26570, 2702, 27260, 46974, 1469, 20887, 27426, 38553}}, + {3, {22152, 24261, 8297}}, + {3, {19347, 9978, 27802}}, + {3, {34991, 6354, 33561}}, + {3, {29782, 30875, 29523}}, + {3, {9278, 48512, 14349}}, + {3, {38061, 4165, 43878}}, + {3, {8548, 33172, 34410}}, + {3, {22535, 28811, 23950}}, + {3, {20439, 4027, 24186}}, + {3, {38618, 8187, 30947}}, + {3, {35538, 43880, 21459}}, + {3, {7091, 45616, 15063}}, + {3, {5505, 9315, 21908}}, + {3, {36046, 32914, 11836}}, + {3, {7304, 39782, 33721}}, + {3, {16905, 29962, 12980}}, + {3, {11171, 23709, 22460}}, + {3, {34541, 9937, 44500}}, + {3, {14035, 47316, 8815}}, + {3, {15057, 45482, 24461}}, + {3, {30518, 36877, 879}}, + {3, {7583, 13364, 24332}}, + {3, {448, 27056, 4682}}, + {3, {12083, 31378, 21670}}, + {3, {1159, 18031, 2221}}, + {3, {17028, 38715, 9350}}, + {3, {17343, 24530, 29574}}, + {3, {46128, 31039, 32818}}, + {3, {20373, 36967, 18345}}, + {3, {46685, 20622, 32806}}, + }}; + +// EN 302 307-1 Table B.2 +static const s2_ldpc_table ldpc_nf_fec13 = + {120, 60, { + {12, {34903, 20927, 32093, 1052, 25611, 16093, 16454, 5520, 506, 37399, 18518, 21120}}, + {11, {11636, 14594, 22158, 14763, 15333, 6838, 22222, 37856, 14985, 31041, 18704}}, + {13, {32910, 17449, 1665, 35639, 16624, 12867, 12449, 10241, 11650, 25622, 34372, 19878, 26894}}, + {12, {29235, 19780, 36056, 20129, 20029, 5457, 8157, 35554, 21237, 7943, 13873, 14980}}, + {11, {9912, 7143, 35911, 12043, 17360, 37253, 25588, 11827, 29152, 21936, 24125}}, + {13, {40870, 40701, 36035, 39556, 12366, 19946, 29072, 16365, 35495, 22686, 11106, 8756, 34863}}, + {12, {19165, 15702, 13536, 40238, 4465, 40034, 40590, 37540, 17162, 1712, 20577, 14138}}, + {12, {31338, 19342, 9301, 39375, 3211, 1316, 33409, 28670, 12282, 6118, 29236, 35787}}, + {12, {11504, 30506, 19558, 5100, 24188, 24738, 30397, 33775, 9699, 6215, 3397, 37451}}, + {12, {34689, 23126, 7571, 1058, 12127, 27518, 23064, 11265, 14867, 30451, 28289, 2966}}, + {12, {11660, 15334, 16867, 15160, 38343, 3778, 4265, 39139, 17293, 26229, 42604, 13486}}, + {12, {31497, 1365, 14828, 7453, 26350, 41346, 28643, 23421, 8354, 16255, 11055, 24279}}, + {12, {15687, 12467, 13906, 5215, 41328, 23755, 20800, 6447, 7970, 2803, 33262, 39843}}, + {12, {5363, 22469, 38091, 28457, 36696, 34471, 23619, 2404, 24229, 41754, 1297, 18563}}, + {12, {3673, 39070, 14480, 30279, 37483, 7580, 29519, 30519, 39831, 20252, 18132, 20010}}, + {12, {34386, 7252, 27526, 12950, 6875, 43020, 31566, 39069, 18985, 15541, 40020, 16715}}, + {12, {1721, 37332, 39953, 17430, 32134, 29162, 10490, 12971, 28581, 29331, 6489, 35383}}, + {12, {736, 7022, 42349, 8783, 6767, 11871, 21675, 10325, 11548, 25978, 431, 24085}}, + {12, {1925, 10602, 28585, 12170, 15156, 34404, 8351, 13273, 20208, 5800, 15367, 21764}}, + {12, {16279, 37832, 34792, 21250, 34192, 7406, 41488, 18346, 29227, 26127, 25493, 7048}}, + {3, {39948, 28229, 24899}}, + {3, {17408, 14274, 38993}}, + {3, {38774, 15968, 28459}}, + {3, {41404, 27249, 27425}}, + {3, {41229, 6082, 43114}}, + {3, {13957, 4979, 40654}}, + {3, {3093, 3438, 34992}}, + {3, {34082, 6172, 28760}}, + {3, {42210, 34141, 41021}}, + {3, {14705, 17783, 10134}}, + {3, {41755, 39884, 22773}}, + {3, {14615, 15593, 1642}}, + {3, {29111, 37061, 39860}}, + {3, {9579, 33552, 633}}, + {3, {12951, 21137, 39608}}, + {3, {38244, 27361, 29417}}, + {3, {2939, 10172, 36479}}, + {3, {29094, 5357, 19224}}, + {3, {9562, 24436, 28637}}, + {3, {40177, 2326, 13504}}, + {3, {6834, 21583, 42516}}, + {3, {40651, 42810, 25709}}, + {3, {31557, 32138, 38142}}, + {3, {18624, 41867, 39296}}, + {3, {37560, 14295, 16245}}, + {3, {6821, 21679, 31570}}, + {3, {25339, 25083, 22081}}, + {3, {8047, 697, 35268}}, + {3, {9884, 17073, 19995}}, + {3, {26848, 35245, 8390}}, + {3, {18658, 16134, 14807}}, + {3, {12201, 32944, 5035}}, + {3, {25236, 1216, 38986}}, + {3, {42994, 24782, 8681}}, + {3, {28321, 4932, 34249}}, + {3, {4107, 29382, 32124}}, + {3, {22157, 2624, 14468}}, + {3, {38788, 27081, 7936}}, + {3, {4368, 26148, 10578}}, + {3, {25353, 4122, 39751}}, + }}; + +// EN 302 307-1 Table B.3 +static const s2_ldpc_table ldpc_nf_fec25 = + {108, 72, { + {12, {31413, 18834, 28884, 947, 23050, 14484, 14809, 4968, 455, 33659, 16666, 19008}}, + {12, {13172, 19939, 13354, 13719, 6132, 20086, 34040, 13442, 27958, 16813, 29619, 16553}}, + {12, {1499, 32075, 14962, 11578, 11204, 9217, 10485, 23062, 30936, 17892, 24204, 24885}}, + {12, {32490, 18086, 18007, 4957, 7285, 32073, 19038, 7152, 12486, 13483, 24808, 21759}}, + {12, {32321, 10839, 15620, 33521, 23030, 10646, 26236, 19744, 21713, 36784, 8016, 12869}}, + {12, {35597, 11129, 17948, 26160, 14729, 31943, 20416, 10000, 7882, 31380, 27858, 33356}}, + {12, {14125, 12131, 36199, 4058, 35992, 36594, 33698, 15475, 1566, 18498, 12725, 7067}}, + {12, {17406, 8372, 35437, 2888, 1184, 30068, 25802, 11056, 5507, 26313, 32205, 37232}}, + {12, {15254, 5365, 17308, 22519, 35009, 718, 5240, 16778, 23131, 24092, 20587, 33385}}, + {12, {27455, 17602, 4590, 21767, 22266, 27357, 30400, 8732, 5596, 3060, 33703, 3596}}, + {12, {6882, 873, 10997, 24738, 20770, 10067, 13379, 27409, 25463, 2673, 6998, 31378}}, + {12, {15181, 13645, 34501, 3393, 3840, 35227, 15562, 23615, 38342, 12139, 19471, 15483}}, + {12, {13350, 6707, 23709, 37204, 25778, 21082, 7511, 14588, 10010, 21854, 28375, 33591}}, + {12, {12514, 4695, 37190, 21379, 18723, 5802, 7182, 2529, 29936, 35860, 28338, 10835}}, + {12, {34283, 25610, 33026, 31017, 21259, 2165, 21807, 37578, 1175, 16710, 21939, 30841}}, + {12, {27292, 33730, 6836, 26476, 27539, 35784, 18245, 16394, 17939, 23094, 19216, 17432}}, + {12, {11655, 6183, 38708, 28408, 35157, 17089, 13998, 36029, 15052, 16617, 5638, 36464}}, + {12, {15693, 28923, 26245, 9432, 11675, 25720, 26405, 5838, 31851, 26898, 8090, 37037}}, + {12, {24418, 27583, 7959, 35562, 37771, 17784, 11382, 11156, 37855, 7073, 21685, 34515}}, + {12, {10977, 13633, 30969, 7516, 11943, 18199, 5231, 13825, 19589, 23661, 11150, 35602}}, + {12, {19124, 30774, 6670, 37344, 16510, 26317, 23518, 22957, 6348, 34069, 8845, 20175}}, + {12, {34985, 14441, 25668, 4116, 3019, 21049, 37308, 24551, 24727, 20104, 24850, 12114}}, + {12, {38187, 28527, 13108, 13985, 1425, 21477, 30807, 8613, 26241, 33368, 35913, 32477}}, + {12, {5903, 34390, 24641, 26556, 23007, 27305, 38247, 2621, 9122, 32806, 21554, 18685}}, + {3, {17287, 27292, 19033}}, + {3, {25796, 31795, 12152}}, + {3, {12184, 35088, 31226}}, + {3, {38263, 33386, 24892}}, + {3, {23114, 37995, 29796}}, + {3, {34336, 10551, 36245}}, + {3, {35407, 175, 7203}}, + {3, {14654, 38201, 22605}}, + {3, {28404, 6595, 1018}}, + {3, {19932, 3524, 29305}}, + {3, {31749, 20247, 8128}}, + {3, {18026, 36357, 26735}}, + {3, {7543, 29767, 13588}}, + {3, {13333, 25965, 8463}}, + {3, {14504, 36796, 19710}}, + {3, {4528, 25299, 7318}}, + {3, {35091, 25550, 14798}}, + {3, {7824, 215, 1248}}, + {3, {30848, 5362, 17291}}, + {3, {28932, 30249, 27073}}, + {3, {13062, 2103, 16206}}, + {3, {7129, 32062, 19612}}, + {3, {9512, 21936, 38833}}, + {3, {35849, 33754, 23450}}, + {3, {18705, 28656, 18111}}, + {3, {22749, 27456, 32187}}, + {3, {28229, 31684, 30160}}, + {3, {15293, 8483, 28002}}, + {3, {14880, 13334, 12584}}, + {3, {28646, 2558, 19687}}, + {3, {6259, 4499, 26336}}, + {3, {11952, 28386, 8405}}, + {3, {10609, 961, 7582}}, + {3, {10423, 13191, 26818}}, + {3, {15922, 36654, 21450}}, + {3, {10492, 1532, 1205}}, + {3, {30551, 36482, 22153}}, + {3, {5156, 11330, 34243}}, + {3, {28616, 35369, 13322}}, + {3, {8962, 1485, 21186}}, + {3, {23541, 17445, 35561}}, + {3, {33133, 11593, 19895}}, + {3, {33917, 7863, 33651}}, + {3, {20063, 28331, 10702}}, + {3, {13195, 21107, 21859}}, + {3, {4364, 31137, 4804}}, + {3, {5585, 2037, 4830}}, + {3, {30672, 16927, 14800}}, + }}; + +// EN 302 307-1 Table B.4 +static const s2_ldpc_table ldpc_nf_fec12 = + {90, 90, { + {8, {54, 9318, 14392, 27561, 26909, 10219, 2534, 8597}}, + {8, {55, 7263, 4635, 2530, 28130, 3033, 23830, 3651}}, + {8, {56, 24731, 23583, 26036, 17299, 5750, 792, 9169}}, + {8, {57, 5811, 26154, 18653, 11551, 15447, 13685, 16264}}, + {8, {58, 12610, 11347, 28768, 2792, 3174, 29371, 12997}}, + {8, {59, 16789, 16018, 21449, 6165, 21202, 15850, 3186}}, + {8, {60, 31016, 21449, 17618, 6213, 12166, 8334, 18212}}, + {8, {61, 22836, 14213, 11327, 5896, 718, 11727, 9308}}, + {8, {62, 2091, 24941, 29966, 23634, 9013, 15587, 5444}}, + {8, {63, 22207, 3983, 16904, 28534, 21415, 27524, 25912}}, + {8, {64, 25687, 4501, 22193, 14665, 14798, 16158, 5491}}, + {8, {65, 4520, 17094, 23397, 4264, 22370, 16941, 21526}}, + {8, {66, 10490, 6182, 32370, 9597, 30841, 25954, 2762}}, + {8, {67, 22120, 22865, 29870, 15147, 13668, 14955, 19235}}, + {8, {68, 6689, 18408, 18346, 9918, 25746, 5443, 20645}}, + {8, {69, 29982, 12529, 13858, 4746, 30370, 10023, 24828}}, + {8, {70, 1262, 28032, 29888, 13063, 24033, 21951, 7863}}, + {8, {71, 6594, 29642, 31451, 14831, 9509, 9335, 31552}}, + {8, {72, 1358, 6454, 16633, 20354, 24598, 624, 5265}}, + {8, {73, 19529, 295, 18011, 3080, 13364, 8032, 15323}}, + {8, {74, 11981, 1510, 7960, 21462, 9129, 11370, 25741}}, + {8, {75, 9276, 29656, 4543, 30699, 20646, 21921, 28050}}, + {8, {76, 15975, 25634, 5520, 31119, 13715, 21949, 19605}}, + {8, {77, 18688, 4608, 31755, 30165, 13103, 10706, 29224}}, + {8, {78, 21514, 23117, 12245, 26035, 31656, 25631, 30699}}, + {8, {79, 9674, 24966, 31285, 29908, 17042, 24588, 31857}}, + {8, {80, 21856, 27777, 29919, 27000, 14897, 11409, 7122}}, + {8, {81, 29773, 23310, 263, 4877, 28622, 20545, 22092}}, + {8, {82, 15605, 5651, 21864, 3967, 14419, 22757, 15896}}, + {8, {83, 30145, 1759, 10139, 29223, 26086, 10556, 5098}}, + {8, {84, 18815, 16575, 2936, 24457, 26738, 6030, 505}}, + {8, {85, 30326, 22298, 27562, 20131, 26390, 6247, 24791}}, + {8, {86, 928, 29246, 21246, 12400, 15311, 32309, 18608}}, + {8, {87, 20314, 6025, 26689, 16302, 2296, 3244, 19613}}, + {8, {88, 6237, 11943, 22851, 15642, 23857, 15112, 20947}}, + {8, {89, 26403, 25168, 19038, 18384, 8882, 12719, 7093}}, + {3, {0, 14567, 24965}}, + {3, {1, 3908, 100}}, + {3, {2, 10279, 240}}, + {3, {3, 24102, 764}}, + {3, {4, 12383, 4173}}, + {3, {5, 13861, 15918}}, + {3, {6, 21327, 1046}}, + {3, {7, 5288, 14579}}, + {3, {8, 28158, 8069}}, + {3, {9, 16583, 11098}}, + {3, {10, 16681, 28363}}, + {3, {11, 13980, 24725}}, + {3, {12, 32169, 17989}}, + {3, {13, 10907, 2767}}, + {3, {14, 21557, 3818}}, + {3, {15, 26676, 12422}}, + {3, {16, 7676, 8754}}, + {3, {17, 14905, 20232}}, + {3, {18, 15719, 24646}}, + {3, {19, 31942, 8589}}, + {3, {20, 19978, 27197}}, + {3, {21, 27060, 15071}}, + {3, {22, 6071, 26649}}, + {3, {23, 10393, 11176}}, + {3, {24, 9597, 13370}}, + {3, {25, 7081, 17677}}, + {3, {26, 1433, 19513}}, + {3, {27, 26925, 9014}}, + {3, {28, 19202, 8900}}, + {3, {29, 18152, 30647}}, + {3, {30, 20803, 1737}}, + {3, {31, 11804, 25221}}, + {3, {32, 31683, 17783}}, + {3, {33, 29694, 9345}}, + {3, {34, 12280, 26611}}, + {3, {35, 6526, 26122}}, + {3, {36, 26165, 11241}}, + {3, {37, 7666, 26962}}, + {3, {38, 16290, 8480}}, + {3, {39, 11774, 10120}}, + {3, {40, 30051, 30426}}, + {3, {41, 1335, 15424}}, + {3, {42, 6865, 17742}}, + {3, {43, 31779, 12489}}, + {3, {44, 32120, 21001}}, + {3, {45, 14508, 6996}}, + {3, {46, 979, 25024}}, + {3, {47, 4554, 21896}}, + {3, {48, 7989, 21777}}, + {3, {49, 4972, 20661}}, + {3, {50, 6612, 2730}}, + {3, {51, 12742, 4418}}, + {3, {52, 29194, 595}}, + {3, {53, 19267, 20113}}, + }}; + +// EN 302 307-1 Table B.5 +static const s2_ldpc_table ldpc_nf_fec35 = + {72, 108, { + {12, {22422, 10282, 11626, 19997, 11161, 2922, 3122, 99, 5625, 17064, 8270, 179}}, + {12, {25087, 16218, 17015, 828, 20041, 25656, 4186, 11629, 22599, 17305, 22515, 6463}}, + {12, {11049, 22853, 25706, 14388, 5500, 19245, 8732, 2177, 13555, 11346, 17265, 3069}}, + {12, {16581, 22225, 12563, 19717, 23577, 11555, 25496, 6853, 25403, 5218, 15925, 21766}}, + {12, {16529, 14487, 7643, 10715, 17442, 11119, 5679, 14155, 24213, 21000, 1116, 15620}}, + {12, {5340, 8636, 16693, 1434, 5635, 6516, 9482, 20189, 1066, 15013, 25361, 14243}}, + {12, {18506, 22236, 20912, 8952, 5421, 15691, 6126, 21595, 500, 6904, 13059, 6802}}, + {12, {8433, 4694, 5524, 14216, 3685, 19721, 25420, 9937, 23813, 9047, 25651, 16826}}, + {12, {21500, 24814, 6344, 17382, 7064, 13929, 4004, 16552, 12818, 8720, 5286, 2206}}, + {12, {22517, 2429, 19065, 2921, 21611, 1873, 7507, 5661, 23006, 23128, 20543, 19777}}, + {12, {1770, 4636, 20900, 14931, 9247, 12340, 11008, 12966, 4471, 2731, 16445, 791}}, + {12, {6635, 14556, 18865, 22421, 22124, 12697, 9803, 25485, 7744, 18254, 11313, 9004}}, + {12, {19982, 23963, 18912, 7206, 12500, 4382, 20067, 6177, 21007, 1195, 23547, 24837}}, + {12, {756, 11158, 14646, 20534, 3647, 17728, 11676, 11843, 12937, 4402, 8261, 22944}}, + {12, {9306, 24009, 10012, 11081, 3746, 24325, 8060, 19826, 842, 8836, 2898, 5019}}, + {12, {7575, 7455, 25244, 4736, 14400, 22981, 5543, 8006, 24203, 13053, 1120, 5128}}, + {12, {3482, 9270, 13059, 15825, 7453, 23747, 3656, 24585, 16542, 17507, 22462, 14670}}, + {12, {15627, 15290, 4198, 22748, 5842, 13395, 23918, 16985, 14929, 3726, 25350, 24157}}, + {12, {24896, 16365, 16423, 13461, 16615, 8107, 24741, 3604, 25904, 8716, 9604, 20365}}, + {12, {3729, 17245, 18448, 9862, 20831, 25326, 20517, 24618, 13282, 5099, 14183, 8804}}, + {12, {16455, 17646, 15376, 18194, 25528, 1777, 6066, 21855, 14372, 12517, 4488, 17490}}, + {12, {1400, 8135, 23375, 20879, 8476, 4084, 12936, 25536, 22309, 16582, 6402, 24360}}, + {12, {25119, 23586, 128, 4761, 10443, 22536, 8607, 9752, 25446, 15053, 1856, 4040}}, + {12, {377, 21160, 13474, 5451, 17170, 5938, 10256, 11972, 24210, 17833, 22047, 16108}}, + {12, {13075, 9648, 24546, 13150, 23867, 7309, 19798, 2988, 16858, 4825, 23950, 15125}}, + {12, {20526, 3553, 11525, 23366, 2452, 17626, 19265, 20172, 18060, 24593, 13255, 1552}}, + {12, {18839, 21132, 20119, 15214, 14705, 7096, 10174, 5663, 18651, 19700, 12524, 14033}}, + {12, {4127, 2971, 17499, 16287, 22368, 21463, 7943, 18880, 5567, 8047, 23363, 6797}}, + {12, {10651, 24471, 14325, 4081, 7258, 4949, 7044, 1078, 797, 22910, 20474, 4318}}, + {12, {21374, 13231, 22985, 5056, 3821, 23718, 14178, 9978, 19030, 23594, 8895, 25358}}, + {12, {6199, 22056, 7749, 13310, 3999, 23697, 16445, 22636, 5225, 22437, 24153, 9442}}, + {12, {7978, 12177, 2893, 20778, 3175, 8645, 11863, 24623, 10311, 25767, 17057, 3691}}, + {12, {20473, 11294, 9914, 22815, 2574, 8439, 3699, 5431, 24840, 21908, 16088, 18244}}, + {12, {8208, 5755, 19059, 8541, 24924, 6454, 11234, 10492, 16406, 10831, 11436, 9649}}, + {12, {16264, 11275, 24953, 2347, 12667, 19190, 7257, 7174, 24819, 2938, 2522, 11749}}, + {12, {3627, 5969, 13862, 1538, 23176, 6353, 2855, 17720, 2472, 7428, 573, 15036}}, + {3, {0, 18539, 18661}}, + {3, {1, 10502, 3002}}, + {3, {2, 9368, 10761}}, + {3, {3, 12299, 7828}}, + {3, {4, 15048, 13362}}, + {3, {5, 18444, 24640}}, + {3, {6, 20775, 19175}}, + {3, {7, 18970, 10971}}, + {3, {8, 5329, 19982}}, + {3, {9, 11296, 18655}}, + {3, {10, 15046, 20659}}, + {3, {11, 7300, 22140}}, + {3, {12, 22029, 14477}}, + {3, {13, 11129, 742}}, + {3, {14, 13254, 13813}}, + {3, {15, 19234, 13273}}, + {3, {16, 6079, 21122}}, + {3, {17, 22782, 5828}}, + {3, {18, 19775, 4247}}, + {3, {19, 1660, 19413}}, + {3, {20, 4403, 3649}}, + {3, {21, 13371, 25851}}, + {3, {22, 22770, 21784}}, + {3, {23, 10757, 14131}}, + {3, {24, 16071, 21617}}, + {3, {25, 6393, 3725}}, + {3, {26, 597, 19968}}, + {3, {27, 5743, 8084}}, + {3, {28, 6770, 9548}}, + {3, {29, 4285, 17542}}, + {3, {30, 13568, 22599}}, + {3, {31, 1786, 4617}}, + {3, {32, 23238, 11648}}, + {3, {33, 19627, 2030}}, + {3, {34, 13601, 13458}}, + {3, {35, 13740, 17328}}, + {3, {36, 25012, 13944}}, + {3, {37, 22513, 6687}}, + {3, {38, 4934, 12587}}, + {3, {39, 21197, 5133}}, + {3, {40, 22705, 6938}}, + {3, {41, 7534, 24633}}, + {3, {42, 24400, 12797}}, + {3, {43, 21911, 25712}}, + {3, {44, 12039, 1140}}, + {3, {45, 24306, 1021}}, + {3, {46, 14012, 20747}}, + {3, {47, 11265, 15219}}, + {3, {48, 4670, 15531}}, + {3, {49, 9417, 14359}}, + {3, {50, 2415, 6504}}, + {3, {51, 24964, 24690}}, + {3, {52, 14443, 8816}}, + {3, {53, 6926, 1291}}, + {3, {54, 6209, 20806}}, + {3, {55, 13915, 4079}}, + {3, {56, 24410, 13196}}, + {3, {57, 13505, 6117}}, + {3, {58, 9869, 8220}}, + {3, {59, 1570, 6044}}, + {3, {60, 25780, 17387}}, + {3, {61, 20671, 24913}}, + {3, {62, 24558, 20591}}, + {3, {63, 12402, 3702}}, + {3, {64, 8314, 1357}}, + {3, {65, 20071, 14616}}, + {3, {66, 17014, 3688}}, + {3, {67, 19837, 946}}, + {3, {68, 15195, 12136}}, + {3, {69, 7758, 22808}}, + {3, {70, 3564, 2925}}, + {3, {71, 3434, 7769}}, + }}; + +// EN 302 307-1 Table B.6 +static const s2_ldpc_table ldpc_nf_fec23 = + {60, 120, { + {13, {0, 10491, 16043, 506, 12826, 8065, 8226, 2767, 240, 18673, 9279, 10579, 20928}}, + {13, {1, 17819, 8313, 6433, 6224, 5120, 5824, 12812, 17187, 9940, 13447, 13825, 18483}}, + {13, {2, 17957, 6024, 8681, 18628, 12794, 5915, 14576, 10970, 12064, 20437, 4455, 7151}}, + {13, {3, 19777, 6183, 9972, 14536, 8182, 17749, 11341, 5556, 4379, 17434, 15477, 18532}}, + {13, {4, 4651, 19689, 1608, 659, 16707, 14335, 6143, 3058, 14618, 17894, 20684, 5306}}, + {13, {5, 9778, 2552, 12096, 12369, 15198, 16890, 4851, 3109, 1700, 18725, 1997, 15882}}, + {13, {6, 486, 6111, 13743, 11537, 5591, 7433, 15227, 14145, 1483, 3887, 17431, 12430}}, + {13, {7, 20647, 14311, 11734, 4180, 8110, 5525, 12141, 15761, 18661, 18441, 10569, 8192}}, + {13, {8, 3791, 14759, 15264, 19918, 10132, 9062, 10010, 12786, 10675, 9682, 19246, 5454}}, + {13, {9, 19525, 9485, 7777, 19999, 8378, 9209, 3163, 20232, 6690, 16518, 716, 7353}}, + {13, {10, 4588, 6709, 20202, 10905, 915, 4317, 11073, 13576, 16433, 368, 3508, 21171}}, + {13, {11, 14072, 4033, 19959, 12608, 631, 19494, 14160, 8249, 10223, 21504, 12395, 4322}}, + {3, {12, 13800, 14161}}, + {3, {13, 2948, 9647}}, + {3, {14, 14693, 16027}}, + {3, {15, 20506, 11082}}, + {3, {16, 1143, 9020}}, + {3, {17, 13501, 4014}}, + {3, {18, 1548, 2190}}, + {3, {19, 12216, 21556}}, + {3, {20, 2095, 19897}}, + {3, {21, 4189, 7958}}, + {3, {22, 15940, 10048}}, + {3, {23, 515, 12614}}, + {3, {24, 8501, 8450}}, + {3, {25, 17595, 16784}}, + {3, {26, 5913, 8495}}, + {3, {27, 16394, 10423}}, + {3, {28, 7409, 6981}}, + {3, {29, 6678, 15939}}, + {3, {30, 20344, 12987}}, + {3, {31, 2510, 14588}}, + {3, {32, 17918, 6655}}, + {3, {33, 6703, 19451}}, + {3, {34, 496, 4217}}, + {3, {35, 7290, 5766}}, + {3, {36, 10521, 8925}}, + {3, {37, 20379, 11905}}, + {3, {38, 4090, 5838}}, + {3, {39, 19082, 17040}}, + {3, {40, 20233, 12352}}, + {3, {41, 19365, 19546}}, + {3, {42, 6249, 19030}}, + {3, {43, 11037, 19193}}, + {3, {44, 19760, 11772}}, + {3, {45, 19644, 7428}}, + {3, {46, 16076, 3521}}, + {3, {47, 11779, 21062}}, + {3, {48, 13062, 9682}}, + {3, {49, 8934, 5217}}, + {3, {50, 11087, 3319}}, + {3, {51, 18892, 4356}}, + {3, {52, 7894, 3898}}, + {3, {53, 5963, 4360}}, + {3, {54, 7346, 11726}}, + {3, {55, 5182, 5609}}, + {3, {56, 2412, 17295}}, + {3, {57, 9845, 20494}}, + {3, {58, 6687, 1864}}, + {3, {59, 20564, 5216}}, + {3, {0, 18226, 17207}}, + {3, {1, 9380, 8266}}, + {3, {2, 7073, 3065}}, + {3, {3, 18252, 13437}}, + {3, {4, 9161, 15642}}, + {3, {5, 10714, 10153}}, + {3, {6, 11585, 9078}}, + {3, {7, 5359, 9418}}, + {3, {8, 9024, 9515}}, + {3, {9, 1206, 16354}}, + {3, {10, 14994, 1102}}, + {3, {11, 9375, 20796}}, + {3, {12, 15964, 6027}}, + {3, {13, 14789, 6452}}, + {3, {14, 8002, 18591}}, + {3, {15, 14742, 14089}}, + {3, {16, 253, 3045}}, + {3, {17, 1274, 19286}}, + {3, {18, 14777, 2044}}, + {3, {19, 13920, 9900}}, + {3, {20, 452, 7374}}, + {3, {21, 18206, 9921}}, + {3, {22, 6131, 5414}}, + {3, {23, 10077, 9726}}, + {3, {24, 12045, 5479}}, + {3, {25, 4322, 7990}}, + {3, {26, 15616, 5550}}, + {3, {27, 15561, 10661}}, + {3, {28, 20718, 7387}}, + {3, {29, 2518, 18804}}, + {3, {30, 8984, 2600}}, + {3, {31, 6516, 17909}}, + {3, {32, 11148, 98}}, + {3, {33, 20559, 3704}}, + {3, {34, 7510, 1569}}, + {3, {35, 16000, 11692}}, + {3, {36, 9147, 10303}}, + {3, {37, 16650, 191}}, + {3, {38, 15577, 18685}}, + {3, {39, 17167, 20917}}, + {3, {40, 4256, 3391}}, + {3, {41, 20092, 17219}}, + {3, {42, 9218, 5056}}, + {3, {43, 18429, 8472}}, + {3, {44, 12093, 20753}}, + {3, {45, 16345, 12748}}, + {3, {46, 16023, 11095}}, + {3, {47, 5048, 17595}}, + {3, {48, 18995, 4817}}, + {3, {49, 16483, 3536}}, + {3, {50, 1439, 16148}}, + {3, {51, 3661, 3039}}, + {3, {52, 19010, 18121}}, + {3, {53, 8968, 11793}}, + {3, {54, 13427, 18003}}, + {3, {55, 5303, 3083}}, + {3, {56, 531, 16668}}, + {3, {57, 4771, 6722}}, + {3, {58, 5695, 7960}}, + {3, {59, 3589, 14630}}, + }}; + +// EN 302 307-1 Table B.7 +static const s2_ldpc_table ldpc_nf_fec34 = + {45, 135, { + {12, {0, 6385, 7901, 14611, 13389, 11200, 3252, 5243, 2504, 2722, 821, 7374}}, + {12, {1, 11359, 2698, 357, 13824, 12772, 7244, 6752, 15310, 852, 2001, 11417}}, + {12, {2, 7862, 7977, 6321, 13612, 12197, 14449, 15137, 13860, 1708, 6399, 13444}}, + {12, {3, 1560, 11804, 6975, 13292, 3646, 3812, 8772, 7306, 5795, 14327, 7866}}, + {12, {4, 7626, 11407, 14599, 9689, 1628, 2113, 10809, 9283, 1230, 15241, 4870}}, + {12, {5, 1610, 5699, 15876, 9446, 12515, 1400, 6303, 5411, 14181, 13925, 7358}}, + {12, {6, 4059, 8836, 3405, 7853, 7992, 15336, 5970, 10368, 10278, 9675, 4651}}, + {12, {7, 4441, 3963, 9153, 2109, 12683, 7459, 12030, 12221, 629, 15212, 406}}, + {12, {8, 6007, 8411, 5771, 3497, 543, 14202, 875, 9186, 6235, 13908, 3563}}, + {12, {9, 3232, 6625, 4795, 546, 9781, 2071, 7312, 3399, 7250, 4932, 12652}}, + {12, {10, 8820, 10088, 11090, 7069, 6585, 13134, 10158, 7183, 488, 7455, 9238}}, + {12, {11, 1903, 10818, 119, 215, 7558, 11046, 10615, 11545, 14784, 7961, 15619}}, + {12, {12, 3655, 8736, 4917, 15874, 5129, 2134, 15944, 14768, 7150, 2692, 1469}}, + {12, {13, 8316, 3820, 505, 8923, 6757, 806, 7957, 4216, 15589, 13244, 2622}}, + {12, {14, 14463, 4852, 15733, 3041, 11193, 12860, 13673, 8152, 6551, 15108, 8758}}, + {3, {15, 3149, 11981}}, + {3, {16, 13416, 6906}}, + {3, {17, 13098, 13352}}, + {3, {18, 2009, 14460}}, + {3, {19, 7207, 4314}}, + {3, {20, 3312, 3945}}, + {3, {21, 4418, 6248}}, + {3, {22, 2669, 13975}}, + {3, {23, 7571, 9023}}, + {3, {24, 14172, 2967}}, + {3, {25, 7271, 7138}}, + {3, {26, 6135, 13670}}, + {3, {27, 7490, 14559}}, + {3, {28, 8657, 2466}}, + {3, {29, 8599, 12834}}, + {3, {30, 3470, 3152}}, + {3, {31, 13917, 4365}}, + {3, {32, 6024, 13730}}, + {3, {33, 10973, 14182}}, + {3, {34, 2464, 13167}}, + {3, {35, 5281, 15049}}, + {3, {36, 1103, 1849}}, + {3, {37, 2058, 1069}}, + {3, {38, 9654, 6095}}, + {3, {39, 14311, 7667}}, + {3, {40, 15617, 8146}}, + {3, {41, 4588, 11218}}, + {3, {42, 13660, 6243}}, + {3, {43, 8578, 7874}}, + {3, {44, 11741, 2686}}, + {3, {0, 1022, 1264}}, + {3, {1, 12604, 9965}}, + {3, {2, 8217, 2707}}, + {3, {3, 3156, 11793}}, + {3, {4, 354, 1514}}, + {3, {5, 6978, 14058}}, + {3, {6, 7922, 16079}}, + {3, {7, 15087, 12138}}, + {3, {8, 5053, 6470}}, + {3, {9, 12687, 14932}}, + {3, {10, 15458, 1763}}, + {3, {11, 8121, 1721}}, + {3, {12, 12431, 549}}, + {3, {13, 4129, 7091}}, + {3, {14, 1426, 8415}}, + {3, {15, 9783, 7604}}, + {3, {16, 6295, 11329}}, + {3, {17, 1409, 12061}}, + {3, {18, 8065, 9087}}, + {3, {19, 2918, 8438}}, + {3, {20, 1293, 14115}}, + {3, {21, 3922, 13851}}, + {3, {22, 3851, 4000}}, + {3, {23, 5865, 1768}}, + {3, {24, 2655, 14957}}, + {3, {25, 5565, 6332}}, + {3, {26, 4303, 12631}}, + {3, {27, 11653, 12236}}, + {3, {28, 16025, 7632}}, + {3, {29, 4655, 14128}}, + {3, {30, 9584, 13123}}, + {3, {31, 13987, 9597}}, + {3, {32, 15409, 12110}}, + {3, {33, 8754, 15490}}, + {3, {34, 7416, 15325}}, + {3, {35, 2909, 15549}}, + {3, {36, 2995, 8257}}, + {3, {37, 9406, 4791}}, + {3, {38, 11111, 4854}}, + {3, {39, 2812, 8521}}, + {3, {40, 8476, 14717}}, + {3, {41, 7820, 15360}}, + {3, {42, 1179, 7939}}, + {3, {43, 2357, 8678}}, + {3, {44, 7703, 6216}}, + {3, {0, 3477, 7067}}, + {3, {1, 3931, 13845}}, + {3, {2, 7675, 12899}}, + {3, {3, 1754, 8187}}, + {3, {4, 7785, 1400}}, + {3, {5, 9213, 5891}}, + {3, {6, 2494, 7703}}, + {3, {7, 2576, 7902}}, + {3, {8, 4821, 15682}}, + {3, {9, 10426, 11935}}, + {3, {10, 1810, 904}}, + {3, {11, 11332, 9264}}, + {3, {12, 11312, 3570}}, + {3, {13, 14916, 2650}}, + {3, {14, 7679, 7842}}, + {3, {15, 6089, 13084}}, + {3, {16, 3938, 2751}}, + {3, {17, 8509, 4648}}, + {3, {18, 12204, 8917}}, + {3, {19, 5749, 12443}}, + {3, {20, 12613, 4431}}, + {3, {21, 1344, 4014}}, + {3, {22, 8488, 13850}}, + {3, {23, 1730, 14896}}, + {3, {24, 14942, 7126}}, + {3, {25, 14983, 8863}}, + {3, {26, 6578, 8564}}, + {3, {27, 4947, 396}}, + {3, {28, 297, 12805}}, + {3, {29, 13878, 6692}}, + {3, {30, 11857, 11186}}, + {3, {31, 14395, 11493}}, + {3, {32, 16145, 12251}}, + {3, {33, 13462, 7428}}, + {3, {34, 14526, 13119}}, + {3, {35, 2535, 11243}}, + {3, {36, 6465, 12690}}, + {3, {37, 6872, 9334}}, + {3, {38, 15371, 14023}}, + {3, {39, 8101, 10187}}, + {3, {40, 11963, 4848}}, + {3, {41, 15125, 6119}}, + {3, {42, 8051, 14465}}, + {3, {43, 11139, 5167}}, + {3, {44, 2883, 14521}}, + }}; + +// EN 302 307-1 Table B.8 +static const s2_ldpc_table ldpc_nf_fec45 = + {36, 144, { + {11, {0, 149, 11212, 5575, 6360, 12559, 8108, 8505, 408, 10026, 12828}}, + {11, {1, 5237, 490, 10677, 4998, 3869, 3734, 3092, 3509, 7703, 10305}}, + {11, {2, 8742, 5553, 2820, 7085, 12116, 10485, 564, 7795, 2972, 2157}}, + {11, {3, 2699, 4304, 8350, 712, 2841, 3250, 4731, 10105, 517, 7516}}, + {11, {4, 12067, 1351, 11992, 12191, 11267, 5161, 537, 6166, 4246, 2363}}, + {11, {5, 6828, 7107, 2127, 3724, 5743, 11040, 10756, 4073, 1011, 3422}}, + {11, {6, 11259, 1216, 9526, 1466, 10816, 940, 3744, 2815, 11506, 11573}}, + {11, {7, 4549, 11507, 1118, 1274, 11751, 5207, 7854, 12803, 4047, 6484}}, + {11, {8, 8430, 4115, 9440, 413, 4455, 2262, 7915, 12402, 8579, 7052}}, + {11, {9, 3885, 9126, 5665, 4505, 2343, 253, 4707, 3742, 4166, 1556}}, + {11, {10, 1704, 8936, 6775, 8639, 8179, 7954, 8234, 7850, 8883, 8713}}, + {11, {11, 11716, 4344, 9087, 11264, 2274, 8832, 9147, 11930, 6054, 5455}}, + {11, {12, 7323, 3970, 10329, 2170, 8262, 3854, 2087, 12899, 9497, 11700}}, + {11, {13, 4418, 1467, 2490, 5841, 817, 11453, 533, 11217, 11962, 5251}}, + {11, {14, 1541, 4525, 7976, 3457, 9536, 7725, 3788, 2982, 6307, 5997}}, + {11, {15, 11484, 2739, 4023, 12107, 6516, 551, 2572, 6628, 8150, 9852}}, + {11, {16, 6070, 1761, 4627, 6534, 7913, 3730, 11866, 1813, 12306, 8249}}, + {11, {17, 12441, 5489, 8748, 7837, 7660, 2102, 11341, 2936, 6712, 11977}}, + {3, {18, 10155, 4210}}, + {3, {19, 1010, 10483}}, + {3, {20, 8900, 10250}}, + {3, {21, 10243, 12278}}, + {3, {22, 7070, 4397}}, + {3, {23, 12271, 3887}}, + {3, {24, 11980, 6836}}, + {3, {25, 9514, 4356}}, + {3, {26, 7137, 10281}}, + {3, {27, 11881, 2526}}, + {3, {28, 1969, 11477}}, + {3, {29, 3044, 10921}}, + {3, {30, 2236, 8724}}, + {3, {31, 9104, 6340}}, + {3, {32, 7342, 8582}}, + {3, {33, 11675, 10405}}, + {3, {34, 6467, 12775}}, + {3, {35, 3186, 12198}}, + {3, {0, 9621, 11445}}, + {3, {1, 7486, 5611}}, + {3, {2, 4319, 4879}}, + {3, {3, 2196, 344}}, + {3, {4, 7527, 6650}}, + {3, {5, 10693, 2440}}, + {3, {6, 6755, 2706}}, + {3, {7, 5144, 5998}}, + {3, {8, 11043, 8033}}, + {3, {9, 4846, 4435}}, + {3, {10, 4157, 9228}}, + {3, {11, 12270, 6562}}, + {3, {12, 11954, 7592}}, + {3, {13, 7420, 2592}}, + {3, {14, 8810, 9636}}, + {3, {15, 689, 5430}}, + {3, {16, 920, 1304}}, + {3, {17, 1253, 11934}}, + {3, {18, 9559, 6016}}, + {3, {19, 312, 7589}}, + {3, {20, 4439, 4197}}, + {3, {21, 4002, 9555}}, + {3, {22, 12232, 7779}}, + {3, {23, 1494, 8782}}, + {3, {24, 10749, 3969}}, + {3, {25, 4368, 3479}}, + {3, {26, 6316, 5342}}, + {3, {27, 2455, 3493}}, + {3, {28, 12157, 7405}}, + {3, {29, 6598, 11495}}, + {3, {30, 11805, 4455}}, + {3, {31, 9625, 2090}}, + {3, {32, 4731, 2321}}, + {3, {33, 3578, 2608}}, + {3, {34, 8504, 1849}}, + {3, {35, 4027, 1151}}, + {3, {0, 5647, 4935}}, + {3, {1, 4219, 1870}}, + {3, {2, 10968, 8054}}, + {3, {3, 6970, 5447}}, + {3, {4, 3217, 5638}}, + {3, {5, 8972, 669}}, + {3, {6, 5618, 12472}}, + {3, {7, 1457, 1280}}, + {3, {8, 8868, 3883}}, + {3, {9, 8866, 1224}}, + {3, {10, 8371, 5972}}, + {3, {11, 266, 4405}}, + {3, {12, 3706, 3244}}, + {3, {13, 6039, 5844}}, + {3, {14, 7200, 3283}}, + {3, {15, 1502, 11282}}, + {3, {16, 12318, 2202}}, + {3, {17, 4523, 965}}, + {3, {18, 9587, 7011}}, + {3, {19, 2552, 2051}}, + {3, {20, 12045, 10306}}, + {3, {21, 11070, 5104}}, + {3, {22, 6627, 6906}}, + {3, {23, 9889, 2121}}, + {3, {24, 829, 9701}}, + {3, {25, 2201, 1819}}, + {3, {26, 6689, 12925}}, + {3, {27, 2139, 8757}}, + {3, {28, 12004, 5948}}, + {3, {29, 8704, 3191}}, + {3, {30, 8171, 10933}}, + {3, {31, 6297, 7116}}, + {3, {32, 616, 7146}}, + {3, {33, 5142, 9761}}, + {3, {34, 10377, 8138}}, + {3, {35, 7616, 5811}}, + {3, {0, 7285, 9863}}, + {3, {1, 7764, 10867}}, + {3, {2, 12343, 9019}}, + {3, {3, 4414, 8331}}, + {3, {4, 3464, 642}}, + {3, {5, 6960, 2039}}, + {3, {6, 786, 3021}}, + {3, {7, 710, 2086}}, + {3, {8, 7423, 5601}}, + {3, {9, 8120, 4885}}, + {3, {10, 12385, 11990}}, + {3, {11, 9739, 10034}}, + {3, {12, 424, 10162}}, + {3, {13, 1347, 7597}}, + {3, {14, 1450, 112}}, + {3, {15, 7965, 8478}}, + {3, {16, 8945, 7397}}, + {3, {17, 6590, 8316}}, + {3, {18, 6838, 9011}}, + {3, {19, 6174, 9410}}, + {3, {20, 255, 113}}, + {3, {21, 6197, 5835}}, + {3, {22, 12902, 3844}}, + {3, {23, 4377, 3505}}, + {3, {24, 5478, 8672}}, + {3, {25, 4453, 2132}}, + {3, {26, 9724, 1380}}, + {3, {27, 12131, 11526}}, + {3, {28, 12323, 9511}}, + {3, {29, 8231, 1752}}, + {3, {30, 497, 9022}}, + {3, {31, 9288, 3080}}, + {3, {32, 2481, 7515}}, + {3, {33, 2696, 268}}, + {3, {34, 4023, 12341}}, + {3, {35, 7108, 5553}}, + }}; + +// EN 302 307-1 Table B.9 +static const s2_ldpc_table ldpc_nf_fec56 = + {30, 150, { + {13, {0, 4362, 416, 8909, 4156, 3216, 3112, 2560, 2912, 6405, 8593, 4969, 6723}}, + {13, {1, 2479, 1786, 8978, 3011, 4339, 9313, 6397, 2957, 7288, 5484, 6031, 10217}}, + {13, {2, 10175, 9009, 9889, 3091, 4985, 7267, 4092, 8874, 5671, 2777, 2189, 8716}}, + {13, {3, 9052, 4795, 3924, 3370, 10058, 1128, 9996, 10165, 9360, 4297, 434, 5138}}, + {13, {4, 2379, 7834, 4835, 2327, 9843, 804, 329, 8353, 7167, 3070, 1528, 7311}}, + {13, {5, 3435, 7871, 348, 3693, 1876, 6585, 10340, 7144, 5870, 2084, 4052, 2780}}, + {13, {6, 3917, 3111, 3476, 1304, 10331, 5939, 5199, 1611, 1991, 699, 8316, 9960}}, + {13, {7, 6883, 3237, 1717, 10752, 7891, 9764, 4745, 3888, 10009, 4176, 4614, 1567}}, + {13, {8, 10587, 2195, 1689, 2968, 5420, 2580, 2883, 6496, 111, 6023, 1024, 4449}}, + {13, {9, 3786, 8593, 2074, 3321, 5057, 1450, 3840, 5444, 6572, 3094, 9892, 1512}}, + {13, {10, 8548, 1848, 10372, 4585, 7313, 6536, 6379, 1766, 9462, 2456, 5606, 9975}}, + {13, {11, 8204, 10593, 7935, 3636, 3882, 394, 5968, 8561, 2395, 7289, 9267, 9978}}, + {13, {12, 7795, 74, 1633, 9542, 6867, 7352, 6417, 7568, 10623, 725, 2531, 9115}}, + {13, {13, 7151, 2482, 4260, 5003, 10105, 7419, 9203, 6691, 8798, 2092, 8263, 3755}}, + {13, {14, 3600, 570, 4527, 200, 9718, 6771, 1995, 8902, 5446, 768, 1103, 6520}}, + {3, {15, 6304, 7621}}, + {3, {16, 6498, 9209}}, + {3, {17, 7293, 6786}}, + {3, {18, 5950, 1708}}, + {3, {19, 8521, 1793}}, + {3, {20, 6174, 7854}}, + {3, {21, 9773, 1190}}, + {3, {22, 9517, 10268}}, + {3, {23, 2181, 9349}}, + {3, {24, 1949, 5560}}, + {3, {25, 1556, 555}}, + {3, {26, 8600, 3827}}, + {3, {27, 5072, 1057}}, + {3, {28, 7928, 3542}}, + {3, {29, 3226, 3762}}, + {3, {0, 7045, 2420}}, + {3, {1, 9645, 2641}}, + {3, {2, 2774, 2452}}, + {3, {3, 5331, 2031}}, + {3, {4, 9400, 7503}}, + {3, {5, 1850, 2338}}, + {3, {6, 10456, 9774}}, + {3, {7, 1692, 9276}}, + {3, {8, 10037, 4038}}, + {3, {9, 3964, 338}}, + {3, {10, 2640, 5087}}, + {3, {11, 858, 3473}}, + {3, {12, 5582, 5683}}, + {3, {13, 9523, 916}}, + {3, {14, 4107, 1559}}, + {3, {15, 4506, 3491}}, + {3, {16, 8191, 4182}}, + {3, {17, 10192, 6157}}, + {3, {18, 5668, 3305}}, + {3, {19, 3449, 1540}}, + {3, {20, 4766, 2697}}, + {3, {21, 4069, 6675}}, + {3, {22, 1117, 1016}}, + {3, {23, 5619, 3085}}, + {3, {24, 8483, 8400}}, + {3, {25, 8255, 394}}, + {3, {26, 6338, 5042}}, + {3, {27, 6174, 5119}}, + {3, {28, 7203, 1989}}, + {3, {29, 1781, 5174}}, + {3, {0, 1464, 3559}}, + {3, {1, 3376, 4214}}, + {3, {2, 7238, 67}}, + {3, {3, 10595, 8831}}, + {3, {4, 1221, 6513}}, + {3, {5, 5300, 4652}}, + {3, {6, 1429, 9749}}, + {3, {7, 7878, 5131}}, + {3, {8, 4435, 10284}}, + {3, {9, 6331, 5507}}, + {3, {10, 6662, 4941}}, + {3, {11, 9614, 10238}}, + {3, {12, 8400, 8025}}, + {3, {13, 9156, 5630}}, + {3, {14, 7067, 8878}}, + {3, {15, 9027, 3415}}, + {3, {16, 1690, 3866}}, + {3, {17, 2854, 8469}}, + {3, {18, 6206, 630}}, + {3, {19, 363, 5453}}, + {3, {20, 4125, 7008}}, + {3, {21, 1612, 6702}}, + {3, {22, 9069, 9226}}, + {3, {23, 5767, 4060}}, + {3, {24, 3743, 9237}}, + {3, {25, 7018, 5572}}, + {3, {26, 8892, 4536}}, + {3, {27, 853, 6064}}, + {3, {28, 8069, 5893}}, + {3, {29, 2051, 2885}}, + {3, {0, 10691, 3153}}, + {3, {1, 3602, 4055}}, + {3, {2, 328, 1717}}, + {3, {3, 2219, 9299}}, + {3, {4, 1939, 7898}}, + {3, {5, 617, 206}}, + {3, {6, 8544, 1374}}, + {3, {7, 10676, 3240}}, + {3, {8, 6672, 9489}}, + {3, {9, 3170, 7457}}, + {3, {10, 7868, 5731}}, + {3, {11, 6121, 10732}}, + {3, {12, 4843, 9132}}, + {3, {13, 580, 9591}}, + {3, {14, 6267, 9290}}, + {3, {15, 3009, 2268}}, + {3, {16, 195, 2419}}, + {3, {17, 8016, 1557}}, + {3, {18, 1516, 9195}}, + {3, {19, 8062, 9064}}, + {3, {20, 2095, 8968}}, + {3, {21, 753, 7326}}, + {3, {22, 6291, 3833}}, + {3, {23, 2614, 7844}}, + {3, {24, 2303, 646}}, + {3, {25, 2075, 611}}, + {3, {26, 4687, 362}}, + {3, {27, 8684, 9940}}, + {3, {28, 4830, 2065}}, + {3, {29, 7038, 1363}}, + {3, {0, 1769, 7837}}, + {3, {1, 3801, 1689}}, + {3, {2, 10070, 2359}}, + {3, {3, 3667, 9918}}, + {3, {4, 1914, 6920}}, + {3, {5, 4244, 5669}}, + {3, {6, 10245, 7821}}, + {3, {7, 7648, 3944}}, + {3, {8, 3310, 5488}}, + {3, {9, 6346, 9666}}, + {3, {10, 7088, 6122}}, + {3, {11, 1291, 7827}}, + {3, {12, 10592, 8945}}, + {3, {13, 3609, 7120}}, + {3, {14, 9168, 9112}}, + {3, {15, 6203, 8052}}, + {3, {16, 3330, 2895}}, + {3, {17, 4264, 10563}}, + {3, {18, 10556, 6496}}, + {3, {19, 8807, 7645}}, + {3, {20, 1999, 4530}}, + {3, {21, 9202, 6818}}, + {3, {22, 3403, 1734}}, + {3, {23, 2106, 9023}}, + {3, {24, 6881, 3883}}, + {3, {25, 3895, 2171}}, + {3, {26, 4062, 6424}}, + {3, {27, 3755, 9536}}, + {3, {28, 4683, 2131}}, + {3, {29, 7347, 8027}}, + }}; + +// EN 302 307-1 Table B.10 +static const s2_ldpc_table ldpc_nf_fec89 = + {20, 160, { + {4, {0, 6235, 2848, 3222}}, + {4, {1, 5800, 3492, 5348}}, + {4, {2, 2757, 927, 90}}, + {4, {3, 6961, 4516, 4739}}, + {4, {4, 1172, 3237, 6264}}, + {4, {5, 1927, 2425, 3683}}, + {4, {6, 3714, 6309, 2495}}, + {4, {7, 3070, 6342, 7154}}, + {4, {8, 2428, 613, 3761}}, + {4, {9, 2906, 264, 5927}}, + {4, {10, 1716, 1950, 4273}}, + {4, {11, 4613, 6179, 3491}}, + {4, {12, 4865, 3286, 6005}}, + {4, {13, 1343, 5923, 3529}}, + {4, {14, 4589, 4035, 2132}}, + {4, {15, 1579, 3920, 6737}}, + {4, {16, 1644, 1191, 5998}}, + {4, {17, 1482, 2381, 4620}}, + {4, {18, 6791, 6014, 6596}}, + {4, {19, 2738, 5918, 3786}}, + {3, {0, 5156, 6166}}, + {3, {1, 1504, 4356}}, + {3, {2, 130, 1904}}, + {3, {3, 6027, 3187}}, + {3, {4, 6718, 759}}, + {3, {5, 6240, 2870}}, + {3, {6, 2343, 1311}}, + {3, {7, 1039, 5465}}, + {3, {8, 6617, 2513}}, + {3, {9, 1588, 5222}}, + {3, {10, 6561, 535}}, + {3, {11, 4765, 2054}}, + {3, {12, 5966, 6892}}, + {3, {13, 1969, 3869}}, + {3, {14, 3571, 2420}}, + {3, {15, 4632, 981}}, + {3, {16, 3215, 4163}}, + {3, {17, 973, 3117}}, + {3, {18, 3802, 6198}}, + {3, {19, 3794, 3948}}, + {3, {0, 3196, 6126}}, + {3, {1, 573, 1909}}, + {3, {2, 850, 4034}}, + {3, {3, 5622, 1601}}, + {3, {4, 6005, 524}}, + {3, {5, 5251, 5783}}, + {3, {6, 172, 2032}}, + {3, {7, 1875, 2475}}, + {3, {8, 497, 1291}}, + {3, {9, 2566, 3430}}, + {3, {10, 1249, 740}}, + {3, {11, 2944, 1948}}, + {3, {12, 6528, 2899}}, + {3, {13, 2243, 3616}}, + {3, {14, 867, 3733}}, + {3, {15, 1374, 4702}}, + {3, {16, 4698, 2285}}, + {3, {17, 4760, 3917}}, + {3, {18, 1859, 4058}}, + {3, {19, 6141, 3527}}, + {3, {0, 2148, 5066}}, + {3, {1, 1306, 145}}, + {3, {2, 2319, 871}}, + {3, {3, 3463, 1061}}, + {3, {4, 5554, 6647}}, + {3, {5, 5837, 339}}, + {3, {6, 5821, 4932}}, + {3, {7, 6356, 4756}}, + {3, {8, 3930, 418}}, + {3, {9, 211, 3094}}, + {3, {10, 1007, 4928}}, + {3, {11, 3584, 1235}}, + {3, {12, 6982, 2869}}, + {3, {13, 1612, 1013}}, + {3, {14, 953, 4964}}, + {3, {15, 4555, 4410}}, + {3, {16, 4925, 4842}}, + {3, {17, 5778, 600}}, + {3, {18, 6509, 2417}}, + {3, {19, 1260, 4903}}, + {3, {0, 3369, 3031}}, + {3, {1, 3557, 3224}}, + {3, {2, 3028, 583}}, + {3, {3, 3258, 440}}, + {3, {4, 6226, 6655}}, + {3, {5, 4895, 1094}}, + {3, {6, 1481, 6847}}, + {3, {7, 4433, 1932}}, + {3, {8, 2107, 1649}}, + {3, {9, 2119, 2065}}, + {3, {10, 4003, 6388}}, + {3, {11, 6720, 3622}}, + {3, {12, 3694, 4521}}, + {3, {13, 1164, 7050}}, + {3, {14, 1965, 3613}}, + {3, {15, 4331, 66}}, + {3, {16, 2970, 1796}}, + {3, {17, 4652, 3218}}, + {3, {18, 1762, 4777}}, + {3, {19, 5736, 1399}}, + {3, {0, 970, 2572}}, + {3, {1, 2062, 6599}}, + {3, {2, 4597, 4870}}, + {3, {3, 1228, 6913}}, + {3, {4, 4159, 1037}}, + {3, {5, 2916, 2362}}, + {3, {6, 395, 1226}}, + {3, {7, 6911, 4548}}, + {3, {8, 4618, 2241}}, + {3, {9, 4120, 4280}}, + {3, {10, 5825, 474}}, + {3, {11, 2154, 5558}}, + {3, {12, 3793, 5471}}, + {3, {13, 5707, 1595}}, + {3, {14, 1403, 325}}, + {3, {15, 6601, 5183}}, + {3, {16, 6369, 4569}}, + {3, {17, 4846, 896}}, + {3, {18, 7092, 6184}}, + {3, {19, 6764, 7127}}, + {3, {0, 6358, 1951}}, + {3, {1, 3117, 6960}}, + {3, {2, 2710, 7062}}, + {3, {3, 1133, 3604}}, + {3, {4, 3694, 657}}, + {3, {5, 1355, 110}}, + {3, {6, 3329, 6736}}, + {3, {7, 2505, 3407}}, + {3, {8, 2462, 4806}}, + {3, {9, 4216, 214}}, + {3, {10, 5348, 5619}}, + {3, {11, 6627, 6243}}, + {3, {12, 2644, 5073}}, + {3, {13, 4212, 5088}}, + {3, {14, 3463, 3889}}, + {3, {15, 5306, 478}}, + {3, {16, 4320, 6121}}, + {3, {17, 3961, 1125}}, + {3, {18, 5699, 1195}}, + {3, {19, 6511, 792}}, + {3, {0, 3934, 2778}}, + {3, {1, 3238, 6587}}, + {3, {2, 1111, 6596}}, + {3, {3, 1457, 6226}}, + {3, {4, 1446, 3885}}, + {3, {5, 3907, 4043}}, + {3, {6, 6839, 2873}}, + {3, {7, 1733, 5615}}, + {3, {8, 5202, 4269}}, + {3, {9, 3024, 4722}}, + {3, {10, 5445, 6372}}, + {3, {11, 370, 1828}}, + {3, {12, 4695, 1600}}, + {3, {13, 680, 2074}}, + {3, {14, 1801, 6690}}, + {3, {15, 2669, 1377}}, + {3, {16, 2463, 1681}}, + {3, {17, 5972, 5171}}, + {3, {18, 5728, 4284}}, + {3, {19, 1696, 1459}}, + }}; + +// EN 302 307-1 Table B.11 +static const s2_ldpc_table ldpc_nf_fec910 = + {18, 162, { + {4, {0, 5611, 2563, 2900}}, + {4, {1, 5220, 3143, 4813}}, + {4, {2, 2481, 834, 81}}, + {4, {3, 6265, 4064, 4265}}, + {4, {4, 1055, 2914, 5638}}, + {4, {5, 1734, 2182, 3315}}, + {4, {6, 3342, 5678, 2246}}, + {4, {7, 2185, 552, 3385}}, + {4, {8, 2615, 236, 5334}}, + {4, {9, 1546, 1755, 3846}}, + {4, {10, 4154, 5561, 3142}}, + {4, {11, 4382, 2957, 5400}}, + {4, {12, 1209, 5329, 3179}}, + {4, {13, 1421, 3528, 6063}}, + {4, {14, 1480, 1072, 5398}}, + {4, {15, 3843, 1777, 4369}}, + {4, {16, 1334, 2145, 4163}}, + {4, {17, 2368, 5055, 260}}, + {3, {0, 6118, 5405}}, + {3, {1, 2994, 4370}}, + {3, {2, 3405, 1669}}, + {3, {3, 4640, 5550}}, + {3, {4, 1354, 3921}}, + {3, {5, 117, 1713}}, + {3, {6, 5425, 2866}}, + {3, {7, 6047, 683}}, + {3, {8, 5616, 2582}}, + {3, {9, 2108, 1179}}, + {3, {10, 933, 4921}}, + {3, {11, 5953, 2261}}, + {3, {12, 1430, 4699}}, + {3, {13, 5905, 480}}, + {3, {14, 4289, 1846}}, + {3, {15, 5374, 6208}}, + {3, {16, 1775, 3476}}, + {3, {17, 3216, 2178}}, + {3, {0, 4165, 884}}, + {3, {1, 2896, 3744}}, + {3, {2, 874, 2801}}, + {3, {3, 3423, 5579}}, + {3, {4, 3404, 3552}}, + {3, {5, 2876, 5515}}, + {3, {6, 516, 1719}}, + {3, {7, 765, 3631}}, + {3, {8, 5059, 1441}}, + {3, {9, 5629, 598}}, + {3, {10, 5405, 473}}, + {3, {11, 4724, 5210}}, + {3, {12, 155, 1832}}, + {3, {13, 1689, 2229}}, + {3, {14, 449, 1164}}, + {3, {15, 2308, 3088}}, + {3, {16, 1122, 669}}, + {3, {17, 2268, 5758}}, + {3, {0, 5878, 2609}}, + {3, {1, 782, 3359}}, + {3, {2, 1231, 4231}}, + {3, {3, 4225, 2052}}, + {3, {4, 4286, 3517}}, + {3, {5, 5531, 3184}}, + {3, {6, 1935, 4560}}, + {3, {7, 1174, 131}}, + {3, {8, 3115, 956}}, + {3, {9, 3129, 1088}}, + {3, {10, 5238, 4440}}, + {3, {11, 5722, 4280}}, + {3, {12, 3540, 375}}, + {3, {13, 191, 2782}}, + {3, {14, 906, 4432}}, + {3, {15, 3225, 1111}}, + {3, {16, 6296, 2583}}, + {3, {17, 1457, 903}}, + {3, {0, 855, 4475}}, + {3, {1, 4097, 3970}}, + {3, {2, 4433, 4361}}, + {3, {3, 5198, 541}}, + {3, {4, 1146, 4426}}, + {3, {5, 3202, 2902}}, + {3, {6, 2724, 525}}, + {3, {7, 1083, 4124}}, + {3, {8, 2326, 6003}}, + {3, {9, 5605, 5990}}, + {3, {10, 4376, 1579}}, + {3, {11, 4407, 984}}, + {3, {12, 1332, 6163}}, + {3, {13, 5359, 3975}}, + {3, {14, 1907, 1854}}, + {3, {15, 3601, 5748}}, + {3, {16, 6056, 3266}}, + {3, {17, 3322, 4085}}, + {3, {0, 1768, 3244}}, + {3, {1, 2149, 144}}, + {3, {2, 1589, 4291}}, + {3, {3, 5154, 1252}}, + {3, {4, 1855, 5939}}, + {3, {5, 4820, 2706}}, + {3, {6, 1475, 3360}}, + {3, {7, 4266, 693}}, + {3, {8, 4156, 2018}}, + {3, {9, 2103, 752}}, + {3, {10, 3710, 3853}}, + {3, {11, 5123, 931}}, + {3, {12, 6146, 3323}}, + {3, {13, 1939, 5002}}, + {3, {14, 5140, 1437}}, + {3, {15, 1263, 293}}, + {3, {16, 5949, 4665}}, + {3, {17, 4548, 6380}}, + {3, {0, 3171, 4690}}, + {3, {1, 5204, 2114}}, + {3, {2, 6384, 5565}}, + {3, {3, 5722, 1757}}, + {3, {4, 2805, 6264}}, + {3, {5, 1202, 2616}}, + {3, {6, 1018, 3244}}, + {3, {7, 4018, 5289}}, + {3, {8, 2257, 3067}}, + {3, {9, 2483, 3073}}, + {3, {10, 1196, 5329}}, + {3, {11, 649, 3918}}, + {3, {12, 3791, 4581}}, + {3, {13, 5028, 3803}}, + {3, {14, 3119, 3506}}, + {3, {15, 4779, 431}}, + {3, {16, 3888, 5510}}, + {3, {17, 4387, 4084}}, + {3, {0, 5836, 1692}}, + {3, {1, 5126, 1078}}, + {3, {2, 5721, 6165}}, + {3, {3, 3540, 2499}}, + {3, {4, 2225, 6348}}, + {3, {5, 1044, 1484}}, + {3, {6, 6323, 4042}}, + {3, {7, 1313, 5603}}, + {3, {8, 1303, 3496}}, + {3, {9, 3516, 3639}}, + {3, {10, 5161, 2293}}, + {3, {11, 4682, 3845}}, + {3, {12, 3045, 643}}, + {3, {13, 2818, 2616}}, + {3, {14, 3267, 649}}, + {3, {15, 6236, 593}}, + {3, {16, 646, 2948}}, + {3, {17, 4213, 1442}}, + {3, {0, 5779, 1596}}, + {3, {1, 2403, 1237}}, + {3, {2, 2217, 1514}}, + {3, {3, 5609, 716}}, + {3, {4, 5155, 3858}}, + {3, {5, 1517, 1312}}, + {3, {6, 2554, 3158}}, + {3, {7, 5280, 2643}}, + {3, {8, 4990, 1353}}, + {3, {9, 5648, 1170}}, + {3, {10, 1152, 4366}}, + {3, {11, 3561, 5368}}, + {3, {12, 3581, 1411}}, + {3, {13, 5647, 4661}}, + {3, {14, 1542, 5401}}, + {3, {15, 5078, 2687}}, + {3, {16, 316, 1755}}, + {3, {17, 3392, 1991}}, + }}; + +// EN 302 307-1 Table C.1 +static const s2_ldpc_table ldpc_sf_fec14 = + {36, 9, { + {12, {6295, 9626, 304, 7695, 4839, 4936, 1660, 144, 11203, 5567, 6347, 12557}}, + {12, {10691, 4988, 3859, 3734, 3071, 3494, 7687, 10313, 5964, 8069, 8296, 11090}}, + {12, {10774, 3613, 5208, 11177, 7676, 3549, 8746, 6583, 7239, 12265, 2674, 4292}}, + {12, {11869, 3708, 5981, 8718, 4908, 10650, 6805, 3334, 2627, 10461, 9285, 11120}}, + {3, {7844, 3079, 10773}}, + {3, {3385, 10854, 5747}}, + {3, {1360, 12010, 12202}}, + {3, {6189, 4241, 2343}}, + {3, {9840, 12726, 4977}}, + }}; + +// EN 302 307-1 Table C.2 +static const s2_ldpc_table ldpc_sf_fec13 = + {30, 15, { + {12, {416, 8909, 4156, 3216, 3112, 2560, 2912, 6405, 8593, 4969, 6723, 6912}}, + {12, {8978, 3011, 4339, 9312, 6396, 2957, 7288, 5485, 6031, 10218, 2226, 3575}}, + {12, {3383, 10059, 1114, 10008, 10147, 9384, 4290, 434, 5139, 3536, 1965, 2291}}, + {12, {2797, 3693, 7615, 7077, 743, 1941, 8716, 6215, 3840, 5140, 4582, 5420}}, + {12, {6110, 8551, 1515, 7404, 4879, 4946, 5383, 1831, 3441, 9569, 10472, 4306}}, + {3, {1505, 5682, 7778}}, + {3, {7172, 6830, 6623}}, + {3, {7281, 3941, 3505}}, + {3, {10270, 8669, 914}}, + {3, {3622, 7563, 9388}}, + {3, {9930, 5058, 4554}}, + {3, {4844, 9609, 2707}}, + {3, {6883, 3237, 1714}}, + {3, {4768, 3878, 10017}}, + {3, {10127, 3334, 8267}}, + }}; + +// EN 302 307-1 Table C.3 +static const s2_ldpc_table ldpc_sf_fec25 = + {27, 18, { + {12, {5650, 4143, 8750, 583, 6720, 8071, 635, 1767, 1344, 6922, 738, 6658}}, + {12, {5696, 1685, 3207, 415, 7019, 5023, 5608, 2605, 857, 6915, 1770, 8016}}, + {12, {3992, 771, 2190, 7258, 8970, 7792, 1802, 1866, 6137, 8841, 886, 1931}}, + {12, {4108, 3781, 7577, 6810, 9322, 8226, 5396, 5867, 4428, 8827, 7766, 2254}}, + {12, {4247, 888, 4367, 8821, 9660, 324, 5864, 4774, 227, 7889, 6405, 8963}}, + {12, {9693, 500, 2520, 2227, 1811, 9330, 1928, 5140, 4030, 4824, 806, 3134}}, + {12, {1652, 8171, 1435}}, + {12, {3366, 6543, 3745}}, + {12, {9286, 8509, 4645}}, + {12, {7397, 5790, 8972}}, + {12, {6597, 4422, 1799}}, + {12, {9276, 4041, 3847}}, + {12, {8683, 7378, 4946}}, + {12, {5348, 1993, 9186}}, + {12, {6724, 9015, 5646}}, + {12, {4502, 4439, 8474}}, + {12, {5107, 7342, 9442}}, + {12, {1387, 8910, 2660}}, + }}; + +// EN 302 307-1 Table C.4 +static const s2_ldpc_table ldpc_sf_fec12 = + {25, 20, { + {8, {20, 712, 2386, 6354, 4061, 1062, 5045, 5158}}, + {8, {21, 2543, 5748, 4822, 2348, 3089, 6328, 5876}}, + {8, {22, 926, 5701, 269, 3693, 2438, 3190, 3507}}, + {8, {23, 2802, 4520, 3577, 5324, 1091, 4667, 4449}}, + {8, {24, 5140, 2003, 1263, 4742, 6497, 1185, 6202}}, + {3, {0, 4046, 6934}}, + {3, {1, 2855, 66}}, + {3, {2, 6694, 212}}, + {3, {3, 3439, 1158}}, + {3, {4, 3850, 4422}}, + {3, {5, 5924, 290}}, + {3, {6, 1467, 4049}}, + {3, {7, 7820, 2242}}, + {3, {8, 4606, 3080}}, + {3, {9, 4633, 7877}}, + {3, {10, 3884, 6868}}, + {3, {11, 8935, 4996}}, + {3, {12, 3028, 764}}, + {3, {13, 5988, 1057}}, + {3, {14, 7411, 3450}}, + }}; + +// EN 302 307-1 Table C.5 +static const s2_ldpc_table ldpc_sf_fec35 = + {18, 27, { + {12, {2765, 5713, 6426, 3596, 1374, 4811, 2182, 544, 3394, 2840, 4310, 771}}, + {12, {4951, 211, 2208, 723, 1246, 2928, 398, 5739, 265, 5601, 5993, 2615}}, + {12, {210, 4730, 5777, 3096, 4282, 6238, 4939, 1119, 6463, 5298, 6320, 4016}}, + {12, {4167, 2063, 4757, 3157, 5664, 3956, 6045, 563, 4284, 2441, 3412, 6334}}, + {12, {4201, 2428, 4474, 59, 1721, 736, 2997, 428, 3807, 1513, 4732, 6195}}, + {12, {2670, 3081, 5139, 3736, 1999, 5889, 4362, 3806, 4534, 5409, 6384, 5809}}, + {12, {5516, 1622, 2906, 3285, 1257, 5797, 3816, 817, 875, 2311, 3543, 1205}}, + {12, {4244, 2184, 5415, 1705, 5642, 4886, 2333, 287, 1848, 1121, 3595, 6022}}, + {12, {2142, 2830, 4069, 5654, 1295, 2951, 3919, 1356, 884, 1786, 396, 4738}}, + {3, {0, 2161, 2653}}, + {3, {1, 1380, 1461}}, + {3, {2, 2502, 3707}}, + {3, {3, 3971, 1057}}, + {3, {4, 5985, 6062}}, + {3, {5, 1733, 6028}}, + {3, {6, 3786, 1936}}, + {3, {7, 4292, 956}}, + {3, {8, 5692, 3417}}, + {3, {9, 266, 4878}}, + {3, {10, 4913, 3247}}, + {3, {11, 4763, 3937}}, + {3, {12, 3590, 2903}}, + {3, {13, 2566, 4215}}, + {3, {14, 5208, 4707}}, + {3, {15, 3940, 3388}}, + {3, {16, 5109, 4556}}, + {3, {17, 4908, 4177}}, + }}; + +// EN 302 307-1 Table C.6 +static const s2_ldpc_table ldpc_sf_fec23 = + {15, 30, { + {13, {0, 2084, 1613, 1548, 1286, 1460, 3196, 4297, 2481, 3369, 3451, 4620, 2622}}, + {13, {1, 122, 1516, 3448, 2880, 1407, 1847, 3799, 3529, 373, 971, 4358, 3108}}, + {13, {2, 259, 3399, 929, 2650, 864, 3996, 3833, 107, 5287, 164, 3125, 2350}}, + {3, {3, 342, 3529}}, + {3, {4, 4198, 2147}}, + {3, {5, 1880, 4836}}, + {3, {6, 3864, 4910}}, + {3, {7, 243, 1542}}, + {3, {8, 3011, 1436}}, + {3, {9, 2167, 2512}}, + {3, {10, 4606, 1003}}, + {3, {11, 2835, 705}}, + {3, {12, 3426, 2365}}, + {3, {13, 3848, 2474}}, + {3, {14, 1360, 1743}}, + {3, {0, 163, 2536}}, + {3, {1, 2583, 1180}}, + {3, {2, 1542, 509}}, + {3, {3, 4418, 1005}}, + {3, {4, 5212, 5117}}, + {3, {5, 2155, 2922}}, + {3, {6, 347, 2696}}, + {3, {7, 226, 4296}}, + {3, {8, 1560, 487}}, + {3, {9, 3926, 1640}}, + {3, {10, 149, 2928}}, + {3, {11, 2364, 563}}, + {3, {12, 635, 688}}, + {3, {13, 231, 1684}}, + {3, {14, 1129, 3894}}, + }}; + +// EN 302 307-1 Table C.7 +static const s2_ldpc_table ldpc_sf_fec34 = + {12, 33, { + {3, {3, 3198, 478, 4207, 1481, 1009, 2616, 1924, 3437, 554, 683, 1801}}, + {3, {4, 2681, 2135}}, + {3, {5, 3107, 4027}}, + {3, {6, 2637, 3373}}, + {3, {7, 3830, 3449}}, + {3, {8, 4129, 2060}}, + {3, {9, 4184, 2742}}, + {3, {10, 3946, 1070}}, + {3, {11, 2239, 984}}, + {3, {0, 1458, 3031}}, + {3, {1, 3003, 1328}}, + {3, {2, 1137, 1716}}, + {3, {3, 132, 3725}}, + {3, {4, 1817, 638}}, + {3, {5, 1774, 3447}}, + {3, {6, 3632, 1257}}, + {3, {7, 542, 3694}}, + {3, {8, 1015, 1945}}, + {3, {9, 1948, 412}}, + {3, {10, 995, 2238}}, + {3, {11, 4141, 1907}}, + {3, {0, 2480, 3079}}, + {3, {1, 3021, 1088}}, + {3, {2, 713, 1379}}, + {3, {3, 997, 3903}}, + {3, {4, 2323, 3361}}, + {3, {5, 1110, 986}}, + {3, {6, 2532, 142}}, + {3, {7, 1690, 2405}}, + {3, {8, 1298, 1881}}, + {3, {9, 615, 174}}, + {3, {10, 1648, 3112}}, + {3, {11, 1415, 2808}}, + }}; + +// EN 302 307-1 Table C.8 +static const s2_ldpc_table ldpc_sf_fec45 = + {10, 35, { + {3, {5, 896, 1565}}, + {3, {6, 2493, 184}}, + {3, {7, 212, 3210}}, + {3, {8, 727, 1339}}, + {3, {9, 3428, 612}}, + {3, {0, 2663, 1947}}, + {3, {1, 230, 2695}}, + {3, {2, 2025, 2794}}, + {3, {3, 3039, 283}}, + {3, {4, 862, 2889}}, + {3, {5, 376, 2110}}, + {3, {6, 2034, 2286}}, + {3, {7, 951, 2068}}, + {3, {8, 3108, 3542}}, + {3, {9, 307, 1421}}, + {3, {0, 2272, 1197}}, + {3, {1, 1800, 3280}}, + {3, {2, 331, 2308}}, + {3, {3, 465, 2552}}, + {3, {4, 1038, 2479}}, + {3, {5, 1383, 343}}, + {3, {6, 94, 236}}, + {3, {7, 2619, 121}}, + {3, {8, 1497, 2774}}, + {3, {9, 2116, 1855}}, + {3, {0, 722, 1584}}, + {3, {1, 2767, 1881}}, + {3, {2, 2701, 1610}}, + {3, {3, 3283, 1732}}, + {3, {4, 168, 1099}}, + {3, {5, 3074, 243}}, + {3, {6, 3460, 945}}, + {3, {7, 2049, 1746}}, + {3, {8, 566, 1427}}, + {3, {9, 3545, 1168}}, + }}; + +// EN 302 307-1 Table C.9 +static const s2_ldpc_table ldpc_sf_fec56 = + {8, 37, { + {13, {3, 2409, 499, 1481, 908, 559, 716, 1270, 333, 2508, 2264, 1702, 2805}}, + {3, {4, 2447, 1926}}, + {3, {5, 414, 1224}}, + {3, {6, 2114, 842}}, + {3, {7, 212, 573}}, + {3, {0, 2383, 2112}}, + {3, {1, 2286, 2348}}, + {3, {2, 545, 819}}, + {3, {3, 1264, 143}}, + {3, {4, 1701, 2258}}, + {3, {5, 964, 166}}, + {3, {6, 114, 2413}}, + {3, {7, 2243, 81}}, + {3, {0, 1245, 1581}}, + {3, {1, 775, 169}}, + {3, {2, 1696, 1104}}, + {3, {3, 1914, 2831}}, + {3, {4, 532, 1450}}, + {3, {5, 91, 974}}, + {3, {6, 497, 2228}}, + {3, {7, 2326, 1579}}, + {3, {0, 2482, 256}}, + {3, {1, 1117, 1261}}, + {3, {2, 1257, 1658}}, + {3, {3, 1478, 1225}}, + {3, {4, 2511, 980}}, + {3, {5, 2320, 2675}}, + {3, {6, 435, 1278}}, + {3, {7, 228, 503}}, + {3, {0, 1885, 2369}}, + {3, {1, 57, 483}}, + {3, {2, 838, 1050}}, + {3, {3, 1231, 1990}}, + {3, {4, 1738, 68}}, + {3, {5, 2392, 951}}, + {3, {6, 163, 645}}, + {3, {7, 2644, 1704}}, + }}; + +// EN 302 307-1 Table C.10 +static const s2_ldpc_table ldpc_sf_fec89 = + {5, 40, { + {4, {0, 1558, 712, 805}}, + {4, {1, 1450, 873, 1337}}, + {4, {2, 1741, 1129, 1184}}, + {4, {3, 294, 806, 1566}}, + {4, {4, 482, 605, 923}}, + {3, {0, 926, 1578}}, + {3, {1, 777, 1374}}, + {3, {2, 608, 151}}, + {3, {3, 1195, 210}}, + {3, {4, 1484, 692}}, + {3, {0, 427, 488}}, + {3, {1, 828, 1124}}, + {3, {2, 874, 1366}}, + {3, {3, 1500, 835}}, + {3, {4, 1496, 502}}, + {3, {0, 1006, 1701}}, + {3, {1, 1155, 97}}, + {3, {2, 657, 1403}}, + {3, {3, 1453, 624}}, + {3, {4, 429, 1495}}, + {3, {0, 809, 385}}, + {3, {1, 367, 151}}, + {3, {2, 1323, 202}}, + {3, {3, 960, 318}}, + {3, {4, 1451, 1039}}, + {3, {0, 1098, 1722}}, + {3, {1, 1015, 1428}}, + {3, {2, 1261, 1564}}, + {3, {3, 544, 1190}}, + {3, {4, 1472, 1246}}, + {3, {0, 508, 630}}, + {3, {1, 421, 1704}}, + {3, {2, 284, 898}}, + {3, {3, 392, 577}}, + {3, {4, 1155, 556}}, + {3, {0, 631, 1000}}, + {3, {1, 732, 1368}}, + {3, {2, 1328, 329}}, + {3, {3, 1515, 506}}, + {3, {4, 1104, 1172}}, + }}; diff --git a/plugins/channelrx/demoddatv/leansdr/filtergen.cpp b/plugins/channelrx/demoddatv/leansdr/filtergen.cpp new file mode 100644 index 000000000..2a7bf41ff --- /dev/null +++ b/plugins/channelrx/demoddatv/leansdr/filtergen.cpp @@ -0,0 +1,20 @@ + +#include + +#include "filtergen.h" + +namespace leansdr +{ +namespace filtergen +{ + +void dump_filter(const char *name, int ncoeffs, float *coeffs) +{ + fprintf(stderr, "%s = [", name); + for (int i = 0; i < ncoeffs; ++i) + fprintf(stderr, "%s %f", (i ? "," : ""), coeffs[i]); + fprintf(stderr, " ];\n"); +} + +} // filtergen +} // leansdr \ No newline at end of file diff --git a/plugins/channelrx/demoddatv/leansdr/filtergen.h b/plugins/channelrx/demoddatv/leansdr/filtergen.h index 67e67e2c5..8d6494e70 100644 --- a/plugins/channelrx/demoddatv/leansdr/filtergen.h +++ b/plugins/channelrx/demoddatv/leansdr/filtergen.h @@ -1,90 +1,123 @@ +// This file is part of LeanSDR Copyright (C) 2016-2018 . +// See the toplevel README for more information. +// +// This program 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. +// +// This program 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 this program. If not, see . + #ifndef LEANSDR_FILTERGEN_H #define LEANSDR_FILTERGEN_H #include -namespace leansdr { - namespace filtergen { - - template - void normalize_power(int n, T *coeffs, float gain=1) { - float s2 = 0; - for ( int i=0; i - void normalize_dcgain(int n, T *coeffs, float gain=1) { - float s = 0; - for ( int i=0; i +void normalize_power(int n, T *coeffs, float gain = 1) +{ + float s2 = 0; + for (int i = 0; i < n; ++i) + s2 = s2 + coeffs[i] * coeffs[i]; // TBD complex + if (s2) + gain /= gen_sqrt(s2); + for (int i = 0; i < n; ++i) + coeffs[i] = coeffs[i] * gain; +} - template - int lowpass(int order, float Fcut, T **coeffs, float gain=1) { - int ncoeffs = order + 1; - *coeffs = new T[ncoeffs]; - for ( int i=0; i +void normalize_dcgain(int n, T *coeffs, float gain = 1) +{ + float s = 0; + for (int i = 0; i < n; ++i) + s = s + coeffs[i]; + if (s) + gain /= s; + for (int i = 0; i < n; ++i) + coeffs[i] = coeffs[i] * gain; +} + +template +void cancel_dcgain(int n, T *coeffs) +{ + float s = 0; + for (int i = 0; i < n; ++i) + s = s + coeffs[i]; + for (int i = 0; i < n; ++i) + coeffs[i] -= s / n; +} + +// Generate coefficients for a sinc filter. +// https://en.wikipedia.org/wiki/Sinc_filter + +template +int lowpass(int order, float Fcut, T **coeffs, float gain = 1) +{ + int ncoeffs = order + 1; + *coeffs = new T[ncoeffs]; + for (int i = 0; i < ncoeffs; ++i) + { + float t = i - (ncoeffs - 1) * 0.5; + float sinc = 2 * Fcut * (t ? sin(2 * M_PI * Fcut * t) / (2 * M_PI * Fcut * t) : 1); +#if 0 // Hamming float alpha = 25.0/46, beta = 21.0/46; float window = alpha - beta*cos(2*M_PI*i/order); #else - float window = 1; + float window = 1; #endif - (*coeffs)[i] = sinc * window; - } - normalize_dcgain(ncoeffs, *coeffs, gain); - return ncoeffs; + (*coeffs)[i] = sinc * window; } + normalize_dcgain(ncoeffs, *coeffs, gain); + return ncoeffs; +} - - // Generate coefficients for a RRC filter. - // https://en.wikipedia.org/wiki/Root-raised-cosine_filter +// Generate coefficients for a RRC filter. +// https://en.wikipedia.org/wiki/Root-raised-cosine_filter - template - int root_raised_cosine(int order, float Fs, float rolloff, T **coeffs) { - float B = rolloff, pi = M_PI; - int ncoeffs = (order+1) | 1; - *coeffs = new T[ncoeffs]; - for ( int i=0; i +int root_raised_cosine(int order, float Fs, float rolloff, T **coeffs) +{ + float B = rolloff, pi = M_PI; + int ncoeffs = (order + 1) | 1; + *coeffs = new T[ncoeffs]; + for (int i = 0; i < ncoeffs; ++i) + { + int t = i - ncoeffs / 2; + float c; + if (t == 0) + c = sqrt(Fs) * (1 - B + 4 * B / pi); + else + { + float tT = t * Fs; + float den = pi * tT * (1 - (4 * B * tT) * (4 * B * tT)); + if (!den) + c = B * sqrt(Fs / 2) * ((1 + 2 / pi) * sin(pi / (4 * B)) + (1 - 2 / pi) * cos(pi / (4 * B))); + else + c = sqrt(Fs) * (sin(pi * tT * (1 - B)) + 4 * B * tT * cos(pi * tT * (1 + B))) / den; + } + (*coeffs)[i] = c; } + normalize_dcgain(ncoeffs, *coeffs); + return ncoeffs; +} +// Dump filter coefficients for matlab/octave +void dump_filter(const char *name, int ncoeffs, float *coeffs); - // Dump filter coefficients for matlab/octave +} // namespace filtergen +} // namespace leansdr - inline void dump_filter(const char *name, int ncoeffs, float *coeffs) { - fprintf(stderr, "%s = [", name); - for ( int i=0; i. +// See the toplevel README for more information. +// +// This program 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. +// +// This program 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 this program. If not, see . + #ifndef LEANSDR_FRAMEWORK_H #define LEANSDR_FRAMEWORK_H -#include -#include - +#include +#include #include #include #include -#include + +#ifndef VERSION +#define VERSION "undefined" +#endif namespace leansdr { -inline void fatal(const char *s) -{ - perror(s); -} - -inline void fail(const char *f, const char *s) -{ - fprintf(stderr, "leansdr::%s: %s\n", f, s); -} +void fatal(const char *s); +void fail(const char *s); ////////////////////////////////////////////////////////////////////// // DSP framework @@ -50,25 +61,25 @@ struct pipebuf_common virtual void dump(std::size_t *total_bufs) { - (void) total_bufs; + (void)total_bufs; } - pipebuf_common(const char *_name) : - name(_name) + const char *name; + + pipebuf_common(const char *_name) : name(_name) { } virtual ~pipebuf_common() { } - - const char *name; }; struct runnable_common { - runnable_common(const char *_name) : - name(_name) + const char *name; + + runnable_common(const char *_name) : name(_name) { } @@ -83,13 +94,12 @@ struct runnable_common virtual void shutdown() { } - #ifdef DEBUG ~runnable_common() - { fprintf(stderr, "Deallocating %s !\n", name);} + { + fprintf(stderr, "Deallocating %s !\n", name); + } #endif - - const char *name; }; struct window_placement @@ -105,39 +115,35 @@ struct scheduler runnable_common *runnables[MAX_RUNNABLES]; int nrunnables; window_placement *windows; - bool verbose, debug; + bool verbose, debug, debug2; - scheduler() : - npipes(0), nrunnables(0), windows(nullptr), verbose(false), debug(false) + scheduler() : npipes(0), + nrunnables(0), + windows(NULL), + verbose(false), + debug(false), + debug2(false) { - std::fill(pipes, pipes + MAX_PIPES, nullptr); - std::fill(runnables, runnables + MAX_RUNNABLES, nullptr); } void add_pipe(pipebuf_common *p) { if (npipes == MAX_PIPES) - { - fail("scheduler::add_pipe", "MAX_PIPES"); - return; - } + fail("MAX_PIPES"); pipes[npipes++] = p; } void add_runnable(runnable_common *r) { if (nrunnables == MAX_RUNNABLES) - { - fail("scheduler::add_runnable", "MAX_RUNNABLES"); - } + fail("MAX_RUNNABLES"); runnables[nrunnables++] = r; } void step() { - for (int i = 0; i < nrunnables; ++i) { + for (int i = 0; i < nrunnables; ++i) runnables[i]->run(); - } } void run() @@ -148,26 +154,23 @@ struct scheduler { step(); unsigned long long h = hash(); - if (h == prev_hash) { + if (h == prev_hash) break; - } prev_hash = h; } } void shutdown() { - for (int i = 0; i < nrunnables; ++i) { + for (int i = 0; i < nrunnables; ++i) runnables[i]->shutdown(); - } } unsigned long long hash() { unsigned long long h = 0; - for (int i = 0; i < npipes; ++i) { + for (int i = 0; i < npipes; ++i) h += (1 + i) * pipes[i]->hash(); - } return h; } @@ -175,26 +178,26 @@ struct scheduler { fprintf(stderr, "\n"); std::size_t total_bufs = 0; - for (int i = 0; i < npipes; ++i) { + for (int i = 0; i < npipes; ++i) pipes[i]->dump(&total_bufs); - } - fprintf(stderr, "leansdr::scheduler::dump Total buffer memory: %ld KiB\n", (unsigned long) total_bufs / 1024); + fprintf(stderr, "Total buffer memory: %ld KiB\n", + (unsigned long)total_bufs / 1024); } }; -struct runnable: runnable_common +struct runnable : runnable_common { - runnable(scheduler *_sch, const char *name) : - runnable_common(name), sch(_sch) + runnable(scheduler *_sch, const char *name) : runnable_common(name), sch(_sch) { sch->add_runnable(this); } -protected: + + protected: scheduler *sch; }; -template -struct pipebuf: pipebuf_common +template +struct pipebuf : pipebuf_common { T *buf; T *rds[MAX_READERS]; @@ -207,9 +210,13 @@ struct pipebuf: pipebuf_common return sizeof(T); } - pipebuf(scheduler *sch, const char *name, unsigned long size) : - pipebuf_common(name), buf(new T[size]), nrd(0), wr(buf), end( - buf + size), min_write(1), total_written(0), total_read(0) + pipebuf(scheduler *sch, const char *name, unsigned long size) : pipebuf_common(name), + buf(new T[size]), + nrd(0), wr(buf), + end(buf + size), + min_write(1), + total_written(0), + total_read(0) { sch->add_pipe(this); } @@ -217,10 +224,7 @@ struct pipebuf: pipebuf_common int add_reader() { if (nrd == MAX_READERS) - { - fail("pipebuf::add_reader", "too many readers"); - return nrd; - } + fail("too many readers"); rds[nrd] = wr; return nrd++; } @@ -229,16 +233,12 @@ struct pipebuf: pipebuf_common { T *rd = wr; for (int i = 0; i < nrd; ++i) - { - if (rds[i] < rd) { + if (rds[i] < rd) rd = rds[i]; - } - } memmove(buf, rd, (wr - rd) * sizeof(T)); wr -= rd - buf; - for (int i = 0; i < nrd; ++i) { + for (int i = 0; i < nrd; ++i) rds[i] -= rd - buf; - } } long long hash() @@ -248,71 +248,53 @@ struct pipebuf: pipebuf_common void dump(std::size_t *total_bufs) { - if (total_written < 10000) { - fprintf(stderr, "leansdr::pipebuf::dump: .%-16s : %4ld/%4ld", name, total_read, total_written); - } else if (total_written < 1000000) { - fprintf(stderr, "leansdr::pipebuf::dump: .%-16s : %3ldk/%3ldk", name, total_read / 1000, total_written / 1000); - } else { - fprintf(stderr, "leansdr::pipebuf::dump: .%-16s : %3ldM/%3ldM", name, total_read / 1000000, total_written / 1000000); - } - + if (total_written < 10000) + fprintf(stderr, ".%-16s : %4ld/%4ld", name, total_read, + total_written); + else if (total_written < 1000000) + fprintf(stderr, ".%-16s : %3ldk/%3ldk", name, total_read / 1000, + total_written / 1000); + else + fprintf(stderr, ".%-16s : %3ldM/%3ldM", name, total_read / 1000000, + total_written / 1000000); *total_bufs += (end - buf) * sizeof(T); unsigned long nw = end - wr; - fprintf(stderr, "leansdr::pipebuf: %6ld writable %c,", nw, (nw < min_write) ? '!' : ' '); + fprintf(stderr, " %6ld writable %c,", nw, (nw < min_write) ? '!' : ' '); T *rd = wr; - for (int j = 0; j < nrd; ++j) - { - if (rds[j] < rd) { + if (rds[j] < rd) rd = rds[j]; - } - } - - fprintf(stderr, "leansdr::pipebuf::dump: %6d unread (", (int) (wr - rd)); - - for (int j = 0; j < nrd; ++j) { - fprintf(stderr, "leansdr::pipebuf: %d", (int) (wr - rds[j])); - } - - fprintf(stderr, "leansdr::pipebuf::dump: )\n"); + fprintf(stderr, " %6d unread (", (int)(wr - rd)); + for (int j = 0; j < nrd; ++j) + fprintf(stderr, " %d", (int)(wr - rds[j])); + fprintf(stderr, " )\n"); } unsigned long min_write; unsigned long total_written, total_read; #ifdef DEBUG ~pipebuf() - { fprintf(stderr, "Deallocating %s !\n", name);} + { + fprintf(stderr, "Deallocating %s !\n", name); + } #endif }; -template +template struct pipewriter { pipebuf &buf; - pipewriter(pipebuf &_buf, unsigned long min_write = 1) : - buf(_buf) + pipewriter(pipebuf &_buf, unsigned long min_write = 1) : buf(_buf) { - if (min_write > buf.min_write) { + if (min_write > buf.min_write) buf.min_write = min_write; - } } - - /** Return number of items writable at this->wr, 0 if full. */ - unsigned long writable() + // Return number of items writable at this->wr, 0 if full. + long writable() { - if (buf.end < buf.wr) - { - fprintf(stderr, "leansdr::pipewriter::writable: overflow in %s buffer\n", buf.name); - return 0; - } - - unsigned long delta = buf.end - buf.wr; - - if (delta < buf.min_write) { + if (buf.end < buf.min_write + buf.wr) buf.pack(); - } - - return delta; + return buf.end - buf.wr; } T *wr() @@ -324,9 +306,9 @@ struct pipewriter { if (buf.wr + n > buf.end) { - fprintf(stderr, "leansdr::pipewriter::written: overflow in %s buffer\n", buf.name); - return; + fprintf(stderr, "Bug: overflow to %s\n", buf.name); } + buf.wr += n; buf.total_written += n; } @@ -340,38 +322,36 @@ struct pipewriter // Convenience functions for working with optional pipes -template -pipewriter *opt_writer(pipebuf *buf) +template +pipewriter *opt_writer(pipebuf *buf, unsigned long min_write = 1) { - return buf ? new pipewriter(*buf) : NULL; + return buf ? new pipewriter(*buf, min_write) : NULL; } -template -bool opt_writable(pipewriter *p, unsigned int n = 1) +template +bool opt_writable(pipewriter *p, int n = 1) { return (p == NULL) || p->writable() >= n; } -template +template void opt_write(pipewriter *p, T val) { - if (p) { + if (p) p->write(val); - } } -template +template struct pipereader { pipebuf &buf; int id; - pipereader(pipebuf &_buf) : - buf(_buf), id(_buf.add_reader()) + pipereader(pipebuf &_buf) : buf(_buf), id(_buf.add_reader()) { } - unsigned long readable() + long readable() { return buf.wr - buf.rds[id]; } @@ -385,9 +365,9 @@ struct pipereader { if (buf.rds[id] + n > buf.wr) { - fprintf(stderr, "leansdr::pipereader::read: underflow in %s buffer\n", buf.name); - return; + fprintf(stderr, "Bug: underflow from %s\n", buf.name); } + buf.rds[id] += n; buf.total_read += n; } @@ -395,7 +375,8 @@ struct pipereader // Math functions for templates -template T gen_sqrt(T x); +template +T gen_sqrt(T x); inline float gen_sqrt(float x) { return sqrtf(x); @@ -411,7 +392,8 @@ inline long double gen_sqrt(long double x) return sqrtl(x); } -template T gen_abs(T x); +template +T gen_abs(T x); inline float gen_abs(float x) { return fabsf(x); @@ -427,7 +409,8 @@ inline long int gen_abs(long int x) return labs(x); } -template T gen_hypot(T x, T y); +template +T gen_hypot(T x, T y); inline float gen_hypot(float x, float y) { return hypotf(x, y); @@ -438,7 +421,8 @@ inline long double gen_hypot(long double x, long double y) return hypotl(x, y); } -template T gen_atan2(T y, T x); +template +T gen_atan2(T y, T x); inline float gen_atan2(float y, float x) { return atan2f(y, x); @@ -449,13 +433,13 @@ inline long double gen_atan2(long double y, long double x) return atan2l(y, x); } -template +template T min(const T &x, const T &y) { return (x < y) ? x : y; } -template +template T max(const T &x, const T &y) { return (x < y) ? y : x; @@ -470,6 +454,6 @@ typedef signed char s8; typedef signed short s16; typedef signed long s32; -} // namespace +} // namespace leansdr -#endif // LEANSDR_FRAMEWORK_H +#endif // LEANSDR_FRAMEWORK_H diff --git a/plugins/channelrx/demoddatv/leansdr/generic.h b/plugins/channelrx/demoddatv/leansdr/generic.h index d7e541d28..68371af80 100644 --- a/plugins/channelrx/demoddatv/leansdr/generic.h +++ b/plugins/channelrx/demoddatv/leansdr/generic.h @@ -1,14 +1,26 @@ +// This file is part of LeanSDR Copyright (C) 2016-2018 . +// See the toplevel README for more information. +// +// This program 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. +// +// This program 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 this program. If not, see . + #ifndef LEANSDR_GENERIC_H #define LEANSDR_GENERIC_H +#include +#include #include - -#ifdef _MSC_VER -#include -#include -#else #include -#endif #include "leansdr/math.h" @@ -22,11 +34,14 @@ namespace leansdr // [file_reader] reads raw data from a file descriptor into a [pipebuf]. // If the file descriptor is seekable, data can be looped. -template -struct file_reader: runnable +template +struct file_reader : runnable { - file_reader(scheduler *sch, int _fdin, pipebuf &_out) : - runnable(sch, _out.name), loop(false), fdin(_fdin), out(_out) + file_reader(scheduler *sch, int _fdin, pipebuf &_out) + : runnable(sch, _out.name), + loop(false), + filler(NULL), + fdin(_fdin), out(_out) { } void run() @@ -35,32 +50,29 @@ struct file_reader: runnable if (!size) return; -#ifdef _MSC_VER - again: ssize_t nr = _read(fdin, out.wr(), size); -#else - again: ssize_t nr = read(fdin, out.wr(), size); -#endif - if (nr < 0) + again: + ssize_t nr = read(fdin, out.wr(), size); + if (nr < 0 && errno == EWOULDBLOCK) { - fatal("leansdr::file_reader::run: read"); + if (filler) + { + if (sch->debug) + fprintf(stderr, "U"); + out.write(*filler); + } return; } + if (nr < 0) + fatal("read(file_reader)"); if (!nr) { if (!loop) return; if (sch->debug) - fprintf(stderr, "leansdr::file_reader::run: %s looping\n", name); -#ifdef _MSC_VER - off_t res = _lseek(fdin, 0, SEEK_SET); -#else + fprintf(stderr, "%s looping\n", name); off_t res = lseek(fdin, 0, SEEK_SET); -#endif - if (res == (off_t) -1) - { - fatal("leansdr::file_reader::run: lseek"); - return; - } + if (res == (off_t)-1) + fatal("lseek"); goto again; } @@ -71,16 +83,9 @@ struct file_reader: runnable { if (sch->debug) fprintf(stderr, "+"); -#ifdef _MSC_VER - ssize_t nr2 = _read(fdin, (char*) out.wr() + nr, remain); -#else - ssize_t nr2 = read(fdin, (char*) out.wr() + nr, remain); -#endif + ssize_t nr2 = read(fdin, (char *)out.wr() + nr, remain); if (nr2 <= 0) - { - fatal("leansdr::file_reader::run: partial read"); - return; - } + fatal("partial read"); nr += nr2; remain -= nr2; } @@ -88,18 +93,27 @@ struct file_reader: runnable out.written(nr / sizeof(T)); } bool loop; -private: + void set_realtime(T &_filler) + { + int flags = fcntl(fdin, F_GETFL); + if (fcntl(fdin, F_SETFL, flags | O_NONBLOCK)) + fatal("fcntl"); + filler = new T(_filler); + } + + private: + T *filler; int fdin; pipewriter out; }; // [file_writer] writes raw data from a [pipebuf] to a file descriptor. -template -struct file_writer: runnable +template +struct file_writer : runnable { - file_writer(scheduler *sch, pipebuf &_in, int _fdout) : - runnable(sch, _in.name), in(_in), fdout(_fdout) + file_writer(scheduler *sch, pipebuf &_in, int _fdout) : runnable(sch, _in.name), + in(_in), fdout(_fdout) { } void run() @@ -107,29 +121,17 @@ struct file_writer: runnable int size = in.readable() * sizeof(T); if (!size) return; -#ifdef _MSC_VER - int nw = _write(fdout, in.rd(), size); -#else int nw = write(fdout, in.rd(), size); -#endif if (!nw) - { - fatal("leansdr::file_writer::run: pipe"); - return; - } + fatal("pipe"); if (nw < 0) - { - fatal("leansdr::file_writer::run: write"); - return; - } + fatal("write"); if (nw % sizeof(T)) - { - fatal("leansdr::file_writer::run:partial write"); - return; - } + fatal("partial write"); in.read(nw / sizeof(T)); } -private: + + private: pipereader in; int fdout; }; @@ -137,11 +139,14 @@ private: // [file_printer] writes data from a [pipebuf] to a file descriptor, // with printf-style formatting and optional scaling. -template -struct file_printer: runnable +template +struct file_printer : runnable { - file_printer(scheduler *sch, const char *_format, pipebuf &_in, int _fdout, int _decimation = 1) : - runnable(sch, _in.name), scale(1), decimation(_decimation), in(_in), format(_format), fdout(_fdout), phase(0) + file_printer(scheduler *sch, const char *_format, + pipebuf &_in, int _fdout, + int _decimation = 1) : runnable(sch, _in.name), + scale(1), decimation(_decimation), + in(_in), format(_format), fdout(_fdout), phase(0) { } void run() @@ -156,27 +161,18 @@ struct file_printer: runnable char buf[256]; int len = snprintf(buf, sizeof(buf), format, (*pin) * scale); if (len < 0) - { - fatal("leansdr::file_printer::run: obsolete glibc"); - return; - } -#ifdef _MSC_VER - int nw = _write(fdout, buf, len); -#else + fatal("obsolete glibc"); int nw = write(fdout, buf, len); -#endif if (nw != len) - { - fatal("leansdr::file_printer::run: partial write"); - return; - } + fatal("partial write"); } } in.read(n); } T scale; int decimation; -private: + + private: pipereader in; const char *format; int fdout; @@ -187,11 +183,18 @@ private: // to a file descriptor on a single line. // Special case for complex. -template -struct file_carrayprinter: runnable +template +struct file_carrayprinter : runnable { - file_carrayprinter(scheduler *sch, const char *_head, const char *_format, const char *_sep, const char *_tail, pipebuf > &_in, int _fdout) : - runnable(sch, _in.name), scale(1), fixed_size(0), in(_in), head(_head), format(_format), sep(_sep), tail(_tail), fout(fdopen(_fdout, "w")) + file_carrayprinter(scheduler *sch, + const char *_head, + const char *_format, + const char *_sep, + const char *_tail, + pipebuf> &_in, int _fdout) : runnable(sch, _in.name), + scale(1), fixed_size(0), in(_in), + head(_head), format(_format), sep(_sep), tail(_tail), + fout(fdopen(_fdout, "w")) { } void run() @@ -218,32 +221,36 @@ struct file_carrayprinter: runnable } } T scale; - int fixed_size; // Number of elements per batch, or 0. -private: - pipereader > in; + int fixed_size; // Number of elements per batch, or 0. + private: + pipereader> in; const char *head, *format, *sep, *tail; FILE *fout; }; -template -struct file_vectorprinter: runnable +template +struct file_vectorprinter : runnable { - file_vectorprinter(scheduler *sch, const char *_head, const char *_format, const char *_sep, const char *_tail, pipebuf &_in, int _fdout) : - runnable(sch, _in.name), scale(1), in(_in), head(_head), format(_format), sep(_sep), tail(_tail) + file_vectorprinter(scheduler *sch, + const char *_head, + const char *_format, + const char *_sep, + const char *_tail, + pipebuf &_in, int _fdout, int _n = N) : runnable(sch, _in.name), scale(1), in(_in), + head(_head), format(_format), sep(_sep), tail(_tail), n(_n) { fout = fdopen(_fdout, "w"); if (!fout) - { - fatal("leansdr::file_vectorprinter::file_vectorprinter: fdopen"); - } + fatal("fdopen"); } void run() { while (in.readable() >= 1) { - fprintf(fout, head, N); - T (*pin)[N] = in.rd(); - for (int i = 0; i < N; ++i) + fprintf(fout, head, n); + T(*pin) + [N] = in.rd(); + for (int i = 0; i < n; ++i) { if (i) fprintf(fout, "%s", sep); @@ -255,20 +262,23 @@ struct file_vectorprinter: runnable fflush(fout); } T scale; -private: + + private: pipereader in; const char *head, *format, *sep, *tail; FILE *fout; + int n; }; // [itemcounter] writes the number of input items to the output [pipebuf]. // [Tout] must be a numeric type. -template -struct itemcounter: runnable +template +struct itemcounter : runnable { - itemcounter(scheduler *sch, pipebuf &_in, pipebuf &_out) : - runnable(sch, "itemcounter"), in(_in), out(_out) + itemcounter(scheduler *sch, pipebuf &_in, pipebuf &_out) + : runnable(sch, "itemcounter"), + in(_in), out(_out) { } void run() @@ -281,20 +291,23 @@ struct itemcounter: runnable out.write(count); in.read(count); } -private: + + private: pipereader in; pipewriter out; }; // [decimator] forwards 1 in N sample. -template -struct decimator: runnable +template +struct decimator : runnable { unsigned int d; - decimator(scheduler *sch, int _d, pipebuf &_in, pipebuf &_out) : - runnable(sch, "decimator"), d(_d), in(_in), out(_out) + decimator(scheduler *sch, int _d, pipebuf &_in, pipebuf &_out) + : runnable(sch, "decimator"), + d(_d), + in(_in), out(_out) { } void run() @@ -306,7 +319,8 @@ struct decimator: runnable in.read(count * d); out.written(count); } -private: + + private: pipereader in; pipewriter out; }; @@ -314,13 +328,18 @@ private: // [rate_estimator] accumulates counts of two quantities // and periodically outputs their ratio. -template -struct rate_estimator: runnable +template +struct rate_estimator : runnable { int sample_size; - rate_estimator(scheduler *sch, pipebuf &_num, pipebuf &_den, pipebuf &_rate) : - runnable(sch, "rate_estimator"), sample_size(10000), num(_num), den(_den), rate(_rate), acc_num(0), acc_den(0) + rate_estimator(scheduler *sch, + pipebuf &_num, pipebuf &_den, + pipebuf &_rate) + : runnable(sch, "rate_estimator"), + sample_size(10000), + num(_num), den(_den), rate(_rate), + acc_num(0), acc_den(0) { } @@ -339,12 +358,12 @@ struct rate_estimator: runnable den.read(count); if (acc_den >= sample_size) { - rate.write((float) acc_num / acc_den); + rate.write((float)acc_num / acc_den); acc_num = acc_den = 0; } } -private: + private: pipereader num, den; pipewriter rate; T acc_num, acc_den; @@ -352,18 +371,16 @@ private: // SERIALIZER -template -struct serializer: runnable +template +struct serializer : runnable { - serializer(scheduler *sch, pipebuf &_in, pipebuf &_out) : - nin(max((size_t) 1, sizeof(Tin) / sizeof(Tout))), nout(max((size_t) 1, sizeof(Tout) / sizeof(Tin))), in(_in), out(_out, nout) + serializer(scheduler *sch, pipebuf &_in, pipebuf &_out) + : nin(max((size_t)1, sizeof(Tin) / sizeof(Tout))), + nout(max((size_t)1, sizeof(Tout) / sizeof(Tin))), + in(_in), out(_out, nout) { - (void) sch; if (nin * sizeof(Tin) != nout * sizeof(Tout)) - { - fail("serializer::serializer", "incompatible sizes"); - return; - } + fail("serializer: incompatible sizes"); } void run() { @@ -374,61 +391,63 @@ struct serializer: runnable out.written(nout); } } -private: + + private: int nin, nout; pipereader in; pipewriter out; -}; -// serializer +}; // serializer // [buffer_reader] reads from a user-supplied buffer. -template -struct buffer_reader: runnable +template +struct buffer_reader : runnable { - buffer_reader(scheduler *sch, T *_data, int _count, pipebuf &_out) : - runnable(sch, "buffer_reader"), data(_data), count(_count), out(_out), pos(0) + buffer_reader(scheduler *sch, T *_data, int _count, pipebuf &_out) + : runnable(sch, "buffer_reader"), + data(_data), count(_count), out(_out), pos(0) { } void run() { - int n = min(out.writable(), (unsigned long) (count - pos)); + int n = min(out.writable(), (unsigned long)(count - pos)); memcpy(out.wr(), &data[pos], n * sizeof(T)); pos += n; out.written(n); } -private: + + private: T *data; int count; pipewriter out; int pos; -}; -// buffer_reader +}; // buffer_reader // [buffer_writer] writes to a user-supplied buffer. -template -struct buffer_writer: runnable +template +struct buffer_writer : runnable { - buffer_writer(scheduler *sch, pipebuf &_in, T *_data, int _count) : - runnable(sch, "buffer_reader"), in(_in), data(_data), count(_count), pos(0) + buffer_writer(scheduler *sch, pipebuf &_in, T *_data, int _count) + : runnable(sch, "buffer_reader"), + in(_in), data(_data), count(_count), pos(0) { } void run() { - int n = min(in.readable(), (unsigned long) (count - pos)); + int n = min(in.readable(), (unsigned long)(count - pos)); memcpy(&data[pos], in.rd(), n * sizeof(T)); in.read(n); pos += n; } -private: + + private: pipereader in; T *data; int count; int pos; -}; -// buffer_writer +}; // buffer_writer -}// namespace +} // namespace leansdr -#endif // LEANSDR_GENERIC_H +#endif // LEANSDR_GENERIC_H diff --git a/plugins/channelrx/demoddatv/leansdr/gui.h b/plugins/channelrx/demoddatv/leansdr/gui.h index 14a75453a..6f31044b1 100644 --- a/plugins/channelrx/demoddatv/leansdr/gui.h +++ b/plugins/channelrx/demoddatv/leansdr/gui.h @@ -1,3 +1,19 @@ +// This file is part of LeanSDR Copyright (C) 2016-2018 . +// See the toplevel README for more information. +// +// This program 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. +// +// This program 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 this program. If not, see . + #ifndef LEANSDR_GUI_H #define LEANSDR_GUI_H @@ -5,220 +21,271 @@ #include "framework.h" -namespace leansdr { +namespace leansdr +{ + +////////////////////////////////////////////////////////////////////// +// GUI blocks +////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////// - // GUI blocks - ////////////////////////////////////////////////////////////////////// - #ifdef GUI - + #include #include #include - - static const int DEFAULT_GUI_DECIMATION = 64; - - struct gfx { + +static const int DEFAULT_GUI_DECIMATION = 64; + +struct gfx +{ Display *display; int screen; int w, h; Window window; GC gc; Pixmap dbuf; - gfx(scheduler *sch, const char *name) { - window_placement *wp; - for ( wp=sch->windows; wp && wp->name; ++wp ) - if ( ! strcmp(wp->name, name) ) break; - if ( wp && wp->name ) - init(wp->name, wp->x, wp->y, wp->w, wp->h); - else { - fprintf(stderr, "No placement hints for window '%s'\n", name); - init(name, -1, -1, 320, 240); - } + gfx(scheduler *sch, const char *name) + { + window_placement *wp; + for (wp = sch->windows; wp && wp->name; ++wp) + if (!strcmp(wp->name, name)) + break; + if (wp && wp->name) + init(wp->name, wp->x, wp->y, wp->w, wp->h); + else + { + fprintf(stderr, "No placement hints for window '%s'\n", name); + init(name, -1, -1, 320, 240); + } } - gfx(const char *name, int _x, int _y, int _w, int _h) { - init(name, _x, _y, _w, _h); + gfx(const char *name, int _x, int _y, int _w, int _h) + { + init(name, _x, _y, _w, _h); } - void init(const char *name, int _x, int _y, int _w, int _h) { - buttons = 0; - clicks = 0; - mmoved = false; - w = _w; - h = _h; - display = XOpenDisplay(getenv("DISPLAY")); - if ( ! display ) fatal("display"); - screen = DefaultScreen(display); - XSetWindowAttributes xswa; - xswa.event_mask = (ExposureMask| - StructureNotifyMask| - ButtonPressMask| - ButtonReleaseMask| - KeyPressMask| - KeyReleaseMask| - PointerMotionMask); - xswa.background_pixel = BlackPixel(display, screen); - window = XCreateWindow(display, DefaultRootWindow(display), - 100,100, w,h, 10, CopyFromParent,InputOutput, - CopyFromParent, CWEventMask|CWBackPixel, - &xswa); - if ( !window ) fatal("window"); - XStoreName(display, window, name); - XMapWindow(display, window); - if ( _x>=0 && _y>=0 ) - XMoveWindow(display, window, _x, _y); - dbuf = XCreatePixmap(display, window, w, h, DefaultDepth(display,screen)); - gc = XCreateGC(display, dbuf, 0, NULL); - if ( ! gc ) fatal("gc"); + void init(const char *name, int _x, int _y, int _w, int _h) + { + buttons = 0; + clicks = 0; + mmoved = false; + w = _w; + h = _h; + display = XOpenDisplay(getenv("DISPLAY")); + if (!display) + fatal("display"); + screen = DefaultScreen(display); + XSetWindowAttributes xswa; + xswa.event_mask = (ExposureMask | + StructureNotifyMask | + ButtonPressMask | + ButtonReleaseMask | + KeyPressMask | + KeyReleaseMask | + PointerMotionMask); + xswa.background_pixel = BlackPixel(display, screen); + window = XCreateWindow(display, DefaultRootWindow(display), + 100, 100, w, h, 10, CopyFromParent, InputOutput, + CopyFromParent, CWEventMask | CWBackPixel, + &xswa); + if (!window) + fatal("window"); + XStoreName(display, window, name); + XMapWindow(display, window); + if (_x >= 0 && _y >= 0) + XMoveWindow(display, window, _x, _y); + dbuf = XCreatePixmap(display, window, w, h, DefaultDepth(display, screen)); + gc = XCreateGC(display, dbuf, 0, NULL); + if (!gc) + fatal("gc"); } - void clear() { - setfg(0, 0, 0); - XFillRectangle(display, dbuf, gc, 0, 0, w, h); + void clear() + { + setfg(0, 0, 0); + XFillRectangle(display, dbuf, gc, 0, 0, w, h); } - void show() { - XCopyArea(display, dbuf, window, gc, 0, 0, w, h, 0, 0); + void show() + { + XCopyArea(display, dbuf, window, gc, 0, 0, w, h, 0, 0); } - void sync() { - XSync(display, False); + void sync() + { + XSync(display, False); } - void events() { - XEvent ev; - while ( XCheckWindowEvent(display, window, -1, &ev) ) { - switch ( ev.type ) { - case ButtonPress: { - int b = ev.xbutton.button; - buttons |= 1< - struct cscope : runnable { + int buttons; // Mask of button states (2|4|8) + int clicks; // Same, accumulated (must be cleared by owner) + int mx, my; // Cursor position + bool mmoved; // Pointer moved (must be cleared by owner) +}; + +template +struct cscope : runnable +{ T xymin, xymax; unsigned long decimation; unsigned long pixels_per_frame; - cstln_lut<256> **cstln; // Optional ptr to optional constellation - cscope(scheduler *sch, pipebuf< complex > &_in, T _xymin, T _xymax, - const char *_name=NULL) - : runnable(sch, _name?_name:_in.name), - xymin(_xymin), xymax(_xymax), - decimation(DEFAULT_GUI_DECIMATION), pixels_per_frame(1024), - cstln(NULL), - in(_in), phase(0), g(sch, name) { + cstln_base **cstln; // Optional ptr to optional constellation + cscope(scheduler *sch, pipebuf> &_in, T _xymin, T _xymax, + const char *_name = NULL) + : runnable(sch, _name ? _name : _in.name), + xymin(_xymin), xymax(_xymax), + decimation(DEFAULT_GUI_DECIMATION), pixels_per_frame(1024), + cstln(NULL), + in(_in), phase(0), g(sch, name) + { } - void run() { - while ( in.readable() >= pixels_per_frame ) { - if ( ! phase ) { - draw_begin(); - g.setfg(0, 255, 0); - complex *p = in.rd(), *pend = p+pixels_per_frame; - for ( ; pre-xymin)/(xymax-xymin), - g.h - g.h*(p->im-xymin)/(xymax-xymin)); - if ( cstln && (*cstln) ) { - // Plot constellation points - g.setfg(255, 255, 255); - for ( int i=0; i<(*cstln)->nsymbols; ++i ) { - complex *p = &(*cstln)->symbols[i]; - int x = g.w*(p->re-xymin)/(xymax-xymin); - int y = g.h - g.h*(p->im-xymin)/(xymax-xymin); - for ( int d=-2; d<=2; ++d ) { - g.point(x+d, y); - g.point(x, y+d); - } - } - } - g.show(); - g.sync(); - } - in.read(pixels_per_frame); - if ( ++phase >= decimation ) phase = 0; - } + void run() + { + while (in.readable() >= pixels_per_frame) + { + if (!phase) + { + draw_begin(); + g.setfg(0, 255, 0); + complex *p = in.rd(), *pend = p + pixels_per_frame; + for (; p < pend; ++p) + g.point(g.w * (p->re - xymin) / (xymax - xymin), + g.h - g.h * (p->im - xymin) / (xymax - xymin)); + if (cstln && (*cstln)) + { + // Plot constellation points + g.setfg(255, 255, 255); + for (int i = 0; i < (*cstln)->nsymbols; ++i) + { + complex *p = &(*cstln)->symbols[i]; + int x = g.w * (p->re - xymin) / (xymax - xymin); + int y = g.h - g.h * (p->im - xymin) / (xymax - xymin); + for (int d = -2; d <= 2; ++d) + { + g.point(x + d, y); + g.point(x, y + d); + } + } + } + g.show(); + g.sync(); + } + in.read(pixels_per_frame); + if (++phase >= decimation) + phase = 0; + } } //private: - pipereader< complex > in; + pipereader> in; unsigned long phase; gfx g; - void draw_begin() { - g.clear(); - g.setfg(0, 255, 0); - g.line(g.w/2,0, g.w/2, g.h); - g.line(0,g.h/2, g.w,g.h/2); - } - }; - - template - struct wavescope : runnable { + void draw_begin() + { + g.clear(); + g.setfg(0, 255, 0); + g.line(g.w / 2, 0, g.w / 2, g.h); + g.line(0, g.h / 2, g.w, g.h / 2); + } +}; + +template +struct wavescope : runnable +{ T ymin, ymax; unsigned long decimation; + float hgrid; wavescope(scheduler *sch, pipebuf &_in, - T _ymin, T _ymax, const char *_name=NULL) - : runnable(sch, _name?_name:_in.name), - in(_in), ymin(_ymin), ymax(_ymax), - decimation(DEFAULT_GUI_DECIMATION), - g(sch, name), phase(0), - x(0) { - g.clear(); - } - void run() { - while ( in.readable() >= g.w ) { - if ( ! phase ) plot(in.rd(), g.w); - in.read(g.w); - if ( ++phase >= decimation ) phase = 0; - } + T _ymin, T _ymax, const char *_name = NULL) + : runnable(sch, _name ? _name : _in.name), + ymin(_ymin), ymax(_ymax), + decimation(DEFAULT_GUI_DECIMATION), hgrid(0), + in(_in), + phase(0), g(sch, name), x(0) + { + g.clear(); } - void plot(T *p, int count) { - T *pend = p + count; - g.clear(); - g.setfg(0, 255, 0); - for ( int x=0; p= g.w) + { + if (!phase) + plot(in.rd(), g.w); + in.read(g.w); + if (++phase >= decimation) + phase = 0; + } + } + void plot(T *p, int count) + { + T *pend = p + count; + g.clear(); + g.setfg(128, 128, 0); + g.line(0, g.h / 2, g.w - 1, g.h / 2); + if (hgrid) + { + for (float x = 0; x < g.w; x += hgrid) + g.line(x, 0, x, g.h - 1); + } + g.setfg(0, 255, 0); + for (int x = 0; p < pend; ++x, ++p) + { + T v = *p; + g.point(x, g.h - 1 - (g.h - 1) * (v - ymin) / (ymax - ymin)); + } + g.show(); + g.sync(); } private: @@ -226,366 +293,459 @@ namespace leansdr { int phase; gfx g; int x; - }; +}; - template - struct slowmultiscope : runnable { - struct chanspec { - pipebuf *in; - const char *name, *format; - unsigned char rgb[3]; - float scale; - float ymin, ymax; - enum flag { - DEFAULT = 0, - ASYNC = 1, // Read whatever is available - COUNT = 2, // Display number of items read instead of value - SUM = 4, // Display sum of values - LINE = 8, // Connect points - WRAP = 16, // Modulo min..max - DISABLED = 32, // Ignore channel - } flags; +template +struct slowmultiscope : runnable +{ + struct chanspec + { + pipebuf *in; + const char *name, *format; + unsigned char rgb[3]; + float scale; + float ymin, ymax; + enum flag + { + DEFAULT = 0, + ASYNC = 1, // Read whatever is available + COUNT = 2, // Display number of items read instead of value + SUM = 4, // Display sum of values + LINE = 8, // Connect points + WRAP = 16, // Modulo min..max + DISABLED = 32, // Ignore channel + } flags; }; unsigned long samples_per_pixel; - float sample_freq; // Sample rate in Hz (used for cursor operations) + float sample_freq; // Sample rate in Hz (used for cursor operations) slowmultiscope(scheduler *sch, const chanspec *specs, int nspecs, - const char *_name) - : runnable(sch, _name?_name:"slowmultiscope"), - samples_per_pixel(1), sample_freq(1), - g(sch, name), t(0), x(0), total_samples(0) { - chans = new channel[nspecs]; - nchans = 0; - for ( int i=0; i(*specs[i].in); - chans[nchans].accum = 0; - ++nchans; - } - g.clear(); + const char *_name) + : runnable(sch, _name ? _name : "slowmultiscope"), + samples_per_pixel(1), sample_freq(1), + g(sch, name), t(0), x(0), total_samples(0) + { + chans = new channel[nspecs]; + nchans = 0; + for (int i = 0; i < nspecs; ++i) + { + if (specs[i].flags & chanspec::DISABLED) + continue; + chans[nchans].spec = specs[i]; + chans[nchans].in = new pipereader(*specs[i].in); + chans[nchans].accum = 0; + ++nchans; + } + g.clear(); } - void run() { - // Read up to one pixel worth of data - unsigned long count = samples_per_pixel; - for ( channel *c=chans; cspec.flags&chanspec::ASYNC) ) - count = min(count, c->in->readable()); - for ( int n=count; n--; ) { - for ( channel *c=chans; cspec.flags & chanspec::ASYNC ) - // For async channels, read any and all available data. - nr = c->in->readable(); - else - nr = 1; - g.setfg(c->spec.rgb[0], c->spec.rgb[1], c->spec.rgb[2]); - int y = -1; - while ( nr-- ) { - float v = *c->in->rd() * c->spec.scale; - if ( c->spec.flags & chanspec::COUNT ) - ++c->accum; - else if ( c->spec.flags & chanspec::SUM ) - c->accum += v; - else { - c->print_val = v; - float nv = (v-c->spec.ymin) / (c->spec.ymax-c->spec.ymin); - if ( c->spec.flags & chanspec::WRAP ) - nv = nv - floor(nv); - y = g.h - g.h*nv; - } - c->in->read(1); - } - // Display count/sum channels only when the cursor is about to move. - if ( (c->spec.flags&(chanspec::COUNT|chanspec::SUM)) && - t+1 >= samples_per_pixel ) { - T v = c->accum; - y = g.h-1 - g.h*(v-c->spec.ymin)/(c->spec.ymax-c->spec.ymin); - c->accum = 0; - c->print_val = v; - } - if ( y >= 0 ) { - if ( c->spec.flags & chanspec::LINE ) { - if ( x ) g.line(x-1, c->prev_y, x, y); - c->prev_y = y; - } else - g.point(x, y); - } - } - g.show(); - // Print instantatenous values as text - for ( int i=0; ispec.rgb[0], c->spec.rgb[1], c->spec.rgb[2]); - char text[256]; - sprintf(text, c->spec.format, c->print_val); - g.transient_text(5, 20+16*i, text); - } - run_gui(); - if ( ++t >= samples_per_pixel ) { - t = 0; - ++x; - if ( x >= g.w ) x = 0; - g.setfg(0, 0, 0); - g.line(x, 0, x, g.h-1); - } - run_gui(); - g.sync(); - } - total_samples += count; - } - void run_gui() { - g.events(); - // Print cursor time - float ct = g.mx * samples_per_pixel / sample_freq; - float tt = total_samples / sample_freq; - char text[256]; - sprintf(text, "%.3f / %.3f s", ct, tt); - g.setfg(255, 255, 255); - g.transient_text(g.w*3/4, 20, text); + + void run() + { + // Asynchronous channels: Read all available data + for (channel *c = chans; c < chans + nchans; ++c) + { + if (c->spec.flags & chanspec::ASYNC) + run_channel(c, c->in->readable()); + } + // Synchronous channels: Read up to one pixel worth of data + // between display syncs. + unsigned long count = samples_per_pixel; + for (channel *c = chans; c < chans + nchans; ++c) + if (!(c->spec.flags & chanspec::ASYNC)) + count = min(count, c->in->readable()); + for (int n = count; n--;) + { + for (channel *c = chans; c < chans + nchans; ++c) + if (!(c->spec.flags & chanspec::ASYNC)) + run_channel(c, 1); + } + t += count; + g.show(); + // Print instantatenous values as text + for (int i = 0; i < nchans; ++i) + { + channel *c = &chans[i]; + g.setfg(c->spec.rgb[0], c->spec.rgb[1], c->spec.rgb[2]); + char text[256]; + sprintf(text, c->spec.format, c->print_val); + g.transient_text(5, 20 + 16 * i, text); + } + run_gui(); + if (t >= samples_per_pixel) + { + t = 0; + ++x; + if (x >= g.w) + x = 0; + g.setfg(0, 0, 0); + g.line(x, 0, x, g.h - 1); + } + run_gui(); + g.sync(); + total_samples += count; } + private: int nchans; - struct channel { - chanspec spec; - pipereader *in; - float accum; - int prev_y; - float print_val; - } *chans; + struct channel + { + chanspec spec; + pipereader *in; + float accum; + int prev_y; + float print_val; + } * chans; + void run_channel(channel *c, int nr) + { + g.setfg(c->spec.rgb[0], c->spec.rgb[1], c->spec.rgb[2]); + int y = -1; + while (nr--) + { + float v = *c->in->rd() * c->spec.scale; + if (c->spec.flags & chanspec::COUNT) + ++c->accum; + else if (c->spec.flags & chanspec::SUM) + c->accum += v; + else + { + c->print_val = v; + float nv = (v - c->spec.ymin) / (c->spec.ymax - c->spec.ymin); + if (c->spec.flags & chanspec::WRAP) + nv = nv - floor(nv); + y = g.h - g.h * nv; + } + c->in->read(1); + } + // Display count/sum channels only when the cursor is about to move. + if ((c->spec.flags & (chanspec::COUNT | chanspec::SUM)) && + t + 1 >= samples_per_pixel) + { + T v = c->accum; + y = g.h - 1 - g.h * (v - c->spec.ymin) / (c->spec.ymax - c->spec.ymin); + c->accum = 0; + c->print_val = v; + } + if (y >= 0) + { + if (c->spec.flags & chanspec::LINE) + { + if (x) + g.line(x - 1, c->prev_y, x, y); + c->prev_y = y; + } + else + g.point(x, y); + } + } + void run_gui() + { + g.events(); + // Print cursor time + float ct = g.mx * samples_per_pixel / sample_freq; + float tt = total_samples / sample_freq; + char text[256]; + sprintf(text, "%.3f / %.3f s", ct, tt); + g.setfg(255, 255, 255); + g.transient_text(g.w * 3 / 4, 20, text); + } gfx g; - unsigned long t; + unsigned long t; // Time for synchronous channels int x; int total_samples; - }; - - template - struct spectrumscope : runnable { - T ymax; - float amax; +}; + +template +struct spectrumscope : runnable +{ unsigned long size; + float amax; unsigned long decimation; - spectrumscope(scheduler *sch, pipebuf< complex > & _in, - T _max, const char *_name=NULL) - : runnable(sch, _name?_name:_in.name), - ymax(_max), amax(_max), - size(4096), decimation(DEFAULT_GUI_DECIMATION), - in(_in), phase(0), g(sch, name), fft(NULL) { + spectrumscope(scheduler *sch, pipebuf> &_in, + T _max, const char *_name = NULL) + : runnable(sch, _name ? _name : _in.name), + size(4096), amax(_max / sqrtf(size)), + decimation(DEFAULT_GUI_DECIMATION), + in(_in), + nmarkers(0), + phase(0), g(sch, name), fft(NULL) + { } - void run() { - while ( in.readable() >= size ) { - if ( ! phase ) do_fft(in.rd()); - in.read(size); - if ( ++phase >= decimation ) phase = 0; - } + void mark_freq(float f) + { + if (nmarkers == MAX_MARKERS) + fail("Too many markers"); + markers[nmarkers++] = f; } + void run() + { + while (in.readable() >= size) + { + if (!phase) + do_fft(in.rd()); + in.read(size); + if (++phase >= decimation) + phase = 0; + } + } + private: - pipereader< complex > in; + pipereader> in; + static const int MAX_MARKERS = 4; + float markers[MAX_MARKERS]; + int nmarkers; int phase; gfx g; cfft_engine *fft; - void do_fft(complex *input) { - draw_begin(); - if ( !fft || fft->n!=size ) { - if ( fft ) delete fft; - fft = new cfft_engine(size); - } - complex *pin=input, *pend=pin+size; - complex data[size], *pout=data; - g.setfg(255, 0, 0); - for ( int x=0; pinre = (float)pin->re; - pout->im = (float)pin->im; - // g.point(x, g.h/2-pout->re*g.h/2/ymax); - } - fft->inplace(data, true); - g.setfg(0, 255, 0); - for ( int i=0; i v = data[i];; - float y = hypot(v.re, v.im); - g.line(x, g.h-1, x, g.h-1-y*g.h/amax); - } - if ( g.buttons ) { - char s[256]; - float f = 2.4e6 * (g.mx-g.w/2) / g.w; - sprintf(s, "%f", f); - g.text(16, 16, s); - } - g.show(); - g.sync(); + void do_fft(complex *input) + { + draw_begin(); + if (!fft || fft->n != size) + { + if (fft) + delete fft; + fft = new cfft_engine(size); + } + complex *pin = input, *pend = pin + size; + complex data[size], *pout = data; + g.setfg(255, 0, 0); + for (int x = 0; pin < pend; ++pin, ++pout, ++x) + { + pout->re = (float)pin->re; + pout->im = (float)pin->im; + // g.point(x, g.h/2-pout->re*g.h/2/ymax); + } + fft->inplace(data, true); + g.setfg(0, 255, 0); + for (int i = 0; i < size; ++i) + { + int x = ((i < size / 2) ? i + size / 2 : i - size / 2) * g.w / size; + complex v = data[i]; + ; + float y = hypot(v.re, v.im); + g.line(x, g.h - 1, x, g.h - 1 - y * g.h / amax); + } + if (g.buttons) + { + char s[256]; + float f = 2.4e6 * (g.mx - g.w / 2) / g.w; + sprintf(s, "%f", f); + g.text(16, 16, s); + } + g.show(); + g.sync(); } - void draw_begin() { - g.clear(); - g.setfg(255, 255, 255); - g.line(g.w/2,0, g.w/2,g.h); + void draw_begin() + { + g.clear(); + g.setfg(255, 255, 255); + g.line(g.w / 2, 0, g.w / 2, g.h); + g.setfg(255, 0, 0); + for (int i = 0; i < nmarkers; ++i) + { + int x = g.w * (0.5 + markers[i]); + g.line(x, 0, x, g.h); + } } - }; - - template - struct rfscope : runnable { +}; + +template +struct rfscope : runnable +{ unsigned long size; unsigned long decimation; - float Fs; // Sampling freq for display (Hz) - float Fc; // Center freq for display (Hz) + float Fs; // Sampling freq for display (Hz) + float Fc; // Center freq for display (Hz) int ncursors; float *cursors; // Cursor frequencies float hzoom; // Horizontal zoom factor float db0, dbrange; // Vertical range db0 .. db0+dbrange float bw; // Smoothing bandwidth - rfscope(scheduler *sch, pipebuf< complex > & _in, - const char *_name=NULL) - : runnable(sch, _name?_name:_in.name), - size(4096), decimation(DEFAULT_GUI_DECIMATION), - Fs(1), Fc(0), ncursors(0), hzoom(1), - db0(-25), dbrange(50), bw(0.05), - in(_in), phase(0), g(sch, name), fft(NULL), filtered(NULL) { + rfscope(scheduler *sch, pipebuf> &_in, + const char *_name = NULL) + : runnable(sch, _name ? _name : _in.name), + size(4096), decimation(DEFAULT_GUI_DECIMATION), + Fs(1), Fc(0), ncursors(0), hzoom(1), + db0(-25), dbrange(50), bw(0.05), + in(_in), phase(0), g(sch, name), fft(NULL), filtered(NULL) + { } - void run() { - while ( in.readable() >= size ) { - if ( ! phase ) do_fft(in.rd()); - in.read(size); - if ( ++phase >= decimation ) phase = 0; - } + void run() + { + while (in.readable() >= size) + { + if (!phase) + do_fft(in.rd()); + in.read(size); + if (++phase >= decimation) + phase = 0; + } } + private: - pipereader< complex > in; + pipereader> in; int phase; gfx g; cfft_engine *fft; float *filtered; - void do_fft(complex *input) { - g.events(); - draw_begin(); - if ( !fft || fft->n!=size ) { - if ( fft ) delete fft; - fft = new cfft_engine(size); - } - // Convert to complex and transform - complex *pin=input, *pend=pin+size; - complex data[size], *pout=data; - for ( int x=0; pinre = (float)pin->re; - pout->im = (float)pin->im; - } - fft->inplace(data, true); - float amp2[size]; - for ( int i=0; i &v = data[i];; - amp2[i] = (v.re*v.re + v.im*v.im)*size; - } - if ( ! filtered ) { - filtered = new float[size]; - for ( int i=0; i *input) + { + g.events(); + draw_begin(); + if (!fft || fft->n != size) + { + if (fft) + delete fft; + fft = new cfft_engine(size); + } + // Convert to complex and transform + complex *pin = input, *pend = pin + size; + complex data[size], *pout = data; + for (int x = 0; pin < pend; ++pin, ++pout, ++x) + { + pout->re = (float)pin->re; + pout->im = (float)pin->im; + } + fft->inplace(data, true); + float amp2[size]; + for (int i = 0; i < size; ++i) + { + complex &v = data[i]; + ; + amp2[i] = (v.re * v.re + v.im * v.im) * size; + } + if (!filtered) + { + filtered = new float[size]; + for (int i = 0; i < size; ++i) + filtered[i] = amp2[i]; + } + float bwcomp = 1 - bw; + g.setfg(0, 255, 0); + for (int i = 0; i < size; ++i) + { + filtered[i] = amp2[i] * bw + filtered[i] * bwcomp; + float db = filtered[i] ? 10 * logf(filtered[i]) / logf(10) : db0; + int is = (i < size / 2) ? i : i - size; + int x = g.w / 2 + is * hzoom * g.w / size; + int y = g.h - 1 - (db - db0) * g.h / dbrange; + g.line(x, g.h - 1, x, y); + } + if (g.buttons) + { + char s[256]; + float freq = Fc + Fs * (g.mx - g.w / 2) / g.w / hzoom; + float val = db0 + (float)((g.h - 1) - g.my) * dbrange / g.h; + sprintf(s, "%f.3 Hz %f.2 dB", freq, val); + g.setfg(255, 255, 255); + g.text(16, 16, s); + } + // Draw cursors + g.setfg(255, 255, 0); + for (int i = 0; i < ncursors; ++i) + { + int x = g.w / 2 + (cursors[i] - Fc) * hzoom * g.w / Fs; + g.line(x, 0, x, g.h - 1); + } + g.show(); + g.sync(); } - void draw_begin() { - g.clear(); - // dB scale - g.setfg(64, 64, 64); - for ( float db=floorf(db0); db - struct genscope : runnable { - struct render { - int x, y; - char dir; // 'h'orizontal or 'v'ertical +template +struct genscope : runnable +{ + struct render + { + int x, y; + char dir; // 'h'orizontal or 'v'ertical }; - struct chanspec { - pipebuf *in; // NULL if disabled - render r; + struct chanspec + { + pipebuf *in; // NULL if disabled + render r; }; genscope(scheduler *sch, chanspec *specs, int _nchans, - const char *_name=NULL) - : runnable(sch, _name?_name:"genscope"), - nchans(_nchans), - g(sch, name) { - chans = new channel[nchans]; - for ( int i=0; i(*specs[i].in); - } - } - g.clear(); - gettimeofday(&tv, NULL); + const char *_name = NULL) + : runnable(sch, _name ? _name : "genscope"), + nchans(_nchans), + g(sch, name) + { + chans = new channel[nchans]; + for (int i = 0; i < nchans; ++i) + { + if (!specs[i].in) + { + chans[i].in = NULL; + } + else + { + chans[i].spec = specs[i]; + chans[i].in = new pipereader(*specs[i].in); + } + } + g.clear(); + gettimeofday(&tv, NULL); } - struct channel { - chanspec spec; - pipereader *in; - } *chans; + struct channel + { + chanspec spec; + pipereader *in; + } * chans; int nchans; struct timeval tv; - void run() { - g.setfg(0, 255, 0); - for ( channel *pc=chans; pcin ) continue; - int n = pc->in->readable(); - T last = pc->in->rd()[n-1]; - pc->in->read(n); - int dx = pc->spec.r.dir=='h' ? last : 0; - int dy = pc->spec.r.dir=='v' ? last : 0; - dx /= 3; - dy /= 3; - g.line(pc->spec.r.x-dx, pc->spec.r.y-dy, - pc->spec.r.x+dx, pc->spec.r.y+dy); - char txt[16]; - sprintf(txt, "%d", (int)last); - g.text(pc->spec.r.x+5, pc->spec.r.y-2, txt); - } - struct timeval newtv; - gettimeofday(&newtv, NULL); - int dt = (newtv.tv_sec-tv.tv_sec)*1000 + (newtv.tv_usec-tv.tv_usec)/1000; - if ( dt > 100 ) { - fprintf(stderr, "#"); - g.show(); - g.sync(); - g.clear(); - tv = newtv; - } + void run() + { + g.setfg(0, 255, 0); + for (channel *pc = chans; pc < chans + nchans; ++pc) + { + if (!pc->in) + continue; + int n = pc->in->readable(); + T last = pc->in->rd()[n - 1]; + pc->in->read(n); + int dx = pc->spec.r.dir == 'h' ? last : 0; + int dy = pc->spec.r.dir == 'v' ? last : 0; + dx /= 3; + dy /= 3; + g.line(pc->spec.r.x - dx, pc->spec.r.y - dy, + pc->spec.r.x + dx, pc->spec.r.y + dy); + char txt[16]; + sprintf(txt, "%d", (int)last); + g.text(pc->spec.r.x + 5, pc->spec.r.y - 2, txt); + } + struct timeval newtv; + gettimeofday(&newtv, NULL); + int dt = (newtv.tv_sec - tv.tv_sec) * 1000 + (newtv.tv_usec - tv.tv_usec) / 1000; + if (dt > 100) + { + fprintf(stderr, "#"); + g.show(); + g.sync(); + g.clear(); + tv = newtv; + } } + private: gfx g; - }; +}; -#endif // GUI +#endif // GUI -} // namespace +} // namespace leansdr -#endif // LEANSDR_GUI_H +#endif // LEANSDR_GUI_H diff --git a/plugins/channelrx/demoddatv/leansdr/hdlc.h b/plugins/channelrx/demoddatv/leansdr/hdlc.h index ac936f5d1..2b6e60e6d 100644 --- a/plugins/channelrx/demoddatv/leansdr/hdlc.h +++ b/plugins/channelrx/demoddatv/leansdr/hdlc.h @@ -1,3 +1,19 @@ +// This file is part of LeanSDR Copyright (C) 2016-2018 . +// See the toplevel README for more information. +// +// This program 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. +// +// This program 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 this program. If not, see . + #ifndef LEANSDR_HDLC_H #define LEANSDR_HDLC_H @@ -11,19 +27,14 @@ namespace leansdr struct hdlc_dec { - hdlc_dec(int _minframesize, // Including CRC, excluding HDLC flags. - int _maxframesize, bool _invert) : - minframesize(_minframesize), - maxframesize(_maxframesize), - invertmask(_invert ? 0xff : 0), - framebuf(new u8[maxframesize]), - debug(false) + hdlc_dec(int _minframesize, // Including CRC, excluding HDLC flags. + int _maxframesize, + bool _invert) : minframesize(_minframesize), + maxframesize(_maxframesize), + invertmask(_invert ? 0xff : 0), + framebuf(new u8[maxframesize]), + debug(false) { - byte_out = 0; - nbits_out = 0; - framesize = 0; - crc16 = 0; - reset(); } @@ -45,24 +56,26 @@ struct hdlc_dec // Return number of checksum errors in *fcs_errors. // *ppin will have increased by at least 1 (unless count==0). - u8 *decode(u8 **ppin, int count, int *pdatasize, int *hdlc_errors, - int *fcs_errors) + u8 *decode(u8 **ppin, int count, int *pdatasize, int *hdlc_errors, int *fcs_errors) { *hdlc_errors = 0; *fcs_errors = 0; *pdatasize = -1; u8 *pin = *ppin, *pend = pin + count; + for (; pin < pend; ++pin) { u8 byte_in = (*pin) ^ invertmask; + for (int bits = 8; bits--; byte_in <<= 1) { u8 bit_in = byte_in & 128; shiftreg = (shiftreg >> 1) | bit_in; + if (!inframe) { if (shiftreg == 0x7e) - { // HDLC flag 01111110 + { // HDLC flag 01111110 inframe = true; nbits_out = 0; begin_frame(); @@ -71,11 +84,11 @@ struct hdlc_dec else { if ((shiftreg & 0xfe) == 0x7c) - { // 0111110x HDLC stuffing + { // 0111110x HDLC stuffing // Unstuff this 0 } else if (shiftreg == 0x7e) - { // 01111110 HDLC flag + { // 01111110 HDLC flag if (nbits_out != 7) { // Not at byte boundary @@ -87,8 +100,7 @@ struct hdlc_dec { // Checksum crc16 ^= 0xffff; - if (framesize < 2 || framesize < minframesize - || crc16 != crc16_check) + if (framesize < 2 || framesize < minframesize || crc16 != crc16_check) { if (debug) fprintf(stderr, "!"); @@ -111,7 +123,7 @@ struct hdlc_dec // Special cases 0111111 and 1111111 cannot affect *pdatasize. } else if (shiftreg == 0xfe) - { // 11111110 HDLC invalid + { // 11111110 HDLC invalid if (framesize) { if (debug) @@ -121,7 +133,7 @@ struct hdlc_dec inframe = false; } else - { // Data bit + { // Data bit byte_out = (byte_out >> 1) | bit_in; // HDLC is LSB first ++nbits_out; if (nbits_out == 8) @@ -134,8 +146,8 @@ struct hdlc_dec nbits_out = 0; } } - } // inframe - } // bits + } // inframe + } // bits if (*pdatasize != -1) { // Found a complete frame @@ -143,88 +155,98 @@ struct hdlc_dec return framebuf; } } + *ppin = pin; return NULL; } -private: + private: // Config int minframesize, maxframesize; u8 invertmask; - u8 *framebuf; // [maxframesize] + u8 *framebuf; // [maxframesize] // State - u8 shiftreg; // Input bitstream - bool inframe; // Currently receiving a frame ? - u8 byte_out; // Accumulator for data bits - int nbits_out; // Number of data bits in byte_out - int framesize; // Number of bytes in framebuf, if inframe - u16 crc16; // CRC of framebuf[framesize] + u8 shiftreg; // Input bitstream + bool inframe; // Currently receiving a frame ? + u8 byte_out; // Accumulator for data bits + int nbits_out; // Number of data bits in byte_out + int framesize; // Number of bytes in framebuf, if inframe + u16 crc16; // CRC of framebuf[framesize] // CRC static const u16 crc16_init = 0xffff; - static const u16 crc16_poly = 0x8408; // 0x1021 MSB-first + static const u16 crc16_poly = 0x8408; // 0x1021 MSB-first static const u16 crc16_check = 0x0f47; + void crc16_byte(u8 data) { crc16 ^= data; + for (int bit = 8; bit--;) + { crc16 = (crc16 & 1) ? (crc16 >> 1) ^ crc16_poly : (crc16 >> 1); + } } -public: + public: bool debug; }; // hdlc_dec // HDLC synchronizer with polarity detection -struct hdlc_sync: runnable +struct hdlc_sync : runnable { - hdlc_sync(scheduler *sch, - pipebuf &_in, // Packed bits - pipebuf &_out, // Bytes - int _minframesize, // Including CRC, excluding HDLC flags. - int _maxframesize, - // Status - pipebuf *_lock_out = NULL, - pipebuf *_framecount_out = NULL, - pipebuf *_fcserrcount_out = NULL, - pipebuf *_hdlcbytecount_out = NULL, - pipebuf *_databytecount_out = NULL) : - runnable(sch, "hdlc_sync"), minframesize(_minframesize), maxframesize( - _maxframesize), chunk_size(maxframesize + 2), in(_in), out( - _out, _maxframesize + chunk_size), lock_out( - opt_writer(_lock_out)), framecount_out( - opt_writer(_framecount_out)), fcserrcount_out( - opt_writer(_fcserrcount_out)), hdlcbytecount_out( - opt_writer(_hdlcbytecount_out)), databytecount_out( - opt_writer(_databytecount_out)), cur_sync(0), resync_phase( - 0), lock_state(false), resync_period(32), header16(false) + hdlc_sync(scheduler *sch, pipebuf &_in, // Packed bits + pipebuf &_out, // Bytes + int _minframesize, // Including CRC, excluding HDLC flags. + int _maxframesize, + // Status + pipebuf *_lock_out = NULL, + pipebuf *_framecount_out = NULL, + pipebuf *_fcserrcount_out = NULL, + pipebuf *_hdlcbytecount_out = NULL, + pipebuf *_databytecount_out = NULL) : runnable(sch, "hdlc_sync"), + minframesize(_minframesize), + maxframesize(_maxframesize), + chunk_size(maxframesize + 2), + in(_in), + out(_out, _maxframesize + chunk_size), + lock_out(opt_writer(_lock_out)), + framecount_out(opt_writer(_framecount_out)), + fcserrcount_out(opt_writer(_fcserrcount_out)), + hdlcbytecount_out(opt_writer(_hdlcbytecount_out)), + databytecount_out(opt_writer(_databytecount_out)), + cur_sync(0), + resync_phase(0), + lock_state(false), + resync_period(32), + header16(false) { for (int s = 0; s < NSYNCS; ++s) { syncs[s].dec = new hdlc_dec(minframesize, maxframesize, s != 0); + for (int h = 0; h < NERRHIST; ++h) syncs[s].errhist[h] = 0; } + syncs[cur_sync].dec->debug = sch->debug; errslot = 0; } void run() { - if (!opt_writable(lock_out) || !opt_writable(framecount_out) - || !opt_writable(fcserrcount_out) - || !opt_writable(hdlcbytecount_out) - || !opt_writable(databytecount_out)) + if (!opt_writable(lock_out) || !opt_writable(framecount_out) || !opt_writable(fcserrcount_out) || !opt_writable(hdlcbytecount_out) || !opt_writable(databytecount_out)) + { return; + } bool previous_lock_state = lock_state; int fcserrcount = 0, framecount = 0; int hdlcbytecount = 0, databytecount = 0; // Note: hdlc_dec may already hold one frame ready for output. - while ((long) in.readable() >= chunk_size - && (long) out.writable() >= maxframesize + chunk_size) + while ((long)in.readable() >= chunk_size && (long)out.writable() >= maxframesize + chunk_size) { if (!resync_phase) { @@ -233,14 +255,15 @@ struct hdlc_sync: runnable { if (s != cur_sync) syncs[s].dec->reset(); + syncs[s].errhist[errslot] = 0; - for (u8 *pin = in.rd(), *pend = pin + chunk_size; - pin < pend;) + + for (u8 *pin = in.rd(), *pend = pin + chunk_size; pin < pend;) { int datasize, hdlc_errors, fcs_errors; - u8 *f = syncs[s].dec->decode(&pin, pend - pin, - &datasize, &hdlc_errors, &fcs_errors); + u8 *f = syncs[s].dec->decode(&pin, pend - pin, &datasize, &hdlc_errors, &fcs_errors); syncs[s].errhist[errslot] += hdlc_errors; + if (s == cur_sync) { if (f) @@ -250,32 +273,39 @@ struct hdlc_sync: runnable databytecount += datasize; ++framecount; } + fcserrcount += fcs_errors; framecount += fcs_errors; } } } + errslot = (errslot + 1) % NERRHIST; // Switch to another sync option ? // Compare total error counts over about NERRHIST frames. int total_errors[NSYNCS]; + for (int s = 0; s < NSYNCS; ++s) { total_errors[s] = 0; + for (int h = 0; h < NERRHIST; ++h) total_errors[s] += syncs[s].errhist[h]; } + int best = cur_sync; + for (int s = 0; s < NSYNCS; ++s) if (total_errors[s] < total_errors[best]) best = s; + if (best != cur_sync) { lock_state = false; + if (sch->debug) - fprintf(stderr, "[%d:%d->%d:%d]", cur_sync, - total_errors[cur_sync], best, - total_errors[best]); + fprintf(stderr, "[%d:%d->%d:%d]", cur_sync, total_errors[cur_sync], best, total_errors[best]); + // No verbose messages on candidate syncs syncs[cur_sync].dec->debug = false; cur_sync = best; @@ -288,8 +318,8 @@ struct hdlc_sync: runnable for (u8 *pin = in.rd(), *pend = pin + chunk_size; pin < pend;) { int datasize, hdlc_errors, fcs_errors; - u8 *f = syncs[cur_sync].dec->decode(&pin, pend - pin, - &datasize, &hdlc_errors, &fcs_errors); + u8 *f = syncs[cur_sync].dec->decode(&pin, pend - pin, &datasize, &hdlc_errors, &fcs_errors); + if (f) { lock_state = true; @@ -297,25 +327,29 @@ struct hdlc_sync: runnable databytecount += datasize; ++framecount; } + fcserrcount += fcs_errors; framecount += fcs_errors; } - } // resync_phase + } // resync_phase + in.read(chunk_size); hdlcbytecount += chunk_size; + if (++resync_phase >= resync_period) resync_phase = 0; - } // Work to do + } // Work to do if (lock_state != previous_lock_state) opt_write(lock_out, lock_state ? 1 : 0); + opt_write(framecount_out, framecount); opt_write(fcserrcount_out, fcserrcount); opt_write(hdlcbytecount_out, hdlcbytecount); opt_write(databytecount_out, databytecount); } -private: + private: void output_frame(u8 *f, int size) { if (header16) @@ -324,6 +358,7 @@ private: out.write(size >> 8); out.write(size & 255); } + memcpy(out.wr(), f, size); out.written(size); opt_write(framecount_out, 1); @@ -336,23 +371,26 @@ private: pipewriter *lock_out; pipewriter *framecount_out, *fcserrcount_out; pipewriter *hdlcbytecount_out, *databytecount_out; - static const int NSYNCS = 2; // Two possible polarities - static const int NERRHIST = 2; // Compare error counts over two frames + static const int NSYNCS = 2; // Two possible polarities + static const int NERRHIST = 2; // Compare error counts over two frames + struct { hdlc_dec *dec; int errhist[NERRHIST]; } syncs[NSYNCS]; + int errslot; int cur_sync; int resync_phase; bool lock_state; -public: + + public: int resync_period; - bool header16; // Output length prefix + bool header16; // Output length prefix }; // hdlc_sync -}// namespace +} // namespace leansdr -#endif // LEANSDR_HDLC_H +#endif // LEANSDR_HDLC_H diff --git a/plugins/channelrx/demoddatv/leansdr/iess.h b/plugins/channelrx/demoddatv/leansdr/iess.h index 778043d43..66111f14b 100644 --- a/plugins/channelrx/demoddatv/leansdr/iess.h +++ b/plugins/channelrx/demoddatv/leansdr/iess.h @@ -1,58 +1,79 @@ +// This file is part of LeanSDR Copyright (C) 2016-2018 . +// See the toplevel README for more information. +// +// This program 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. +// +// This program 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 this program. If not, see . + #ifndef LEANSDR_IESS_H #define LEANSDR_IESS_H #include "leansdr/framework.h" -namespace leansdr { +namespace leansdr +{ - // SELF-SYNCHRONIZING DESCRAMBLER - // Per ETSI TR 192 figure 8 (except Q20/ not connected to CLOCK). - // This implementation operates on packed bits, MSB first. +// SELF-SYNCHRONIZING DESCRAMBLER +// Per ETSI TR 192 figure 8 (except Q20/ not connected to CLOCK). +// This implementation operates on packed bits, MSB first. - struct etr192_descrambler : runnable { +struct etr192_descrambler : runnable +{ etr192_descrambler(scheduler *sch, - pipebuf &_in, // Packed scrambled bits - pipebuf &_out) // Packed bits - : runnable(sch, "etr192_dec"), - in(_in), out(_out), - shiftreg(0), counter(0) + pipebuf &_in, // Packed scrambled bits + pipebuf &_out) // Packed bits + : runnable(sch, "etr192_dec"), + in(_in), out(_out), + shiftreg(0), counter(0) { } - void run() { - int count = min(in.readable(), out.writable()); - for ( u8 *pin=in.rd(), *pend=pin+count, *pout=out.wr(); - pin>8)) & 1; - int counter_overflow = (counter==31) ? 1 : 0; - int taps = (shiftreg>>2) ^ (shiftreg>>19); - int bit_out = (taps ^ counter_overflow ^ bit_in ^ 1) & 1; - // Execute clock transition -#if 1 // Descramble - shiftreg = (shiftreg<<1) | bit_in; -#else // Scramble - shiftreg = (shiftreg<<1) | bit_out; + void run() + { + int count = min(in.readable(), out.writable()); + for (u8 *pin = in.rd(), *pend = pin + count, *pout = out.wr(); + pin < pend; ++pin, ++pout) + { + u8 byte_in = *pin, byte_out = 0; + for (int b = 8; b--; byte_in <<= 1) + { + // Levels before clock transition + int bit_in = (byte_in & 128) ? 1 : 0; + int reset_counter = (shiftreg ^ (shiftreg >> 8)) & 1; + int counter_overflow = (counter == 31) ? 1 : 0; + int taps = (shiftreg >> 2) ^ (shiftreg >> 19); + int bit_out = (taps ^ counter_overflow ^ bit_in ^ 1) & 1; + // Execute clock transition +#if 1 // Descramble + shiftreg = (shiftreg << 1) | bit_in; +#else // Scramble + shiftreg = (shiftreg << 1) | bit_out; #endif - counter = reset_counter ? 0 : (counter+1)&31; - byte_out = (byte_out<<1) | bit_out; - } - *pout = byte_out; - } - in.read(count); - out.written(count); + counter = reset_counter ? 0 : (counter + 1) & 31; + byte_out = (byte_out << 1) | bit_out; + } + *pout = byte_out; + } + in.read(count); + out.written(count); } private: pipereader in; pipewriter out; - u32 shiftreg; // 20 bits - u8 counter; // 5 bits - }; // etr192_descrambler + u32 shiftreg; // 20 bits + u8 counter; // 5 bits +}; // etr192_descrambler -} // namespace +} // namespace leansdr -#endif // LEANSDR_IESS_H +#endif // LEANSDR_IESS_H diff --git a/plugins/channelrx/demoddatv/leansdr/incrementalarray.h b/plugins/channelrx/demoddatv/leansdr/incrementalarray.h deleted file mode 100644 index ac2c33db5..000000000 --- a/plugins/channelrx/demoddatv/leansdr/incrementalarray.h +++ /dev/null @@ -1,64 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2016 Edouard Griffiths, F4EXB // -// // -// This program 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 as version 3 of the License, or // -// // -// This program 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 V3 for more details. // -// // -// You should have received a copy of the GNU General Public License // -// along with this program. If not, see . // -/////////////////////////////////////////////////////////////////////////////////// - -#ifndef SDRBASE_UTIL_INCREMENTALARRAY_H_ -#define SDRBASE_UTIL_INCREMENTALARRAY_H_ - -#include - -namespace leansdr -{ - -template -class IncrementalArray -{ -public: - T *m_array; - - IncrementalArray(); - ~IncrementalArray(); - - void allocate(uint32_t size); - -private: - uint32_t m_size; -}; - -template -IncrementalArray::IncrementalArray() : - m_array(0), - m_size(0) -{ -} - -template -IncrementalArray::~IncrementalArray() -{ - if (m_array) { delete[] m_array; } -} - -template -void IncrementalArray::allocate(uint32_t size) -{ - if (size <= m_size) { return; } - if (m_array) { delete[] m_array; } - m_array = new T[size]; - m_size = size; -} - -} // namespace - -#endif /* SDRBASE_UTIL_INCREMENTALARRAY_H_ */ diff --git a/plugins/channelrx/demoddatv/leansdr/ldpc.h b/plugins/channelrx/demoddatv/leansdr/ldpc.h new file mode 100644 index 000000000..39bf25a73 --- /dev/null +++ b/plugins/channelrx/demoddatv/leansdr/ldpc.h @@ -0,0 +1,520 @@ +// This file is part of LeanSDR Copyright (C) 2016-2018 . +// See the toplevel README for more information. +// +// This program 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. +// +// This program 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 this program. If not, see . + +#ifndef LEANSDR_LDPC_H +#define LEANSDR_LDPC_H + +#define lfprintf(...) \ + { \ + } + +namespace leansdr +{ + +// LDPC sparse matrix specified like in the DVB-S2 standard +// Taddr must be wide enough to index message bits and address bits. + +template +struct ldpc_table +{ + // TBD Save space + static const int MAX_ROWS = 162; // 64800 * (9/10) / 360 + static const int MAX_COLS = 13; + int q; + int nrows; + struct row + { + int ncols; + Taddr cols[MAX_COLS]; + } rows[MAX_ROWS]; +}; + +// LDPC ENGINE + +// SOFTBITs can be hard (e.g. bool) or soft (e.g. llr_t). +// They are stored as SOFTWORDs containing SWSIZE SOFTBITs. +// See interface in softword.h. + +template +struct ldpc_engine +{ + + ldpc_engine() + : vnodes(NULL), cnodes(NULL) + { + } + + // vnodes: Value/variable nodes (message bits) + // cnodes: Check nodes (parity bits) + + int k; // Message size in bits + int n; // Codeword size in bits + + struct node + { + Taddr *edges; + int nedges; + static const int CHUNK = 4; // Grow edges[] in steps of CHUNK. + void append(Taddr a) + { + if (nedges % CHUNK == 0) + { // Full ? + edges = (Taddr *)realloc(edges, (nedges + CHUNK) * sizeof(Taddr)); + if (!edges) + fatal("realloc"); + } + edges[nedges++] = a; + } + }; + + node *vnodes; // [k] + node *cnodes; // [n-k] + + // Initialize from a S2-style table. + + ldpc_engine(const ldpc_table *table, int _k, int _n) + : k(_k), n(_n) + { + // Sanity checks + if (360 % SWSIZE) + fatal("Bad LDPC word size"); + if (k % SWSIZE) + fatal("Bad LDPC k"); + if (n % SWSIZE) + fatal("Bad LDPC n"); + if (k != table->nrows * 360) + fatal("Bad table"); + int n_k = n - k; + if (table->q * 360 != n_k) + fatal("Bad q"); + + vnodes = new node[k]; + memset(vnodes, 0, sizeof(node) * k); + cnodes = new node[n_k]; + memset(cnodes, 0, sizeof(node) * n_k); + + // Expand the graph. + + int m = 0; + // Iterate over rows + for (const typename ldpc_table::row *prow = table->rows; + prow < table->rows + table->nrows; + ++prow) + { + // Process 360 bits per row. + int q = table->q; + int qoffs = 0; + for (int mw = 360; mw--; ++m, qoffs += q) + { + const Taddr *pa = prow->cols; + for (int nc = prow->ncols; nc--; ++pa) + { + int a = (int)*pa + qoffs; + if (a >= n_k) + a -= n_k; // Modulo n-k. Note qoffs<360*q. + if (a >= n_k) + fail("Invalid LDPC table"); + vnodes[m].append(a); + cnodes[a].append(m); + } + } + } + } + + void print_node_stats() + { + int nedges = count_edges(vnodes, k); + fprintf(stderr, "LDPC(%5d,%5d)(%.2f)" + " %5.2f edges/vnode, %5.2f edges/cnode\n", + k, n - k, (float)k / n, (float)nedges / k, (float)nedges / (n - k)); + } + + int count_edges(node *nodes, int nnodes) + { + int c = 0; + for (int i = 0; i < nnodes; ++i) + c += nodes[i].nedges; + return c; + } + + // k: Message size in bits + // n: Codeword size in bits + // integrate: Optional S2-style post-processing + +#if 0 + void encode_hard(const ldpc_table *table, const uint8_t *msg, + int k, int n, uint8_t *parity, bool integrate=true) { + // Sanity checks + if ( 360 % SWSIZE ) fatal("Bad LDPC word size"); + if ( k % SWSIZE ) fatal("Bad LDPC k"); + if ( n % SWSIZE ) fatal("Bad LDPC n"); + if ( k != table->nrows*360 ) fatal("Bad table"); + int n_k = n - k; + if ( table->q*360 != n_k ) fatal("Bad q"); + + for ( int i=0; i::row *prow = table->rows; // quirk + prow < table->rows+table->nrows; + ++prow ) { + // Process 360 bits per row, in words of SWSIZE bits + int q = table->q; + int qoffs = 0; + for ( int mw=360/SWSIZE; mw--; ++msg ) { + SOFTWORD msgword = *msg; + for ( int wbit=0; wbitcols; + for ( int nc=prow->ncols; nc--; ++pa ) { + // Don't wrap modulo range of Taddr + int a = (int)*pa + qoffs; + // Note: qoffs < 360*q=n-k + if ( a >= n_k ) a -= n_k; // TBD not predictable + softwords_flip(parity, a); + } + } + } + } + + if ( integrate ) + integrate_bits(parity, parity, n_k/SWSIZE); + } +#endif + + void encode(const ldpc_table *table, const SOFTWORD *msg, + int k, int n, SOFTWORD *parity, int integrate = true) + { + // Sanity checks + if (360 % SWSIZE) + fatal("Bad LDPC word size"); + if (k % SWSIZE) + fatal("Bad LDPC k"); + if (n % SWSIZE) + fatal("Bad LDPC n"); + if (k != table->nrows * 360) + fatal("Bad table"); + int n_k = n - k; + if (table->q * 360 != n_k) + fatal("Bad q"); + + for (int i = 0; i < n_k / SWSIZE; ++i) + softword_zero(&parity[i]); + + // Iterate over rows + for (const typename ldpc_table::row *prow = table->rows; // quirk + prow < table->rows + table->nrows; + ++prow) + { + // Process 360 bits per row, in words of SWSIZE bits + int q = table->q; + int qoffs = 0; + for (int mw = 360 / SWSIZE; mw--; ++msg) + { + SOFTWORD msgword = *msg; + for (int wbit = 0; wbit < SWSIZE; ++wbit, qoffs += q) + { + SOFTBIT msgbit = softword_get(msgword, wbit); + if (!softbit_harden(msgbit)) + continue; + const Taddr *pa = prow->cols; + for (int nc = prow->ncols; nc--; ++pa) + { + int a = (int)*pa + qoffs; + // Note: qoffs < 360*q=n-k + if (a >= n_k) + a -= n_k; // TBD not predictable + softwords_flip(parity, a); + } + } + } + } + + if (integrate) + integrate_bits(parity, parity, n_k / SWSIZE); + } + + // Flip bits connected to parity errors, one at a time, + // as long as things improve and max_bitflips is not exceeded. + + // cw: codeword (k value bits followed by n-k check bits) + + static const int PPCM = 39; + + typedef int64_t score_t; + + score_t compute_scores(SOFTWORD *m, SOFTWORD *p, SOFTWORD *q, int nc, + score_t *score, int k) + { + int total = 0; + memset(score, 0, k * sizeof(*score)); + for (int c = 0; c < nc; ++c) + { + SOFTBIT err = softwords_xor(p, q, c); + if (softbit_harden(err)) + { + Taddr *pe = cnodes[c].edges; + for (int e = cnodes[c].nedges; e--; ++pe) + { + int v = *pe; + int s = err * softwords_weight(m, v) * PPCM / vnodes[v].nedges; + //fprintf(stderr, "c[%d] bad => v[%d] += %d (%d*%d)\n", + ///c, v, s, err, softwords_weight(m,*pe)); + score[v] += s; + total += s; + } + } + } + return total; + } + + int decode_bitflip(const ldpc_table *table, SOFTWORD *cw, + int k, int n, + int max_bitflips) + { + if (!vnodes) + fail("LDPC graph not initialized"); + int n_k = n - k; + + // Compute the expected check bits (without the final mixing) + SOFTWORD expected[n_k / SWSIZE]; + encode(table, cw, k, n, expected, false); + // Reverse the integrator mixing from the received check bits + SOFTWORD received[n_k / SWSIZE]; + diff_bits(cw + k / SWSIZE, received, n_k / SWSIZE); + + // Compute initial scores + score_t score[k]; + score_t tots = compute_scores(cw, expected, received, n_k, score, k); + lfprintf(stderr, "Initial score %d\n", (int)tots); + + int nflipped = 0; + + score_t score_threshold; + { + SOFTBIT one; + softbit_set(&one, true); + score_threshold = (int)one * 2; + } + + bool progress = true; + while (progress && nflipped < max_bitflips) + { + progress = false; + // Try to flip parity bits. + // Due to differential decoding, they appear as consecutive errors. + SOFTBIT prev_err = softwords_xor(expected, received, 0); + for (int b = 0; b < n - k - 1; ++b) + { + prev_err = softwords_xor(expected, received, b); //TBD + SOFTBIT err = softwords_xor(expected, received, b + 1); + if (softbit_harden(prev_err) && softbit_harden(err)) + { + lfprintf(stderr, "flip parity %d\n", b); + softwords_flip(received, b); + softwords_flip(received, b + 1); + ++nflipped; // Counts as one flip before differential decoding. + progress = true; + int dtot = 0; + // Depenalize adjacent message bits. + { + Taddr *pe = cnodes[b].edges; + for (int e = cnodes[b].nedges; e--; ++pe) + { + int d = prev_err * softwords_weight(cw, *pe) * PPCM / vnodes[*pe].nedges; + score[*pe] -= d; + dtot -= d; + } + } + { + Taddr *pe = cnodes[b + 1].edges; + for (int e = cnodes[b + 1].nedges; e--; ++pe) + { + int d = err * softwords_weight(cw, *pe) * PPCM / vnodes[*pe].nedges; + score[*pe] -= d; + dtot -= d; + } + } + tots += dtot; +#if 1 + // Also update the codeword in-place. + // TBD Useful for debugging only. + softwords_flip(cw, k + b); +#endif + // TBD version soft. err = ! err; + } + prev_err = err; + } // c nodes + score_t maxs = -(1 << 30); + for (int v = 0; v < k; ++v) + if (score[v] > maxs) + maxs = score[v]; + if (!maxs) + break; + lfprintf(stderr, "maxs %d\n", (int)maxs); + // Try to flip each message bits with maximal score + for (int v = 0; v < k; ++v) + { + if (score[v] < score_threshold) + continue; + // if ( score[v] < maxs*9/10 ) continue; + if (score[v] < maxs - 4) + continue; + lfprintf(stderr, " flip %d score=%d\n", (int)v, (int)score[v]); + // Update expected parities and scores that depend on them. + score_t dtot = 0; + for (int commit = 0; commit <= 1; ++commit) + { + Taddr *pe = vnodes[v].edges; + for (int e = vnodes[v].nedges; e--; ++pe) + { + Taddr c = *pe; + SOFTBIT was_bad = softwords_xor(expected, received, c); + if (softbit_harden(was_bad)) + { + Taddr *pe = cnodes[c].edges; + for (int e = cnodes[c].nedges; e--; ++pe) + { + int d = was_bad * softwords_weight(cw, *pe) * PPCM / vnodes[*pe].nedges; + if (commit) + score[*pe] -= d; + else + dtot -= d; + } + } + softwords_flip(expected, c); + SOFTBIT is_bad = softwords_xor(expected, received, c); + if (softbit_harden(is_bad)) + { + Taddr *pe = cnodes[c].edges; + for (int e = cnodes[c].nedges; e--; ++pe) + { + int d = is_bad * softwords_weight(cw, *pe) * PPCM / vnodes[*pe].nedges; + if (commit) + score[*pe] += d; + else + dtot += d; + } + } + if (!commit) + softwords_flip(expected, c); + } + if (!commit) + { + if (dtot >= 0) + { + lfprintf(stderr, " abort %d\n", v); + break; // Next v + } + } + else + { + softwords_flip(cw, v); + ++nflipped; + tots += dtot; + progress = true; + v = k - 1; // Force exit to update maxs ? + } + } // commit + } // v + lfprintf(stderr, "progress %d\n", progress); +#if 0 + fprintf(stderr, "CHECKING TOTS INCREMENT (slow) %d\n", tots); + score_t tots2 = compute_scores(cw, expected, received, n_k, score, k); + if ( tots2 != tots ) fail("bad tots update"); +#endif + } + return nflipped; + } + + // EN 302 307-1 5.3.2.1 post-processing of parity bits. + // In-place allowed. + +#if 1 + static void integrate_bits(const SOFTWORD *in, SOFTWORD *out, int nwords) + { + SOFTBIT sum; + softbit_clear(&sum); + for (int i = 0; i < nwords; ++i) + { + SOFTWORD w = in[i]; + for (int b = 0; b < SWSIZE; ++b) + { + sum = softbit_xor(sum, softword_get(w, b)); + softword_write(w, b, sum); + } + out[i] = w; + } + } +#else + // Optimized for hard_sb + static void integrate_bits(const uint8_t *in, uint8_t *out, int nwords) + { + // TBD Optimize + uint8_t prev = 0; + for (int i = 0; i < nwords; ++i) + { + uint8_t c = in[i]; + for (int j = SWSIZE; j--;) + { + c ^= prev << j; + prev = (c >> j) & 1; + } + out[i] = c; + } + } +#endif + + // Undo EN 302 307-1 5.3.2.1, post-processing of parity bits. + // In-place allowed. + +#if 1 + static void diff_bits(const SOFTWORD *in, SOFTWORD *out, int nwords) + { + SOFTBIT prev; + softbit_clear(&prev); + for (int i = 0; i < nwords; ++i, ++in, ++out) + { + SOFTWORD w = *in; + for (int b = 0; b < SWSIZE; ++b) + { + SOFTBIT n = softword_get(w, b); + softword_write(w, b, softbit_xor(prev, n)); + prev = n; + } + *out = w; + } + } +#else + // Optimized for hard_sb + static void diff_bits(const uint8_t *in, uint8_t *out, int nwords) + { + uint8_t prev = 0; + for (int i = 0; i < nwords; ++i) + { + uint8_t c = in[i]; + out[i] = c ^ (prev | (c >> 1)); + prev = (c & 1) << (SWSIZE - 1); + } + } +#endif + +}; // ldpc_engine + +} // namespace leansdr + +#endif // LEANSDR_LDPC_H diff --git a/plugins/channelrx/demoddatv/leansdr/math.cpp b/plugins/channelrx/demoddatv/leansdr/math.cpp new file mode 100644 index 000000000..29131a192 --- /dev/null +++ b/plugins/channelrx/demoddatv/leansdr/math.cpp @@ -0,0 +1,56 @@ +#include "math.h" + +namespace leansdr +{ + +int hamming_weight(uint8_t x) +{ + static const int lut[16] = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4}; + return lut[x & 15] + lut[x >> 4]; +} + +int hamming_weight(uint16_t x) +{ + return hamming_weight((uint8_t)x) + hamming_weight((uint8_t)(x >> 8)); +} + +int hamming_weight(uint32_t x) +{ + return hamming_weight((uint16_t)x) + hamming_weight((uint16_t)(x >> 16)); +} + +int hamming_weight(uint64_t x) +{ + return hamming_weight((uint32_t)x) + hamming_weight((uint32_t)(x >> 32)); +} + +unsigned char parity(uint8_t x) +{ + x ^= x >> 4; + return (0x6996 >> (x & 15)) & 1; // 16-entry look-up table +} + +unsigned char parity(uint16_t x) +{ + return parity((uint8_t)(x ^ (x >> 8))); +} + +unsigned char parity(uint32_t x) +{ + return parity((uint16_t)(x ^ (x >> 16))); +} + +unsigned char parity(uint64_t x) +{ + return parity((uint32_t)(x ^ (x >> 32))); +} + +int log2i(uint64_t x) +{ + int n = -1; + for (; x; ++n, x >>= 1) + ; + return n; +} + +} // leansdr \ No newline at end of file diff --git a/plugins/channelrx/demoddatv/leansdr/math.h b/plugins/channelrx/demoddatv/leansdr/math.h index ada707f17..629569d4b 100644 --- a/plugins/channelrx/demoddatv/leansdr/math.h +++ b/plugins/channelrx/demoddatv/leansdr/math.h @@ -1,99 +1,155 @@ +// This file is part of LeanSDR Copyright (C) 2016-2018 . +// See the toplevel README for more information. +// +// This program 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. +// +// This program 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 this program. If not, see . + #ifndef LEANSDR_MATH_H #define LEANSDR_MATH_H -#define _USE_MATH_DEFINES #include #include -namespace leansdr { +namespace leansdr +{ - template - struct complex { +template +struct complex +{ T re, im; - complex() : re(0), im(0) { } - complex(T x) : re(x), im(0) { } - complex(T x, T y) : re(x), im(y) { } - inline void operator +=(const complex &x) { re+=x.re; im+=x.im; } - }; - - template - complex operator +(const complex &a, const complex &b) { - return complex(a.re+b.re, a.im+b.im); - } - - template - complex operator *(const complex &a, const complex &b) { - return complex(a.re*b.re-a.im*b.im, a.re*b.im+a.im*b.re); - } - - template - complex operator *(const complex &a, const T &k) { - return complex(a.re*k, a.im*k); - } - - template - complex operator *(const T &k, const complex &a) { - return complex(k*a.re, k*a.im); - } - - // TBD Optimize with dedicated instructions - inline int hamming_weight(uint8_t x) { - static const int lut[16] = { 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4 }; - return lut[x&15] + lut[x>>4]; - } - inline int hamming_weight(uint16_t x) { - return hamming_weight((uint8_t)x) - + hamming_weight((uint8_t)(x>>8)); - } - inline int hamming_weight(uint32_t x) { - return hamming_weight((uint16_t)x) - + hamming_weight((uint16_t)(x>>16)); - } - inline int hamming_weight(uint64_t x) { - return hamming_weight((uint32_t)x) - + hamming_weight((uint32_t)(x>>32)); - } - - inline unsigned char parity(uint8_t x) { - x ^= x>>4; - return (0x6996 >> (x&15)) & 1; // 16-entry look-up table - } - inline unsigned char parity(uint16_t x) { - return parity((uint8_t)(x^(x>>8))); - } - inline unsigned char parity(uint32_t x) { - return parity((uint16_t)(x^(x>>16))); - } - inline unsigned char parity(uint64_t x) { - return parity((uint32_t)(x^(x>>32))); - } - - inline int log2i(uint64_t x) { - int n = -1; - for ( ; x; ++n,x>>=1 ) ; - return n; - } - - // Pre-computed sin/cos for 16-bit angles - - struct trig16 { - complex lut[65536]; // TBD static and shared - trig16() { - for ( int a=0; a<65536; ++a ) { - float af = a * 2*M_PI / 65536; - lut[a].re = cosf(af); - lut[a].im = sinf(af); - } + complex() {} + complex(T x) : re(x), im(0) {} + complex(T x, T y) : re(x), im(y) {} + inline void operator+=(const complex &x) + { + re += x.re; + im += x.im; } - inline const complex &expi(uint16_t a) const { - return lut[a]; + inline void operator*=(const complex &c) + { + T tre = re * c.re - im * c.im; + im = re * c.im + im * c.re; + re = tre; + } + inline void operator*=(const T &k) + { + re *= k; + im *= k; + } +}; + +template +complex operator+(const complex &a, const complex &b) +{ + return complex(a.re + b.re, a.im + b.im); +} + +template +complex operator*(const complex &a, const complex &b) +{ + return complex(a.re * b.re - a.im * b.im, a.re * b.im + a.im * b.re); +} + +template +complex operator*(const complex &a, const T &k) +{ + return complex(a.re * k, a.im * k); +} + +template +complex operator*(const T &k, const complex &a) +{ + return complex(k * a.re, k * a.im); +} + +template +T dotprod(const T *u, const T *v, int n) +{ + T acc = 0; + while (n--) + acc += (*u++) * (*v++); + return acc; +} + +template +inline T cnorm2(const complex &u) +{ + return u.re * u.re + u.im * u.im; +} + +template +T cnorm2(const complex *p, int n) +{ + T res = 0; + for (; n--; ++p) + res += cnorm2(*p); + return res; +} + +// Return conj(u)*v +template +inline complex conjprod(const complex &u, const complex &v) +{ + return complex(u.re * v.re + u.im * v.im, + u.re * v.im - u.im * v.re); +} + +// Return sum(conj(u[i])*v[i]) +template +complex conjprod(const complex *u, const complex *v, int n) +{ + complex acc = 0; + while (n--) + acc += conjprod(*u++, *v++); + return acc; +} + +// TBD Optimize with dedicated instructions +int hamming_weight(uint8_t x); +int hamming_weight(uint16_t x); +int hamming_weight(uint32_t x); +int hamming_weight(uint64_t x); +unsigned char parity(uint8_t x); +unsigned char parity(uint16_t x); +unsigned char parity(uint32_t x); +unsigned char parity(uint64_t x); +int log2i(uint64_t x); + +// Pre-computed sin/cos for 16-bit angles + +struct trig16 +{ + complex lut[65536]; // TBD static and shared + trig16() + { + for (int a = 0; a < 65536; ++a) + { + float af = a * 2 * M_PI / 65536; + lut[a].re = cosf(af); + lut[a].im = sinf(af); + } + } + inline const complex &expi(uint16_t a) const + { + return lut[a]; } // a must fit in a int32_t, otherwise behaviour is undefined - inline const complex &expi(float a) const { - return expi((uint16_t)(int16_t)(int32_t)a); + inline const complex &expi(float a) const + { + return expi((uint16_t)(int16_t)(int32_t)a); } - }; +}; -} // namespace +} // namespace leansdr -#endif // LEANSDR_MATH_H +#endif // LEANSDR_MATH_H diff --git a/plugins/channelrx/demoddatv/leansdr/rs.h b/plugins/channelrx/demoddatv/leansdr/rs.h index 32e5c0a93..7af6746e4 100644 --- a/plugins/channelrx/demoddatv/leansdr/rs.h +++ b/plugins/channelrx/demoddatv/leansdr/rs.h @@ -1,3 +1,19 @@ +// This file is part of LeanSDR Copyright (C) 2016-2018 . +// See the toplevel README for more information. +// +// This program 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. +// +// This program 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 this program. If not, see . + #ifndef LEANSDR_RS_H #define LEANSDR_RS_H @@ -28,16 +44,13 @@ namespace leansdr // Tp is a C++ integer type for representing P(X) (1 bit larger than Te). // ALPHA is a primitive element of GF(2^N). Usually "2"=[X] is chosen. -template +template struct gf2x_p { gf2x_p() { if (ALPHA != 2) - { - fail("gf2x_p::gf2x_p", "alpha!=2 not implemented"); - return; - } + fail("alpha!=2 not implemented"); // Precompute log and exp tables. Tp alpha_i = 1; for (Tp i = 0; i < (1 << N); ++i) @@ -45,20 +58,14 @@ struct gf2x_p lut_exp[i] = alpha_i; lut_exp[((1 << N) - 1) + i] = alpha_i; lut_log[alpha_i] = i; - alpha_i <<= 1; // Multiply by alpha=[X] i.e. increase degrees + alpha_i <<= 1; // Multiply by alpha=[X] i.e. increase degrees if (alpha_i & (1 << N)) - alpha_i ^= P; // Modulo P iteratively + alpha_i ^= P; // Modulo P iteratively } } static const Te alpha = ALPHA; - inline Te add(Te x, Te y) - { - return x ^ y; - } // Addition modulo 2 - inline Te sub(Te x, Te y) - { - return x ^ y; - } // Subtraction modulo 2 + inline Te add(Te x, Te y) { return x ^ y; } // Addition modulo 2 + inline Te sub(Te x, Te y) { return x ^ y; } // Subtraction modulo 2 inline Te mul(Te x, Te y) { if (!x || !y) @@ -77,16 +84,11 @@ struct gf2x_p // if ( ! x ) fail("inv"); return lut_exp[((1 << N) - 1) - lut_log[x]]; } - inline Te exp(Te x) - { - return lut_exp[x]; - } - inline Te log(Te x) - { - return lut_log[x]; - } -private: - Te lut_exp[(1 << N) * 2]; // Wrap to avoid indexing modulo 2^N-1 + inline Te exp(Te x) { return lut_exp[x]; } + inline Te log(Te x) { return lut_log[x]; } + + private: + Te lut_exp[(1 << N) * 2]; // Wrap to avoid indexing modulo 2^N-1 Te lut_log[1 << N]; }; @@ -98,14 +100,14 @@ struct rs_engine // p(X) = X^8 + X^4 + X^3 + X^2 + 1 gf2x_p gf; - u8 G[17]; // { G_16, ..., G_0 } + u8 G[17]; // { G_16, ..., G_0 } rs_engine() { // EN 300 421, section 4.4.2, Code Generator Polynomial // G(X) = (X-alpha^0)*...*(X-alpha^15) for (int i = 0; i <= 16; ++i) - G[i] = (i == 16) ? 1 : 0; // Init G=1 + G[i] = (i == 16) ? 1 : 0; // Init G=1 for (int d = 0; d < 16; ++d) { // Multiply by (X-alpha^d) @@ -115,7 +117,8 @@ struct rs_engine } #if DEBUG_RS fprintf(stderr, "RS generator:"); - for ( int i=0; i<=16; ++i ) fprintf(stderr, " %02x", G[i]); + for (int i = 0; i <= 16; ++i) + fprintf(stderr, " %02x", G[i]); fprintf(stderr, "\n"); #endif } @@ -162,12 +165,13 @@ struct rs_engine { // TBD Avoid copying u8 p[204]; - memcpy(p, msg, 204); // was 188 but causing underflow (PVS https://www.viva64.com/en/w/v512/) + memcpy(p, msg, 188); memset(p + 188, 0, 16); // p = msg*X^16 #if DEBUG_RS fprintf(stderr, "uncoded:"); - for ( int i=0; i<204; ++i ) fprintf(stderr, " %d", p[i]); + for (int i = 0; i < 204; ++i) + fprintf(stderr, " %d", p[i]); fprintf(stderr, "\n"); #endif // Compute remainder modulo G @@ -183,7 +187,8 @@ struct rs_engine } #if DEBUG_RS fprintf(stderr, "coded:"); - for ( int i=0; i<204; ++i ) fprintf(stderr, " %d", p[i]); + for (int i = 0; i < 204; ++i) + fprintf(stderr, " %d", p[i]); fprintf(stderr, "\n"); #endif memcpy(msg + 188, p + 188, 16); @@ -193,14 +198,13 @@ struct rs_engine // If pin[] is provided, errors will be fixed in the original // message too and syndromes will be updated. - bool correct(u8 synd[16], u8 pout[188], u8 pin[204] = NULL, int *bits_corrected = NULL) + bool correct(u8 synd[16], u8 pout[188], + u8 pin[204] = NULL, int *bits_corrected = NULL) { // Berlekamp - Massey // http://en.wikipedia.org/wiki/Berlekamp%E2%80%93Massey_algorithm#Code_sample - u8 C[16] = - { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; // Max degree is L - u8 B[16] = - { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + u8 C[16] = {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Max degree is L + u8 B[16] = {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; int L = 0; int m = 1; u8 b = 1; @@ -234,11 +238,13 @@ struct rs_engine // L is the number of errors // C of degree L is now the error locator polynomial (Lambda) #if DEBUG_RS - fprintf(stderr, "[L=%d C=",L); - for ( int i=0; i<16; ++i ) fprintf(stderr, " %d", C[i]); + fprintf(stderr, "[L=%d C=", L); + for (int i = 0; i < 16; ++i) + fprintf(stderr, " %d", C[i]); fprintf(stderr, "]\n"); fprintf(stderr, "[S="); - for ( int i=0; i<16; ++i ) fprintf(stderr, " %d", synd[i]); + for (int i = 0; i < 16; ++i) + fprintf(stderr, " %d", synd[i]); fprintf(stderr, "]\n"); #endif @@ -255,7 +261,8 @@ struct rs_engine omega[i + j] ^= gf.mul(synd[i], C[j]); #if DEBUG_RS fprintf(stderr, "omega="); - for ( int i=0; i<16; ++i ) fprintf(stderr, " %d", omega[i]); + for (int i = 0; i < 16; ++i) + fprintf(stderr, " %d", omega[i]); fprintf(stderr, "\n"); #endif @@ -265,7 +272,8 @@ struct rs_engine Cprime[i] = (i & 1) ? 0 : C[i + 1]; #if DEBUG_RS fprintf(stderr, "Cprime="); - for ( int i=0; i<15; ++i ) fprintf(stderr, " %d", Cprime[i]); + for (int i = 0; i < 15; ++i) + fprintf(stderr, " %d", Cprime[i]); fprintf(stderr, "\n"); #endif @@ -274,15 +282,15 @@ struct rs_engine int roots_found = 0; for (int i = 0; i < 255; ++i) { - u8 r = gf.exp(i); // Candidate root alpha^0..alpha^254 + u8 r = gf.exp(i); // Candidate root alpha^0..alpha^254 u8 v = eval_poly(C, L, r); if (!v) { // r is a root X_k^-1 of the error locator polynomial. u8 xk = gf.inv(r); - int loc = (255 - i) % 255; // == log(xk) + int loc = (255 - i) % 255; // == log(xk) #if DEBUG_RS - fprintf(stderr, "found root=%d, inv=%d, loc=%d\n", r, xk, loc); + fprintf(stderr, "found root=%d, inv=%d, loc=%d\n", r, xk, loc); #endif if (loc < 204) { @@ -308,9 +316,8 @@ struct rs_engine else return false; } - }; -} // namespace +} // namespace leansdr -#endif // LEANSDR_RS_H +#endif // LEANSDR_RS_H diff --git a/plugins/channelrx/demoddatv/leansdr/sdr.cpp b/plugins/channelrx/demoddatv/leansdr/sdr.cpp new file mode 100644 index 000000000..3cb04c108 --- /dev/null +++ b/plugins/channelrx/demoddatv/leansdr/sdr.cpp @@ -0,0 +1,98 @@ +#include "sdr.h" + +namespace leansdr +{ + +const char *cstln_base::names[] = + { + [BPSK] = "BPSK", + [QPSK] = "QPSK", + [PSK8] = "8PSK", + [APSK16] = "16APSK", + [APSK32] = "32APSK", + [APSK64E] = "64APSKe", + [QAM16] = "16QAM", + [QAM64] = "64QAM", + [QAM256] = "256QAM" + }; + + +void softsymb_harden(llr_ss *ss) +{ + for (int b = 0; b < 8; ++b) + ss->bits[b] = (ss->bits[b] < 0) ? -127 : 127; +} + +void softsymb_harden(hard_ss *ss) +{ + (void)ss; // NOP +} + +void softsymb_harden(eucl_ss *ss) +{ + for (int s = 0; s < ss->MAX_SYMBOLS; ++s) + ss->dists2[s] = (s == ss->nearest) ? 0 : 1; +} + + +uint8_t softsymb_to_dump(const llr_ss &ss, int bit) +{ + return 128 - ss.bits[bit]; +} + +uint8_t softsymb_to_dump(const hard_ss &ss, int bit) +{ + return ((ss >> bit) & 1) ? 255 : 0; +} + +uint8_t softsymb_to_dump(const eucl_ss &ss, int bit) +{ + (void)bit; + return ss.dists2[ss.nearest] >> 8; +} + + +void to_softsymb(const full_ss *fss, hard_ss *ss) +{ + *ss = fss->nearest; +} + +void to_softsymb(const full_ss *fss, eucl_ss *ss) +{ + for (int s = 0; s < ss->MAX_SYMBOLS; ++s) + ss->dists2[s] = fss->dists2[s]; + + uint16_t best = 65535, best2 = 65535; + + for (int s = 0; s < ss->MAX_SYMBOLS; ++s) + { + if (fss->dists2[s] < best) + { + best2 = best; + best = fss->dists2[s]; + } + else if (fss->dists2[s] < best2) + { + best2 = fss->dists2[s]; + } + } + + ss->discr2 = best2 - best; + ss->nearest = fss->nearest; +} + +void to_softsymb(const full_ss *fss, llr_ss *ss) +{ + for (int b = 0; b < 8; ++b) + { + float v = (1.0f - fss->p[b]) / (fss->p[b] + 1e-6); + int r = logf(v) * 5; // TBD Optimal scaling vs saturation ? + if (r < -127) + r = -127; + if (r > 127) + r = 127; + ss->bits[b] = r; + } +} + +} // leansdr \ No newline at end of file diff --git a/plugins/channelrx/demoddatv/leansdr/sdr.h b/plugins/channelrx/demoddatv/leansdr/sdr.h index a8da24be9..1fc26d594 100644 --- a/plugins/channelrx/demoddatv/leansdr/sdr.h +++ b/plugins/channelrx/demoddatv/leansdr/sdr.h @@ -1,9 +1,24 @@ +// This file is part of LeanSDR Copyright (C) 2016-2019 . +// See the toplevel README for more information. +// +// This program 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. +// +// This program 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 this program. If not, see . + #ifndef LEANSDR_SDR_H #define LEANSDR_SDR_H -#include "leansdr/math.h" #include "leansdr/dsp.h" -#include "leansdr/incrementalarray.h" +#include "leansdr/math.h" namespace leansdr { @@ -24,35 +39,42 @@ typedef complex cf32; // AUTO-NOTCH FILTER -// Periodically detects the [n__slots] strongest peaks with a FFT, +// Periodically detects the [nslots] strongest peaks with a FFT, // removes them with a first-order filter. -template -struct auto_notch: runnable +template +struct auto_notch : runnable { int decimation; float k; - auto_notch(scheduler *sch, pipebuf > &_in, pipebuf > &_out, int _n__slots, T _agc_rms_setpoint) : - runnable(sch, "auto_notch"), - decimation(1024 * 4096), - k(0.002), // k(0.01) - fft(4096), - in(_in), - out(_out, fft.n), - n__slots(_n__slots), - __slots(new slot[n__slots]), - phase(0), - gain(1), - agc_rms_setpoint(_agc_rms_setpoint) + auto_notch(scheduler *sch, + pipebuf> &_in, + pipebuf> &_out, + int _nslots, + T _agc_rms_setpoint) : runnable(sch, "auto_notch"), + decimation(1024 * 4096), + k(0.002), // k(0.01) + fft(4096), + in(_in), + out(_out, fft.n), + nslots(_nslots), + phase(0), + gain(1), + agc_rms_setpoint(_agc_rms_setpoint) { - for (int s = 0; s < n__slots; ++s) + __slots = new slot[nslots]; + + for (int s = 0; s < nslots; ++s) { __slots[s].i = -1; - __slots[s].expj = new complex [fft.n]; + __slots[s].expj = new complex[fft.n]; } - m_data.allocate(fft.n); - m_amp.allocate(fft.n); + } + + ~auto_notch() + { + delete[] __slots; } void run() @@ -60,11 +82,13 @@ struct auto_notch: runnable while (in.readable() >= fft.n && out.writable() >= fft.n) { phase += fft.n; + if (phase >= decimation) { phase -= decimation; detect(); } + process(); in.read(fft.n); out.written(fft.n); @@ -74,15 +98,14 @@ struct auto_notch: runnable void detect() { complex *pin = in.rd(); - //complex data[fft.n]; - complex *data = m_data.m_array; + complex *data = new complex[fft.n]; float m0 = 0, m2 = 0; - for (unsigned int i = 0; i < fft.n; ++i) + for (int i = 0; i < fft.n; ++i) { data[i].re = pin[i].re; data[i].im = pin[i].im; - m2 += (float) pin[i].re * pin[i].re + (float) pin[i].im * pin[i].im; + m2 += (float)pin[i].re * pin[i].re + (float)pin[i].im * pin[i].im; if (gen_abs(pin[i].re) > m0) m0 = gen_abs(pin[i].re); if (gen_abs(pin[i].im) > m0) @@ -99,79 +122,85 @@ struct auto_notch: runnable } fft.inplace(data, true); - //float amp[fft.n]; - float *amp = m_amp.m_array; + float *amp = new float[fft.n]; - for (unsigned int i = 0; i < fft.n; ++i) { + for (int i = 0; i < fft.n; ++i) amp[i] = hypotf(data[i].re, data[i].im); - } - for (slot *s = __slots; s < __slots + n__slots; ++s) + for (slot *s = __slots; s < __slots + nslots; ++s) { int iamax = 0; - for (unsigned int i = 0; i < fft.n; ++i) - { - if (amp[i] > amp[iamax]) { + + for (int i = 0; i < fft.n; ++i) + if (amp[i] > amp[iamax]) iamax = i; - } - } + if (iamax != s->i) { if (sch->debug) - fprintf(stderr, "%s: slot %d new peak %d -> %d\n", name, - (int) (s - __slots), s->i, iamax); + fprintf(stderr, "%s: slot %d new peak %d -> %d\n", name, (int)(s - __slots), s->i, iamax); + s->i = iamax; s->estim.re = 0; s->estim.im = 0; s->estt = 0; - for (unsigned int i = 0; i < fft.n; ++i) + + for (int i = 0; i < fft.n; ++i) { float a = 2 * M_PI * s->i * i / fft.n; s->expj[i].re = cosf(a); s->expj[i].im = sinf(a); } } + amp[iamax] = 0; - if (iamax - 1 >= 0) { + + if (iamax - 1 >= 0) amp[iamax - 1] = 0; - } - if (iamax + 1 < (int) fft.n) { + + if (iamax + 1 < fft.n) amp[iamax + 1] = 0; - } } + + delete[] amp; + delete[] data; } void process() { complex *pin = in.rd(), *pend = pin + fft.n, *pout = out.wr(); - for (slot *s = __slots; s < __slots + n__slots; ++s) + + for (slot *s = __slots; s < __slots + nslots; ++s) s->ej = s->expj; + for (; pin < pend; ++pin, ++pout) { complex out = *pin; - // TODO Optimize for n__slots==1 ? - for (slot *s = __slots; s < __slots + n__slots; ++s->ej, ++s) + // TODO Optimize for nslots==1 ? + + for (slot *s = __slots; s < __slots + nslots; ++s->ej, ++s) { complex bb(pin->re * s->ej->re + pin->im * s->ej->im, - -pin->re * s->ej->im + pin->im * s->ej->re); + -pin->re * s->ej->im + pin->im * s->ej->re); s->estim.re = bb.re * k + s->estim.re * (1 - k); s->estim.im = bb.im * k + s->estim.im * (1 - k); complex sub( - s->estim.re * s->ej->re - s->estim.im * s->ej->im, - s->estim.re * s->ej->im + s->estim.im * s->ej->re); + s->estim.re * s->ej->re - s->estim.im * s->ej->im, + s->estim.re * s->ej->im + s->estim.im * s->ej->re); out.re -= sub.re; out.im -= sub.im; } + pout->re = gain * out.re; pout->im = gain * out.im; } } -private: + private: cfft_engine fft; - pipereader > in; - pipewriter > out; - int n__slots; + pipereader> in; + pipewriter> out; + int nslots; struct slot { @@ -179,32 +208,30 @@ private: complex estim; complex *expj, *ej; int estt; - }*__slots; + }; + slot *__slots; int phase; float gain; T agc_rms_setpoint; - IncrementalArray > m_data; - IncrementalArray m_amp; }; // SIGNAL STRENGTH ESTIMATOR // Outputs RMS values. -template -struct ss_estimator: runnable +template +struct ss_estimator : runnable { - unsigned long window_size; // Samples per estimation + unsigned long window_size; // Samples per estimation unsigned long decimation; // Output rate - ss_estimator(scheduler *sch, pipebuf > &_in, pipebuf &_out) : - runnable(sch, "SS estimator"), - window_size(1024), - decimation(1024), - in(_in), - out(_out), - phase(0) + ss_estimator(scheduler *sch, pipebuf> &_in, pipebuf &_out) : runnable(sch, "SS estimator"), + window_size(1024), + decimation(1024), + in(_in), + out(_out), + phase(0) { } @@ -213,58 +240,65 @@ struct ss_estimator: runnable while (in.readable() >= window_size && out.writable() >= 1) { phase += window_size; + if (phase >= decimation) { phase -= decimation; complex *p = in.rd(), *pend = p + window_size; float s = 0; + for (; p < pend; ++p) - s += (float) p->re * p->re + (float) p->im * p->im; + s += (float)p->re * p->re + (float)p->im * p->im; + out.write(sqrtf(s / window_size)); } in.read(window_size); } } -private: - pipereader > in; + private: + pipereader> in; pipewriter out; unsigned long phase; }; -template -struct ss_amp_estimator: runnable +template +struct ss_amp_estimator : runnable { - unsigned long window_size; // Samples per estimation + unsigned long window_size; // Samples per estimation unsigned long decimation; // Output rate - ss_amp_estimator(scheduler *sch, pipebuf > &_in, pipebuf &_out_ss, pipebuf &_out_ampmin, pipebuf &_out_ampmax) : - runnable(sch, "SS estimator"), - window_size(1024), - decimation(1024), - in(_in), - out_ss(_out_ss), - out_ampmin(_out_ampmin), - out_ampmax(_out_ampmax), - phase(0) + ss_amp_estimator(scheduler *sch, + pipebuf> &_in, + pipebuf &_out_ss, + pipebuf &_out_ampmin, + pipebuf &_out_ampmax) : runnable(sch, "SS estimator"), + window_size(1024), + decimation(1024), + in(_in), + out_ss(_out_ss), + out_ampmin(_out_ampmin), + out_ampmax(_out_ampmax), + phase(0) { } void run() { - while (in.readable() >= window_size && out_ss.writable() >= 1 - && out_ampmin.writable() >= 1 && out_ampmax.writable() >= 1) + while (in.readable() >= window_size && out_ss.writable() >= 1 && out_ampmin.writable() >= 1 && out_ampmax.writable() >= 1) { phase += window_size; + if (phase >= decimation) { phase -= decimation; complex *p = in.rd(), *pend = p + window_size; float s2 = 0; float amin = 1e38, amax = 0; + for (; p < pend; ++p) { - float mag2 = (float) p->re * p->re + (float) p->im * p->im; + float mag2 = (float)p->re * p->re + (float)p->im * p->im; s2 += mag2; float mag = sqrtf(mag2); if (mag < amin) @@ -272,43 +306,46 @@ struct ss_amp_estimator: runnable if (mag > amax) amax = mag; } + out_ss.write(sqrtf(s2 / window_size)); out_ampmin.write(amin); out_ampmax.write(amax); } + in.read(window_size); } } -private: - pipereader > in; + private: + pipereader> in; pipewriter out_ss, out_ampmin, out_ampmax; unsigned long phase; }; // AGC -template -struct simple_agc: runnable +template +struct simple_agc : runnable { - float out_rms; // Desired RMS output power - float bw; // Bandwidth - float estimated; // Input power + float out_rms; // Desired RMS output power + float bw; // Bandwidth + float estimated; // Input power + static const int chunk_size = 128; - simple_agc(scheduler *sch, pipebuf > &_in, pipebuf > &_out) : - runnable(sch, "AGC"), - out_rms(1), - bw(0.001), - estimated(0), - in(_in), - out(_out) + simple_agc(scheduler *sch, + pipebuf> &_in, + pipebuf> &_out) : runnable(sch, "AGC"), + out_rms(1), + bw(0.001), + estimated(0), + in(_in), + out(_out, chunk_size) { } -private: - pipereader > in; - pipewriter > out; - static const int chunk_size = 128; + private: + pipereader> in; + pipewriter> out; void run() { @@ -316,20 +353,27 @@ private: { complex *pin = in.rd(), *pend = pin + chunk_size; float amp2 = 0; + for (; pin < pend; ++pin) amp2 += pin->re * pin->re + pin->im * pin->im; + amp2 /= chunk_size; + if (!estimated) estimated = amp2; + estimated = estimated * (1 - bw) + amp2 * bw; float gain = estimated ? out_rms / sqrtf(estimated) : 0; pin = in.rd(); complex *pout = out.wr(); + float bwcomp = 1 - bw; + for (; pin < pend; ++pin, ++pout) { pout->re = pin->re * gain; pout->im = pin->im * gain; } + in.read(chunk_size); out.written(chunk_size); } @@ -337,7 +381,7 @@ private: }; // simple_agc -typedef uint16_t u_angle; // [0,2PI[ in 65536 steps +typedef uint16_t u_angle; // [0,2PI[ in 65536 steps typedef int16_t s_angle; // [-PI,PI[ in 65536 steps // GENERIC CONSTELLATION DECODING BY LOOK-UP TABLE. @@ -347,8 +391,8 @@ typedef int16_t s_angle; // [-PI,PI[ in 65536 steps // Up to 256 symbols. struct softsymbol -{ - int16_t cost; // For Viterbi with TBM=int16_t +{ // TBD obsolete + int16_t cost; // For Viterbi with TBM=int16_t uint8_t symbol; }; @@ -357,62 +401,130 @@ struct softsymbol //const float cstln_amp = 90; // Best for QPSK //const float cstln_amp = 64; // Best for BPSK //const float cstln_amp = 75; // Best for BPSK at 45° -const float cstln_amp = 75; // Trade-off +const float cstln_amp = 75; // Trade-off -template -struct cstln_lut +// A struct that temporarily holds all the info we precompute for the LUT. +struct full_ss { - complex *symbols; - int nsymbols; - int nrotations; + uint8_t nearest; // Index of nearest in constellation + uint16_t dists2[256]; // Squared distances + float p[8]; // 0..1 probability of bits being 1 +}; +// Options for soft-symbols. +// These functions are overloaded to keep cstln_lut generic: +// to_softsymb(const full_ss *fss, SOFTSYMB *ss) +// softsymb_harden(SOFTSYMB *ss) { +// softsymb_to_dump(const SOFTSYMB &ss, int bit) To grey 0..255 +// For LUT initialization only. Performance is not critical. + +// Hard decision soft-symbols. +// Value is the symbol index, 0..255. +typedef uint8_t hard_ss; +void to_softsymb(const full_ss *fss, hard_ss *ss); +void softsymb_harden(hard_ss *ss); +uint8_t softsymb_to_dump(const hard_ss &ss, int bit); + +// Euclidian QPSK soft-symbols. +// Additive metric suitable for Viterbi. +// Backward-compatible with simplified Viterbi (TBD remove) +struct eucl_ss +{ + static const int MAX_SYMBOLS = 4; + uint16_t dists2[MAX_SYMBOLS]; + uint16_t discr2; // 2nd_nearest - nearest + uint8_t nearest; +}; + +void to_softsymb(const full_ss *fss, eucl_ss *ss); +void softsymb_harden(eucl_ss *ss); +uint8_t softsymb_to_dump(const eucl_ss &ss, int bit); + +// Log-Likelihood Ratios soft-symbols +typedef int8_t llr_t; // log(p(0)/p(1)), clip -127=1 +127=0 + +inline bool llr_harden(llr_t v) +{ + return v & 128; +} + +struct llr_ss +{ + llr_t bits[8]; // Up to 8 bit considered independent +}; + +void to_softsymb(const full_ss *fss, llr_ss *ss); +void softsymb_harden(llr_ss *ss); +uint8_t softsymb_to_dump(const llr_ss &ss, int bit); + +struct cstln_base +{ enum predef { - BPSK, // DVB-S2 (and DVB-S variant) - QPSK, // DVB-S + BPSK, // DVB-S2 (and DVB-S variant) + QPSK, // DVB-S PSK8, APSK16, - APSK32, // DVB-S2 - APSK64E, // DVB-S2X + APSK32, // DVB-S2 + APSK64E, // DVB-S2X QAM16, QAM64, - QAM256 // For experimentation only + QAM256, // For experimentation only + COUNT }; - cstln_lut(predef type, float gamma1 = 1, float gamma2 = 1, float gamma3 = 1) + static const char *names[]; + float amp_max; // Max amplitude. 1 for PSK, 0 if not applicable. + complex *symbols; + int nsymbols; + int nrotations; +}; +// cstln_base + +template +struct cstln_lut : cstln_base +{ + cstln_lut(cstln_base::predef type, + float mer = 10, + float gamma1 = 0, + float gamma2 = 0, + float gamma3 = 0) { switch (type) { case BPSK: + amp_max = 1; nrotations = 2; nsymbols = 2; - symbols = new complex [nsymbols]; -#if 0 // BPSK at 0° + symbols = new complex[nsymbols]; +#if 0 // BPSK at 0° symbols[0] = polar(1, 2, 0); symbols[1] = polar(1, 2, 1); -#else // BPSK at 45° +#else // BPSK at 45° symbols[0] = polar(1, 8, 1); symbols[1] = polar(1, 8, 5); #endif - make_lut_from_symbols(); + make_lut_from_symbols(mer); break; case QPSK: + amp_max = 1; // EN 300 421, section 4.5 Baseband shaping and modulation // EN 302 307, section 5.4.1 nrotations = 4; nsymbols = 4; - symbols = new complex [nsymbols]; + symbols = new complex[nsymbols]; symbols[0] = polar(1, 4, 0.5); symbols[1] = polar(1, 4, 3.5); symbols[2] = polar(1, 4, 1.5); symbols[3] = polar(1, 4, 2.5); - make_lut_from_symbols(); + make_lut_from_symbols(mer); break; case PSK8: + amp_max = 1; // EN 302 307, section 5.4.2 nrotations = 8; nsymbols = 8; - symbols = new complex [nsymbols]; + symbols = new complex[nsymbols]; symbols[0] = polar(1, 8, 1); symbols[1] = polar(1, 8, 0); symbols[2] = polar(1, 8, 4); @@ -421,16 +533,20 @@ struct cstln_lut symbols[5] = polar(1, 8, 7); symbols[6] = polar(1, 8, 3); symbols[7] = polar(1, 8, 6); - make_lut_from_symbols(); + make_lut_from_symbols(mer); break; case APSK16: { + // Default gamma for non-DVB-S2 applications. + if (!gamma1) + gamma1 = 2.57; // EN 302 307, section 5.4.3 float r1 = sqrtf(4 / (1 + 3 * gamma1 * gamma1)); float r2 = gamma1 * r1; + amp_max = r2; nrotations = 4; nsymbols = 16; - symbols = new complex [nsymbols]; + symbols = new complex[nsymbols]; symbols[0] = polar(r2, 12, 1.5); symbols[1] = polar(r2, 12, 10.5); symbols[2] = polar(r2, 12, 4.5); @@ -447,19 +563,25 @@ struct cstln_lut symbols[13] = polar(r1, 4, 3.5); symbols[14] = polar(r1, 4, 1.5); symbols[15] = polar(r1, 4, 2.5); - make_lut_from_symbols(); + make_lut_from_symbols(mer); break; } case APSK32: { + // Default gammas for non-DVB-S2 applications. + if (!gamma1) + gamma1 = 2.53; + if (!gamma2) + gamma2 = 4.30; // EN 302 307, section 5.4.3 float r1 = sqrtf( - 8 / (1 + 3 * gamma1 * gamma1 + 4 * gamma2 * gamma2)); + 8 / (1 + 3 * gamma1 * gamma1 + 4 * gamma2 * gamma2)); float r2 = gamma1 * r1; float r3 = gamma2 * r1; + amp_max = r3; nrotations = 4; nsymbols = 32; - symbols = new complex [nsymbols]; + symbols = new complex[nsymbols]; symbols[0] = polar(r2, 12, 1.5); symbols[1] = polar(r2, 12, 2.5); symbols[2] = polar(r2, 12, 10.5); @@ -492,22 +614,28 @@ struct cstln_lut symbols[29] = polar(r3, 16, 5); symbols[30] = polar(r3, 16, 8); symbols[31] = polar(r3, 16, 10); - make_lut_from_symbols(); + make_lut_from_symbols(mer); break; } case APSK64E: { + // Default gammas for non-DVB-S2 applications. + if (!gamma1) + gamma1 = 2.4; + if (!gamma2) + gamma2 = 4.3; + if (!gamma3) + gamma3 = 7.0; // EN 302 307-2, section 5.4.5, Table 13e float r1 = sqrtf( - 64 - / (4 + 12 * gamma1 * gamma1 + 20 * gamma2 * gamma2 - + 28 * gamma3 * gamma3)); + 64 / (4 + 12 * gamma1 * gamma1 + 20 * gamma2 * gamma2 + 28 * gamma3 * gamma3)); float r2 = gamma1 * r1; float r3 = gamma2 * r1; float r4 = gamma3 * r1; + amp_max = r4; nrotations = 4; nsymbols = 64; - symbols = new complex [nsymbols]; + symbols = new complex[nsymbols]; polar2(0, r4, 1.0 / 4, 7.0 / 4, 3.0 / 4, 5.0 / 4); polar2(4, r4, 13.0 / 28, 43.0 / 28, 15.0 / 28, 41.0 / 28); polar2(8, r4, 1.0 / 28, 55.0 / 28, 27.0 / 28, 29.0 / 28); @@ -524,28 +652,33 @@ struct cstln_lut polar2(52, r3, 7.0 / 20, 33.0 / 20, 13.0 / 20, 27.0 / 20); polar2(56, r3, 3.0 / 20, 37.0 / 20, 17.0 / 20, 23.0 / 20); polar2(60, r2, 1.0 / 4, 7.0 / 4, 3.0 / 4, 5.0 / 4); - make_lut_from_symbols(); + make_lut_from_symbols(mer); break; } case QAM16: + amp_max = 0; make_qam(16); break; case QAM64: + amp_max = 1; make_qam(64); break; case QAM256: + amp_max = 1; make_qam(256); break; default: - fail("cstln_lut::cstln_lut", "Constellation not implemented"); - return; + fail("Constellation not implemented"); } } + struct result { - struct softsymbol ss; + SOFTSYMB ss; s_angle phase_error; + uint8_t symbol; // Nearest symbol, useful for C&T recovery }; + inline result *lookup(float I, float Q) { // Handling of overflows beyond the lookup table: @@ -563,33 +696,33 @@ struct cstln_lut Q *= 0.5; } #endif - return &lut[(u8) (s8) I][(u8) (s8) Q]; + return &lut[(u8)(s8)I][(u8)(s8)Q]; } inline result *lookup(int I, int Q) { // Ignore wrapping modulo 256 - return &lut[(u8) I][(u8) Q]; + return &lut[(u8)I][(u8)Q]; } -private: + private: complex polar(float r, int n, float i) { float a = i * 2 * M_PI / n; return complex(r * cosf(a) * cstln_amp, - r * sinf(a) * cstln_amp); + r * sinf(a) * cstln_amp); } // Helper function for some constellation tables void polar2(int i, float r, float a0, float a1, float a2, float a3) { - float a[] = - { a0, a1, a2, a3 }; + float a[] = {a0, a1, a2, a3}; + for (int j = 0; j < 4; ++j) { float phi = a[j] * M_PI; symbols[i + j] = complex(r * cosf(phi) * cstln_amp, - r * sinf(phi) * cstln_amp); + r * sinf(phi) * cstln_amp); } } @@ -597,129 +730,169 @@ private: { nrotations = 4; nsymbols = n; - symbols = new complex [nsymbols]; + symbols = new complex[nsymbols]; int m = sqrtl(n); float scale; + { // Average power in first quadrant with unit grid int q = m / 2; - float avgpower = 2 - * (q * 0.25 + (q - 1) * (q / 2) - + (q - 1) * q * (2 * q - 1) / 6) / q; + float avgpower = 2 * (q * 0.25 + (q - 1) * q / 2 + (q - 1) * q * (2 * q - 1) / 6) / q; scale = 1.0 / sqrtf(avgpower); } // Arbitrary mapping + int s = 0; + for (int x = 0; x < m; ++x) + { for (int y = 0; y < m; ++y) { - float I = x - (float) (m - 1) / 2; - float Q = y - (float) (m - 1) / 2; + float I = x - (float)(m - 1) / 2; + float Q = y - (float)(m - 1) / 2; symbols[s].re = I * scale * cstln_amp; symbols[s].im = Q * scale * cstln_amp; ++s; } - make_lut_from_symbols(); + } + + make_lut_from_symbols(20); // TBD } result lut[R][R]; - void make_lut_from_symbols() + void make_lut_from_symbols(float mer) { + // Note: Excessively low values of MER will break 16APSK and 32APSK. + float sigma = cstln_amp * exp10f(-mer / 20); + + // Precomputed values. + // Shared scope so that we don't have to reset dists2[nsymbols..] to -1. + struct full_ss fss; + + for (int s = 0; s < 256; ++s) + fss.dists2[s] = -1; + for (int I = -R / 2; I < R / 2; ++I) + { for (int Q = -R / 2; Q < R / 2; ++Q) { - result *pr = &lut[I & (R - 1)][Q & (R - 1)]; - // Simplified metric: - // Distance to nearest minus distance to second-nearest. - // Null at edge of decision regions - // => Suitable for Viterbi with partial metrics. - uint8_t nearest = 0; - int32_t cost = R * R * 2, cost2 = R * R * 2; + // Nearest symbol + fss.nearest = 0; + fss.dists2[0] = 65535; + // Conditional probabilities: + // Sum likelyhoods from all candidate symbols. + // + // P(TX[b]==B | RX==IQ) = + // sum(S=0..nsymbols-1, P(TX[b]==B | RX==IQ && TXs==S)) + // + // P(TX[b] == B | RX==IQ && TX==S) = + // P(TX[b]==B && RX==IQ && TX==S) / P(RX==IQ && TX==S) + float probs[8][2]; + memset(probs, 0, sizeof(probs)); + for (int s = 0; s < nsymbols; ++s) { - int32_t d2 = (I - symbols[s].re) * (I - symbols[s].re) - + (Q - symbols[s].im) * (Q - symbols[s].im); - if (d2 < cost) + float d2 = ((I - symbols[s].re) * (I - symbols[s].re) + (Q - symbols[s].im) * (Q - symbols[s].im)); + + if (d2 < fss.dists2[fss.nearest]) + fss.nearest = s; + + fss.dists2[s] = d2; + float p = expf(-d2 / (2 * sigma * sigma)) / (sqrtf(2 * M_PI) * sigma); + + for (int bit = 0; bit < 8; ++bit) { - cost2 = cost; - cost = d2; - nearest = s; - } - else if (d2 < cost2) - { - cost2 = d2; + probs[bit][(s >> bit) & 1] += p; } } - if (cost > 32767) - cost = 32767; - if (cost2 > 32767) - cost2 = 32767; - pr->ss.cost = cost - cost2; - pr->ss.symbol = nearest; - float ph_symbol = atan2f(symbols[pr->ss.symbol].im, - symbols[pr->ss.symbol].re); + + // Normalize + for (int b = 0; b < 8; ++b) + { + float p = probs[b][1] / (probs[b][0] + probs[b][1]); + // Avoid trouble when sigma is unrealistically low. + if (!isnormal(p)) + p = 0; + fss.p[b] = p; + } + + result *pr = &lut[I & (R - 1)][Q & (R - 1)]; + to_softsymb(&fss, &pr->ss); + // Always record nearest symbol and phase error for C&T. + pr->symbol = fss.nearest; + float ph_symbol = atan2f(symbols[pr->symbol].im, + symbols[pr->symbol].re); float ph_err = atan2f(Q, I) - ph_symbol; - pr->phase_error = (s32) (ph_err * 65536 / (2 * M_PI)); // Mod 65536 + pr->phase_error = (int32_t)(ph_err * 65536 / (2 * M_PI)); // Mod 65536 } + } + } + + public: + void dump(FILE *f) + { + int bps = log2(nsymbols); + fprintf(f, "P5\n%d %d\n255\n", R, R * (bps + 1)); + + for (int bit = 0; bit < bps + 1; ++bit) + { + for (int Q = R / 2 - 1; Q >= -R / 2; --Q) + { + for (int I = -R / 2; I < R / 2; ++I) + { + result *pr = &lut[I & (R - 1)][Q & (R - 1)]; + uint8_t v; + if (bit < bps) + v = softsymb_to_dump(pr->ss, bit); + else + v = 128 + pr->phase_error / 64; + // Highlight the constellation symbols. + for (int s = 0; s < nsymbols; ++s) + { + if (symbols[s].re == I && symbols[s].im == Q) + v ^= 128; + } + + fputc(v, f); + } + } + } } -public: // Convert soft metric to Hamming distance void harden() { for (int i = 0; i < R; ++i) + { for (int q = 0; q < R; ++q) - { - softsymbol *ss = &lut[i][q].ss; - if (ss->cost < 0) - ss->cost = -1; - if (ss->cost > 0) - ss->cost = 1; - } // for I,Q + softsymb_harden(&lut[i][q].ss); + } } - }; // cstln_lut -//static const char *cstln_names[] = -//{ [cstln_lut<256>::BPSK] = "BPSK", -// [cstln_lut<256>::QPSK] = "QPSK", -// [cstln_lut<256>::PSK8] = "8PSK", -// [cstln_lut<256>::APSK16] = "16APSK", -// [cstln_lut<256>::APSK32] = "32APSK", -// [cstln_lut<256>::APSK64E] = "64APSKe", -// [cstln_lut<256>::QAM16] = "16QAM", -// [cstln_lut<256>::QAM64] = "64QAM", -// [cstln_lut<256>::QAM256] = "256QAM" -//}; - // SAMPLER INTERFACE FOR CSTLN_RECEIVER -template +template struct sampler_interface { - virtual complex interp(const complex *pin, float mu, float phase) = 0; - - virtual void update_freq(float freqw) - { - (void) freqw; - } // 65536 = 1 Hz - - virtual int readahead() - { - return 0; - } - virtual ~sampler_interface() { } + virtual complex interp(const complex *pin, float mu, float phase) = 0; + virtual void update_freq(float freqw, int period = 1) + { + (void) freqw; + (void) period; + } // 65536 = 1 Hz + virtual int readahead() = 0; }; // NEAREST-SAMPLE SAMPLER FOR CSTLN_RECEIVER // Suitable for bandpass-filtered, oversampled signals only -template -struct nearest_sampler: sampler_interface +template +struct nearest_sampler : sampler_interface { int readahead() { @@ -732,15 +905,15 @@ struct nearest_sampler: sampler_interface return pin[0] * trig.expi(-phase); } -private: + private: trig16 trig; }; // nearest_sampler // LINEAR SAMPLER FOR CSTLN_RECEIVER -template -struct linear_sampler: sampler_interface +template +struct linear_sampler : sampler_interface { int readahead() { @@ -756,12 +929,13 @@ struct linear_sampler: sampler_interface return s0 * (1 - mu) + s1 * mu; } - void update_freq(float _freqw) + void update_freq(float _freqw, int period = 1) { + (void) period; freqw = _freqw; } -private: + private: trig16 trig; float freqw; }; @@ -769,13 +943,16 @@ private: // FIR SAMPLER FOR CSTLN_RECEIVER -template -struct fir_sampler: sampler_interface +template +struct fir_sampler : sampler_interface { - fir_sampler(int _ncoeffs, Tc *_coeffs, int _subsampling = 1) : - ncoeffs(_ncoeffs), coeffs(_coeffs), subsampling(_subsampling), shifted_coeffs( - new complex [ncoeffs]), update_freq_phase(0) + fir_sampler(int _ncoeffs, Tc *_coeffs, int _subsampling = 1) : ncoeffs(_ncoeffs), + coeffs(_coeffs), + subsampling(_subsampling), + shifted_coeffs(new complex[ncoeffs]), + update_freq_phase(0) { + do_update_freq(0); // In case application never calls update_freq() } int readahead() @@ -787,8 +964,9 @@ struct fir_sampler: sampler_interface { // Apply FIR filter with subsampling complex acc(0, 0); - complex *pc = shifted_coeffs + (int) ((1 - mu) * subsampling); + complex *pc = shifted_coeffs + (int)((1 - mu) * subsampling); complex *pcend = shifted_coeffs + ncoeffs; + if (subsampling == 1) { // Special case for heavily oversampled signals, @@ -804,15 +982,17 @@ struct fir_sampler: sampler_interface for (; pc < pcend; pc += subsampling, ++pin) acc += (*pc) * (*pin); } + // Derotate return trig.expi(-phase) * acc; } - void update_freq(float freqw) + void update_freq(float freqw, int period) { // Throttling: Update one coeff per 16 processed samples, // to keep the overhead of freq tracking below about 10%. - update_freq_phase -= 128; // chunk_size of cstln_receiver + update_freq_phase -= period; + if (update_freq_phase <= 0) { update_freq_phase = ncoeffs * 16; @@ -820,7 +1000,7 @@ struct fir_sampler: sampler_interface } } -private: + private: void do_update_freq(float freqw) { float f = freqw / subsampling; @@ -842,44 +1022,42 @@ private: // Linear interpolation: good enough for 1.2 samples/symbol, // but higher oversampling is recommended. -template -struct cstln_receiver: runnable +template +struct cstln_receiver : runnable { sampler_interface *sampler; - cstln_lut<256> *cstln; - unsigned long meas_decimation; // Measurement rate - float omega, min_omega, max_omega; // Samples per symbol - float freqw, min_freqw, max_freqw; // Freq offs (65536 = 1 Hz) + cstln_lut *cstln; + unsigned long meas_decimation; // Measurement rate + float omega, min_omega, max_omega; // Samples per symbol + float freqw, min_freqw, max_freqw; // Freq offs (65536 = 1 Hz) float pll_adjustment; - bool allow_drift; // Follow carrier beyond safe limits + bool allow_drift; // Follow carrier beyond safe limits static const unsigned int chunk_size = 128; float kest; - cstln_receiver( - scheduler *sch, - sampler_interface *_sampler, - pipebuf > &_in, - pipebuf &_out, - pipebuf *_freq_out = NULL, - pipebuf *_ss_out = NULL, - pipebuf *_mer_out = NULL, - pipebuf *_cstln_out = NULL) : - runnable(sch, "Constellation receiver"), - sampler(_sampler), - cstln(NULL), - meas_decimation(1048576), - pll_adjustment(1.0), - allow_drift(false), - kest(0.01), - in(_in), - out(_out, chunk_size), - est_insp(cstln_amp * cstln_amp), - agc_gain(1), - mu(0), - phase(0), - est_sp(0), - est_ep(0), - meas_count(0) + cstln_receiver(scheduler *sch, + sampler_interface *_sampler, + pipebuf> &_in, + pipebuf &_out, + pipebuf *_freq_out = NULL, + pipebuf *_ss_out = NULL, + pipebuf *_mer_out = NULL, + pipebuf *_cstln_out = NULL) : runnable(sch, "Constellation receiver"), + sampler(_sampler), + cstln(NULL), + meas_decimation(1048576), + pll_adjustment(1.0), + allow_drift(false), + kest(0.01), + in(_in), + out(_out, chunk_size), + est_insp(cstln_amp * cstln_amp), + agc_gain(1), + mu(0), + phase(0), + est_sp(0), + est_ep(0), + meas_count(0) { set_omega(1); set_freq(0); @@ -914,30 +1092,32 @@ struct cstln_receiver: runnable { // Prevent PLL from crossing +-SR/n/2 and locking at +-SR/n. int n = 4; + if (cstln) { switch (cstln->nsymbols) { case 2: n = 2; - break; // BPSK + break; // BPSK case 4: n = 4; - break; // QPSK + break; // QPSK case 8: n = 8; - break; // 8PSK + break; // 8PSK case 16: n = 12; - break; // 16APSK + break; // 16APSK case 32: n = 16; - break; // 32APSK + break; // 32APSK default: n = 4; break; } } + min_freqw = freqw - 65536 / max_omega / n / 2; max_freqw = freqw + 65536 / max_omega / n / 2; } @@ -945,35 +1125,26 @@ struct cstln_receiver: runnable void run() { if (!cstln) - { - fail("cstln_lut::run", "constellation not set"); - return; - } + fail("constellation not set"); // Magic constants that work with the qa recordings. float freq_alpha = 0.04; float freq_beta = 0.0012 / omega * pll_adjustment; float gain_mu = 0.02 / (cstln_amp * cstln_amp) * 2; - unsigned int max_meas = chunk_size / meas_decimation + 1; + int max_meas = chunk_size / meas_decimation + 1; // Large margin on output_size because mu adjustments // can lead to more than chunk_size/min_omega symbols. - while (in.readable() >= chunk_size + sampler->readahead() - && out.writable() >= chunk_size - && (!freq_out || freq_out->writable() >= max_meas) - && (!ss_out || ss_out->writable() >= max_meas) - && (!mer_out || mer_out->writable() >= max_meas) - && (!cstln_out || cstln_out->writable() >= max_meas)) + while (in.readable() >= chunk_size + sampler->readahead() && out.writable() >= chunk_size && (!freq_out || freq_out->writable() >= max_meas) && (!ss_out || ss_out->writable() >= max_meas) && (!mer_out || mer_out->writable() >= max_meas) && (!cstln_out || cstln_out->writable() >= max_meas)) { - - sampler->update_freq(freqw); + sampler->update_freq(freqw, chunk_size); complex *pin = in.rd(), *pin0 = pin, *pend = pin + chunk_size; - softsymbol *pout = out.wr(), *pout0 = pout; + SOFTSYMB *pout = out.wr(), *pout0 = pout; // These are scoped outside the loop for SS and MER estimation. - complex sg(0.0, 0.0); // Symbol before AGC; - complex s(0.0, 0.0); // For MER estimation and constellation viewer + complex sg{0.0f, 0.0f}; // Symbol before AGC; + complex s; // For MER estimation and constellation viewer complex *cstln_point = NULL; while (pin < pend) @@ -983,11 +1154,12 @@ struct cstln_receiver: runnable { // Here 0<=mu<1 is the fractional time of the next symbol // between pin and pin+1. - sg = sampler->interp(pin, mu, phase); + sg = sampler->interp(pin, mu, phase + mu * freqw); s = sg * agc_gain; // Constellation look-up - cstln_lut<256>::result *cr = cstln->lookup(s.re, s.im); + typename cstln_lut::result *cr = + cstln->lookup(s.re, s.im); *pout = cr->ss; ++pout; @@ -1004,14 +1176,10 @@ struct cstln_receiver: runnable hist[1] = hist[0]; hist[0].p.re = s.re; hist[0].p.im = s.im; - cstln_point = &cstln->symbols[cr->ss.symbol]; + cstln_point = &cstln->symbols[cr->symbol]; hist[0].c.re = cstln_point->re; hist[0].c.im = cstln_point->im; - float muerr = ((hist[0].p.re - hist[2].p.re) * hist[1].c.re - + (hist[0].p.im - hist[2].p.im) * hist[1].c.im) - - ((hist[0].c.re - hist[2].c.re) * hist[1].p.re - + (hist[0].c.im - hist[2].c.im) - * hist[1].p.im); + float muerr = ((hist[0].p.re - hist[2].p.re) * hist[1].c.re + (hist[0].p.im - hist[2].p.im) * hist[1].c.im) - ((hist[0].c.re - hist[2].c.re) * hist[1].p.re + (hist[0].c.im - hist[2].c.im) * hist[1].p.im); float mucorr = muerr * gain_mu; const float max_mucorr = 0.1; // TBD Optimize out statically @@ -1020,14 +1188,14 @@ struct cstln_receiver: runnable if (mucorr > max_mucorr) mucorr = max_mucorr; mu += mucorr; - mu += omega; // Next symbol time; - } // mu<1 + mu += omega; // Next symbol time; + } // mu<1 // Next sample ++pin; --mu; phase += freqw; - } // chunk_size + } // chunk_size in.read(pin - pin0); out.written(pout - pout0); @@ -1035,11 +1203,10 @@ struct cstln_receiver: runnable // Normalize phase so that it never exceeds 32 bits. // Max freqw is 2^31/65536/chunk_size = 256 Hz // (this may happen with leandvb --drift --decim). - phase = fmodf(phase, 65536); + phase = fmodf(phase, 65536); // Rounding direction irrelevant if (cstln_point) { - // Output the last interpolated PSK symbol, max once per chunk_size if (cstln_out) cstln_out->write(s); @@ -1049,32 +1216,32 @@ struct cstln_receiver: runnable // TODO Use a better estimator at low SNR. float insp = sg.re * sg.re + sg.im * sg.im; est_insp = insp * kest + est_insp * (1 - kest); + if (est_insp) agc_gain = cstln_amp / gen_sqrt(est_insp); // SS and MER complex ev(s.re - cstln_point->re, - s.im - cstln_point->im); + s.im - cstln_point->im); float sig_power, ev_power; + if (cstln->nsymbols == 2) { // Special case for BPSK: Ignore quadrature component of noise. // TBD Projection on I axis assumes BPSK at 45° - float sig_real = (cstln_point->re + cstln_point->im) - * 0.707; + float sig_real = (cstln_point->re + cstln_point->im) * 0.707; float ev_real = (ev.re + ev.im) * 0.707; sig_power = sig_real * sig_real; ev_power = ev_real * ev_real; } else { - sig_power = (int) cstln_point->re * cstln_point->re - + (int) cstln_point->im * cstln_point->im; + sig_power = (int)cstln_point->re * cstln_point->re + (int)cstln_point->im * cstln_point->im; ev_power = ev.re * ev.re + ev.im * ev.im; } + est_sp = sig_power * kest + est_sp * (1 - kest); est_ep = ev_power * kest + est_ep * (1 - kest); - } // This is best done periodically ouside the inner loop, @@ -1091,6 +1258,7 @@ struct cstln_receiver: runnable refresh_freq_tap(); meas_count += pin - pin0; + while (meas_count >= meas_decimation) { meas_count -= meas_decimation; @@ -1100,31 +1268,34 @@ struct cstln_receiver: runnable ss_out->write(sqrtf(est_insp)); if (mer_out) mer_out->write( - est_ep ? 10 * logf(est_sp / est_ep) / logf(10) : 0); + est_ep ? 10 * logf(est_sp / est_ep) / logf(10) : 0); } - } // Work to do + } // Work to do } float freq_tap; + void refresh_freq_tap() { freq_tap = freqw / 65536; } -private: + + private: struct { - complex p; // Received symbol - complex c; // Matched constellation point + complex p; // Received symbol + complex c; // Matched constellation point } hist[3]; - pipereader > in; - pipewriter out; + + pipereader> in; + pipewriter out; float est_insp, agc_gain; - float mu; // PSK time expressed in clock ticks - float phase; // 65536=2pi + float mu; // PSK time expressed in clock ticks + float phase; // 65536=2pi // Signal estimation - float est_sp; // Estimated RMS signal power - float est_ep; // Estimated RMS error vector power + float est_sp; // Estimated RMS signal power + float est_ep; // Estimated RMS error vector power unsigned long meas_count; pipewriter *freq_out, *ss_out, *mer_out; pipewriter *cstln_out; @@ -1135,38 +1306,35 @@ private: // Optimized for u8 input, no AGC, uses phase information only. // Outputs hard symbols. -template -struct fast_qpsk_receiver: runnable +template +struct fast_qpsk_receiver : runnable { typedef u8 hardsymbol; - unsigned long meas_decimation; // Measurement rate - float omega, min_omega, max_omega; // Samples per symbol - signed long freqw, min_freqw, max_freqw; // Freq offs (angle per sample) + unsigned long meas_decimation; // Measurement rate + float omega, min_omega, max_omega; // Samples per symbol + signed long freqw, min_freqw, max_freqw; // Freq offs (angle per sample) float pll_adjustment; - bool allow_drift; // Follow carrier beyond safe limits + bool allow_drift; // Follow carrier beyond safe limits static const unsigned int chunk_size = 128; - fast_qpsk_receiver( - scheduler *sch, - pipebuf > &_in, - pipebuf &_out, - pipebuf *_freq_out = NULL, - pipebuf > *_cstln_out = NULL) : - runnable(sch, "Fast QPSK receiver"), - meas_decimation(1048576), - pll_adjustment(1.0), - allow_drift(false), - in(_in), - out(_out, chunk_size), - mu(0), - phase(0), - meas_count(0) + fast_qpsk_receiver(scheduler *sch, + pipebuf> &_in, + pipebuf &_out, + pipebuf *_freq_out = NULL, + pipebuf> *_cstln_out = NULL) : runnable(sch, "Fast QPSK receiver"), + meas_decimation(1048576), + pll_adjustment(1.0), + allow_drift(false), + in(_in), + out(_out, chunk_size), + mu(0), + phase(0), + meas_count(0) { set_omega(1); set_freq(0); freq_out = _freq_out ? new pipewriter(*_freq_out) : NULL; - cstln_out = - _cstln_out ? new pipewriter >(*_cstln_out) : NULL; + cstln_out = _cstln_out ? new pipewriter>(*_cstln_out) : NULL; memset(hist, 0, sizeof(hist)); init_lookup_tables(); } @@ -1201,29 +1369,24 @@ struct fast_qpsk_receiver: runnable // Magic constants that work with the qa recordings. signed long freq_alpha = 0.04 * 65536; signed long freq_beta = 0.0012 * 256 * 65536 / omega * pll_adjustment; + if (!freq_beta) - { - fail("fast_qpsk_receiver::run", "Excessive oversampling"); - return; - } + fail("Excessive oversampling"); float gain_mu = 0.02 / (cstln_amp * cstln_amp) * 2; int max_meas = chunk_size / meas_decimation + 1; // Largin margin on output_size because mu adjustments // can lead to more than chunk_size/min_omega symbols. - while (in.readable() >= chunk_size + 1 - && // +1 for interpolation - out.writable() >= chunk_size - && (!freq_out || freq_out->writable() >= max_meas) - && (!cstln_out || cstln_out->writable() >= max_meas)) - { + while (in.readable() >= chunk_size + 1 && // +1 for interpolation + out.writable() >= chunk_size && (!freq_out || freq_out->writable() >= max_meas) && (!cstln_out || cstln_out->writable() >= max_meas)) + { complex *pin = in.rd(), *pin0 = pin, *pend = pin + chunk_size; hardsymbol *pout = out.wr(), *pout0 = pout; cu8 s; - u_angle symbol_arg = 0; // Exported for constellation viewer + u_angle symbol_arg = 0; // Exported for constellation viewer while (pin < pend) { @@ -1234,48 +1397,46 @@ struct fast_qpsk_receiver: runnable // between pin and pin+1. // Derotate and interpolate -#if 0 // Phase only (does not work) - // Careful with the float/signed/unsigned casts +#if 0 /* Phase only (does not work) + Careful with the float/signed/unsigned casts */ u_angle a0 = fast_arg(pin[0]) - phase; u_angle a1 = fast_arg(pin[1]) - (phase+freqw); s_angle da = a1 - a0; symbol_arg = a0 + (s_angle)(da*mu); s = arg_to_symbol(symbol_arg); -#elif 1 // Linear by lookup-table. 1.2M on bench3bishs +#elif 1 // Linear by lookup-table. 1.2M on bench3bishs polar *p0 = &lut_polar[pin[0].re][pin[0].im]; - u_angle a0 = (u_angle) (p0->a - phase) >> (16 - RLUT_BITS); + u_angle a0 = (u_angle)(p0->a - phase) >> (16 - RLUT_BITS); cu8 *p0r = &lut_rect[a0][p0->r >> 1]; polar *p1 = &lut_polar[pin[1].re][pin[1].im]; - u_angle a1 = (u_angle) (p1->a - (phase + freqw)) - >> (16 - RLUT_BITS); + u_angle a1 = (u_angle)(p1->a - (phase + freqw)) >> (16 - RLUT_BITS); cu8 *p1r = &lut_rect[a1][p1->r >> 1]; - s.re = (int) (p0r->re + (p1r->re - p0r->re) * mu); - s.im = (int) (p0r->im + (p1r->im - p0r->im) * mu); + s.re = (int)(p0r->re + (p1r->re - p0r->re) * mu); + s.im = (int)(p0r->im + (p1r->im - p0r->im) * mu); symbol_arg = fast_arg(s); -#else // Linear floating-point, for reference - float a0 = -(int)phase*M_PI/32768; - float cosa0=cosf(a0), sina0=sinf(a0); +#else // Linear floating-point, for reference + float a0 = -(int)phase * M_PI / 32768; + float cosa0 = cosf(a0), sina0 = sinf(a0); complex - p0r(((float)pin[0].re-128)*cosa0 - ((float)pin[0].im-128)*sina0, - ((float)pin[0].re-128)*sina0 + ((float)pin[0].im-128)*cosa0); - float a1 = -(int)(phase+freqw)*M_PI/32768; - float cosa1=cosf(a1), sina1=sinf(a1); + p0r(((float)pin[0].re - 128) * cosa0 - ((float)pin[0].im - 128) * sina0, + ((float)pin[0].re - 128) * sina0 + ((float)pin[0].im - 128) * cosa0); + float a1 = -(int)(phase + freqw) * M_PI / 32768; + float cosa1 = cosf(a1), sina1 = sinf(a1); complex - p1r(((float)pin[1].re-128)*cosa1 - ((float)pin[1].im-128)*sina1, - ((float)pin[1].re-128)*sina1 + ((float)pin[1].im-128)*cosa1); - s.re = (int)(128 + p0r.re + (p1r.re-p0r.re)*mu); - s.im = (int)(128 + p0r.im + (p1r.im-p0r.im)*mu); + p1r(((float)pin[1].re - 128) * cosa1 - ((float)pin[1].im - 128) * sina1, + ((float)pin[1].re - 128) * sina1 + ((float)pin[1].im - 128) * cosa1); + s.re = (int)(128 + p0r.re + (p1r.re - p0r.re) * mu); + s.im = (int)(128 + p0r.im + (p1r.im - p0r.im) * mu); symbol_arg = fast_arg(s); #endif int quadrant = symbol_arg >> 14; - static unsigned char quadrant_to_symbol[4] = - { 0, 2, 3, 1 }; + static unsigned char quadrant_to_symbol[4] = {0, 2, 3, 1}; *pout = quadrant_to_symbol[quadrant]; ++pout; // PLL - s_angle phase_error = (s_angle) (symbol_arg & 16383) - 8192; + s_angle phase_error = (s_angle)(symbol_arg & 16383) - 8192; phase += (phase_error * freq_alpha + 32768) >> 16; freqw += (phase_error * freq_beta + 32768 * 256) >> 24; @@ -1291,30 +1452,21 @@ struct fast_qpsk_receiver: runnable hist[0].p.re = (float)s.re - 128; hist[0].p.im = (float)s.im - 128; - cu8 cp = arg_to_symbol((symbol_arg&49152)+8192); + cu8 cp = arg_to_symbol((symbol_arg & 49152) + 8192); hist[0].c.re = (float)cp.re - 128; hist[0].c.im = (float)cp.im - 128; float muerr = - ( (hist[0].p.re-hist[2].p.re)*hist[1].c.re + - (hist[0].p.im-hist[2].p.im)*hist[1].c.im ) - - ( (hist[0].c.re-hist[2].c.re)*hist[1].p.re + - (hist[0].c.im-hist[2].c.im)*hist[1].p.im ); + ((hist[0].p.re - hist[2].p.re) * hist[1].c.re + + (hist[0].p.im - hist[2].p.im) * hist[1].c.im) - + ((hist[0].c.re - hist[2].c.re) * hist[1].p.re + + (hist[0].c.im - hist[2].c.im) * hist[1].p.im); #else hist[0].p = s; hist[0].c = arg_to_symbol((symbol_arg & 49152) + 8192); int muerr = - ((signed char) (hist[0].p.re - hist[2].p.re) - * ((int) hist[1].c.re - 128) - + (signed char) (hist[0].p.im - hist[2].p.im) - * ((int) hist[1].c.im - 128)) - - ((signed char) (hist[0].c.re - - hist[2].c.re) - * ((int) hist[1].p.re - 128) - + (signed char) (hist[0].c.im - - hist[2].c.im) - * ((int) hist[1].p.im - 128)); + ((signed char)(hist[0].p.re - hist[2].p.re) * ((int)hist[1].c.re - 128) + (signed char)(hist[0].p.im - hist[2].p.im) * ((int)hist[1].c.im - 128)) - ((signed char)(hist[0].c.re - hist[2].c.re) * ((int)hist[1].p.re - 128) + (signed char)(hist[0].c.im - hist[2].c.im) * ((int)hist[1].p.im - 128)); #endif float mucorr = muerr * gain_mu; const float max_mucorr = 0.1; @@ -1324,14 +1476,14 @@ struct fast_qpsk_receiver: runnable if (mucorr > max_mucorr) mucorr = max_mucorr; mu += mucorr; - mu += omega; // Next symbol time; - } // mu<1 + mu += omega; // Next symbol time; + } // mu<1 // Next sample ++pin; --mu; phase += freqw; - } // chunk_size + } // chunk_size in.read(pin - pin0); out.written(pout - pout0); @@ -1352,73 +1504,82 @@ struct fast_qpsk_receiver: runnable // Output measurements meas_count += pin - pin0; + while (meas_count >= meas_decimation) { meas_count -= meas_decimation; + if (freq_out) - freq_out->write((float) freqw / 65536); + freq_out->write((float)freqw / 65536); } - } // Work to do + } // Work to do } -private: - + private: struct polar { u_angle a; unsigned char r; } lut_polar[256][256]; + u_angle fast_arg(const cu8 &c) { // TBD read cu8 as u16 index, same endianness as in init() return lut_polar[c.re][c.im].a; } + cu8 lut_rect[RLUT_ANGLES][256]; cu8 lut_sincos[65536]; + cu8 arg_to_symbol(u_angle a) { return lut_sincos[a]; } + void init_lookup_tables() { for (int i = 0; i < 256; ++i) + { for (int q = 0; q < 256; ++q) { // Don't cast float to unsigned directly - lut_polar[i][q].a = (s_angle) (atan2f(q - 128, i - 128) * 65536 - / (2 * M_PI)); - lut_polar[i][q].r = (int) hypotf(i - 128, q - 128); + lut_polar[i][q].a = (s_angle)(atan2f(q - 128, i - 128) * 65536 / (2 * M_PI)); + lut_polar[i][q].r = (int)hypotf(i - 128, q - 128); } + } + for (unsigned long a = 0; a < 65536; ++a) { float f = 2 * M_PI * a / 65536; lut_sincos[a].re = 128 + cstln_amp * cosf(f); lut_sincos[a].im = 128 + cstln_amp * sinf(f); } + for (int a = 0; a < RLUT_ANGLES; ++a) + { for (int r = 0; r < 256; ++r) { - lut_rect[a][r].re = (int) (128 - + r * cos(2 * M_PI * a / RLUT_ANGLES)); - lut_rect[a][r].im = (int) (128 - + r * sin(2 * M_PI * a / RLUT_ANGLES)); + lut_rect[a][r].re = (int)(128 + r * cos(2 * M_PI * a / RLUT_ANGLES)); + lut_rect[a][r].im = (int)(128 + r * sin(2 * M_PI * a / RLUT_ANGLES)); } + } } struct { #if HIST_FLOAT - complex p; // Received symbol - complex c;// Matched constellation point + complex p; // Received symbol + complex c; // Matched constellation point #else - cu8 p; // Received symbol - cu8 c; // Matched constellation point + cu8 p; // Received symbol + cu8 c; // Matched constellation point #endif } hist[3]; + pipereader in; pipewriter out; - float mu; // PSK time expressed in clock ticks. TBD fixed point. + float mu; // PSK time expressed in clock ticks. TBD fixed point. u_angle phase; unsigned long meas_count; pipewriter *freq_out, *mer_out; @@ -1430,42 +1591,43 @@ private: // Maps symbols to I/Q points. -template -struct cstln_transmitter: runnable +template +struct cstln_transmitter : runnable { - cstln_lut<256> *cstln; + cstln_lut *cstln; - cstln_transmitter(scheduler *sch, pipebuf &_in, pipebuf > &_out) : - runnable(sch, "cstln_transmitter"), - cstln(0), - in(_in), - out(_out) + cstln_transmitter(scheduler *sch, + pipebuf &_in, + pipebuf> &_out) : runnable(sch, "cstln_transmitter"), + in(_in), + out(_out), + cstln(0) { } void run() { if (!cstln) - { - fail("cstln_transmitter::run", "constellation not set"); - return; - } + fail("constellation not set"); + int count = min(in.readable(), out.writable()); u8 *pin = in.rd(), *pend = pin + count; complex *pout = out.wr(); + for (; pin < pend; ++pin, ++pout) { complex *cp = &cstln->symbols[*pin]; pout->re = Zout + cp->re; pout->im = Zout + cp->im; } + in.read(count); out.written(count); } -private: + private: pipereader in; - pipewriter > out; + pipewriter> out; }; // cstln_transmitter @@ -1473,18 +1635,21 @@ private: // Resolution is sample_freq/65536. -template -struct rotator: runnable +template +struct rotator : runnable { - rotator(scheduler *sch, pipebuf > &_in, pipebuf > &_out, float freq) : - runnable(sch, "rotator"), - in(_in), - out(_out), - index(0) + rotator(scheduler *sch, + pipebuf> &_in, + pipebuf> &_out, + float freq) : runnable(sch, "rotator"), + in(_in), + out(_out), + index(0) { int ifreq = freq * 65536; if (sch->debug) fprintf(stderr, "Rotate: req=%f real=%f\n", freq, ifreq / 65536.0); + for (int i = 0; i < 65536; ++i) { lut_cos[i] = cosf(2 * M_PI * i * ifreq / 65536); @@ -1497,6 +1662,7 @@ struct rotator: runnable unsigned long count = min(in.readable(), out.writable()); complex *pin = in.rd(), *pend = pin + count; complex *pout = out.wr(); + for (; pin < pend; ++pin, ++pout, ++index) { float c = lut_cos[index]; @@ -1504,16 +1670,17 @@ struct rotator: runnable pout->re = pin->re * c - pin->im * s; pout->im = pin->re * s + pin->im * c; } + in.read(count); out.written(count); } -private: - pipereader > in; - pipewriter > out; + private: + pipereader> in; + pipewriter> out; float lut_cos[65536]; float lut_sin[65536]; - unsigned short index; // Current phase + unsigned short index; // Current phase }; // rotator @@ -1526,96 +1693,104 @@ private: // // Maximum roll-off 0.5 -template -struct cnr_fft: runnable +template +struct cnr_fft : runnable { - cnr_fft(scheduler *sch, pipebuf > &_in, pipebuf &_out, float _bandwidth, int nfft = 4096) : - runnable(sch, "cnr_fft"), - bandwidth(_bandwidth), - freq_tap(NULL), - tap_multiplier(1), - decimation(1048576), - kavg(0.1), - in(_in), - out(_out), - fft(nfft), - avgpower(NULL), - phase(0) + cnr_fft(scheduler *sch, + pipebuf> &_in, + pipebuf &_out, + float _bandwidth, int nfft = 4096) : runnable(sch, "cnr_fft"), + bandwidth(_bandwidth), + freq_tap(NULL), + tap_multiplier(1), + decimation(1048576), + kavg(0.1), + in(_in), + out(_out), + fft(nfft), + avgpower(NULL), + phase(0) { - if (bandwidth > 0.25) { - fail("cnr_fft::cnr_fft", "CNR estimator requires Fsampling > 4x Fsignal"); - } - m_data.allocate(fft.n); - m_power.allocate(fft.n); + if (bandwidth > 0.25) + fail("CNR estimator requires Fsampling > 4x Fsignal"); } float bandwidth; float *freq_tap, tap_multiplier; int decimation; float kavg; - IncrementalArray > m_data; - IncrementalArray m_power; void run() { while (in.readable() >= fft.n && out.writable() >= 1) { phase += fft.n; + if (phase >= decimation) { phase -= decimation; do_cnr(); } + in.read(fft.n); } } -private: - + private: void do_cnr() { float center_freq = freq_tap ? *freq_tap * tap_multiplier : 0; int icf = floor(center_freq * fft.n + 0.5); - //complex data[fft.n]; - complex *data = m_data.m_array; + complex *data = new complex[fft.n]; memcpy(data, in.rd(), fft.n * sizeof(data[0])); fft.inplace(data, true); - //T power[fft.n]; - T *power = m_power.m_array; - for (unsigned int i = 0; i < fft.n; ++i) + T *power = new T[fft.n]; + + for (int i = 0; i < fft.n; ++i) power[i] = data[i].re * data[i].re + data[i].im * data[i].im; + if (!avgpower) { // Initialize with first spectrum avgpower = new T[fft.n]; memcpy(avgpower, power, fft.n * sizeof(avgpower[0])); } + // Accumulate and low-pass filter - for (unsigned int i = 0; i < fft.n; ++i) + for (int i = 0; i < fft.n; ++i) avgpower[i] = avgpower[i] * (1 - kavg) + power[i] * kavg; - int bw__slots = (bandwidth / 4) * fft.n; - if (!bw__slots) + int bwslots = (bandwidth / 4) * fft.n; + + if (!bwslots) + { + delete[] power; + delete[] data; return; + } + // Measure carrier+noise in center band - float c2plusn2 = avg__slots(icf - bw__slots, icf + bw__slots); + float c2plusn2 = avgslots(icf - bwslots, icf + bwslots); // Measure noise left and right of roll-off zones - float n2 = (avg__slots(icf - bw__slots * 4, icf - bw__slots * 3) - + avg__slots(icf + bw__slots * 3, icf + bw__slots * 4)) / 2; + float n2 = (avgslots(icf - bwslots * 4, icf - bwslots * 3) + avgslots(icf + bwslots * 3, icf + bwslots * 4)) / 2; float c2 = c2plusn2 - n2; float cnr = (c2 > 0 && n2 > 0) ? 10 * logf(c2 / n2) / logf(10) : -50; out.write(cnr); + delete[] power; + delete[] data; } - float avg__slots(int i0, int i1) - { // i0 <= i1 + float avgslots(int i0, int i1) + { // i0 <= i1 T s = 0; + for (int i = i0; i <= i1; ++i) s += avgpower[i & (fft.n - 1)]; + return s / (i1 - i0 + 1); } - pipereader > in; + pipereader> in; pipewriter out; cfft_engine fft; T *avgpower; @@ -1623,56 +1798,72 @@ private: }; // cnr_fft -template -struct spectrum: runnable +template +struct spectrum : runnable { - static const int nfft = 1024; - spectrum(scheduler *sch, pipebuf > &_in, pipebuf &_out) : - runnable(sch, "spectrum"), - decimation(1048576), - kavg(0.1), - in(_in), - out(_out), - fft(nfft), - avgpower(NULL), - phase(0) + spectrum(scheduler *sch, + pipebuf> &_in, + pipebuf &_out) : runnable(sch, "spectrum"), + decimation(1048576), + kavg(0.1), + decim(1), in(in), + out(_out), + fft(NFFT), + avgpower(NULL), + phase(0) { } int decimation; float kavg; + int decim; void run() { - while (in.readable() >= fft.n && out.writable() >= 1) + while (in.readable() >= fft.n * decim && out.writable() >= 1) { - phase += fft.n; + phase += fft.n * decim; + if (phase >= decimation) { phase -= decimation; do_spectrum(); } - in.read(fft.n); + + in.read(fft.n * decim); } } -private: - + private: void do_spectrum() { complex data[fft.n]; - memcpy(data, in.rd(), fft.n * sizeof(data[0])); + + if (decim == 1) + { + memcpy(data, in.rd(), fft.n * sizeof(data[0])); + } + else + { + complex *pin = in.rd(); + + for (int i = 0; i < fft.n; ++i, pin += decim) + data[i] = *pin; + } + fft.inplace(data, true); - float power[nfft]; + float power[NFFT]; + for (int i = 0; i < fft.n; ++i) - power[i] = (float) data[i].re * data[i].re - + (float) data[i].im * data[i].im; + power[i] = (float)data[i].re * data[i].re + (float)data[i].im * data[i].im; + if (!avgpower) { // Initialize with first spectrum avgpower = new float[fft.n]; memcpy(avgpower, power, fft.n * sizeof(avgpower[0])); } + // Accumulate and low-pass filter for (int i = 0; i < fft.n; ++i) avgpower[i] = avgpower[i] * (1 - kavg) + power[i] * kavg; @@ -1680,21 +1871,22 @@ private: // Reuse power[] for (int i = 0; i < fft.n / 2; ++i) { - power[i] = 10 * log10f(avgpower[nfft / 2 + i]); - power[nfft / 2 + i] = 10 * log10f(avgpower[i]); + power[i] = 10 * log10f(avgpower[NFFT / 2 + i]); + power[NFFT / 2 + i] = 10 * log10f(avgpower[i]); } - memcpy(out.wr(), power, sizeof(power[0]) * nfft); + + memcpy(out.wr(), power, sizeof(power[0]) * NFFT); out.written(1); } - pipereader > in; - pipewriter out; + pipereader> in; + pipewriter out; cfft_engine fft; T *avgpower; int phase; }; // spectrum -}// namespace +} // namespace leansdr -#endif // LEANSDR_SDR_H +#endif // LEANSDR_SDR_H diff --git a/plugins/channelrx/demoddatv/leansdr/softword.h b/plugins/channelrx/demoddatv/leansdr/softword.h new file mode 100644 index 000000000..a4a58dfd2 --- /dev/null +++ b/plugins/channelrx/demoddatv/leansdr/softword.h @@ -0,0 +1,188 @@ +#ifndef LEANSDR_SOFTWORD_H +#define LEANSDR_SOFTWORD_H + +namespace leansdr +{ + +// This file implements options for SOFTBYTE in s2_frame_receiver +// (representation of bytes after deinterleaving). +// Also used as SOFTWORD in ldpc_engine +// (representation of packed SOFTBITs). + +// Interface for ldpc_engine: +// SOFTBIT softword_get(const SOFTWORD &p, int b) +// SOFTBIT softwords_xor(const SOFTWORD p1[], const SOFTWORD p2[], int b) +// void softword_zero(SOFTWORD *p, int b) +// void softwords_set(SOFTWORD p[], int b) +// void softwords_flip(SOFTWORD p[], int b) + +// HARD SOFTWORD / SOFTBYTE + +typedef uint8_t hard_sb; // Bit 7 is transmitted first. + +inline bool softword_get(const hard_sb &p, int b) +{ + return p & (1 << (7 - b)); +} +inline void softword_set(hard_sb *p, int b, bool v) +{ + hard_sb mask = 1 << (7 - b); + *p = ((*p) & ~mask) | (v << (7 - b)); +} +inline void softword_clear(hard_sb *p) { *p = 0; } +inline bool softword_weight(const bool &l) +{ + return true; +} +inline void softbit_set(bool *p, bool v) { *p = v; } +inline bool softbit_harden(bool b) { return b; } +inline uint8_t softbyte_harden(const hard_sb &b) { return b; } +inline bool softbit_xor(bool x, bool y) { return x ^ y; } +inline void softbit_clear(bool *p) { *p = false; } +inline bool softwords_xor(const hard_sb p1[], const hard_sb p2[], int b) +{ + return (p1[b / 8] ^ p2[b / 8]) & (1 << (7 - (b & 7))); +} +inline void softword_zero(hard_sb *p) +{ + *p = 0; +} +inline void softwords_set(hard_sb p[], int b) +{ + p[b / 8] |= 1 << (7 - (b & 7)); +} +inline void softword_write(hard_sb &p, int b, bool v) +{ + hard_sb mask = 1 << (7 - b); + p = (p & ~mask) | (hard_sb)v << (7 - b); +} +inline void softwords_write(hard_sb p[], int b, bool v) +{ + softword_write(p[b / 8], b & 7, v); +} +inline void softwords_flip(hard_sb p[], int b) +{ + p[b / 8] ^= 1 << (7 - (b & 7)); +} +uint8_t *softbytes_harden(hard_sb p[], int nbytes, uint8_t storage[]) +{ + return p; +} + +// LLR SOFTWORD + +struct llr_sb +{ + llr_t bits[8]; // bits[0] is transmitted first. +}; + +float prob(llr_t l) +{ + return (127.0 + l) / 254; +} +llr_t llr(float p) +{ + int r = -127 + 254 * p; + if (r < -127) + r = -127; + if (r > 127) + r = 127; + return r; +} +inline llr_t llr_xor(llr_t lx, llr_t ly) +{ + float px = prob(lx), py = prob(ly); + return llr(px * (1 - py) + (1 - px) * py); +} + +inline llr_t softword_get(const llr_sb &p, int b) +{ + return p.bits[b]; +} +// Special case to avoid computing b/8*8+b%7. Assumes llr_sb[] packed. +inline llr_t softwords_get(const llr_sb p[], int b) +{ + return p[0].bits[b]; // Beyond bounds on purpose +} +inline void softword_set(llr_sb *p, int b, llr_t v) +{ + p->bits[b] = v; +} +inline void softword_clear(llr_sb *p) +{ + memset(p->bits, 0, sizeof(p->bits)); +} +// inline llr_t softwords_get(const llr_sb p[], int b) { +// return softword_get(p[b/8], b&7); +// } +inline llr_t softword_weight(llr_t l) { return abs(l); } +inline void softbit_set(llr_t *p, bool v) { *p = v ? -127 : 127; } +inline bool softbit_harden(llr_t l) { return (l < 0); } +inline uint8_t softbyte_harden(const llr_sb &b) +{ + // Without conditional jumps + uint8_t r = (((b.bits[0] & 128) >> 0) | + ((b.bits[1] & 128) >> 1) | + ((b.bits[2] & 128) >> 2) | + ((b.bits[3] & 128) >> 3) | + ((b.bits[4] & 128) >> 4) | + ((b.bits[5] & 128) >> 5) | + ((b.bits[6] & 128) >> 6) | + ((b.bits[7] & 128) >> 7)); + return r; +} +inline llr_t softbit_xor(llr_t x, llr_t y) { return llr_xor(x, y); } +inline void softbit_clear(llr_t *p) { *p = -127; } +inline llr_t softwords_xor(const llr_sb p1[], const llr_sb p2[], int b) +{ + return llr_xor(p1[b / 8].bits[b & 7], p2[b / 8].bits[b & 7]); +} +inline void softword_zero(llr_sb *p) +{ + memset(p, -127, sizeof(*p)); +} +inline void softwords_set(llr_sb p[], int b) +{ + p[b / 8].bits[b & 7] = 127; +} +inline void softword_write(llr_sb &p, int b, llr_t v) +{ + p.bits[b] = v; +} +inline void softwords_write(llr_sb p[], int b, llr_t v) +{ + softword_write(p[b / 8], b & 7, v); +} +inline void softwords_flip(llr_sb p[], int b) +{ + llr_t *l = &p[b / 8].bits[b & 7]; + *l = -*l; +} +uint8_t *softbytes_harden(llr_sb p[], int nbytes, uint8_t storage[]) +{ + for (uint8_t *q = storage; nbytes--; ++p, ++q) + *q = softbyte_harden(*p); + return storage; +} + +// Generic wrappers + +template +inline SOFTBIT softwords_get(const SOFTBYTE p[], int b) +{ + return softword_get(p[b / 8], b & 7); +} +template +inline void softwords_set(SOFTBYTE p[], int b, SOFTBIT v) +{ + softword_set(&p[b / 8], b & 7, v); +} +template +inline SOFTBIT softwords_weight(const SOFTBYTE p[], int b) +{ + return softword_weight(softwords_get(p, b)); +} + +} // namespace leansdr + +#endif // LEANSDR_SOFTWORD_H diff --git a/plugins/channelrx/demoddatv/leansdr/viterbi.h b/plugins/channelrx/demoddatv/leansdr/viterbi.h index e4121ce73..79798b4ab 100644 --- a/plugins/channelrx/demoddatv/leansdr/viterbi.h +++ b/plugins/channelrx/demoddatv/leansdr/viterbi.h @@ -1,3 +1,19 @@ +// This file is part of LeanSDR Copyright (C) 2016-2018 . +// See the toplevel README for more information. +// +// This program 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. +// +// This program 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 this program. If not, see . + #ifndef LEANSDR_VITERBI_H #define LEANSDR_VITERBI_H @@ -24,7 +40,7 @@ namespace leansdr // TPM, TBM are unsigned integer types for path/branch metrics. // TPM is at least as wide as TBM. -template +template struct trellis { static const int NOSTATE = NSTATES + 1; @@ -33,9 +49,9 @@ struct trellis { struct branch { - TS pred; // Predecessor state or NOSTATE - TUS us; // Uncoded symbol - } branches[NCS]; // Incoming branches indexed by coded symbol + TS pred; // Predecessor state or NOSTATE + TUS us; // Uncoded symbol + } branches[NCS]; // Incoming branches indexed by coded symbol } states[NSTATES]; trellis() @@ -50,8 +66,7 @@ struct trellis { if (NCS & (NCS - 1)) { - fprintf(stderr, "leansdr::trellis::init_convolutional: NCS must be a power of 2\n"); - return; + fprintf(stderr, "NCS must be a power of 2\n"); } // Derive number of polynomials from NCS. int nG = log2i(NCS); @@ -61,29 +76,27 @@ struct trellis for (TUS us = 0; us < NUS; ++us) { // Run the convolutional encoder from state s with input us - uint64_t shiftreg = s; // TBD type + uint64_t shiftreg = s; // TBD type // Reverse bits TUS us_rev = 0; for (int b = 1; b < NUS; b *= 2) if (us & b) us_rev |= (NUS / 2 / b); shiftreg |= us_rev * NSTATES; - uint32_t cs = 0; // TBD type + uint32_t cs = 0; // TBD type for (int g = 0; g < nG; ++g) cs = (cs << 1) | parity(shiftreg & G[g]); - shiftreg /= NUS; // Shift bits for 1 uncoded symbol + shiftreg /= NUS; // Shift bits for 1 uncoded symbol // [us] at state [s] emits [cs] and leads to state [shiftreg]. typename state::branch *b = &states[shiftreg].branches[cs]; if (b->pred != NOSTATE) { - fprintf(stderr, "leansdr::trellis::init_convolutional: Invalid convolutional code\n"); - return; + fprintf(stderr, "Invalid convolutional code\n"); } b->pred = s; b->us = us; } } - } void dump() @@ -102,42 +115,46 @@ struct trellis fprintf(stderr, "\n"); } } - }; // Interface that hides the templated internals. -template +template struct viterbi_dec_interface { - virtual TUS update(TBM costs[], TPM *quality = NULL)=0; - virtual TUS update(TCS s, TBM cost, TPM *quality = NULL)=0; - virtual TUS update(int nm, TCS cs[], TBM costs[], TPM *quality = NULL)=0; + virtual TUS update(TBM *costs, TPM *quality = NULL) = 0; + virtual TUS update(TCS s, TBM cost, TPM *quality = NULL) = 0; }; -template -struct viterbi_dec: viterbi_dec_interface +template +struct viterbi_dec : viterbi_dec_interface { trellis *trell; struct state { - TPM cost; // Metric of best path leading to this state - TP path; // Best path leading to this state + TPM cost; // Metric of best path leading to this state + TP path; // Best path leading to this state }; typedef state statebank[NSTATES]; state statebanks[2][NSTATES]; - statebank *states, *newstates; // Alternate between banks + statebank *states, *newstates; // Alternate between banks - viterbi_dec(trellis *_trellis) : - trell(_trellis) + viterbi_dec(trellis *_trellis) : trell(_trellis) { states = &statebanks[0]; newstates = &statebanks[1]; for (TS s = 0; s < NSTATES; ++s) (*states)[s].cost = 0; // Determine max value that can fit in TPM - max_tpm = (TPM) 0 - 1; + max_tpm = (TPM)0 - 1; if (max_tpm < 0) { // TPM is signed @@ -160,12 +177,13 @@ struct viterbi_dec: viterbi_dec_interface // Select best branch for (int cs = 0; cs < NCS; ++cs) { - typename trellis::state::branch *b = &trell->states[s].branches[cs]; + typename trellis::state::branch *b = + &trell->states[s].branches[cs]; if (b->pred == trell->NOSTATE) continue; TPM m = (*states)[b->pred].cost + costs[cs]; if (m <= best_m) - { // <= guarantees one match + { // <= guarantees one match best_m = m; best_b = b; } @@ -193,10 +211,10 @@ struct viterbi_dec: viterbi_dec_interface for (TS s = 0; s < NSTATES; ++s) (*states)[s].cost -= best_tpm; #if 0 - // Observe that the min-max range remains bounded - fprintf(stderr,"-%2d = [", best_tpm); - for ( TS s=0; s typename trellis::state::branch *best_b = NULL; for (int im = 0; im < nm; ++im) { - typename trellis::state::branch *b = &trell->states[s].branches[cs[im]]; + typename trellis::state::branch *b = + &trell->states[s].branches[cs[im]]; if (b->pred == trell->NOSTATE) continue; TPM m = (*states)[b->pred].cost + costs[im]; if (m <= best_m) - { // <= guarantees one match + { // <= guarantees one match best_m = m; best_b = b; } @@ -238,7 +257,8 @@ struct viterbi_dec: viterbi_dec_interface // This works because costs are negative. for (int cs = 0; cs < NCS; ++cs) { - typename trellis::state::branch *b = &trell->states[s].branches[cs]; + typename trellis::state::branch *b = + &trell->states[s].branches[cs]; if (b->pred == trell->NOSTATE) continue; TPM m = (*states)[b->pred].cost; @@ -272,10 +292,10 @@ struct viterbi_dec: viterbi_dec_interface for (TS s = 0; s < NSTATES; ++s) (*states)[s].cost -= best_tpm; #if 0 - // Observe that the min-max range remains bounded - fprintf(stderr,"-%2d = [", best_tpm); - for ( TS s=0; s fprintf(stderr, "\n"); } -private: + private: TPM max_tpm; }; @@ -310,24 +330,15 @@ private: // DEPTH is the number of symbols stored in the path. // T is an unsigned integer type wider than NBITS*DEPTH. -template +template struct bitpath { T val; - bitpath() : - val(0) - { - } - void append(TUS us) - { - val = (val << NBITS) | us; - } - TUS read() - { - return (val >> (DEPTH - 1) * NBITS) & ((1 << NBITS) - 1); - } + bitpath() : val(0) {} + void append(TUS us) { val = (val << NBITS) | us; } + TUS read() { return (val >> (DEPTH - 1) * NBITS) & ((1 << NBITS) - 1); } }; -} // namespace +} // namespace leansdr -#endif // LEANSDR_VITERBI_H +#endif // LEANSDR_VITERBI_H