Merge "res_calendar: Specialized calendars depend on symbols of general calendar."
[asterisk/asterisk.git] / main / dsp.c
index 33db505..8b39fe5 100644 (file)
  * \author Steve Underwood <steveu@coppice.org>
  */
 
+/*! \li \ref dsp.c uses the configuration file \ref dsp.conf
+ * \addtogroup configuration_file Configuration Files
+ */
+
+/*!
+ * \page dsp.conf dsp.conf
+ * \verbinclude dsp.conf.sample
+ */
+
 /* Some routines from tone_detect.c by Steven Underwood as published under the zapata library */
 /*
        tone_detect.c - General telephony tone detection, and specific
 
 #include "asterisk.h"
 
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
 #include <math.h>
 
 #include "asterisk/frame.h"
+#include "asterisk/format_cache.h"
 #include "asterisk/channel.h"
 #include "asterisk/dsp.h"
 #include "asterisk/ulaw.h"
@@ -58,6 +66,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/utils.h"
 #include "asterisk/options.h"
 #include "asterisk/config.h"
+#include "asterisk/test.h"
 
 /*! Number of goertzels for progress detect */
 enum gsamp_size {
@@ -102,21 +111,30 @@ static struct progalias {
        { "uk", PROG_MODE_UK },
 };
 
+#define FREQ_ARRAY_SIZE 7
+
 static struct progress {
        enum gsamp_size size;
-       int freqs[7];
+       int freqs[FREQ_ARRAY_SIZE];
 } modes[] = {
        { GSAMP_SIZE_NA, { 350, 440, 480, 620, 950, 1400, 1800 } },     /*!< North America */
        { GSAMP_SIZE_CR, { 425 } },                                     /*!< Costa Rica, Brazil */
        { GSAMP_SIZE_UK, { 350, 400, 440 } },                           /*!< UK */
 };
 
-/*!\brief This value is the minimum threshold, calculated by averaging all
- * of the samples within a frame, for which a frame is determined to either
- * be silence (below the threshold) or noise (above the threshold).  Please
- * note that while the default threshold is an even exponent of 2, there is
- * no requirement that it be so.  The threshold will accept any value between
- * 0 and 32767.
+/*!
+ * \brief Default minimum average magnitude threshold to determine talking/noise by the DSP.
+ *
+ * \details
+ * The magnitude calculated for this threshold is determined by
+ * averaging the absolute value of all samples within a frame.
+ *
+ * This value is the threshold for which a frame's average magnitude
+ * is determined to either be silence (below the threshold) or
+ * noise/talking (at or above the threshold).  Please note that while
+ * the default threshold is an even exponent of 2, there is no
+ * requirement that it be so.  The threshold will work for any value
+ * between 1 and 2^15.
  */
 #define DEFAULT_THRESHOLD      512
 
@@ -125,7 +143,7 @@ enum busy_detect {
        BUSY_PAT_PERCENT = 7,   /*!< The percentage difference between measured and actual pattern */
        BUSY_THRESHOLD = 100,   /*!< Max number of ms difference between max and min times in busy */
        BUSY_MIN = 75,          /*!< Busy must be at least 80 ms in half-cadence */
-       BUSY_MAX =3100          /*!< Busy can't be longer than 3100 ms in half-cadence */
+       BUSY_MAX = 3100         /*!< Busy can't be longer than 3100 ms in half-cadence */
 };
 
 /*! Remember last 15 units */
@@ -146,7 +164,7 @@ enum gsamp_thresh {
 
 #define        MAX_DTMF_DIGITS         128
 
-/* Basic DTMF specs:
+/* Basic DTMF (AT&T) specs:
  *
  * Minimum tone on = 40ms
  * Minimum tone off = 50ms
@@ -159,18 +177,21 @@ enum gsamp_thresh {
  */
 
 #define DTMF_THRESHOLD         8.0e7
-#define FAX_THRESHOLD          8.0e7
-#define FAX_2ND_HARMONIC       2.0     /* 4dB */
-#define DTMF_NORMAL_TWIST      6.3     /* 8dB */
+#define TONE_THRESHOLD         7.8e7
+
+#define DEF_DTMF_NORMAL_TWIST          6.31     /* 8.0dB */
+#define DEF_RELAX_DTMF_NORMAL_TWIST    6.31     /* 8.0dB */
+
 #ifdef RADIO_RELAX
-#define DTMF_REVERSE_TWIST          (relax ? 6.5 : 2.5)     /* 4dB normal */
+#define DEF_DTMF_REVERSE_TWIST         2.51     /* 4.01dB */
+#define DEF_RELAX_DTMF_REVERSE_TWIST   6.61     /* 8.2dB */
 #else
-#define DTMF_REVERSE_TWIST          (relax ? 4.0 : 2.5)     /* 4dB normal */
+#define DEF_DTMF_REVERSE_TWIST         2.51     /* 4.01dB */
+#define DEF_RELAX_DTMF_REVERSE_TWIST   3.98     /* 6.0dB */
 #endif
+
 #define DTMF_RELATIVE_PEAK_ROW 6.3     /* 8dB */
 #define DTMF_RELATIVE_PEAK_COL 6.3     /* 8dB */
-#define DTMF_2ND_HARMONIC_ROW       (relax ? 1.7 : 2.5)     /* 4dB normal */
-#define DTMF_2ND_HARMONIC_COL  63.1    /* 18dB */
 #define DTMF_TO_TOTAL_ENERGY   42.0
 
 #define BELL_MF_THRESHOLD      1.6e9
@@ -185,7 +206,7 @@ enum gsamp_thresh {
  * followed by a 3 second silent (2100 Hz OFF) period.
  */
 #define FAX_TONE_CNG_FREQ      1100
-#define FAX_TONE_CNG_DURATION  500
+#define FAX_TONE_CNG_DURATION  500     /* ms */
 #define FAX_TONE_CNG_DB                16
 
 /* This signal may be sent by the Terminating FAX machine anywhere between
@@ -193,7 +214,7 @@ enum gsamp_thresh {
  * of a 2100 Hz tone that is from 2.6 to 4 seconds in duration.
 */
 #define FAX_TONE_CED_FREQ      2100
-#define FAX_TONE_CED_DURATION  2600
+#define FAX_TONE_CED_DURATION  2600    /* ms */
 #define FAX_TONE_CED_DB                16
 
 #define DEFAULT_SAMPLE_RATE            8000
@@ -204,10 +225,15 @@ enum gsamp_thresh {
 /* DTMF goertzel size */
 #define DTMF_GSIZE             102
 
-/* How many successive hits needed to consider begin of a digit */
-#define DTMF_HITS_TO_BEGIN     4
-/* How many successive misses needed to consider end of a digit */
-#define DTMF_MISSES_TO_END     4
+/* How many successive hits needed to consider begin of a digit
+ * IE. Override with dtmf_hits_to_begin=4 in dsp.conf
+ */
+#define DEF_DTMF_HITS_TO_BEGIN 2
+
+/* How many successive misses needed to consider end of a digit
+ * IE. Override with dtmf_misses_to_end=4 in dsp.conf
+ */
+#define DEF_DTMF_MISSES_TO_END 3
 
 /*!
  * \brief The default silence threshold we will use if an alternate
@@ -218,11 +244,14 @@ static const int DEFAULT_SILENCE_THRESHOLD = 256;
 #define CONFIG_FILE_NAME "dsp.conf"
 
 typedef struct {
+       /*! The previous previous sample calculation (No binary point just plain int) */
        int v2;
+       /*! The previous sample calculation (No binary point just plain int) */
        int v3;
+       /*! v2 and v3 power of two exponent to keep value in int range */
        int chunky;
+       /*! 15 bit fixed point goertzel coefficient = 2 * cos(2 * pi * freq / sample_rate) */
        int fac;
-       int samples;
 } goertzel_state_t;
 
 typedef struct {
@@ -252,8 +281,6 @@ typedef struct
 {
        goertzel_state_t row_out[4];
        goertzel_state_t col_out[4];
-       int hits_to_begin;              /* How many successive hits needed to consider begin of a digit */
-       int misses_to_end;              /* How many successive misses needed to consider end of a digit */
        int hits;                       /* How many successive hits we have seen already */
        int misses;                     /* How many successive misses we have seen already */
        int lasthit;
@@ -298,53 +325,62 @@ static const float mf_tones[] = {
 static const char dtmf_positions[] = "123A" "456B" "789C" "*0#D";
 static const char bell_mf_positions[] = "1247C-358A--69*---0B----#";
 static int thresholds[THRESHOLD_MAX];
+static float dtmf_normal_twist;                /* AT&T = 8dB */
+static float dtmf_reverse_twist;       /* AT&T = 4dB */
+static float relax_dtmf_normal_twist;  /* AT&T = 8dB */
+static float relax_dtmf_reverse_twist; /* AT&T = 6dB */
+static int dtmf_hits_to_begin;         /* How many successive hits needed to consider begin of a digit */
+static int dtmf_misses_to_end;         /* How many successive misses needed to consider end of a digit */
 
 static inline void goertzel_sample(goertzel_state_t *s, short sample)
 {
        int v1;
 
+       /*
+        * Shift previous values so
+        * v1 is previous previous value
+        * v2 is previous value
+        * until the new v3 is calculated.
+        */
        v1 = s->v2;
        s->v2 = s->v3;
 
+       /* Discard the binary fraction introduced by s->fac */
        s->v3 = (s->fac * s->v2) >> 15;
+       /* Scale sample to match previous values */
        s->v3 = s->v3 - v1 + (sample >> s->chunky);
-       if (abs(s->v3) > 32768) {
+
+       if (abs(s->v3) > (1 << 15)) {
+               /* The result is now too large so increase the chunky power. */
                s->chunky++;
                s->v3 = s->v3 >> 1;
                s->v2 = s->v2 >> 1;
-               v1 = v1 >> 1;
-       }
-}
-
-static inline void goertzel_update(goertzel_state_t *s, short *samps, int count)
-{
-       int i;
-
-       for (i = 0; i < count; i++) {
-               goertzel_sample(s, samps[i]);
        }
 }
 
-
 static inline float goertzel_result(goertzel_state_t *s)
 {
        goertzel_result_t r;
+
        r.value = (s->v3 * s->v3) + (s->v2 * s->v2);
        r.value -= ((s->v2 * s->v3) >> 15) * s->fac;
+       /*
+        * We have to double the exponent because we multiplied the
+        * previous sample calculation values together.
+        */
        r.power = s->chunky * 2;
        return (float)r.value * (float)(1 << r.power);
 }
 
-static inline void goertzel_init(goertzel_state_t *s, float freq, int samples, unsigned int sample_rate)
+static inline void goertzel_init(goertzel_state_t *s, float freq, unsigned int sample_rate)
 {
-       s->v2 = s->v3 = s->chunky = 0.0;
+       s->v2 = s->v3 = s->chunky = 0;
        s->fac = (int)(32768.0 * 2.0 * cos(2.0 * M_PI * freq / sample_rate));
-       s->samples = samples;
 }
 
 static inline void goertzel_reset(goertzel_state_t *s)
 {
-       s->v2 = s->v3 = s->chunky = 0.0;
+       s->v2 = s->v3 = s->chunky = 0;
 }
 
 typedef struct {
@@ -353,7 +389,7 @@ typedef struct {
 } fragment_t;
 
 /* Note on tone suppression (squelching). Individual detectors (DTMF/MF/generic tone)
- * report fragmens of the frame in which detected tone resides and which needs
+ * report fragments of the frame in which detected tone resides and which needs
  * to be "muted" in order to suppress the tone. To mark fragment for muting,
  * detectors call mute_fragment passing fragment_t there. Multiple fragments
  * can be marked and ast_dsp_process later will mute all of them.
@@ -368,7 +404,9 @@ typedef struct {
 struct ast_dsp {
        struct ast_frame f;
        int threshold;
+       /*! Accumulated total silence in ms since last talking/noise. */
        int totalsilence;
+       /*! Accumulated total talking/noise in ms since last silence. */
        int totalnoise;
        int features;
        int ringtimeout;
@@ -377,7 +415,7 @@ struct ast_dsp {
        struct ast_dsp_busy_pattern busy_cadence;
        int historicnoise[DSP_HISTORY];
        int historicsilence[DSP_HISTORY];
-       goertzel_state_t freqs[7];
+       goertzel_state_t freqs[FREQ_ARRAY_SIZE];
        int freqcount;
        int gsamps;
        enum gsamp_size gsamp_size;
@@ -438,14 +476,14 @@ static void ast_tone_detect_init(tone_detect_state_t *s, int freq, int duration,
        s->block_size = periods_in_block * sample_rate / freq;
 
        /* tone_detect is currently only used to detect fax tones and we
-          do not need suqlching the fax tones */
+          do not need squelching the fax tones */
        s->squelch = 0;
 
        /* Account for the first and the last block to be incomplete
           and thus no tone will be detected in them */
        s->hits_required = (duration_samples - (s->block_size - 1)) / s->block_size;
 
-       goertzel_init(&s->tone, freq, s->block_size, sample_rate);
+       goertzel_init(&s->tone, freq, sample_rate);
 
        s->samples_pending = s->block_size;
        s->hit_count = 0;
@@ -479,32 +517,30 @@ static void ast_fax_detect_init(struct ast_dsp *s)
 
 }
 
-static void ast_dtmf_detect_init (dtmf_detect_state_t *s, unsigned int sample_rate)
+static void ast_dtmf_detect_init(dtmf_detect_state_t *s, unsigned int sample_rate)
 {
        int i;
 
+       for (i = 0; i < 4; i++) {
+               goertzel_init(&s->row_out[i], dtmf_row[i], sample_rate);
+               goertzel_init(&s->col_out[i], dtmf_col[i], sample_rate);
+       }
        s->lasthit = 0;
        s->current_hit = 0;
-       for (i = 0;  i < 4;  i++) {
-               goertzel_init(&s->row_out[i], dtmf_row[i], DTMF_GSIZE, sample_rate);
-               goertzel_init(&s->col_out[i], dtmf_col[i], DTMF_GSIZE, sample_rate);
-               s->energy = 0.0;
-       }
+       s->energy = 0.0;
        s->current_sample = 0;
        s->hits = 0;
        s->misses = 0;
-
-       s->hits_to_begin = DTMF_HITS_TO_BEGIN;
-       s->misses_to_end = DTMF_MISSES_TO_END;
 }
 
-static void ast_mf_detect_init (mf_detect_state_t *s, unsigned int sample_rate)
+static void ast_mf_detect_init(mf_detect_state_t *s, unsigned int sample_rate)
 {
        int i;
-       s->hits[0] = s->hits[1] = s->hits[2] = s->hits[3] = s->hits[4] = 0;
-       for (i = 0;  i < 6;  i++) {
-               goertzel_init (&s->tone_out[i], mf_tones[i], 160, sample_rate);
+
+       for (i = 0; i < 6; i++) {
+               goertzel_init(&s->tone_out[i], mf_tones[i], sample_rate);
        }
+       s->hits[0] = s->hits[1] = s->hits[2] = s->hits[3] = s->hits[4] = 0;
        s->current_sample = 0;
        s->current_hit = 0;
 }
@@ -531,6 +567,7 @@ static int tone_detect(struct ast_dsp *dsp, tone_detect_state_t *s, int16_t *amp
        int limit;
        int res = 0;
        int16_t *ptr;
+       short samp;
        int start, end;
        fragment_t mute = {0, 0};
 
@@ -539,7 +576,7 @@ static int tone_detect(struct ast_dsp *dsp, tone_detect_state_t *s, int16_t *amp
                s->mute_samples -= mute.end;
        }
 
-       for (start = 0;  start < samples;  start = end) {
+       for (start = 0; start < samples; start = end) {
                /* Process in blocks. */
                limit = samples - start;
                if (limit > s->samples_pending) {
@@ -548,10 +585,11 @@ static int tone_detect(struct ast_dsp *dsp, tone_detect_state_t *s, int16_t *amp
                end = start + limit;
 
                for (i = limit, ptr = amp ; i > 0; i--, ptr++) {
-                       /* signed 32 bit int should be enough to suqare any possible signed 16 bit value */
-                       s->energy += (int32_t) *ptr * (int32_t) *ptr;
+                       samp = *ptr;
+                       /* signed 32 bit int should be enough to square any possible signed 16 bit value */
+                       s->energy += (int32_t) samp * (int32_t) samp;
 
-                       goertzel_sample(&s->tone, *ptr);
+                       goertzel_sample(&s->tone, samp);
                }
 
                s->samples_pending -= limit;
@@ -567,10 +605,11 @@ static int tone_detect(struct ast_dsp *dsp, tone_detect_state_t *s, int16_t *amp
                tone_energy *= 2.0;
                s->energy *= s->block_size;
 
-               ast_debug(10, "tone %d, Ew=%.2E, Et=%.2E, s/n=%10.2f\n", s->freq, tone_energy, s->energy, tone_energy / (s->energy - tone_energy));
+               ast_debug(10, "%d Hz tone %2d Ew=%.4E, Et=%.4E, s/n=%10.2f\n", s->freq, s->hit_count, tone_energy, s->energy, tone_energy / (s->energy - tone_energy));
                hit = 0;
-               if (tone_energy > s->energy * s->threshold) {
-                       ast_debug(10, "Hit! count=%d\n", s->hit_count);
+               if (TONE_THRESHOLD <= tone_energy
+                       && tone_energy > s->energy * s->threshold) {
+                       ast_debug(10, "%d Hz tone Hit! %2d Ew=%.4E, Et=%.4E, s/n=%10.2f\n", s->freq, s->hit_count, tone_energy, s->energy, tone_energy / (s->energy - tone_energy));
                        hit = 1;
                }
 
@@ -589,7 +628,7 @@ static int tone_detect(struct ast_dsp *dsp, tone_detect_state_t *s, int16_t *amp
                }
 
                if (s->hit_count == s->hits_required) {
-                       ast_debug(1, "%d Hz done detected\n", s->freq);
+                       ast_debug(1, "%d Hz tone detected\n", s->freq);
                        res = 1;
                }
 
@@ -644,10 +683,10 @@ static int dtmf_detect(struct ast_dsp *dsp, digit_detect_state_t *s, int16_t amp
 {
        float row_energy[4];
        float col_energy[4];
-       float famp;
        int i;
        int j;
        int sample;
+       short samp;
        int best_row;
        int best_col;
        int hit;
@@ -670,18 +709,18 @@ static int dtmf_detect(struct ast_dsp *dsp, digit_detect_state_t *s, int16_t amp
                /* The following unrolled loop takes only 35% (rough estimate) of the
                   time of a rolled loop on the machine on which it was developed */
                for (j = sample; j < limit; j++) {
-                       famp = amp[j];
-                       s->td.dtmf.energy += famp*famp;
+                       samp = amp[j];
+                       s->td.dtmf.energy += (int32_t) samp * (int32_t) samp;
                        /* With GCC 2.95, the following unrolled code seems to take about 35%
                           (rough estimate) as long as a neat little 0-3 loop */
-                       goertzel_sample(s->td.dtmf.row_out, amp[j]);
-                       goertzel_sample(s->td.dtmf.col_out, amp[j]);
-                       goertzel_sample(s->td.dtmf.row_out + 1, amp[j]);
-                       goertzel_sample(s->td.dtmf.col_out + 1, amp[j]);
-                       goertzel_sample(s->td.dtmf.row_out + 2, amp[j]);
-                       goertzel_sample(s->td.dtmf.col_out + 2, amp[j]);
-                       goertzel_sample(s->td.dtmf.row_out + 3, amp[j]);
-                       goertzel_sample(s->td.dtmf.col_out + 3, amp[j]);
+                       goertzel_sample(s->td.dtmf.row_out, samp);
+                       goertzel_sample(s->td.dtmf.col_out, samp);
+                       goertzel_sample(s->td.dtmf.row_out + 1, samp);
+                       goertzel_sample(s->td.dtmf.col_out + 1, samp);
+                       goertzel_sample(s->td.dtmf.row_out + 2, samp);
+                       goertzel_sample(s->td.dtmf.col_out + 2, samp);
+                       goertzel_sample(s->td.dtmf.row_out + 3, samp);
+                       goertzel_sample(s->td.dtmf.col_out + 3, samp);
                }
                s->td.dtmf.current_sample += (limit - sample);
                if (s->td.dtmf.current_sample < DTMF_GSIZE) {
@@ -689,27 +728,31 @@ static int dtmf_detect(struct ast_dsp *dsp, digit_detect_state_t *s, int16_t amp
                }
                /* We are at the end of a DTMF detection block */
                /* Find the peak row and the peak column */
-               row_energy[0] = goertzel_result (&s->td.dtmf.row_out[0]);
-               col_energy[0] = goertzel_result (&s->td.dtmf.col_out[0]);
+               row_energy[0] = goertzel_result(&s->td.dtmf.row_out[0]);
+               col_energy[0] = goertzel_result(&s->td.dtmf.col_out[0]);
 
-               for (best_row = best_col = 0, i = 1;  i < 4;  i++) {
-                       row_energy[i] = goertzel_result (&s->td.dtmf.row_out[i]);
+               for (best_row = best_col = 0, i = 1; i < 4; i++) {
+                       row_energy[i] = goertzel_result(&s->td.dtmf.row_out[i]);
                        if (row_energy[i] > row_energy[best_row]) {
                                best_row = i;
                        }
-                       col_energy[i] = goertzel_result (&s->td.dtmf.col_out[i]);
+                       col_energy[i] = goertzel_result(&s->td.dtmf.col_out[i]);
                        if (col_energy[i] > col_energy[best_col]) {
                                best_col = i;
                        }
                }
+               ast_debug(10, "DTMF best '%c' Erow=%.4E Ecol=%.4E Erc=%.4E Et=%.4E\n",
+                       dtmf_positions[(best_row << 2) + best_col],
+                       row_energy[best_row], col_energy[best_col],
+                       row_energy[best_row] + col_energy[best_col], s->td.dtmf.energy);
                hit = 0;
                /* Basic signal level test and the twist test */
                if (row_energy[best_row] >= DTMF_THRESHOLD &&
                    col_energy[best_col] >= DTMF_THRESHOLD &&
-                   col_energy[best_col] < row_energy[best_row] * DTMF_REVERSE_TWIST &&
-                   col_energy[best_col] * DTMF_NORMAL_TWIST > row_energy[best_row]) {
+                   col_energy[best_col] < row_energy[best_row] * (relax ? relax_dtmf_reverse_twist : dtmf_reverse_twist) &&
+                   row_energy[best_row] < col_energy[best_col] * (relax ? relax_dtmf_normal_twist : dtmf_normal_twist)) {
                        /* Relative peak test */
-                       for (i = 0;  i < 4;  i++) {
+                       for (i = 0; i < 4; i++) {
                                if ((i != best_col &&
                                    col_energy[i] * DTMF_RELATIVE_PEAK_COL > col_energy[best_col]) ||
                                    (i != best_row
@@ -722,42 +765,95 @@ static int dtmf_detect(struct ast_dsp *dsp, digit_detect_state_t *s, int16_t amp
                            (row_energy[best_row] + col_energy[best_col]) > DTMF_TO_TOTAL_ENERGY * s->td.dtmf.energy) {
                                /* Got a hit */
                                hit = dtmf_positions[(best_row << 2) + best_col];
+                               ast_debug(10, "DTMF hit '%c'\n", hit);
                        }
                }
 
-               if (hit == s->td.dtmf.lasthit) {
-                       if (s->td.dtmf.current_hit) {
-                               /* We are in the middle of a digit already */
-                               if (hit) {
-                                       if (hit != s->td.dtmf.current_hit) {
-                                               /* Look for a start of a new digit.
-                                                  This is because hits_to_begin may be smaller than misses_to_end
-                                                  and we may find the beginning of new digit before we consider last one ended. */
-                                               s->td.dtmf.current_hit = 0;
-                                       } else {
-                                               /* Current hit was same as last, so increment digit duration (of last digit) */
-                                               s->digitlen[s->current_digits - 1] += DTMF_GSIZE;
-                                       }
-                               } else {
-                                       /* No Digit */
-                                       s->td.dtmf.misses++;
-                                       if (s->td.dtmf.misses == s->td.dtmf.misses_to_end) {
-                                               /* There were enough misses to consider digit ended */
-                                               s->td.dtmf.current_hit = 0;
-                                       }
-                               }
-                       } else if (hit) {
-                               /* Detecting new digit */
-                               s->td.dtmf.hits++;
-                               if (s->td.dtmf.hits == s->td.dtmf.hits_to_begin) {
-                                       store_digit(s, hit);
-                                       s->td.dtmf.current_hit = hit;
+/*
+ * Adapted from ETSI ES 201 235-3 V1.3.1 (2006-03)
+ * (40ms reference is tunable with hits_to_begin and misses_to_end)
+ * each hit/miss is 12.75ms with DTMF_GSIZE at 102
+ *
+ * Character recognition: When not DRC *(1) and then
+ *      Shall exist VSC > 40 ms (hits_to_begin)
+ *      May exist 20 ms <= VSC <= 40 ms
+ *      Shall not exist VSC < 20 ms
+ *
+ * Character recognition: When DRC and then
+ *      Shall cease Not VSC > 40 ms (misses_to_end)
+ *      May cease 20 ms >= Not VSC >= 40 ms
+ *      Shall not cease Not VSC < 20 ms
+ *
+ * *(1) or optionally a different digit recognition condition
+ *
+ * Legend: VSC The continuous existence of a valid signal condition.
+ *      Not VSC The continuous non-existence of valid signal condition.
+ *      DRC The existence of digit recognition condition.
+ *      Not DRC The non-existence of digit recognition condition.
+ */
+
+/*
+ * Example: hits_to_begin=2 misses_to_end=3
+ * -------A last_hit=A hits=0&1
+ * ------AA hits=2 current_hit=A misses=0       BEGIN A
+ * -----AA- misses=1 last_hit=' ' hits=0
+ * ----AA-- misses=2
+ * ---AA--- misses=3 current_hit=' '            END A
+ * --AA---B last_hit=B hits=0&1
+ * -AA---BC last_hit=C hits=0&1
+ * AA---BCC hits=2 current_hit=C misses=0       BEGIN C
+ * A---BCC- misses=1 last_hit=' ' hits=0
+ * ---BCC-C misses=0 last_hit=C hits=0&1
+ * --BCC-CC misses=0
+ *
+ * Example: hits_to_begin=3 misses_to_end=2
+ * -------A last_hit=A hits=0&1
+ * ------AA hits=2
+ * -----AAA hits=3 current_hit=A misses=0       BEGIN A
+ * ----AAAB misses=1 last_hit=B hits=0&1
+ * ---AAABB misses=2 current_hit=' ' hits=2     END A
+ * --AAABBB hits=3 current_hit=B misses=0       BEGIN B
+ * -AAABBBB misses=0
+ *
+ * Example: hits_to_begin=2 misses_to_end=2
+ * -------A last_hit=A hits=0&1
+ * ------AA hits=2 current_hit=A misses=0       BEGIN A
+ * -----AAB misses=1 hits=0&1
+ * ----AABB misses=2 current_hit=' ' hits=2 current_hit=B misses=0 BEGIN B
+ * ---AABBB misses=0
+ */
+
+               if (s->td.dtmf.current_hit) {
+                       /* We are in the middle of a digit already */
+                       if (hit != s->td.dtmf.current_hit) {
+                               s->td.dtmf.misses++;
+                               if (s->td.dtmf.misses == dtmf_misses_to_end) {
+                                       /* There were enough misses to consider digit ended */
+                                       s->td.dtmf.current_hit = 0;
                                }
+                       } else {
+                               s->td.dtmf.misses = 0;
+                               /* Current hit was same as last, so increment digit duration (of last digit) */
+                               s->digitlen[s->current_digits - 1] += DTMF_GSIZE;
                        }
-               } else {
-                       s->td.dtmf.hits = 1;
-                       s->td.dtmf.misses = 1;
+               }
+
+               /* Look for a start of a new digit no matter if we are already in the middle of some
+                  digit or not. This is because hits_to_begin may be smaller than misses_to_end
+                  and we may find begin of new digit before we consider last one ended. */
+
+               if (hit != s->td.dtmf.lasthit) {
                        s->td.dtmf.lasthit = hit;
+                       s->td.dtmf.hits = 0;
+               }
+               if (hit && hit != s->td.dtmf.current_hit) {
+                       s->td.dtmf.hits++;
+                       if (s->td.dtmf.hits == dtmf_hits_to_begin) {
+                               store_digit(s, hit);
+                               s->digitlen[s->current_digits - 1] = dtmf_hits_to_begin * DTMF_GSIZE;
+                               s->td.dtmf.current_hit = hit;
+                               s->td.dtmf.misses = 0;
+                       }
                }
 
                /* If we had a hit in this block, include it into mute fragment */
@@ -799,6 +895,7 @@ static int mf_detect(struct ast_dsp *dsp, digit_detect_state_t *s, int16_t amp[]
        int i;
        int j;
        int sample;
+       short samp;
        int hit;
        int limit;
        fragment_t mute = {0, 0};
@@ -809,7 +906,7 @@ static int mf_detect(struct ast_dsp *dsp, digit_detect_state_t *s, int16_t amp[]
        }
 
        hit = 0;
-       for (sample = 0;  sample < samples;  sample = limit) {
+       for (sample = 0; sample < samples; sample = limit) {
                /* 80 is optimised to meet the MF specs. */
                /* XXX So then why is MF_GSIZE defined as 120? */
                if ((samples - sample) >= (MF_GSIZE - s->td.mf.current_sample)) {
@@ -819,15 +916,16 @@ static int mf_detect(struct ast_dsp *dsp, digit_detect_state_t *s, int16_t amp[]
                }
                /* The following unrolled loop takes only 35% (rough estimate) of the
                   time of a rolled loop on the machine on which it was developed */
-               for (j = sample;  j < limit;  j++) {
+               for (j = sample; j < limit; j++) {
                        /* With GCC 2.95, the following unrolled code seems to take about 35%
                           (rough estimate) as long as a neat little 0-3 loop */
-                       goertzel_sample(s->td.mf.tone_out, amp[j]);
-                       goertzel_sample(s->td.mf.tone_out + 1, amp[j]);
-                       goertzel_sample(s->td.mf.tone_out + 2, amp[j]);
-                       goertzel_sample(s->td.mf.tone_out + 3, amp[j]);
-                       goertzel_sample(s->td.mf.tone_out + 4, amp[j]);
-                       goertzel_sample(s->td.mf.tone_out + 5, amp[j]);
+                       samp = amp[j];
+                       goertzel_sample(s->td.mf.tone_out, samp);
+                       goertzel_sample(s->td.mf.tone_out + 1, samp);
+                       goertzel_sample(s->td.mf.tone_out + 2, samp);
+                       goertzel_sample(s->td.mf.tone_out + 3, samp);
+                       goertzel_sample(s->td.mf.tone_out + 4, samp);
+                       goertzel_sample(s->td.mf.tone_out + 5, samp);
                }
                s->td.mf.current_sample += (limit - sample);
                if (s->td.mf.current_sample < MF_GSIZE) {
@@ -918,11 +1016,11 @@ static int mf_detect(struct ast_dsp *dsp, digit_detect_state_t *s, int16_t amp[]
                                mute_fragment(dsp, &mute);
                                mute.start = (sample > MF_GSIZE) ? (sample - MF_GSIZE) : 0;
                        }
-                       mute.end = limit + DTMF_GSIZE;
+                       mute.end = limit + MF_GSIZE;
                }
 
                /* Reinitialise the detector for the next block */
-               for (i = 0;  i < 6;  i++) {
+               for (i = 0; i < 6; i++) {
                        goertzel_reset(&s->td.mf.tone_out[i]);
                }
                s->td.mf.current_sample = 0;
@@ -964,11 +1062,14 @@ static inline int pair_there(float p1, float p2, float i1, float i2, float e)
 
 static int __ast_dsp_call_progress(struct ast_dsp *dsp, short *s, int len)
 {
+       short samp;
        int x;
        int y;
        int pass;
        int newstate = DSP_TONE_STATE_SILENCE;
        int res = 0;
+       int freqcount = dsp->freqcount > FREQ_ARRAY_SIZE ? FREQ_ARRAY_SIZE : dsp->freqcount;
+
        while (len) {
                /* Take the lesser of the number of samples we need and what we have */
                pass = len;
@@ -976,17 +1077,18 @@ static int __ast_dsp_call_progress(struct ast_dsp *dsp, short *s, int len)
                        pass = dsp->gsamp_size - dsp->gsamps;
                }
                for (x = 0; x < pass; x++) {
-                       for (y = 0; y < dsp->freqcount; y++) {
-                               goertzel_sample(&dsp->freqs[y], s[x]);
+                       samp = s[x];
+                       dsp->genergy += (int32_t) samp * (int32_t) samp;
+                       for (y = 0; y < freqcount; y++) {
+                               goertzel_sample(&dsp->freqs[y], samp);
                        }
-                       dsp->genergy += s[x] * s[x];
                }
                s += pass;
                dsp->gsamps += pass;
                len -= pass;
                if (dsp->gsamps == dsp->gsamp_size) {
-                       float hz[7];
-                       for (y = 0; y < 7; y++) {
+                       float hz[FREQ_ARRAY_SIZE];
+                       for (y = 0; y < FREQ_ARRAY_SIZE; y++) {
                                hz[y] = goertzel_result(&dsp->freqs[y]);
                        }
                        switch (dsp->progmode) {
@@ -1032,7 +1134,7 @@ static int __ast_dsp_call_progress(struct ast_dsp *dsp, short *s, int len)
                                }
                                break;
                        default:
-                               ast_log(LOG_WARNING, "Can't process in unknown prog mode '%d'\n", dsp->progmode);
+                               ast_log(LOG_WARNING, "Can't process in unknown prog mode '%u'\n", dsp->progmode);
                        }
                        if (newstate == dsp->tstate) {
                                dsp->tcount++;
@@ -1106,7 +1208,7 @@ int ast_dsp_call_progress(struct ast_dsp *dsp, struct ast_frame *inf)
                ast_log(LOG_WARNING, "Can't check call progress of non-voice frames\n");
                return 0;
        }
-       if (!ast_format_is_slinear(&inf->subclass.format)) {
+       if (!ast_format_cache_is_slinear(inf->subclass.format)) {
                ast_log(LOG_WARNING, "Can only check call progress in signed-linear frames\n");
                return 0;
        }
@@ -1331,30 +1433,29 @@ static int ast_dsp_silence_noise_with_energy(struct ast_dsp *dsp, struct ast_fra
                ast_log(LOG_WARNING, "Can't calculate silence on a non-voice frame\n");
                return 0;
        }
-       if (!ast_format_is_slinear(&f->subclass.format)) {
+
+       if (ast_format_cache_is_slinear(f->subclass.format)) {
+               s = f->data.ptr;
+               len = f->datalen/2;
+       } else {
                odata = f->data.ptr;
                len = f->datalen;
-               switch (f->subclass.format.id) {
-                       case AST_FORMAT_ULAW:
-                               s = ast_alloca(len * 2);
-                               for (x = 0;x < len; x++) {
-                                       s[x] = AST_MULAW(odata[x]);
-                               }
-                               break;
-                       case AST_FORMAT_ALAW:
-                               s = ast_alloca(len * 2);
-                               for (x = 0;x < len; x++) {
-                                       s[x] = AST_ALAW(odata[x]);
-                               }
-                               break;
-                       default:
-                               ast_log(LOG_WARNING, "Can only calculate silence on signed-linear, alaw or ulaw frames :(\n");
+               if (ast_format_cmp(f->subclass.format, ast_format_ulaw)) {
+                       s = ast_alloca(len * 2);
+                       for (x = 0; x < len; x++) {
+                               s[x] = AST_MULAW(odata[x]);
+                       }
+               } else if (ast_format_cmp(f->subclass.format, ast_format_alaw)) {
+                       s = ast_alloca(len * 2);
+                       for (x = 0; x < len; x++) {
+                               s[x] = AST_ALAW(odata[x]);
+                       }
+               } else {
+                       ast_log(LOG_WARNING, "Can only calculate silence on signed-linear, alaw or ulaw frames :(\n");
                        return 0;
                }
-       } else {
-               s = f->data.ptr;
-               len = f->datalen/2;
        }
+
        if (noise) {
                return __ast_dsp_silence_noise(dsp, s, len, NULL, total, frames_energy);
        } else {
@@ -1399,31 +1500,26 @@ struct ast_frame *ast_dsp_process(struct ast_channel *chan, struct ast_dsp *dsp,
        odata = af->data.ptr;
        len = af->datalen;
        /* Make sure we have short data */
-       if (ast_format_is_slinear(&af->subclass.format)) {
+       if (ast_format_cache_is_slinear(af->subclass.format)) {
                shortdata = af->data.ptr;
                len = af->datalen / 2;
+       } else if (ast_format_cmp(af->subclass.format, ast_format_ulaw) == AST_FORMAT_CMP_EQUAL) {
+               shortdata = ast_alloca(af->datalen * 2);
+               for (x = 0; x < len; x++) {
+                       shortdata[x] = AST_MULAW(odata[x]);
+               }
+       } else if (ast_format_cmp(af->subclass.format, ast_format_alaw) == AST_FORMAT_CMP_EQUAL) {
+               shortdata = ast_alloca(af->datalen * 2);
+               for (x = 0; x < len; x++) {
+                       shortdata[x] = AST_ALAW(odata[x]);
+               }
        } else {
-               switch (af->subclass.format.id) {
-               case AST_FORMAT_ULAW:
-               case AST_FORMAT_TESTLAW:
-                       shortdata = ast_alloca(af->datalen * 2);
-                       for (x = 0;x < len; x++) {
-                               shortdata[x] = AST_MULAW(odata[x]);
-                       }
-                       break;
-               case AST_FORMAT_ALAW:
-                       shortdata = ast_alloca(af->datalen * 2);
-                       for (x = 0; x < len; x++) {
-                               shortdata[x] = AST_ALAW(odata[x]);
-                       }
-                       break;
-               default:
-                       /*Display warning only once. Otherwise you would get hundreds of warnings every second */
-                       if (dsp->display_inband_dtmf_warning)
-                               ast_log(LOG_WARNING, "Inband DTMF is not supported on codec %s. Use RFC2833\n", ast_getformatname(&af->subclass.format));
-                       dsp->display_inband_dtmf_warning = 0;
-                       return af;
+               /*Display warning only once. Otherwise you would get hundreds of warnings every second */
+               if (dsp->display_inband_dtmf_warning) {
+                       ast_log(LOG_WARNING, "Inband DTMF is not supported on codec %s. Use RFC2833\n", ast_format_get_name(af->subclass.format));
                }
+               dsp->display_inband_dtmf_warning = 0;
+               return af;
        }
 
        /* Initially we do not want to mute anything */
@@ -1552,19 +1648,14 @@ done:
                memset(shortdata + dsp->mute_data[x].start, 0, sizeof(int16_t) * (dsp->mute_data[x].end - dsp->mute_data[x].start));
        }
 
-       switch (af->subclass.format.id) {
-       case AST_FORMAT_ULAW:
+       if (ast_format_cmp(af->subclass.format, ast_format_ulaw) == AST_FORMAT_CMP_EQUAL) {
                for (x = 0; x < len; x++) {
                        odata[x] = AST_LIN2MU((unsigned short) shortdata[x]);
                }
-               break;
-       case AST_FORMAT_ALAW:
+       } else if (ast_format_cmp(af->subclass.format, ast_format_alaw) == AST_FORMAT_CMP_EQUAL) {
                for (x = 0; x < len; x++) {
                        odata[x] = AST_LIN2A((unsigned short) shortdata[x]);
                }
-               /* fall through */
-       default:
-               break;
        }
 
        if (outf) {
@@ -1585,14 +1676,14 @@ static void ast_dsp_prog_reset(struct ast_dsp *dsp)
 
        dsp->gsamp_size = modes[dsp->progmode].size;
        dsp->gsamps = 0;
-       for (x = 0; x < ARRAY_LEN(modes[dsp->progmode].freqs); x++) {
+       for (x = 0; x < FREQ_ARRAY_SIZE; x++) {
                if (modes[dsp->progmode].freqs[x]) {
-                       goertzel_init(&dsp->freqs[x], (float)modes[dsp->progmode].freqs[x], dsp->gsamp_size, dsp->sample_rate);
+                       goertzel_init(&dsp->freqs[x], (float)modes[dsp->progmode].freqs[x], dsp->sample_rate);
                        max = x + 1;
                }
        }
        dsp->freqcount = max;
-       dsp->ringtimeout= 0;
+       dsp->ringtimeout = 0;
 }
 
 unsigned int ast_dsp_get_sample_rate(const struct ast_dsp *dsp)
@@ -1611,6 +1702,7 @@ static struct ast_dsp *__ast_dsp_new(unsigned int sample_rate)
                dsp->digitmode = DSP_DIGITMODE_DTMF;
                dsp->faxmode = DSP_FAXMODE_DETECT_CNG;
                dsp->sample_rate = sample_rate;
+               dsp->freqcount = 0;
                /* Initialize digit detector */
                ast_digit_detect_init(&dsp->digit_state, dsp->digitmode & DSP_DIGITMODE_MF, dsp->sample_rate);
                dsp->display_inband_dtmf_warning = 1;
@@ -1640,6 +1732,13 @@ void ast_dsp_set_features(struct ast_dsp *dsp, int features)
        }
 }
 
+
+int ast_dsp_get_features(struct ast_dsp *dsp)
+{
+        return (dsp->features);
+}
+
+
 void ast_dsp_free(struct ast_dsp *dsp)
 {
        ast_free(dsp);
@@ -1675,19 +1774,21 @@ void ast_dsp_digitreset(struct ast_dsp *dsp)
        if (dsp->digitmode & DSP_DIGITMODE_MF) {
                mf_detect_state_t *s = &dsp->digit_state.td.mf;
                /* Reinitialise the detector for the next block */
-               for (i = 0;  i < 6;  i++) {
+               for (i = 0; i < 6; i++) {
                        goertzel_reset(&s->tone_out[i]);
                }
-               s->hits[4] = s->hits[3] = s->hits[2] = s->hits[1] = s->hits[0] = s->current_hit = 0;
+               s->hits[4] = s->hits[3] = s->hits[2] = s->hits[1] = s->hits[0] = 0;
+               s->current_hit = 0;
                s->current_sample = 0;
        } else {
                dtmf_detect_state_t *s = &dsp->digit_state.td.dtmf;
                /* Reinitialise the detector for the next block */
-               for (i = 0;  i < 4;  i++) {
+               for (i = 0; i < 4; i++) {
                        goertzel_reset(&s->row_out[i]);
                        goertzel_reset(&s->col_out[i]);
                }
-               s->lasthit = s->current_hit = 0;
+               s->lasthit = 0;
+               s->current_hit = 0;
                s->energy = 0.0;
                s->current_sample = 0;
                s->hits = 0;
@@ -1709,7 +1810,7 @@ void ast_dsp_reset(struct ast_dsp *dsp)
        }
        memset(dsp->historicsilence, 0, sizeof(dsp->historicsilence));
        memset(dsp->historicnoise, 0, sizeof(dsp->historicnoise));
-       dsp->ringtimeout= 0;
+       dsp->ringtimeout = 0;
 }
 
 int ast_dsp_set_digitmode(struct ast_dsp *dsp, int digitmode)
@@ -1771,12 +1872,19 @@ static int _dsp_init(int reload)
        struct ast_variable *v;
        struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
        int cfg_threshold;
+       float cfg_twist;
 
        if ((cfg = ast_config_load2(CONFIG_FILE_NAME, "dsp", config_flags)) == CONFIG_STATUS_FILEUNCHANGED) {
                return 0;
        }
 
        thresholds[THRESHOLD_SILENCE] = DEFAULT_SILENCE_THRESHOLD;
+       dtmf_normal_twist = DEF_DTMF_NORMAL_TWIST;
+       dtmf_reverse_twist = DEF_DTMF_REVERSE_TWIST;
+       relax_dtmf_normal_twist = DEF_RELAX_DTMF_NORMAL_TWIST;
+       relax_dtmf_reverse_twist = DEF_RELAX_DTMF_REVERSE_TWIST;
+        dtmf_hits_to_begin = DEF_DTMF_HITS_TO_BEGIN;
+        dtmf_misses_to_end = DEF_DTMF_MISSES_TO_END;
 
        if (cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEINVALID) {
                return 0;
@@ -1791,6 +1899,54 @@ static int _dsp_init(int reload)
                        } else {
                                thresholds[THRESHOLD_SILENCE] = cfg_threshold;
                        }
+               } else if (!strcasecmp(v->name, "dtmf_normal_twist")) {
+                       if (sscanf(v->value, "%30f", &cfg_twist) < 1) {
+                               ast_log(LOG_WARNING, "Unable to convert '%s' to a numeric value.\n", v->value);
+                       } else if ((cfg_twist < 2.0) || (cfg_twist > 100.0)) {          /* < 3.0dB or > 20dB */
+                               ast_log(LOG_WARNING, "Invalid dtmf_normal_twist value '%.2f' specified, using default of %.2f\n", cfg_twist, dtmf_normal_twist);
+                       } else {
+                               dtmf_normal_twist = cfg_twist;
+                       }
+               } else if (!strcasecmp(v->name, "dtmf_reverse_twist")) {
+                       if (sscanf(v->value, "%30f", &cfg_twist) < 1) {
+                               ast_log(LOG_WARNING, "Unable to convert '%s' to a numeric value.\n", v->value);
+                       } else if ((cfg_twist < 2.0) || (cfg_twist > 100.0)) {          /* < 3.0dB or > 20dB */
+                               ast_log(LOG_WARNING, "Invalid dtmf_reverse_twist value '%.2f' specified, using default of %.2f\n", cfg_twist, dtmf_reverse_twist);
+                       } else {
+                               dtmf_reverse_twist = cfg_twist;
+                       }
+               } else if (!strcasecmp(v->name, "relax_dtmf_normal_twist")) {
+                       if (sscanf(v->value, "%30f", &cfg_twist) < 1) {
+                               ast_log(LOG_WARNING, "Unable to convert '%s' to a numeric value.\n", v->value);
+                       } else if ((cfg_twist < 2.0) || (cfg_twist > 100.0)) {          /* < 3.0dB or > 20dB */
+                               ast_log(LOG_WARNING, "Invalid relax_dtmf_normal_twist value '%.2f' specified, using default of %.2f\n", cfg_twist, relax_dtmf_normal_twist);
+                       } else {
+                               relax_dtmf_normal_twist = cfg_twist;
+                       }
+               } else if (!strcasecmp(v->name, "relax_dtmf_reverse_twist")) {
+                       if (sscanf(v->value, "%30f", &cfg_twist) < 1) {
+                               ast_log(LOG_WARNING, "Unable to convert '%s' to a numeric value.\n", v->value);
+                       } else if ((cfg_twist < 2.0) || (cfg_twist > 100.0)) {          /* < 3.0dB or > 20dB */
+                               ast_log(LOG_WARNING, "Invalid relax_dtmf_reverse_twist value '%.2f' specified, using default of %.2f\n", cfg_twist, relax_dtmf_reverse_twist);
+                       } else {
+                               relax_dtmf_reverse_twist = cfg_twist;
+                       }
+               } else if (!strcasecmp(v->name, "dtmf_hits_to_begin")) {
+                       if (sscanf(v->value, "%30d", &cfg_threshold) < 1) {
+                               ast_log(LOG_WARNING, "Unable to convert '%s' to a numeric value.\n", v->value);
+                       } else if (cfg_threshold < 1) {         /* must be 1 or greater */
+                               ast_log(LOG_WARNING, "Invalid dtmf_hits_to_begin value '%d' specified, using default of %d\n", cfg_threshold, dtmf_hits_to_begin);
+                       } else {
+                               dtmf_hits_to_begin = cfg_threshold;
+                       }
+               } else if (!strcasecmp(v->name, "dtmf_misses_to_end")) {
+                       if (sscanf(v->value, "%30d", &cfg_threshold) < 1) {
+                               ast_log(LOG_WARNING, "Unable to convert '%s' to a numeric value.\n", v->value);
+                       } else if (cfg_threshold < 1) {         /* must be 1 or greater */
+                               ast_log(LOG_WARNING, "Invalid dtmf_misses_to_end value '%d' specified, using default of %d\n", cfg_threshold, dtmf_misses_to_end);
+                       } else {
+                               dtmf_misses_to_end = cfg_threshold;
+                       }
                }
        }
        ast_config_destroy(cfg);
@@ -1803,9 +1959,460 @@ int ast_dsp_get_threshold_from_settings(enum threshold which)
        return thresholds[which];
 }
 
+#ifdef TEST_FRAMEWORK
+static void test_tone_sample_gen(short *slin_buf, int samples, int rate, int freq, short amplitude)
+{
+       int idx;
+       double sample_step = 2.0 * M_PI * freq / rate;/* radians per step */
+
+       for (idx = 0; idx < samples; ++idx) {
+               slin_buf[idx] = amplitude * sin(sample_step * idx);
+       }
+}
+#endif
+
+#ifdef TEST_FRAMEWORK
+static void test_tone_sample_gen_add(short *slin_buf, int samples, int rate, int freq, short amplitude)
+{
+       int idx;
+       double sample_step = 2.0 * M_PI * freq / rate;/* radians per step */
+
+       for (idx = 0; idx < samples; ++idx) {
+               slin_buf[idx] += amplitude * sin(sample_step * idx);
+       }
+}
+#endif
+
+#ifdef TEST_FRAMEWORK
+static void test_dual_sample_gen(short *slin_buf, int samples, int rate, int f1, short a1, int f2, short a2)
+{
+       test_tone_sample_gen(slin_buf, samples, rate, f1, a1);
+       test_tone_sample_gen_add(slin_buf, samples, rate, f2, a2);
+}
+#endif
+
+#ifdef TEST_FRAMEWORK
+#define TONE_AMPLITUDE_MAX     0x7fff  /* Max signed linear amplitude */
+#define TONE_AMPLITUDE_MIN     80              /* Min signed linear amplitude detectable */
+
+static int test_tone_amplitude_sweep(struct ast_test *test, struct ast_dsp *dsp, tone_detect_state_t *tone_state)
+{
+       short slin_buf[tone_state->block_size];
+       int result;
+       int idx;
+       struct {
+               short amp_val;
+               int detect;
+       } amp_tests[] = {
+               { .amp_val = TONE_AMPLITUDE_MAX,        .detect = 1, },
+               { .amp_val = 10000,                                     .detect = 1, },
+               { .amp_val = 1000,                                      .detect = 1, },
+               { .amp_val = 100,                                       .detect = 1, },
+               { .amp_val = TONE_AMPLITUDE_MIN,        .detect = 1, },
+               { .amp_val = 75,                                        .detect = 0, },
+               { .amp_val = 10,                                        .detect = 0, },
+               { .amp_val = 1,                                         .detect = 0, },
+       };
+
+       result = 0;
+
+       for (idx = 0; idx < ARRAY_LEN(amp_tests); ++idx) {
+               int detected;
+               int duration;
+
+               ast_debug(1, "Test %d Hz at amplitude %d\n",
+                       tone_state->freq, amp_tests[idx].amp_val);
+               test_tone_sample_gen(slin_buf, tone_state->block_size, DEFAULT_SAMPLE_RATE,
+                       tone_state->freq, amp_tests[idx].amp_val);
+
+               detected = 0;
+               for (duration = 0; !detected && duration < tone_state->hits_required + 3; ++duration) {
+                       detected = tone_detect(dsp, tone_state, slin_buf, tone_state->block_size) ? 1 : 0;
+               }
+               if (amp_tests[idx].detect != detected) {
+                       /*
+                        * Both messages are needed.  ast_debug for when figuring out
+                        * what went wrong and the test update for normal output before
+                        * you start debugging.  The different logging methods are not
+                        * synchronized.
+                        */
+                       ast_debug(1,
+                               "Test %d Hz at amplitude %d failed.  Detected: %s\n",
+                               tone_state->freq, amp_tests[idx].amp_val,
+                               detected ? "yes" : "no");
+                       ast_test_status_update(test,
+                               "Test %d Hz at amplitude %d failed.  Detected: %s\n",
+                               tone_state->freq, amp_tests[idx].amp_val,
+                               detected ? "yes" : "no");
+                       result = -1;
+               }
+               tone_state->hit_count = 0;
+       }
+
+       return result;
+}
+#endif
+
+#ifdef TEST_FRAMEWORK
+static int test_dtmf_amplitude_sweep(struct ast_test *test, struct ast_dsp *dsp, int digit_index)
+{
+       short slin_buf[DTMF_GSIZE];
+       int result;
+       int row;
+       int column;
+       int idx;
+       struct {
+               short amp_val;
+               int digit;
+       } amp_tests[] = {
+               /*
+                * XXX Since there is no current DTMF level detection issue.  This test
+                * just checks the current detection levels.
+                */
+               { .amp_val = TONE_AMPLITUDE_MAX/2,      .digit = dtmf_positions[digit_index], },
+               { .amp_val = 10000,                                     .digit = dtmf_positions[digit_index], },
+               { .amp_val = 1000,                                      .digit = dtmf_positions[digit_index], },
+               { .amp_val = 500,                                       .digit = dtmf_positions[digit_index], },
+               { .amp_val = 250,                                       .digit = dtmf_positions[digit_index], },
+               { .amp_val = 200,                                       .digit = dtmf_positions[digit_index], },
+               { .amp_val = 180,                                       .digit = dtmf_positions[digit_index], },
+               /* Various digits detect and not detect in this range */
+               { .amp_val = 170,                                       .digit = 0, },
+               { .amp_val = 100,                                       .digit = 0, },
+               /*
+                * Amplitudes below TONE_AMPLITUDE_MIN start having questionable detection
+                * over quantization and background noise.
+                */
+               { .amp_val = TONE_AMPLITUDE_MIN,        .digit = 0, },
+               { .amp_val = 75,                                        .digit = 0, },
+               { .amp_val = 10,                                        .digit = 0, },
+               { .amp_val = 1,                                         .digit = 0, },
+       };
+
+       row = (digit_index >> 2) & 0x03;
+       column = digit_index & 0x03;
+
+       result = 0;
+
+       for (idx = 0; idx < ARRAY_LEN(amp_tests); ++idx) {
+               int digit;
+               int duration;
+
+               ast_debug(1, "Test '%c' at amplitude %d\n",
+                       dtmf_positions[digit_index], amp_tests[idx].amp_val);
+               test_dual_sample_gen(slin_buf, ARRAY_LEN(slin_buf), DEFAULT_SAMPLE_RATE,
+                       (int) dtmf_row[row], amp_tests[idx].amp_val,
+                       (int) dtmf_col[column], amp_tests[idx].amp_val);
+
+               digit = 0;
+               for (duration = 0; !digit && duration < 3; ++duration) {
+                       digit = dtmf_detect(dsp, &dsp->digit_state, slin_buf, ARRAY_LEN(slin_buf),
+                               0, 0);
+               }
+               if (amp_tests[idx].digit != digit) {
+                       /*
+                        * Both messages are needed.  ast_debug for when figuring out
+                        * what went wrong and the test update for normal output before
+                        * you start debugging.  The different logging methods are not
+                        * synchronized.
+                        */
+                       ast_debug(1,
+                               "Test '%c' at amplitude %d failed.  Detected Digit: '%c'\n",
+                               dtmf_positions[digit_index], amp_tests[idx].amp_val,
+                               digit ?: ' ');
+                       ast_test_status_update(test,
+                               "Test '%c' at amplitude %d failed.  Detected Digit: '%c'\n",
+                               dtmf_positions[digit_index], amp_tests[idx].amp_val,
+                               digit ?: ' ');
+                       result = -1;
+               }
+               ast_dsp_digitreset(dsp);
+       }
+
+       return result;
+}
+#endif
+
+#ifdef TEST_FRAMEWORK
+static int test_dtmf_twist_sweep(struct ast_test *test, struct ast_dsp *dsp, int digit_index)
+{
+       short slin_buf[DTMF_GSIZE];
+       int result;
+       int row;
+       int column;
+       int idx;
+       struct {
+               short amp_row;
+               short amp_col;
+               int digit;
+       } twist_tests[] = {
+               /*
+                * XXX Since there is no current DTMF twist detection issue.  This test
+                * just checks the current detection levels.
+                *
+                * Normal twist has the column higher than the row amplitude.
+                * Reverse twist is the other way.
+                */
+               { .amp_row = 1000 + 1800, .amp_col = 1000 +    0, .digit = 0, },
+               { .amp_row = 1000 + 1700, .amp_col = 1000 +    0, .digit = 0, },
+               /* Various digits detect and not detect in this range */
+               { .amp_row = 1000 + 1400, .amp_col = 1000 +    0, .digit = dtmf_positions[digit_index], },
+               { .amp_row = 1000 + 1300, .amp_col = 1000 +    0, .digit = dtmf_positions[digit_index], },
+               { .amp_row = 1000 + 1200, .amp_col = 1000 +    0, .digit = dtmf_positions[digit_index], },
+               { .amp_row = 1000 + 1100, .amp_col = 1000 +    0, .digit = dtmf_positions[digit_index], },
+               { .amp_row = 1000 + 1000, .amp_col = 1000 +    0, .digit = dtmf_positions[digit_index], },
+               { .amp_row = 1000 +  100, .amp_col = 1000 +    0, .digit = dtmf_positions[digit_index], },
+               { .amp_row = 1000 +    0, .amp_col = 1000 +  100, .digit = dtmf_positions[digit_index], },
+               { .amp_row = 1000 +    0, .amp_col = 1000 +  200, .digit = dtmf_positions[digit_index], },
+               { .amp_row = 1000 +    0, .amp_col = 1000 +  300, .digit = dtmf_positions[digit_index], },
+               { .amp_row = 1000 +    0, .amp_col = 1000 +  400, .digit = dtmf_positions[digit_index], },
+               { .amp_row = 1000 +    0, .amp_col = 1000 +  500, .digit = dtmf_positions[digit_index], },
+               { .amp_row = 1000 +    0, .amp_col = 1000 +  550, .digit = dtmf_positions[digit_index], },
+               /* Various digits detect and not detect in this range */
+               { .amp_row = 1000 +    0, .amp_col = 1000 +  650, .digit = 0, },
+               { .amp_row = 1000 +    0, .amp_col = 1000 +  700, .digit = 0, },
+               { .amp_row = 1000 +    0, .amp_col = 1000 +  800, .digit = 0, },
+       };
+       float save_normal_twist;
+       float save_reverse_twist;
+
+       save_normal_twist = dtmf_normal_twist;
+       save_reverse_twist = dtmf_reverse_twist;
+       dtmf_normal_twist = DEF_DTMF_NORMAL_TWIST;
+       dtmf_reverse_twist = DEF_DTMF_REVERSE_TWIST;
+
+       row = (digit_index >> 2) & 0x03;
+       column = digit_index & 0x03;
+
+       result = 0;
+
+       for (idx = 0; idx < ARRAY_LEN(twist_tests); ++idx) {
+               int digit;
+               int duration;
+
+               ast_debug(1, "Test '%c' twist row %d col %d amplitudes\n",
+                       dtmf_positions[digit_index],
+                       twist_tests[idx].amp_row, twist_tests[idx].amp_col);
+               test_dual_sample_gen(slin_buf, ARRAY_LEN(slin_buf), DEFAULT_SAMPLE_RATE,
+                       (int) dtmf_row[row], twist_tests[idx].amp_row,
+                       (int) dtmf_col[column], twist_tests[idx].amp_col);
+
+               digit = 0;
+               for (duration = 0; !digit && duration < 3; ++duration) {
+                       digit = dtmf_detect(dsp, &dsp->digit_state, slin_buf, ARRAY_LEN(slin_buf),
+                               0, 0);
+               }
+               if (twist_tests[idx].digit != digit) {
+                       /*
+                        * Both messages are needed.  ast_debug for when figuring out
+                        * what went wrong and the test update for normal output before
+                        * you start debugging.  The different logging methods are not
+                        * synchronized.
+                        */
+                       ast_debug(1,
+                               "Test '%c' twist row %d col %d amplitudes failed.  Detected Digit: '%c'\n",
+                               dtmf_positions[digit_index],
+                               twist_tests[idx].amp_row, twist_tests[idx].amp_col,
+                               digit ?: ' ');
+                       ast_test_status_update(test,
+                               "Test '%c' twist row %d col %d amplitudes failed.  Detected Digit: '%c'\n",
+                               dtmf_positions[digit_index],
+                               twist_tests[idx].amp_row, twist_tests[idx].amp_col,
+                               digit ?: ' ');
+                       result = -1;
+               }
+               ast_dsp_digitreset(dsp);
+       }
+
+       dtmf_normal_twist = save_normal_twist;
+       dtmf_reverse_twist = save_reverse_twist;
+
+       return result;
+}
+#endif
+
+#ifdef TEST_FRAMEWORK
+static int test_tone_freq_sweep(struct ast_test *test, struct ast_dsp *dsp, tone_detect_state_t *tone_state, short amplitude)
+{
+       short slin_buf[tone_state->block_size];
+       int result;
+       int freq;
+       int lower_freq;
+       int upper_freq;
+
+       /* Calculate detection frequency range */
+       lower_freq = tone_state->freq - 4;
+       upper_freq = tone_state->freq + 4;
+
+       result = 0;
+
+       /* Sweep frequencies loop. */
+       for (freq = 100; freq <= 3500; freq += 1) {
+               int detected;
+               int duration;
+               int expect_detection;
+
+               if (freq == tone_state->freq) {
+                       /* This case is done by the amplitude sweep. */
+                       continue;
+               }
+
+               expect_detection = (lower_freq <= freq && freq <= upper_freq) ? 1 : 0;
+
+               ast_debug(1, "Test %d Hz detection given %d Hz tone at amplitude %d.  Range:%d-%d Expect detect: %s\n",
+                       tone_state->freq, freq, amplitude, lower_freq, upper_freq,
+                       expect_detection ? "yes" : "no");
+               test_tone_sample_gen(slin_buf, tone_state->block_size, DEFAULT_SAMPLE_RATE, freq,
+                       amplitude);
+
+               detected = 0;
+               for (duration = 0; !detected && duration < tone_state->hits_required + 3; ++duration) {
+                       detected = tone_detect(dsp, tone_state, slin_buf, tone_state->block_size) ? 1 : 0;
+               }
+               if (expect_detection != detected) {
+                       /*
+                        * Both messages are needed.  ast_debug for when figuring out
+                        * what went wrong and the test update for normal output before
+                        * you start debugging.  The different logging methods are not
+                        * synchronized.
+                        */
+                       ast_debug(1,
+                               "Test %d Hz detection given %d Hz tone at amplitude %d failed.  Range:%d-%d Detected: %s\n",
+                               tone_state->freq, freq, amplitude, lower_freq, upper_freq,
+                               detected ? "yes" : "no");
+                       ast_test_status_update(test,
+                               "Test %d Hz detection given %d Hz tone at amplitude %d failed.  Range:%d-%d Detected: %s\n",
+                               tone_state->freq, freq, amplitude, lower_freq, upper_freq,
+                               detected ? "yes" : "no");
+                       result = -1;
+               }
+               tone_state->hit_count = 0;
+       }
+
+       return result;
+}
+#endif
+
+#ifdef TEST_FRAMEWORK
+AST_TEST_DEFINE(test_dsp_fax_detect)
+{
+       struct ast_dsp *dsp;
+       enum ast_test_result_state result;
+
+       switch (cmd) {
+       case TEST_INIT:
+               info->name = "fax";
+               info->category = "/main/dsp/";
+               info->summary = "DSP fax tone detect unit test";
+               info->description =
+                       "Tests fax tone detection code.";
+               return AST_TEST_NOT_RUN;
+       case TEST_EXECUTE:
+               break;
+       }
+
+       dsp = ast_dsp_new();
+       if (!dsp) {
+               return AST_TEST_FAIL;
+       }
+
+       result = AST_TEST_PASS;
+
+       /* Test CNG tone amplitude detection */
+       if (test_tone_amplitude_sweep(test, dsp, &dsp->cng_tone_state)) {
+               result = AST_TEST_FAIL;
+       }
+
+       /* Test CED tone amplitude detection */
+       if (test_tone_amplitude_sweep(test, dsp, &dsp->ced_tone_state)) {
+               result = AST_TEST_FAIL;
+       }
+
+       /* Test CNG tone frequency detection */
+       if (test_tone_freq_sweep(test, dsp, &dsp->cng_tone_state, TONE_AMPLITUDE_MAX)) {
+               result = AST_TEST_FAIL;
+       }
+       if (test_tone_freq_sweep(test, dsp, &dsp->cng_tone_state, TONE_AMPLITUDE_MIN)) {
+               result = AST_TEST_FAIL;
+       }
+
+       /* Test CED tone frequency detection */
+       if (test_tone_freq_sweep(test, dsp, &dsp->ced_tone_state, TONE_AMPLITUDE_MAX)) {
+               result = AST_TEST_FAIL;
+       }
+       if (test_tone_freq_sweep(test, dsp, &dsp->ced_tone_state, TONE_AMPLITUDE_MIN)) {
+               result = AST_TEST_FAIL;
+       }
+
+       ast_dsp_free(dsp);
+       return result;
+}
+#endif
+
+#ifdef TEST_FRAMEWORK
+AST_TEST_DEFINE(test_dsp_dtmf_detect)
+{
+       int idx;
+       struct ast_dsp *dsp;
+       enum ast_test_result_state result;
+
+       switch (cmd) {
+       case TEST_INIT:
+               info->name = "dtmf";
+               info->category = "/main/dsp/";
+               info->summary = "DSP DTMF detect unit test";
+               info->description =
+                       "Tests DTMF detection code.";
+               return AST_TEST_NOT_RUN;
+       case TEST_EXECUTE:
+               break;
+       }
+
+       dsp = ast_dsp_new();
+       if (!dsp) {
+               return AST_TEST_FAIL;
+       }
+
+       result = AST_TEST_PASS;
+
+       for (idx = 0; dtmf_positions[idx]; ++idx) {
+               if (test_dtmf_amplitude_sweep(test, dsp, idx)) {
+                       result = AST_TEST_FAIL;
+               }
+       }
+
+       for (idx = 0; dtmf_positions[idx]; ++idx) {
+               if (test_dtmf_twist_sweep(test, dsp, idx)) {
+                       result = AST_TEST_FAIL;
+               }
+       }
+
+       ast_dsp_free(dsp);
+       return result;
+}
+#endif
+
+#ifdef TEST_FRAMEWORK
+static void test_dsp_shutdown(void)
+{
+       AST_TEST_UNREGISTER(test_dsp_fax_detect);
+       AST_TEST_UNREGISTER(test_dsp_dtmf_detect);
+}
+#endif
+
 int ast_dsp_init(void)
 {
-       return _dsp_init(0);
+       int res = _dsp_init(0);
+
+#ifdef TEST_FRAMEWORK
+       if (!res) {
+               AST_TEST_REGISTER(test_dsp_fax_detect);
+               AST_TEST_REGISTER(test_dsp_dtmf_detect);
+
+               ast_register_cleanup(test_dsp_shutdown);
+       }
+#endif
+       return res;
 }
 
 int ast_dsp_reload(void)