mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-12-23 18:15:45 -05:00
1873 lines
51 KiB
C++
1873 lines
51 KiB
C++
///////////////////////////////////////////////////////////////////////////////////////
|
|
// Copyright (C) 2018-2019, 2021 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
|
// Copyright (C) 2019 Davide Gerhard <rainbow@irh.it> //
|
|
// Copyright (C) 2020 Kacper Michajłow <kasper93@gmail.com> //
|
|
// //
|
|
// This file is part of LeanSDR Copyright (C) 2016-2019 <pabr@pabr.org>. //
|
|
// //
|
|
// 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 //
|
|
// (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 V3 for more details. //
|
|
// //
|
|
// You should have received a copy of the GNU General Public License //
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
|
///////////////////////////////////////////////////////////////////////////////////////
|
|
// This file is part of LeanSDR Copyright (C) 2016-2019 <pabr@pabr.org>.
|
|
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
#ifndef LEANSDR_DVB_H
|
|
#define LEANSDR_DVB_H
|
|
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
|
|
#include "leansdr/convolutional.h"
|
|
#include "leansdr/rs.h"
|
|
#include "leansdr/sdr.h"
|
|
#include "leansdr/viterbi.h"
|
|
|
|
namespace leansdr
|
|
{
|
|
|
|
static const int SIZE_RSPACKET = 204;
|
|
static const int MPEG_SYNC = 0x47;
|
|
static const int MPEG_SYNC_INV = (MPEG_SYNC ^ 0xff);
|
|
static const int MPEG_SYNC_CORRUPTED = 0x55;
|
|
|
|
// Generic deconvolution
|
|
|
|
enum code_rate
|
|
{
|
|
FEC12,
|
|
FEC23,
|
|
FEC46,
|
|
FEC34,
|
|
FEC56,
|
|
FEC78, // DVB-S
|
|
FEC45,
|
|
FEC89,
|
|
FEC910, // DVB-S2
|
|
FEC14,
|
|
FEC13,
|
|
FEC25,
|
|
FEC35, // DVB-S2
|
|
FEC_COUNT
|
|
};
|
|
|
|
// 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 = 0171;
|
|
static const int DVBS_G2 = 0133;
|
|
|
|
// G1 = 0b1111001
|
|
// G2 = 0b1011011
|
|
//
|
|
// G1 = [ 1 1 1 1 0 0 1 ]
|
|
// G2 = [ 1 0 1 1 0 1 1 ]
|
|
//
|
|
// C = [ G2 ;
|
|
// G1 ;
|
|
// 0 G2 ;
|
|
// 0 G1 ;
|
|
// 0 0 G2 ;
|
|
// 0 0 G1 ]
|
|
//
|
|
// C = [ 1 0 1 1 0 1 1 0 0 0 0 0 0 ;
|
|
// 1 1 1 1 0 0 1 0 0 0 0 0 0 ;
|
|
// 0 1 0 1 1 0 1 1 0 0 0 0 0 ;
|
|
// 0 1 1 1 1 0 0 1 0 0 0 0 0 ;
|
|
// 0 0 1 0 1 1 0 1 1 0 0 0 0 ;
|
|
// 0 0 1 1 1 1 0 0 1 0 0 0 0 ;
|
|
// 0 0 0 1 0 1 1 0 1 1 0 0 0 ;
|
|
// 0 0 0 1 1 1 1 0 0 1 0 0 0 ;
|
|
// 0 0 0 0 1 0 1 1 0 1 1 0 0 ;
|
|
// 0 0 0 0 1 1 1 1 0 0 1 0 0 ;
|
|
// 0 0 0 0 0 1 0 1 1 0 1 1 0 ;
|
|
// 0 0 0 0 0 1 1 1 1 0 0 1 0 ;
|
|
// 0 0 0 0 0 0 1 0 1 1 0 1 1 ;
|
|
// 0 0 0 0 0 0 1 1 1 1 0 0 1 ]
|
|
//
|
|
// IQ = [ Q1; I1; ... Q10; I10 ] = C * S
|
|
//
|
|
// D * C == [ 1 0 0 0 0 0 0 0 0 0 0 0 0 0 ]
|
|
//
|
|
// D = [ 0 1 0 1 1 1 0 1 1 1 0 0 0 0]
|
|
// D = 0x3ba
|
|
|
|
template <typename Tbyte, Tbyte BYTE_ERASED>
|
|
struct deconvol_sync : runnable
|
|
{
|
|
deconvol_sync(
|
|
scheduler *sch,
|
|
pipebuf<eucl_ss> &_in,
|
|
pipebuf<Tbyte> &_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;
|
|
conv[1] = gY;
|
|
nG = 2;
|
|
punct = new uint32_t[2];
|
|
punct[0] = pX;
|
|
punct[1] = pY;
|
|
punctperiod = 0;
|
|
punctweight = 0;
|
|
|
|
for (int i = 0; i < 2; ++i)
|
|
{
|
|
int nbits = log2(punct[i]) + 1;
|
|
if (nbits > punctperiod)
|
|
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();
|
|
init_syncs();
|
|
locked = &syncs[0];
|
|
}
|
|
|
|
~deconvol_sync()
|
|
{
|
|
delete[] deconv2;
|
|
delete[] deconv;
|
|
delete[] punct;
|
|
delete[] conv;
|
|
}
|
|
|
|
typedef uint64_t signal_t;
|
|
typedef uint64_t iq_t;
|
|
|
|
static int log2(uint64_t x)
|
|
{
|
|
int n = -1;
|
|
for (; x; ++n, x >>= 1)
|
|
;
|
|
return n;
|
|
}
|
|
|
|
iq_t convolve(signal_t s)
|
|
{
|
|
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
|
|
unsigned char bit = (s >> b) & 1;
|
|
state = (state >> 1) | (bit << 6); // Shift register
|
|
|
|
for (int j = 0; j < nG; ++j)
|
|
{
|
|
unsigned char xy = parity(state & conv[j]); // Taps
|
|
if (punct[j] & (1 << (b % punctperiod)))
|
|
iq = (iq << 1) | xy;
|
|
}
|
|
}
|
|
|
|
return iq;
|
|
}
|
|
|
|
void run()
|
|
{
|
|
run_decoding();
|
|
}
|
|
|
|
void next_sync()
|
|
{
|
|
if (fastlock)
|
|
fail("Bug: next_sync() called with fastlock");
|
|
|
|
++locked;
|
|
|
|
if (locked == &syncs[NSYNCS])
|
|
{
|
|
locked = &syncs[0];
|
|
// Try next symbol alignment (for FEC other than 1/2)
|
|
skip = 1;
|
|
}
|
|
}
|
|
|
|
bool fastlock;
|
|
|
|
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
|
|
|
|
void solve_rec(iq_t prefix, unsigned int nprefix, signal_t exp, iq_t *best)
|
|
{
|
|
if (prefix > *best)
|
|
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))
|
|
{
|
|
// Current candidate does not solve this column.
|
|
if ((response[b] >> nprefix) == 0)
|
|
// No more bits to trace back.
|
|
return;
|
|
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);
|
|
}
|
|
|
|
static const int LATENCY = 0;
|
|
|
|
void inverse_convolution()
|
|
{
|
|
for (int sbit = 0; sbit < maxsbits; ++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;
|
|
solve_rec(0, 0, 1 << (LATENCY + b), &deconv[b]);
|
|
}
|
|
|
|
// Alternate polynomials for fastlock
|
|
for (int b = 0; b < punctperiod; ++b)
|
|
{
|
|
uint64_t d = deconv[b], d2 = d;
|
|
// 1/2
|
|
if (d == 0x00000000000003baLL)
|
|
d2 = 0x0000000000038ccaLL;
|
|
// 2/3
|
|
if (d == 0x0000000000000f29LL)
|
|
d2 = 0x000000003c569329LL;
|
|
if (d == 0x000000000003c552LL)
|
|
d2 = 0x00000000001dee1cLL;
|
|
if (d == 0x0000000000007948LL)
|
|
d2 = 0x00000001e2b49948LL;
|
|
if (d == 0x00000000000001deLL)
|
|
d2 = 0x00000000001e2a90LL;
|
|
// 3/4
|
|
if (d == 0x000000000000f247LL)
|
|
d2 = 0x000000000fd6383bLL;
|
|
if (d == 0x00000000000fd9eeLL)
|
|
d2 = 0x000000000fd91392LL;
|
|
if (d == 0x0000000000f248d8LL)
|
|
d2 = 0x00000000fd9eef18LL;
|
|
// 5/6
|
|
if (d == 0x0000000000f5727fLL)
|
|
d2 = 0x000003d5c909758fLL;
|
|
if (d == 0x000000003d5c90aaLL)
|
|
d2 = 0x0f5727f0229c90aaLL;
|
|
if (d == 0x000000003daa371cLL)
|
|
d2 = 0x000003d5f45630ecLL;
|
|
if (d == 0x0000000f5727ff48LL)
|
|
d2 = 0x0000f57d28260348LL;
|
|
if (d == 0x0000000f57d28260LL)
|
|
d2 = 0xf5727ff48128260LL;
|
|
// 7/8
|
|
if (d == 0x0000fbeac76c454fLL)
|
|
d2 = 0x00fb11d6ba045a8fLL;
|
|
if (d == 0x00000000fb11d6baLL)
|
|
d2 = 0xfbea3c7d930e16baLL;
|
|
if (d == 0x0000fb112d5038dcLL)
|
|
d2 = 0x00fb112d5038271cLL;
|
|
if (d == 0x000000fbea3c7d68LL)
|
|
d2 = 0x00fbeac7975462a8LL;
|
|
if (d == 0x00000000fb112d50LL)
|
|
d2 = 0x00fbea3c86793290LL;
|
|
if (d == 0x0000fb112dabd2e0LL)
|
|
d2 = 0x00fb112d50c3cd20LL;
|
|
if (d == 0x00000000fb11d640LL)
|
|
d2 = 0x00fbea3c8679c980LL;
|
|
if (d2 == d)
|
|
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);
|
|
}
|
|
}
|
|
|
|
// Sanity check
|
|
for (int b = 0; b < punctperiod; ++b)
|
|
{
|
|
for (int i = 0; i < maxsbits; ++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("Failed to inverse convolutional coding");
|
|
int d2 = parity(iq & deconv2[b]);
|
|
if (d2 != expect)
|
|
fail("Failed to inverse convolutional coding (alt)");
|
|
}
|
|
|
|
if (traceback > sizeof(iq_t) * 8)
|
|
fail("Bug: traceback exceeds register size");
|
|
if (log2(deconv[b]) + 1 > traceback)
|
|
fail("traceback insufficient for deconvolution");
|
|
if (log2(deconv2[b]) + 1 > traceback)
|
|
fail("traceback insufficient for deconvolution (alt)");
|
|
}
|
|
}
|
|
|
|
static const int NSYNCS = 4;
|
|
|
|
struct sync_t
|
|
{
|
|
u8 lut[2][2]; // lut[(re>0)?1:0][(im>0)?1:0] = 0b000000IQ
|
|
iq_t in;
|
|
int n_in;
|
|
signal_t out;
|
|
int n_out;
|
|
// Auxiliary shift register for fastlock
|
|
iq_t in2;
|
|
int n_in2, n_out2;
|
|
} syncs[NSYNCS];
|
|
|
|
void init_syncs()
|
|
{
|
|
// EN 300 421, section 4.5, Figure 5 QPSK constellation
|
|
// Four rotations * two conjugations.
|
|
// 180° rotation is detected as polarity inversion in mpeg_sync.
|
|
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; //int im_neg = !im_pos;
|
|
int I, Q;
|
|
switch (sync_id)
|
|
{
|
|
case 0: // Direct 0°
|
|
I = re_pos ? 0 : 1;
|
|
Q = im_pos ? 0 : 1;
|
|
break;
|
|
case 1: // Direct 90°
|
|
I = im_pos ? 0 : 1;
|
|
Q = re_neg ? 0 : 1;
|
|
break;
|
|
case 2: // Conj 0°
|
|
I = re_pos ? 0 : 1;
|
|
Q = im_pos ? 1 : 0;
|
|
break;
|
|
case 3: // Conj 90°
|
|
I = im_pos ? 1 : 0;
|
|
Q = re_neg ? 0 : 1;
|
|
break;
|
|
#if 0
|
|
case 4: // Direct 180°
|
|
I = re_neg ? 0 : 1;
|
|
Q = im_neg ? 0 : 1;
|
|
break;
|
|
case 5:// Direct 270°
|
|
I = im_neg ? 0 : 1;
|
|
Q = re_pos ? 0 : 1;
|
|
break;
|
|
case 6:// Conj 180°
|
|
I = re_neg ? 0 : 1;
|
|
Q = im_neg ? 1 : 0;
|
|
break;
|
|
case 7:// Conj 270°
|
|
I = im_neg ? 1 : 0;
|
|
Q = re_pos ? 0 : 1;
|
|
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;
|
|
syncs[sync_id].n_out2 = 0;
|
|
}
|
|
}
|
|
|
|
// TODO: Unroll for each code rate setting.
|
|
// 1/2: 8 symbols -> 1 byte
|
|
// 2/3 12 symbols -> 2 bytes
|
|
// 3/4 16 symbols -> 3 bytes
|
|
// 5/6 24 symbols -> 5 bytes
|
|
// 7/8 32 symbols -> 7 bytes
|
|
|
|
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->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, 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->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]);
|
|
u8 bit2 = parity(iq & deconv2[b]);
|
|
if (bit2 != bit)
|
|
++res;
|
|
}
|
|
|
|
s->n_out2 += punctperiod;
|
|
s->n_in2 -= punctweight;
|
|
}
|
|
|
|
s->n_out2 -= 8;
|
|
return res;
|
|
}
|
|
|
|
void run_decoding()
|
|
{
|
|
in.read(skip);
|
|
skip = 0;
|
|
|
|
// 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();
|
|
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
|
|
long errors_best = 1 << 30;
|
|
sync_t *best = &syncs[0];
|
|
|
|
for (sync_t *s = syncs; s < syncs + NSYNCS; ++s)
|
|
{
|
|
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));
|
|
}
|
|
|
|
locked = best;
|
|
}
|
|
|
|
// If deconvolution bit error rate > 33%, try next sample alignment
|
|
if (errors_best > n * 8 / 3)
|
|
{
|
|
// fprintf(stderr, ">");
|
|
skip = 1;
|
|
}
|
|
}
|
|
|
|
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<eucl_ss> in;
|
|
pipewriter<Tbyte> out;
|
|
// DECONVOL
|
|
int nG;
|
|
uint32_t *conv; // [nG] Convolution polynomials; MSB is newest
|
|
uint32_t *punct; // [nG] Puncturing pattern
|
|
int punctperiod, punctweight;
|
|
iq_t *deconv; // [punctperiod] Deconvolution polynomials
|
|
iq_t *deconv2; // [punctperiod] Alternate polynomials (for fastlock)
|
|
sync_t *locked;
|
|
int skip;
|
|
};
|
|
|
|
typedef deconvol_sync<u8, 0> deconvol_sync_simple;
|
|
|
|
deconvol_sync_simple *make_deconvol_sync_simple(
|
|
scheduler *sch,
|
|
pipebuf<eucl_ss> &_in,
|
|
pipebuf<u8> &_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
|
|
};
|
|
|
|
// 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_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_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_COUNT] =
|
|
{
|
|
{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
|
|
};
|
|
|
|
struct dvb_convol : runnable
|
|
{
|
|
typedef u8 uncoded_byte;
|
|
typedef u8 hardsymbol;
|
|
|
|
dvb_convol(
|
|
scheduler *sch,
|
|
pipebuf<uncoded_byte> &_in,
|
|
pipebuf<hardsymbol> &_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("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("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);
|
|
// Process in multiples of the puncturing period and of 8 bits.
|
|
int chunk = convol.bits_in;
|
|
count = (count / chunk) * chunk;
|
|
convol.encode(in.rd(), out.wr(), count);
|
|
in.read(count);
|
|
int nout = count * 8 / convol.bits_in * convol.bits_out / convol.bps;
|
|
out.written(nout);
|
|
}
|
|
|
|
private:
|
|
pipereader<uncoded_byte> in;
|
|
pipewriter<hardsymbol> out;
|
|
convol_multipoly<uint16_t, 16> convol;
|
|
};
|
|
// dvb_convol
|
|
|
|
// NEW ALGEBRAIC DECONVOLUTION
|
|
|
|
// QPSK 1/2 only;
|
|
// With DVB-S polynomials hardcoded.
|
|
|
|
template <typename Tin>
|
|
struct dvb_deconvol_sync : runnable
|
|
{
|
|
typedef u8 decoded_byte;
|
|
int resync_period;
|
|
static const int chunk_size = 64; // At least 2*sizeof(Thist)/8
|
|
|
|
dvb_deconvol_sync(
|
|
scheduler *sch,
|
|
pipebuf<Tin> &_in,
|
|
pipebuf<decoded_byte> &_out
|
|
) :
|
|
runnable(sch, "deconvol_sync_multipoly"),
|
|
resync_period(32),
|
|
in(_in),
|
|
out(_out, chunk_size),
|
|
resync_phase(0)
|
|
{
|
|
init_syncs();
|
|
locked = &syncs[0];
|
|
}
|
|
|
|
void run()
|
|
{
|
|
while (in.readable() >= chunk_size * 8 && out.writable() >= chunk_size)
|
|
{
|
|
int errors_best = 1 << 30;
|
|
sync_t *best = nullptr;
|
|
|
|
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));
|
|
locked = best;
|
|
}
|
|
|
|
if (++resync_phase >= resync_period)
|
|
resync_phase = 0;
|
|
} // Work to do
|
|
} // run()
|
|
|
|
private:
|
|
pipereader<Tin> in;
|
|
pipewriter<decoded_byte> out;
|
|
int resync_phase;
|
|
|
|
static const int NSYNCS = 4;
|
|
|
|
struct sync_t
|
|
{
|
|
deconvol_poly2<Tin, uint32_t, uint64_t, 0x3ba, 0x38f70> deconv;
|
|
u8 lut[4]; // TBD Swap and flip bits in the polynomials instead.
|
|
} syncs[NSYNCS];
|
|
|
|
sync_t *locked;
|
|
|
|
void init_syncs()
|
|
{
|
|
for (int s = 0; s < NSYNCS; ++s)
|
|
// EN 300 421, section 4.5, Figure 5 QPSK constellation
|
|
// Four rotations * two conjugations.
|
|
// 180° rotation is detected as polarity inversion in mpeg_sync.
|
|
// 2 | 0
|
|
// --+--
|
|
// 3 | 1
|
|
// 0°
|
|
syncs[0].lut[0] = 0;
|
|
|
|
syncs[0].lut[1] = 1;
|
|
syncs[0].lut[2] = 2;
|
|
syncs[0].lut[3] = 3;
|
|
// 90°
|
|
syncs[1].lut[0] = 2;
|
|
syncs[1].lut[1] = 0;
|
|
syncs[1].lut[2] = 3;
|
|
syncs[1].lut[3] = 1;
|
|
// 0° conjugated
|
|
syncs[2].lut[0] = 1;
|
|
syncs[2].lut[1] = 0;
|
|
syncs[2].lut[2] = 3;
|
|
syncs[2].lut[3] = 2;
|
|
// 90° conjugated
|
|
syncs[3].lut[0] = 0;
|
|
syncs[3].lut[1] = 2;
|
|
syncs[3].lut[2] = 1;
|
|
syncs[3].lut[3] = 3;
|
|
}
|
|
};
|
|
// dvb_deconvol_sync
|
|
|
|
typedef dvb_deconvol_sync<eucl_ss> dvb_deconvol_sync_soft;
|
|
typedef dvb_deconvol_sync<u8> dvb_deconvol_sync_hard;
|
|
|
|
// BIT ALIGNMENT AND MPEG SYNC DETECTION
|
|
|
|
template <typename Tbyte, Tbyte BYTE_ERASED>
|
|
struct mpeg_sync : runnable
|
|
{
|
|
int scan_syncs, want_syncs;
|
|
unsigned long lock_timeout;
|
|
bool fastlock;
|
|
int resync_period;
|
|
|
|
mpeg_sync(
|
|
scheduler *sch,
|
|
pipebuf<Tbyte> &_in,
|
|
pipebuf<Tbyte> &_out,
|
|
deconvol_sync<Tbyte, 0> *_deconv,
|
|
pipebuf<int> *_state_out = nullptr,
|
|
pipebuf<unsigned long> *_locktime_out = nullptr
|
|
) :
|
|
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<int>(*_state_out) : nullptr;
|
|
locktime_out = _locktime_out ? new pipewriter<unsigned long>(*_locktime_out) : nullptr;
|
|
}
|
|
|
|
~mpeg_sync()
|
|
{
|
|
if (state_out) {
|
|
delete state_out;
|
|
}
|
|
if (locktime_out) {
|
|
delete locktime_out;
|
|
}
|
|
}
|
|
|
|
void run()
|
|
{
|
|
if (report_state && state_out && state_out->writable() >= 1)
|
|
{
|
|
// Report unlocked state on first invocation.
|
|
state_out->write(0);
|
|
report_state = false;
|
|
}
|
|
|
|
if (synchronized)
|
|
{
|
|
run_decoding();
|
|
}
|
|
else
|
|
{
|
|
if (fastlock)
|
|
run_searching_fast();
|
|
else
|
|
run_searching();
|
|
}
|
|
}
|
|
|
|
void run_searching()
|
|
{
|
|
bool next_sync = false;
|
|
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))
|
|
{
|
|
if (search_sync())
|
|
return;
|
|
|
|
in.read(chunk);
|
|
// Switch to next bit alignment
|
|
++bitphase;
|
|
|
|
if (bitphase == 8)
|
|
{
|
|
bitphase = 0;
|
|
next_sync = true;
|
|
}
|
|
}
|
|
|
|
if (next_sync)
|
|
{
|
|
// No lock this time
|
|
++next_sync_count;
|
|
|
|
if (next_sync_count >= 3)
|
|
{
|
|
// After a few cycles without a lock, resync the deconvolver.
|
|
next_sync_count = 0;
|
|
if (deconv)
|
|
deconv->next_sync();
|
|
}
|
|
}
|
|
}
|
|
|
|
void run_searching_fast()
|
|
{
|
|
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))
|
|
{
|
|
if (resync_phase == 0)
|
|
{
|
|
// Try all bit alighments
|
|
for (bitphase = 0; bitphase <= 7; ++bitphase)
|
|
{
|
|
if (search_sync())
|
|
return;
|
|
}
|
|
}
|
|
|
|
in.read(SIZE_RSPACKET);
|
|
|
|
if (++resync_phase >= resync_period)
|
|
resync_phase = 0;
|
|
}
|
|
}
|
|
|
|
bool search_sync()
|
|
{
|
|
int chunk = SIZE_RSPACKET * scan_syncs;
|
|
// Bit-shift [scan_sync] packets according to current [bitphase]
|
|
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 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;
|
|
nsyncs = nsyncs_p;
|
|
phase8 = phase8_p;
|
|
}
|
|
else
|
|
{
|
|
polarity = -1;
|
|
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
|
|
i = SIZE_RSPACKET;
|
|
phase8 = (phase8 + 1) & 7;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void run_decoding()
|
|
{
|
|
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:
|
|
pipereader<Tbyte> in;
|
|
pipewriter<Tbyte> out;
|
|
deconvol_sync<Tbyte, 0> *deconv;
|
|
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
|
|
unsigned long lock_timeleft;
|
|
unsigned long locktime;
|
|
pipewriter<int> *state_out;
|
|
pipewriter<unsigned long> *locktime_out;
|
|
bool report_state;
|
|
};
|
|
|
|
template <typename Tbyte>
|
|
struct rspacket
|
|
{
|
|
Tbyte data[SIZE_RSPACKET];
|
|
};
|
|
|
|
// INTERLEAVER
|
|
|
|
struct interleaver : runnable
|
|
{
|
|
interleaver(
|
|
scheduler *sch,
|
|
pipebuf<rspacket<u8>> &_in,
|
|
pipebuf<u8> &_out
|
|
) :
|
|
runnable(sch, "interleaver"),
|
|
in(_in),
|
|
out(_out, SIZE_RSPACKET)
|
|
{
|
|
}
|
|
|
|
void run()
|
|
{
|
|
while (in.readable() >= 12 && out.writable() >= SIZE_RSPACKET)
|
|
{
|
|
rspacket<u8> *pin = in.rd();
|
|
u8 *pout = out.wr();
|
|
int delay = 0;
|
|
|
|
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<rspacket<u8>> in;
|
|
pipewriter<u8> out;
|
|
};
|
|
// interleaver
|
|
|
|
// DEINTERLEAVER
|
|
|
|
template <typename Tbyte>
|
|
struct deinterleaver : runnable
|
|
{
|
|
deinterleaver(
|
|
scheduler *sch,
|
|
pipebuf<Tbyte> &_in,
|
|
pipebuf<rspacket<Tbyte>> &_out
|
|
) :
|
|
runnable(sch, "deinterleaver"),
|
|
in(_in),
|
|
out(_out)
|
|
{
|
|
}
|
|
|
|
void run()
|
|
{
|
|
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)) {
|
|
*pout = pin[-delay * 12];
|
|
}
|
|
|
|
in.read(SIZE_RSPACKET);
|
|
out.written(1);
|
|
}
|
|
}
|
|
|
|
private:
|
|
pipereader<Tbyte> in;
|
|
pipewriter<rspacket<Tbyte>> out;
|
|
};
|
|
// deinterleaver
|
|
|
|
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
|
|
{
|
|
rs_encoder(
|
|
scheduler *sch,
|
|
pipebuf<tspacket> &_in,
|
|
pipebuf<rspacket<u8>> &_out
|
|
) :
|
|
runnable(sch, "RS encoder"),
|
|
in(_in),
|
|
out(_out)
|
|
{
|
|
}
|
|
|
|
void run()
|
|
{
|
|
while (in.readable() >= 1 && out.writable() >= 1)
|
|
{
|
|
u8 *pin = in.rd()->data;
|
|
u8 *pout = out.wr()->data;
|
|
// The first 188 bytes are the uncoded message P(X)
|
|
memcpy(pout, pin, SIZE_TSPACKET);
|
|
// Append 16 RS parity bytes R(X) = - (P(X)*X^16 mod G(X))
|
|
// so that G(X) divides the coded message S(X) = P(X)*X^16 - R(X).
|
|
rs.encode(pout);
|
|
in.read(1);
|
|
out.written(1);
|
|
}
|
|
}
|
|
|
|
private:
|
|
rs_engine rs;
|
|
pipereader<tspacket> in;
|
|
pipewriter<rspacket<u8>> out;
|
|
};
|
|
// rs_encoder
|
|
|
|
// RS DECODER
|
|
|
|
template <typename Tbyte, int BYTE_ERASED>
|
|
struct rs_decoder : runnable
|
|
{
|
|
rs_engine rs;
|
|
|
|
rs_decoder(
|
|
scheduler *sch,
|
|
pipebuf<rspacket<Tbyte>> &_in,
|
|
pipebuf<tspacket> &_out,
|
|
pipebuf<int> *_bitcount = nullptr,
|
|
pipebuf<int> *_errcount = nullptr
|
|
) :
|
|
runnable(sch, "RS decoder"),
|
|
in(_in),
|
|
out(_out)
|
|
{
|
|
bitcount = _bitcount ? new pipewriter<int>(*_bitcount) : nullptr;
|
|
errcount = _errcount ? new pipewriter<int>(*_errcount) : nullptr;
|
|
}
|
|
|
|
~rs_decoder()
|
|
{
|
|
if (bitcount) {
|
|
delete bitcount;
|
|
}
|
|
if (errcount) {
|
|
delete errcount;
|
|
}
|
|
}
|
|
|
|
void run()
|
|
{
|
|
if (bitcount && bitcount->writable() < 1)
|
|
return;
|
|
if (errcount && errcount->writable() < 1)
|
|
return;
|
|
|
|
int nbits = 0, nerrs = 0;
|
|
|
|
while (in.readable() >= 1 && out.writable() >= 1)
|
|
{
|
|
Tbyte *pin = in.rd()->data;
|
|
u8 *pout = out.wr()->data;
|
|
|
|
nbits += SIZE_RSPACKET * 8;
|
|
|
|
// The message is the first 188 bytes.
|
|
if (sizeof(Tbyte) == 1)
|
|
memcpy(pout, pin, SIZE_TSPACKET);
|
|
else
|
|
fail("Erasures not implemented");
|
|
|
|
u8 synd[16];
|
|
bool corrupted = rs.syndromes(pin, synd);
|
|
|
|
#if 0
|
|
if ( ! corrupted ) {
|
|
// Test BM
|
|
fprintf(stderr, "Simulating errors\n");
|
|
pin[203] ^= 42;
|
|
pin[202] ^= 99;
|
|
corrupted = rs.syndromes(pin, synd);
|
|
}
|
|
#endif
|
|
if (!corrupted)
|
|
{
|
|
if (sch->debug)
|
|
fprintf(stderr, "_"); // Packet received without errors.
|
|
}
|
|
else
|
|
{
|
|
corrupted = rs.correct(synd, pout, pin, &nerrs);
|
|
|
|
if (sch->debug)
|
|
{
|
|
if (!corrupted)
|
|
fprintf(stderr, "."); // Errors were corrected.
|
|
else
|
|
fprintf(stderr, "!"); // Packet still corrupted.
|
|
}
|
|
}
|
|
|
|
in.read(1);
|
|
|
|
// Output corrupted packets (with a special mark)
|
|
// otherwise the derandomizer will lose synchronization.
|
|
if (corrupted)
|
|
pout[0] ^= MPEG_SYNC_CORRUPTED;
|
|
|
|
out.written(1);
|
|
}
|
|
|
|
if (nbits)
|
|
{
|
|
if (bitcount)
|
|
bitcount->write(nbits);
|
|
if (errcount)
|
|
errcount->write(nerrs);
|
|
}
|
|
}
|
|
|
|
private:
|
|
pipereader<rspacket<Tbyte>> in;
|
|
pipewriter<tspacket> out;
|
|
pipewriter<int> *bitcount, *errcount;
|
|
};
|
|
// rs_decoder
|
|
|
|
// RANDOMIZER
|
|
|
|
struct randomizer : runnable
|
|
{
|
|
randomizer(
|
|
scheduler *sch,
|
|
pipebuf<tspacket> &_in,
|
|
pipebuf<tspacket> &_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 = 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
|
|
}
|
|
|
|
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)
|
|
fprintf(stderr, "randomizer: bad MPEG sync %02x\n", pin[0]);
|
|
for (; pin < pend; ++pin, ++pout, ++pos)
|
|
*pout = *pin ^ *pos;
|
|
if (pos == pattern_end)
|
|
pos = pattern;
|
|
in.read(1);
|
|
out.written(1);
|
|
}
|
|
}
|
|
|
|
private:
|
|
u8 pattern[188 * 8], *pattern_end, *pos;
|
|
pipereader<tspacket> in;
|
|
pipewriter<tspacket> out;
|
|
};
|
|
// randomizer
|
|
|
|
// DERANDOMIZER
|
|
|
|
struct derandomizer : runnable
|
|
{
|
|
derandomizer(
|
|
scheduler *sch,
|
|
pipebuf<tspacket> &_in,
|
|
pipebuf<tspacket> &_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 = 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
|
|
}
|
|
|
|
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 (pos != pattern)
|
|
{
|
|
if (sch->debug)
|
|
fprintf(stderr, "derandomizer: resynchronizing\n");
|
|
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);
|
|
}
|
|
else
|
|
{
|
|
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.
|
|
//out.written(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
u8 pattern[188 * 8], *pattern_end, *pos;
|
|
pipereader<tspacket> in;
|
|
pipewriter<tspacket> out;
|
|
};
|
|
// derandomizer
|
|
|
|
// VITERBI DECODING
|
|
// Supports all code rates and constellations
|
|
// Simplified metric to support large constellations.
|
|
|
|
// This version implements puncturing by expanding the trellis.
|
|
// TBD Compare performance vs skipping updates in a 1/2 trellis.
|
|
|
|
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 TPM;
|
|
typedef viterbi_dec_interface<TUS, TCS, TBM, TPM> dvb_dec_interface;
|
|
|
|
// 1/2: 6 bits of state, 1 bit in, 2 bits out
|
|
typedef bitpath<uint32_t, TUS, 1, 32> path_12;
|
|
typedef trellis<TS, 64, TUS, 2, 4> trellis_12;
|
|
typedef viterbi_dec<TS, 64, TUS, 2, TCS, 4, TBM, TPM, path_12> dvb_dec_12;
|
|
|
|
// 2/3: 6 bits of state, 2 bits in, 3 bits out
|
|
typedef bitpath<uint64_t, TUS, 3, 21> path_23;
|
|
typedef trellis<TS, 64, TUS, 4, 8> trellis_23;
|
|
typedef viterbi_dec<TS, 64, TUS, 4, TCS, 8, TBM, TPM, path_23> dvb_dec_23;
|
|
|
|
// 4/6: 6 bits of state, 4 bits in, 6 bits out
|
|
typedef bitpath<uint64_t, TUS, 4, 16> path_46;
|
|
typedef trellis<TS, 64, TUS, 16, 64> trellis_46;
|
|
typedef viterbi_dec<TS, 64, TUS, 16, TCS, 64, TBM, TPM, path_46> dvb_dec_46;
|
|
|
|
// 3/4: 6 bits of state, 3 bits in, 4 bits out
|
|
typedef bitpath<uint64_t, TUS, 3, 21> path_34;
|
|
typedef trellis<TS, 64, TUS, 8, 16> trellis_34;
|
|
typedef viterbi_dec<TS, 64, TUS, 8, TCS, 16, TBM, TPM, path_34> dvb_dec_34;
|
|
|
|
// 4/5: 6 bits of state, 4 bits in, 5 bits out (non-standard)
|
|
typedef bitpath<uint64_t, TUS, 4, 16> path_45;
|
|
typedef trellis<TS, 64, TUS, 16, 32> trellis_45;
|
|
typedef viterbi_dec<TS, 64, TUS, 16, TCS, 32, TBM, TPM, path_45> dvb_dec_45;
|
|
|
|
// 5/6: 6 bits of state, 5 bits in, 6 bits out
|
|
typedef bitpath<uint64_t, TUS, 5, 12> path_56;
|
|
typedef trellis<TS, 64, TUS, 32, 64> trellis_56;
|
|
typedef viterbi_dec<TS, 64, TUS, 32, TCS, 64, TBM, TPM, path_56> dvb_dec_56;
|
|
|
|
// 7/8: 6 bits of state, 7 bits in, 8 bits out
|
|
typedef bitpath<uint64_t, TUS, 7, 9> path_78;
|
|
typedef trellis<TS, 64, TUS, 128, 256> trellis_78;
|
|
typedef viterbi_dec<TS, 64, TUS, 128, TCS, 256, TBM, TPM, path_78> dvb_dec_78;
|
|
|
|
private:
|
|
pipereader<eucl_ss> in;
|
|
pipewriter<unsigned char> out;
|
|
cstln_lut<eucl_ss, 256> *cstln;
|
|
fec_spec *fec;
|
|
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]
|
|
|
|
int current_sync;
|
|
static const int chunk_size = 128;
|
|
int resync_phase;
|
|
|
|
public:
|
|
int resync_period;
|
|
|
|
viterbi_sync(
|
|
scheduler *sch,
|
|
pipebuf<eucl_ss> &_in,
|
|
pipebuf<unsigned char> &_out,
|
|
cstln_lut<eucl_ss, 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
|
|
{
|
|
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("Code rate not suitable for this constellation");
|
|
}
|
|
|
|
int nconj;
|
|
|
|
switch (cstln->nsymbols)
|
|
{
|
|
case 2:
|
|
nconj = 1;
|
|
break; // Conjugation is not relevant for BPSK
|
|
default:
|
|
nconj = 2;
|
|
break;
|
|
}
|
|
|
|
int nrotations;
|
|
|
|
switch (cstln->nsymbols)
|
|
{
|
|
case 2:
|
|
case 4:
|
|
// For BPSK and QPSK, 180° rotation is handled as
|
|
// polarity inversion in mpeg_sync.
|
|
nrotations = cstln->nrotations / 2;
|
|
break;
|
|
default:
|
|
nrotations = cstln->nrotations;
|
|
break;
|
|
}
|
|
|
|
nshifts = fec->bits_out / bits_per_symbol;
|
|
nsyncs = nconj * nrotations * nshifts;
|
|
|
|
// TBD Many HOM constellations are labelled in such a way
|
|
// that certain rot/conj combinations are equivalent to
|
|
// polarity inversion. We could reduce nsyncs.
|
|
|
|
syncs = new sync[nsyncs];
|
|
|
|
for (int s = 0; s < nsyncs; ++s)
|
|
{
|
|
// Bit pattern [shift|conj|rot]
|
|
int rot = s % nrotations;
|
|
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);
|
|
#if 0
|
|
fprintf(stderr, "sync %3d: conj%d offs%d rot%d/%d map:",
|
|
s, conj, syncs[s].shift, rot, cstln->nrotations);
|
|
for ( int i=0; i<cstln->nsymbols; ++i )
|
|
fprintf(stderr, " %2d", syncs[s].map[i]);
|
|
fprintf(stderr, "\n");
|
|
#endif
|
|
}
|
|
|
|
if (cr == FEC12)
|
|
{
|
|
trellis_12 *trell = new trellis_12();
|
|
trell->init_convolutional(fec->polys);
|
|
for (int s = 0; s < nsyncs; ++s)
|
|
syncs[s].dec = new dvb_dec_12(trell);
|
|
}
|
|
else if (cr == FEC23)
|
|
{
|
|
trellis_23 *trell = new trellis_23();
|
|
trell->init_convolutional(fec->polys);
|
|
for (int s = 0; s < nsyncs; ++s)
|
|
syncs[s].dec = new dvb_dec_23(trell);
|
|
}
|
|
else if (cr == FEC46)
|
|
{
|
|
trellis_46 *trell = new trellis_46();
|
|
trell->init_convolutional(fec->polys);
|
|
for (int s = 0; s < nsyncs; ++s)
|
|
syncs[s].dec = new dvb_dec_46(trell);
|
|
}
|
|
else if (cr == FEC34)
|
|
{
|
|
trellis_34 *trell = new trellis_34();
|
|
trell->init_convolutional(fec->polys);
|
|
for (int s = 0; s < nsyncs; ++s)
|
|
syncs[s].dec = new dvb_dec_34(trell);
|
|
}
|
|
else if (cr == FEC45)
|
|
{
|
|
trellis_45 *trell = new trellis_45();
|
|
trell->init_convolutional(fec->polys);
|
|
for (int s = 0; s < nsyncs; ++s)
|
|
syncs[s].dec = new dvb_dec_45(trell);
|
|
}
|
|
else if (cr == FEC56)
|
|
{
|
|
trellis_56 *trell = new trellis_56();
|
|
trell->init_convolutional(fec->polys);
|
|
for (int s = 0; s < nsyncs; ++s)
|
|
syncs[s].dec = new dvb_dec_56(trell);
|
|
}
|
|
else if (cr == FEC78)
|
|
{
|
|
trellis_78 *trell = new trellis_78();
|
|
trell->init_convolutional(fec->polys);
|
|
for (int s = 0; s < nsyncs; ++s)
|
|
syncs[s].dec = new dvb_dec_78(trell);
|
|
}
|
|
else
|
|
{
|
|
fail("CR not supported");
|
|
}
|
|
}
|
|
|
|
~viterbi_sync()
|
|
{
|
|
delete syncs;
|
|
}
|
|
|
|
TCS *init_map(bool conj, float angle)
|
|
{
|
|
// Each constellation has its own pattern for labels.
|
|
// 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].real();
|
|
int8_t Q = cstln->symbols[i].imag();
|
|
|
|
if (conj)
|
|
Q = -Q;
|
|
|
|
int8_t RI = I * ca - Q * sa;
|
|
int8_t RQ = I * sa + Q * ca;
|
|
cstln_lut<eucl_ss, 256>::result *pr = cstln->lookup(RI, RQ);
|
|
map[i] = pr->ss.nearest;
|
|
}
|
|
|
|
return map;
|
|
}
|
|
|
|
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->nearest];
|
|
cost -= pin->discr2;
|
|
}
|
|
|
|
return syncs[s].dec->update(cs, cost, discr);
|
|
}
|
|
|
|
void run()
|
|
{
|
|
// Number of FEC blocks to fill the bitpath depth.
|
|
// Before that we cannot discriminate between synchronizers
|
|
int discr_delay = 64 / fec->bits_in;
|
|
|
|
// Process [chunk_size] FEC blocks at a time
|
|
|
|
TPM *totaldiscr = new TPM[nsyncs];
|
|
|
|
while ((long)in.readable() >= nshifts * chunk_size + (nshifts - 1) && (long)out.writable() * 8 >= fec->bits_in * chunk_size)
|
|
{
|
|
for (int s = 0; s < nsyncs; ++s)
|
|
totaldiscr[s] = 0;
|
|
|
|
uint64_t outstream = 0;
|
|
int nout = 0;
|
|
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.
|
|
for (int s = 0; s < nsyncs; ++s)
|
|
{
|
|
if (s == current_sync)
|
|
continue;
|
|
|
|
TPM 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
|
|
|
|
in.read(chunk_size * nshifts);
|
|
|
|
if (nout)
|
|
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)
|
|
fprintf(stderr, "{%d->%d}", current_sync, best);
|
|
current_sync = best;
|
|
}
|
|
}
|
|
|
|
if (++resync_phase >= resync_period)
|
|
resync_phase = 0;
|
|
}
|
|
|
|
delete[] totaldiscr;
|
|
}
|
|
};
|
|
// viterbi_sync
|
|
|
|
} // namespace leansdr
|
|
|
|
#endif // LEANSDR_DVB_H
|