mirror of
https://github.com/drowe67/codec2
synced 2026-08-14 19:31:34 -04:00
495 lines
14 KiB
C
495 lines
14 KiB
C
/*---------------------------------------------------------------------------*\
|
|
|
|
FILE........: fsk_demod.c
|
|
AUTHOR......: Brady O'Brien and David Rowe
|
|
DATE CREATED: 8 January 2016
|
|
|
|
Command line FSK demodulator. Reads in FSK samples, writes demodulated
|
|
output bits.
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
/*
|
|
Copyright (C) 2016 David Rowe
|
|
|
|
All rights reserved.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU Lesser General Public License version 2.1, as
|
|
published by the Free Software Foundation. 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 Lesser General Public License
|
|
along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#define TEST_FRAME_SIZE 100 /* must match fsk_get_test_bits.c */
|
|
|
|
#include <assert.h>
|
|
#include <getopt.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include "codec2_fdmdv.h"
|
|
#include "fsk.h"
|
|
#include "modem_stats.h"
|
|
#include "mpdecode_core.h"
|
|
|
|
/* cleanly exit when we get a SIGTERM */
|
|
|
|
void sig_handler(int signo) {
|
|
if (signo == SIGTERM) {
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
struct FSK *fsk;
|
|
struct MODEM_STATS stats;
|
|
int Fs, Rs, M, P, stats_ctr, stats_loop;
|
|
float loop_time;
|
|
int enable_stats = 0;
|
|
FILE *fin, *fout;
|
|
uint8_t *bitbuf = NULL;
|
|
int16_t *rawbuf;
|
|
COMP *modbuf;
|
|
float *rx_filt = NULL;
|
|
float *llrs = NULL;
|
|
int i, j, Ndft;
|
|
int soft_dec_mode = 0;
|
|
stats_loop = 0;
|
|
int complex_input = 1, bytes_per_sample = 2;
|
|
int stats_rate = 8;
|
|
int testframe_mode = 0;
|
|
P = 8; /* default */
|
|
M = 0;
|
|
int fsk_lower = 0;
|
|
int fsk_upper = 0;
|
|
int user_fsk_lower = 0;
|
|
int user_fsk_upper = 0;
|
|
int nsym = FSK_DEFAULT_NSYM;
|
|
int mask = 0;
|
|
int tone_separation = 100;
|
|
|
|
int o = 0;
|
|
int opt_idx = 0;
|
|
while (o != -1) {
|
|
static struct option long_opts[] = {
|
|
{"help", no_argument, 0, 'h'},
|
|
{"conv", required_argument, 0, 'p'},
|
|
{"cs16", no_argument, 0, 'c'},
|
|
{"cu8", no_argument, 0, 'd'},
|
|
{"fsk_lower", required_argument, 0, 'b'},
|
|
{"fsk_upper", required_argument, 0, 'u'},
|
|
{"stats", optional_argument, 0, 't'},
|
|
{"soft-dec", no_argument, 0, 's'},
|
|
{"testframes", no_argument, 0, 'f'},
|
|
{"nsym", required_argument, 0, 'n'},
|
|
{"mask", required_argument, 0, 'm'},
|
|
{0, 0, 0, 0}};
|
|
|
|
o = getopt_long(argc, argv, "fhlp:cdt::sb:u:m", long_opts, &opt_idx);
|
|
|
|
switch (o) {
|
|
case 'c':
|
|
complex_input = 2;
|
|
bytes_per_sample = 2;
|
|
break;
|
|
case 'd':
|
|
complex_input = 2;
|
|
bytes_per_sample = 1;
|
|
break;
|
|
case 'f':
|
|
testframe_mode = 1;
|
|
break;
|
|
case 't':
|
|
enable_stats = 1;
|
|
if (optarg != NULL) {
|
|
stats_rate = atoi(optarg);
|
|
if (stats_rate == 0) {
|
|
stats_rate = 8;
|
|
}
|
|
}
|
|
break;
|
|
case 's':
|
|
soft_dec_mode = 1;
|
|
break;
|
|
case 'p':
|
|
P = atoi(optarg);
|
|
break;
|
|
case 'b':
|
|
if (optarg != NULL) {
|
|
fsk_lower = atoi(optarg);
|
|
user_fsk_lower = 1;
|
|
}
|
|
break;
|
|
case 'u':
|
|
if (optarg != NULL) {
|
|
fsk_upper = atoi(optarg);
|
|
user_fsk_upper = 1;
|
|
}
|
|
break;
|
|
case 'n':
|
|
if (optarg != NULL) {
|
|
nsym = atoi(optarg);
|
|
}
|
|
break;
|
|
case 'm':
|
|
mask = 1;
|
|
tone_separation = atoi(optarg);
|
|
break;
|
|
case 'h':
|
|
case '?':
|
|
goto helpmsg;
|
|
break;
|
|
}
|
|
}
|
|
int dx = optind;
|
|
|
|
if ((argc - dx) < 5) {
|
|
fprintf(stderr, "Too few arguments\n");
|
|
goto helpmsg;
|
|
}
|
|
|
|
if ((argc - dx) > 5) {
|
|
fprintf(stderr, "Too many arguments\n");
|
|
helpmsg:
|
|
fprintf(stderr,
|
|
"usage: %s [options] (2|4) SampleRate SymbolRate InputModemRawFile "
|
|
"OutputFile\n",
|
|
argv[0]);
|
|
fprintf(stderr,
|
|
" -c --cs16 The raw input file will be in complex signed "
|
|
"16 bit format.\n");
|
|
fprintf(stderr,
|
|
" -d --cu8 The raw input file will be in complex "
|
|
"unsigned 8 bit format.\n");
|
|
fprintf(stderr,
|
|
" If neither -c nor -d are used, the input "
|
|
"should be in signed 16 bit format.\n");
|
|
fprintf(stderr,
|
|
" -f --testframes Testframe mode, prints stats to stderr when a "
|
|
"testframe is detected, if -t (JSON) \n");
|
|
fprintf(stderr,
|
|
" is enabled stats will be in JSON format\n");
|
|
fprintf(
|
|
stderr,
|
|
" -t[r] --stats=[r] Print out modem statistics to stderr in JSON.\n");
|
|
fprintf(stderr,
|
|
" r, if provided, sets the number of modem "
|
|
"frames between statistic printouts.\n");
|
|
fprintf(stderr,
|
|
" -s --soft-dec The output file will be in a soft-decision "
|
|
"format, with one 32-bit float per bit.\n");
|
|
fprintf(stderr,
|
|
" If -s is not used, the output will be in a 1 "
|
|
"byte-per-bit format.\n");
|
|
fprintf(stderr,
|
|
" -p P Number of timing offsets we have to choose "
|
|
"from, default %d.\n",
|
|
FSK_DEFAULT_P);
|
|
fprintf(stderr,
|
|
" Fs/Rs/P must be an integer. Smaller values "
|
|
"result in faster operation, but\n");
|
|
fprintf(stderr, " coarse sampling. Try to keep >= 8\n");
|
|
fprintf(stderr,
|
|
" processing but lower demodulation "
|
|
"performance. Default %d\n",
|
|
FSK_DEFAULT_P);
|
|
fprintf(stderr,
|
|
" --fsk_lower freq lower limit of freq estimator (default 0 for "
|
|
"real input, -Fs/2 for complex input)\n");
|
|
fprintf(
|
|
stderr,
|
|
" --fsk_upper freq upper limit of freq estimator (default Fs/2)\n");
|
|
fprintf(stderr,
|
|
" --nsym Nsym number of symbols used for estimators. "
|
|
"Default %d\n",
|
|
FSK_DEFAULT_NSYM);
|
|
fprintf(stderr,
|
|
" --mask TxFreqSpace Use \"mask\" freq estimator (default is "
|
|
"\"peak\" estimator)\n");
|
|
exit(1);
|
|
}
|
|
|
|
/* Extract parameters */
|
|
M = atoi(argv[dx]);
|
|
Fs = atoi(argv[dx + 1]);
|
|
Rs = atoi(argv[dx + 2]);
|
|
|
|
if ((M != 2) && (M != 4)) {
|
|
fprintf(stderr, "Mode %d is not valid. Mode must be 2 or 4.\n", M);
|
|
goto helpmsg;
|
|
}
|
|
|
|
/* Open files */
|
|
if (strcmp(argv[dx + 3], "-") == 0) {
|
|
fin = stdin;
|
|
} else {
|
|
fin = fopen(argv[dx + 3], "r");
|
|
}
|
|
|
|
if (strcmp(argv[dx + 4], "-") == 0) {
|
|
fout = stdout;
|
|
} else {
|
|
fout = fopen(argv[dx + 4], "w");
|
|
}
|
|
|
|
/* set up FSK */
|
|
fsk = fsk_create_hbr(Fs, Rs, M, P, nsym, FSK_NONE, tone_separation);
|
|
|
|
/* set freq estimator limits */
|
|
if (!user_fsk_lower) {
|
|
if (complex_input == 1)
|
|
fsk_lower = 0;
|
|
else
|
|
fsk_lower = -Fs / 2;
|
|
}
|
|
if (!user_fsk_upper) {
|
|
fsk_upper = Fs / 2;
|
|
}
|
|
fprintf(stderr, "Setting estimator limits to %d to %d Hz.\n", fsk_lower,
|
|
fsk_upper);
|
|
fsk_set_freq_est_limits(fsk, fsk_lower, fsk_upper);
|
|
|
|
fsk_set_freq_est_alg(fsk, mask);
|
|
|
|
if (fin == NULL || fout == NULL || fsk == NULL) {
|
|
fprintf(stderr, "Couldn't open files\n");
|
|
exit(1);
|
|
}
|
|
|
|
/* set up testframe mode */
|
|
|
|
int testframecnt, bitcnt, biterr, testframe_detected;
|
|
uint8_t *bitbuf_tx = NULL, *bitbuf_rx = NULL;
|
|
if (testframe_mode) {
|
|
bitbuf_tx = (uint8_t *)malloc(sizeof(uint8_t) * TEST_FRAME_SIZE);
|
|
assert(bitbuf_tx != NULL);
|
|
bitbuf_rx = (uint8_t *)malloc(sizeof(uint8_t) * TEST_FRAME_SIZE);
|
|
assert(bitbuf_rx != NULL);
|
|
|
|
/* Generate known tx frame from known seed */
|
|
|
|
srand(158324);
|
|
for (i = 0; i < TEST_FRAME_SIZE; i++) {
|
|
bitbuf_tx[i] = rand() & 0x1;
|
|
bitbuf_rx[i] = 0;
|
|
}
|
|
|
|
testframecnt = 0;
|
|
bitcnt = 0;
|
|
biterr = 0;
|
|
}
|
|
|
|
if (enable_stats) {
|
|
loop_time = ((float)fsk_nin(fsk)) / ((float)Fs);
|
|
stats_loop = (int)(1 / (stats_rate * loop_time));
|
|
stats_ctr = 0;
|
|
}
|
|
|
|
/* allocate buffers for processing */
|
|
if (soft_dec_mode) {
|
|
rx_filt = (float *)malloc(sizeof(float) * fsk->mode * fsk->Nsym);
|
|
assert(rx_filt != NULL);
|
|
llrs = (float *)malloc(sizeof(float) * fsk->Nbits);
|
|
assert(llrs != NULL);
|
|
} else {
|
|
bitbuf = (uint8_t *)malloc(sizeof(uint8_t) * fsk->Nbits);
|
|
assert(bitbuf != NULL);
|
|
}
|
|
rawbuf = (int16_t *)malloc(bytes_per_sample * (fsk->N + fsk->Ts * 2) *
|
|
complex_input);
|
|
modbuf = (COMP *)malloc(sizeof(COMP) * (fsk->N + fsk->Ts * 2));
|
|
|
|
/* set up signal handler so we can terminate gracefully */
|
|
|
|
if (signal(SIGTERM, sig_handler) == SIG_ERR) {
|
|
printf("\ncan't catch SIGTERM\n");
|
|
}
|
|
|
|
/* Demodulate! */
|
|
|
|
while (fread(rawbuf, bytes_per_sample * complex_input, fsk_nin(fsk), fin) ==
|
|
fsk_nin(fsk)) {
|
|
/* convert input to a buffer of floats. Note scaling isn't really necessary
|
|
* for FSK */
|
|
|
|
if (complex_input == 1) {
|
|
/* S16 real input */
|
|
for (i = 0; i < fsk_nin(fsk); i++) {
|
|
modbuf[i].real = ((float)rawbuf[i]) / FDMDV_SCALE;
|
|
modbuf[i].imag = 0.0;
|
|
}
|
|
} else {
|
|
if (bytes_per_sample == 1) {
|
|
/* U8 complex */
|
|
uint8_t *rawbuf_u8 = (uint8_t *)rawbuf;
|
|
for (i = 0; i < fsk_nin(fsk); i++) {
|
|
modbuf[i].real = ((float)rawbuf_u8[2 * i] - 127.0) / 128.0;
|
|
modbuf[i].imag = ((float)rawbuf_u8[2 * i + 1] - 127.0) / 128.0;
|
|
}
|
|
} else {
|
|
/* S16 complex */
|
|
for (i = 0; i < fsk_nin(fsk); i++) {
|
|
modbuf[i].real = ((float)rawbuf[2 * i]) / FDMDV_SCALE;
|
|
modbuf[i].imag = ((float)rawbuf[2 * i + 1] / FDMDV_SCALE);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (soft_dec_mode) {
|
|
int bps = log2(fsk->mode);
|
|
assert(fsk->Nbits == bps * fsk->Nsym);
|
|
/* output bit LLRs */
|
|
fsk_demod_sd(fsk, rx_filt, modbuf);
|
|
fsk_rx_filt_to_llrs(llrs, rx_filt, fsk->v_est, fsk->SNRest, fsk->mode,
|
|
fsk->Nsym);
|
|
} else {
|
|
fsk_demod(fsk, bitbuf, modbuf);
|
|
}
|
|
|
|
testframe_detected = 0;
|
|
if (testframe_mode) {
|
|
assert(soft_dec_mode == 0);
|
|
|
|
/* attempt to find a testframe and update stats */
|
|
/* update silding window of input bits */
|
|
|
|
int errs;
|
|
for (j = 0; j < fsk->Nbits; j++) {
|
|
for (i = 0; i < TEST_FRAME_SIZE - 1; i++) {
|
|
bitbuf_rx[i] = bitbuf_rx[i + 1];
|
|
}
|
|
bitbuf_rx[TEST_FRAME_SIZE - 1] = bitbuf[j];
|
|
|
|
/* compare to know tx frame. If they are time aligned, there
|
|
will be a fairly low bit error rate */
|
|
|
|
errs = 0;
|
|
for (i = 0; i < TEST_FRAME_SIZE; i++) {
|
|
if (bitbuf_rx[i] != bitbuf_tx[i]) {
|
|
errs++;
|
|
}
|
|
}
|
|
|
|
if (errs < 0.1 * TEST_FRAME_SIZE) {
|
|
/* OK, we have a valid test frame sync, so lets count errors */
|
|
testframe_detected = 1;
|
|
testframecnt++;
|
|
bitcnt += TEST_FRAME_SIZE;
|
|
biterr += errs;
|
|
if (enable_stats == 0) {
|
|
fprintf(stderr,
|
|
"errs: %d FSK BER %f, bits tested %d, bit errors %d\n",
|
|
errs, ((float)biterr / (float)bitcnt), bitcnt, biterr);
|
|
}
|
|
}
|
|
}
|
|
} /* if (testframe_mode) ... */
|
|
|
|
if (enable_stats) {
|
|
if ((stats_ctr < 0) || testframe_detected) {
|
|
fsk_get_demod_stats(fsk, &stats);
|
|
|
|
/* Print standard 2FSK stats */
|
|
|
|
fprintf(stderr, "{");
|
|
time_t seconds = time(NULL);
|
|
|
|
fprintf(stderr, "\"secs\": %ld, \"EbNodB\": %5.1f, \"ppm\": %4d,",
|
|
(long)seconds, stats.snr_est, (int)fsk->ppm);
|
|
float *f_est;
|
|
if (fsk->freq_est_type)
|
|
f_est = fsk->f2_est;
|
|
else
|
|
f_est = fsk->f_est;
|
|
fprintf(stderr, " \"f1_est\":%.1f, \"f2_est\":%.1f", f_est[0],
|
|
f_est[1]);
|
|
|
|
/* Print 4FSK stats if in 4FSK mode */
|
|
|
|
if (fsk->mode == 4) {
|
|
fprintf(stderr, ", \"f3_est\":%.1f, \"f4_est\":%.1f", f_est[2],
|
|
f_est[3]);
|
|
}
|
|
|
|
if (testframe_mode == 0) {
|
|
/* Print the eye diagram */
|
|
|
|
fprintf(stderr, ",\t\"eye_diagram\":[");
|
|
for (i = 0; i < stats.neyetr; i++) {
|
|
fprintf(stderr, "[");
|
|
for (j = 0; j < stats.neyesamp; j++) {
|
|
fprintf(stderr, "%f ", stats.rx_eye[i][j]);
|
|
if (j < stats.neyesamp - 1) fprintf(stderr, ",");
|
|
}
|
|
fprintf(stderr, "]");
|
|
if (i < stats.neyetr - 1) fprintf(stderr, ",");
|
|
}
|
|
fprintf(stderr, "],");
|
|
|
|
/* Print a sample of the FFT from the freq estimator */
|
|
fprintf(stderr, "\"samp_fft\":[");
|
|
Ndft = fsk->Ndft / 2;
|
|
for (i = 0; i < Ndft; i++) {
|
|
fprintf(stderr, "%f ", (fsk->Sf)[i]);
|
|
if (i < Ndft - 1) fprintf(stderr, ",");
|
|
}
|
|
fprintf(stderr, "]");
|
|
}
|
|
|
|
if (testframe_mode) {
|
|
fprintf(stderr, ", \"frames\":%d, \"bits\":%d, \"errs\":%d",
|
|
testframecnt, bitcnt, biterr);
|
|
}
|
|
|
|
fprintf(stderr, "}\n");
|
|
|
|
if (stats_ctr < 0) {
|
|
stats_ctr = stats_loop;
|
|
}
|
|
}
|
|
if (testframe_mode == 0) {
|
|
stats_ctr--;
|
|
}
|
|
}
|
|
|
|
if (soft_dec_mode) {
|
|
fwrite(llrs, sizeof(float), fsk->Nbits, fout);
|
|
} else {
|
|
fwrite(bitbuf, sizeof(uint8_t), fsk->Nbits, fout);
|
|
}
|
|
|
|
if (fout == stdin) {
|
|
fflush(fout);
|
|
}
|
|
} /* while(fread ...... */
|
|
|
|
if (testframe_mode) {
|
|
free(bitbuf_tx);
|
|
free(bitbuf_rx);
|
|
}
|
|
|
|
if (soft_dec_mode) {
|
|
free(rx_filt);
|
|
free(llrs);
|
|
} else {
|
|
free(bitbuf);
|
|
}
|
|
|
|
free(rawbuf);
|
|
free(modbuf);
|
|
|
|
fclose(fin);
|
|
fclose(fout);
|
|
fsk_destroy(fsk);
|
|
|
|
return 0;
|
|
}
|