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