diff --git a/demod/iq_svcl/README.md b/demod/iq_svcl/README.md index 7867894..c7cc3ec 100644 --- a/demod/iq_svcl/README.md +++ b/demod/iq_svcl/README.md @@ -23,16 +23,23 @@ receive IF stream from baseband IQ via TCP, default `PORT=1280 (iq_svcl.h)`
     ``: IF sample rate
     `=8,16,32`: output/IF bits per (real) sample (u8, s16 or f32)
- down-converts up to `MAX_FQ=(4+1) (iq_base.h)` channels/signals. More signals than number of CPUs/cores is not recommended.
+ down-converts up to `MAX_FQ=(4+1) (iq_base.h)` channels/signals. (On older CPUs, more signals than number of CPU cores is not recommended.)
(Note: If the baseband sample rate has no appropriate factors (e.g. if prime), the IF sample rate might be high and IF-processing slow.)
- One channel can be used for scanning, `--fft ` makes FFT (2 seconds average). - The FFT is saved in `` as `;`, approx. 200 Hz per bin.
+ One channel can be used for scanning, e.g. `./iq_server --fft_avg ` makes *m* rows of avg-FFT (FFT_AVG=2 seconds average), and + this channel will be reused for client FFT requests. Only one channel/thread can be used for FFT/scanning. A client can request a new FFT, + if the last FFT has finished.
+ There are two kinds of FFTs, `fft_all` and `fft_avg`. `fft_avg` integrates over FFT_AVG=2 seconds and can be used for signal peak scanning. + For waterfall display, `--fft_all ` produces *m*\*FFT_AVG\*FFT_FPS/2 rows of FFT (FFT_AVG seconds, FFT_FPS/2 per sec). + The FFT is saved in `` as
+    `sec.ms,freq_min,freq_max,Hz/bin,N_bins, db_1,...,db_N`
+ approx. 200 Hz per bin. + Choose `filename="-"` for `stdout`.
If no output bps is chosen (`--bo [8,16,32]`), the IF bps is equal to the baseband bps. It is recommended to use `--bo 32` (i.e. float32) output, then no quantization noise is introduced when converting from internal float32 samples.
- Ex.2
[terminal 1]
- `T1$ rtl_sdr -f 403.0M -s 1920000 - | ./iq_server --fft fft_server.txt --bo 32 - 1920000 8`
+ `T1$ rtl_sdr -f 403.0M -s 1920000 - | ./iq_server --fft_avg 1 fft_server.csv --bo 32 - 1920000 8`
[terminal 2]
`T2$ ./iq_client --freq -0.3125 | ./m10mod -c -vv --IQ 0.0 - 48000 32`
[terminal 3]
@@ -41,7 +48,17 @@ receive IF stream from baseband IQ via TCP, default `PORT=1280 (iq_svcl.h)`
`T4$ ./iq_client --stop`    (*close all clients and stop server*)
- The `iq_server` `--fft` option immediately starts reading the IQ stream (so buffering is reduced).
- `./iq_client --fft ` can also request FFT.
- The IF sample rate `if_sr` is at least 48000 such that the baseband sample rate `sr` is a multiple of `if_sr`. + - The `iq_server` FFT options immediately start reading the IQ stream (so buffering is reduced).
+ `./iq_client `, where `=--fft_avg_cl` or `--fft_all_cl`, requests FFT from the server.
+ (`=--fft_avg_sv/--fft_all_sv` would save the FFT at the server, but only if `./iq_server --enable_clsv_out`.)
+ The IF sample rate `if_sr` is at least 48000 and such that the baseband sample rate `sr` is a multiple of `if_sr`. + + - Ex.3
+ [terminal 1]
+ `T1$ rtl_sdr -f 404550k -s 2048000 - | ./iq_server --fft_avg 1 fft_avg.csv --bo 32 - 2048000 8`
+ (scan FFT: `./scan_fft fft_avg.csv`)
+ [terminal 3]
+ `T3$ ./iq_client --fft_all_cl -1 - | python plot_fft_ani.py 3 -`
+ ![FFT image](fft3-1.png "FFT") + diff --git a/demod/iq_svcl/fft3-1.png b/demod/iq_svcl/fft3-1.png new file mode 100644 index 0000000..029c60b Binary files /dev/null and b/demod/iq_svcl/fft3-1.png differ diff --git a/demod/iq_svcl/iq_base.h b/demod/iq_svcl/iq_base.h index db33108..eca90c5 100644 --- a/demod/iq_svcl/iq_base.h +++ b/demod/iq_svcl/iq_base.h @@ -34,7 +34,10 @@ typedef struct { double xlt_fq; float complex *blk; int used; + // int fft; + int stop_fft; + int fft_num; } thd_t; @@ -97,6 +100,7 @@ typedef struct { thd_t thd; int fd; char *fname; + FILE *fpo; } thargs_t; diff --git a/demod/iq_svcl/iq_client.c b/demod/iq_svcl/iq_client.c index 93b5487..2cb8180 100644 --- a/demod/iq_svcl/iq_client.c +++ b/demod/iq_svcl/iq_client.c @@ -4,6 +4,27 @@ * * gcc -O2 iq_client.c -o iq_client * + * usage: + * (request IF IQ samples) + * ./iq_client [--ip ] [--port ] --freq # -0.5 < fq < 0.5 + * + * (request FFT) + * ./iq_client # FFT csv output + * : + * --fft_avg_cl # client out + * --fft_all_cl # client out + * --fft_avg_sv # server out (if iq_server --enable_clsv_out) + * --fft_avg_sv # server out (if iq_server --enable_clsv_out) + * : + * _avg_: m avg-FFTs + * _all_: m*FFT_FPS/2 FFTs + * m = -1 : continuous FFT output + * : + * csv filename ("-": stdout) + * + * ./iq_client - # close client + * ./iq_client --stop # close all clients, stop server + * * author: zilog80 */ @@ -49,17 +70,24 @@ int main(int argc, char *argv[]) { } else serv_port = port; } - else if (strcmp(*argv, "--fft0") == 0) { - sprintf(sendln, "%s", "--fft0"); - } - else if (strcmp(*argv, "--fft") == 0) { - sprintf(sendln, "%s", "--fft"); - ++argv; - if (*argv) { - fname_fft = *argv; + else if (strncmp(*argv, "--fft", 5) == 0) { + char *arg_fft = *argv; + int fft_num = 0; + int opt_fft_cl = 0; + if (strncmp(arg_fft+5, "_avg", 4) == 0) opt_fft_cl = OPT_FFT_AVG; + else if (strncmp(arg_fft+5, "_all", 4) != 0) return -1; + if (strncmp(arg_fft+5+4, "_cl", 3) == 0) { + opt_fft_cl |= OPT_FFT_CLNT; + re = 2; } + else if (strncmp(arg_fft+5+4, "_sv", 3) == 0) opt_fft_cl |= OPT_FFT_SERV; else return -1; - re = 2; + ++argv; + if (*argv) fft_num = atoi(*argv); else return -1; + ++argv; + if (*argv) fname_fft = *argv; else return -1; + //sprintf(sendln, "%s_%s_%s", "--fft", (opt_fft_cl & OPT_FFT_AVG) ? "avg" : "all", (opt_fft_cl & OPT_FFT_CLNT) ? "cl" : "sv"); + sprintf(sendln, "%s %d %s", arg_fft, fft_num, fname_fft); } else if (strcmp(*argv, "--freq") == 0) { ++argv; @@ -155,7 +183,9 @@ int main(int argc, char *argv[]) { else if ( re == 2 ) { // fft data - FILE *fpo = fopen(fname_fft, "wb"); + FILE *fpo = NULL; + if (fname_fft[0] == '-') fpo = stdout; + else fpo = fopen(fname_fft, "wb"); if (fpo != NULL) { memset(recvln, 0, LINELEN+1); diff --git a/demod/iq_svcl/iq_server.c b/demod/iq_svcl/iq_server.c index b78cdba..0a7d5bf 100644 --- a/demod/iq_svcl/iq_server.c +++ b/demod/iq_svcl/iq_server.c @@ -7,6 +7,24 @@ * * (gcc -O2 iq_client.c -o iq_client) * + * usage: + * ./iq_server [--enable_clsv_out] [--port ] [iq_baseband.wav] + * no wav-file: stdin + * + * ./iq_server [--bo ] - [iq_baseband.raw] + * =8,16,32 bit client/IF output + * + * ./iq_server [iq_baseband.wav] # FFT csv output + * : + * --fft_avg + * --fft_all + * : + * _avg: m avg-FFTs + * _all: m*FFT_FPS/2 FFTs + * m = -1 : continuous FFT output + * : + * csv filename ("-": stdout) + * * author: zilog80 */ @@ -27,13 +45,13 @@ #include "iq_svcl.h" #include "iq_base.h" +#define FFT_AVG 2 // fft_avg: integrate FFT_AVG seconds, 2*FFT_FPS FFTs +#define FFT_FPS 16 // fft_all: output (ca.) FFT_FPS/2 per sec + #define FPOUT stderr -#define OPT_FFT_SERV 1 // server -#define OPT_FFT_CLSV 2 // server (client request) -#define OPT_FFT_CLNT 3 // server -> client - static int option_dbg = 0; +static int option_clsv_out = 0; static int tcp_eof = 0; @@ -275,21 +293,82 @@ static void *thd_IF(void *targs) { // pcm_t *pcm, double xlt_fq return NULL; } -#define FFT_SEC 2 -#define FFT_FPS 20 + +static int fft_txt_prn(FILE *fpo, dft_t *dft, float *db) { + int j; + + fprintf(fpo, "# ; ## sr:%d , N:%d\n", dft->sr, dft->N); + for (j = dft->N/2; j < dft->N/2 + dft->N; j++) { + fprintf(fpo, "%+11.8f;%7.2f\n", bin2fq(dft, j % dft->N), db[j % dft->N]); + } + + return 0; +} + +static int fft_txt_tcp(int fd, dft_t *dft, float *db) { + char sendln[LINELEN+1]; + int sendln_len; + int j, l; + + snprintf(sendln, LINELEN, "# ; ## sr:%d , N:%d\n", dft->sr, dft->N); + sendln_len = strlen(sendln); + l = write(fd, sendln, sendln_len); + for (j = dft->N/2; j < dft->N/2 + dft->N; j++) { + memset(sendln, 0, LINELEN+1); + snprintf(sendln, LINELEN, "%+11.8f;%7.2f\n", bin2fq(dft, j % dft->N), db[j % dft->N]); + sendln_len = strlen(sendln); + l = write(fd, sendln, sendln_len); + } + + return 0; +} + + +static int fft_csv_prn(FILE *fpo, dft_t *dft, float *db, double t_sec) { + int j; + + fprintf(fpo, "%7.3f, ", t_sec); + fprintf(fpo, "%d, %d, ", (int)bin2freq(dft, dft->N/2), (int)bin2freq(dft, dft->N/2 - 1)); + fprintf(fpo, "%.2f, ", dft->sr/(double)dft->N); + fprintf(fpo, "%d, ", dft->N); + for (j = dft->N/2; j < dft->N/2 + dft->N; j++) { + fprintf(fpo, "%7.2f%c", db[j % dft->N], j < dft->N/2 + dft->N-1 ? ',' : '\n'); + } + + return 0; +} + +static int fft_csv_tcp(int fd, dft_t *dft, float *db, double t_sec) { + char sendln[LINELEN+1]; + int sendln_len; + int j, l; + + snprintf(sendln, LINELEN, "%7.3f, %d, %d, %.2f, %d, ", + t_sec, (int)bin2freq(dft, dft->N/2), (int)bin2freq(dft, dft->N/2 - 1), dft->sr/(double)dft->N, dft->N); + sendln_len = strlen(sendln); + l = write(fd, sendln, sendln_len); + for (j = dft->N/2; j < dft->N/2 + dft->N; j++) { + memset(sendln, 0, LINELEN+1); + snprintf(sendln, LINELEN, "%7.2f%c", db[j % dft->N], j < dft->N/2 + dft->N-1 ? ',' : '\n'); + sendln_len = strlen(sendln); + l = write(fd, sendln, sendln_len); + } + + return 0; +} + static void *thd_FFT(void *targs) { thargs_t *tharg = targs; pcm_t *pcm = &(tharg->pcm); - FILE *fpo = NULL; - char *fname_fft = "db_fft.txt"; - int k; int bitQ = 0; float complex *z = NULL; + float *db = NULL; + float *all_rZ = NULL; float *avg_rZ = NULL; float *avg_db = NULL; @@ -312,7 +391,6 @@ static void *thd_FFT(void *targs) { dsp.bps_out = pcm->bps_out; - //(dsp.thd)->fft = 1; if (option_dbg) { fprintf(stderr, "init FFT buffers\n"); } @@ -326,6 +404,8 @@ static void *thd_FFT(void *targs) { z = calloc(dsp.decM+1, sizeof(float complex)); if (z == NULL) goto exit_thread; + db = calloc(dsp.DFT.N+1, sizeof(float)); if (db == NULL) goto exit_thread; + all_rZ = calloc(dsp.DFT.N+1, sizeof(float)); if (all_rZ == NULL) goto exit_thread; avg_rZ = calloc(dsp.DFT.N+1, sizeof(float)); if (avg_rZ == NULL) goto exit_thread; avg_db = calloc(dsp.DFT.N+1, sizeof(float)); if (avg_db == NULL) goto exit_thread; @@ -334,12 +414,16 @@ static void *thd_FFT(void *targs) { int len = dsp.DFT.N / dsp.decM; int mlen = len*dsp.decM; int sum_n = 0; - int sec = FFT_SEC; - int fft_step = dsp.sr_base/(dsp.DFT.N*FFT_FPS); + int sum_fft = 0; + int avg_sec = FFT_AVG; + int fft_step = (int)(dsp.sr_base/(double)(dsp.DFT.N*FFT_FPS) + 0.5); int n_fft = 0; int th_used = 0; int readSamples = 1; + int n_out = 0; + + bitQ = 0; while ( bitQ != EOF ) { @@ -367,65 +451,95 @@ static void *thd_FFT(void *targs) { n++; if (n == len) { // mlen = len * decM <= DFT.N - n_fft += 1; - - if ((dsp.thd)->fft && sum_n*n_fft*mlen < sec*dsp.sr_base && n_fft >= fft_step) + if ( (dsp.thd)->fft ) { - for (j = 0; j < mlen; j++) { - dsp.DFT.Z[j] *= dsp.DFT.win[j]; + if ((dsp.thd)->fft_num == 0) + { + if ( tharg->fpo ) { fclose(tharg->fpo); tharg->fpo = NULL; } + else if ( tharg->fd > STDIN_FILENO ) { close(tharg->fd); tharg->fd = -1; } + + (dsp.thd)->fft = 0; } - while (j < dsp.DFT.N) dsp.DFT.Z[j++] = 0.0; // dft(Z[...]) != 0 - raw_dft(&(dsp.DFT), dsp.DFT.Z); + n_fft += 1; - for (j = 0; j < dsp.DFT.N; j++) avg_rZ[j] += cabs(dsp.DFT.Z[j]); + if (sum_n*n_fft*mlen < avg_sec*dsp.sr_base && n_fft >= fft_step) { + n_fft = fft_step; - sum_n++; - n_fft = 0; - } - if (sum_n*fft_step*mlen >= sec*dsp.sr_base) { - - for (j = 0; j < dsp.DFT.N; j++) avg_rZ[j] /= dsp.DFT.N*(float)sum_n; - for (j = 0; j < dsp.DFT.N; j++) avg_db[j] = 20.0*log10(avg_rZ[j]+1e-20); - - - pthread_mutex_lock( (dsp.thd)->mutex ); - fprintf(FPOUT, "<%d: FFT>\n", (dsp.thd)->tn); - pthread_mutex_unlock( (dsp.thd)->mutex ); - - if ( (dsp.thd)->fft == OPT_FFT_CLNT ) { // send FFT data to client - char sendln[LINELEN+1]; - int sendln_len; - int l; - snprintf(sendln, LINELEN, "# ; ## sr:%d , N:%d\n", dsp.DFT.sr, dsp.DFT.N); - sendln_len = strlen(sendln); - l = write(tharg->fd, sendln, sendln_len); - for (j = dsp.DFT.N/2; j < dsp.DFT.N/2 + dsp.DFT.N; j++) { - memset(sendln, 0, LINELEN+1); - snprintf(sendln, LINELEN, "%+11.8f;%7.2f\n", bin2fq(&(dsp.DFT), j % dsp.DFT.N), avg_db[j % dsp.DFT.N]); - sendln_len = strlen(sendln); - l = write(tharg->fd, sendln, sendln_len); + if (sum_fft == 0) { + pthread_mutex_lock( (dsp.thd)->mutex ); + fprintf(FPOUT, "<%d: FFT_START>\n", (dsp.thd)->tn); + pthread_mutex_unlock( (dsp.thd)->mutex ); } - } - else { // save FFT at server - if ( (dsp.thd)->fft == OPT_FFT_SERV ) fname_fft = tharg->fname; - else /* OPT_FFT_CLSV */ fname_fft = "db_fft_cl.txt"; - fpo = fopen(fname_fft, "wb"); - if (fpo != NULL) { - fprintf(fpo, "# ; ## sr:%d , N:%d\n", dsp.DFT.sr, dsp.DFT.N); - for (j = dsp.DFT.N/2; j < dsp.DFT.N/2 + dsp.DFT.N; j++) { - fprintf(fpo, "%+11.8f;%7.2f\n", bin2fq(&(dsp.DFT), j % dsp.DFT.N), avg_db[j % dsp.DFT.N]); + + for (j = 0; j < mlen; j++) { + dsp.DFT.Z[j] *= dsp.DFT.win[j]; + } + while (j < dsp.DFT.N) dsp.DFT.Z[j++] = 0.0; // dft(Z[...]) != 0 + + raw_dft(&(dsp.DFT), dsp.DFT.Z); + + for (j = 0; j < dsp.DFT.N; j++) { + float rZ = cabs(dsp.DFT.Z[j]); + avg_rZ[j] += rZ; + all_rZ[j] += rZ; + } + + if ( (sum_fft&1)==1 && ((dsp.thd)->fft & OPT_FFT_AVG) == 0 ) { + double t_sec = sum_fft*n_fft*mlen / (double)dsp.sr_base; + for (j = 0; j < dsp.DFT.N; j++) { // if sum_fft odd, + db[j] = 20.0*log10(0.5*all_rZ[j]/dsp.DFT.N+1e-20); // 0.5: rZ_0+rZ_1 + all_rZ[j] = 0.0f; + } + // sec.ms, freq_min, freq_max, Hz/bin, N_bins, db_1, ..., db_N + if ( tharg->fpo ) { // save FFT at server + fft_csv_prn(tharg->fpo, &(dsp.DFT), db, t_sec); + } + else if ( tharg->fd > STDIN_FILENO ) { + fft_csv_tcp(tharg->fd, &(dsp.DFT), db, t_sec); } - fclose(fpo); } - else { - fprintf(stderr, "error: open %s\n", fname_fft); - } - } - if ( (dsp.thd)->fft != OPT_FFT_SERV ) close(tharg->fd); - (dsp.thd)->fft = 0; - sum_n = 0; + sum_n++; + sum_fft++; + n_fft = 0; + } + if (sum_n*fft_step*mlen >= avg_sec*dsp.sr_base) { + float nN = 1.0/(dsp.DFT.N*(float)sum_n); + for (j = 0; j < dsp.DFT.N; j++) { + avg_db[j] = 20.0*log10(nN*avg_rZ[j]+1e-20); + avg_rZ[j] = 0.0f; + } + + if ( (dsp.thd)->fft & OPT_FFT_AVG ) { + double t_sec = sum_fft*fft_step*mlen / (double)dsp.sr_base; + if ( tharg->fpo ) { // send FFT data to client + fft_csv_prn(tharg->fpo, &(dsp.DFT), avg_db, t_sec); + } + else if ( tharg->fd > STDIN_FILENO ) { + fft_csv_tcp(tharg->fd, &(dsp.DFT), avg_db, t_sec); + } + } + + n_out++; + + if ((dsp.thd)->fft_num > 0 && n_out >= (dsp.thd)->fft_num) + { + if ( tharg->fpo ) { fclose(tharg->fpo); tharg->fpo = NULL; } + else if ( tharg->fd > STDIN_FILENO ) { close(tharg->fd); tharg->fd = -1; } + + (dsp.thd)->fft = 0; + sum_fft = 0; + n_out = 0; + + pthread_mutex_lock( (dsp.thd)->mutex ); + fprintf(FPOUT, "<%d: FFT_STOP>\n", (dsp.thd)->tn); + pthread_mutex_unlock( (dsp.thd)->mutex ); + + } + + sum_n = 0; + } } #ifdef FFT_READ_SINK_MIN @@ -445,12 +559,39 @@ static void *thd_FFT(void *targs) { if ( (dsp.thd)->used == 0 ) { + (dsp.thd)->fft = 0; pthread_mutex_lock( (dsp.thd)->mutex ); fprintf(FPOUT, "<%d: CLOSE>\n", (dsp.thd)->tn); pthread_mutex_unlock( (dsp.thd)->mutex ); break; } + if ( (dsp.thd)->stop_fft > 0 ) + { + if ( tharg->fpo ) { fclose(tharg->fpo); tharg->fpo = NULL; } + else if ( tharg->fd > STDIN_FILENO ) { close(tharg->fd); tharg->fd = -1; } + + (dsp.thd)->fft = 0; + (dsp.thd)->stop_fft = 0; + sum_fft = 0; + sum_n = 0; + n_out = 0; + + pthread_mutex_lock( (dsp.thd)->mutex ); + fprintf(FPOUT, "<%d: STOP_FFT>\n", (dsp.thd)->tn); + pthread_mutex_unlock( (dsp.thd)->mutex ); + } + } + + if ((dsp.thd)->fft_num < 0) + { + if ( tharg->fpo ) { fclose(tharg->fpo); tharg->fpo = NULL; } + else if ( tharg->fd > STDIN_FILENO ) { close(tharg->fd); tharg->fd = -1; } + (dsp.thd)->fft = 0; + + pthread_mutex_lock( (dsp.thd)->mutex ); + fprintf(FPOUT, "<%d: FFT_STOP>\n", (dsp.thd)->tn); + pthread_mutex_unlock( (dsp.thd)->mutex ); } if (bitQ == EOF) { @@ -465,6 +606,8 @@ static void *thd_FFT(void *targs) { exit_thread: if (z) { free(z); z = NULL; } + if (db) { free(db); db = NULL; } + if (all_rZ) { free(all_rZ); all_rZ = NULL; } if (avg_rZ) { free(avg_rZ); avg_rZ = NULL; } if (avg_db) { free(avg_db); avg_db = NULL; } @@ -496,6 +639,8 @@ int main(int argc, char **argv) { char tcp_buf[TCPBUF_LEN]; int th_used = 0; int tn_fft = -1; + int opt_fft = 0; + int fft_num = 0; pcm_t pcm = {0}; @@ -510,11 +655,15 @@ int main(int argc, char **argv) { for (k = 0; k < MAX_FQ; k++) base_fqs[k] = 0.0; + // server options ++argv; while ((*argv) && (!wavloaded)) { if (strcmp(*argv, "--dbg") == 0) { option_dbg = 1; } + else if (strcmp(*argv, "--enable_clsv_out") == 0) { + option_clsv_out = 1; + } else if (strcmp(*argv, "--port") == 0) { int port = 0; ++argv; @@ -524,7 +673,11 @@ int main(int argc, char **argv) { } else serv_port = port; } - else if (strcmp(*argv, "--fft") == 0) { + else if (strncmp(*argv, "--fft", 5) == 0) { + char *arg_fft = *argv; + if (strncmp(arg_fft+5, "_avg", 4) == 0) opt_fft = OPT_FFT_SERV | OPT_FFT_AVG; + else if (strncmp(arg_fft+5, "_all", 4) == 0) opt_fft = OPT_FFT_SERV; + else return -1; if (xlt_cnt < MAX_FQ) { base_fqs[xlt_cnt] = 0.0; rstype[xlt_cnt] = thd_FFT; @@ -532,6 +685,8 @@ int main(int argc, char **argv) { xlt_cnt++; } ++argv; + if (*argv) fft_num = atoi(*argv); else return -1; + ++argv; if (*argv) fname_fft = *argv; else return -1; } else if (strcmp(*argv, "-") == 0) { @@ -597,8 +752,21 @@ int main(int argc, char **argv) { for (k = 0; k < xlt_cnt; k++) { if (k == tn_fft) { - tharg[k].thd.fft = OPT_FFT_SERV; + tharg[k].thd.fft = opt_fft; tharg[k].fname = fname_fft; + tharg[k].thd.fft_num = fft_num; + tharg[k].thd.stop_fft = 0; + + if ( (tharg[k].thd.fft & 0xF) == OPT_FFT_SERV ) { // save FFT at server + if (fname_fft) { + if (fname_fft[0] == '-') tharg[k].fpo = stdout; + else tharg[k].fpo = fopen(fname_fft, "wb"); + } + else return -1; + if (tharg[k].fpo == NULL) { + fprintf(stderr, "error: open %s\n", fname_fft); + } + } } tharg[k].thd.tn = k; tharg[k].thd.tn_bit = (1< 1 ) { char *freq = tcp_buf; while (l > 1 && tcp_buf[l-1] < 0x20) l--; @@ -701,23 +870,76 @@ int main(int argc, char **argv) { break; } else if ( strncmp(tcp_buf, "--fft", 5) == 0 ) { + opt_fft = 0; + if (strncmp(tcp_buf+5, "_avg", 4) == 0) opt_fft = OPT_FFT_AVG; + else if (strncmp(tcp_buf+5, "_all", 4) != 0) return -1; + if (strncmp(tcp_buf+5+4, "_cl", 3) == 0) opt_fft |= OPT_FFT_CLNT; + else if (strncmp(tcp_buf+5+4, "_sv", 3) == 0) { + if (option_clsv_out) opt_fft |= OPT_FFT_SERV; + else { + pthread_mutex_lock( &mutex ); + fprintf(FPOUT, "\n"); + pthread_mutex_unlock( &mutex ); + close(conn_fd); + continue; + } + } + else return -1; + if (tcp_buf+5+4+4) fft_num = atoi(tcp_buf+5+4+4); else fft_num = 0; char *fname_fft_cl = "db_fft_cl.txt"; - int opt_fft = strcmp(tcp_buf, "--fft0") == 0 ? OPT_FFT_CLSV : OPT_FFT_CLNT; - //close(conn_fd); + char *pbuf = tcp_buf; + for (pbuf = tcp_buf+5+4+4; *pbuf; pbuf++) { + if (*pbuf == ' ') break; + } + if (*pbuf == ' ') { + fname_fft_cl = pbuf+1; + } if ( !tcp_eof ) { if (tn_fft >= 0) { - tharg[tn_fft].thd.fft = opt_fft; - tharg[tn_fft].fname = fname_fft_cl; - tharg[tn_fft].fd = conn_fd; + if (tharg[tn_fft].thd.fft == 0) { + tharg[tn_fft].thd.fft_num = fft_num; + tharg[tn_fft].thd.stop_fft = 0; + tharg[tn_fft].fname = fname_fft_cl; + tharg[tn_fft].fd = conn_fd; + + if ( (opt_fft & 0xF) == OPT_FFT_SERV ) { // save FFT at server + if (fname_fft_cl) { + if (fname_fft_cl[0] == '-') tharg[tn_fft].fpo = stdout; + else tharg[tn_fft].fpo = fopen(fname_fft_cl, "wb"); + } + else return -1; + if (tharg[tn_fft].fpo == NULL) { + fprintf(stderr, "error: open %s\n", fname_fft_cl); + } + close(tharg[tn_fft].fd); tharg[tn_fft].fd = -1; // tharg[tn_fft].fd == conn_fd + } + else { // (opt_fft & 0xF) == OPT_FFT_CLNT : send FFT to client + tharg[tn_fft].fpo = NULL; + } + + tharg[tn_fft].thd.fft = opt_fft; + } + else { + pthread_mutex_lock( &mutex ); + fprintf(FPOUT, "<%d: FFT running>\n", tn_fft); + pthread_mutex_unlock( &mutex ); + + close(conn_fd); + } } else { for (k = 0; k < MAX_FQ; k++) { - if (tharg[k].thd.used == 0) break; + if (tharg[k].thd.used == 0) { + if (k != tn_fft) break; + } } if (k < MAX_FQ) { - tharg[k].thd.fft = opt_fft; + tharg[k].thd.fft_num = fft_num; + tharg[k].thd.stop_fft = 0; tharg[k].fname = fname_fft_cl; + tharg[k].fd = conn_fd; + tn_fft = k; tharg[k].thd.tn = k; tharg[k].thd.tn_bit = (1< : close int num = atoi(tcp_buf+1); if (num >= 0 && num < MAX_FQ) { - if (num != tn_fft) { + if (num != tn_fft) + { tharg[num].thd.used = 0; } + else + { + tharg[num].thd.stop_fft = 1; + } } close(conn_fd); } @@ -789,6 +1030,7 @@ int main(int argc, char **argv) { rbf1 |= tharg[k].thd.tn_bit; tharg[k].thd.used = 1; tharg[k].thd.fft = 0; + tharg[k].thd.stop_fft = 0; pthread_create(&tharg[k].thd.tid, NULL, thd_IF, &tharg[k]); diff --git a/demod/iq_svcl/iq_svcl.h b/demod/iq_svcl/iq_svcl.h index 04a7c56..071b19c 100644 --- a/demod/iq_svcl/iq_svcl.h +++ b/demod/iq_svcl/iq_svcl.h @@ -6,6 +6,10 @@ #include +#define OPT_FFT_SERV 1 // server +#define OPT_FFT_CLNT 2 // server -> client +#define OPT_FFT_AVG 0x100 + #define TCPBUF_LEN 1024 #define SERV_BACKLOG 6 diff --git a/demod/iq_svcl/plot_fft.py b/demod/iq_svcl/plot_fft.py index 32a81d0..ddebabc 100644 --- a/demod/iq_svcl/plot_fft.py +++ b/demod/iq_svcl/plot_fft.py @@ -8,7 +8,7 @@ import matplotlib.ticker as ticker if len(sys.argv) < 2: print("usage:") - print("\tpython %s " % sys.argv[0]) + print("\tpython %s " % sys.argv[0]) sys.exit() fft_file = sys.argv[1] @@ -18,10 +18,17 @@ if not os.path.isfile(fft_file): sys.exit() -data = np.genfromtxt( fft_file, delimiter=';', names=['fq','db'] , skip_header=1 ) +raw_data = np.genfromtxt( fft_file, delimiter=',', max_rows=1) +data1 = raw_data[:] # max_rows=2: raw_data[0,:] +print(data1) -fq = data['fq'] -db = data['db'] +db = data1[5:] + +sr = -2.0*data1[1] + +freq_min = data1[1] / sr +freq_max = data1[2] / sr +fq = np.arange(freq_min, freq_max, 1.0/(data1[4]+1)) N = len(db) m = np.mean(db) diff --git a/demod/iq_svcl/plot_fft_ani.py b/demod/iq_svcl/plot_fft_ani.py new file mode 100644 index 0000000..c3d20b4 --- /dev/null +++ b/demod/iq_svcl/plot_fft_ani.py @@ -0,0 +1,186 @@ + +import os +import sys +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.ticker as ticker +import matplotlib.animation as animation + + +if len(sys.argv) < 3: + print("usage:") + print("\tpython <1/2/3> %s " % sys.argv[0]) + sys.exit() + +OPT_FFT_L = 1 +OPT_FFT_W = 2 +OPT_FFT_B = 3 + +OPT_FFT = OPT_FFT_B +if (sys.argv[1] == '1'): + OPT_FFT = OPT_FFT_L +elif (sys.argv[1] == '2'): + OPT_FFT = OPT_FFT_W + +fft_file = sys.argv[2] + + +if (fft_file == "-"): + f = sys.stdin +else: + try: + f = open(fft_file) + except IOError: + print("error: open %s" % fft_file) + sys.exit() + + +FFT_FPS = 16/2 +WIN_SEC = 10 +win = WIN_SEC*FFT_FPS + +line = f.readline() +#line = f.readline() + +row = np.fromstring(line, dtype=float, sep=',') +data = row[5:] + +l = len(data) +m = np.mean(data) + +data5 = np.full([win,l], m) +for x in range(2, l-2): + data5[0,x] = (data[x-2]+data[x-1]+data[x]+data[x+1]+data[x+2])/5.0 + +min_db = np.min(data5) +max_db = np.max(data5) + +sr = -2.0*row[1] + +freq_min = row[1] +freq_max = row[2] +N = row[4] + +limits = [freq_min/1e3, freq_max/1e3, WIN_SEC, 0.0] + +fq = np.arange(freq_min/sr, freq_max/sr, 1.0/(row[4]+1)) + + +################################################################################################ + +if (OPT_FFT == OPT_FFT_L): + fig = plt.figure(figsize=(12, 5)) + ax1 = fig.add_subplot(111) + ax1.set_xlim([fq[0], fq[-1]]) + ax1.set_ylim([-110.0, -30.0]) + + lp, = ax1.plot( fq, data5[0,:], color='g', linewidth=0.4) + + count = 0 + + def animate_lp(i): + line = f.readline() + if line: + row = np.fromstring(line, dtype=float, sep=',') + data = row[5:] + + global count + count += 1 + global data5 + #data5 = np.roll(data5, 1, axis=0) + for x in range(2, l-2): + data5[0,x] = (data[x-2]+data[x-1]+data[x]+data[x+1]+data[x+2])/5.0 + + lp.set_data(fq, data5[0,:]) + + return [lp] + + ani = animation.FuncAnimation(fig, animate_lp, interval=10, blit=True) + +################################################################################################ + +elif (OPT_FFT == OPT_FFT_W): + fig = plt.figure(figsize=(12, 5)) + ax2 = fig.add_subplot(111) + ax2.set_xlabel('Frequency (kHz)') + ax2.set_ylabel('Time (s)') + + im = ax2.imshow(data5, vmin=-110.0, vmax=-50.0, extent=limits, animated=True) + ax2.set_aspect('auto') + fig.colorbar(im, orientation='vertical') + + count = 0 + + def animate_im(i): + line = f.readline() + if line: + row = np.fromstring(line, dtype=float, sep=',') + data = row[5:] + + global count + count += 1 + global data5 + data5 = np.roll(data5, 1, axis=0) + for x in range(2, l-2): + data5[0,x] = (data[x-2]+data[x-1]+data[x]+data[x+1]+data[x+2])/5.0 + + im.set_data(data5) + + # update vmin/vmax + if (count % win == 0): + min_db = np.min(data5) + max_db = np.max(data5) + im.set_clim(vmin=min_db, vmax=max_db) + return [im] + + ani = animation.FuncAnimation(fig, animate_im, interval=10, blit=True) + +################################################################################################ + +else: + fig = plt.figure(figsize=(12, 8)) + ax1 = fig.add_subplot(211) + ax1.set_xlim([fq[0], fq[-1]]) + ax1.set_ylim([-110.0, -30.0]) + ax2 = fig.add_subplot(212) + ax2.set_xlabel('Frequency (kHz)') + ax2.set_ylabel('Time (s)') + + lp, = ax1.plot( fq, data5[0,:], color='g', linewidth=0.4) + im = ax2.imshow(data5, vmin=-110.0, vmax=-50.0, extent=limits, animated=True) + + ax2.set_aspect('auto') + + count = 0 + + def animate(i): + line = f.readline() + if line: + row = np.fromstring(line, dtype=float, sep=',') + data = row[5:] + + global count + count += 1 + global data5 + data5 = np.roll(data5, 1, axis=0) + for x in range(2, l-2): + data5[0,x] = (data[x-2]+data[x-1]+data[x]+data[x+1]+data[x+2])/5.0 + + im.set_data(data5) + lp.set_data(fq, data5[0,:]) + + # update vmin/vmax + if (count % win == 0): + min_db = np.min(data5) + max_db = np.max(data5) + im.set_clim(vmin=min_db, vmax=max_db) + return [lp,im] + + ani = animation.FuncAnimation(fig, animate, interval=10, blit=True) + +################################################################################################ + +plt.show() + + + diff --git a/demod/iq_svcl/plot_wfft.py b/demod/iq_svcl/plot_wfft.py new file mode 100644 index 0000000..cf29e87 --- /dev/null +++ b/demod/iq_svcl/plot_wfft.py @@ -0,0 +1,52 @@ + +import os +import sys +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.ticker as ticker + + +if len(sys.argv) < 2: + print("usage:") + print("\tpython %s " % sys.argv[0]) + sys.exit() + +fft_file = sys.argv[1] + +if not os.path.isfile(fft_file): + print("error: %s not found" % fft_file) + sys.exit() + + +raw_data = np.genfromtxt( fft_file, delimiter=',') +data = raw_data[:,5:] + +dim = np.shape(data) +m = np.mean(data) +data5 = np.full(dim, m) +for y in range(dim[0]): + for x in range(2, dim[1]-2): + data5[y,x] = (data[y,x-2]+data[y,x-1]+data[y,x]+data[y,x+1]+data[y,x+2])/5.0 + +freq_min = raw_data[0,1] / 1e3 +freq_max = raw_data[0,2] / 1e3 +N = raw_data[0,4] +tmin = raw_data[dim[0]-1,0] +tmax = raw_data[0,0] +limits = [freq_min, freq_max, tmin, tmax] + + +fig = plt.figure(figsize=(15, 5)) +ax = fig.add_subplot(111) +ax.set_title('Waterfall') +ax.set_xlabel('Frequency (kHz)') +ax.set_ylabel('Time (s)') + +plt.imshow(data5, extent=limits) + +ax.set_aspect('auto') +plt.colorbar(orientation='vertical') +plt.show() + + + diff --git a/demod/iq_svcl/scan_fft.c b/demod/iq_svcl/scan_fft.c index 42d6a37..9e60eb1 100644 --- a/demod/iq_svcl/scan_fft.c +++ b/demod/iq_svcl/scan_fft.c @@ -52,7 +52,7 @@ int main(int argc, char **argv) { if (argv[1] == NULL) { fprintf(stderr, "usage:\n"); - fprintf(stderr, "\t%s \n", argv[0]); + fprintf(stderr, "\t%s \n", argv[0]); return 1; } fp = fopen(argv[1], "rb"); @@ -61,9 +61,34 @@ int main(int argc, char **argv) { return 1; } + + memset(line, 0, LINELEN+1); + N = 0; + j = 0; + n = 0; + // sec.ms,freq_min,freq_max,Hz/bin,N_bins, ... while ( (c = fgetc(fp)) != EOF) { - if (c == '\n') N++; + if (c == '\n') break; + if (c == ' ') continue; + if (c == ',') { + if (n == 1) { + int freq_min = atoi(line); + sr = -2*freq_min; + } + if (n == 4) { + N = atoi(line); + break; + } + + n++; + memset(line, 0, LINELEN+1); + j = 0; + } + else { + line[j] = c; + j++; + } } db = calloc(N+1, sizeof(float)); if (db == NULL) return 2; @@ -73,20 +98,30 @@ int main(int argc, char **argv) { intdb = calloc(N+1, sizeof(float)); if (intdb == NULL) return 2; peak = calloc(N+1, sizeof(float)); if (peak == NULL) return 2; - fseek(fp, 0, SEEK_SET); - pbuf = fgets(line, LINELEN, fp); - p1 = strstr(line, "sr:"); - if (p1) sr = atoi(p1+3); - for (n = 0; n < N; n++) { - memset(line, 0, LINELEN+1); - pbuf = fgets(line, LINELEN, fp); - p1 = strstr(line, ";"); //p2 = strstr(p1+1, ";"); - if (p1) { - fq[n] = atof(line); //freq[n] = atof(p1+1); - db[n] = atof(p1+1); //atof(p2+1); + // ..., db_1,...,db_N: + memset(line, 0, LINELEN+1); + j = 0; + n = 0; + while ( (c = fgetc(fp)) != EOF) { + if (c == '\n') break; + if (c == ' ') continue; + if (c == ',') { + if (n < N) { + db[n] = atof(line); + fq[n] = -0.5 + n/(float)N; + } + + n++; + memset(line, 0, LINELEN+1); + j = 0; + } + else { + line[j] = c; + j++; } } + f0 = N/2; globmin = 0.0; @@ -94,14 +129,12 @@ int main(int argc, char **argv) { float db_spike3 = 10.0; int spike_wl3 = 3; //freq2bin(&DFT, 200); // 3 // 200 Hz int spike_wl5 = 5; //freq2bin(&DFT, 200); // 3 // 200 Hz - //float db_spike1 = 15.0; - //int spike_wl1 = 1; //freq2bin(&DFT, 200); // 3 // 200 Hz + dx = 200.0; if (sr) dx = sr*(fq[f0+1]-fq[f0]); //freq[f0+1]-freq[f0]; dn = 2*(int)(2400.0/dx)+1; // (odd/symmetric) integration width: 4800+dx Hz if (option_verbose > 1) fprintf(stderr, "dn = %d\n", dn); - //for (j = 0; j < N; j++) db[j] /= (float)n; // dc-spike (N-1,)N,0,1(,2): subtract mean/avg // spikes in general: