5
|
1 /*
|
|
2 * SpanDSP - a series of DSP components for telephony
|
|
3 *
|
|
4 * dtmf.c - DTMF generation and detection.
|
|
5 *
|
|
6 * Written by Steve Underwood <steveu@coppice.org>
|
|
7 *
|
|
8 * Copyright (C) 2001-2003, 2005, 2006 Steve Underwood
|
|
9 *
|
|
10 * All rights reserved.
|
|
11 *
|
|
12 * This program is free software; you can redistribute it and/or modify
|
|
13 * it under the terms of the GNU General Public License version 2, as
|
|
14 * published by the Free Software Foundation.
|
|
15 *
|
|
16 * This program is distributed in the hope that it will be useful,
|
|
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
19 * GNU General Public License for more details.
|
|
20 *
|
|
21 * You should have received a copy of the GNU General Public License
|
|
22 * along with this program; if not, write to the Free Software
|
|
23 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
24 *
|
|
25 * $Id: dtmf.c,v 1.11 2006/11/19 14:07:24 steveu Exp $
|
|
26 */
|
|
27
|
|
28 /*! \file dtmf.h */
|
|
29
|
|
30 #ifdef HAVE_CONFIG_H
|
|
31 #include "config.h"
|
|
32 #endif
|
|
33
|
|
34 #include <inttypes.h>
|
|
35 #include <stdlib.h>
|
|
36 #if defined(HAVE_TGMATH_H)
|
|
37 #include <tgmath.h>
|
|
38 #endif
|
|
39 #if defined(HAVE_MATH_H)
|
|
40 #include <math.h>
|
|
41 #endif
|
|
42 #include <string.h>
|
|
43 #include <stdio.h>
|
|
44 #include <time.h>
|
|
45 #include <fcntl.h>
|
|
46
|
|
47 #include "spandsp/telephony.h"
|
|
48 #include "spandsp/tone_detect.h"
|
|
49 #include "spandsp/tone_generate.h"
|
|
50 #include "spandsp/dtmf.h"
|
|
51
|
|
52 #if !defined(M_PI)
|
|
53 /* C99 systems may not define M_PI */
|
|
54 #define M_PI 3.14159265358979323846264338327
|
|
55 #endif
|
|
56
|
|
57 //#define USE_3DNOW
|
|
58
|
|
59 #define ms_to_samples(t) (((t)*SAMPLE_RATE)/1000)
|
|
60
|
|
61 #define DTMF_DURATION ms_to_samples(70)
|
|
62 #define DTMF_PAUSE ms_to_samples(80)
|
|
63 #define DTMF_CYCLE (DTMF_DURATION + DTMF_PAUSE)
|
|
64
|
|
65 #define DTMF_THRESHOLD 8.0e7f
|
|
66 #define DTMF_NORMAL_TWIST 6.3f /* 8dB */
|
|
67 #define DTMF_REVERSE_TWIST 2.5f /* 4dB */
|
|
68 #define DTMF_RELATIVE_PEAK_ROW 6.3f /* 8dB */
|
|
69 #define DTMF_RELATIVE_PEAK_COL 6.3f /* 8dB */
|
|
70 #define DTMF_TO_TOTAL_ENERGY 42.0f
|
|
71
|
|
72 static const float dtmf_row[] =
|
|
73 {
|
|
74 697.0f, 770.0f, 852.0f, 941.0f
|
|
75 };
|
|
76 static const float dtmf_col[] =
|
|
77 {
|
|
78 1209.0f, 1336.0f, 1477.0f, 1633.0f
|
|
79 };
|
|
80
|
|
81 static const char dtmf_positions[] = "123A" "456B" "789C" "*0#D";
|
|
82
|
|
83 static goertzel_descriptor_t dtmf_detect_row[4];
|
|
84 static goertzel_descriptor_t dtmf_detect_col[4];
|
|
85
|
|
86 static int dtmf_tx_inited = FALSE;
|
|
87 static tone_gen_descriptor_t dtmf_digit_tones[16];
|
|
88
|
|
89 #if defined(USE_3DNOW)
|
|
90 static __inline__ void _dtmf_goertzel_update(goertzel_state_t *s,
|
|
91 float x[],
|
|
92 int samples)
|
|
93 {
|
|
94 int n;
|
|
95 float v;
|
|
96 int i;
|
|
97 float vv[16];
|
|
98
|
|
99 vv[4] = s[0].v2;
|
|
100 vv[5] = s[1].v2;
|
|
101 vv[6] = s[2].v2;
|
|
102 vv[7] = s[3].v2;
|
|
103 vv[8] = s[0].v3;
|
|
104 vv[9] = s[1].v3;
|
|
105 vv[10] = s[2].v3;
|
|
106 vv[11] = s[3].v3;
|
|
107 vv[12] = s[0].fac;
|
|
108 vv[13] = s[1].fac;
|
|
109 vv[14] = s[2].fac;
|
|
110 vv[15] = s[3].fac;
|
|
111
|
|
112 //v1 = s->v2;
|
|
113 //s->v2 = s->v3;
|
|
114 //s->v3 = s->fac*s->v2 - v1 + x[0];
|
|
115
|
|
116 __asm__ __volatile__ (
|
|
117 " femms;\n"
|
|
118
|
|
119 " movq 16(%%edx),%%mm2;\n"
|
|
120 " movq 24(%%edx),%%mm3;\n"
|
|
121 " movq 32(%%edx),%%mm4;\n"
|
|
122 " movq 40(%%edx),%%mm5;\n"
|
|
123 " movq 48(%%edx),%%mm6;\n"
|
|
124 " movq 56(%%edx),%%mm7;\n"
|
|
125
|
|
126 " jmp 1f;\n"
|
|
127 " .align 32;\n"
|
|
128
|
|
129 " 1: ;\n"
|
|
130 " prefetch (%%eax);\n"
|
|
131 " movq %%mm3,%%mm1;\n"
|
|
132 " movq %%mm2,%%mm0;\n"
|
|
133 " movq %%mm5,%%mm3;\n"
|
|
134 " movq %%mm4,%%mm2;\n"
|
|
135
|
|
136 " pfmul %%mm7,%%mm5;\n"
|
|
137 " pfmul %%mm6,%%mm4;\n"
|
|
138 " pfsub %%mm1,%%mm5;\n"
|
|
139 " pfsub %%mm0,%%mm4;\n"
|
|
140
|
|
141 " movq (%%eax),%%mm0;\n"
|
|
142 " movq %%mm0,%%mm1;\n"
|
|
143 " punpckldq %%mm0,%%mm1;\n"
|
|
144 " add $4,%%eax;\n"
|
|
145 " pfadd %%mm1,%%mm5;\n"
|
|
146 " pfadd %%mm1,%%mm4;\n"
|
|
147
|
|
148 " dec %%ecx;\n"
|
|
149
|
|
150 " jnz 1b;\n"
|
|
151
|
|
152 " movq %%mm2,16(%%edx);\n"
|
|
153 " movq %%mm3,24(%%edx);\n"
|
|
154 " movq %%mm4,32(%%edx);\n"
|
|
155 " movq %%mm5,40(%%edx);\n"
|
|
156
|
|
157 " femms;\n"
|
|
158 :
|
|
159 : "c" (samples), "a" (x), "d" (vv)
|
|
160 : "memory", "eax", "ecx");
|
|
161
|
|
162 s[0].v2 = vv[4];
|
|
163 s[1].v2 = vv[5];
|
|
164 s[2].v2 = vv[6];
|
|
165 s[3].v2 = vv[7];
|
|
166 s[0].v3 = vv[8];
|
|
167 s[1].v3 = vv[9];
|
|
168 s[2].v3 = vv[10];
|
|
169 s[3].v3 = vv[11];
|
|
170 }
|
|
171 #endif
|
|
172 /*- End of function --------------------------------------------------------*/
|
|
173
|
|
174 int dtmf_rx(dtmf_rx_state_t *s, const int16_t amp[], int samples)
|
|
175 {
|
|
176 float row_energy[4];
|
|
177 float col_energy[4];
|
|
178 float famp;
|
|
179 float v1;
|
|
180 int i;
|
|
181 int j;
|
|
182 int sample;
|
|
183 int best_row;
|
|
184 int best_col;
|
|
185 int limit;
|
|
186 uint8_t hit;
|
|
187
|
|
188 hit = 0;
|
|
189 for (sample = 0; sample < samples; sample = limit)
|
|
190 {
|
|
191 /* The block length is optimised to meet the DTMF specs. */
|
|
192 if ((samples - sample) >= (102 - s->current_sample))
|
|
193 limit = sample + (102 - s->current_sample);
|
|
194 else
|
|
195 limit = samples;
|
|
196 #if defined(USE_3DNOW)
|
|
197 _dtmf_goertzel_update(s->row_out, amp + sample, limit - sample);
|
|
198 _dtmf_goertzel_update(s->col_out, amp + sample, limit - sample);
|
|
199 #else
|
|
200 /* The following unrolled loop takes only 35% (rough estimate) of the
|
|
201 time of a rolled loop on the machine on which it was developed */
|
|
202 for (j = sample; j < limit; j++)
|
|
203 {
|
|
204 famp = amp[j];
|
|
205 if (s->filter_dialtone)
|
|
206 {
|
|
207 /* Sharp notches applied at 350Hz and 440Hz - the two common dialtone frequencies.
|
|
208 These are rather high Q, to achieve the required narrowness, without using lots of
|
|
209 sections. */
|
|
210 v1 = 0.98356f*famp + 1.8954426f*s->z350_1 - 0.9691396f*s->z350_2;
|
|
211 famp = v1 - 1.9251480f*s->z350_1 + s->z350_2;
|
|
212 s->z350_2 = s->z350_1;
|
|
213 s->z350_1 = v1;
|
|
214
|
|
215 v1 = 0.98456f*famp + 1.8529543f*s->z440_1 - 0.9691396f*s->z440_2;
|
|
216 famp = v1 - 1.8819938f*s->z440_1 + s->z440_2;
|
|
217 s->z440_2 = s->z440_1;
|
|
218 s->z440_1 = v1;
|
|
219 }
|
|
220 s->energy += famp*famp;
|
|
221 /* With GCC 2.95, the following unrolled code seems to take about 35%
|
|
222 (rough estimate) as long as a neat little 0-3 loop */
|
|
223 v1 = s->row_out[0].v2;
|
|
224 s->row_out[0].v2 = s->row_out[0].v3;
|
|
225 s->row_out[0].v3 = s->row_out[0].fac*s->row_out[0].v2 - v1 + famp;
|
|
226
|
|
227 v1 = s->col_out[0].v2;
|
|
228 s->col_out[0].v2 = s->col_out[0].v3;
|
|
229 s->col_out[0].v3 = s->col_out[0].fac*s->col_out[0].v2 - v1 + famp;
|
|
230
|
|
231 v1 = s->row_out[1].v2;
|
|
232 s->row_out[1].v2 = s->row_out[1].v3;
|
|
233 s->row_out[1].v3 = s->row_out[1].fac*s->row_out[1].v2 - v1 + famp;
|
|
234
|
|
235 v1 = s->col_out[1].v2;
|
|
236 s->col_out[1].v2 = s->col_out[1].v3;
|
|
237 s->col_out[1].v3 = s->col_out[1].fac*s->col_out[1].v2 - v1 + famp;
|
|
238
|
|
239 v1 = s->row_out[2].v2;
|
|
240 s->row_out[2].v2 = s->row_out[2].v3;
|
|
241 s->row_out[2].v3 = s->row_out[2].fac*s->row_out[2].v2 - v1 + famp;
|
|
242
|
|
243 v1 = s->col_out[2].v2;
|
|
244 s->col_out[2].v2 = s->col_out[2].v3;
|
|
245 s->col_out[2].v3 = s->col_out[2].fac*s->col_out[2].v2 - v1 + famp;
|
|
246
|
|
247 v1 = s->row_out[3].v2;
|
|
248 s->row_out[3].v2 = s->row_out[3].v3;
|
|
249 s->row_out[3].v3 = s->row_out[3].fac*s->row_out[3].v2 - v1 + famp;
|
|
250
|
|
251 v1 = s->col_out[3].v2;
|
|
252 s->col_out[3].v2 = s->col_out[3].v3;
|
|
253 s->col_out[3].v3 = s->col_out[3].fac*s->col_out[3].v2 - v1 + famp;
|
|
254 }
|
|
255 #endif
|
|
256 s->current_sample += (limit - sample);
|
|
257 if (s->current_sample < 102)
|
|
258 continue;
|
|
259
|
|
260 /* We are at the end of a DTMF detection block */
|
|
261 /* Find the peak row and the peak column */
|
|
262 row_energy[0] = goertzel_result(&s->row_out[0]);
|
|
263 best_row = 0;
|
|
264 col_energy[0] = goertzel_result(&s->col_out[0]);
|
|
265 best_col = 0;
|
|
266
|
|
267 for (i = 1; i < 4; i++)
|
|
268 {
|
|
269 row_energy[i] = goertzel_result(&s->row_out[i]);
|
|
270 if (row_energy[i] > row_energy[best_row])
|
|
271 best_row = i;
|
|
272 col_energy[i] = goertzel_result(&s->col_out[i]);
|
|
273 if (col_energy[i] > col_energy[best_col])
|
|
274 best_col = i;
|
|
275 }
|
|
276 hit = 0;
|
|
277 /* Basic signal level test and the twist test */
|
|
278 if (row_energy[best_row] >= DTMF_THRESHOLD
|
|
279 &&
|
|
280 col_energy[best_col] >= DTMF_THRESHOLD
|
|
281 &&
|
|
282 col_energy[best_col] < row_energy[best_row]*s->reverse_twist
|
|
283 &&
|
|
284 col_energy[best_col]*s->normal_twist > row_energy[best_row])
|
|
285 {
|
|
286 /* Relative peak test ... */
|
|
287 for (i = 0; i < 4; i++)
|
|
288 {
|
|
289 if ((i != best_col && col_energy[i]*DTMF_RELATIVE_PEAK_COL > col_energy[best_col])
|
|
290 ||
|
|
291 (i != best_row && row_energy[i]*DTMF_RELATIVE_PEAK_ROW > row_energy[best_row]))
|
|
292 {
|
|
293 break;
|
|
294 }
|
|
295 }
|
|
296 /* ... and fraction of total energy test */
|
|
297 if (i >= 4
|
|
298 &&
|
|
299 (row_energy[best_row] + col_energy[best_col]) > DTMF_TO_TOTAL_ENERGY*s->energy)
|
|
300 {
|
|
301 hit = dtmf_positions[(best_row << 2) + best_col];
|
|
302 }
|
|
303 }
|
|
304 /* The logic in the next test should ensure the following for different successive hit patterns:
|
|
305 -----ABB = start of digit B.
|
|
306 ----B-BB = start of digit B
|
|
307 ----A-BB = start of digit B
|
|
308 BBBBBABB = still in digit B.
|
|
309 BBBBBB-- = end of digit B
|
|
310 BBBBBBC- = end of digit B
|
|
311 BBBBACBB = B ends, then B starts again.
|
|
312 BBBBBBCC = B ends, then C starts.
|
|
313 BBBBBCDD = B ends, then D starts.
|
|
314 This can work with:
|
|
315 - Back to back differing digits. Back-to-back digits should
|
|
316 not happen. The spec. says there should be a gap between digits.
|
|
317 However, many real phones do not impose a gap, and rolling across
|
|
318 the keypad can produce little or no gap.
|
|
319 - It tolerates nasty phones that give a very wobbly start to a digit.
|
|
320 - VoIP can give sample slips. The phase jumps that produces will cause
|
|
321 the block it is in to give no detection. This logic will ride over a
|
|
322 single missed block, and not falsely declare a second digit. If the
|
|
323 hiccup happens in the wrong place on a minimum length digit, however
|
|
324 we would still fail to detect that digit. Could anything be done to
|
|
325 deal with that? Packet loss is clearly a no-go zone.
|
|
326 Note this is only relevant to VoIP using A-law, u-law or similar.
|
|
327 Low bit rate codecs scramble DTMF too much for it to be recognised,
|
|
328 and often slip in units larger than a sample. */
|
|
329 if (hit != s->in_digit)
|
|
330 {
|
|
331 if (s->last_hit != s->in_digit)
|
|
332 {
|
|
333 /* We have two successive indications that something has changed. */
|
|
334 /* To declare digit on, the hits must agree. Otherwise we declare tone off. */
|
|
335 hit = (hit && hit == s->last_hit) ? hit : 0;
|
|
336 if (s->realtime_callback)
|
|
337 {
|
|
338 /* Avoid reporting multiple no digit conditions on flaky hits */
|
|
339 if (s->in_digit || hit)
|
|
340 s->realtime_callback(s->realtime_callback_data, hit);
|
|
341 }
|
|
342 else
|
|
343 {
|
|
344 if (hit)
|
|
345 {
|
|
346 if (s->current_digits < MAX_DTMF_DIGITS)
|
|
347 {
|
|
348 s->digits[s->current_digits++] = (char) hit;
|
|
349 s->digits[s->current_digits] = '\0';
|
|
350 if (s->callback)
|
|
351 {
|
|
352 s->callback(s->callback_data, s->digits, s->current_digits);
|
|
353 s->current_digits = 0;
|
|
354 }
|
|
355 }
|
|
356 else
|
|
357 {
|
|
358 s->lost_digits++;
|
|
359 }
|
|
360 }
|
|
361 }
|
|
362 s->in_digit = hit;
|
|
363 }
|
|
364 }
|
|
365 s->last_hit = hit;
|
|
366 /* Reinitialise the detector for the next block */
|
|
367 for (i = 0; i < 4; i++)
|
|
368 {
|
|
369 goertzel_reset(&s->row_out[i]);
|
|
370 goertzel_reset(&s->col_out[i]);
|
|
371 }
|
|
372 s->energy = 0.0;
|
|
373 s->current_sample = 0;
|
|
374 }
|
|
375 if (s->current_digits && s->callback)
|
|
376 {
|
|
377 s->callback(s->callback_data, s->digits, s->current_digits);
|
|
378 s->digits[0] = '\0';
|
|
379 s->current_digits = 0;
|
|
380 }
|
|
381 return 0;
|
|
382 }
|
|
383 /*- End of function --------------------------------------------------------*/
|
|
384
|
|
385 size_t dtmf_rx_get(dtmf_rx_state_t *s, char *buf, int max)
|
|
386 {
|
|
387 if (max > s->current_digits)
|
|
388 max = s->current_digits;
|
|
389 if (max > 0)
|
|
390 {
|
|
391 memcpy(buf, s->digits, max);
|
|
392 memmove(s->digits, s->digits + max, s->current_digits - max);
|
|
393 s->current_digits -= max;
|
|
394 }
|
|
395 buf[max] = '\0';
|
|
396 return max;
|
|
397 }
|
|
398 /*- End of function --------------------------------------------------------*/
|
|
399
|
|
400 void dtmf_rx_set_realtime_callback(dtmf_rx_state_t *s,
|
|
401 void (*callback)(void *user_data, int signal),
|
|
402 void *user_data)
|
|
403 {
|
|
404 s->realtime_callback = callback;
|
|
405 s->realtime_callback_data = user_data;
|
|
406 }
|
|
407 /*- End of function --------------------------------------------------------*/
|
|
408
|
|
409 void dtmf_rx_parms(dtmf_rx_state_t *s,
|
|
410 int filter_dialtone,
|
|
411 int twist,
|
|
412 int reverse_twist)
|
|
413 {
|
|
414 if (filter_dialtone >= 0)
|
|
415 {
|
|
416 s->z350_1 = 0.0;
|
|
417 s->z350_2 = 0.0;
|
|
418 s->z440_1 = 0.0;
|
|
419 s->z440_2 = 0.0;
|
|
420 s->filter_dialtone = filter_dialtone;
|
|
421 }
|
|
422 if (twist >= 0)
|
|
423 s->normal_twist = powf(10.0, twist/10.0);
|
|
424 if (reverse_twist >= 0)
|
|
425 s->reverse_twist = powf(10.0, reverse_twist/10.0);
|
|
426 }
|
|
427 /*- End of function --------------------------------------------------------*/
|
|
428
|
|
429 dtmf_rx_state_t *dtmf_rx_init(dtmf_rx_state_t *s,
|
|
430 void (*callback)(void *user_data, const char *digits, int len),
|
|
431 void *user_data)
|
|
432 {
|
|
433 int i;
|
|
434 static int initialised = FALSE;
|
|
435
|
|
436 s->callback = callback;
|
|
437 s->callback_data = user_data;
|
|
438 s->realtime_callback = NULL;
|
|
439 s->realtime_callback_data = NULL;
|
|
440 s->filter_dialtone = FALSE;
|
|
441 s->normal_twist = DTMF_NORMAL_TWIST;
|
|
442 s->reverse_twist = DTMF_REVERSE_TWIST;
|
|
443
|
|
444 s->in_digit = 0;
|
|
445 s->last_hit = 0;
|
|
446
|
|
447 if (!initialised)
|
|
448 {
|
|
449 for (i = 0; i < 4; i++)
|
|
450 {
|
|
451 make_goertzel_descriptor(&dtmf_detect_row[i], dtmf_row[i], 102);
|
|
452 make_goertzel_descriptor(&dtmf_detect_col[i], dtmf_col[i], 102);
|
|
453 }
|
|
454 initialised = TRUE;
|
|
455 }
|
|
456 for (i = 0; i < 4; i++)
|
|
457 {
|
|
458 goertzel_init(&s->row_out[i], &dtmf_detect_row[i]);
|
|
459 goertzel_init(&s->col_out[i], &dtmf_detect_col[i]);
|
|
460 }
|
|
461 s->energy = 0.0;
|
|
462 s->current_sample = 0;
|
|
463 s->lost_digits = 0;
|
|
464 s->current_digits = 0;
|
|
465 s->digits[0] = '\0';
|
|
466 return s;
|
|
467 }
|
|
468 /*- End of function --------------------------------------------------------*/
|
|
469
|
|
470 static void dtmf_tx_initialise(void)
|
|
471 {
|
|
472 int row;
|
|
473 int col;
|
|
474
|
|
475 if (dtmf_tx_inited)
|
|
476 return;
|
|
477 for (row = 0; row < 4; row++)
|
|
478 {
|
|
479 for (col = 0; col < 4; col++)
|
|
480 {
|
|
481 make_tone_gen_descriptor(&dtmf_digit_tones[row*4 + col],
|
|
482 (int) dtmf_row[row],
|
|
483 -10,
|
|
484 (int) dtmf_col[col],
|
|
485 -10,
|
|
486 50,
|
|
487 55,
|
|
488 0,
|
|
489 0,
|
|
490 FALSE);
|
|
491 }
|
|
492 }
|
|
493 dtmf_tx_inited = TRUE;
|
|
494 }
|
|
495 /*- End of function --------------------------------------------------------*/
|
|
496
|
|
497 int dtmf_tx(dtmf_tx_state_t *s, int16_t amp[], int max_samples)
|
|
498 {
|
|
499 int len;
|
|
500 size_t dig;
|
|
501 char *cp;
|
|
502
|
|
503 len = 0;
|
|
504 if (s->tones.current_section >= 0)
|
|
505 {
|
|
506 /* Deal with the fragment left over from last time */
|
|
507 len = tone_gen(&(s->tones), amp, max_samples);
|
|
508 }
|
|
509 dig = 0;
|
|
510 while (dig < s->current_digits && len < max_samples)
|
|
511 {
|
|
512 /* Step to the next digit */
|
|
513 if ((cp = strchr(dtmf_positions, s->digits[dig++])) == NULL)
|
|
514 continue;
|
|
515 tone_gen_init(&(s->tones), &(s->tone_descriptors[cp - dtmf_positions]));
|
|
516 len += tone_gen(&(s->tones), amp + len, max_samples - len);
|
|
517 }
|
|
518 if (dig)
|
|
519 {
|
|
520 /* Shift out the consumed digits */
|
|
521 s->current_digits -= dig;
|
|
522 memmove(s->digits, s->digits + dig, s->current_digits);
|
|
523 }
|
|
524 return len;
|
|
525 }
|
|
526 /*- End of function --------------------------------------------------------*/
|
|
527
|
|
528 size_t dtmf_tx_put(dtmf_tx_state_t *s, const char *digits)
|
|
529 {
|
|
530 size_t len;
|
|
531
|
|
532 /* This returns the number of characters that would not fit in the buffer.
|
|
533 The buffer will only be loaded if the whole string of digits will fit,
|
|
534 in which case zero is returned. */
|
|
535 if ((len = strlen(digits)) > 0)
|
|
536 {
|
|
537 if (s->current_digits + len <= MAX_DTMF_DIGITS)
|
|
538 {
|
|
539 memcpy(s->digits + s->current_digits, digits, len);
|
|
540 s->current_digits += len;
|
|
541 len = 0;
|
|
542 }
|
|
543 else
|
|
544 {
|
|
545 len = MAX_DTMF_DIGITS - s->current_digits;
|
|
546 }
|
|
547 }
|
|
548 return len;
|
|
549 }
|
|
550 /*- End of function --------------------------------------------------------*/
|
|
551
|
|
552 dtmf_tx_state_t *dtmf_tx_init(dtmf_tx_state_t *s)
|
|
553 {
|
|
554 if (!dtmf_tx_inited)
|
|
555 dtmf_tx_initialise();
|
|
556 s->tone_descriptors = dtmf_digit_tones;
|
|
557 tone_gen_init(&(s->tones), &dtmf_digit_tones[0]);
|
|
558 s->current_sample = 0;
|
|
559 s->current_digits = 0;
|
|
560 s->tones.current_section = -1;
|
|
561 return s;
|
|
562 }
|
|
563 /*- End of function --------------------------------------------------------*/
|
|
564 /*- End of file ------------------------------------------------------------*/
|