diff spandsp-0.0.3/spandsp-0.0.3/src/dtmf.c @ 5:f762bf195c4b

import spandsp-0.0.3
author Peter Meerwald <pmeerw@cosy.sbg.ac.at>
date Fri, 25 Jun 2010 16:00:21 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spandsp-0.0.3/spandsp-0.0.3/src/dtmf.c	Fri Jun 25 16:00:21 2010 +0200
@@ -0,0 +1,564 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * dtmf.c - DTMF generation and detection.
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2001-2003, 2005, 2006 Steve Underwood
+ *
+ * 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 version 2, as
+ * published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * $Id: dtmf.c,v 1.11 2006/11/19 14:07:24 steveu Exp $
+ */
+ 
+/*! \file dtmf.h */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <inttypes.h>
+#include <stdlib.h>
+#if defined(HAVE_TGMATH_H)
+#include <tgmath.h>
+#endif
+#if defined(HAVE_MATH_H)
+#include <math.h>
+#endif
+#include <string.h>
+#include <stdio.h>
+#include <time.h>
+#include <fcntl.h>
+
+#include "spandsp/telephony.h"
+#include "spandsp/tone_detect.h"
+#include "spandsp/tone_generate.h"
+#include "spandsp/dtmf.h"
+
+#if !defined(M_PI)
+/* C99 systems may not define M_PI */
+#define M_PI 3.14159265358979323846264338327
+#endif
+
+//#define USE_3DNOW
+
+#define ms_to_samples(t)            (((t)*SAMPLE_RATE)/1000)
+
+#define DTMF_DURATION               ms_to_samples(70)
+#define DTMF_PAUSE                  ms_to_samples(80)
+#define DTMF_CYCLE                  (DTMF_DURATION + DTMF_PAUSE)
+
+#define DTMF_THRESHOLD              8.0e7f
+#define DTMF_NORMAL_TWIST           6.3f    /* 8dB */
+#define DTMF_REVERSE_TWIST          2.5f    /* 4dB */
+#define DTMF_RELATIVE_PEAK_ROW      6.3f    /* 8dB */
+#define DTMF_RELATIVE_PEAK_COL      6.3f    /* 8dB */
+#define DTMF_TO_TOTAL_ENERGY        42.0f
+
+static const float dtmf_row[] =
+{
+     697.0f,  770.0f,  852.0f,  941.0f
+};
+static const float dtmf_col[] =
+{
+    1209.0f, 1336.0f, 1477.0f, 1633.0f
+};
+
+static const char dtmf_positions[] = "123A" "456B" "789C" "*0#D";
+
+static goertzel_descriptor_t dtmf_detect_row[4];
+static goertzel_descriptor_t dtmf_detect_col[4];
+
+static int dtmf_tx_inited = FALSE;
+static tone_gen_descriptor_t dtmf_digit_tones[16];
+
+#if defined(USE_3DNOW)
+static __inline__ void _dtmf_goertzel_update(goertzel_state_t *s,
+                                             float x[],
+                                             int samples)
+{
+    int n;
+    float v;
+    int i;
+    float vv[16];
+
+    vv[4] = s[0].v2;
+    vv[5] = s[1].v2;
+    vv[6] = s[2].v2;
+    vv[7] = s[3].v2;
+    vv[8] = s[0].v3;
+    vv[9] = s[1].v3;
+    vv[10] = s[2].v3;
+    vv[11] = s[3].v3;
+    vv[12] = s[0].fac;
+    vv[13] = s[1].fac;
+    vv[14] = s[2].fac;
+    vv[15] = s[3].fac;
+
+    //v1 = s->v2;
+    //s->v2 = s->v3;
+    //s->v3 = s->fac*s->v2 - v1 + x[0];
+
+    __asm__ __volatile__ (
+             " femms;\n"
+
+             " movq        16(%%edx),%%mm2;\n"
+             " movq        24(%%edx),%%mm3;\n"
+             " movq        32(%%edx),%%mm4;\n"
+             " movq        40(%%edx),%%mm5;\n"
+             " movq        48(%%edx),%%mm6;\n"
+             " movq        56(%%edx),%%mm7;\n"
+
+             " jmp         1f;\n"
+             " .align 32;\n"
+
+             " 1: ;\n"
+             " prefetch    (%%eax);\n"
+             " movq        %%mm3,%%mm1;\n"
+             " movq        %%mm2,%%mm0;\n"
+             " movq        %%mm5,%%mm3;\n"
+             " movq        %%mm4,%%mm2;\n"
+
+             " pfmul       %%mm7,%%mm5;\n"
+             " pfmul       %%mm6,%%mm4;\n"
+             " pfsub       %%mm1,%%mm5;\n"
+             " pfsub       %%mm0,%%mm4;\n"
+
+             " movq        (%%eax),%%mm0;\n"
+             " movq        %%mm0,%%mm1;\n"
+             " punpckldq   %%mm0,%%mm1;\n"
+             " add         $4,%%eax;\n"
+             " pfadd       %%mm1,%%mm5;\n"
+             " pfadd       %%mm1,%%mm4;\n"
+
+             " dec         %%ecx;\n"
+
+             " jnz         1b;\n"
+
+             " movq        %%mm2,16(%%edx);\n"
+             " movq        %%mm3,24(%%edx);\n"
+             " movq        %%mm4,32(%%edx);\n"
+             " movq        %%mm5,40(%%edx);\n"
+
+             " femms;\n"
+             :
+             : "c" (samples), "a" (x), "d" (vv)
+             : "memory", "eax", "ecx");
+
+    s[0].v2 = vv[4];
+    s[1].v2 = vv[5];
+    s[2].v2 = vv[6];
+    s[3].v2 = vv[7];
+    s[0].v3 = vv[8];
+    s[1].v3 = vv[9];
+    s[2].v3 = vv[10];
+    s[3].v3 = vv[11];
+}
+#endif
+/*- End of function --------------------------------------------------------*/
+
+int dtmf_rx(dtmf_rx_state_t *s, const int16_t amp[], int samples)
+{
+    float row_energy[4];
+    float col_energy[4];
+    float famp;
+    float v1;
+    int i;
+    int j;
+    int sample;
+    int best_row;
+    int best_col;
+    int limit;
+    uint8_t hit;
+
+    hit = 0;
+    for (sample = 0;  sample < samples;  sample = limit)
+    {
+        /* The block length is optimised to meet the DTMF specs. */
+        if ((samples - sample) >= (102 - s->current_sample))
+            limit = sample + (102 - s->current_sample);
+        else
+            limit = samples;
+#if defined(USE_3DNOW)
+        _dtmf_goertzel_update(s->row_out, amp + sample, limit - sample);
+        _dtmf_goertzel_update(s->col_out, amp + sample, limit - sample);
+#else
+        /* 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];
+            if (s->filter_dialtone)
+            {
+                /* Sharp notches applied at 350Hz and 440Hz - the two common dialtone frequencies.
+                   These are rather high Q, to achieve the required narrowness, without using lots of
+                   sections. */
+                v1 = 0.98356f*famp + 1.8954426f*s->z350_1 - 0.9691396f*s->z350_2;
+                famp = v1 - 1.9251480f*s->z350_1 + s->z350_2;
+                s->z350_2 = s->z350_1;
+                s->z350_1 = v1;
+
+                v1 = 0.98456f*famp + 1.8529543f*s->z440_1 - 0.9691396f*s->z440_2;
+                famp = v1 - 1.8819938f*s->z440_1 + s->z440_2;
+                s->z440_2 = s->z440_1;
+                s->z440_1 = v1;
+            }
+            s->energy += famp*famp;
+            /* With GCC 2.95, the following unrolled code seems to take about 35%
+               (rough estimate) as long as a neat little 0-3 loop */
+            v1 = s->row_out[0].v2;
+            s->row_out[0].v2 = s->row_out[0].v3;
+            s->row_out[0].v3 = s->row_out[0].fac*s->row_out[0].v2 - v1 + famp;
+    
+            v1 = s->col_out[0].v2;
+            s->col_out[0].v2 = s->col_out[0].v3;
+            s->col_out[0].v3 = s->col_out[0].fac*s->col_out[0].v2 - v1 + famp;
+    
+            v1 = s->row_out[1].v2;
+            s->row_out[1].v2 = s->row_out[1].v3;
+            s->row_out[1].v3 = s->row_out[1].fac*s->row_out[1].v2 - v1 + famp;
+    
+            v1 = s->col_out[1].v2;
+            s->col_out[1].v2 = s->col_out[1].v3;
+            s->col_out[1].v3 = s->col_out[1].fac*s->col_out[1].v2 - v1 + famp;
+    
+            v1 = s->row_out[2].v2;
+            s->row_out[2].v2 = s->row_out[2].v3;
+            s->row_out[2].v3 = s->row_out[2].fac*s->row_out[2].v2 - v1 + famp;
+    
+            v1 = s->col_out[2].v2;
+            s->col_out[2].v2 = s->col_out[2].v3;
+            s->col_out[2].v3 = s->col_out[2].fac*s->col_out[2].v2 - v1 + famp;
+    
+            v1 = s->row_out[3].v2;
+            s->row_out[3].v2 = s->row_out[3].v3;
+            s->row_out[3].v3 = s->row_out[3].fac*s->row_out[3].v2 - v1 + famp;
+
+            v1 = s->col_out[3].v2;
+            s->col_out[3].v2 = s->col_out[3].v3;
+            s->col_out[3].v3 = s->col_out[3].fac*s->col_out[3].v2 - v1 + famp;
+        }
+#endif
+        s->current_sample += (limit - sample);
+        if (s->current_sample < 102)
+            continue;
+
+        /* We are at the end of a DTMF detection block */
+        /* Find the peak row and the peak column */
+        row_energy[0] = goertzel_result(&s->row_out[0]);
+        best_row = 0;
+        col_energy[0] = goertzel_result(&s->col_out[0]);
+        best_col = 0;
+
+        for (i = 1;  i < 4;  i++)
+        {
+            row_energy[i] = goertzel_result(&s->row_out[i]);
+            if (row_energy[i] > row_energy[best_row])
+                best_row = i;
+            col_energy[i] = goertzel_result(&s->col_out[i]);
+            if (col_energy[i] > col_energy[best_col])
+                best_col = i;
+        }
+        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]*s->reverse_twist
+            &&
+            col_energy[best_col]*s->normal_twist > row_energy[best_row])
+        {
+            /* Relative peak test ... */
+            for (i = 0;  i < 4;  i++)
+            {
+                if ((i != best_col  &&  col_energy[i]*DTMF_RELATIVE_PEAK_COL > col_energy[best_col])
+                    ||
+                    (i != best_row  &&  row_energy[i]*DTMF_RELATIVE_PEAK_ROW > row_energy[best_row]))
+                {
+                    break;
+                }
+            }
+            /* ... and fraction of total energy test */
+            if (i >= 4
+                &&
+                (row_energy[best_row] + col_energy[best_col]) > DTMF_TO_TOTAL_ENERGY*s->energy)
+            {
+                hit = dtmf_positions[(best_row << 2) + best_col];
+            }
+        }
+        /* The logic in the next test should ensure the following for different successive hit patterns:
+                -----ABB = start of digit B.
+                ----B-BB = start of digit B
+                ----A-BB = start of digit B
+                BBBBBABB = still in digit B.
+                BBBBBB-- = end of digit B
+                BBBBBBC- = end of digit B
+                BBBBACBB = B ends, then B starts again.
+                BBBBBBCC = B ends, then C starts.
+                BBBBBCDD = B ends, then D starts.
+           This can work with:
+                - Back to back differing digits. Back-to-back digits should
+                  not happen. The spec. says there should be a gap between digits.
+                  However, many real phones do not impose a gap, and rolling across
+                  the keypad can produce little or no gap.
+                - It tolerates nasty phones that give a very wobbly start to a digit.
+                - VoIP can give sample slips. The phase jumps that produces will cause
+                  the block it is in to give no detection. This logic will ride over a
+                  single missed block, and not falsely declare a second digit. If the
+                  hiccup happens in the wrong place on a minimum length digit, however
+                  we would still fail to detect that digit. Could anything be done to
+                  deal with that? Packet loss is clearly a no-go zone.
+                  Note this is only relevant to VoIP using A-law, u-law or similar.
+                  Low bit rate codecs scramble DTMF too much for it to be recognised,
+                  and often slip in units larger than a sample. */
+        if (hit != s->in_digit)
+        {
+            if (s->last_hit != s->in_digit)
+            {
+                /* We have two successive indications that something has changed. */
+                /* To declare digit on, the hits must agree. Otherwise we declare tone off. */
+                hit = (hit  &&  hit == s->last_hit)  ?  hit   :  0;
+                if (s->realtime_callback)
+                {
+                    /* Avoid reporting multiple no digit conditions on flaky hits */
+                    if (s->in_digit  ||  hit)
+                        s->realtime_callback(s->realtime_callback_data, hit);
+                }
+                else
+                {
+                    if (hit)
+                    {
+                        if (s->current_digits < MAX_DTMF_DIGITS)
+                        {
+                            s->digits[s->current_digits++] = (char) hit;
+                            s->digits[s->current_digits] = '\0';
+                            if (s->callback)
+                            {
+                                s->callback(s->callback_data, s->digits, s->current_digits);
+                                s->current_digits = 0;
+                            }
+                        }
+                        else
+                        {
+                            s->lost_digits++;
+                        }
+                    }
+                }
+                s->in_digit = hit;
+            }
+        }
+        s->last_hit = hit;
+        /* Reinitialise the detector for the next block */
+        for (i = 0;  i < 4;  i++)
+        {
+            goertzel_reset(&s->row_out[i]);
+            goertzel_reset(&s->col_out[i]);
+        }
+        s->energy = 0.0;
+        s->current_sample = 0;
+    }
+    if (s->current_digits  &&  s->callback)
+    {
+        s->callback(s->callback_data, s->digits, s->current_digits);
+        s->digits[0] = '\0';
+        s->current_digits = 0;
+    }
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+size_t dtmf_rx_get(dtmf_rx_state_t *s, char *buf, int max)
+{
+    if (max > s->current_digits)
+        max = s->current_digits;
+    if (max > 0)
+    {
+        memcpy(buf, s->digits, max);
+        memmove(s->digits, s->digits + max, s->current_digits - max);
+        s->current_digits -= max;
+    }
+    buf[max] = '\0';
+    return  max;
+}
+/*- End of function --------------------------------------------------------*/
+
+void dtmf_rx_set_realtime_callback(dtmf_rx_state_t *s,
+                                   void (*callback)(void *user_data, int signal),
+                                   void *user_data)
+{
+    s->realtime_callback = callback;
+    s->realtime_callback_data = user_data;
+}
+/*- End of function --------------------------------------------------------*/
+
+void dtmf_rx_parms(dtmf_rx_state_t *s,
+                   int filter_dialtone,
+                   int twist,
+                   int reverse_twist)
+{
+    if (filter_dialtone >= 0)
+    {
+        s->z350_1 = 0.0;
+        s->z350_2 = 0.0;
+        s->z440_1 = 0.0;
+        s->z440_2 = 0.0;
+        s->filter_dialtone = filter_dialtone;
+    }
+    if (twist >= 0)
+        s->normal_twist = powf(10.0, twist/10.0);
+    if (reverse_twist >= 0)
+        s->reverse_twist = powf(10.0, reverse_twist/10.0);
+}
+/*- End of function --------------------------------------------------------*/
+
+dtmf_rx_state_t *dtmf_rx_init(dtmf_rx_state_t *s,
+                              void (*callback)(void *user_data, const char *digits, int len),
+                              void *user_data)
+{
+    int i;
+    static int initialised = FALSE;
+
+    s->callback = callback;
+    s->callback_data = user_data;
+    s->realtime_callback = NULL;
+    s->realtime_callback_data = NULL;
+    s->filter_dialtone = FALSE;
+    s->normal_twist = DTMF_NORMAL_TWIST;
+    s->reverse_twist = DTMF_REVERSE_TWIST;
+
+    s->in_digit = 0;
+    s->last_hit = 0;
+
+    if (!initialised)
+    {
+        for (i = 0;  i < 4;  i++)
+        {
+            make_goertzel_descriptor(&dtmf_detect_row[i], dtmf_row[i], 102);
+            make_goertzel_descriptor(&dtmf_detect_col[i], dtmf_col[i], 102);
+        }
+        initialised = TRUE;
+    }
+    for (i = 0;  i < 4;  i++)
+    {
+        goertzel_init(&s->row_out[i], &dtmf_detect_row[i]);
+        goertzel_init(&s->col_out[i], &dtmf_detect_col[i]);
+    }
+    s->energy = 0.0;
+    s->current_sample = 0;
+    s->lost_digits = 0;
+    s->current_digits = 0;
+    s->digits[0] = '\0';
+    return s;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void dtmf_tx_initialise(void)
+{
+    int row;
+    int col;
+
+    if (dtmf_tx_inited)
+        return;
+    for (row = 0;  row < 4;  row++)
+    {
+    	for (col = 0;  col < 4;  col++)
+        {
+    	    make_tone_gen_descriptor(&dtmf_digit_tones[row*4 + col],
+                                     (int) dtmf_row[row],
+                                     -10,
+                                     (int) dtmf_col[col],
+                                     -10,
+                                     50,
+                                     55,
+                                     0,
+                                     0,
+                                     FALSE);
+        }
+    }
+    dtmf_tx_inited = TRUE;
+}
+/*- End of function --------------------------------------------------------*/
+
+int dtmf_tx(dtmf_tx_state_t *s, int16_t amp[], int max_samples)
+{
+    int len;
+    size_t dig;
+    char *cp;
+
+    len = 0;
+    if (s->tones.current_section >= 0)
+    {
+        /* Deal with the fragment left over from last time */
+        len = tone_gen(&(s->tones), amp, max_samples);
+    }
+    dig = 0;
+    while (dig < s->current_digits  &&  len < max_samples)
+    {
+        /* Step to the next digit */
+        if ((cp = strchr(dtmf_positions, s->digits[dig++])) == NULL)
+            continue;
+        tone_gen_init(&(s->tones), &(s->tone_descriptors[cp - dtmf_positions]));
+        len += tone_gen(&(s->tones), amp + len, max_samples - len);
+    }
+    if (dig)
+    {
+        /* Shift out the consumed digits */
+        s->current_digits -= dig;
+        memmove(s->digits, s->digits + dig, s->current_digits);
+    }
+    return len;
+}
+/*- End of function --------------------------------------------------------*/
+
+size_t dtmf_tx_put(dtmf_tx_state_t *s, const char *digits)
+{
+    size_t len;
+
+    /* This returns the number of characters that would not fit in the buffer.
+       The buffer will only be loaded if the whole string of digits will fit,
+       in which case zero is returned. */
+    if ((len = strlen(digits)) > 0)
+    {
+        if (s->current_digits + len <= MAX_DTMF_DIGITS)
+        {
+            memcpy(s->digits + s->current_digits, digits, len);
+            s->current_digits += len;
+            len = 0;
+        }
+        else
+        {
+            len = MAX_DTMF_DIGITS - s->current_digits;
+        }
+    }
+    return len;
+}
+/*- End of function --------------------------------------------------------*/
+
+dtmf_tx_state_t *dtmf_tx_init(dtmf_tx_state_t *s)
+{
+    if (!dtmf_tx_inited)
+        dtmf_tx_initialise();
+    s->tone_descriptors = dtmf_digit_tones;
+    tone_gen_init(&(s->tones), &dtmf_digit_tones[0]);
+    s->current_sample = 0;
+    s->current_digits = 0;
+    s->tones.current_section = -1;
+    return s;
+}
+/*- End of function --------------------------------------------------------*/
+/*- End of file ------------------------------------------------------------*/

Repositories maintained by Peter Meerwald, pmeerw@pmeerw.net.