dtmfdetect: Add DTMF tone detector

It looks at raw audio data and emits messages when DTMF is detected.
The dtmf detector is the same Goertzel implementation used in FreeSwitch
and Asterisk. It is in the public domain.
This commit is contained in:
Olivier Crête 2009-11-04 22:19:58 -05:00
parent f01e6c6f89
commit 12837a2611
6 changed files with 993 additions and 2 deletions

View file

@ -1,14 +1,18 @@
plugin_LTLIBRARIES = libgstdtmf.la
libgstdtmf_la_SOURCES = gstdtmfsrc.c \
gstdtmfdetect.c \
gstrtpdtmfsrc.c \
gstrtpdtmfdepay.c \
tone_detect.c \
gstdtmf.c
noinst_HEADERS = gstdtmfsrc.h \
gstdtmfdetect.h \
gstrtpdtmfsrc.h \
gstrtpdtmfdepay.h \
gstrtpdtmfcommon.h
gstrtpdtmfcommon.h \
tone_detect.h
libgstdtmf_la_CFLAGS = $(GST_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) $(ERROR_CFLAGS) -DEXTERN_BUF -DRTP_SUPPORT
libgstdtmf_la_LIBADD = $(GST_LIBS_LIBS) -lm

View file

@ -3,6 +3,7 @@
#include "config.h"
#endif
#include "gstdtmfdetect.h"
#include "gstdtmfsrc.h"
#include "gstrtpdtmfsrc.h"
#include "gstrtpdtmfdepay.h"
@ -11,13 +12,15 @@
static gboolean
plugin_init (GstPlugin * plugin)
{
if (!gst_dtmf_detect_plugin_init (plugin))
return FALSE;
if (!gst_dtmf_src_plugin_init (plugin))
return FALSE;
if (!gst_rtp_dtmf_src_plugin_init (plugin))
return FALSE;
if (!gst_rtp_dtmf_depay_plugin_init (plugin))
return FALSE;

308
gst/dtmf/gstdtmfdetect.c Normal file
View file

@ -0,0 +1,308 @@
/*
* GStreamer - DTMF Detection
*
* Copyright 2009 Nokia Corporation
* Copyright 2009 Collabora Ltd,
* @author: Olivier Crete <olivier.crete@collabora.co.uk>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
*/
/**
* SECTION:element-dtmfdetect
* @short_description: Detects DTMF tones
*
* This element will detect DTMF tones and emit messages
*
* The message is called "dtmf-event" and has the following fields
* <informaltable>
* <tgroup cols='4'>
* <colspec colname='Name' />
* <colspec colname='Type' />
* <colspec colname='Possible values' />
* <colspec colname='Purpose' />
* <thead>
* <row>
* <entry>Name</entry>
* <entry>GType</entry>
* <entry>Possible values</entry>
* <entry>Purpose</entry>
* </row>
* </thead>
* <tbody>
* <row>
* <entry>type</entry>
* <entry>G_TYPE_INT</entry>
* <entry>0-1</entry>
* <entry>The application uses this field to specify which of the two methods
* specified in RFC 2833 to use. The value should be 0 for tones and 1 for
* named events. Tones are specified by their frequencies and events are specied
* by their number. This element can only take events as input. Do not confuse
* with "method" which specified the output.
* </entry>
* </row>
* <row>
* <entry>number</entry>
* <entry>G_TYPE_INT</entry>
* <entry>0-16</entry>
* <entry>The event number.</entry>
* </row>
* <row>
* <entry>method</entry>
* <entry>G_TYPE_INT</entry>
* <entry>2</entry>
* <entry>This field will always been 2 (ie sound) from this element.
* </entry>
* </row>
* </tbody>
* </tgroup>
* </informaltable>
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstdtmfdetect.h"
#include <string.h>
GST_DEBUG_CATEGORY (dtmf_detect_debug);
#define GST_CAT_DEFAULT (dtmf_detect_debug)
/* elementfactory information */
static const GstElementDetails gst_dtmf_detect_details =
GST_ELEMENT_DETAILS ("DTMF detector element",
"Detect",
"This element detects DTMF tones",
"Olivier Crete <olivier.crete@collabora.co.uk>");
static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-raw-int, "
"width = (int) 16, "
"depth = (int) 16, "
"endianness = (int) " G_STRINGIFY (G_BYTE_ORDER) ", "
"signed = (bool) true, rate = (int) [1, MAX], channels = (int) 1"));
static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-raw-int, "
"width = (int) 16, "
"depth = (int) 16, "
"endianness = (int) " G_STRINGIFY (G_BYTE_ORDER) ", "
"signed = (bool) true, rate = (int) [1, MAX], channels = (int) 1"));
/* signals and args */
enum
{
/* FILL ME */
LAST_SIGNAL
};
enum
{
PROP_0,
};
static gboolean gst_dtmf_detect_set_caps (GstBaseTransform * trans,
GstCaps * incaps, GstCaps * outcaps);
static GstFlowReturn gst_dtmf_detect_transform_ip (GstBaseTransform * trans,
GstBuffer * buf);
static gboolean gst_dtmf_detect_event (GstBaseTransform * trans,
GstEvent * event);
static void
_do_init (GType type)
{
GST_DEBUG_CATEGORY_INIT (dtmf_detect_debug, "dtmfdetect", 0, "dtmfdetect");
}
GST_BOILERPLATE_FULL (GstDtmfDetect, gst_dtmf_detect, GstBaseTransform,
GST_TYPE_BASE_TRANSFORM, _do_init);
static void
gst_dtmf_detect_base_init (gpointer klass)
{
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&srctemplate));
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&sinktemplate));
gst_element_class_set_details (element_class, &gst_dtmf_detect_details);
}
static void
gst_dtmf_detect_class_init (GstDtmfDetectClass * klass)
{
GstBaseTransformClass *gstbasetransform_class;
gstbasetransform_class = (GstBaseTransformClass *) klass;
gstbasetransform_class->set_caps =
GST_DEBUG_FUNCPTR (gst_dtmf_detect_set_caps);
gstbasetransform_class->transform_ip =
GST_DEBUG_FUNCPTR (gst_dtmf_detect_transform_ip);
gstbasetransform_class->event = GST_DEBUG_FUNCPTR (gst_dtmf_detect_event);
}
static void
gst_dtmf_detect_init (GstDtmfDetect * dtmfdetect, GstDtmfDetectClass * klass)
{
gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (dtmfdetect), TRUE);
gst_base_transform_set_gap_aware (GST_BASE_TRANSFORM (dtmfdetect), TRUE);
}
static gboolean
gst_dtmf_detect_set_caps (GstBaseTransform * trans, GstCaps * incaps,
GstCaps * outcaps)
{
GstDtmfDetect *self = GST_DTMF_DETECT (trans);
GstStructure *s = gst_caps_get_structure (incaps, 0);
if (!gst_structure_get_int (s, "rate", &self->rate))
return FALSE;
zap_dtmf_detect_init (&self->dtmf_state);
return TRUE;
}
static GstFlowReturn
gst_dtmf_detect_transform_ip (GstBaseTransform * trans, GstBuffer * buf)
{
GstDtmfDetect *self = GST_DTMF_DETECT (trans);
int dtmf_count;
char dtmfbuf[MAX_DTMF_DIGITS] = "";
int i;
if (GST_BUFFER_IS_DISCONT (buf))
zap_dtmf_detect_init (&self->dtmf_state);
zap_dtmf_detect (&self->dtmf_state, (int16_t *) GST_BUFFER_DATA (buf),
GST_BUFFER_SIZE (buf) / 2, FALSE);
dtmf_count = zap_dtmf_get (&self->dtmf_state, dtmfbuf, MAX_DTMF_DIGITS);
if (dtmf_count)
GST_DEBUG_OBJECT (self, "Got %d DTMF events: %s", dtmf_count, dtmfbuf);
else
GST_LOG_OBJECT (self, "Got no DTMF events");
for (i = 0; i < dtmf_count; i++) {
GstMessage *dtmf_message = NULL;
GstStructure *structure;
gint dtmf_payload_event;
GST_DEBUG_OBJECT (self, "Got DTMF event %c", dtmfbuf[i]);
switch (dtmfbuf[i]) {
case '0':
dtmf_payload_event = 0;
break;
case '1':
dtmf_payload_event = 1;
break;
case '2':
dtmf_payload_event = 2;
break;
case '3':
dtmf_payload_event = 3;
break;
case '4':
dtmf_payload_event = 4;
break;
case '5':
dtmf_payload_event = 5;
break;
case '6':
dtmf_payload_event = 6;
break;
case '7':
dtmf_payload_event = 7;
break;
case '8':
dtmf_payload_event = 8;
break;
case '9':
dtmf_payload_event = 9;
break;
case '*':
dtmf_payload_event = 10;
break;
case '#':
dtmf_payload_event = 11;
break;
case 'A':
dtmf_payload_event = 12;
break;
case 'B':
dtmf_payload_event = 13;
break;
case 'C':
dtmf_payload_event = 14;
break;
case 'D':
dtmf_payload_event = 15;
break;
default:
continue;
}
structure = gst_structure_new ("dtmf-event",
"type", G_TYPE_INT, 1,
"number", G_TYPE_INT, dtmf_payload_event,
"method", G_TYPE_INT, 2, NULL);
dtmf_message = gst_message_new_element (GST_OBJECT (self), structure);
gst_element_post_message (GST_ELEMENT (self), dtmf_message);
}
return GST_FLOW_OK;
}
static gboolean
gst_dtmf_detect_event (GstBaseTransform * trans, GstEvent * event)
{
GstDtmfDetect *self = GST_DTMF_DETECT (trans);
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_FLUSH_STOP:
zap_dtmf_detect_init (&self->dtmf_state);
break;
default:
break;
}
return GST_CALL_PARENT_WITH_DEFAULT (GST_BASE_TRANSFORM_CLASS, event,
(trans, event), TRUE);
}
gboolean
gst_dtmf_detect_plugin_init (GstPlugin * plugin)
{
return gst_element_register (plugin, "dtmfdetect",
GST_RANK_MARGINAL, GST_TYPE_DTMF_DETECT);
}

73
gst/dtmf/gstdtmfdetect.h Normal file
View file

@ -0,0 +1,73 @@
/*
* GStreamer - DTMF Detection
*
* Copyright 2009 Nokia Corporation
* Copyright 2009 Collabora Ltd,
* @author: Olivier Crete <olivier.crete@collabora.co.uk>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
*/
#ifndef __GST_DTMF_DETECT_H__
#define __GST_DTMF_DETECT_H__
#include <gst/gst.h>
#include <gst/base/gstbasetransform.h>
#include "tone_detect.h"
G_BEGIN_DECLS
/* #define's don't like whitespacey bits */
#define GST_TYPE_DTMF_DETECT \
(gst_dtmf_detect_get_type())
#define GST_DTMF_DETECT(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj), \
GST_TYPE_DTMF_DETECT,GstDtmfDetect))
#define GST_DTMF_DETECT_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST((klass), \
GST_TYPE_DTMF_DETECT,GstDtmfDetectClass))
#define GST_IS_DTMF_DETECT(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_DTMF_DETECT))
#define GST_IS_DTMF_DETECT_CLASS(obj) \
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_DTMF_DETECT))
typedef struct _GstDtmfDetect GstDtmfDetect;
typedef struct _GstDtmfDetectClass GstDtmfDetectClass;
typedef struct _GstDtmfDetectPrivate GstDtmfDetectPrivate;
struct _GstDtmfDetect
{
GstBaseTransform parent;
gint rate;
dtmf_detect_state_t dtmf_state;
};
struct _GstDtmfDetectClass
{
GstBaseTransformClass parent_class;
};
GType gst_dtmf_detect_get_type (void);
gboolean gst_dtmf_detect_plugin_init (GstPlugin *plugin);
G_END_DECLS
#endif /* __GST_DTMF_DETECT_H__ */

