summaryrefslogtreecommitdiff
path: root/util/spkmodem_decode
diff options
context:
space:
mode:
Diffstat (limited to 'util/spkmodem_decode')
-rw-r--r--util/spkmodem_decode/spkmodem-decode.c355
1 files changed, 218 insertions, 137 deletions
diff --git a/util/spkmodem_decode/spkmodem-decode.c b/util/spkmodem_decode/spkmodem-decode.c
index 0c98faf6..3b3b33f8 100644
--- a/util/spkmodem_decode/spkmodem-decode.c
+++ b/util/spkmodem_decode/spkmodem-decode.c
@@ -63,6 +63,7 @@
#endif
#include <errno.h>
+#include <limits.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
@@ -143,7 +144,7 @@
* Convert tone frequency ranges into pulse counts within the
* sliding analysis window.
*
- * pulse_count ≈ tone_frequency / FRAME_RATE
+ * pulse_count = tone_frequency / FRAME_RATE
* where FRAME_RATE = SAMPLE_RATE / SAMPLES_PER_FRAME.
*/
#define FREQ_SEP_MIN ((SEP_TONE_MIN_HZ) / (FRAME_RATE))
@@ -205,34 +206,69 @@ struct decoder_state {
int freq_max;
int freq_threshold;
int learn_frames;
+
+ /* previous sample used for edge detection */
+ signed short prev_sample;
};
static const char *argv0;
+/*
+ * 16-bit little endian words are read
+ * continuously. we will swap them, if
+ * the host cpu is big endian.
+ */
static int host_is_big_endian(void);
+
+/* main loop */
static void handle_audio(struct decoder_state *st);
+
+/* separate tone tolerances */
+static void select_separator_tone(struct decoder_state *st);
+static int is_valid_signal(struct decoder_state *st);
+
+/* output to terminal */
+static int set_ascii_bit(struct decoder_state *st);
+static void print_char(struct decoder_state *st);
+static void reset_char(struct decoder_state *st);
+
+/* process samples/frames */
+static void decode_pulse(struct decoder_state *st);
+static signed short read_sample(struct decoder_state *st);
+static void read_words(struct decoder_state *st);
+
+/* continually adjust tone */
static void detect_tone(struct decoder_state *st);
static int silent_signal(struct decoder_state *st);
static void select_low_tone(struct decoder_state *st);
-static void collect_separator_tone(struct decoder_state *st);
-static int valid_signal(struct decoder_state *st);
-static void decode_pulse(struct decoder_state *st);
-static signed short read_sample(struct decoder_state *st);
-static int set_ascii_bit(struct decoder_state *st);
-static void print_char(struct decoder_state *st);
+
+/* debug */
static void print_stats(struct decoder_state *st);
-static void reset_char(struct decoder_state *st);
+/* error handling / usage */
static void err(int errval, const char *msg, ...);
static void usage(void);
static const char *progname(void);
+/* portability (old systems) */
int getopt(int, char * const *, const char *);
extern char *optarg;
extern int optind;
extern int opterr;
extern int optopt;
+#ifndef CHAR_BIT
+#define CHAR_BIT 8
+#endif
+
+typedef char static_assert_char_is_8_bits[(CHAR_BIT == 8) ? 1 : -1];
+typedef char static_assert_char_is_1[(sizeof(char) == 1) ? 1 : -1];
+typedef char static_assert_short[(sizeof(short) == 2) ? 1 : -1];
+typedef char static_assert_int_is_4[(sizeof(int) >= 4) ? 1 : -1];
+typedef char static_assert_twos_complement[
+ ((-1 & 3) == 3) ? 1 : -1
+];
+
int
main(int argc, char **argv)
{
@@ -297,130 +333,115 @@ handle_audio(struct decoder_state *st)
int sample;
/*
- * If the modem signal disappears for several frames,
- * discard the partially assembled character.
+ * If the modem signal disappears for several (read: 3)
+ * frames, discard the partially assembled character.
*/
- if (st->sample_count >= (3 * SAMPLES_PER_FRAME))
+ if (st->sample_count >= (3 * SAMPLES_PER_FRAME) ||
+ st->freq_separator <= 0)
reset_char(st);
- collect_separator_tone(st);
- decode_pulse(st);
-
- if (set_ascii_bit(st) < 0)
- print_char(st);
-
st->sample_count = 0;
+
+ /* process exactly one frame */
for (sample = 0; sample < SAMPLES_PER_FRAME; sample++)
decode_pulse(st);
+ select_separator_tone(st);
+
+ if (set_ascii_bit(st) < 0)
+ print_char(st);
+
/* Detect tone per each frame */
detect_tone(st);
}
/*
- * Automatically detect spkmodem tone
+ * collect separator tone statistics
+ * (and auto-adjust tolerances)
*/
static void
-detect_tone(struct decoder_state *st)
+select_separator_tone(struct decoder_state *st)
{
- if (st->learn_frames >= LEARN_FRAMES)
+ int avg;
+
+ if (!is_valid_signal(st))
return;
- st->learn_frames++;
+ st->sep_sum += st->freq_separator;
+ st->sep_samples++;
- if (silent_signal(st))
+ if (st->sep_samples != 50)
return;
- select_low_tone(st);
+ avg = st->sep_sum / st->sep_samples;
- if (st->learn_frames == LEARN_FRAMES) {
- st->freq_threshold =
- (st->freq_min + st->freq_max) / 2;
+ st->sep_min = avg - SEP_TOLERANCE_PULSES;
+ st->sep_max = avg + SEP_TOLERANCE_PULSES;
- if (st->debug)
- printf("auto threshold: %dHz\n",
- st->freq_threshold * FRAME_RATE);
- }
+ /* reset calibration accumulators */
+ st->sep_sum = 0;
+ st->sep_samples = 0;
+
+ if (st->debug)
+ printf("separator calibrated: %dHz\n",
+ avg * FRAME_RATE);
}
/*
- * Ignore silence / near silence.
- * Both FIR windows will be near zero when no signal exists.
+ * Verify that the observed pulse densities fall within the
+ * expected ranges for spkmodem tones. This prevents random noise
+ * from being misinterpreted as data.
*/
static int
-silent_signal(struct decoder_state *st)
+is_valid_signal(struct decoder_state *st)
{
- return (st->freq_data <= 2 &&
- st->freq_separator <= 2);
+ if (st->freq_data <= 0)
+ return 0;
+
+ if (st->freq_separator < st->sep_min ||
+ st->freq_separator > st->sep_max)
+ return 0;
+
+ return 1;
}
/*
- * Choose the lowest active tone.
- * Separator frames carry tone in the separator window,
- * data frames carry tone in the data window.
+ * Each validated frame contributes one bit of modem data.
+ * Bits are accumulated MSB-first into the ASCII byte.
*/
-static void
-select_low_tone(struct decoder_state *st)
+static int
+set_ascii_bit(struct decoder_state *st)
{
- int f;
-
- f = st->freq_data;
+ if (st->debug)
+ print_stats(st);
- if (f <= 0 || (st->freq_separator > 0 &&
- st->freq_separator < f))
- f = st->freq_separator;
+ if (!is_valid_signal(st))
+ return st->ascii_bit;
- if (f <= 0)
- return;
+ if (st->freq_data < st->freq_threshold)
+ st->ascii |= (1 << st->ascii_bit);
- if (f < st->freq_min)
- st->freq_min = f;
+ st->ascii_bit--;
- if (f > st->freq_max)
- st->freq_max = f;
+ return st->ascii_bit;
}
-/*
- * collect separator tone statistics
- * (and auto-adjust tolerances)
- */
static void
-collect_separator_tone(struct decoder_state *st)
+print_char(struct decoder_state *st)
{
- int avg;
-
- if (valid_signal(st))
- return;
-
- if (st->sep_samples >= 50 && st->freq_separator <= 0)
- return;
-
- st->sep_sum += st->freq_separator;
- st->sep_samples++;
-
- if (st->sep_samples != 50)
- return;
-
- avg = st->sep_sum / st->sep_samples;
-
- st->sep_min = avg - SEP_TOLERANCE_PULSES;
- st->sep_max = avg + SEP_TOLERANCE_PULSES;
-
if (st->debug)
- printf("separator calibrated: %dHz\n",
- avg * FRAME_RATE);
+ printf("<%c,%x>", st->ascii, st->ascii);
+ else
+ putchar(st->ascii);
+
+ reset_char(st);
}
-/*
- * Verify that the observed pulse densities fall within the
- * expected ranges for spkmodem tones. This prevents random noise
- * from being misinterpreted as data.
- */
-static int
-valid_signal(struct decoder_state *st)
+static void
+reset_char(struct decoder_state *st)
{
- return (st->freq_separator > 0 &&
- st->freq_data > 0);
+ st->ascii = 0;
+ st->ascii_bit = 7;
}
/*
@@ -431,9 +452,11 @@ decode_pulse(struct decoder_state *st)
{
unsigned char old_ring, old_sep;
unsigned char new_pulse;
+ signed short sample;
int ringpos;
int sep_pos;
- signed short sample;
+ int diff_edge;
+ int diff_amp;
ringpos = st->ringpos;
sep_pos = st->sep_pos;
@@ -469,18 +492,33 @@ decode_pulse(struct decoder_state *st)
sample = read_sample(st);
/*
- * Convert the waveform sample into a pulse (0 or 1).
+ * Avoid startup edge. Since
+ * it's zero at startup, this
+ * may wrongly produce a pulse
+ */
+ if (st->sample_count == 0)
+ st->prev_sample = sample;
+
+ /*
+ * Detect edges instead of amplitude.
+ * This is more tolerant of weak microphones
+ * and speaker distortion..
*
- * The unsigned comparison creates a small dead zone near zero,
- * suppressing small amplitude noise from microphones or
- * cheap ADCs. Real PC speaker tones are far outside this
- * range, so they still produce clean pulses.
+ * However, we check both slope edges and
+ * amplitude, to mitagate noise.
*/
- if ((unsigned)(sample + THRESHOLD)
- > (unsigned)(2 * THRESHOLD))
+ diff_amp = sample;
+ diff_edge = sample - st->prev_sample;
+ if (diff_edge < 0)
+ diff_edge = -diff_edge;
+ if (diff_amp < 0)
+ diff_amp = -diff_amp;
+ if (diff_edge > THRESHOLD &&
+ diff_amp > THRESHOLD)
new_pulse = 1;
else
new_pulse = 0;
+ st->prev_sample = sample;
st->pulse[ringpos] = new_pulse;
st->freq_separator += new_pulse;
@@ -490,11 +528,9 @@ decode_pulse(struct decoder_state *st)
* The separator window always stays one frame ahead
* of the data window.
*/
- ringpos++;
- if (ringpos >= MAX_SAMPLES)
+ if (++ringpos >= MAX_SAMPLES)
ringpos = 0;
- sep_pos++;
- if (sep_pos >= MAX_SAMPLES)
+ if (++sep_pos >= MAX_SAMPLES)
sep_pos = 0;
st->ringpos = ringpos;
@@ -506,25 +542,11 @@ decode_pulse(struct decoder_state *st)
static signed short
read_sample(struct decoder_state *st)
{
- size_t n;
signed short sample;
unsigned short u;
- while (st->inpos >= st->inlen) {
-
- n = fread(st->inbuf, sizeof(st->inbuf[0]),
- READ_BUF, stdin);
-
- if (n == 0) {
- if (ferror(stdin))
- err(errno, "stdin read");
- if (feof(stdin))
- exit(EXIT_SUCCESS);
- }
-
- st->inpos = 0;
- st->inlen = n;
- }
+ while (st->inpos >= st->inlen)
+ read_words(st);
sample = st->inbuf[st->inpos++];
@@ -538,31 +560,97 @@ read_sample(struct decoder_state *st)
return sample;
}
+static void
+read_words(struct decoder_state *st)
+{
+ size_t n;
+
+ n = fread(st->inbuf, sizeof(st->inbuf[0]),
+ READ_BUF, stdin);
+
+ if (n != 0) {
+ st->inpos = 0;
+ st->inlen = n;
+
+ return;
+ }
+
+ if (ferror(stdin))
+ err(errno, "stdin read");
+ if (feof(stdin))
+ exit(EXIT_SUCCESS);
+}
+
/*
- * Each validated frame contributes one bit of modem data.
- * Bits are accumulated MSB-first into the ASCII byte.
+ * Automatically detect spkmodem tone
*/
-static int
-set_ascii_bit(struct decoder_state *st)
+static void
+detect_tone(struct decoder_state *st)
{
+ if (st->learn_frames >= LEARN_FRAMES)
+ return;
+
+ st->learn_frames++;
+
+ if (silent_signal(st))
+ return;
+
+ select_low_tone(st);
+
+ if (st->learn_frames != LEARN_FRAMES)
+ return;
+
+ /*
+ * If the observed frequencies are too close,
+ * learning likely failed (only one tone seen).
+ * Keep the default threshold.
+ */
+ if (st->freq_max - st->freq_min < 2)
+ return;
+
+ st->freq_threshold =
+ (st->freq_min + st->freq_max) / 2;
+
if (st->debug)
- print_stats(st);
- if (st->freq_data < st->freq_threshold)
- st->ascii |= (1 << st->ascii_bit);
+ printf("auto threshold: %dHz\n",
+ st->freq_threshold * FRAME_RATE);
+}
- st->ascii_bit--;
- return st->ascii_bit;
+/*
+ * Ignore silence / near silence.
+ * Both FIR windows will be near zero when no signal exists.
+ */
+static int
+silent_signal(struct decoder_state *st)
+{
+ return (st->freq_data <= 2 &&
+ st->freq_separator <= 2);
}
+/*
+ * Choose the lowest active tone.
+ * Separator frames carry tone in the separator window,
+ * data frames carry tone in the data window.
+ */
static void
-print_char(struct decoder_state *st)
+select_low_tone(struct decoder_state *st)
{
- if (st->debug)
- printf("<%c,%x>", st->ascii, st->ascii);
- else
- putchar(st->ascii);
+ int f;
- reset_char(st);
+ f = st->freq_data;
+
+ if (f <= 0 || (st->freq_separator > 0 &&
+ st->freq_separator < f))
+ f = st->freq_separator;
+
+ if (f <= 0)
+ return;
+
+ if (f < st->freq_min)
+ st->freq_min = f;
+
+ if (f > st->freq_max)
+ st->freq_max = f;
}
static void
@@ -599,13 +687,6 @@ print_stats(struct decoder_state *st)
}
static void
-reset_char(struct decoder_state *st)
-{
- st->ascii = 0;
- st->ascii_bit = 7;
-}
-
-static void
err(int errval, const char *msg, ...)
{
va_list ap;