kopia lustrzana https://github.com/pabr/leansdr
1649 wiersze
54 KiB
C++
1649 wiersze
54 KiB
C++
// This file is part of LeanSDR Copyright (C) 2016-2022 <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/>.
|
|
|
|
|
|
// http://www.pabr.org/radio/leandvb
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include <fcntl.h>
|
|
|
|
#include "leansdr/framework.h"
|
|
#include "leansdr/generic.h"
|
|
#include "leansdr/dsp.h"
|
|
#include "leansdr/sdr.h"
|
|
#include "leansdr/dvb.h"
|
|
#include "leansdr/dvbs2.h"
|
|
#include "leansdr/rs.h"
|
|
#include "leansdr/gui.h"
|
|
#include "leansdr/filtergen.h"
|
|
#include "leansdr/network.h"
|
|
|
|
#include "leansdr/hdlc.h"
|
|
#include "leansdr/iess.h"
|
|
|
|
using namespace leansdr;
|
|
|
|
// Main loop
|
|
|
|
struct config {
|
|
bool verbose, debug, debug2;
|
|
bool highspeed; // Demodulate raw u8 I/Q without preprocessing
|
|
enum {
|
|
INPUT_U8, INPUT_S8,
|
|
INPUT_S12, INPUT_S16,
|
|
INPUT_F32
|
|
} input_format;
|
|
float float_scale; // Scaling factor for float data.
|
|
bool loop_input;
|
|
int input_pipe; // Resize stdin pipe, or 0
|
|
int input_buffer; // Extra input buffer size
|
|
int buf_factor; // Buffer sizing
|
|
float Fs; // Sampling frequency (Hz)
|
|
float Fderot; // Shift the signal (Hz). Note: Ftune is faster
|
|
int anf; // Number of auto notch filters
|
|
bool cnr; // Measure CNR
|
|
unsigned int decim; // Decimation, 0=auto
|
|
int fd_pp; // FD for preprocessed data, or -1
|
|
int fd_gse; // FD for DVB-S2 Generic streams, or -1
|
|
int fd_iqsymbols; // FD for sampled symbols, or -1
|
|
float awgn; // Standard deviation of noise
|
|
|
|
float Fm; // QPSK symbol rate (Hz)
|
|
enum dvb_version { DVB_S, DVB_S2 } standard;
|
|
cstln_base::predef constellation;
|
|
bool strongpls; // For S2 APSK, expect PLS symbols at maximum amplitude
|
|
uint32_t modcods; // Bitmask of desired S2 modcods
|
|
uint8_t framesizes; // Bitmask of desired S2 frame sizes
|
|
code_rate fec;
|
|
float Ftune; // Bias frequency for the QPSK demodulator (Hz)
|
|
bool allow_drift;
|
|
float freq_tol;
|
|
float sr_tol;
|
|
bool fastlock;
|
|
bool fastdrift;
|
|
bool viterbi;
|
|
bool hard_metric;
|
|
int ldpc_bf;
|
|
const char *ldpc_helper;
|
|
int nhelpers;
|
|
bool resample;
|
|
float resample_rej; // Approx. filter rejection in dB
|
|
enum { SAMP_NEAREST, SAMP_LINEAR, SAMP_RRC } sampler;
|
|
int rrc_steps; // Discrete steps between symbols, 0=auto
|
|
float rrc_rej; // Approx. RRC filter rejection in dB
|
|
float rolloff; // Roll-off 0..1
|
|
|
|
bool hdlc; // Expect HDLC frames instead of MPEG packets
|
|
bool packetized; // Output frames with 16-bit BE length
|
|
|
|
bool gui; // Plot stuff
|
|
float duration; // Horizontal span of timeline GUI (s)
|
|
bool linger; // Keep GUI running after EOF
|
|
int fd_info; // FD for status information in text format, or -1
|
|
float Finfo; // Desired refresh rate on fd_info (Hz)
|
|
int fd_const; // FD for constellation and symbols, or -1
|
|
int fd_spectrum; // FD for spectrum data, or -1
|
|
bool json; // Use JSON syntax
|
|
const char *udp_dst; // Output TS to this IP:PORT instead of stdout
|
|
|
|
config()
|
|
: verbose(false),
|
|
debug(false),
|
|
debug2(false),
|
|
highspeed(false),
|
|
input_format(INPUT_U8),
|
|
float_scale(1.0),
|
|
loop_input(false),
|
|
input_pipe(0),
|
|
input_buffer(0),
|
|
buf_factor(4),
|
|
Fs(2.4e6),
|
|
Fderot(0),
|
|
anf(0),
|
|
cnr(false),
|
|
decim(0),
|
|
fd_pp(-1),
|
|
fd_gse(-1),
|
|
fd_iqsymbols(-1),
|
|
awgn(0),
|
|
Fm(2e6),
|
|
standard(DVB_S),
|
|
constellation(cstln_base::QPSK),
|
|
strongpls(false),
|
|
modcods(0xffffffff),
|
|
framesizes(0x03),
|
|
fec(FEC12),
|
|
Ftune(0),
|
|
allow_drift(false),
|
|
freq_tol(0.25),
|
|
sr_tol(100e-6),
|
|
fastlock(false),
|
|
fastdrift(false),
|
|
viterbi(false),
|
|
hard_metric(false),
|
|
ldpc_bf(0),
|
|
ldpc_helper(NULL),
|
|
nhelpers(1),
|
|
resample(false),
|
|
resample_rej(10),
|
|
sampler(config::SAMP_LINEAR),
|
|
rrc_steps(0),
|
|
rrc_rej(10),
|
|
rolloff(0.35),
|
|
|
|
hdlc(false),
|
|
packetized(false),
|
|
|
|
gui(false),
|
|
duration(60),
|
|
linger(false),
|
|
fd_info(-1),
|
|
Finfo(5),
|
|
fd_const(-1),
|
|
fd_spectrum(-1),
|
|
json(false),
|
|
udp_dst(NULL) {
|
|
}
|
|
};
|
|
|
|
int decimation(float Fin, float Fout) {
|
|
int d = Fin / Fout;
|
|
return max(d, 1);
|
|
}
|
|
|
|
void output_initial_info(FILE *f, const config &cfg) {
|
|
const char *quote = cfg.json ? "\"" : "";
|
|
static const char *standard_names[] = {
|
|
[config::DVB_S]="DVB-S",
|
|
[config::DVB_S2]="DVB-S2",
|
|
};
|
|
fprintf(f, "STANDARD %s%s%s\n", quote, standard_names[cfg.standard], quote);
|
|
if ( cfg.standard == config::DVB_S ) {
|
|
fprintf(f, "CONSTELLATION %s%s%s\n",
|
|
quote, cstln_base::names[cfg.constellation], quote);
|
|
fec_spec *fs = &fec_specs[cfg.fec];
|
|
fprintf(f, "CR %s%d/%d%s\n", quote, fs->bits_in, fs->bits_out, quote);
|
|
}
|
|
fprintf(f, "SR %f\n", cfg.Fm);
|
|
}
|
|
|
|
struct runtime_common {
|
|
scheduler *sch;
|
|
pipebuf<cf32> *p_rawiq;
|
|
fir_filter<cf32,float> *r_resample;
|
|
pipebuf<cf32> *p_preprocessed;
|
|
float Fspp; // cfg.Fs adjusted after resampling/decimation
|
|
int decimpp; // cfg.decim or auto
|
|
int rrc_steps; // cfg.rrc_steps or auto-selected
|
|
pipebuf<f32> *p_freq;
|
|
pipebuf<f32> *p_ss;
|
|
pipebuf<f32> *p_cnr;
|
|
cnr_fft<f32> *r_cnr;
|
|
pipebuf<f32> *p_mer;
|
|
pipebuf<cf32> *p_cstln;
|
|
pipebuf<cf32> *p_cstln_pls;
|
|
sampler_interface<f32> *sampler;
|
|
pipebuf<cf32> *p_iqsymbols; // DIVERSITY
|
|
pipebuf<int> *p_framelock;
|
|
pipebuf<float> *p_vber;
|
|
pipebuf<int> *p_lock;
|
|
pipebuf<unsigned long> *p_locktime;
|
|
#ifdef GUI
|
|
cscope<f32> *r_scope_cstln;
|
|
cscope<f32> *r_scope_cstln_pls;
|
|
pipebuf<float> *p_tscount;
|
|
#endif
|
|
pipebuf<int> *p_vbitcount;
|
|
pipebuf<int> *p_verrcount;
|
|
|
|
pipebuf<tspacket> *p_tspackets;
|
|
|
|
runtime_common(const config &cfg) {
|
|
sch = new scheduler();
|
|
sch->verbose = cfg.verbose;
|
|
sch->debug = cfg.debug;
|
|
sch->debug2 = cfg.debug2;
|
|
|
|
int w_timeline = 512, h_timeline = 256;
|
|
int w_fft = 1024, h_fft = 256;
|
|
int wh_const = 256;
|
|
|
|
int x0 = 100, y0 = 20;
|
|
|
|
static window_placement window_hints[] = {
|
|
{ "rawiq (iq)", x0, y0, wh_const,wh_const },
|
|
{ "rawiq (spectrum)", x0+300, y0, w_fft, h_fft },
|
|
{ "preprocessed (iq)", x0, y0+300, wh_const, wh_const },
|
|
{ "preprocessed (spectrum)", x0+300, y0+300, w_fft, h_fft },
|
|
{ "PSK symbols", x0, y0+600, wh_const, wh_const }, // TBD obsolete
|
|
{ "cstln", x0, y0+600, wh_const, wh_const },
|
|
{ "PLS cstln", x0+300, y0+600, wh_const, wh_const },
|
|
{ "timeline", x0+600, y0+600, w_timeline, h_timeline },
|
|
{ NULL, }
|
|
};
|
|
sch->windows = window_hints;
|
|
|
|
unsigned long S2_MAX_SYMBOLS = (90*(1+360)+36*((360-1)/16));
|
|
// Min buffer size for baseband data
|
|
// scopes: 1024
|
|
// ss_estimator: 1024
|
|
// anf: 4096
|
|
unsigned long BUF_BASEBAND;
|
|
switch ( cfg.standard ) {
|
|
case config::DVB_S:
|
|
// cstln_receiver reads in chunks of 128+1
|
|
BUF_BASEBAND = 4096 * cfg.buf_factor;
|
|
break;
|
|
case config::DVB_S2:
|
|
// s2_frame_receiver need enough samples for one frame
|
|
BUF_BASEBAND = S2_MAX_SYMBOLS * 2 * (cfg.Fs/cfg.Fm) * cfg.buf_factor;
|
|
break;
|
|
default:
|
|
fail("not implemented");
|
|
}
|
|
|
|
// Min buffer size for TS packets: Up to 39 per BBFRAME
|
|
unsigned long BUF_S2PACKETS = (fec_info::KBCH_MAX/188/8+1) * cfg.buf_factor;
|
|
// Min buffer size for misc measurements: 1
|
|
unsigned long BUF_SLOW = cfg.buf_factor;
|
|
|
|
// INPUT
|
|
|
|
if ( cfg.input_pipe ) {
|
|
if ( fcntl(0, F_SETPIPE_SZ, cfg.input_pipe) < 0 ) {
|
|
if ( errno == EBADF )
|
|
fprintf(stderr, "--inpipe: Standard input is not a pipe\n");
|
|
else
|
|
perror("--inpipe");
|
|
if ( errno == EPERM ) {
|
|
fprintf(stderr, "Try 'echo %d > /proc/sys/fs/pipe-max-size'\n",
|
|
cfg.input_pipe);
|
|
fprintf(stderr, "Increase /proc/sys/fs/pipe-user-pages-soft\n");
|
|
}
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
p_rawiq = new pipebuf<cf32>(sch, "rawiq", BUF_BASEBAND);
|
|
|
|
float amp = 1.0;
|
|
|
|
switch ( cfg.input_format ) {
|
|
case config::INPUT_U8: {
|
|
pipebuf<cu8> *p_stdin =
|
|
new pipebuf<cu8>(sch, "stdin", BUF_BASEBAND+cfg.input_buffer);
|
|
file_reader<cu8> *r_stdin =
|
|
new file_reader<cu8>(sch, 0, *p_stdin);
|
|
r_stdin->loop = cfg.loop_input;
|
|
cconverter<u8,128, f32,0, 1,1> *r_convert =
|
|
new cconverter<u8,128, f32,0, 1,1>(sch, *p_stdin, *p_rawiq);
|
|
amp = 128;
|
|
break;
|
|
}
|
|
case config::INPUT_S8: {
|
|
pipebuf<cs8> *p_stdin =
|
|
new pipebuf<cs8>(sch, "stdin", BUF_BASEBAND+cfg.input_buffer);
|
|
file_reader<cs8> *r_stdin =
|
|
new file_reader<cs8>(sch, 0, *p_stdin);
|
|
r_stdin->loop = cfg.loop_input;
|
|
cconverter<s8,0, f32,0, 1,1> *r_convert =
|
|
new cconverter<s8,0, f32,0, 1,1>(sch, *p_stdin, *p_rawiq);
|
|
amp = 128;
|
|
break;
|
|
}
|
|
case config::INPUT_S12:
|
|
case config::INPUT_S16: {
|
|
pipebuf<cs16> *p_stdin =
|
|
new pipebuf<cs16>(sch, "stdin", BUF_BASEBAND+cfg.input_buffer);
|
|
file_reader<cs16> *r_stdin =
|
|
new file_reader<cs16>(sch, 0, *p_stdin);
|
|
r_stdin->loop = cfg.loop_input;
|
|
cconverter<s16,0, f32,0, 1,1> *r_convert =
|
|
new cconverter<s16,0, f32,0, 1,1>(sch, *p_stdin, *p_rawiq);
|
|
amp = (cfg.input_format==config::INPUT_S12 ? 2048 : 32768);
|
|
break;
|
|
}
|
|
case config::INPUT_F32: {
|
|
pipebuf<cf32> *p_stdin =
|
|
new pipebuf<cf32>(sch, "stdin", BUF_BASEBAND+cfg.input_buffer);
|
|
file_reader<cf32> *r_stdin =
|
|
new file_reader<cf32>(sch, 0, *p_stdin);
|
|
r_stdin->loop = cfg.loop_input;
|
|
scaler<float,cf32,cf32> *r_scale =
|
|
new scaler<float,cf32,cf32>(sch, cfg.float_scale, *p_stdin, *p_rawiq);
|
|
amp = 2.0;
|
|
break;
|
|
}
|
|
default:
|
|
fail("Input format not implemented");
|
|
}
|
|
|
|
p_preprocessed = p_rawiq;
|
|
|
|
#ifdef GUI
|
|
if ( cfg.gui ) {
|
|
cscope<f32> *r_cscope_raw =
|
|
new cscope<f32>(sch, *p_rawiq, -amp, amp, "rawiq (iq)");
|
|
spectrumscope<f32> *r_fft_raw =
|
|
new spectrumscope<f32>(sch, *p_rawiq, amp, "rawiq (spectrum)");
|
|
r_fft_raw->amax *= sqrtf(cfg.Fs/cfg.Fm);
|
|
r_fft_raw->mark_freq((cfg.Fderot+cfg.Ftune-cfg.Fm/2) / cfg.Fs);
|
|
r_fft_raw->mark_freq((cfg.Fderot+cfg.Ftune+cfg.Fm/2) / cfg.Fs);
|
|
}
|
|
#else
|
|
(void)amp; // GCC warning
|
|
#endif
|
|
|
|
// NOISE
|
|
|
|
if ( cfg.awgn ) {
|
|
if ( cfg.verbose )
|
|
fprintf(stderr, "Adding noise with stddev %f\n", cfg.awgn);
|
|
pipebuf<cf32> *p_noise =
|
|
new pipebuf<cf32>(sch, "noise", BUF_BASEBAND);
|
|
wgn_c<f32> *r_noise =
|
|
new wgn_c<f32>(sch, *p_noise);
|
|
r_noise->stddev = cfg.awgn;
|
|
pipebuf<cf32> *p_noisy =
|
|
new pipebuf<cf32>(sch, "noisy", BUF_BASEBAND);
|
|
adder<cf32> *r_addnoise =
|
|
new adder<cf32>(sch, *p_preprocessed, *p_noise, *p_noisy);
|
|
p_preprocessed = p_noisy;
|
|
}
|
|
|
|
// NOTCH FILTER
|
|
|
|
if ( cfg.anf ) {
|
|
pipebuf<cf32> *p_autonotched =
|
|
new pipebuf<cf32>(sch, "autonotched", BUF_BASEBAND);
|
|
auto_notch<f32> *r_auto_notch =
|
|
new auto_notch<f32>(sch, *p_preprocessed, *p_autonotched,
|
|
cfg.anf, 0);
|
|
p_preprocessed = p_autonotched;
|
|
} else {
|
|
if ( cfg.verbose )
|
|
fprintf(stderr, "ANF is disabled (requires a clean signal).\n");
|
|
}
|
|
|
|
// FREQUENCY CORRECTION
|
|
|
|
if ( cfg.Fderot ) {
|
|
if ( cfg.verbose )
|
|
fprintf(stderr, "Derotating from %.3f kHz\n", cfg.Fderot/1e3);
|
|
pipebuf<cf32> *p_derot =
|
|
new pipebuf<cf32>(sch, "derotated", BUF_BASEBAND);
|
|
rotator<f32> *r_derot =
|
|
new rotator<f32>(sch, *p_preprocessed, *p_derot, -cfg.Fderot/cfg.Fs);
|
|
p_preprocessed = p_derot;
|
|
}
|
|
|
|
// CNR ESTIMATION
|
|
|
|
p_cnr = new pipebuf<f32>(sch, "cnr", BUF_SLOW);
|
|
if ( cfg.cnr ) {
|
|
if ( cfg.verbose )
|
|
fprintf(stderr, "Measuring CNR\n");
|
|
r_cnr = new cnr_fft<f32>(sch, *p_preprocessed, *p_cnr, cfg.Fm/cfg.Fs);
|
|
r_cnr->decimation = decimation(cfg.Fs, 1); // 1 Hz
|
|
} else {
|
|
r_cnr = NULL;
|
|
}
|
|
|
|
// SPECTRUM
|
|
|
|
pipebuf<f32[1024]> *p_spectrum = NULL;
|
|
|
|
if ( cfg.fd_spectrum ) {
|
|
if ( cfg.verbose )
|
|
fprintf(stderr, "Measuring SPD\n");
|
|
p_spectrum = new pipebuf<float[1024]>(sch, "spectrum", BUF_SLOW);
|
|
spectrum<f32,1024> *r_spectrum =
|
|
new spectrum<f32,1024>(sch, *p_preprocessed, *p_spectrum);
|
|
r_spectrum->decimation = decimation(cfg.Fs, 1); // 1 Hz
|
|
r_spectrum->kavg = 0.5;
|
|
}
|
|
|
|
// FILTERING
|
|
|
|
if ( cfg.verbose ) fprintf(stderr, "Roll-off %g\n", cfg.rolloff);
|
|
|
|
Fspp = cfg.Fs;
|
|
|
|
decimpp = 1;
|
|
|
|
r_resample = NULL;
|
|
if ( cfg.resample ) {
|
|
// Lowpass-filter and decimate.
|
|
if ( cfg.decim )
|
|
decimpp = cfg.decim;
|
|
else {
|
|
// Decimate to just above 4 samples per symbol
|
|
float target_Fs = cfg.Fm * 4;
|
|
decimpp = cfg.Fs / target_Fs;
|
|
if ( decimpp < 1 ) decimpp = 1;
|
|
}
|
|
float transition = (cfg.Fm/2) * cfg.rolloff;
|
|
int order = cfg.resample_rej * cfg.Fs / (22*transition);
|
|
order = ((order+1)/2) * 2; // Make even
|
|
if ( cfg.verbose )
|
|
fprintf(stderr, "Inserting filter: order %d, decimation %d.\n",
|
|
order, decimpp);
|
|
pipebuf<cf32> *p_resampled =
|
|
new pipebuf<cf32>(sch, "resampled", BUF_BASEBAND);
|
|
float *coeffs;
|
|
#if 1 // Cut in middle of roll-off region
|
|
float Fcut = (cfg.Fm/2) * (1+cfg.rolloff/2) / cfg.Fs;
|
|
#else // Cut at beginning of roll-off region
|
|
float Fcut = (cfg.Fm/2) / cfg.Fs;
|
|
#endif
|
|
int ncoeffs = filtergen::lowpass(order, Fcut, &coeffs);
|
|
filtergen::normalize_dcgain(ncoeffs, coeffs, 1);
|
|
if ( cfg.debug ) filtergen::dump_filter("lowpass", ncoeffs, coeffs);
|
|
r_resample = new fir_filter<cf32,float>
|
|
(sch, ncoeffs, coeffs, *p_preprocessed, *p_resampled, decimpp);
|
|
(void)r_resample; // gcc warning
|
|
p_preprocessed = p_resampled;
|
|
Fspp /= decimpp;
|
|
}
|
|
|
|
// DECIMATION
|
|
// (Unless already done in resampler)
|
|
|
|
if ( !cfg.resample && cfg.decim>1 ) {
|
|
decimpp = cfg.decim;
|
|
if ( cfg.verbose )
|
|
fprintf(stderr, "Inserting decimator 1/%u\n", decimpp);
|
|
pipebuf<cf32> *p_decimated =
|
|
new pipebuf<cf32>(sch, "decimated", BUF_BASEBAND);
|
|
decimator<cf32> *p_decim =
|
|
new decimator<cf32>(sch, decimpp, *p_preprocessed, *p_decimated);
|
|
p_preprocessed = p_decimated;
|
|
Fspp /= decimpp;
|
|
}
|
|
|
|
if ( cfg.verbose )
|
|
fprintf(stderr, "Fs after resampling/decimation: %f Hz\n", Fspp);
|
|
|
|
#ifdef GUI
|
|
if ( cfg.gui ) {
|
|
cscope<f32> *r_cscope_pp =
|
|
new cscope<f32>(sch, *p_preprocessed, -amp, amp, "preprocessed (iq)");
|
|
spectrumscope<f32> *r_fft_pp =
|
|
new spectrumscope<f32>(sch, *p_preprocessed, amp,
|
|
"preprocessed (spectrum)");
|
|
r_fft_pp->amax *= sqrtf(Fspp/cfg.Fm);
|
|
r_fft_pp->mark_freq((cfg.Ftune-cfg.Fm/2) / Fspp);
|
|
r_fft_pp->mark_freq((cfg.Ftune+cfg.Fm/2) / Fspp);
|
|
r_fft_pp->decimation /= decimpp;
|
|
}
|
|
#endif
|
|
|
|
// OUTPUT PREPROCESSED DATA
|
|
|
|
if ( cfg.fd_pp >= 0 ) {
|
|
if ( cfg.verbose )
|
|
fprintf(stderr, "Writing preprocessed data to FD %d\n", cfg.fd_pp);
|
|
file_writer<cf32> *r_ppout =
|
|
new file_writer<cf32>(sch, *p_preprocessed, cfg.fd_pp);
|
|
}
|
|
|
|
// RECEIVER
|
|
|
|
p_freq = new pipebuf<f32>(sch, "freq", BUF_SLOW);
|
|
p_ss = new pipebuf<f32>(sch, "SS", BUF_SLOW);
|
|
p_mer = new pipebuf<f32>(sch, "MER", BUF_SLOW);
|
|
p_cstln = new pipebuf<cf32>(sch, "cstln", BUF_BASEBAND);
|
|
p_cstln_pls = new pipebuf<cf32>(sch, "PLS cstln", BUF_BASEBAND);
|
|
p_framelock = new pipebuf<int>(sch, "frame lock", BUF_SLOW);
|
|
p_vber = new pipebuf<float>(sch, "VBER", BUF_SLOW);
|
|
p_lock = new pipebuf<int>(sch, "lock", BUF_SLOW*2);
|
|
p_locktime = new pipebuf<unsigned long>(sch, "locktime", BUF_S2PACKETS);
|
|
|
|
rrc_steps = cfg.rrc_steps;
|
|
|
|
switch ( cfg.sampler ) {
|
|
case config::SAMP_NEAREST:
|
|
sampler = new nearest_sampler<float>();
|
|
break;
|
|
case config::SAMP_LINEAR:
|
|
sampler = new linear_sampler<float>();
|
|
break;
|
|
case config::SAMP_RRC: {
|
|
float *coeffs;
|
|
if ( rrc_steps == 0 ) {
|
|
// At least 64 discrete sampling points between symbols
|
|
rrc_steps = max(1, (int)(64*cfg.Fm / Fspp));
|
|
}
|
|
if ( cfg.verbose )
|
|
fprintf(stderr, "RRC interpolator: %d steps\n", rrc_steps);
|
|
float Frrc = Fspp * rrc_steps; // Sample freq of the RRC filter
|
|
float transition = (cfg.Fm/2) * cfg.rolloff;
|
|
int order = cfg.rrc_rej * Frrc / (22*transition);
|
|
int ncoeffs = filtergen::root_raised_cosine
|
|
(order, cfg.Fm/Frrc, cfg.rolloff, &coeffs);
|
|
// Total gain: rrc_steps * stride through the coeffs = 1
|
|
filtergen::normalize_dcgain(ncoeffs, coeffs, rrc_steps);
|
|
if ( cfg.verbose )
|
|
fprintf(stderr, "RRC filter: %d coeffs.\n", ncoeffs);
|
|
if ( cfg.debug2 ) filtergen::dump_filter("rrc", ncoeffs, coeffs);
|
|
sampler = new fir_sampler<float,float>
|
|
(ncoeffs, coeffs, rrc_steps);
|
|
break;
|
|
}
|
|
default:
|
|
fatal("Interpolator not implemented");
|
|
}
|
|
|
|
if ( cfg.fd_iqsymbols >= 0 ) {
|
|
p_iqsymbols = new pipebuf<cf32> (sch, "cstln", BUF_BASEBAND);
|
|
(void)new file_writer<cf32>(sch, *p_iqsymbols, cfg.fd_iqsymbols);
|
|
} else {
|
|
p_iqsymbols = NULL;
|
|
}
|
|
|
|
#ifdef GUI
|
|
float s_amp = 128;
|
|
if ( cfg.gui ) {
|
|
r_scope_cstln = new cscope<f32>(sch, *p_cstln, -s_amp,s_amp);
|
|
r_scope_cstln->decimation = 1;
|
|
if ( cfg.standard == config::DVB_S2 ) {
|
|
// Dedicated scope for PLS symbols
|
|
r_scope_cstln_pls = new cscope<f32>(sch, *p_cstln_pls, -s_amp,s_amp);
|
|
r_scope_cstln_pls->decimation = 1;
|
|
}
|
|
} else {
|
|
r_scope_cstln = NULL;
|
|
r_scope_cstln_pls = NULL;
|
|
}
|
|
#endif
|
|
|
|
p_tspackets = new pipebuf<tspacket>(sch, "TS packets", BUF_S2PACKETS);
|
|
|
|
p_vbitcount= new pipebuf<int>(sch, "Bits processed", BUF_S2PACKETS);
|
|
p_verrcount = new pipebuf<int>(sch, "Bits corrected", BUF_S2PACKETS);
|
|
|
|
// Standard-specific code would go here,
|
|
// outputting into p_tspackets and into the measurements channels.
|
|
|
|
if ( cfg.udp_dst )
|
|
new udp_output<tspacket>(sch, *p_tspackets, cfg.udp_dst, 7);
|
|
else
|
|
new file_writer<tspacket>(sch, *p_tspackets, 1);
|
|
|
|
// BER ESTIMATION
|
|
|
|
p_vber = new pipebuf<float>(sch, "VBER", BUF_SLOW);
|
|
rate_estimator<float> *r_vber =
|
|
new rate_estimator<float>(sch, *p_verrcount, *p_vbitcount, *p_vber);
|
|
// About twice per second, and slow enough for 2e-5 resolution
|
|
//TBD r_vber->sample_size = cfg.Fm/2; // About twice per second, depending on CR
|
|
r_vber->sample_size = 1;
|
|
if ( r_vber->sample_size < 50000 ) r_vber->sample_size = 50000;
|
|
|
|
// AUX OUTPUT
|
|
|
|
if ( cfg.fd_info >= 0 ) {
|
|
file_printer<f32> *r_printfreq =
|
|
new file_printer<f32>(sch, "FREQ %.0f\n", *p_freq, cfg.fd_info);
|
|
r_printfreq->scale = cfg.Fs;
|
|
new file_printer<f32>(sch, "SS %f\n", *p_ss, cfg.fd_info);
|
|
new file_printer<f32>(sch, "MER %.1f\n", *p_mer, cfg.fd_info);
|
|
new file_printer<int>(sch, "FRAMELOCK %d\n", *p_framelock, cfg.fd_info);
|
|
new file_printer<int>(sch, "LOCK %d\n", *p_lock, cfg.fd_info);
|
|
new file_printer<unsigned long>
|
|
(sch, "LOCKTIME %lu\n", *p_locktime, cfg.fd_info,
|
|
decimation(cfg.Fm/8/204, cfg.Finfo)); // TBD CR
|
|
new file_printer<f32>(sch, "CNR %.1f\n", *p_cnr, cfg.fd_info);
|
|
new file_printer<float>(sch, "VBER %.6f\n", *p_vber, cfg.fd_info);
|
|
// Output constants immediately
|
|
FILE *f = fdopen(cfg.fd_info, "w");
|
|
if ( ! f ) fatal("fdopen(fd_info)");
|
|
output_initial_info(f, cfg);
|
|
fflush(f); // Do not close the FILE.
|
|
}
|
|
|
|
#ifdef GUI
|
|
p_tscount = new pipebuf<float>(sch, "packet counter", BUF_S2PACKETS);
|
|
new itemcounter<tspacket,float>(sch, *p_tspackets, *p_tscount);
|
|
|
|
float max_packet_rate = cfg.Fm / 8 / 204;
|
|
float pixel_rate = cfg.Finfo;
|
|
float max_packets_per_pixel = max_packet_rate / pixel_rate;
|
|
|
|
slowmultiscope<f32>::chanspec chans[] = {
|
|
{ p_freq, "estimated frequency", "Offset %3.3f kHz", {0,255,255},
|
|
cfg.Fs*1e-3f,
|
|
(cfg.Ftune-cfg.Fm/2)*1e-3f, (cfg.Ftune+cfg.Fm/2)*1e-3f,
|
|
slowmultiscope<f32>::chanspec::WRAP },
|
|
{ p_ss, "signal strength", "SS %3.3f", {255,0,0},
|
|
1, 0,128,
|
|
slowmultiscope<f32>::chanspec::DEFAULT },
|
|
{ p_mer, "MER", "MER %5.1f dB", {255,0,255},
|
|
1, -10,20,
|
|
slowmultiscope<f32>::chanspec::DEFAULT },
|
|
{ p_cnr, "CNR", "CNR %5.1f dB", {255,255,0},
|
|
1, -10,20,
|
|
(r_cnr?
|
|
slowmultiscope<f32>::chanspec::ASYNC:
|
|
slowmultiscope<f32>::chanspec::DISABLED) },
|
|
{ p_tscount, "TS recovery", "%3.0f %%", {255,255,0},
|
|
110/max_packets_per_pixel, 0, 101,
|
|
(slowmultiscope<f32>::chanspec::flag)
|
|
(slowmultiscope<f32>::chanspec::ASYNC |
|
|
slowmultiscope<f32>::chanspec::SUM) },
|
|
};
|
|
|
|
if ( cfg.gui ) {
|
|
slowmultiscope<f32> *r_scope_timeline =
|
|
new slowmultiscope<f32>(sch, chans, sizeof(chans)/sizeof(chans[0]),
|
|
"timeline");
|
|
r_scope_timeline->sample_freq = cfg.Fs / cfg.Fm * cfg.Finfo;
|
|
unsigned long nsamples = cfg.duration * cfg.Fs / cfg.Fm * cfg.Finfo;
|
|
r_scope_timeline->samples_per_pixel = (nsamples+w_timeline)/w_timeline;
|
|
}
|
|
#endif // GUI
|
|
|
|
} // constructor
|
|
|
|
}; // runtime_common
|
|
|
|
#ifndef LEANSDR_EXTENSIONS
|
|
int run_dvbs2(config &cfg) {
|
|
fail("DVB-S2 support not enabled at compile-time.");
|
|
return 1;
|
|
}
|
|
#else
|
|
int run_dvbs2(config &cfg) {
|
|
runtime_common run(cfg);
|
|
|
|
// Min buffer size for slots: 4 for deinterleaver
|
|
unsigned long BUF_SLOTS = modcod_info::MAX_SLOTS_PER_FRAME * cfg.buf_factor;
|
|
pipebuf< plslot<llr_ss> > p_slots(run.sch, "PL slots", BUF_SLOTS);
|
|
s2_frame_receiver<f32,llr_ss> demod(run.sch, run.sampler,
|
|
*run.p_preprocessed, p_slots,
|
|
run.p_freq, run.p_ss, run.p_mer,
|
|
run.p_cstln, run.p_cstln_pls,
|
|
run.p_iqsymbols, run.p_framelock);
|
|
demod.omega0 = cfg.Fs/cfg.Fm;
|
|
if ( cfg.Ftune ) {
|
|
if ( cfg.verbose )
|
|
fprintf(stderr, "Biasing frame receiver to %.3f kHz\n", cfg.Ftune/1e3);
|
|
demod.Ftune = cfg.Ftune / cfg.Fm; // Per symbol
|
|
}
|
|
demod.allow_drift = cfg.allow_drift;
|
|
demod.freq_tol = cfg.freq_tol;
|
|
demod.sr_tol = cfg.sr_tol;
|
|
demod.meas_decimation = decimation(cfg.Fm, cfg.Finfo);
|
|
demod.strongpls = cfg.strongpls;
|
|
demod.fastlock = cfg.fastlock;
|
|
demod.fastdrift = cfg.fastdrift;
|
|
demod.modcods = cfg.modcods;
|
|
demod.framesizes = cfg.framesizes;
|
|
|
|
if ( cfg.fd_const ) {
|
|
file_carrayprinter<f32> *symbol_printer;
|
|
if ( cfg.json )
|
|
symbol_printer = new file_carrayprinter<f32>
|
|
(run.sch, "SYMBOLS [", "[%.0f,%.0f]", ",", "]\n",
|
|
*run.p_cstln, cfg.fd_const);
|
|
else
|
|
symbol_printer = new file_carrayprinter<f32>
|
|
(run.sch, "SYMBOLS %d", " %.0f,%.0f", "", "\n",
|
|
*run.p_cstln, cfg.fd_const);
|
|
symbol_printer->fixed_size = 128;
|
|
}
|
|
|
|
#ifdef GUI
|
|
if ( run.r_scope_cstln )
|
|
run.r_scope_cstln->cstln = (cstln_base**)&demod.cstln; // TBD variance
|
|
if ( run.r_scope_cstln_pls )
|
|
run.r_scope_cstln_pls->cstln = (cstln_base**)&demod.qpsk; // TBD variance
|
|
#endif
|
|
|
|
unsigned long BUF_FRAMES = cfg.buf_factor;
|
|
pipebuf<bbframe> p_bbframes(run.sch, "BB frames", BUF_FRAMES);
|
|
|
|
if ( ! cfg.ldpc_helper ) {
|
|
// Bit-flipping mode.
|
|
// Deinterleave into hard bits.
|
|
pipebuf< fecframe<hard_sb> > *p_fecframes
|
|
= new pipebuf< fecframe<hard_sb> >(run.sch, "FEC frames", BUF_FRAMES);
|
|
new s2_deinterleaver<llr_ss,hard_sb>(run.sch, p_slots, *p_fecframes);
|
|
// Decode FEC-protected frames into plain BB frames.
|
|
s2_fecdec<bool,hard_sb> *r_fecdec =
|
|
new s2_fecdec<bool,hard_sb>(run.sch, *p_fecframes, p_bbframes,
|
|
run.p_vbitcount, run.p_verrcount);
|
|
r_fecdec->bitflips = cfg.ldpc_bf;
|
|
if ( ! cfg.ldpc_bf )
|
|
fprintf(stderr, "Warning: No LDPC error correction selected.\n");
|
|
} else {
|
|
// External LDPC decoder mode.
|
|
// Deinterleave into soft bits.
|
|
// TBD Latency
|
|
pipebuf< fecframe<llr_sb> > *p_fecframes =
|
|
new pipebuf< fecframe<llr_sb> >(run.sch, "FEC frames", BUF_FRAMES);
|
|
new s2_deinterleaver<llr_ss,llr_sb>(run.sch, p_slots, *p_fecframes);
|
|
// Decode FEC-protected frames into plain BB frames.
|
|
s2_fecdec_helper<llr_t,llr_sb> *r_fecdec =
|
|
new s2_fecdec_helper<llr_t,llr_sb>(run.sch, *p_fecframes, p_bbframes,
|
|
cfg.ldpc_helper,
|
|
run.p_vbitcount, run.p_verrcount);
|
|
r_fecdec->nhelpers = cfg.nhelpers;
|
|
r_fecdec->must_buffer = (cfg.nhelpers>1) || (cfg.input_pipe>0);
|
|
}
|
|
|
|
// Deframe BB frames to TS packets
|
|
s2_deframer deframer(run.sch, p_bbframes, *run.p_tspackets,
|
|
run.p_lock, run.p_locktime);
|
|
if ( cfg.fd_gse >= 0 ) deframer.fd_gse = cfg.fd_gse;
|
|
|
|
if ( cfg.debug )
|
|
fprintf(stderr,
|
|
"Output:\n"
|
|
" '_': S2 frame received without errors\n"
|
|
" '.': error-corrected S2 frame\n"
|
|
" 'C': TS packet with bad CRC\n"
|
|
" '!': S2 frame with remaining errors\n");
|
|
|
|
run.sch->run();
|
|
fprintf(stderr, "sch stopped\n");
|
|
run.sch->shutdown();
|
|
if ( cfg.debug ) run.sch->dump();
|
|
|
|
return 0;
|
|
} // run_dvbs2
|
|
#endif // LEANSDR_EXTENSIONS
|
|
|
|
int run_dvbs(config &cfg) {
|
|
runtime_common run(cfg);
|
|
|
|
typedef eucl_ss softsymb;
|
|
unsigned long BUF_SYMBOLS = 4096 * cfg.buf_factor;
|
|
pipebuf<softsymb> p_symbols(run.sch, "PSK soft-symbols", BUF_SYMBOLS);
|
|
cstln_receiver<f32,softsymb> demod(run.sch, run.sampler,
|
|
*run.p_preprocessed, p_symbols,
|
|
run.p_freq, run.p_ss, run.p_mer,
|
|
run.p_cstln);
|
|
if ( cfg.constellation != cstln_base::QPSK &&
|
|
cfg.constellation != cstln_base::BPSK )
|
|
fprintf(stderr, "Warning: non-standard constellation for DVB-S\n");
|
|
demod.cstln = new cstln_lut<softsymb,256>(cfg.constellation);
|
|
#if 0 // Dump LUT as greymap
|
|
demod.cstln->dump(stdout, 0);
|
|
exit(0);
|
|
#endif
|
|
if ( cfg.hard_metric ) {
|
|
if ( cfg.verbose )
|
|
fprintf(stderr, "Using hard metric.\n");
|
|
demod.cstln->harden();
|
|
}
|
|
demod.set_omega(cfg.Fs/cfg.Fm);
|
|
if ( cfg.Ftune ) {
|
|
if ( cfg.verbose )
|
|
fprintf(stderr, "Biasing receiver to %.3f kHz\n", cfg.Ftune/1e3);
|
|
demod.set_freq(cfg.Ftune/cfg.Fs);
|
|
}
|
|
if ( cfg.allow_drift ) {
|
|
if ( cfg.verbose )
|
|
fprintf(stderr, "Allowing unlimited drift.\n");
|
|
demod.set_allow_drift(true);
|
|
} else {
|
|
if ( cfg.verbose )
|
|
fprintf(stderr, "Frequency offset limits: %+.3f..%+.3f kHz.\n",
|
|
demod.min_freqw*cfg.Fs/65536/1000,
|
|
demod.max_freqw*cfg.Fs/65536/1000);
|
|
}
|
|
if ( cfg.viterbi ) {
|
|
if ( cfg.verbose ) fprintf(stderr, "PLL parameters for low SNR\n");
|
|
demod.pll_adjustment /= 6;
|
|
}
|
|
demod.meas_decimation = decimation(cfg.Fs, cfg.Finfo);
|
|
|
|
#ifdef GUI
|
|
if ( run.r_scope_cstln )
|
|
run.r_scope_cstln->cstln = (cstln_base**)&demod.cstln; // TBD variance
|
|
#endif
|
|
|
|
// TRACKING FILTERS
|
|
|
|
if ( run.r_resample ) {
|
|
run.r_resample->freq_tap = &demod.freq_tap;
|
|
run.r_resample->tap_multiplier = 1.0 / run.decimpp;
|
|
run.r_resample->freq_tol = cfg.Fm/(run.Fspp*run.decimpp) * 0.1;
|
|
}
|
|
|
|
if ( run.r_cnr ) {
|
|
run.r_cnr->freq_tap = &demod.freq_tap;
|
|
run.r_cnr->tap_multiplier = 1.0 / run.decimpp;
|
|
}
|
|
|
|
// DECONVOLUTION AND SYNCHRONIZATION
|
|
|
|
// Min buffer size for unsynchronized bytes
|
|
// deconv_sync: writes 32 bytes
|
|
// mpeg_sync: reads up to 204*scan_syncs = 1632 bytes
|
|
unsigned long BUF_BYTES = 2048 * cfg.buf_factor;
|
|
pipebuf<u8> p_bytes(run.sch, "bytes", BUF_BYTES);
|
|
|
|
deconvol_sync_simple *r_deconv = NULL;
|
|
|
|
if ( cfg.viterbi ) {
|
|
if ( cfg.fec==FEC23 && (demod.cstln->nsymbols==4 ||
|
|
demod.cstln->nsymbols==64) ) {
|
|
if ( cfg.verbose ) fprintf(stderr, "Handling rate 2/3 as 4/6\n");
|
|
cfg.fec = FEC46;
|
|
}
|
|
viterbi_sync *r = new viterbi_sync(run.sch, p_symbols, p_bytes,
|
|
demod.cstln, cfg.fec);
|
|
if ( cfg.fastlock ) r->resync_period = 1;
|
|
} else {
|
|
r_deconv = make_deconvol_sync_simple(run.sch, p_symbols, p_bytes, cfg.fec);
|
|
r_deconv->fastlock = cfg.fastlock;
|
|
}
|
|
|
|
// Min buffer size for synchronized (but interleaved) bytes
|
|
// mpeg_sync: writes 1 rspacket
|
|
// deinterleaver: reads 17*11*12+204 = 2448 bytes
|
|
unsigned long BUF_MPEGBYTES = 2448 * cfg.buf_factor;
|
|
|
|
if ( cfg.hdlc ) {
|
|
pipebuf<u8> *p_descrambled =
|
|
new pipebuf<u8>(run.sch, "descrambled", BUF_MPEGBYTES);
|
|
new etr192_descrambler(run.sch, p_bytes, *p_descrambled);
|
|
pipebuf<u8> *p_frames =
|
|
new pipebuf<u8>(run.sch, "frames", BUF_MPEGBYTES);
|
|
hdlc_sync *r_sync = new hdlc_sync(run.sch, *p_descrambled, *p_frames, 2, 278);
|
|
if ( cfg.fastlock ) r_sync->resync_period = 1;
|
|
if ( cfg.packetized ) r_sync->header16 = true;
|
|
new file_writer<u8>(run.sch, *p_frames, 1);
|
|
}
|
|
|
|
pipebuf<u8> p_mpegbytes(run.sch, "mpegbytes", BUF_MPEGBYTES);
|
|
if ( ! cfg.hdlc ) {
|
|
mpeg_sync<u8,0> *r_sync =
|
|
new mpeg_sync<u8,0>(run.sch, p_bytes, p_mpegbytes, r_deconv,
|
|
run.p_lock, run.p_locktime);
|
|
r_sync->fastlock = cfg.fastlock;
|
|
}
|
|
|
|
// DEINTERLEAVING
|
|
|
|
// Min buffer size for TS packets: 1
|
|
unsigned long BUF_PACKETS = cfg.buf_factor;
|
|
pipebuf< rspacket<u8> > p_rspackets(run.sch, "RS-enc packets", BUF_PACKETS);
|
|
deinterleaver<u8> r_deinter(run.sch, p_mpegbytes, p_rspackets);
|
|
|
|
// REED-SOLOMON
|
|
|
|
pipebuf<tspacket> p_rtspackets(run.sch, "rand TS packets", BUF_PACKETS);
|
|
rs_decoder<u8,0> r_rsdec(run.sch, p_rspackets, p_rtspackets,
|
|
run.p_vbitcount, run.p_verrcount);
|
|
|
|
// DERANDOMIZATION
|
|
|
|
derandomizer r_derand(run.sch, p_rtspackets, *run.p_tspackets);
|
|
|
|
if ( cfg.fd_const >= 0 ) {
|
|
cstln_lut<eucl_ss,256> *c = demod.cstln;
|
|
if ( c ) {
|
|
// Output constellation immediately
|
|
FILE *f = fdopen(cfg.fd_const, "w");
|
|
if ( ! f ) fatal("fdopen(fd_const)");
|
|
if ( cfg.json ) {
|
|
fprintf(f, "CONST [");
|
|
for ( int i=0; i<c->nsymbols; ++i )
|
|
fprintf(f, "%s[%d,%d]", i?",":"",
|
|
c->symbols[i].re, c->symbols[i].im);
|
|
fprintf(f, "]\n");
|
|
} else {
|
|
fprintf(f, "CONST %d", c->nsymbols);
|
|
for ( int i=0; i<c->nsymbols; ++i )
|
|
fprintf(f, " %d,%d", c->symbols[i].re, c->symbols[i].im);
|
|
fprintf(f, "\n");
|
|
}
|
|
fflush(f);
|
|
}
|
|
file_carrayprinter<f32> *symbol_printer;
|
|
if ( cfg.json )
|
|
symbol_printer = new file_carrayprinter<f32>
|
|
(run.sch, "SYMBOLS [", "[%.0f,%.0f]", ",", "]\n",
|
|
*run.p_cstln, cfg.fd_const);
|
|
else
|
|
symbol_printer = new file_carrayprinter<f32>
|
|
(run.sch, "SYMBOLS %d", " %.0f,%.0f", "", "\n",
|
|
*run.p_cstln, cfg.fd_const);
|
|
symbol_printer->fixed_size = 128;
|
|
}
|
|
|
|
if ( cfg.fd_spectrum >= 0 ) {
|
|
// TIMELINE SCOPE
|
|
|
|
#if 0
|
|
float max_packet_rate = cfg.Fm / 8 / 204;
|
|
float pixel_rate = cfg.Fs / demod.meas_decimation;
|
|
float max_packets_per_pixel = max_packet_rate / pixel_rate;
|
|
|
|
slowmultiscope<f32>::chanspec chans[] = {
|
|
{ &p_freq, "estimated frequency", "Offset %3.3f kHz", {0,255,255},
|
|
cfg.Fs*1e-3f,
|
|
(cfg.Ftune-cfg.Fm/2)*1e-3f, (cfg.Ftune+cfg.Fm/2)*1e-3f,
|
|
slowmultiscope<f32>::chanspec::WRAP },
|
|
{ &p_ss, "signal strength", "SS %3.3f", {255,0,0},
|
|
1, 0,128,
|
|
slowmultiscope<f32>::chanspec::DEFAULT },
|
|
{ &p_mer, "MER", "MER %5.1f dB", {255,0,255},
|
|
1, -10,20,
|
|
slowmultiscope<f32>::chanspec::DEFAULT },
|
|
{ &p_cnr, "CNR", "CNR %5.1f dB", {255,255,0},
|
|
1, -10,20,
|
|
(r_cnr?
|
|
slowmultiscope<f32>::chanspec::ASYNC:
|
|
slowmultiscope<f32>::chanspec::DISABLED) },
|
|
{ &p_tscount, "TS recovery", "%3.0f %%", {255,255,0},
|
|
110/max_packets_per_pixel, 0, 101,
|
|
(slowmultiscope<f32>::chanspec::flag)
|
|
(slowmultiscope<f32>::chanspec::ASYNC |
|
|
slowmultiscope<f32>::chanspec::SUM) },
|
|
};
|
|
|
|
if ( cfg.gui ) {
|
|
slowmultiscope<f32> *r_scope_timeline =
|
|
new slowmultiscope<f32>(run.sch, chans, sizeof(chans)/sizeof(chans[0]),
|
|
"timeline");
|
|
r_scope_timeline->sample_freq = cfg.Fs / demod.meas_decimation;
|
|
unsigned long nsamples = cfg.duration * cfg.Fs / demod.meas_decimation;
|
|
r_scope_timeline->samples_per_pixel = (nsamples+w_timeline)/w_timeline;
|
|
}
|
|
#endif // GUI
|
|
}
|
|
if ( cfg.debug ) {
|
|
if ( ! cfg.hdlc )
|
|
fprintf(stderr,
|
|
"Output:\n"
|
|
" '_': TS packet received without errors\n"
|
|
" '.': error-corrected TS packet\n"
|
|
" '!': TS packet with remaining errors\n");
|
|
else
|
|
fprintf(stderr,
|
|
"Output:\n"
|
|
" '_': HDLC frame with correct checksum\n"
|
|
" '!': HDLC frame with invalid checksum\n"
|
|
" '^': HDLC framing error\n");
|
|
}
|
|
|
|
run.sch->run();
|
|
|
|
run.sch->shutdown();
|
|
|
|
if ( cfg.debug ) run.sch->dump();
|
|
|
|
if ( cfg.gui && cfg.linger ) while ( 1 ) { run.sch->run(); usleep(10000); }
|
|
|
|
return 0;
|
|
} // run_dvbs
|
|
|
|
|
|
#if 1 // TBD
|
|
int run_highspeed_s2(config &cfg) {
|
|
fail("--hs is broken.");
|
|
return 1;
|
|
}
|
|
#else
|
|
int run_highspeed_s2(config &cfg) {
|
|
|
|
int w_timeline = 512, h_timeline = 256;
|
|
int w_fft = 1024, h_fft = 256;
|
|
int wh_const = 256;
|
|
|
|
scheduler sch;
|
|
sch.verbose = cfg.verbose;
|
|
sch.debug = cfg.debug;
|
|
|
|
int x0 = 100, y0 = 40;
|
|
|
|
window_placement window_hints[] = {
|
|
{ "rawiq (iq)", x0, y0, wh_const,wh_const },
|
|
{ "PSK symbols", x0, y0+600, wh_const, wh_const },
|
|
{ "timeline", x0+300, y0+600, w_timeline, h_timeline },
|
|
{ NULL, }
|
|
};
|
|
sch.windows = window_hints;
|
|
|
|
unsigned long MAX_SYMBOLS = (90*(1+360)+36*((360-1)/16)) * cfg.buf_factor;
|
|
unsigned long BUF_BASEBAND = MAX_SYMBOLS * 2 * cfg.buf_factor;
|
|
// Min buffer size for IQ symbols
|
|
// cstln_receiver: writes in chunks of 128/omega symbols (margin 128)
|
|
// deconv_sync: reads at least 64+32
|
|
// A larger buffer improves performance significantly.
|
|
unsigned long BUF_SYMBOLS = MAX_SYMBOLS * cfg.buf_factor;
|
|
// Min buffer size for unsynchronized bytes
|
|
// deconv_sync: writes 32 bytes
|
|
// mpeg_sync: reads up to 204*scan_syncs = 1632 bytes
|
|
unsigned long BUF_BYTES = 2048 * cfg.buf_factor;
|
|
// Min buffer size for synchronized (but interleaved) bytes
|
|
// mpeg_sync: writes 1 rspacket
|
|
// deinterleaver: reads 17*11*12+204 = 2448 bytes
|
|
unsigned long BUF_MPEGBYTES = 2448 * cfg.buf_factor;
|
|
// Min buffer size for packets: 1
|
|
unsigned long BUF_PACKETS = cfg.buf_factor;
|
|
// Min buffer size for TS packets: Up to 39 per BBFRAME
|
|
unsigned long BUF_S2PACKETS = 39 * cfg.buf_factor;
|
|
// Min buffer size for misc measurements: 1
|
|
unsigned long BUF_SLOW = cfg.buf_factor;
|
|
|
|
// HIGHSPEED S2: INPUT
|
|
|
|
if ( cfg.input_format != config::INPUT_S16 )
|
|
fail("--hs requires --s16");
|
|
|
|
pipebuf<cs16> p_rawiq(&sch, "rawiq", BUF_BASEBAND+cfg.input_buffer);
|
|
file_reader<cs16> r_stdin(&sch, 0, p_rawiq);
|
|
r_stdin.loop = cfg.loop_input;
|
|
|
|
#ifdef GUI
|
|
float amp = 2048;
|
|
if ( cfg.gui ) {
|
|
cscope<s16> *r_cscope_raw =
|
|
new cscope<s16>(&sch, p_rawiq, -amp, amp, "rawiq (iq)");
|
|
}
|
|
#endif
|
|
|
|
// HIGHSPEED S2: DEMOD
|
|
|
|
pipebuf<cf32> p_sampled(&sch, "PSK symbols", BUF_SYMBOLS);
|
|
pipebuf<plslot> p_slots(&sch, "slots", BUF_SYMBOLS/90);
|
|
s2_frame_synchronizer frame_sync(&sch, p_rawiq, p_slots, &p_sampled);
|
|
frame_sync.Fm = cfg.Fm;
|
|
frame_sync.Ftune = cfg.Ftune / cfg.Fm;
|
|
frame_sync.omega = cfg.Fs / cfg.Fm;
|
|
|
|
#ifdef GUI
|
|
if ( cfg.gui ) {
|
|
cscope<float> *r_scope_symbols =
|
|
new cscope<float>(&sch, p_sampled, -amp, amp);
|
|
r_scope_symbols->decimation = 1;
|
|
}
|
|
#endif
|
|
|
|
// HIGHSPEED S2: DECODING
|
|
|
|
pipebuf<fecframe> p_fecframes(&sch, "FEC frames", BUF_PACKETS);
|
|
s2_deinterleaver deint(&sch, p_slots, p_fecframes);
|
|
pipebuf<bbframe> p_bbframes(&sch, "BB frames", BUF_PACKETS);
|
|
s2_fecdec fecdec(&sch, p_fecframes, p_bbframes);
|
|
pipebuf<tspacket> p_tspackets(&sch, "TS packets", BUF_S2PACKETS);
|
|
s2_deframer deframer(&sch, p_bbframes, p_tspackets);
|
|
file_writer<tspacket> r_stdout(&sch, p_tspackets, 1);
|
|
fprintf(stderr, "Running S2 --hs\n");
|
|
|
|
sch.run();
|
|
|
|
sch.shutdown();
|
|
|
|
if ( cfg.debug ) sch.dump();
|
|
|
|
if ( cfg.gui && cfg.linger ) while ( 1 ) { sch.run(); usleep(10000); }
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if 1 // TBD
|
|
int run_highspeed(config &cfg) {
|
|
fail("--hs is broken.");
|
|
return 1;
|
|
}
|
|
#else
|
|
int run_highspeed(config &cfg) {
|
|
|
|
int w_timeline = 512, h_timeline = 256;
|
|
int w_fft = 1024, h_fft = 256;
|
|
int wh_const = 256;
|
|
|
|
scheduler sch;
|
|
sch.verbose = cfg.verbose;
|
|
sch.debug = cfg.debug;
|
|
|
|
int x0 = 100, y0 = 40;
|
|
|
|
window_placement window_hints[] = {
|
|
{ "rawiq (iq)", x0, y0, wh_const,wh_const },
|
|
{ "PSK symbols", x0, y0+600, wh_const, wh_const },
|
|
{ "timeline", x0+300, y0+600, w_timeline, h_timeline },
|
|
{ NULL, }
|
|
};
|
|
sch.windows = window_hints;
|
|
|
|
// Min buffer size for baseband data
|
|
// scopes: 1024
|
|
// ss_estimator: 1024
|
|
// anf: 4096
|
|
// cstln_receiver: reads in chunks of 128+1
|
|
unsigned long BUF_BASEBAND = 4096 * cfg.buf_factor;
|
|
// Min buffer size for IQ symbols
|
|
// cstln_receiver: writes in chunks of 128/omega symbols (margin 128)
|
|
// deconv_sync: reads at least 64+32
|
|
// A larger buffer improves performance significantly.
|
|
unsigned long BUF_SYMBOLS = 1024 * cfg.buf_factor;
|
|
// Min buffer size for unsynchronized bytes
|
|
// deconv_sync: writes 32 bytes
|
|
// mpeg_sync: reads up to 204*scan_syncs = 1632 bytes
|
|
unsigned long BUF_BYTES = 2048 * cfg.buf_factor;
|
|
// Min buffer size for synchronized (but interleaved) bytes
|
|
// mpeg_sync: writes 1 rspacket
|
|
// deinterleaver: reads 17*11*12+204 = 2448 bytes
|
|
unsigned long BUF_MPEGBYTES = 2448 * cfg.buf_factor;
|
|
// Min buffer size for packets: 1
|
|
unsigned long BUF_PACKETS = cfg.buf_factor;
|
|
// Min buffer size for misc measurements: 1
|
|
unsigned long BUF_SLOW = cfg.buf_factor;
|
|
|
|
// HIGHSPEED: INPUT
|
|
|
|
if ( cfg.input_format != config::INPUT_U8 )
|
|
fail("--hs requires --u8");
|
|
|
|
pipebuf<cu8> p_rawiq(&sch, "rawiq", BUF_BASEBAND+cfg.input_buffer);
|
|
file_reader<cu8> r_stdin(&sch, 0, p_rawiq);
|
|
r_stdin.loop = cfg.loop_input;
|
|
|
|
#ifdef GUI
|
|
float amp = 128;
|
|
|
|
if ( cfg.gui ) {
|
|
cscope<u8> *r_cscope_raw =
|
|
new cscope<u8>(&sch, p_rawiq, 0, 2*amp, "rawiq (iq)");
|
|
}
|
|
#endif
|
|
|
|
// HIGHSPEED: QPSK
|
|
|
|
#define ALGEBRAIC_COMPAT 0 // Use legacy constellation receiver ?
|
|
|
|
pipebuf<f32> p_freq(&sch, "freq", BUF_SLOW);
|
|
pipebuf<cu8> p_sampled(&sch, "PSK symbols", BUF_BASEBAND);
|
|
#if ALGEBRAIC_COMPAT
|
|
fprintf(stderr, "--hs: Using legacy receiver (slower)\n");
|
|
pipebuf<cf32> p_rawiqf(&sch, "rawiq float", BUF_BASEBAND);
|
|
cconverter<u8,128, f32,0, 1,1> r_convert_in(&sch, p_rawiq, p_rawiqf);
|
|
|
|
pipebuf<softsymbol> p_symbols(&sch, "PSK soft-symbols", BUF_SYMBOLS);
|
|
pipebuf<cf32> p_sampledf(&sch, "PSK fsymbols", BUF_BASEBAND);
|
|
// TBD retype preprocess as unsigned char
|
|
cstln_receiver<f32> demod(&sch, p_rawiqf, p_symbols,
|
|
&p_freq, NULL, NULL, &p_sampledf);
|
|
cstln_lut<256> qpsk(cstln_base::QPSK);
|
|
demod.cstln = &qpsk;
|
|
// Convert the sampled symbols to cu8 for GUI
|
|
cconverter<f32,0, u8,128, 1,1>
|
|
r_convert_samp(&sch, p_sampledf, p_sampled);
|
|
#else
|
|
// Use the new fixed-point receiver
|
|
pipebuf<u8> p_symbols(&sch, "PSK hard symbols", BUF_SYMBOLS);
|
|
fast_qpsk_receiver<u8> demod(&sch, p_rawiq, p_symbols,
|
|
&p_freq, &p_sampled);
|
|
#endif
|
|
demod.set_omega(cfg.Fs/cfg.Fm);
|
|
if ( cfg.Ftune ) {
|
|
if ( cfg.verbose )
|
|
fprintf(stderr, "Biasing receiver to %.3f kHz\n", cfg.Ftune/1e3);
|
|
demod.set_freq(cfg.Ftune/cfg.Fs);
|
|
}
|
|
if ( cfg.allow_drift ) {
|
|
if ( cfg.verbose )
|
|
fprintf(stderr, "Allowing unlimited drift.\n");
|
|
demod.allow_drift = true;
|
|
} else {
|
|
if ( cfg.verbose )
|
|
fprintf(stderr, "Frequency offset limits: %+.3f..%+.3f kHz.\n",
|
|
demod.min_freqw*cfg.Fs/65536/1000,
|
|
demod.max_freqw*cfg.Fs/65536/1000);
|
|
}
|
|
demod.meas_decimation = decimation(cfg.Fs, cfg.Finfo);
|
|
|
|
#ifdef GUI
|
|
if ( cfg.gui ) {
|
|
cscope<u8> *r_scope_symbols =
|
|
new cscope<u8>(&sch, p_sampled, 0,2*amp);
|
|
r_scope_symbols->decimation = 1;
|
|
}
|
|
#endif
|
|
|
|
// HIGHSPEED: DECONVOLUTION
|
|
|
|
if ( cfg.fec != FEC12 )
|
|
fail("--hs currently supports code rate 1/2 only");
|
|
|
|
pipebuf<u8> p_bytes(&sch, "bytes", BUF_BYTES);
|
|
#if ALGEBRAIC_COMPAT
|
|
dvb_deconvol_sync_soft r_deconv(&sch, p_symbols, p_bytes);
|
|
#else
|
|
dvb_deconvol_sync_hard r_deconv(&sch, p_symbols, p_bytes);
|
|
#endif
|
|
r_deconv.resync_period = cfg.fastlock ? 1 : 32;
|
|
|
|
// HIGHSPEED: SYNCHRONIZATION
|
|
|
|
pipebuf<u8> p_mpegbytes(&sch, "mpegbytes", BUF_MPEGBYTES);
|
|
pipebuf<int> p_lock(&sch, "lock", BUF_SLOW);
|
|
pipebuf<u32> p_locktime(&sch, "locktime", BUF_PACKETS);
|
|
mpeg_sync<u8,0> r_sync(&sch, p_bytes, p_mpegbytes, NULL,
|
|
&p_lock, &p_locktime);
|
|
r_sync.fastlock = true;
|
|
r_sync.resync_period = cfg.fastlock ? 1 : 32;
|
|
|
|
// HIGHSPEED: DEINTERLEAVING
|
|
|
|
pipebuf< rspacket<u8> > p_rspackets(&sch, "RS-enc packets", BUF_PACKETS);
|
|
deinterleaver<u8> r_deinter(&sch, p_mpegbytes, p_rspackets);
|
|
|
|
// HIGHSPEED: REED-SOLOMON
|
|
|
|
pipebuf<int> p_vbitcount(&sch, "Bits processed", BUF_PACKETS);
|
|
pipebuf<int> p_verrcount(&sch, "Bits corrected", BUF_PACKETS);
|
|
pipebuf<tspacket> p_rtspackets(&sch, "rand TS packets", BUF_PACKETS);
|
|
rs_decoder<u8,0> r_rsdec(&sch, p_rspackets, p_rtspackets,
|
|
&p_vbitcount, &p_verrcount);
|
|
|
|
// HIGHSPEED: BER ESTIMATION
|
|
|
|
pipebuf<float> p_vber(&sch, "VBER", BUF_SLOW);
|
|
rate_estimator<float> r_vber(&sch, p_verrcount, p_vbitcount, p_vber);
|
|
r_vber.sample_size = cfg.Fm/2; // About twice per second, depending on CR
|
|
// Require resolution better than 2E-5
|
|
if ( r_vber.sample_size < 50000 ) r_vber.sample_size = 50000;
|
|
|
|
// HIGHSPEED: DERANDOMIZATION
|
|
|
|
pipebuf<tspacket> p_tspackets(&sch, "TS packets", BUF_PACKETS);
|
|
derandomizer r_derand(&sch, p_rtspackets, p_tspackets);
|
|
|
|
// HIGHSPEED: OUTPUT
|
|
|
|
file_writer<tspacket> r_stdout(&sch, p_tspackets, 1);
|
|
|
|
// HIGHSPEED: AUX OUTPUT
|
|
|
|
if ( cfg.fd_info >= 0 ) {
|
|
file_printer<f32> *r_printfreq =
|
|
new file_printer<f32>(&sch, "FREQ %.0f\n", p_freq, cfg.fd_info);
|
|
r_printfreq->scale = cfg.Fs;
|
|
new file_printer<int>(&sch, "LOCK %d\n", p_lock, cfg.fd_info);
|
|
new file_printer<u32>(&sch, "LOCKTIME %lu\n", p_locktime, cfg.fd_info,
|
|
decimation(cfg.Fm/8/204, cfg.Finfo)); // TBD CR
|
|
new file_printer<float>(&sch, "VBER %.6f\n", p_vber, cfg.fd_info);
|
|
// Output constants immediately
|
|
FILE *f = fdopen(cfg.fd_info, "w");
|
|
if ( ! f ) fatal("fdopen(fd_info)");
|
|
output_initial_info(f, cfg);
|
|
fflush(f);
|
|
}
|
|
if ( cfg.fd_const >= 0 ) {
|
|
file_carrayprinter<u8> *symbol_printer;
|
|
if ( cfg.json )
|
|
symbol_printer = new file_carrayprinter<u8>
|
|
(&sch, "SYMBOLS [", "[%.0f,%.0f]", ",", "]\n", p_sampled, cfg.fd_const);
|
|
else
|
|
symbol_printer = new file_carrayprinter<u8>
|
|
(&sch, "SYMBOLS %d", " %d,%d", "", "\n", p_sampled, cfg.fd_const);
|
|
symbol_printer->fixed_size = 128;
|
|
}
|
|
|
|
// HIGHSPEED: TIMELINE SCOPE
|
|
|
|
#ifdef GUI
|
|
pipebuf<float> p_tscount(&sch, "packet counter", BUF_PACKETS*100);
|
|
itemcounter<tspacket,float> r_tscounter(&sch, p_tspackets, p_tscount);
|
|
float max_packet_rate = cfg.Fm / 8 / 204;
|
|
float pixel_rate = cfg.Fs / demod.meas_decimation;
|
|
float max_packets_per_pixel = max_packet_rate / pixel_rate;
|
|
|
|
slowmultiscope<f32>::chanspec chans[] = {
|
|
{ &p_freq, "estimated frequency", "%3.3f kHz", {0,255,255},
|
|
cfg.Fs*1e-3f,
|
|
(cfg.Ftune-cfg.Fm/2)*1e-3f, (cfg.Ftune+cfg.Fm/2)*1e-3f,
|
|
slowmultiscope<f32>::chanspec::WRAP },
|
|
{ &p_tscount, "TS recovery", "%3.0f %%", {255,255,0},
|
|
110/max_packets_per_pixel, 0, 101,
|
|
(slowmultiscope<f32>::chanspec::flag)
|
|
(slowmultiscope<f32>::chanspec::ASYNC |
|
|
slowmultiscope<f32>::chanspec::SUM) },
|
|
};
|
|
|
|
if ( cfg.gui ) {
|
|
slowmultiscope<f32> *r_scope_timeline =
|
|
new slowmultiscope<f32>(&sch, chans, sizeof(chans)/sizeof(chans[0]),
|
|
"timeline");
|
|
r_scope_timeline->sample_freq = cfg.Fs / demod.meas_decimation;
|
|
unsigned long nsamples = cfg.duration * cfg.Fs / demod.meas_decimation;
|
|
r_scope_timeline->samples_per_pixel = (nsamples+w_timeline)/w_timeline;
|
|
}
|
|
#endif // GUI
|
|
|
|
if ( cfg.debug )
|
|
fprintf(stderr,
|
|
"Output:\n"
|
|
" '_': packet received without errors\n"
|
|
" '.': error-corrected packet\n"
|
|
" '!': packet with remaining errors\n");
|
|
|
|
sch.run();
|
|
|
|
sch.shutdown();
|
|
|
|
if ( cfg.debug ) sch.dump();
|
|
|
|
if ( cfg.gui && cfg.linger ) while ( 1 ) { sch.run(); usleep(10000); }
|
|
|
|
return 0;
|
|
}
|
|
#endif // TBD
|
|
|
|
// Command-line
|
|
|
|
void usage(const char *name, FILE *f, int c, const char *info=NULL) {
|
|
fprintf(f, "Usage: %s [options] < IQ > TS\n", name);
|
|
fprintf(f, "Demodulate DVB-S I/Q on stdin, output MPEG packets on stdout\n");
|
|
fprintf
|
|
(f,
|
|
"\nInput options:\n"
|
|
" --u8 Input format is 8-bit unsigned (rtl_sdr, default)\n"
|
|
" --s12 Input format is 12/16-bit signed (PlutoSDR, LimeSDR)\n"
|
|
" --s16 Input format is 16-bit signed\n"
|
|
" --f32 Input format is 32-bit float (gqrx)\n"
|
|
" -f HZ Input sample rate (Hz, default: 2.4e6)\n"
|
|
" --loop Repeat (stdin must be a file)\n"
|
|
" --inpipe INT Resize stdin pipe (bytes)\n"
|
|
" --inbuf INT Additional input buffering (samples)\n"
|
|
);
|
|
fprintf
|
|
(f,
|
|
"\nPreprocessing options:\n"
|
|
" --anf INT Number of birdies to remove (default: 0)\n"
|
|
" --derotate HZ For use with --fd-pp, otherwise use --tune\n"
|
|
" --resample Resample baseband (CPU-intensive)\n"
|
|
" --resample-rej FLOAT Aliasing rejection (default: 10)\n"
|
|
" --decim INT Decimate baseband (causes aliasing)\n"
|
|
" --cnr Measure CNR (requires samprate>3*symbrate)\n"
|
|
);
|
|
fprintf
|
|
(f,
|
|
"\nCommon DVB options:\n"
|
|
" --sr FLOAT Symbol rate (Hz, default: 2e6)\n"
|
|
" --tune FLOAT Bias frequency for demodulation (Hz)\n"
|
|
" --drift Track frequency drift beyond safe limits\n"
|
|
" --standard S DVB-S(default), DVB-S2\n"
|
|
" --fastlock Synchronize more aggressively (CPU-intensive)\n"
|
|
" --sampler Symbol estimation: nearest, linear, rrc\n"
|
|
" --rrc-steps INT RRC interpolation factor\n"
|
|
" --rrc-rej FLOAT RRC filter rejection (defaut: 10)\n"
|
|
" --roll-off FLOAT RRC roll-off (default: 0.35)\n"
|
|
" --ts-udp IP:PORT Send TS to UDP address\n"
|
|
);
|
|
fprintf
|
|
(f,
|
|
"\nDVB-S options:\n"
|
|
" --const STRING Constellation: QPSK(default), BPSK\n"
|
|
" --cr NUM/DEN Code rate: 1/2(default) .. 7/8\n"
|
|
" --viterbi Use Viterbi (CPU-intensive)\n"
|
|
" --hard-metric Use Hamming distances with Viterbi\n"
|
|
);
|
|
fprintf
|
|
(f,
|
|
"\nDVB-S2 options:\n"
|
|
" --strongpls Expect PLS symbols at max amplitude\n"
|
|
" --modcods INT Bitmask of desired modcods\n"
|
|
" --framesizes INT Bitmask of desired sizes (1=normal,2=short)\n"
|
|
" --fastdrift Assume carrier drifts faster than pilots\n"
|
|
" --ldpc-bf INT Max number of LDPC bitflips (default: 0)\n"
|
|
" --ldpc-helper CMD Spawn external LDPC decoder:\n"
|
|
" 'CMD --standard DVB-S2 --modcod N [--shortframes]'\n"
|
|
" --nhelpers INT Number of decoders per modcod/framesize\n"
|
|
);
|
|
fprintf
|
|
(f,
|
|
"\nCompatibility options:\n"
|
|
" --hdlc Expect HDLC frames instead of MPEG packets\n"
|
|
" --packetized Output 16-bit length prefix (default: as stream)\n"
|
|
);
|
|
fprintf
|
|
(f,
|
|
"\nGeneral options:\n"
|
|
" --buf-factor INT Buffer size factor (default:4)\n"
|
|
" --hq Maximize sensitivity\n"
|
|
" (Enables all CPU-intensive features)\n"
|
|
" --hs Maximize throughput (QPSK CR1/2 only)\n"
|
|
" (Disables all preprocessing)\n"
|
|
);
|
|
fprintf
|
|
(f,
|
|
"\nUI options:\n"
|
|
" -h Display this help message and exit\n"
|
|
" -v Output debugging info at startup and exit\n"
|
|
" -d Output debugging info during operation\n"
|
|
" --version Display version and exit\n"
|
|
" --fd-info FDNUM Output demodulator status to file descriptor\n"
|
|
" --fd-const FDNUM Output constellation and symbols to file descr\n"
|
|
" --fd-spectrum FDNUM Output spectrum to file descr\n"
|
|
" --json Use JSON syntax\n"
|
|
);
|
|
#ifdef GUI
|
|
fprintf
|
|
(f,
|
|
" --gui Show constellation and spectrum (X11)\n"
|
|
" --duration FLOAT Width of timeline plot (s, default 60)\n"
|
|
" --linger Keep GUI running after EOF\n"
|
|
);
|
|
#endif
|
|
fprintf
|
|
(f, "\nTesting options:\n"
|
|
" --fd-pp FDNUM Dump preprocessed IQ data to file descriptor\n"
|
|
" --fd-iqsymbols FDNUM Dump sampled IQ symbols to file descriptor\n"
|
|
" --fd-gse FDNUM Dump DVB-S2 generic streams to this FD\n"
|
|
" --awgn FLOAT Add white gaussian noise stddev (slow)\n"
|
|
);
|
|
if ( info ) fprintf(f, "** Error while processing '%s'\n", info);
|
|
exit(c);
|
|
}
|
|
|
|
int main(int argc, const char *argv[]) {
|
|
config cfg;
|
|
|
|
for ( int i=1; i<argc; ++i ) {
|
|
if ( ! strcmp(argv[i], "-h") )
|
|
usage(argv[0], stdout, 0);
|
|
else if ( ! strcmp(argv[i], "-v") )
|
|
cfg.verbose = true;
|
|
else if ( ! strcmp(argv[i], "-d") ) {
|
|
cfg.debug2 = cfg.debug;
|
|
cfg.debug = true;
|
|
}
|
|
else if ( ! strcmp(argv[i], "--version") ) {
|
|
printf("%s\n", VERSION);
|
|
return 0;
|
|
}
|
|
else if ( ! strcmp(argv[i], "-f") && i+1<argc )
|
|
cfg.Fs = atof(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--sr") && i+1<argc )
|
|
cfg.Fm = atof(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--standard") && i+1<argc ) {
|
|
++i;
|
|
if ( ! strcmp(argv[i], "DVB-S" ) )
|
|
cfg.standard = config::DVB_S;
|
|
else if ( ! strcmp(argv[i], "DVB-S2" ) )
|
|
cfg.standard = config::DVB_S2;
|
|
else usage(argv[0], stderr, 1, argv[i]);
|
|
}
|
|
else if ( ! strcmp(argv[i], "--const") && i+1<argc ) {
|
|
++i;
|
|
int c;
|
|
for ( c=0; c<cstln_base::COUNT; ++c )
|
|
if ( ! strcmp(argv[i], cstln_base::names[c]) ) {
|
|
cfg.constellation = (cstln_base::predef)c;
|
|
break;
|
|
}
|
|
if ( c == cstln_base::COUNT )
|
|
usage(argv[0], stderr, 1, argv[i]);
|
|
}
|
|
else if ( ! strcmp(argv[i], "--strongpls") )
|
|
cfg.strongpls = true;
|
|
else if ( ! strcmp(argv[i], "--modcods") && i+1<argc )
|
|
cfg.modcods = strtol(argv[++i], NULL, 0);
|
|
else if ( ! strcmp(argv[i], "--framesizes") && i+1<argc )
|
|
cfg.framesizes = strtol(argv[++i], NULL, 0);
|
|
else if ( ! strcmp(argv[i], "--cr") && i+1<argc ) {
|
|
++i;
|
|
int f;
|
|
for ( f=0; f<FEC_COUNT; ++f )
|
|
if ( ! strcmp(argv[i], fec_names[f]) ) {
|
|
cfg.fec = (code_rate)f;
|
|
break;
|
|
}
|
|
if ( f == FEC_COUNT )
|
|
usage(argv[0], stderr, 1, argv[i]);
|
|
}
|
|
else if ( ! strcmp(argv[i], "--fastlock") )
|
|
cfg.fastlock = true;
|
|
else if ( ! strcmp(argv[i], "--fastdrift") )
|
|
cfg.fastdrift = true;
|
|
else if ( ! strcmp(argv[i], "--viterbi") )
|
|
cfg.viterbi = true;
|
|
else if ( ! strcmp(argv[i], "--ldpc-bf") && i+1<argc )
|
|
cfg.ldpc_bf = atoi(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--ldpc-helper") && i+1<argc )
|
|
cfg.ldpc_helper = argv[++i];
|
|
else if ( ! strcmp(argv[i], "--nhelpers") && i+1<argc )
|
|
cfg.nhelpers = atoi(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--hard-metric") )
|
|
cfg.hard_metric = true;
|
|
else if ( ! strcmp(argv[i], "--filter") ) {
|
|
fprintf(stderr, "--filter is obsolete; use --resample.\n");
|
|
cfg.resample = true;
|
|
}
|
|
else if ( ! strcmp(argv[i], "--resample") )
|
|
cfg.resample = true;
|
|
else if ( ! strcmp(argv[i], "--resample-rej") && i+1<argc )
|
|
cfg.resample_rej = atof(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--decim") && i+1<argc )
|
|
cfg.decim = atoi(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--sampler") && i+1<argc ) {
|
|
++i;
|
|
if (!strcmp(argv[i],"nearest")) cfg.sampler = config::SAMP_NEAREST;
|
|
else if (!strcmp(argv[i],"linear" )) cfg.sampler = config::SAMP_LINEAR;
|
|
else if (!strcmp(argv[i],"rrc" )) cfg.sampler = config::SAMP_RRC;
|
|
else usage(argv[0], stderr, 1, argv[i]);
|
|
}
|
|
else if ( ! strcmp(argv[i], "--rrc-steps") && i+1<argc )
|
|
cfg.rrc_steps = atoi(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--rrc-rej") && i+1<argc )
|
|
cfg.rrc_rej = atof(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--roll-off") && i+1<argc )
|
|
cfg.rolloff = atof(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--hq") ) {
|
|
cfg.fastlock = true;
|
|
cfg.viterbi = true;
|
|
cfg.sampler = config::SAMP_RRC;
|
|
}
|
|
else if ( ! strcmp(argv[i], "--hs") )
|
|
cfg.highspeed = true;
|
|
else if ( ! strcmp(argv[i], "--anf") && i+1<argc )
|
|
cfg.anf = atoi(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--cnr") )
|
|
cfg.cnr = true;
|
|
else if ( ! strcmp(argv[i], "--tune") && i+1<argc ) {
|
|
fprintf(stderr, "\n** --tune is broken, use --derotate instead.\n\n");
|
|
cfg.Ftune = atof(argv[++i]);
|
|
} else if ( ! strcmp(argv[i], "--drift") )
|
|
cfg.allow_drift = true;
|
|
else if ( ! strcmp(argv[i], "--freq-tol") && i+1<argc )
|
|
cfg.freq_tol = atof(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--sr-tol") && i+1<argc )
|
|
cfg.sr_tol = atof(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--hdlc") )
|
|
cfg.hdlc = true;
|
|
else if ( ! strcmp(argv[i], "--packetized") )
|
|
cfg.packetized = true;
|
|
#ifdef GUI
|
|
else if ( ! strcmp(argv[i], "--gui") )
|
|
cfg.gui = true;
|
|
else if ( ! strcmp(argv[i], "--duration") && i+1<argc )
|
|
cfg.duration = atof(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--linger") )
|
|
cfg.linger = true;
|
|
#endif
|
|
else if ( ! strcmp(argv[i], "--u8") )
|
|
cfg.input_format = config::INPUT_U8;
|
|
else if ( ! strcmp(argv[i], "--s8") )
|
|
cfg.input_format = config::INPUT_S8;
|
|
else if ( ! strcmp(argv[i], "--s12") )
|
|
cfg.input_format = config::INPUT_S12;
|
|
else if ( ! strcmp(argv[i], "--s16") )
|
|
cfg.input_format = config::INPUT_S16;
|
|
else if ( ! strcmp(argv[i], "--f32") )
|
|
cfg.input_format = config::INPUT_F32;
|
|
else if ( ! strcmp(argv[i], "--float-scale") && i+1<argc )
|
|
cfg.float_scale = atof(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--loop") )
|
|
cfg.loop_input = true;
|
|
else if ( ! strcmp(argv[i], "--inpipe") && i+1<argc )
|
|
cfg.input_pipe = atoi(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--inbuf") && i+1<argc )
|
|
cfg.input_buffer = atoi(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--buf-factor") && i+1<argc )
|
|
cfg.buf_factor = atoi(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--derotate") && i+1<argc )
|
|
cfg.Fderot = atof(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--fd-pp") && i+1<argc )
|
|
cfg.fd_pp = atoi(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--fd-gse") && i+1<argc )
|
|
cfg.fd_gse = atoi(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--awgn") && i+1<argc )
|
|
cfg.awgn = atof(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--fd-info") && i+1<argc )
|
|
cfg.fd_info = atoi(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--fd-iqsymbols") && i+1<argc )
|
|
cfg.fd_iqsymbols = atoi(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--fd-const") && i+1<argc )
|
|
cfg.fd_const = atoi(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--fd-spectrum") && i+1<argc )
|
|
cfg.fd_spectrum = atoi(argv[++i]);
|
|
else if ( ! strcmp(argv[i], "--json") )
|
|
cfg.json = true;
|
|
else if ( ! strcmp(argv[i], "--ts-udp") && i+1<argc )
|
|
cfg.udp_dst = argv[++i];
|
|
else
|
|
usage(argv[0], stderr, 1, argv[i]);
|
|
}
|
|
|
|
if ( cfg.highspeed ) {
|
|
fail("--hs mode is broken");
|
|
#if 0
|
|
switch ( cfg.standard ) {
|
|
case config::DVB_S: return run_highspeed(cfg);
|
|
case config::DVB_S2: return run_highspeed_s2(cfg);
|
|
}
|
|
#endif
|
|
}
|
|
switch ( cfg.standard ) {
|
|
case config::DVB_S: return run_dvbs(cfg);
|
|
case config::DVB_S2: return run_dvbs2(cfg);
|
|
}
|
|
}
|