kopia lustrzana https://github.com/pabr/leansdr
Add spectrum output and JSON syntax for third-party GUIs
rodzic
fe0f0a1e9d
commit
20acc20b10
|
@ -1,4 +1,5 @@
|
|||
HEAD
|
||||
* leandvb: Spectrum output, JSON syntax.
|
||||
* leandvb, leandvbtx: DVB-S with non-standard constellations (with --viterbi).
|
||||
* leandvb: Support all DVB-S code rates with --viterbi.
|
||||
* leandvbtx: Support all DVB-S code rates.
|
||||
|
|
|
@ -69,6 +69,8 @@ struct config {
|
|||
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),
|
||||
|
@ -110,7 +112,9 @@ struct config {
|
|||
linger(false),
|
||||
fd_info(-1),
|
||||
Finfo(5),
|
||||
fd_const(-1) {
|
||||
fd_const(-1),
|
||||
fd_spectrum(-1),
|
||||
json(false) {
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -119,6 +123,20 @@ int decimation(float Fin, float 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;
|
||||
|
@ -293,6 +311,20 @@ int run(config &cfg) {
|
|||
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);
|
||||
|
@ -561,9 +593,8 @@ int run(config &cfg) {
|
|||
new file_printer<float>(&sch, "VBER %.6f\n", p_vber, cfg.fd_info);
|
||||
// Output constants immediately
|
||||
FILE *f = fdopen(cfg.fd_info, "w");
|
||||
static const char *fec_names[] = { "1/2", "2/3", "3/4", "5/6", "7/8" };
|
||||
fprintf(f, "CR %s\n", fec_names[cfg.fec]);
|
||||
fprintf(f, "SR %f\n", cfg.Fm);
|
||||
if ( ! f ) fatal("fdopen(fd_info)");
|
||||
output_initial_info(f, cfg);
|
||||
fflush(f);
|
||||
}
|
||||
if ( cfg.fd_const >= 0 ) {
|
||||
|
@ -571,14 +602,40 @@ int run(config &cfg) {
|
|||
if ( c ) {
|
||||
// Output constellation immediately
|
||||
FILE *f = fdopen(cfg.fd_const, "w");
|
||||
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");
|
||||
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);
|
||||
}
|
||||
new file_carrayprinter<f32>(&sch, "SYMBOLS %d", " %.0f,%.0f", "\n",
|
||||
p_sampled, cfg.fd_const);
|
||||
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
|
||||
|
@ -830,14 +887,19 @@ int run_highspeed(config &cfg) {
|
|||
new file_printer<float>(&sch, "VBER %.6f\n", p_vber, cfg.fd_info);
|
||||
// Output constants immediately
|
||||
FILE *f = fdopen(cfg.fd_info, "w");
|
||||
static const char *fec_names[] = { "1/2", "2/3", "3/4", "5/6", "7/8" };
|
||||
fprintf(f, "CR %s\n", fec_names[cfg.fec]);
|
||||
fprintf(f, "SR %f\n", cfg.Fm);
|
||||
if ( ! f ) fatal("fdopen(fd_info)");
|
||||
output_initial_info(f, cfg);
|
||||
fflush(f);
|
||||
}
|
||||
if ( cfg.fd_const >= 0 ) {
|
||||
new file_carrayprinter<u8>(&sch, "SYMBOLS %d", " %d,%d", "\n",
|
||||
p_sampled, cfg.fd_const);
|
||||
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
|
||||
|
@ -952,6 +1014,8 @@ void usage(const char *name, FILE *f, int c) {
|
|||
" -d Output debugging info during operation\n"
|
||||
" --fd-info NUM Output demodulator status to file descriptor\n"
|
||||
" --fd-const NUM Output constellation and symbols to file descr\n"
|
||||
" --fd-spectrum NUM Output spectrum to file descr\n"
|
||||
" --json Use JSON syntax\n"
|
||||
);
|
||||
#ifdef GUI
|
||||
fprintf(f,
|
||||
|
@ -1109,6 +1173,10 @@ int main(int argc, const char *argv[]) {
|
|||
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);
|
||||
}
|
||||
|
|
|
@ -115,7 +115,7 @@ private:
|
|||
int phase;
|
||||
};
|
||||
|
||||
// [file_listprinter] writes all data available from a [pipebuf]
|
||||
// [file_carrayprinter] writes all data available from a [pipebuf]
|
||||
// to a file descriptor on a single line.
|
||||
// Special case for complex.
|
||||
|
||||
|
@ -124,29 +124,69 @@ struct file_carrayprinter : runnable {
|
|||
file_carrayprinter(scheduler *sch,
|
||||
const char *_head,
|
||||
const char *_format,
|
||||
const char *_sep,
|
||||
const char *_tail,
|
||||
pipebuf< complex<T> > &_in, int _fdout) :
|
||||
runnable(sch, _in.name),
|
||||
scale(1), in(_in),
|
||||
head(_head), format(_format), tail(_tail),
|
||||
scale(1), fixed_size(0), in(_in),
|
||||
head(_head), format(_format), sep(_sep), tail(_tail),
|
||||
fout(fdopen(_fdout,"w")) {
|
||||
}
|
||||
void run() {
|
||||
int n = in.readable();
|
||||
if ( n && fout ) {
|
||||
fprintf(fout, head, n);
|
||||
complex<T> *pin=in.rd(), *pend=pin+n;
|
||||
for ( ; pin<pend; ++pin )
|
||||
fprintf(fout, format, pin->re*scale, pin->im*scale);
|
||||
fprintf(fout, "%s", tail);
|
||||
int n, nmin = fixed_size ? fixed_size : 1;
|
||||
while ( (n=in.readable()) >= nmin ) {
|
||||
if ( fixed_size ) n = fixed_size;
|
||||
if ( fout ) {
|
||||
fprintf(fout, head, n);
|
||||
complex<T> *pin = in.rd();
|
||||
for ( int i=0; i<n; ++i ) {
|
||||
if ( i ) fprintf(fout, "%s", sep);
|
||||
fprintf(fout, format, pin[i].re*scale, pin[i].im*scale);
|
||||
}
|
||||
fprintf(fout, "%s", tail);
|
||||
}
|
||||
fflush(fout);
|
||||
in.read(n);
|
||||
}
|
||||
in.read(n);
|
||||
}
|
||||
T scale;
|
||||
int fixed_size; // Number of elements per batch, or 0.
|
||||
private:
|
||||
pipereader< complex<T> > in;
|
||||
const char *head, *format, *sep, *tail;
|
||||
FILE *fout;
|
||||
};
|
||||
|
||||
template<typename T, int N>
|
||||
struct file_vectorprinter : runnable {
|
||||
file_vectorprinter(scheduler *sch,
|
||||
const char *_head,
|
||||
const char *_format,
|
||||
const char *_sep,
|
||||
const char *_tail,
|
||||
pipebuf<T[N]> &_in, int _fdout) :
|
||||
runnable(sch, _in.name), scale(1), in(_in),
|
||||
head(_head), format(_format), sep(_sep), tail(_tail) {
|
||||
fout = fdopen(_fdout,"w");
|
||||
if ( ! fout ) fatal("fdopen");
|
||||
}
|
||||
void run() {
|
||||
while ( in.readable() >= 1 ) {
|
||||
fprintf(fout, head, N);
|
||||
T (*pin)[N] = in.rd();
|
||||
for ( int i=0; i<N; ++i ) {
|
||||
if ( i ) fprintf(fout, "%s", sep);
|
||||
fprintf(fout, format, (*pin)[i]*scale);
|
||||
}
|
||||
fprintf(fout, "%s", tail);
|
||||
in.read(1);
|
||||
}
|
||||
fflush(fout);
|
||||
}
|
||||
T scale;
|
||||
private:
|
||||
pipereader< complex<T> > in;
|
||||
const char *head, *format, *tail;
|
||||
pipereader<T[N]> in;
|
||||
const char *head, *format, *sep, *tail;
|
||||
FILE *fout;
|
||||
};
|
||||
|
||||
|
|
|
@ -555,6 +555,17 @@ namespace leansdr {
|
|||
|
||||
}; // cstln_lut
|
||||
|
||||
static const char *cstln_names[] = {
|
||||
[cstln_lut<256>::BPSK] = "BPSK",
|
||||
[cstln_lut<256>::QPSK] = "QPSK",
|
||||
[cstln_lut<256>::PSK8] = "8PSK",
|
||||
[cstln_lut<256>::APSK16] = "16APSK",
|
||||
[cstln_lut<256>::APSK32] = "32APSK",
|
||||
[cstln_lut<256>::APSK64E] = "64APSKe",
|
||||
[cstln_lut<256>::QAM16] = "16QAM",
|
||||
[cstln_lut<256>::QAM64] = "64QAM",
|
||||
[cstln_lut<256>::QAM256] = "256QAM"
|
||||
};
|
||||
|
||||
// SAMPLER INTERFACE FOR CSTLN_RECEIVER
|
||||
|
||||
|
@ -1315,7 +1326,67 @@ namespace leansdr {
|
|||
T *avgpower;
|
||||
int phase;
|
||||
}; // cnr_fft
|
||||
|
||||
|
||||
template<typename T>
|
||||
struct spectrum : runnable {
|
||||
static const int nfft = 1024;
|
||||
spectrum(scheduler *sch, pipebuf< complex<T> > &_in,
|
||||
pipebuf<float[nfft]> &_out)
|
||||
: runnable(sch, "spectrum"),
|
||||
decimation(1048576), kavg(0.1),
|
||||
in(_in), out(_out),
|
||||
fft(nfft), avgpower(NULL), phase(0) {
|
||||
}
|
||||
|
||||
int decimation;
|
||||
float kavg;
|
||||
|
||||
void run() {
|
||||
while ( in.readable()>=fft.n && out.writable()>=1 ) {
|
||||
phase += fft.n;
|
||||
if ( phase >= decimation ) {
|
||||
phase -= decimation;
|
||||
do_spectrum();
|
||||
}
|
||||
in.read(fft.n);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void do_spectrum() {
|
||||
complex<T> data[fft.n];
|
||||
memcpy(data, in.rd(), fft.n*sizeof(data[0]));
|
||||
fft.inplace(data, true);
|
||||
float power[nfft];
|
||||
for ( int i=0; i<fft.n; ++i )
|
||||
power[i] = (float)data[i].re*data[i].re + (float)data[i].im*data[i].im;
|
||||
if ( ! avgpower ) {
|
||||
// Initialize with first spectrum
|
||||
avgpower = new float[fft.n];
|
||||
memcpy(avgpower, power, fft.n*sizeof(avgpower[0]));
|
||||
}
|
||||
// Accumulate and low-pass filter
|
||||
for ( int i=0; i<fft.n; ++i )
|
||||
avgpower[i] = avgpower[i]*(1-kavg) + power[i]*kavg;
|
||||
|
||||
// Reuse power[]
|
||||
for ( int i=0; i<fft.n/2; ++i ) {
|
||||
power[i] = 10 * log10f(avgpower[nfft/2+i]);
|
||||
power[nfft/2+i] = 10 * log10f(avgpower[i]);
|
||||
}
|
||||
memcpy(out.wr(), power, sizeof(power[0])*nfft);
|
||||
out.written(1);
|
||||
}
|
||||
|
||||
pipereader< complex<T> > in;
|
||||
pipewriter< float[nfft] > out;
|
||||
cfft_engine<T> fft;
|
||||
T *avgpower;
|
||||
int phase;
|
||||
}; // spectrum
|
||||
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // LEANSDR_SDR_H
|
||||
|
|
Ładowanie…
Reference in New Issue