Added DTMF decoder for audio mode.

This commit is contained in:
Fritz Elfert 1996-06-05 02:24:12 +00:00
parent cec7c0f0ee
commit fb51381786
4 changed files with 245 additions and 44 deletions

View File

@ -3,6 +3,7 @@
* Linux ISDN subsystem, audio conversion and compression (linklevel).
*
* Copyright 1994,95,96 by Fritz Elfert (fritz@wuemaus.franken.de)
* DTMF code (c) 1996 by Christian Mock (cm@kukuruz.ping.at)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -19,6 +20,10 @@
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Log$
* Revision 1.4 1996/05/17 03:48:01 fritz
* Removed some test statements.
* Added revision string.
*
* Revision 1.3 1996/05/10 08:48:11 fritz
* Corrected adpcm bugs.
*
@ -34,6 +39,7 @@
#include <linux/module.h>
#include <linux/isdn.h>
#include "isdn_audio.h"
#include "isdn_common.h"
char *isdn_audio_revision = "$Revision$";
@ -185,6 +191,46 @@ static char isdn_audio_ulaw_to_alaw[] = {
0x8a, 0x8a, 0x6a, 0x6a, 0xea, 0xea, 0x2a, 0x2a
};
#define NCOEFF 16 /* number of frequencies to be analyzed */
#define DTMF_TRESH 50000 /* above this is dtmf */
#define SILENCE_TRESH 100 /* below this is silence */
#define H2_TRESH 10000 /* 2nd harmonic */
#define AMP_BITS 9 /* bits per sample, reduced to avoid overflow */
#define LOGRP 0
#define HIGRP 1
typedef struct {
int grp; /* low/high group */
int k; /* k */
int k2; /* k fuer 2. harmonic */
} dtmf_t;
/* For DTMF recognition:
* 2 * cos(2 * PI * k / N) precalculated for all k
*/
static int cos2pik[NCOEFF] = {
55812, 29528, 53603, 24032, 51193, 14443, 48590, 6517,
38113, -21204, 33057, -32186, 25889, -45081, 18332, -55279
};
static dtmf_t dtmf_tones[8] = {
{ LOGRP, 0, 1 }, /* 697 Hz */
{ LOGRP, 2, 3 }, /* 770 Hz */
{ LOGRP, 4, 5 }, /* 852 Hz */
{ LOGRP, 6, 7 }, /* 941 Hz */
{ HIGRP, 8, 9 }, /* 1209 Hz */
{ HIGRP, 10, 11 }, /* 1336 Hz */
{ HIGRP, 12, 13 }, /* 1477 Hz */
{ HIGRP, 14, 15 } /* 1633 Hz */
};
static char dtmf_matrix[4][4] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
#if ((CPU == 386) || (CPU == 486) || (CPU == 586))
static inline void
isdn_audio_tlookup(const void *table, void *buff, unsigned long n)
@ -283,7 +329,7 @@ static int bitmask[9] = {
};
static int
isdn_audio_get_bits (adpcm_state *s, unsigned char **in, int *len)
isdn_audio_get_bits (audio_state *s, unsigned char **in, int *len)
{
while( s->nleft < s->nbits) {
int d = *((*in)++);
@ -296,7 +342,7 @@ isdn_audio_get_bits (adpcm_state *s, unsigned char **in, int *len)
}
static void
isdn_audio_put_bits (int data, int nbits, adpcm_state *s,
isdn_audio_put_bits (int data, int nbits, audio_state *s,
unsigned char **out, int *len)
{
s->word = (s->word << nbits) | (data & bitmask[nbits]);
@ -309,22 +355,20 @@ isdn_audio_put_bits (int data, int nbits, adpcm_state *s,
}
}
adpcm_state *
isdn_audio_adpcm_init(int nbits)
audio_state *
isdn_audio_state_init(int nbits)
{
static adpcm_state *s;
static audio_state *s;
#ifdef ATEST
s = (adpcm_state *) malloc(sizeof(adpcm_state));
#else
s = (adpcm_state *) kmalloc(sizeof(adpcm_state), GFP_ATOMIC);
#endif
s = (audio_state *) kmalloc(sizeof(audio_state), GFP_ATOMIC);
if (s) {
s->a = 0;
s->d = 5;
s->word = 0;
s->nleft = 0;
s->nbits = nbits;
s->dtmf_last = ' ';
s->dtmf_idx = 0;
}
return s;
}
@ -335,7 +379,7 @@ static adpcm_state *s;
*/
int
isdn_audio_adpcm2xlaw (adpcm_state *s, int fmt, unsigned char *in,
isdn_audio_adpcm2xlaw (audio_state *s, int fmt, unsigned char *in,
unsigned char *out, int len)
{
int a = s->a;
@ -370,7 +414,7 @@ isdn_audio_adpcm2xlaw (adpcm_state *s, int fmt, unsigned char *in,
}
int
isdn_audio_2adpcm_flush (adpcm_state *s, unsigned char *out)
isdn_audio_2adpcm_flush (audio_state *s, unsigned char *out)
{
int olen = 0;
@ -380,7 +424,7 @@ isdn_audio_2adpcm_flush (adpcm_state *s, unsigned char *out)
}
int
isdn_audio_xlaw2adpcm (adpcm_state *s, int fmt, unsigned char *in,
isdn_audio_xlaw2adpcm (audio_state *s, int fmt, unsigned char *in,
unsigned char *out, int len)
{
int a = s->a;
@ -422,3 +466,139 @@ isdn_audio_xlaw2adpcm (adpcm_state *s, int fmt, unsigned char *in,
return olen;
}
/*
* Goertzel algorithm.
* See http://ptolemy.eecs.berkeley.edu/~pino/Ptolemy/papers/96/dtmf_ict/
* for more info.
* Result is stored into an sk_buff and queued up for later
* evaluation.
*/
void
isdn_audio_goertzel(int *sample, modem_info *info) {
int sk, sk1, sk2;
int k, n;
struct sk_buff *skb;
int *result;
skb = dev_alloc_skb(sizeof(int) * NCOEFF);
if (!skb) {
printk(KERN_WARNING
"isdn_audio: Could not alloc DTMF result for ttyI%d\n",
info->line);
return;
}
result = (int *)skb_put(skb, sizeof(int) * NCOEFF);
skb->free = 1;
skb->users = 0;
for (k = 0; k < NCOEFF; k++) {
sk = sk1 = sk2 = 0;
for (n = 0; n < DTMF_NPOINTS; n++) {
sk = sample[n] + ((cos2pik[k] * sk1) >> 15) - sk2;
sk2 = sk1;
sk1 = sk;
}
result[k] =
((sk * sk) >> AMP_BITS) -
((((cos2pik[k] * sk) >> 15) * sk2) >> AMP_BITS) +
((sk2 * sk2) >> AMP_BITS);
}
skb_queue_tail(&info->dtmf_queue, skb);
isdn_timer_ctrl(ISDN_TIMER_MODEMREAD, 1);
}
void
isdn_audio_eval_dtmf(modem_info *info)
{
struct sk_buff *skb;
int *result;
audio_state *s;
int silence;
int i;
int di;
int ch;
unsigned long flags;
int grp[2];
char what;
char *p;
while ((skb = skb_dequeue(&info->dtmf_queue))) {
result = (int *)skb->data;
s = info->audio_sr;
grp[LOGRP] = grp[HIGRP] = -2;
silence = 0;
for(i = 0; i < 8; i++) {
if ((result[dtmf_tones[i].k] > DTMF_TRESH) &&
(result[dtmf_tones[i].k2] < H2_TRESH) )
grp[dtmf_tones[i].grp] = (grp[dtmf_tones[i].grp] == -2)?i:-1;
else
if ((result[dtmf_tones[i].k] < SILENCE_TRESH) &&
(result[dtmf_tones[i].k2] < SILENCE_TRESH) )
silence++;
}
if(silence == 8)
what = ' ';
else {
if((grp[LOGRP] >= 0) && (grp[HIGRP] >= 0)) {
what = dtmf_matrix[grp[LOGRP]][grp[HIGRP] - 4];
if(s->dtmf_last != ' ' && s->dtmf_last != '.')
s->dtmf_last = what; /* min. 1 non-DTMF between DTMF */
} else
what = '.';
}
if ((what != s->dtmf_last) && (what != ' ') && (what != '.')) {
p = skb->data;
*p++ = 0x10;
*p = what;
skb_trim(skb, 2);
save_flags(flags);
cli();
di = info->isdn_driver;
ch = info->isdn_channel;
__skb_queue_tail(&dev->drv[di]->rpqueue[ch], skb);
dev->drv[di]->rcvcount[ch] += 2;
restore_flags(flags);
/* Schedule dequeuing */
if ((dev->modempoll) && (info->rcvsched))
isdn_timer_ctrl(ISDN_TIMER_MODEMREAD, 1);
wake_up_interruptible(&dev->drv[di]->rcv_waitq[ch]);
} else
kfree_skb(skb, FREE_READ);
s->dtmf_last = what;
}
}
/*
* Decode DTMF tones, queue result in separate sk_buf for
* later examination.
* Parameters:
* s = pointer to state-struct.
* buf = input audio data
* len = size of audio data.
* fmt = audio data format (0 = ulaw, 1 = alaw)
*/
void
isdn_audio_calc_dtmf(modem_info *info, unsigned char *buf, int len, int fmt)
{
audio_state *s = info->audio_sr;
int i;
int c;
while (len) {
c = MIN(len, (DTMF_NPOINTS - s->dtmf_idx));
if (c <= 0)
break;
for (i = 0; i < c; i++) {
if (fmt)
s->dtmf_buf[s->dtmf_idx++] =
isdn_audio_alaw_to_s16[*buf++] >> (15 - AMP_BITS);
else
s->dtmf_buf[s->dtmf_idx++] =
isdn_audio_ulaw_to_s16[*buf++] >> (15 - AMP_BITS);
}
if (s->dtmf_idx == DTMF_NPOINTS) {
isdn_audio_goertzel(s->dtmf_buf, info);
s->dtmf_idx = 0;
}
len -= c;
}
}

View File

@ -19,22 +19,31 @@
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Log$
* Revision 1.2 1996/05/10 08:48:32 fritz
* Corrected adpcm bugs.
*
* Revision 1.1 1996/04/30 09:29:06 fritz
* Taken under CVS control.
*
*/
typedef struct adpcm_state {
int a;
int d;
int word;
int nleft;
int nbits;
} adpcm_state;
#define DTMF_NPOINTS 205 /* Number of samples for DTMF recognition */
typedef struct audio_state {
int a;
int d;
int word;
int nleft;
int nbits;
char dtmf_last;
int dtmf_idx;
int dtmf_buf[DTMF_NPOINTS];
} audio_state;
extern void isdn_audio_ulaw2alaw(unsigned char *, unsigned long);
extern void isdn_audio_alaw2ulaw(unsigned char *, unsigned long);
extern adpcm_state *isdn_audio_adpcm_init(int);
extern int isdn_audio_adpcm2xlaw(adpcm_state *, int, unsigned char *, unsigned char *, int);
extern int isdn_audio_xlaw2adpcm(adpcm_state *, int, unsigned char *, unsigned char *, int);
extern int isdn_audio_2adpcm_flush(adpcm_state *s, unsigned char *out);
extern audio_state *isdn_audio_state_init(int);
extern int isdn_audio_adpcm2xlaw(audio_state *, int, unsigned char *, unsigned char *, int);
extern int isdn_audio_xlaw2adpcm(audio_state *, int, unsigned char *, unsigned char *, int);
extern int isdn_audio_2adpcm_flush(audio_state *s, unsigned char *out);
extern void isdn_audio_calc_dtmf(modem_info *, unsigned char *, int, int);
extern void isdn_audio_eval_dtmf(modem_info *);

View File

@ -21,6 +21,10 @@
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Log$
* Revision 1.16 1996/06/03 20:09:05 fritz
* Bugfix: called wrong function pointer for locking in
* isdn_get_free_channel().
*
* Revision 1.15 1996/05/31 01:10:54 fritz
* Bugfixes:
* Lowlevel modules did not get locked correctly.
@ -290,6 +294,7 @@ static void isdn_receive_skb_callback(int di, int channel, struct sk_buff *skb)
if (info->vonline & 1) {
int ifmt = 1;
/* voice conversion/compression */
isdn_audio_calc_dtmf(info, skb->data, skb->len, ifmt);
switch (info->emu.vpar[3]) {
case 2:
case 3:
@ -298,7 +303,7 @@ static void isdn_receive_skb_callback(int di, int channel, struct sk_buff *skb)
* Since compressed data takes less
* space, we can overwrite the buffer.
*/
skb_trim(skb,isdn_audio_xlaw2adpcm(info->adpcmr,
skb_trim(skb,isdn_audio_xlaw2adpcm(info->audio_sr,
ifmt,
skb->data,
skb->data,

View File

@ -20,6 +20,9 @@
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Log$
* Revision 1.15 1996/06/03 20:35:01 fritz
* Fixed typos.
*
* Revision 1.14 1996/06/03 20:12:19 fritz
* Fixed typos.
* Added call to write_wakeup via isdn_tty_flush_buffer()
@ -100,6 +103,7 @@ static void isdn_tty_check_esc(const u_char *, u_char, int, int *, int *, int);
static void isdn_tty_modem_reset_regs(atemu *, int);
static void isdn_tty_cmd_ATA(modem_info *);
static void isdn_tty_at_cout(char *, modem_info *);
static void isdn_tty_flush_buffer(struct tty_struct *);
/* Leave this unchanged unless you know what you do! */
#define MODEM_PARANOIA_CHECK
@ -184,6 +188,7 @@ void isdn_tty_readmodem(void)
info = &dev->mdm.info[midx];
if (info->online) {
r = 0;
isdn_audio_eval_dtmf(info);
if ((tty = info->tty)) {
if (info->mcr & UART_MCR_RTS) {
c = TTY_FLIPBUF_SIZE - tty->flip.count;
@ -231,6 +236,11 @@ void isdn_tty_cleanup_xmit(modem_info *info)
skb->free = 1;
kfree_skb(skb, FREE_WRITE);
}
if (skb_queue_len(&info->dtmf_queue))
while ((skb = skb_dequeue(&info->dtmf_queue))) {
skb->free = 1;
kfree_skb(skb, FREE_WRITE);
}
restore_flags(flags);
}
@ -399,7 +409,7 @@ static void isdn_tty_senddown(modem_info * info)
/* adpcm, compatible to ZyXel 1496 modem
* with ROM revision 6.01
*/
buflen = isdn_audio_adpcm2xlaw(info->adpcms,
buflen = isdn_audio_adpcm2xlaw(info->audio_ss,
ifmt,
hbuf,
skb_put(skb,skb_len),
@ -573,13 +583,13 @@ void isdn_tty_modem_hup(modem_info * info)
isdn_tty_at_cout("\020\024", info);
}
info->vonline = 0;
if (info->adpcms) {
kfree(info->adpcms);
info->adpcms = NULL;
if (info->audio_ss) {
kfree(info->audio_ss);
info->audio_ss = NULL;
}
if (info->adpcmr) {
kfree(info->adpcmr);
info->adpcmr = NULL;
if (info->audio_sr) {
kfree(info->audio_sr);
info->audio_sr = NULL;
}
info->msr &= ~(UART_MSR_DCD | UART_MSR_RI);
info->lsr |= UART_LSR_TEMT;
@ -1571,6 +1581,7 @@ int isdn_tty_modem_init(void)
info->drv_index = -1;
info->xmit_size = ISDN_SERIAL_XMIT_SIZE;
skb_queue_head_init(&info->xmit_queue);
skb_queue_head_init(&info->dtmf_queue);
if (!(info->xmit_buf = kmalloc(ISDN_SERIAL_XMIT_SIZE + 5, GFP_KERNEL))) {
printk(KERN_ERR "Could not allocate modem xmit-buffer\n");
return -3;
@ -2276,12 +2287,10 @@ static int isdn_tty_cmd_PLUSV(char **p, modem_info * info)
/* AT+VRX - Start recording */
if (!m->vpar[0])
PARSE_ERROR1;
if (m->vpar[3] < 5) {
info->adpcmr = isdn_audio_adpcm_init(m->vpar[3]);
if (!info->adpcmr) {
printk(KERN_WARNING "isdn_tty: Couldn't malloc adpcm state\n");
PARSE_ERROR1;
}
info->audio_sr = isdn_audio_state_init(m->vpar[3]);
if (!info->audio_sr) {
printk(KERN_WARNING "isdn_tty: Couldn't malloc audio state\n");
PARSE_ERROR1;
}
info->vonline = 1;
isdn_tty_modem_result(1, info);
@ -2377,12 +2386,10 @@ static int isdn_tty_cmd_PLUSV(char **p, modem_info * info)
/* AT+VTX - Start sending */
if (!m->vpar[0])
PARSE_ERROR1;
if (m->vpar[3] < 5) {
info->adpcms = isdn_audio_adpcm_init(m->vpar[3]);
if (!info->adpcms) {
printk(KERN_WARNING "isdn_tty: Couldn't malloc adpcm state\n");
PARSE_ERROR1;
}
info->audio_ss = isdn_audio_state_init(m->vpar[3]);
if (!info->audio_ss) {
printk(KERN_WARNING "isdn_tty: Couldn't malloc audio state\n");
PARSE_ERROR1;
}
m->lastDLE = 0;
info->vonline = 2;