From 9e75e64787e10556bf2075f241e23e41d8fd2b4c Mon Sep 17 00:00:00 2001 From: Andreas Eversberg Date: Mon, 20 Nov 2017 19:51:24 +0100 Subject: [PATCH] Add DTMF decoder to libdtmf Note: This decoder has no user yet. --- .gitignore | 1 + src/libdtmf/Makefile.am | 3 +- src/libdtmf/dtmf_decode.c | 259 ++++++++++++++++++++++++++++++++++++++ src/libdtmf/dtmf_decode.h | 35 ++++++ src/test/Makefile.am | 10 ++ src/test/test_dtmf.c | 114 +++++++++++++++++ 6 files changed, 421 insertions(+), 1 deletion(-) create mode 100644 src/libdtmf/dtmf_decode.c create mode 100644 src/libdtmf/dtmf_decode.h create mode 100644 src/test/test_dtmf.c diff --git a/.gitignore b/.gitignore index 494a229..359c9e0 100644 --- a/.gitignore +++ b/.gitignore @@ -61,6 +61,7 @@ src/test/test_filter src/test/test_sendevolumenregler src/test/test_compandor src/test/test_emphasis +src/test/test_dtmf src/test/test_dms src/test/test_sms src/test/test_performance diff --git a/src/libdtmf/Makefile.am b/src/libdtmf/Makefile.am index a44d4f6..f1df569 100644 --- a/src/libdtmf/Makefile.am +++ b/src/libdtmf/Makefile.am @@ -3,4 +3,5 @@ AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) noinst_LIBRARIES = libdtmf.a libdtmf_a_SOURCES = \ - dtmf_encode.c + dtmf_encode.c \ + dtmf_decode.c diff --git a/src/libdtmf/dtmf_decode.c b/src/libdtmf/dtmf_decode.c new file mode 100644 index 0000000..c9ace1e --- /dev/null +++ b/src/libdtmf/dtmf_decode.c @@ -0,0 +1,259 @@ +/* DTMF coder + * + * (C) 2016 by Andreas Eversberg + * All Rights Reserved + * + * 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 "../libsample/sample.h" +#include "dtmf_decode.h" + +//#define DEBUG + +#define level2db(level) (20 * log10(level)) +#define db2level(db) pow(10, (double)db / 20.0) + +#define DTMF_LOW_1 697.0 +#define DTMF_LOW_2 770.0 +#define DTMF_LOW_3 852.0 +#define DTMF_LOW_4 941.0 +#define DTMF_HIGH_1 1209.0 +#define DTMF_HIGH_2 1336.0 +#define DTMF_HIGH_3 1477.0 +#define DTMF_HIGH_4 1633.0 + +static const char dtmf_digit[] = " 123A456B789C*0#D"; + +#ifdef DEBUG +const char *_debug_amplitude(double level) +{ + static char text[42]; + + strcpy(text, " : "); + if (level > 1.0) + level = 1.0; + if (level < -1.0) + level = -1.0; + text[20 + (int)(level * 20)] = '*'; + + return text; +} +#endif + +int dtmf_decode_init(dtmf_dec_t *dtmf, void *priv, void (*recv_digit)(void *priv, char digit, dtmf_meas_t *meas), int samplerate, double max_amplitude, double min_amplitude) +{ + int rc; + + memset(dtmf, 0, sizeof(*dtmf)); + dtmf->priv = priv; + dtmf->recv_digit = recv_digit; + dtmf->samplerate = samplerate; + dtmf->freq_tollerance = 3.0; + dtmf->max_amplitude = max_amplitude; + dtmf->min_amplitude = min_amplitude; + dtmf->forward_twist = db2level(4.0); + dtmf->reverse_twist = db2level(8.0); + dtmf->time_detect = (int)(0.025 * (double)samplerate); + dtmf->time_meas = (int)(0.015 * (double)samplerate); + dtmf->time_pause = (int)(0.010 * (double)samplerate); + + /* init fm demodulator */ + rc = fm_demod_init(&dtmf->demod_low, (double)samplerate, (DTMF_LOW_1 + DTMF_LOW_4) / 2.0, DTMF_LOW_4 - DTMF_LOW_1); + if (rc < 0) + goto error; + rc = fm_demod_init(&dtmf->demod_high, (double)samplerate, (DTMF_HIGH_1 + DTMF_HIGH_4) / 2.0, DTMF_HIGH_4 - DTMF_HIGH_1); + if (rc < 0) + goto error; + + /* use fourth order (2 iter) filter, since it is as fast as second order (1 iter) filter */ + iir_lowpass_init(&dtmf->freq_lp[0], 100.0, samplerate, 2); + iir_lowpass_init(&dtmf->freq_lp[1], 100.0, samplerate, 2); + + return 0; + +error: + dtmf_decode_exit(dtmf); + return rc; +} + +void dtmf_decode_exit(dtmf_dec_t *dtmf) +{ + fm_demod_exit(&dtmf->demod_low); + fm_demod_exit(&dtmf->demod_high); +} + +void dtmf_decode_filter(dtmf_dec_t *dtmf, sample_t *samples, int length, sample_t *frequency_low, sample_t *frequency_high, sample_t *amplitude_low, sample_t *amplitude_high) +{ + sample_t I_low[length], Q_low[length]; + sample_t I_high[length], Q_high[length]; + int i; + + fm_demodulate_real(&dtmf->demod_low, frequency_low, length, samples, I_low, Q_low); + fm_demodulate_real(&dtmf->demod_high, frequency_high, length, samples, I_high, Q_high); + /* peak amplitude is the length of I/Q vector + * since we filter out the unwanted modulation product, the vector is only half of length */ + for (i = 0; i < length; i++) { + amplitude_low[i] = sqrt(I_low[i] * I_low[i] + Q_low[i] * Q_low[i]) * 2.0; + amplitude_high[i] = sqrt(I_high[i] * I_high[i] + Q_high[i] * Q_high[i]) * 2.0; + } + iir_process(&dtmf->freq_lp[0], frequency_low, length); + iir_process(&dtmf->freq_lp[1], frequency_high, length); +} +void dtmf_decode(dtmf_dec_t *dtmf, sample_t *samples, int length) +{ + sample_t frequency_low[length], amplitude_low[length]; + sample_t frequency_high[length], amplitude_high[length]; + double tollerance, min_amplitude, max_amplitude, forward_twist, reverse_twist, f1, f2; + int time_detect, time_meas, time_pause; + int low = 0, high = 0; + char detected, digit; + int count; + int aplitude_ok, twist_ok; + int i; + + tollerance = dtmf->freq_tollerance; + min_amplitude = dtmf->min_amplitude; + max_amplitude = dtmf->max_amplitude; + forward_twist = dtmf->forward_twist; + reverse_twist = dtmf->reverse_twist; + time_detect = dtmf->time_detect; + time_meas = dtmf->time_meas; + time_pause = dtmf->time_pause; + detected = dtmf->detected; + count = dtmf->count; + + /* FM/AM demod */ + dtmf_decode_filter(dtmf, samples, length, frequency_low, frequency_high, amplitude_low, amplitude_high); + + for (i = 0; i < length; i++) { +#ifdef DEBUG + printf("%s %.5f\n", _debug_amplitude(samples[i]/2.0), samples[i]/2.0); +#endif + /* get frequency of low frequencies, correct amplitude drop at cutoff point */ + f1 = frequency_low[i] + (DTMF_LOW_1 + DTMF_LOW_4) / 2.0; + if (f1 >= DTMF_LOW_1 - tollerance && f1 <= DTMF_LOW_1 + tollerance) { + /* cutoff point */ + amplitude_low[i] /= 0.7071; + low = 1; + f1 -= DTMF_LOW_1; + } else + if (f1 >= DTMF_LOW_2 - tollerance && f1 <= DTMF_LOW_2 + tollerance) { + amplitude_low[i] /= 1.0734; + low = 2; + f1 -= DTMF_LOW_2; + } else + if (f1 >= DTMF_LOW_3 - tollerance && f1 <= DTMF_LOW_3 + tollerance) { + amplitude_low[i] /= 1.0389; + low = 3; + f1 -= DTMF_LOW_3; + } else + if (f1 >= DTMF_LOW_4 - tollerance && f1 <= DTMF_LOW_4 + tollerance) { + /* cutoff point */ + amplitude_low[i] /= 0.7071; + low = 4; + f1 -= DTMF_LOW_4; + } else + low = 0; + /* get frequency of high frequencies, correct amplitude drop at cutoff point */ + f2 = frequency_high[i] + (DTMF_HIGH_1 + DTMF_HIGH_4) / 2.0; + if (f2 >= DTMF_HIGH_1 - tollerance && f2 <= DTMF_HIGH_1 + tollerance) { + /* cutoff point */ + amplitude_high[i] /= 0.7071; + high = 1; + f2 -= DTMF_HIGH_1; + } else + if (f2 >= DTMF_HIGH_2 - tollerance && f2 <= DTMF_HIGH_2 + tollerance) { + amplitude_high[i] /= 1.0731; + high = 2; + f2 -= DTMF_HIGH_2; + } else + if (f2 >= DTMF_HIGH_3 - tollerance && f2 <= DTMF_HIGH_3 + tollerance) { + amplitude_high[i] /= 1.0372; + high = 3; + f2 -= DTMF_HIGH_3; + } else + if (f2 >= DTMF_HIGH_4 - tollerance && f2 <= DTMF_HIGH_4 + tollerance) { + /* cutoff point */ + amplitude_high[i] /= 0.7071; + high = 4; + f2 -= DTMF_HIGH_4; + } else + high = 0; + digit = 0; + aplitude_ok = 0; + twist_ok = 0; + if (low && high) { + digit = dtmf_digit[low*4+high]; + /* check for limits */ + if (amplitude_low[i] <= max_amplitude && amplitude_low[i] >= min_amplitude && amplitude_high[i] <= max_amplitude && amplitude_high[i] >= min_amplitude) { + aplitude_ok = 1; +#ifdef DEBUG + printf("%.5f %.5f %.1f\n", amplitude_low[i], amplitude_high[i], level2db(amplitude_high[i] / amplitude_low[i])); +#endif + if (amplitude_high[i] / amplitude_low[i] <= forward_twist && amplitude_low[i] / amplitude_high[i] <= reverse_twist) + twist_ok = 1; + } + } + + if (!detected) { + if (digit && aplitude_ok && twist_ok) { + if (count == 0) { + memset(&dtmf->meas, 0, sizeof(dtmf->meas)); + } + if (count >= time_meas) { + dtmf->meas.frequency_low += f1; + dtmf->meas.frequency_high += f2; + dtmf->meas.amplitude_low += amplitude_low[i]; + dtmf->meas.amplitude_high += amplitude_high[i]; + dtmf->meas.count++; + } + count++; + if (count >= time_detect) { + detected = digit; + dtmf->meas.frequency_low /= dtmf->meas.count; + dtmf->meas.frequency_high /= dtmf->meas.count; + dtmf->meas.amplitude_low /= dtmf->meas.count; + dtmf->meas.amplitude_high /= dtmf->meas.count; + dtmf->meas.count = 1; + dtmf->recv_digit(dtmf->priv, digit, &dtmf->meas); + } + } else + count = 0; + } else { + if (!digit || digit != detected || !aplitude_ok || !twist_ok) { + count++; + if (count >= time_pause) { + detected = 0; +#ifdef DEBUG + printf("lost!\n"); +#endif + } + } else + count = 0; + } +#ifdef DEBUG + if (digit) + printf("DTMF tone='%c' diff frequency=%.1f %.1f amplitude=%.1f %.1f dB (%s) twist=%.1f dB (%s)\n", digit, f1, f2, level2db(amplitude_low[i]), level2db(amplitude_high[i]), (aplitude_ok) ? "OK" : "nok", level2db(amplitude_high[i] / amplitude_low[i]), (twist_ok) ? "OK" : "nok"); +#endif + + dtmf->detected = detected; + dtmf->count = count; + } +} + diff --git a/src/libdtmf/dtmf_decode.h b/src/libdtmf/dtmf_decode.h new file mode 100644 index 0000000..4aa6546 --- /dev/null +++ b/src/libdtmf/dtmf_decode.h @@ -0,0 +1,35 @@ +#include "../libfm/fm.h" + +typedef struct ftmf_meas { + double frequency_low; + double frequency_high; + double amplitude_low; + double amplitude_high; + int count; +} dtmf_meas_t; + +typedef struct dtmf_dec { + void *priv; + void (*recv_digit)(void *priv, char digit, dtmf_meas_t *meas); + int samplerate; /* samplerate */ + double freq_tollerance; /* +- limit of frequency deviation to allow */ + double min_amplitude; /* minimum amplitude relative to 0 dBm */ + double max_amplitude; /* maximum amplitude relative to 0 dBm */ + double forward_twist; /* how much do higher frequencies are louder than lower frequencies */ + double reverse_twist; /* how much do lower frequencies are louder than higher frequencies */ + int time_detect; + int time_meas; + int time_pause; + fm_demod_t demod_low; /* demodulator for low frequencies */ + fm_demod_t demod_high; /* demodulator for high frequencies */ + iir_filter_t freq_lp[2]; /* low pass to filter the frequency result */ + char detected; /* currently detected DTMF digit or 0 for no detection */ + int count; /* counter to count detection or loss (pause) of signal */ + dtmf_meas_t meas; /* measurements */ +} dtmf_dec_t; + +int dtmf_decode_init(dtmf_dec_t *dtmf, void *priv, void (*recv_digit)(void *priv, char digit, dtmf_meas_t *meas), int samplerate, double max_amplitude, double min_amplitude); +void dtmf_decode_exit(dtmf_dec_t *dtmf); +void dtmf_decode(dtmf_dec_t *dtmf, sample_t *samples, int length); +void dtmf_decode_filter(dtmf_dec_t *dtmf, sample_t *samples, int length, sample_t *frequency_low, sample_t *frequency_high, sample_t *amplitude_low, sample_t *amplitude_high); + diff --git a/src/test/Makefile.am b/src/test/Makefile.am index d15256c..cac2cfe 100644 --- a/src/test/Makefile.am +++ b/src/test/Makefile.am @@ -5,6 +5,7 @@ noinst_PROGRAMS = \ test_sendevolumenregler \ test_compandor \ test_emphasis \ + test_dtmf \ test_dms \ test_sms \ test_performance \ @@ -80,6 +81,15 @@ test_emphasis_LDADD += \ $(SOAPY_LIBS) endif +test_dtmf_SOURCES = dummy.x test_dtmf.c + +test_dtmf_LDADD = \ + $(COMMON_LA) \ + $(top_builddir)/src/libdtmf/libdtmf.a \ + $(top_builddir)/src/libfm/libfm.a \ + $(top_builddir)/src/libfilter/libfilter.a \ + -lm + test_dms_SOURCES = test_dms.c dummy.c test_dms_LDADD = \ diff --git a/src/test/test_dtmf.c b/src/test/test_dtmf.c new file mode 100644 index 0000000..8f56e5c --- /dev/null +++ b/src/test/test_dtmf.c @@ -0,0 +1,114 @@ +#include +#include +#include +#include +#include "../libdebug/debug.h" +#include "../libsample/sample.h" +#include "../libdtmf/dtmf_decode.h" +#include "../libdtmf/dtmf_encode.h" + +#define level2db(level) (20 * log10(level)) +#define db2level(db) pow(10, (double)db / 20.0) + +#define SAMPLERATE 8000 + +static double test_frequency[8] = { 697.0, 770.0, 852.0, 941.0, 1209.0, 1336.0, 1477.0, 1633.0 }; +static const char *test_digits = "*#0123456789ABCD"; + +static sample_t samples[SAMPLERATE]; + +/* generate samples with two tones */ +static void generate_test_sample(double frequency1, double frequency2, double amplitude1, double amplitude2) +{ + int i; + double value; + + for (i = 0; i < SAMPLERATE; i++) { + value = cos(2.0 * M_PI * frequency1 / (double)SAMPLERATE * i) * amplitude1; + value += cos(2.0 * M_PI * frequency2 / (double)SAMPLERATE * i) * amplitude2; + samples[i] = value; + } +} + +static void check_level(sample_t *samples, const char *desc, double target, int when1, int when2) +{ + int i; + double amplitude = 0.0, diff; + + for (i = when1; i < when2; i++) { + amplitude += samples[i]; + } + amplitude = amplitude / (when2 - when1); + diff = fabs(amplitude - target); + printf("%s: amplitude between %d and %d ms is %.4f / %.4f db (expected %.4f)\n", desc, when1 * 1000 / SAMPLERATE, when2 * 1000 / SAMPLERATE, amplitude, level2db(amplitude), level2db(target)); + if (diff < -0.1 || diff > 0.1) + printf("**** ERROR: we expected a diff close to 0.0\n"); + else + printf("OK!\n"); +} + +static char got_digit; + +static void recv_digit(void *inst, char digit, dtmf_meas_t *meas) +{ + printf("decoded digit '%c' frequency %.1f %.1f amplitude %.1f %.1f dB\n", digit, meas->frequency_low, meas->frequency_high, level2db(meas->amplitude_low), level2db(meas->amplitude_high)); + got_digit = digit; +} + +int main(void) +{ + dtmf_dec_t dtmf_dec; + dtmf_enc_t dtmf_enc; + sample_t frequency1[SAMPLERATE], frequency2[SAMPLERATE], amplitude1[SAMPLERATE], amplitude2[SAMPLERATE]; + int f, i; + double target; + + dtmf_decode_init(&dtmf_dec, NULL, recv_digit, SAMPLERATE, db2level(0), db2level(-30.0)); + + for (f = 0; f < 8; f++) { + printf("Testing filter with frequency %.0f Hz:\n", test_frequency[f]); + generate_test_sample(test_frequency[f], 0.0, 1.0, 0.0); + + dtmf_decode_filter(&dtmf_dec, samples, SAMPLERATE, frequency1, frequency2, amplitude1, amplitude2); + + if (f == 0 || f == 3) + target = sqrt(0.5); + else if (f == 1 || f == 2) + target = 1.0; + else + target = 0.0; + check_level(amplitude1, "frequency level", target, 900 * SAMPLERATE / 1000, 1000 * SAMPLERATE / 1000); + if (f == 4 || f == 7) + target = sqrt(0.5); + else if (f == 5 || f == 6) + target = 1.0; + else + target = 0.0; + check_level(amplitude2, "frequency level", target, 900 * SAMPLERATE / 1000, 1000 * SAMPLERATE / 1000); + + puts(""); + } + + dtmf_encode_init(&dtmf_enc, SAMPLERATE, 1.0); + + for (i = 0; i < 16; i++) { + printf("Testing digit '%c' encoding and decoding:\n", test_digits[i]); + memset(samples, 0, sizeof(samples[0]) * SAMPLERATE); + dtmf_encode_set_tone(&dtmf_enc, test_digits[i]); + dtmf_encode(&dtmf_enc, samples + SAMPLERATE / 10, SAMPLERATE / 20); + got_digit = 0; + dtmf_decode(&dtmf_dec, samples, SAMPLERATE); + if (got_digit == 0) + printf("**** ERROR: we expected to decode digit '%c', but nothing was decoded\n", test_digits[i]); + else if (got_digit != test_digits[i]) + printf("**** ERROR: we expected to decode digit '%c', but we decoded digit '%c'\n", test_digits[i], got_digit); + else + printf("OK!\n"); + puts(""); + } + + dtmf_decode_exit(&dtmf_dec); + + return 0; +} +