/* * minimodem.c * * minimodem - software audio Bell-type or RTTY FSK modem * * Copyright (C) 2011-2016 Kamal Mostafa * * 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 . */ #include #include #include #include #include #include #include #include #include #include #include #include #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 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 - 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); 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\n" "License GPLv3+: GNU GPL version 3 or later .\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 .\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>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, &litude, &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, &litude2, &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; }