517
gst/dtmf/tone_detect.c Normal file
View file

@ -0,0 +1,517 @@
/*
* DTMF Receiver module, part of:
* BSD Telephony Of Mexico "Zapata" Telecom Library, version 1.10 12/9/01
*
* Part of the "Zapata" Computer Telephony Technology.
*
* See http://www.bsdtelephony.com.mx
*
*
* The technologies, software, hardware, designs, drawings, scheumatics, board
* layouts and/or artwork, concepts, methodologies (including the use of all
* of these, and that which is derived from the use of all of these), all other
* intellectual properties contained herein, and all intellectual property
* rights have been and shall continue to be expressly for the benefit of all
* mankind, and are perpetually placed in the public domain, and may be used,
* copied, and/or modified by anyone, in any manner, for any legal purpose,
* without restriction.
*
* This module written by Stephen Underwood.
*/
/*
tone_detect.c - General telephony tone detection, and specific
detection of DTMF.
Copyright (C) 2001 Steve Underwood <steveu@coppice.org>
Despite my general liking of the GPL, I place this code in the
public domain for the benefit of all mankind - even the slimy
ones who might try to proprietize my work and use it to my
detriment.
*/
#include <math.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <fcntl.h>
#include "tone_detect.h"
#define FALSE 0
#define TRUE (!FALSE)
//#define USE_3DNOW
/* Basic DTMF specs:
*
* Minimum tone on = 40ms
* Minimum tone off = 50ms
* Maximum digit rate = 10 per second
* Normal twist <= 8dB accepted
* Reverse twist <= 4dB accepted
* S/N >= 15dB will detect OK
* Attenuation <= 26dB will detect OK
* Frequency tolerance +- 1.5% will detect, +-3.5% will reject
*/
#define SAMPLE_RATE 8000.0
#define DTMF_THRESHOLD 8.0e7
#define FAX_THRESHOLD 8.0e7
#define FAX_2ND_HARMONIC 2.0 /* 4dB */
#define DTMF_NORMAL_TWIST 6.3 /* 8dB */
#define DTMF_REVERSE_TWIST ((isradio) ? 4.0 : 2.5) /* 4dB normal */
#define DTMF_RELATIVE_PEAK_ROW 6.3 /* 8dB */
#define DTMF_RELATIVE_PEAK_COL 6.3 /* 8dB */
#define DTMF_2ND_HARMONIC_ROW ((isradio) ? 1.7 : 2.5) /* 4dB normal */
#define DTMF_2ND_HARMONIC_COL 63.1 /* 18dB */
static tone_detection_descriptor_t dtmf_detect_row[4];
static tone_detection_descriptor_t dtmf_detect_col[4];
static tone_detection_descriptor_t dtmf_detect_row_2nd[4];
static tone_detection_descriptor_t dtmf_detect_col_2nd[4];
static tone_detection_descriptor_t fax_detect;
static tone_detection_descriptor_t fax_detect_2nd;
static float dtmf_row[] =
{
697.0, 770.0, 852.0, 941.0
};
static float dtmf_col[] =
{
1209.0, 1336.0, 1477.0, 1633.0
};
static float fax_freq = 1100.0;
static char dtmf_positions[] = "123A" "456B" "789C" "*0#D";
static void goertzel_init(goertzel_state_t *s,
tone_detection_descriptor_t *t)
{
s->v2 =
s->v3 = 0.0;
s->fac = t->fac;
}
/*- End of function --------------------------------------------------------*/
#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 --------------------------------------------------------*/
void zap_goertzel_update(goertzel_state_t *s,
int16_t x[],
int samples)
{
int i;
float v1;
for (i = 0; i < samples; i++)
{
v1 = s->v2;
s->v2 = s->v3;
s->v3 = s->fac*s->v2 - v1 + x[i];
}
}
/*- End of function --------------------------------------------------------*/
float zap_goertzel_result (goertzel_state_t *s)
{
return s->v3*s->v3 + s->v2*s->v2 - s->v2*s->v3*s->fac;
}
/*- End of function --------------------------------------------------------*/
void zap_dtmf_detect_init (dtmf_detect_state_t *s)
{
int i;
float theta;
s->hit1 =
s->hit2 = 0;
for (i = 0; i < 4; i++)
{
theta = 2.0*M_PI*(dtmf_row[i]/SAMPLE_RATE);
dtmf_detect_row[i].fac = 2.0*cos(theta);
theta = 2.0*M_PI*(dtmf_col[i]/SAMPLE_RATE);
dtmf_detect_col[i].fac = 2.0*cos(theta);
theta = 2.0*M_PI*(dtmf_row[i]*2.0/SAMPLE_RATE);
dtmf_detect_row_2nd[i].fac = 2.0*cos(theta);
theta = 2.0*M_PI*(dtmf_col[i]*2.0/SAMPLE_RATE);
dtmf_detect_col_2nd[i].fac = 2.0*cos(theta);
goertzel_init (&s->row_out[i], &dtmf_detect_row[i]);
goertzel_init (&s->col_out[i], &dtmf_detect_col[i]);
goertzel_init (&s->row_out2nd[i], &dtmf_detect_row_2nd[i]);
goertzel_init (&s->col_out2nd[i], &dtmf_detect_col_2nd[i]);
s->energy = 0.0;
}
/* Same for the fax dector */
theta = 2.0*M_PI*(fax_freq/SAMPLE_RATE);
fax_detect.fac = 2.0 * cos(theta);
goertzel_init (&s->fax_tone, &fax_detect);
/* Same for the fax dector 2nd harmonic */
theta = 2.0*M_PI*(fax_freq * 2.0/SAMPLE_RATE);
fax_detect_2nd.fac = 2.0 * cos(theta);
goertzel_init (&s->fax_tone2nd, &fax_detect_2nd);
s->current_sample = 0;
s->detected_digits = 0;
s->lost_digits = 0;
s->digits[0] = '\0';
s->mhit = 0;
}
/*- End of function --------------------------------------------------------*/
int zap_dtmf_detect (dtmf_detect_state_t *s,
int16_t amp[],
int samples,
int isradio)
{
float row_energy[4];
float col_energy[4];
float fax_energy;
float fax_energy_2nd;
float famp;
float v1;
int i;
int j;
int sample;
int best_row;
int best_col;
int hit;
int limit;
hit = 0;
for (sample = 0; sample < samples; sample = limit)
{
/* 102 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);
_dtmf_goertzel_update (s->row_out2nd, amp + sample, limit2 - sample);
_dtmf_goertzel_update (s->col_out2nd, amp + sample, limit2 - sample);
/* XXX Need to fax detect for 3dnow too XXX */
#warning "Fax Support Broken"
#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];
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;
v1 = s->col_out2nd[0].v2;
s->col_out2nd[0].v2 = s->col_out2nd[0].v3;
s->col_out2nd[0].v3 = s->col_out2nd[0].fac*s->col_out2nd[0].v2 - v1 + famp;
v1 = s->row_out2nd[0].v2;
s->row_out2nd[0].v2 = s->row_out2nd[0].v3;
s->row_out2nd[0].v3 = s->row_out2nd[0].fac*s->row_out2nd[0].v2 - v1 + famp;
v1 = s->col_out2nd[1].v2;
s->col_out2nd[1].v2 = s->col_out2nd[1].v3;
s->col_out2nd[1].v3 = s->col_out2nd[1].fac*s->col_out2nd[1].v2 - v1 + famp;
v1 = s->row_out2nd[1].v2;
s->row_out2nd[1].v2 = s->row_out2nd[1].v3;
s->row_out2nd[1].v3 = s->row_out2nd[1].fac*s->row_out2nd[1].v2 - v1 + famp;
v1 = s->col_out2nd[2].v2;
s->col_out2nd[2].v2 = s->col_out2nd[2].v3;
s->col_out2nd[2].v3 = s->col_out2nd[2].fac*s->col_out2nd[2].v2 - v1 + famp;
v1 = s->row_out2nd[2].v2;
s->row_out2nd[2].v2 = s->row_out2nd[2].v3;
s->row_out2nd[2].v3 = s->row_out2nd[2].fac*s->row_out2nd[2].v2 - v1 + famp;
v1 = s->col_out2nd[3].v2;
s->col_out2nd[3].v2 = s->col_out2nd[3].v3;
s->col_out2nd[3].v3 = s->col_out2nd[3].fac*s->col_out2nd[3].v2 - v1 + famp;
v1 = s->row_out2nd[3].v2;
s->row_out2nd[3].v2 = s->row_out2nd[3].v3;
s->row_out2nd[3].v3 = s->row_out2nd[3].fac*s->row_out2nd[3].v2 - v1 + famp;
/* Update fax tone */
v1 = s->fax_tone.v2;
s->fax_tone.v2 = s->fax_tone.v3;
s->fax_tone.v3 = s->fax_tone.fac*s->fax_tone.v2 - v1 + famp;
v1 = s->fax_tone.v2;
s->fax_tone2nd.v2 = s->fax_tone2nd.v3;
s->fax_tone2nd.v3 = s->fax_tone2nd.fac*s->fax_tone2nd.v2 - v1 + famp;
}
#endif
s->current_sample += (limit - sample);
if (s->current_sample < 102)
continue;
/* Detect the fax energy, too */
fax_energy = zap_goertzel_result(&s->fax_tone);
/* We are at the end of a DTMF detection block */
/* Find the peak row and the peak column */
row_energy[0] = zap_goertzel_result (&s->row_out[0]);
col_energy[0] = zap_goertzel_result (&s->col_out[0]);
for (best_row = best_col = 0, i = 1; i < 4; i++)
{
row_energy[i] = zap_goertzel_result (&s->row_out[i]);
if (row_energy[i] > row_energy[best_row])
best_row = i;
col_energy[i] = zap_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]*DTMF_REVERSE_TWIST
&&
col_energy[best_col]*DTMF_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 second harmonic test */
if (i >= 4
&&
(row_energy[best_row] + col_energy[best_col]) > 42.0*s->energy
&&
zap_goertzel_result (&s->col_out2nd[best_col])*DTMF_2ND_HARMONIC_COL < col_energy[best_col]
&&
zap_goertzel_result (&s->row_out2nd[best_row])*DTMF_2ND_HARMONIC_ROW < row_energy[best_row])
{
hit = dtmf_positions[(best_row << 2) + best_col];
/* Look for two successive similar results */
/* The logic in the next test is:
We need two successive identical clean detects, with
something different preceeding it. This can work with
back to back differing digits. More importantly, it
can work with nasty phones that give a very wobbly start
to a digit. */
if (hit == s->hit3 && s->hit3 != s->hit2)
{
s->mhit = hit;
s->digit_hits[(best_row << 2) + best_col]++;
s->detected_digits++;
if (s->current_digits < MAX_DTMF_DIGITS)
{
s->digits[s->current_digits++] = hit;
s->digits[s->current_digits] = '\0';
}
else
{
s->lost_digits++;
}
}
}
}
if (!hit && (fax_energy >= FAX_THRESHOLD) && (fax_energy > s->energy * 21.0)) {
fax_energy_2nd = zap_goertzel_result(&s->fax_tone2nd);
if (fax_energy_2nd * FAX_2ND_HARMONIC < fax_energy) {
#if 0
printf("Fax energy/Second Harmonic: %f/%f\n", fax_energy, fax_energy_2nd);
#endif
/* XXX Probably need better checking than just this the energy XXX */
hit = 'f';
s->fax_hits++;
} /* Don't reset fax hits counter */
} else {
if (s->fax_hits > 5) {
s->mhit = 'f';
s->detected_digits++;
if (s->current_digits < MAX_DTMF_DIGITS)
{
s->digits[s->current_digits++] = hit;
s->digits[s->current_digits] = '\0';
}
else
{
s->lost_digits++;
}
}
s->fax_hits = 0;
}
s->hit1 = s->hit2;
s->hit2 = s->hit3;
s->hit3 = hit;
/* Reinitialise the detector for the next block */
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]);
goertzel_init (&s->row_out2nd[i], &dtmf_detect_row_2nd[i]);
goertzel_init (&s->col_out2nd[i], &dtmf_detect_col_2nd[i]);
}
goertzel_init (&s->fax_tone, &fax_detect);
goertzel_init (&s->fax_tone2nd, &fax_detect_2nd);
s->energy = 0.0;
s->current_sample = 0;
}
if ((!s->mhit) || (s->mhit != hit))
{
s->mhit = 0;
return(0);
}
return (hit);
}
/*- End of function --------------------------------------------------------*/
int zap_dtmf_get (dtmf_detect_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 --------------------------------------------------------*/
/*- End of file ------------------------------------------------------------*/

86
gst/dtmf/tone_detect.h Normal file
View file

@ -0,0 +1,86 @@
/*
* Header file for DTMF Receiver module, part of:
* BSD Telephony Of Mexico "Zapata" Telecom Library, version 1.10 12/9/01
*
* Part of the "Zapata" Computer Telephony Technology.
*
* See http://www.bsdtelephony.com.mx
*
*
* The technologies, software, hardware, designs, drawings, scheumatics, board
* layouts and/or artwork, concepts, methodologies (including the use of all
* of these, and that which is derived from the use of all of these), all other
* intellectual properties contained herein, and all intellectual property
* rights have been and shall continue to be expressly for the benefit of all
* mankind, and are perpetually placed in the public domain, and may be used,
* copied, and/or modified by anyone, in any manner, for any legal purpose,
* without restriction.
*
* This module written by Stephen Underwood.
*/
/*
tone_detect.h - General telephony tone detection, and specific
detection of DTMF.
Copyright (C) 2001 Steve Underwood <steveu@coppice.org>
Despite my general liking of the GPL, I place this code in the
public domain for the benefit of all mankind - even the slimy
ones who might try to proprietize my work and use it to my
detriment.
*/
typedef struct
{
float v2;
float v3;
float fac;
} goertzel_state_t;
#define MAX_DTMF_DIGITS 128
typedef struct
{
int hit1;
int hit2;
int hit3;
int hit4;
int mhit;
goertzel_state_t row_out[4];
goertzel_state_t col_out[4];
goertzel_state_t row_out2nd[4];
goertzel_state_t col_out2nd[4];
goertzel_state_t fax_tone;
goertzel_state_t fax_tone2nd;
float energy;
int current_sample;
char digits[MAX_DTMF_DIGITS + 1];
int current_digits;
int detected_digits;
int lost_digits;
int digit_hits[16];
int fax_hits;
} dtmf_detect_state_t;
typedef struct
{
float fac;
} tone_detection_descriptor_t;
void zap_goertzel_update(goertzel_state_t *s,
int16_t x[],
int samples);
float zap_goertzel_result (goertzel_state_t *s);
void zap_dtmf_detect_init (dtmf_detect_state_t *s);
int zap_dtmf_detect (dtmf_detect_state_t *s,
int16_t amp[],
int samples,
int isradio);
int zap_dtmf_get (dtmf_detect_state_t *s,
char *buf,
int max);
/*- End of file ------------------------------------------------------------*/