pabr-leansdr/src/apps/leandvb.cc

1222 wiersze
41 KiB
C++

// This file is part of LeanSDR Copyright (C) 2016-2018 <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/rs.h"
#include "leansdr/gui.h"
#include "leansdr/filtergen.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_U16, INPUT_S16,
INPUT_F32
} input_format;
float float_scale; // Scaling factor for float data.
bool loop_input;
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
float awgn; // Standard deviation of noise
float Fm; // QPSK symbol rate (Hz)
enum dvb_version { DVB_S, DVB_S2 } standard;
cstln_lut<256>::predef constellation;
code_rate fec;
float Ftune; // Bias frequency for the QPSK demodulator (Hz)
bool allow_drift;
bool fastlock;
bool viterbi;
bool hard_metric;
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
config()
: verbose(false),
debug(false),
debug2(false),
highspeed(false),
input_format(INPUT_U8),
float_scale(1.0),
loop_input(false),
input_buffer(0),
buf_factor(4),
Fs(2.4e6),
Fderot(0),
anf(1),
cnr(false),
decim(0),
fd_pp(-1),
awgn(0),
Fm(2e6),
standard(DVB_S),
constellation(cstln_lut<256>::QPSK),
fec(FEC12),
Ftune(0),
allow_drift(false),
fastlock(false),
viterbi(false),
hard_metric(false),
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) {
}
};
int decimation(float Fin, float Fout) {
int d = Fin / Fout;
return max(d, 1);
}
void output_initial_info(FILE *f, 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);
fprintf(f, "CONSTELLATION %s%s%s\n",
quote, cstln_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);
}
int run(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 },
{ "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 },
{ "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;
// INPUT
pipebuf<cf32> p_rawiq(&sch, "rawiq", BUF_BASEBAND);
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);
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);
break;
}
case config::INPUT_U16: {
pipebuf<cu16> *p_stdin =
new pipebuf<cu16>(&sch, "stdin", BUF_BASEBAND+cfg.input_buffer);
file_reader<cu16> *r_stdin =
new file_reader<cu16>(&sch, 0, *p_stdin);
r_stdin->loop = cfg.loop_input;
cconverter<u16,32768, f32,0, 1,1> *r_convert =
new cconverter<u16,32768, f32,0, 1,1>(&sch, *p_stdin, p_rawiq);
break;
}
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);
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);
break;
}
default:
fail("Input format not implemented");
}
#ifdef GUI
float amp = 128;
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 *= 0.25;
}
#endif
pipebuf<cf32> *p_preprocessed = &p_rawiq;
// 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
pipebuf<f32> p_cnr(&sch, "cnr", BUF_SLOW);
cnr_fft<f32> *r_cnr = NULL;
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
}
// 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> *r_spectrum =
new spectrum<f32>(&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);
fir_filter<cf32,float> *r_resample = NULL;
int decim = 1;
if ( cfg.resample ) {
// Lowpass-filter and decimate.
if ( cfg.decim )
decim = cfg.decim;
else {
// Decimate to just above 4 samples per symbol
float target_Fs = cfg.Fm * 4;
decim = cfg.Fs / target_Fs;
if ( decim < 1 ) decim = 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, decim);
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, decim);
p_preprocessed = p_resampled;
cfg.Fs /= decim;
}
// DECIMATION
// (Unless already done in resampler)
if ( !cfg.resample && cfg.decim>1 ) {
decim = cfg.decim;
if ( cfg.verbose )
fprintf(stderr, "Inserting decimator 1/%u\n", decim);
pipebuf<cf32> *p_decimated =
new pipebuf<cf32>(&sch, "decimated", BUF_BASEBAND);
decimator<cf32> *p_decim =
new decimator<cf32>(&sch, decim, *p_preprocessed, *p_decimated);
p_preprocessed = p_decimated;
cfg.Fs /= decim;
}
if ( cfg.verbose )
fprintf(stderr, "Fs after resampling/decimation: %f Hz\n", cfg.Fs);
#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 *= 0.25;
r_fft_pp->decimation /= decim;
}
#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);
}
// Generic constellation receiver
pipebuf<softsymbol> p_symbols(&sch, "PSK soft-symbols", BUF_SYMBOLS);
pipebuf<f32> p_freq(&sch, "freq", BUF_SLOW);
pipebuf<f32> p_ss(&sch, "SS", BUF_SLOW);
pipebuf<f32> p_mer(&sch, "MER", BUF_SLOW);
pipebuf<cf32> p_sampled(&sch, "PSK symbols", BUF_BASEBAND);
sampler_interface<f32> *sampler;
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 ( cfg.rrc_steps == 0 ) {
// At least 64 discrete sampling points between symbols
cfg.rrc_steps = max(1, (int)(64*cfg.Fm / cfg.Fs));
}
if ( cfg.verbose )
fprintf(stderr, "RRC interpolator: %d steps\n", cfg.rrc_steps);
float Frrc = cfg.Fs * cfg.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);
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, cfg.rrc_steps);
break;
}
default:
fatal("Interpolator not implemented");
}
cstln_receiver<f32> demod(&sch, sampler,
*p_preprocessed, p_symbols,
&p_freq, &p_ss, &p_mer, &p_sampled);
if ( cfg.standard == config::DVB_S ) {
if ( cfg.constellation != cstln_lut<256>::QPSK &&
cfg.constellation != cstln_lut<256>::BPSK )
fprintf(stderr, "Warning: non-standard constellation for DVB-S\n");
}
if ( cfg.standard == config::DVB_S2 ) {
// For DVB-S2 testing only.
// Constellation should be determined from PL signalling.
fprintf(stderr, "DVB-S2: Testing symbol sampler only.\n");
}
demod.cstln = make_dvbs2_constellation(cfg.constellation, cfg.fec);
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);
// TRACKING FILTERS
if ( r_resample ) {
r_resample->freq_tap = &demod.freq_tap;
r_resample->tap_multiplier = 1.0 / decim;
r_resample->freq_tol = cfg.Fm/(cfg.Fs*decim) * 0.1;
}
if ( r_cnr ) {
r_cnr->freq_tap = &demod.freq_tap;
r_cnr->tap_multiplier = 1.0 / decim;
}
#ifdef GUI
if ( cfg.gui ) {
cscope<f32> *r_scope_symbols =
new cscope<f32>(&sch, p_sampled, -amp,amp);
r_scope_symbols->decimation = 1;
r_scope_symbols->cstln = &demod.cstln;
}
#endif
// DECONVOLUTION AND SYNCHRONIZATION
pipebuf<u8> p_bytes(&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(&sch, p_symbols, p_bytes,
demod.cstln, cfg.fec);
if ( cfg.fastlock ) r->resync_period = 1;
} else {
r_deconv = make_deconvol_sync_simple(&sch, p_symbols, p_bytes, cfg.fec);
r_deconv->fastlock = cfg.fastlock;
}
if ( cfg.hdlc ) {
pipebuf<u8> *p_descrambled =
new pipebuf<u8>(&sch, "descrambled", BUF_MPEGBYTES);
new etr192_descrambler(&sch, p_bytes, *p_descrambled);
pipebuf<u8> *p_frames =
new pipebuf<u8>(&sch, "frames", BUF_MPEGBYTES);
hdlc_sync *r_sync = new hdlc_sync(&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>(&sch, *p_frames, 1);
}
pipebuf<u8> p_mpegbytes(&sch, "mpegbytes", BUF_MPEGBYTES);
pipebuf<int> p_lock(&sch, "lock", BUF_SLOW);
pipebuf<u32> p_locktime(&sch, "locktime", BUF_PACKETS);
if ( ! cfg.hdlc ) {
mpeg_sync<u8,0> *r_sync =
new mpeg_sync<u8,0>(&sch, p_bytes, p_mpegbytes, r_deconv,
&p_lock, &p_locktime);
r_sync->fastlock = cfg.fastlock;
}
// DEINTERLEAVING
pipebuf< rspacket<u8> > p_rspackets(&sch, "RS-enc packets", BUF_PACKETS);
deinterleaver<u8> r_deinter(&sch, p_mpegbytes, p_rspackets);
// 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);
// 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;
// DERANDOMIZATION
pipebuf<tspacket> p_tspackets(&sch, "TS packets", BUF_PACKETS);
derandomizer r_derand(&sch, p_rtspackets, p_tspackets);
// OUTPUT
file_writer<tspacket> r_stdout(&sch, p_tspackets, 1);
// 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, "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<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);
}
if ( cfg.fd_const >= 0 ) {
cstln_lut<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>
(&sch, "SYMBOLS [", "[%.0f,%.0f]", ",", "]\n", p_sampled, cfg.fd_const);
else
symbol_printer = new file_carrayprinter<f32>
(&sch, "SYMBOLS %d", " %.0f,%.0f", "", "\n", p_sampled, cfg.fd_const);
symbol_printer->fixed_size = 128;
}
if ( cfg.fd_spectrum >= 0 ) {
file_vectorprinter<f32,1024> *spectrum_printer;
if ( cfg.json )
spectrum_printer = new file_vectorprinter<f32,1024>
(&sch, "SPECTRUM [", "%.3f", ",", "]\n", *p_spectrum, cfg.fd_spectrum);
else
spectrum_printer = new file_vectorprinter<f32,1024>
(&sch, "SPECTRUM %d", " %.3f", "", "\n", *p_spectrum, cfg.fd_spectrum);
(void)spectrum_printer;
}
// 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", "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 / 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"
" '_': packet received without errors\n"
" '.': error-corrected packet\n"
" '!': 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");
}
sch.run();
sch.shutdown();
if ( cfg.debug ) sch.dump();
if ( cfg.gui && cfg.linger ) while ( 1 ) { sch.run(); usleep(10000); }
return 0;
}
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_lut<256>::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;
// DERANDOMIZATION
pipebuf<tspacket> p_tspackets(&sch, "TS packets", BUF_PACKETS);
derandomizer r_derand(&sch, p_rtspackets, p_tspackets);
// OUTPUT
file_writer<tspacket> r_stdout(&sch, p_tspackets, 1);
// 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;
}
// 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;
}
// 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"
" --s16 Input format is 16-bit signed (plutosdr)\n"
" --f32 Input format is 32-bit float (gqrx)\n"
" -f HZ Input sample rate (default: 2.4e6)\n"
" --loop Repeat (stdin must be a file)\n"
" --inbuf INT Additional input buffering (samples)\n"
);
fprintf
(f,
"\nPreprocessing options:\n"
" --anf INT Number of birdies to remove (default: 1)\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,
"\nDVB-S options:\n"
" --sr HZ Symbol rate (default: 2e6)\n"
" --tune HZ Bias frequency for demodulation\n"
" --drift Track frequency drift beyond safe limits\n"
" --standard S DVB-S (default), DVB-S2 (not implemented)\n"
" --const STRING QPSK (default),\n"
" BPSK .. 32APSK (DVB-S2),\n"
" 64APSKe (DVB-S2X),\n"
" 16QAM .. 256QAM (experimental)\n"
" --cr NUM/DEN Code rate 1/2 (default) .. 7/8 .. 9/10\n"
" --fastlock Synchronize more aggressively (CPU-intensive)\n"
" --sampler 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"
" --viterbi Use Viterbi (CPU-intensive)\n"
" --hard-metric Use Hamming distances with Viterbi\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-pp FDNUM Dump preprocessed IQ data to file descriptor\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"
" --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;
if ( ! strcmp(argv[i], "BPSK" ) )
cfg.constellation = cstln_lut<256>::BPSK;
else if ( ! strcmp(argv[i], "QPSK" ) )
cfg.constellation = cstln_lut<256>::QPSK;
else if ( ! strcmp(argv[i], "8PSK" ) )
cfg.constellation = cstln_lut<256>::PSK8;
else if ( ! strcmp(argv[i], "16APSK" ) )
cfg.constellation = cstln_lut<256>::APSK16;
else if ( ! strcmp(argv[i], "32APSK" ) )
cfg.constellation = cstln_lut<256>::APSK32;
else if ( ! strcmp(argv[i], "64APSKe" ) )
cfg.constellation = cstln_lut<256>::APSK64E;
else if ( ! strcmp(argv[i], "16QAM" ) )
cfg.constellation = cstln_lut<256>::QAM16;
else if ( ! strcmp(argv[i], "64QAM" ) )
cfg.constellation = cstln_lut<256>::QAM64;
else if ( ! strcmp(argv[i], "256QAM" ) )
cfg.constellation = cstln_lut<256>::QAM256;
else usage(argv[0], stderr, 1, argv[i]);
}
else if ( ! strcmp(argv[i], "--cr") && i+1<argc ) {
++i;
// DVB-S
if ( ! strcmp(argv[i], "1/2" ) ) cfg.fec = FEC12;
else if ( ! strcmp(argv[i], "2/3" ) ) cfg.fec = FEC23;
else if ( ! strcmp(argv[i], "3/4" ) ) cfg.fec = FEC34;
else if ( ! strcmp(argv[i], "5/6" ) ) cfg.fec = FEC56;
else if ( ! strcmp(argv[i], "7/8" ) ) cfg.fec = FEC78;
// DVB-S2
else if ( ! strcmp(argv[i], "4/5" ) ) cfg.fec = FEC45;
else if ( ! strcmp(argv[i], "8/9" ) ) cfg.fec = FEC89;
else if ( ! strcmp(argv[i], "9/10" ) ) cfg.fec = FEC910;
else usage(argv[0], stderr, 1, argv[i]);
}
else if ( ! strcmp(argv[i], "--fastlock") )
cfg.fastlock = true;
else if ( ! strcmp(argv[i], "--viterbi") )
cfg.viterbi = true;
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 )
cfg.Ftune = atof(argv[++i]);
else if ( ! strcmp(argv[i], "--drift") )
cfg.allow_drift = true;
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], "--u16") )
cfg.input_format = config::INPUT_U16;
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], "--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], "--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-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
usage(argv[0], stderr, 1, argv[i]);
}
if ( cfg.highspeed )
return run_highspeed(cfg);
else
return run(cfg);
}