diff spandsp-0.0.6pre17/src/dtmf.c @ 4:26cd8f1ef0b1

import spandsp-0.0.6pre17
author Peter Meerwald <pmeerw@cosy.sbg.ac.at>
date Fri, 25 Jun 2010 15:50:58 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spandsp-0.0.6pre17/src/dtmf.c	Fri Jun 25 15:50:58 2010 +0200
@@ -0,0 +1,547 @@
+/*
+ * 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 Lesser General Public License version 2.1,
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.53 2009/04/12 09:12:10 steveu Exp $
+ */
+ 
+/*! \file */
+
+#if defined(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 "floating_fudge.h"
+#include <string.h>
+#include <stdio.h>
+#include <time.h>
+#include <fcntl.h>
+
+#include "spandsp/telephony.h"
+#include "spandsp/fast_convert.h"
+#include "spandsp/queue.h"
+#include "spandsp/complex.h"
+#include "spandsp/dds.h"
+#include "spandsp/tone_detect.h"
+#include "spandsp/tone_generate.h"
+#include "spandsp/super_tone_rx.h"
+#include "spandsp/dtmf.h"
+
+#include "spandsp/private/queue.h"
+#include "spandsp/private/tone_generate.h"
+#include "spandsp/private/dtmf.h"
+
+#define DEFAULT_DTMF_TX_LEVEL       -10
+#define DEFAULT_DTMF_TX_ON_TIME     50
+#define DEFAULT_DTMF_TX_OFF_TIME    55
+
+#if defined(SPANDSP_USE_FIXED_POINT)
+#define DTMF_THRESHOLD              10438           /* -42dBm0 */
+#define DTMF_NORMAL_TWIST           6.309f          /* 8dB */
+#define DTMF_REVERSE_TWIST          2.512f          /* 4dB */
+#define DTMF_RELATIVE_PEAK_ROW      6.309f          /* 8dB */
+#define DTMF_RELATIVE_PEAK_COL      6.309f          /* 8dB */
+#define DTMF_TO_TOTAL_ENERGY        83.868f         /* -0.85dB */
+#define DTMF_POWER_OFFSET           68.251f         /* 10*log(256.0*256.0*DTMF_SAMPLES_PER_BLOCK) */
+#define DTMF_SAMPLES_PER_BLOCK      102
+#else
+#define DTMF_THRESHOLD              171032462.0f    /* -42dBm0 [((DTMF_SAMPLES_PER_BLOCK*32768.0/1.4142)*10^((-42 - DBM0_MAX_SINE_POWER)/20.0))^2 => 171032462.0] */
+#define DTMF_NORMAL_TWIST           6.309f          /* 8dB [10^(8/10) => 6.309] */
+#define DTMF_REVERSE_TWIST          2.512f          /* 4dB */
+#define DTMF_RELATIVE_PEAK_ROW      6.309f          /* 8dB */
+#define DTMF_RELATIVE_PEAK_COL      6.309f          /* 8dB */
+#define DTMF_TO_TOTAL_ENERGY        83.868f         /* -0.85dB [DTMF_SAMPLES_PER_BLOCK*10^(-0.85/10.0)] */
+#define DTMF_POWER_OFFSET           110.395f        /* 10*log(32768.0*32768.0*DTMF_SAMPLES_PER_BLOCK) */
+#define DTMF_SAMPLES_PER_BLOCK      102
+#endif
+
+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];
+
+SPAN_DECLARE(int) dtmf_rx(dtmf_rx_state_t *s, const int16_t amp[], int samples)
+{
+#if defined(SPANDSP_USE_FIXED_POINT)
+    int32_t row_energy[4];
+    int32_t col_energy[4];
+    int16_t xamp;
+    float famp;
+#else
+    float row_energy[4];
+    float col_energy[4];
+    float xamp;
+    float famp;
+#endif
+    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) >= (DTMF_SAMPLES_PER_BLOCK - s->current_sample))
+            limit = sample + (DTMF_SAMPLES_PER_BLOCK - s->current_sample);
+        else
+            limit = samples;
+        /* 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++)
+        {
+            xamp = amp[j];
+            if (s->filter_dialtone)
+            {
+                famp = xamp;
+                /* 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[0] - 0.9691396f*s->z350[1];
+                famp = v1 - 1.9251480f*s->z350[0] + s->z350[1];
+                s->z350[1] = s->z350[0];
+                s->z350[0] = v1;
+
+                v1 = 0.98456f*famp + 1.8529543f*s->z440[0] - 0.9691396f*s->z440[1];
+                famp = v1 - 1.8819938f*s->z440[0] + s->z440[1];
+                s->z440[1] = s->z440[0];
+                s->z440[0] = v1;
+                xamp = famp;
+            }
+            xamp = goertzel_preadjust_amp(xamp);
+#if defined(SPANDSP_USE_FIXED_POINT)
+            s->energy += ((int32_t) xamp*xamp);
+#else
+            s->energy += xamp*xamp;
+#endif
+            goertzel_samplex(&s->row_out[0], xamp);
+            goertzel_samplex(&s->col_out[0], xamp);
+            goertzel_samplex(&s->row_out[1], xamp);
+            goertzel_samplex(&s->col_out[1], xamp);
+            goertzel_samplex(&s->row_out[2], xamp);
+            goertzel_samplex(&s->col_out[2], xamp);
+            goertzel_samplex(&s->row_out[3], xamp);
+            goertzel_samplex(&s->col_out[3], xamp);
+        }
+        s->current_sample += (limit - sample);
+        if (s->current_sample < DTMF_SAMPLES_PER_BLOCK)
+            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] >= s->threshold
+            &&
+            col_energy[best_col] >= s->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)
+            {
+                /* Got a hit */
+                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)
+                    {
+                        i = (s->in_digit  &&  !hit)  ?  -99  :  lfastrintf(log10f(s->energy)*10.0f - DTMF_POWER_OFFSET + DBM0_MAX_POWER);
+                        s->realtime_callback(s->realtime_callback_data, hit, i, 0);
+                    }
+                }
+                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->digits_callback)
+                            {
+                                s->digits_callback(s->digits_callback_data, s->digits, s->current_digits);
+                                s->current_digits = 0;
+                            }
+                        }
+                        else
+                        {
+                            s->lost_digits++;
+                        }
+                    }
+                }
+                s->in_digit = hit;
+            }
+        }
+        s->last_hit = hit;
+#if defined(SPANDSP_USE_FIXED_POINT)
+        s->energy = 0;
+#else
+        s->energy = 0.0f;
+#endif
+        s->current_sample = 0;
+    }
+    if (s->current_digits  &&  s->digits_callback)
+    {
+        s->digits_callback(s->digits_callback_data, s->digits, s->current_digits);
+        s->digits[0] = '\0';
+        s->current_digits = 0;
+    }
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) dtmf_rx_status(dtmf_rx_state_t *s)
+{
+    if (s->in_digit)
+        return s->in_digit;
+    if (s->last_hit)
+        return 'x';
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(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 --------------------------------------------------------*/
+
+SPAN_DECLARE(void) dtmf_rx_set_realtime_callback(dtmf_rx_state_t *s,
+                                                 tone_report_func_t callback,
+                                                 void *user_data)
+{
+    s->realtime_callback = callback;
+    s->realtime_callback_data = user_data;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) dtmf_rx_parms(dtmf_rx_state_t *s,
+                                 int filter_dialtone,
+                                 int twist,
+                                 int reverse_twist,
+                                 int threshold)
+{
+    float x;
+
+    if (filter_dialtone >= 0)
+    {
+        s->z350[0] = 0.0f;
+        s->z350[1] = 0.0f;
+        s->z440[0] = 0.0f;
+        s->z440[1] = 0.0f;
+        s->filter_dialtone = filter_dialtone;
+    }
+    if (twist >= 0)
+        s->normal_twist = powf(10.0f, twist/10.0f);
+    if (reverse_twist >= 0)
+        s->reverse_twist = powf(10.0f, reverse_twist/10.0f);
+    if (threshold > -99)
+    {
+        x = (DTMF_SAMPLES_PER_BLOCK*32768.0f/1.4142f)*powf(10.0f, (threshold - DBM0_MAX_SINE_POWER)/20.0f);
+        s->threshold = x*x;
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(dtmf_rx_state_t *) dtmf_rx_init(dtmf_rx_state_t *s,
+                                             digits_rx_callback_t callback,
+                                             void *user_data)
+{
+    int i;
+    static int initialised = FALSE;
+
+    if (s == NULL)
+    {
+        if ((s = (dtmf_rx_state_t *) malloc(sizeof (*s))) == NULL)
+            return  NULL;
+    }
+    s->digits_callback = callback;
+    s->digits_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->threshold = DTMF_THRESHOLD;
+
+    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], DTMF_SAMPLES_PER_BLOCK);
+            make_goertzel_descriptor(&dtmf_detect_col[i], dtmf_col[i], DTMF_SAMPLES_PER_BLOCK);
+        }
+        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]);
+    }
+#if defined(SPANDSP_USE_FIXED_POINT)
+    s->energy = 0;
+#else
+    s->energy = 0.0f;
+#endif
+    s->current_sample = 0;
+    s->lost_digits = 0;
+    s->current_digits = 0;
+    s->digits[0] = '\0';
+    return s;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) dtmf_rx_release(dtmf_rx_state_t *s)
+{
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) dtmf_rx_free(dtmf_rx_state_t *s)
+{
+    free(s);
+    return 0;
+}
+/*- 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],
+                                     DEFAULT_DTMF_TX_LEVEL,
+                                     (int) dtmf_col[col],
+                                     DEFAULT_DTMF_TX_LEVEL,
+                                     DEFAULT_DTMF_TX_ON_TIME,
+                                     DEFAULT_DTMF_TX_OFF_TIME,
+                                     0,
+                                     0,
+                                     FALSE);
+        }
+    }
+    dtmf_tx_inited = TRUE;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) dtmf_tx(dtmf_tx_state_t *s, int16_t amp[], int max_samples)
+{
+    int len;
+    const char *cp;
+    int digit;
+
+    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);
+    }
+    while (len < max_samples  &&  (digit = queue_read_byte(&s->queue.queue)) >= 0)
+    {
+        /* Step to the next digit */
+        if (digit == 0)
+            continue;
+        if ((cp = strchr(dtmf_positions, digit)) == NULL)
+            continue;
+        tone_gen_init(&(s->tones), &dtmf_digit_tones[cp - dtmf_positions]);
+        s->tones.tone[0].gain = s->low_level;
+        s->tones.tone[1].gain = s->high_level;
+        s->tones.duration[0] = s->on_time;
+        s->tones.duration[1] = s->off_time;
+        len += tone_gen(&(s->tones), amp + len, max_samples - len);
+    }
+    return len;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) dtmf_tx_put(dtmf_tx_state_t *s, const char *digits, int len)
+{
+    size_t space;
+
+    /* 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 < 0)
+    {
+        if ((len = strlen(digits)) == 0)
+            return 0;
+    }
+    if ((space = queue_free_space(&s->queue.queue)) < (size_t) len)
+        return len - (int) space;
+    if (queue_write(&s->queue.queue, (const uint8_t *) digits, len) >= 0)
+        return 0;
+    return -1;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) dtmf_tx_set_level(dtmf_tx_state_t *s, int level, int twist)
+{
+    s->low_level = dds_scaling_dbm0f((float) level);
+    s->high_level = dds_scaling_dbm0f((float) (level + twist));
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) dtmf_tx_set_timing(dtmf_tx_state_t *s, int on_time, int off_time)
+{
+    s->on_time = ((on_time >= 0)  ?  on_time  :  DEFAULT_DTMF_TX_ON_TIME)*SAMPLE_RATE/1000;
+    s->off_time = ((off_time >= 0)  ?  off_time  :  DEFAULT_DTMF_TX_OFF_TIME)*SAMPLE_RATE/1000;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(dtmf_tx_state_t *) dtmf_tx_init(dtmf_tx_state_t *s)
+{
+    if (s == NULL)
+    {
+        if ((s = (dtmf_tx_state_t *) malloc(sizeof (*s))) == NULL)
+            return  NULL;
+    }
+    if (!dtmf_tx_inited)
+        dtmf_tx_initialise();
+    tone_gen_init(&(s->tones), &dtmf_digit_tones[0]);
+    dtmf_tx_set_level(s, DEFAULT_DTMF_TX_LEVEL, 0);
+    dtmf_tx_set_timing(s, -1, -1);
+    queue_init(&s->queue.queue, MAX_DTMF_DIGITS, QUEUE_READ_ATOMIC | QUEUE_WRITE_ATOMIC);
+    s->tones.current_section = -1;
+    return s;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) dtmf_tx_release(dtmf_tx_state_t *s)
+{
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) dtmf_tx_free(dtmf_tx_state_t *s)
+{
+    free(s);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+/*- End of file ------------------------------------------------------------*/

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