Mercurial > hg > audiostuff
view 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 source
/* * 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 ------------------------------------------------------------*/