minimodem-mirror/src/minimodem.c

1454 lines
41 KiB
C

/*
* minimodem.c
*
* minimodem - software audio Bell-type or RTTY FSK modem
*
* Copyright (C) 2011-2016 Kamal Mostafa <kamal@whence.com>
*
* 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/>.
*/
#include <getopt.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#include <float.h>
#include <assert.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/select.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#else
#define VERSION "unknown"
#endif
#include "simpleaudio.h"
#include "fsk.h"
#include "databits.h"
char *program_name = "";
int tx_transmitting = 0;
int tx_print_eot = 0;
int tx_leader_bits_len = 2;
int tx_trailer_bits_len = 2;
simpleaudio *tx_sa_out;
float tx_bfsk_mark_f;
unsigned int tx_bit_nsamples;
unsigned int tx_flush_nsamples;
void
tx_stop_transmit_sighandler( int sig )
{
// fprintf(stderr, "alarm\n");
int j;
for ( j=0; j<tx_trailer_bits_len; j++ )
simpleaudio_tone(tx_sa_out, tx_bfsk_mark_f, tx_bit_nsamples);
if ( tx_flush_nsamples )
simpleaudio_tone(tx_sa_out, 0, tx_flush_nsamples);
tx_transmitting = 0;
if ( tx_print_eot )
fprintf(stderr, "### EOT\n");
}
/*
* rudimentary BFSK transmitter
*/
static void fsk_transmit_frame(
simpleaudio *sa_out,
unsigned int bits,
unsigned int n_data_bits,
size_t bit_nsamples,
float bfsk_mark_f,
float bfsk_space_f,
float bfsk_nstartbits,
float bfsk_nstopbits,
int invert_start_stop,
int bfsk_msb_first
)
{
int i;
if ( bfsk_nstartbits > 0 )
simpleaudio_tone(sa_out, invert_start_stop ? bfsk_mark_f : bfsk_space_f,
bit_nsamples * bfsk_nstartbits); // start
for ( i=0; i<n_data_bits; i++ ) { // data
unsigned int bit;
if (bfsk_msb_first) {
bit = ( bits >> (n_data_bits - i - 1) ) & 1;
} else {
bit = ( bits >> i ) & 1;
}
float tone_freq = bit == 1 ? bfsk_mark_f : bfsk_space_f;
simpleaudio_tone(sa_out, tone_freq, bit_nsamples);
}
if ( bfsk_nstopbits > 0 )
simpleaudio_tone(sa_out, invert_start_stop ? bfsk_space_f : bfsk_mark_f,
bit_nsamples * bfsk_nstopbits); // stop
}
static void fsk_transmit_stdin(
simpleaudio *sa_out,
int tx_interactive,
float data_rate,
float bfsk_mark_f,
float bfsk_space_f,
int n_data_bits,
float bfsk_nstartbits,
float bfsk_nstopbits,
int invert_start_stop,
int bfsk_msb_first,
unsigned int bfsk_do_tx_sync_bytes,
unsigned int bfsk_sync_byte,
databits_encoder encode,
int txcarrier
)
{
size_t sample_rate = simpleaudio_get_rate(sa_out);
size_t bit_nsamples = sample_rate / data_rate + 0.5f;
tx_sa_out = sa_out;
tx_bfsk_mark_f = bfsk_mark_f;
tx_bit_nsamples = bit_nsamples;
if ( tx_interactive )
tx_flush_nsamples = sample_rate/2; // 0.5 sec of zero samples to flush
else
tx_flush_nsamples = 0;
// one-shot
struct itimerval itv = {
{0, 0}, // it_interval
{0, 1000000/(float)(data_rate+data_rate*0.03f)} // it_value
};
struct itimerval itv_zero = {
{0, 0}, // it_interval
{0, 0} // it_value
};
// arbitrary chosen timeout value: 1/25 of a second
unsigned int idle_carrier_usec = (1000000/25);
int block_input = tx_interactive && !txcarrier;
if ( block_input )
signal(SIGALRM, tx_stop_transmit_sighandler);
// Set up for select() should we need it
int fd = fileno(stdin);
fd_set fdset;
tx_transmitting = 0;
int end_of_file = 0;
unsigned char buf;
int n_read = 0;
int idle = 0;
while ( !end_of_file )
{
FD_ZERO(&fdset);
FD_SET(fd, &fdset);
struct timeval tv_idletimeout = { 0, 0 };
if ( !tx_interactive ) {
// When stdin blocks we "emit idle tone", for a duration of
// idle_carrier_usec. If !tx_interactive (i.e. writing to an
// audio file) make the select timeout the same duration.
tv_idletimeout.tv_usec = idle_carrier_usec;
}
if( block_input || select(fd+1, &fdset, NULL, NULL, &tv_idletimeout) )
{
n_read = read(fd, &buf, sizeof(buf));
if( n_read <= 0 ) //Includes EOF (0) and errors (-1)
{
end_of_file = 1;
continue; //Do nothing else
}
idle = 0;
}
else
idle = 1;
// Cause any running timer to immediately trigger
if ( block_input )
setitimer(ITIMER_REAL, &itv_zero, NULL);
if( !idle )
{
// fprintf(stderr, "<c=%d>", c);
unsigned int nwords;
unsigned int bits[2];
unsigned int j;
nwords = encode(bits, buf);
if ( !tx_transmitting )
{
tx_transmitting = 1;
/* emit leader tone (mark) */
for ( j=0; j<tx_leader_bits_len; j++ )
simpleaudio_tone(sa_out, invert_start_stop ? bfsk_space_f : bfsk_mark_f, bit_nsamples);
}
if ( tx_transmitting < 2)
{
tx_transmitting = 2;
/* emit "preamble" of sync bytes */
for ( j=0; j<bfsk_do_tx_sync_bytes; j++ )
fsk_transmit_frame(sa_out, bfsk_sync_byte, n_data_bits,
bit_nsamples, bfsk_mark_f, bfsk_space_f,
bfsk_nstartbits, bfsk_nstopbits, invert_start_stop, 0);
}
/* emit data bits */
for ( j=0; j<nwords; j++ )
fsk_transmit_frame(sa_out, bits[j], n_data_bits,
bit_nsamples, bfsk_mark_f, bfsk_space_f,
bfsk_nstartbits, bfsk_nstopbits, invert_start_stop, bfsk_msb_first);
}
else
{
tx_transmitting = 1;
/* emit idle tone (mark) */
simpleaudio_tone(sa_out,
invert_start_stop ? bfsk_space_f : bfsk_mark_f,
idle_carrier_usec * sample_rate / 1000000);
}
if ( block_input )
setitimer(ITIMER_REAL, &itv, NULL);
}
if ( block_input ) {
setitimer(ITIMER_REAL, &itv_zero, NULL);
signal(SIGALRM, SIG_DFL);
}
if ( !tx_transmitting )
return;
tx_stop_transmit_sighandler(0);
}
static void
report_no_carrier( fsk_plan *fskp,
unsigned int sample_rate,
float bfsk_data_rate,
float frame_n_bits,
unsigned int nframes_decoded,
size_t carrier_nsamples,
float confidence_total,
float amplitude_total )
{
float nbits_decoded = nframes_decoded * frame_n_bits;
#if 0
fprintf(stderr, "nframes_decoded=%u\n", nframes_decoded);
fprintf(stderr, "nbits_decoded=%f\n", nbits_decoded);
fprintf(stderr, "carrier_nsamples=%lu\n", carrier_nsamples);
#endif
float throughput_rate =
nbits_decoded * sample_rate / (float)carrier_nsamples;
fprintf(stderr, "\n### NOCARRIER ndata=%u confidence=%.3f ampl=%.3f bps=%.2f",
nframes_decoded,
(double)(confidence_total / nframes_decoded),
(double)(amplitude_total / nframes_decoded),
(double)(throughput_rate));
#if 0
fprintf(stderr, " bits*sr=%llu rate*nsamp=%llu",
(unsigned long long)(nbits_decoded * sample_rate + 0.5),
(unsigned long long)(bfsk_data_rate * carrier_nsamples) );
#endif
if ( (unsigned long long)(nbits_decoded * sample_rate + 0.5f) == (unsigned long long)(bfsk_data_rate * carrier_nsamples) ) {
fprintf(stderr, " (rate perfect) ###\n");
} else {
float throughput_skew = (throughput_rate - bfsk_data_rate)
/ bfsk_data_rate;
fprintf(stderr, " (%.1f%% %s) ###\n",
(double)(fabsf(throughput_skew) * 100.0f),
signbit(throughput_skew) ? "slow" : "fast"
);
}
}
void
generate_test_tones( simpleaudio *sa_out, unsigned int duration_sec )
{
unsigned int sample_rate = simpleaudio_get_rate(sa_out);
unsigned int nframes = sample_rate / 10;
int i;
for ( i=0; i<(sample_rate/nframes*duration_sec); i++ ) {
simpleaudio_tone(sa_out, 1000, nframes/2);
simpleaudio_tone(sa_out, 1777, nframes/2);
}
}
static int
benchmarks()
{
fprintf(stdout, "minimodem %s benchmarks\n", VERSION);
int ret;
ret = system("sed -n -e '/^model name/{p;q}' -e '/^cpu model/{p;q}' /proc/cpuinfo");
if ( ret )
; // don't care, hush compiler.
fflush(stdout);
unsigned int sample_rate = 48000;
sa_backend_t backend = SA_BACKEND_BENCHMARK;
// backend = SA_BACKEND_SYSDEFAULT; // for test
simpleaudio *sa_out;
// enable the sine wave LUT
simpleaudio_tone_init(1024, 1.0);
sa_out = simpleaudio_open_stream(backend, NULL, SA_STREAM_PLAYBACK,
SA_SAMPLE_FORMAT_S16, sample_rate, 1,
program_name, "generate-tones-lut1024-S16-mono");
if ( ! sa_out )
return 0;
generate_test_tones(sa_out, 10);
simpleaudio_close(sa_out);
sa_out = simpleaudio_open_stream(backend, NULL, SA_STREAM_PLAYBACK,
SA_SAMPLE_FORMAT_FLOAT, sample_rate, 1,
program_name, "generate-tones-lut1024-FLOAT-mono");
if ( ! sa_out )
return 0;
generate_test_tones(sa_out, 10);
simpleaudio_close(sa_out);
// disable the sine wave LUT
simpleaudio_tone_init(0, 1.0);
sa_out = simpleaudio_open_stream(backend, NULL, SA_STREAM_PLAYBACK,
SA_SAMPLE_FORMAT_S16, sample_rate, 1,
program_name, "generate-tones-nolut-S16-mono");
if ( ! sa_out )
return 0;
generate_test_tones(sa_out, 10);
simpleaudio_close(sa_out);
sa_out = simpleaudio_open_stream(backend, NULL, SA_STREAM_PLAYBACK,
SA_SAMPLE_FORMAT_FLOAT, sample_rate, 1,
program_name, "generate-tones-nolut-FLOAT-mono");
if ( ! sa_out )
return 0;
generate_test_tones(sa_out, 10);
simpleaudio_close(sa_out);
return 1;
}
static int rx_stop = 0;
void
rx_stop_sighandler( int sig )
{
rx_stop = 1;
}
void
version()
{
printf(
"minimodem %s\n"
"Copyright (C) 2011-2016 Kamal Mostafa <kamal@whence.com>\n"
"License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
"This is free software: you are free to change and redistribute it.\n"
"There is NO WARRANTY, to the extent permitted by law.\n\n"
"Written by Kamal Mostafa <kamal@whence.com>.\n",
VERSION);
}
void
usage()
{
fprintf(stderr,
"usage: minimodem [--tx|--rx] [options] {baudmode}\n"
" -t, --tx, --transmit, --write\n"
" -r, --rx, --receive, --read (default)\n"
" [options]\n"
" -a, --auto-carrier\n"
" -i, --inverted\n"
" -c, --confidence {min-confidence-threshold}\n"
" -l, --limit {max-confidence-search-limit}\n"
" -8, --ascii ASCII 8-N-1\n"
" -7, ASCII 7-N-1\n"
" -5, --baudot Baudot 5-N-1\n"
" -f, --file {filename.flac}\n"
" -b, --bandwidth {rx_bandwidth}\n"
" -v, --volume {amplitude or 'E'}\n"
" -M, --mark {mark_freq}\n"
" -S, --space {space_freq}\n"
" --startbits {n}\n"
" --stopbits {n.n}\n"
" --invert-start-stop\n"
" --sync-byte {0xXX}\n"
" -q, --quiet\n"
" -R, --samplerate {rate}\n"
" -V, --version\n"
" -A, --alsa[=plughw:X,Y]\n"
" --lut={tx_sin_table_len}\n"
" --float-samples\n"
" --rx-one\n"
" --benchmarks\n"
" --binary-output\n"
" --binary-raw {nbits}\n"
" --print-filter\n"
" --print-eot\n"
" --tx-carrier\n"
" {baudmode}\n"
" any_number_N Bell-like N bps --ascii\n"
" 1200 Bell202 1200 bps --ascii\n"
" 300 Bell103 300 bps --ascii\n"
" rtty RTTY 45.45 bps --baudot --stopbits=1.5\n"
" tdd TTY/TDD 45.45 bps --baudot --stopbits=2.0\n"
" same NOAA SAME 520.83 bps --sync-byte=0xAB ...\n"
" callerid Bell202 CID 1200 bps\n"
" uic{-train,-ground} UIC-751-3 Train/Ground 600 bps\n"
);
exit(1);
}
int
build_expect_bits_string( char *expect_bits_string,
int bfsk_nstartbits,
int bfsk_n_data_bits,
float bfsk_nstopbits,
int invert_start_stop,
int use_expect_bits,
unsigned long long expect_bits )
{
// example expect_bits_string
// 0123456789A
// isddddddddp i == idle bit (a.k.a. prev_stop bit)
// s == start bit d == data bits p == stop bit
// ebs = "10dddddddd1" <-- expected mark/space framing pattern
//
// NOTE! expect_n_bits ends up being (frame_n_bits+1), because
// we expect the prev_stop bit in addition to this frame's own
// (start + n_data_bits + stop) bits. But for each decoded frame,
// we will advance just frame_n_bits worth of samples, leaving us
// pointing at our stop bit -- it becomes the next frame's prev_stop.
//
// prev_stop--v
// start--v v--stop
// char *expect_bits_string = "10dddddddd1";
//
char start_bit_value = invert_start_stop ? '1' : '0';
char stop_bit_value = invert_start_stop ? '0' : '1';
int j = 0;
if ( bfsk_nstopbits != 0.0f )
expect_bits_string[j++] = stop_bit_value;
int i;
// Nb. only integer number of start bits works (for rx)
for ( i=0; i<bfsk_nstartbits; i++ )
expect_bits_string[j++] = start_bit_value;
for ( i=0; i<bfsk_n_data_bits; i++,j++ ) {
if ( use_expect_bits )
expect_bits_string[j] = ( (expect_bits>>i)&1 ) + '0';
else
expect_bits_string[j] = 'd';
}
if ( bfsk_nstopbits != 0.0f )
expect_bits_string[j++] = stop_bit_value;
expect_bits_string[j] = 0;
return j;
}
int
main( int argc, char*argv[] )
{
char *modem_mode = NULL;
int TX_mode = -1;
int quiet_mode = 0;
int output_print_filter = 0;
float band_width = 0;
float bfsk_mark_f = 0;
float bfsk_space_f = 0;
unsigned int bfsk_inverted_freqs = 0;
int bfsk_nstartbits = -1;
float bfsk_nstopbits = -1;
unsigned int bfsk_do_rx_sync = 0;
unsigned int bfsk_do_tx_sync_bytes = 0;
unsigned long long bfsk_sync_byte = -1;
unsigned int bfsk_n_data_bits = 0;
int bfsk_msb_first = 0;
char *expect_data_string = NULL;
char *expect_sync_string = NULL;
unsigned int expect_n_bits;
int invert_start_stop = 0;
int autodetect_shift;
char *filename = NULL;
float carrier_autodetect_threshold = 0.0;
// fsk_confidence_threshold : signal-to-noise squelch control
//
// The minimum SNR-ish confidence level seen as "a signal".
float fsk_confidence_threshold = 1.5;
// fsk_confidence_search_limit : performance vs. quality
//
// If we find a frame with confidence > confidence_search_limit,
// quit searching for a better frame. confidence_search_limit has a
// dramatic effect on peformance (high value yields low performance, but
// higher decode quality, for noisy or hard-to-discern signals (Bell 103,
// or skewed rates).
float fsk_confidence_search_limit = 2.3f;
// float fsk_confidence_search_limit = INFINITY; /* for test */
sa_backend_t sa_backend = SA_BACKEND_SYSDEFAULT;
char *sa_backend_device = NULL;
sa_format_t sample_format = SA_SAMPLE_FORMAT_S16;
unsigned int sample_rate = 48000;
unsigned int nchannels = 1; // FIXME: only works with one channel
float tx_amplitude = 1.0;
unsigned int tx_sin_table_len = 4096;
unsigned int rx_one = 0;
float rxnoise_factor = 0.0;
int txcarrier = 0;
int output_mode_binary = 0;
int output_mode_raw_nbits = 0;
float bfsk_data_rate = 0.0;
databits_encoder *bfsk_databits_encode;
databits_decoder *bfsk_databits_decode;
bfsk_databits_decode = databits_decode_ascii8;
bfsk_databits_encode = databits_encode_ascii8;
/* validate the default system audio mechanism */
#if !(USE_PULSEAUDIO || USE_ALSA)
# define _MINIMODEM_NO_SYSTEM_AUDIO
# if !USE_SNDFILE
# error At least one of {USE_PULSEAUDIO,USE_ALSA,USE_SNDFILE} must be enabled!
# endif
#endif
program_name = strrchr(argv[0], '/');
if ( program_name )
program_name++;
else
program_name = argv[0];
int c;
int option_index;
enum {
MINIMODEM_OPT_UNUSED=256, // placeholder
MINIMODEM_OPT_MSBFIRST,
MINIMODEM_OPT_STARTBITS,
MINIMODEM_OPT_STOPBITS,
MINIMODEM_OPT_INVERT_START_STOP,
MINIMODEM_OPT_SYNC_BYTE,
MINIMODEM_OPT_LUT,
MINIMODEM_OPT_FLOAT_SAMPLES,
MINIMODEM_OPT_RX_ONE,
MINIMODEM_OPT_BENCHMARKS,
MINIMODEM_OPT_BINARY_OUTPUT,
MINIMODEM_OPT_BINARY_RAW,
MINIMODEM_OPT_PRINT_FILTER,
MINIMODEM_OPT_XRXNOISE,
MINIMODEM_OPT_PRINT_EOT,
MINIMODEM_OPT_TXCARRIER
};
while ( 1 ) {
static struct option long_options[] = {
{ "version", 0, 0, 'V' },
{ "tx", 0, 0, 't' },
{ "transmit", 0, 0, 't' },
{ "write", 0, 0, 't' },
{ "rx", 0, 0, 'r' },
{ "receive", 0, 0, 'r' },
{ "read", 0, 0, 'r' },
{ "confidence", 1, 0, 'c' },
{ "limit", 1, 0, 'l' },
{ "auto-carrier", 0, 0, 'a' },
{ "inverted", 0, 0, 'i' },
{ "ascii", 0, 0, '8' },
{ "", 0, 0, '7' },
{ "baudot", 0, 0, '5' },
{ "msb-first", 0, 0, MINIMODEM_OPT_MSBFIRST },
{ "file", 1, 0, 'f' },
{ "bandwidth", 1, 0, 'b' },
{ "volume", 1, 0, 'v' },
{ "mark", 1, 0, 'M' },
{ "space", 1, 0, 'S' },
{ "startbits", 1, 0, MINIMODEM_OPT_STARTBITS },
{ "stopbits", 1, 0, MINIMODEM_OPT_STOPBITS },
{ "invert-start-stop", 0, 0, MINIMODEM_OPT_INVERT_START_STOP },
{ "sync-byte", 1, 0, MINIMODEM_OPT_SYNC_BYTE },
{ "quiet", 0, 0, 'q' },
{ "alsa", 2, 0, 'A' },
{ "samplerate", 1, 0, 'R' },
{ "lut", 1, 0, MINIMODEM_OPT_LUT },
{ "float-samples", 0, 0, MINIMODEM_OPT_FLOAT_SAMPLES },
{ "rx-one", 0, 0, MINIMODEM_OPT_RX_ONE },
{ "benchmarks", 0, 0, MINIMODEM_OPT_BENCHMARKS },
{ "binary-output", 0, 0, MINIMODEM_OPT_BINARY_OUTPUT },
{ "binary-raw", 1, 0, MINIMODEM_OPT_BINARY_RAW },
{ "print-filter", 0, 0, MINIMODEM_OPT_PRINT_FILTER },
{ "print-eot", 0, 0, MINIMODEM_OPT_PRINT_EOT },
{ "Xrxnoise", 1, 0, MINIMODEM_OPT_XRXNOISE },
{ "tx-carrier", 0, 0, MINIMODEM_OPT_TXCARRIER },
{ 0 }
};
c = getopt_long(argc, argv, "Vtrc:l:ai875f:b:v:M:S:T:qA::R:",
long_options, &option_index);
if ( c == -1 )
break;
switch( c ) {
case 'V':
version();
exit(0);
case 't':
if ( TX_mode == 0 )
usage();
TX_mode = 1;
break;
case 'r':
if ( TX_mode == 1 )
usage();
TX_mode = 0;
break;
case 'c':
fsk_confidence_threshold = atof(optarg);
break;
case 'l':
fsk_confidence_search_limit = atof(optarg);
break;
case 'a':
carrier_autodetect_threshold = 0.001;
break;
case 'i':
bfsk_inverted_freqs = 1;
break;
case 'f':
filename = optarg;
break;
case '8':
bfsk_n_data_bits = 8;
break;
case '7':
bfsk_n_data_bits = 7;
break;
case '5':
bfsk_n_data_bits = 5;
bfsk_databits_decode = databits_decode_baudot;
bfsk_databits_encode = databits_encode_baudot;
break;
case MINIMODEM_OPT_MSBFIRST:
bfsk_msb_first = 1;
break;
case 'b':
band_width = atof(optarg);
assert( band_width != 0 );
break;
case 'v':
if ( optarg[0] == 'E' )
tx_amplitude = FLT_EPSILON;
else
tx_amplitude = atof(optarg);
assert( tx_amplitude > 0.0f );
break;
case 'M':
bfsk_mark_f = atof(optarg);
assert( bfsk_mark_f > 0 );
break;
case 'S':
bfsk_space_f = atof(optarg);
assert( bfsk_space_f > 0 );
break;
case MINIMODEM_OPT_STARTBITS:
bfsk_nstartbits = atoi(optarg);
// Note: bfsk_nstartbits is limited by arrays
// expect_bits_string[32] and fsk.c:bit_something[32]
assert( bfsk_nstartbits >= 0 && bfsk_nstartbits <= 20 );
break;
case MINIMODEM_OPT_STOPBITS:
bfsk_nstopbits = atof(optarg);
assert( bfsk_nstopbits >= 0 );
break;
case MINIMODEM_OPT_INVERT_START_STOP:
invert_start_stop = 1;
break;
case MINIMODEM_OPT_SYNC_BYTE:
bfsk_do_rx_sync = 1;
bfsk_do_tx_sync_bytes = 16;
bfsk_sync_byte = strtol(optarg, NULL, 0);
break;
case 'q':
quiet_mode = 1;
break;
case 'R':
sample_rate = atoi(optarg);
assert( sample_rate > 0 );
break;
case 'A':
#if USE_ALSA
sa_backend = SA_BACKEND_ALSA;
if ( optarg )
sa_backend_device = optarg;
#else
fprintf(stderr, "E: This build of minimodem was configured without alsa support.\n");
exit(1);
#endif
break;
case MINIMODEM_OPT_LUT:
tx_sin_table_len = atoi(optarg);
break;
case MINIMODEM_OPT_FLOAT_SAMPLES:
sample_format = SA_SAMPLE_FORMAT_FLOAT;
break;
case MINIMODEM_OPT_RX_ONE:
rx_one = 1;
break;
case MINIMODEM_OPT_BENCHMARKS:
benchmarks();
exit(0);
break;
case MINIMODEM_OPT_BINARY_OUTPUT:
output_mode_binary = 1;
break;
case MINIMODEM_OPT_BINARY_RAW:
output_mode_raw_nbits = atoi(optarg);
break;
case MINIMODEM_OPT_PRINT_FILTER:
output_print_filter = 1;
break;
case MINIMODEM_OPT_XRXNOISE:
rxnoise_factor = atof(optarg);
break;
case MINIMODEM_OPT_TXCARRIER:
txcarrier = 1;
break;
case MINIMODEM_OPT_PRINT_EOT:
tx_print_eot = 1;
break;
default:
usage();
}
}
if ( TX_mode == -1 )
TX_mode = 0;
/* The receive code requires floating point samples to feed to the FFT */
if ( TX_mode == 0 )
sample_format = SA_SAMPLE_FORMAT_FLOAT;
if ( filename ) {
#if !USE_SNDFILE
fprintf(stderr, "E: This build of minimodem was configured without sndfile,\nE: so the --file flag is not supported.\n");
exit(1);
#endif
} else {
#ifdef _MINIMODEM_NO_SYSTEM_AUDIO
fprintf(stderr, "E: this build of minimodem was configured without system audio support,\nE: so only the --file mode is supported.\n");
exit(1);
#endif
}
#if 0
if (optind < argc) {
printf("non-option ARGV-elements: ");
while (optind < argc)
printf("%s ", argv[optind++]);
printf("\n");
}
#endif
if (optind + 1 != argc) {
fprintf(stderr, "E: *** Must specify {baudmode} (try \"300\") ***\n");
usage();
}
modem_mode = argv[optind++];
if ( strncasecmp(modem_mode, "rtty",5)==0 ) {
bfsk_databits_decode = databits_decode_baudot;
bfsk_databits_encode = databits_encode_baudot;
bfsk_data_rate = 45.45;
if ( bfsk_n_data_bits == 0 )
bfsk_n_data_bits = 5;
if ( bfsk_nstopbits < 0 )
bfsk_nstopbits = 1.5;
} else if ( strncasecmp(modem_mode, "tdd",4)==0 ) {
bfsk_databits_decode = databits_decode_baudot;
bfsk_databits_encode = databits_encode_baudot;
bfsk_data_rate = 45.45;
if ( bfsk_n_data_bits == 0 )
bfsk_n_data_bits = 5;
if ( bfsk_nstopbits < 0 )
bfsk_nstopbits = 2.0;
bfsk_mark_f = 1400;
bfsk_space_f = 1800;
} else if ( strncasecmp(modem_mode, "same",5)==0 ) {
// http://www.nws.noaa.gov/nwr/nwrsame.htm
bfsk_data_rate = 520.0 + 5/6.0;
bfsk_n_data_bits = 8;
bfsk_nstartbits = 0;
bfsk_nstopbits = 0;
bfsk_do_rx_sync = 1;
bfsk_do_tx_sync_bytes = 16;
bfsk_sync_byte = 0xAB;
bfsk_mark_f = 2083.0 + 1/3.0;
bfsk_space_f = 1562.5;
band_width = bfsk_data_rate;
} else if ( strncasecmp(modem_mode, "caller",6)==0 ) {
if ( TX_mode ) {
fprintf(stderr, "E: callerid --tx mode is not supported.\n");
return 1;
}
if ( carrier_autodetect_threshold > 0.0f )
fprintf(stderr, "W: callerid with --auto-carrier is not recommended.\n");
bfsk_databits_decode = databits_decode_callerid;
bfsk_data_rate = 1200;
bfsk_n_data_bits = 8;
} else if ( strncasecmp(modem_mode, "uic", 3) == 0 ) {
if ( TX_mode ) {
fprintf(stderr, "E: uic-751-3 --tx mode is not supported.\n");
return 1;
}
// http://ec.europa.eu/transport/rail/interoperability/doc/ccs-tsi-en-annex.pdf
if (tolower(modem_mode[4]) == 't')
bfsk_databits_decode = databits_decode_uic_train;
else
bfsk_databits_decode = databits_decode_uic_ground;
bfsk_data_rate = 600;
bfsk_n_data_bits = 39;
bfsk_mark_f = 1300;
bfsk_space_f = 1700;
bfsk_nstartbits = 8;
bfsk_nstopbits = 0;
expect_data_string = "11110010ddddddddddddddddddddddddddddddddddddddd";
expect_n_bits = 47;
} else {
bfsk_data_rate = atof(modem_mode);
if ( bfsk_n_data_bits == 0 )
bfsk_n_data_bits = 8;
}
if ( bfsk_data_rate == 0.0f )
usage();
if ( output_mode_binary || output_mode_raw_nbits )
bfsk_databits_decode = databits_decode_binary;
if ( output_mode_raw_nbits ) {
bfsk_nstartbits = 0;
bfsk_nstopbits = 0;
bfsk_n_data_bits = output_mode_raw_nbits;
}
if ( bfsk_data_rate >= 400 ) {
/*
* Bell 202: baud=1200 mark=1200 space=2200
*/
autodetect_shift = - ( bfsk_data_rate * 5 / 6 );
if ( bfsk_mark_f == 0 )
bfsk_mark_f = bfsk_data_rate / 2 + 600;
if ( bfsk_space_f == 0 )
bfsk_space_f = bfsk_mark_f - autodetect_shift;
if ( band_width == 0 )
band_width = 200;
} else if ( bfsk_data_rate >= 100 ) {
/*
* Bell 103: baud=300 mark=1270 space=1070
* ITU-T V.21: baud=300 mark=1280 space=1080
*/
autodetect_shift = 200;
if ( bfsk_mark_f == 0 )
bfsk_mark_f = 1270;
if ( bfsk_space_f == 0 )
bfsk_space_f = bfsk_mark_f - autodetect_shift;
if ( band_width == 0 )
band_width = 50; // close enough
} else {
/*
* RTTY: baud=45.45 mark/space=variable shift=-170
*/
autodetect_shift = 170;
if ( bfsk_mark_f == 0 )
bfsk_mark_f = 1585;
if ( bfsk_space_f == 0 )
bfsk_space_f = bfsk_mark_f - autodetect_shift;
if ( band_width == 0 ) {
band_width = 10; // FIXME chosen arbitrarily
}
}
// defaults: 1 start bit, 1 stop bit
if ( bfsk_nstartbits < 0 )
bfsk_nstartbits = 1;
if ( bfsk_nstopbits < 0 )
bfsk_nstopbits = 1.0;
// do not transmit any leader tone if no start bits
if ( bfsk_nstartbits == 0 )
tx_leader_bits_len = 0;
if ( bfsk_inverted_freqs ) {
float t = bfsk_mark_f;
bfsk_mark_f = bfsk_space_f;
bfsk_space_f = t;
}
/* restrict band_width to <= data rate (FIXME?) */
if ( band_width > bfsk_data_rate )
band_width = bfsk_data_rate;
// sanitize confidence search limit
if ( fsk_confidence_search_limit < fsk_confidence_threshold )
fsk_confidence_search_limit = fsk_confidence_threshold;
char *stream_name = NULL;
if ( filename ) {
sa_backend = SA_BACKEND_FILE;
stream_name = filename;
}
/*
* Handle transmit mode
*/
if ( TX_mode ) {
simpleaudio_tone_init(tx_sin_table_len, tx_amplitude);
int tx_interactive = 0;
if ( ! stream_name ) {
tx_interactive = 1;
stream_name = "output audio";
}
simpleaudio *sa_out;
sa_out = simpleaudio_open_stream(sa_backend, sa_backend_device,
SA_STREAM_PLAYBACK,
sample_format, sample_rate, nchannels,
program_name, stream_name);
if ( ! sa_out )
return 1;
fsk_transmit_stdin(sa_out, tx_interactive,
bfsk_data_rate,
bfsk_mark_f, bfsk_space_f,
bfsk_n_data_bits,
bfsk_nstartbits,
bfsk_nstopbits,
invert_start_stop,
bfsk_msb_first,
bfsk_do_tx_sync_bytes,
bfsk_sync_byte,
bfsk_databits_encode,
txcarrier
);
simpleaudio_close(sa_out);
return 0;
}
/*
* Open the input audio stream
*/
if ( ! stream_name )
stream_name = "input audio";
simpleaudio *sa;
sa = simpleaudio_open_stream(sa_backend, sa_backend_device,
SA_STREAM_RECORD,
sample_format, sample_rate, nchannels,
program_name, stream_name);
if ( ! sa )
return 1;
sample_rate = simpleaudio_get_rate(sa);
if ( rxnoise_factor != 0.0f )
simpleaudio_set_rxnoise(sa, rxnoise_factor);
/*
* Prepare the input sample chunk rate
*/
float nsamples_per_bit = sample_rate / bfsk_data_rate;
/*
* Prepare the fsk plan
*/
fsk_plan *fskp;
fskp = fsk_plan_new(sample_rate, bfsk_mark_f, bfsk_space_f, band_width);
if ( !fskp ) {
fprintf(stderr, "fsk_plan_new() failed\n");
return 1;
}
/*
* Prepare the input sample buffer. For 8-bit frames with prev/start/stop
* we need 11 data-bits worth of samples, and we will scan through one bits
* worth at a time, hence we need a minimum total input buffer size of 12
* data-bits. */
unsigned int nbits = 0;
nbits += 1; // prev stop bit (last whole stop bit)
nbits += bfsk_nstartbits; // start bits
nbits += bfsk_n_data_bits;
nbits += 1; // stop bit (first whole stop bit)
// FIXME EXPLAIN +1 goes with extra bit when scanning
size_t samplebuf_size = ceilf(nsamples_per_bit) * (nbits+1);
samplebuf_size *= 2; // account for the half-buf filling method
#define SAMPLE_BUF_DIVISOR 12
#ifdef SAMPLE_BUF_DIVISOR
// For performance, use a larger samplebuf_size than necessary
if ( samplebuf_size < sample_rate / SAMPLE_BUF_DIVISOR )
samplebuf_size = sample_rate / SAMPLE_BUF_DIVISOR;
#endif
float *samplebuf = malloc(samplebuf_size * sizeof(float));
size_t samples_nvalid = 0;
debug_log("samplebuf_size=%zu\n", samplebuf_size);
/*
* Run the main loop
*/
int ret = 0;
int carrier = 0;
float confidence_total = 0;
float amplitude_total = 0;
unsigned int nframes_decoded = 0;
size_t carrier_nsamples = 0;
unsigned int noconfidence = 0;
unsigned int advance = 0;
// Fraction of nsamples_per_bit that we will "overscan"; range (0.0 .. 1.0)
float fsk_frame_overscan = 0.5;
// should be != 0.0 (only the nyquist edge cases actually require this?)
// for handling of slightly faster-than-us rates:
// should be >> 0.0 to allow us to lag back for faster-than-us rates
// should be << 1.0 or we may lag backwards over whole bits
// for optimal analysis:
// should be >= 0.5 (half a bit width) or we may not find the optimal bit
// should be < 1.0 (a full bit width) or we may skip over whole bits
// for encodings without start/stop bits:
// MUST be <= 0.5 or we may accidentally skip a bit
//
assert( fsk_frame_overscan >= 0.0f && fsk_frame_overscan < 1.0f );
// ensure that we overscan at least a single sample
unsigned int nsamples_overscan
= nsamples_per_bit * fsk_frame_overscan + 0.5f;
if ( fsk_frame_overscan > 0.0f && nsamples_overscan == 0 )
nsamples_overscan = 1;
debug_log("fsk_frame_overscan=%f nsamples_overscan=%u\n",
fsk_frame_overscan, nsamples_overscan);
// n databits plus bfsk_startbit start bits plus bfsk_nstopbit stop bits:
float frame_n_bits = bfsk_n_data_bits + bfsk_nstartbits + bfsk_nstopbits;
unsigned int frame_nsamples = nsamples_per_bit * frame_n_bits + 0.5f;
char expect_data_string_buffer[64];
if (expect_data_string == NULL) {
expect_data_string = expect_data_string_buffer;
expect_n_bits = build_expect_bits_string(expect_data_string, bfsk_nstartbits, bfsk_n_data_bits, bfsk_nstopbits, invert_start_stop, 0, 0);
}
debug_log("eds = '%s' (%lu)\n", expect_data_string, strlen(expect_data_string));
char expect_sync_string_buffer[64];
if (expect_sync_string == NULL && bfsk_do_rx_sync && (long long) bfsk_sync_byte >= 0) {
expect_sync_string = expect_sync_string_buffer;
build_expect_bits_string(expect_sync_string, bfsk_nstartbits, bfsk_n_data_bits, bfsk_nstopbits, invert_start_stop, 1, bfsk_sync_byte);
} else {
expect_sync_string = expect_data_string;
}
debug_log("ess = '%s' (%lu)\n", expect_sync_string, strlen(expect_sync_string));
unsigned int expect_nsamples = nsamples_per_bit * expect_n_bits;
float track_amplitude = 0.0;
float peak_confidence = 0.0;
signal(SIGINT, rx_stop_sighandler);
while ( 1 ) {
if ( rx_stop )
break;
debug_log("advance=%u\n", advance);
/* Shift the samples in samplebuf by 'advance' samples */
assert( advance <= samplebuf_size );
if ( advance == samplebuf_size ) {
samples_nvalid = 0;
advance = 0;
}
if ( advance ) {
if ( advance > samples_nvalid )
break;
memmove(samplebuf, samplebuf+advance,
(samplebuf_size-advance)*sizeof(float));
samples_nvalid -= advance;
}
if ( samples_nvalid < samplebuf_size/2 ) {
float *samples_readptr = samplebuf + samples_nvalid;
size_t read_nsamples = samplebuf_size/2;
/* Read more samples into samplebuf (fill it) */
assert ( read_nsamples > 0 );
assert ( samples_nvalid + read_nsamples <= samplebuf_size );
ssize_t r;
r = simpleaudio_read(sa, samples_readptr, read_nsamples);
debug_log("simpleaudio_read(samplebuf+%td, n=%zu) returns %zd\n",
samples_readptr - samplebuf, read_nsamples, r);
if ( r < 0 ) {
fprintf(stderr, "simpleaudio_read: error\n");
ret = -1;
break;
}
samples_nvalid += r;
}
if ( samples_nvalid == 0 )
break;
/* Auto-detect carrier frequency */
static int carrier_band = -1;
if ( carrier_autodetect_threshold > 0.0f && carrier_band < 0 ) {
unsigned int i;
float nsamples_per_scan = nsamples_per_bit;
if ( nsamples_per_scan > fskp->fftsize )
nsamples_per_scan = fskp->fftsize;
for ( i=0; i+nsamples_per_scan<=samples_nvalid;
i+=nsamples_per_scan ) {
carrier_band = fsk_detect_carrier(fskp,
samplebuf+i, nsamples_per_scan,
carrier_autodetect_threshold);
if ( carrier_band >= 0 )
break;
}
advance = i + nsamples_per_scan;
if ( advance > samples_nvalid )
advance = samples_nvalid;
if ( carrier_band < 0 ) {
debug_log("autodetected carrier band not found\n");
continue;
}
// default negative shift -- reasonable?
int b_shift = - (float)(autodetect_shift + fskp->band_width/2.0f)
/ fskp->band_width;
if ( bfsk_inverted_freqs )
b_shift *= -1;
/* only accept a carrier as b_mark if it will not result
* in a b_space band which is "too low". */
int b_space = carrier_band + b_shift;
if ( b_space < 1 || b_space >= fskp->nbands ) {
debug_log("autodetected space band out of range\n" );
carrier_band = -1;
continue;
}
debug_log("### TONE freq=%.1f ###\n",
carrier_band * fskp->band_width);
fsk_set_tones_by_bandshift(fskp, /*b_mark*/carrier_band, b_shift);
}
/*
* The main processing algorithm: scan samplesbuf for FSK frames,
* looking at an entire frame at once.
*/
debug_log( "--------------------------\n");
if ( samples_nvalid < expect_nsamples )
break;
// try_max_nsamples
// serves two purposes
// 1. avoids finding a non-optimal first frame
// 2. allows us to track slightly slow signals
unsigned int try_max_nsamples;
if ( carrier )
try_max_nsamples = nsamples_per_bit * 0.75f + 0.5f;
else
try_max_nsamples = nsamples_per_bit;
try_max_nsamples += nsamples_overscan;
// FSK_ANALYZE_NSTEPS Try 3 frame positions across the try_max_nsamples
// range. Using a larger nsteps allows for more accurate tracking of
// fast/slow signals (at decreased performance). Note also
// FSK_ANALYZE_NSTEPS_FINE below, which refines the frame
// position upon first acquiring carrier, or if confidence falls.
#define FSK_ANALYZE_NSTEPS 3
unsigned int try_step_nsamples = try_max_nsamples / FSK_ANALYZE_NSTEPS;
if ( try_step_nsamples == 0 )
try_step_nsamples = 1;
float confidence, amplitude;
unsigned long long bits = 0;
/* Note: frame_start_sample is actually the sample where the
* prev_stop bit begins (since the "frame" includes the prev_stop). */
unsigned int frame_start_sample = 0;
unsigned int try_first_sample;
float try_confidence_search_limit;
try_confidence_search_limit = fsk_confidence_search_limit;
try_first_sample = carrier ? nsamples_overscan : 0;
confidence = fsk_find_frame(fskp, samplebuf, expect_nsamples,
try_first_sample,
try_max_nsamples,
try_step_nsamples,
try_confidence_search_limit,
carrier ? expect_data_string : expect_sync_string,
&bits,
&amplitude,
&frame_start_sample
);
int do_refine_frame = 0;
if ( confidence < peak_confidence * 0.75f ) {
do_refine_frame = 1;
debug_log(" ... do_refine_frame rescan (confidence %.3f << %.3f peak)\n", confidence, peak_confidence);
peak_confidence = 0;
}
// no-confidence if amplitude drops abruptly to < 25% of the
// track_amplitude, which follows amplitude with hysteresis
if ( amplitude < track_amplitude * 0.25f ) {
confidence = 0;
}
#define FSK_MAX_NOCONFIDENCE_BITS 20
if ( confidence <= fsk_confidence_threshold ) {
// FIXME: explain
if ( ++noconfidence > FSK_MAX_NOCONFIDENCE_BITS )
{
carrier_band = -1;
if ( carrier ) {
if ( !quiet_mode )
report_no_carrier(fskp, sample_rate, bfsk_data_rate,
frame_n_bits, nframes_decoded,
carrier_nsamples, confidence_total, amplitude_total);
carrier = 0;
carrier_nsamples = 0;
confidence_total = 0;
amplitude_total = 0;
nframes_decoded = 0;
track_amplitude = 0.0;
if ( rx_one )
break;
}
}
/* Advance the sample stream forward by try_max_nsamples so the
* next time around the loop we continue searching from where
* we left off this time. */
advance = try_max_nsamples;
debug_log("@ NOCONFIDENCE=%u advance=%u\n", noconfidence, advance);
continue;
}
// Add a frame's worth of samples to the sample count
carrier_nsamples += frame_nsamples;
if ( carrier ) {
// If we already had carrier, adjust sample count +start -overscan
carrier_nsamples += frame_start_sample;
carrier_nsamples -= nsamples_overscan;
} else {
// We just acquired carrier.
if ( !quiet_mode ) {
if ( bfsk_data_rate >= 100 )
fprintf(stderr, "### CARRIER %u @ %.1f Hz ",
(unsigned int)(bfsk_data_rate + 0.5f),
(double)(fskp->b_mark * fskp->band_width));
else
fprintf(stderr, "### CARRIER %.2f @ %.1f Hz ",
(double)(bfsk_data_rate),
(double)(fskp->b_mark * fskp->band_width));
}
if ( !quiet_mode )
fprintf(stderr, "###\n");
carrier = 1;
bfsk_databits_decode(0, 0, 0, 0); // reset the frame processor
do_refine_frame = 1;
debug_log(" ... do_refine_frame rescan (acquired carrier)\n");
}
if ( do_refine_frame )
{
if ( confidence < INFINITY && try_step_nsamples > 1 ) {
// FSK_ANALYZE_NSTEPS_FINE:
// Scan again, but try harder to find the best frame.
// Since we found a valid confidence frame in the "sloppy"
// fsk_find_frame() call already, we're sure to find one at
// least as good this time.
#define FSK_ANALYZE_NSTEPS_FINE 8
try_step_nsamples = try_max_nsamples / FSK_ANALYZE_NSTEPS_FINE;
if ( try_step_nsamples == 0 )
try_step_nsamples = 1;
try_confidence_search_limit = INFINITY;
float confidence2, amplitude2;
unsigned long long bits2;
unsigned int frame_start_sample2;
confidence2 = fsk_find_frame(fskp, samplebuf, expect_nsamples,
try_first_sample,
try_max_nsamples,
try_step_nsamples,
try_confidence_search_limit,
carrier ? expect_data_string : expect_sync_string,
&bits2,
&amplitude2,
&frame_start_sample2
);
if ( confidence2 > confidence ) {
bits = bits2;
amplitude = amplitude2;
frame_start_sample = frame_start_sample2;
}
}
}
track_amplitude = ( track_amplitude + amplitude ) / 2;
if ( peak_confidence < confidence )
peak_confidence = confidence;
debug_log("@ confidence=%.3f peak_conf=%.3f amplitude=%.3f track_amplitude=%.3f\n",
confidence, peak_confidence, amplitude, track_amplitude );
confidence_total += confidence;
amplitude_total += amplitude;
nframes_decoded++;
noconfidence = 0;
// Advance the sample stream forward past the junk before the
// frame starts (frame_start_sample), and then past decoded frame
// (see also NOTE about frame_n_bits and expect_n_bits)...
// But actually advance just a bit less than that to allow
// for tracking slightly fast signals, hence - nsamples_overscan.
advance = frame_start_sample + frame_nsamples - nsamples_overscan;
debug_log("@ nsamples_per_bit=%.3f n_data_bits=%u "
" frame_start=%u advance=%u\n",
nsamples_per_bit, bfsk_n_data_bits,
frame_start_sample, advance);
// chop off the prev_stop bit
if ( bfsk_nstopbits != 0.0f )
bits = bits >> 1;
/*
* Send the raw data frame bits to the backend frame processor
* for final conversion to output data bytes.
*/
// chop off framing bits
bits = bit_window(bits, bfsk_nstartbits, bfsk_n_data_bits);
if (bfsk_msb_first) {
bits = bit_reverse(bits, bfsk_n_data_bits);
}
debug_log("Input: %08x%08x - Databits: %u - Shift: %i\n", (unsigned int)(bits >> 32), (unsigned int)bits, bfsk_n_data_bits, bfsk_nstartbits);
unsigned int dataout_size = 4096;
char dataoutbuf[4096];
unsigned int dataout_nbytes = 0;
// suppress printing of bfsk_sync_byte bytes
if ( bfsk_do_rx_sync ) {
if ( dataout_nbytes == 0 && bits == bfsk_sync_byte )
continue;
}
dataout_nbytes += bfsk_databits_decode(dataoutbuf + dataout_nbytes,
dataout_size - dataout_nbytes,
bits, (int)bfsk_n_data_bits);
if ( dataout_nbytes == 0 )
continue;
/*
* Print the output buffer to stdout
*/
if ( output_print_filter == 0 ) {
if ( write(1, dataoutbuf, dataout_nbytes) < 0 )
perror("write");
} else {
char *p = dataoutbuf;
for ( ; dataout_nbytes; p++,dataout_nbytes-- ) {
char printable_char = isprint(*p)||isspace(*p) ? *p : '.';
if ( write(1, &printable_char, 1) < 0 )
perror("write");
}
}
} /* end of the main loop */
free(samplebuf);
signal(SIGINT, SIG_DFL);
if ( carrier ) {
if ( !quiet_mode )
report_no_carrier(fskp, sample_rate, bfsk_data_rate,
frame_n_bits, nframes_decoded,
carrier_nsamples, confidence_total, amplitude_total);
}
simpleaudio_close(sa);
fsk_plan_destroy(fskp);
return ret;
}