forked from Mirrors/freeswitch
70c1c03c93
poor detection. Fixes to how timezones are handled for FAX page headers. Various cleanups of the spandsp headers.
926 lines
31 KiB
C
926 lines
31 KiB
C
/*
|
|
* SpanDSP - a series of DSP components for telephony
|
|
*
|
|
* dtmf_rx_tests.c - Test the DTMF detector against the spec., whatever the spec.
|
|
* may be :)
|
|
*
|
|
* Written by Steve Underwood <steveu@coppice.org>
|
|
*
|
|
* Copyright (C) 2001, 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.
|
|
*/
|
|
|
|
/*
|
|
* These tests include conversion to and from A-law. I assume the
|
|
* distortion this produces is comparable to u-law, so it should be
|
|
* a fair test.
|
|
*
|
|
* These tests mirror those on the CM7291 test tape from Mitel.
|
|
* Many of these tests are highly questionable, but they are a
|
|
* well accepted industry standard.
|
|
*
|
|
* However standard these tests might be, Mitel appears to have stopped
|
|
* selling copies of their tape.
|
|
*
|
|
* For the talk-off test the Bellcore tapes may be used. However, they are
|
|
* copyright material, so the test data files produced from the Bellcore
|
|
* tapes cannot be distributed as a part of this package.
|
|
*
|
|
*/
|
|
|
|
/*! \page dtmf_rx_tests_page DTMF receiver tests
|
|
\section dtmf_rx_tests_page_sec_1 What does it do?
|
|
|
|
The DTMF detection test suite performs similar tests to the Mitel test tape,
|
|
traditionally used for testing DTMF receivers. Mitel seem to have discontinued
|
|
this product, but all it not lost.
|
|
|
|
The first side of the Mitel tape consists of a number of tone and tone+noise
|
|
based tests. The test suite synthesizes equivalent test data. Being digitally
|
|
generated, this data is rather more predictable than the test data on the nasty
|
|
old stretchy cassette tapes which Mitel sold.
|
|
|
|
The second side of the Mitel tape contains fragments of real speech from real
|
|
phone calls captured from the North American telephone network. These are
|
|
considered troublesome for DTMF detectors. A good detector is expected to
|
|
achieve a reasonably low number of false detections on this data. Fresh clean
|
|
copies of this seem to be unobtainable. However, Bellcore produce a much more
|
|
aggressive set of three cassette tapes. All six side (about 30 minutes each) are
|
|
filled with much tougher fragments of real speech from the North American
|
|
telephone network. If you can do well in this test, nobody cares about your
|
|
results against the Mitel test tape.
|
|
|
|
A fresh set of tapes was purchased for these tests, and digitised, producing 6
|
|
wave files of 16 bit signed PCM data, sampled at 8kHz. They were transcribed
|
|
using a speed adjustable cassette player. The test tone at the start of the
|
|
tapes is pretty accurate, and the new tapes should not have had much opportunity
|
|
to stretch. It is believed these transcriptions are about as good as the source
|
|
material permits.
|
|
|
|
PLEASE NOTE
|
|
|
|
These transcriptions may be freely used by anyone who has a legitimate copy of
|
|
the original tapes. However, if you don't have a legitimate copy of those tapes,
|
|
you also have no right to use this data. The original tapes are the copyright
|
|
material of BellCore, and they charge over US$200 for a set. I doubt they sell
|
|
enough copies to consider this much of a business. However, it is their data,
|
|
and it is their right to do as they wish with it. Currently I see no indication
|
|
they wish to give it away for free.
|
|
*/
|
|
|
|
#if defined(HAVE_CONFIG_H)
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <time.h>
|
|
#include <sndfile.h>
|
|
|
|
//#if defined(WITH_SPANDSP_INTERNALS)
|
|
#define SPANDSP_EXPOSE_INTERNAL_STRUCTURES
|
|
//#endif
|
|
|
|
#include "spandsp.h"
|
|
#include "spandsp-sim.h"
|
|
|
|
/* 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 DEFAULT_DTMF_TX_LEVEL -10
|
|
#define DEFAULT_DTMF_TX_ON_TIME 50
|
|
#define DEFAULT_DTMF_TX_OFF_TIME 50
|
|
|
|
#define SAMPLES_PER_CHUNK 160
|
|
|
|
#define ALL_POSSIBLE_DIGITS "123A456B789C*0#D"
|
|
|
|
#define MITEL_DIR "../test-data/mitel/"
|
|
#define BELLCORE_DIR "../test-data/bellcore/"
|
|
|
|
const char *bellcore_files[] =
|
|
{
|
|
MITEL_DIR "mitel-cm7291-talkoff.wav",
|
|
BELLCORE_DIR "tr-tsy-00763-1.wav",
|
|
BELLCORE_DIR "tr-tsy-00763-2.wav",
|
|
BELLCORE_DIR "tr-tsy-00763-3.wav",
|
|
BELLCORE_DIR "tr-tsy-00763-4.wav",
|
|
BELLCORE_DIR "tr-tsy-00763-5.wav",
|
|
BELLCORE_DIR "tr-tsy-00763-6.wav",
|
|
""
|
|
};
|
|
|
|
static tone_gen_descriptor_t my_dtmf_digit_tones[16];
|
|
|
|
float dtmf_row[] =
|
|
{
|
|
697.0f, 770.0f, 852.0f, 941.0f
|
|
};
|
|
float dtmf_col[] =
|
|
{
|
|
1209.0f, 1336.0f, 1477.0f, 1633.0f
|
|
};
|
|
|
|
char dtmf_positions[] = "123A" "456B" "789C" "*0#D";
|
|
|
|
int callback_hit;
|
|
int callback_ok;
|
|
int callback_roll;
|
|
int step;
|
|
|
|
int max_forward_twist;
|
|
int max_reverse_twist;
|
|
|
|
int use_dialtone_filter = FALSE;
|
|
|
|
char *decode_test_file = NULL;
|
|
|
|
static int16_t amp[1000000];
|
|
static int16_t amp2[1000000];
|
|
|
|
codec_munge_state_t *munge = NULL;
|
|
|
|
static void my_dtmf_gen_init(float low_fudge,
|
|
int low_level,
|
|
float high_fudge,
|
|
int high_level,
|
|
int duration,
|
|
int gap)
|
|
{
|
|
int row;
|
|
int col;
|
|
|
|
for (row = 0; row < 4; row++)
|
|
{
|
|
for (col = 0; col < 4; col++)
|
|
{
|
|
tone_gen_descriptor_init(&my_dtmf_digit_tones[row*4 + col],
|
|
dtmf_row[row]*(1.0f + low_fudge),
|
|
low_level,
|
|
dtmf_col[col]*(1.0f + high_fudge),
|
|
high_level,
|
|
duration,
|
|
gap,
|
|
0,
|
|
0,
|
|
FALSE);
|
|
}
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static int my_dtmf_generate(int16_t amp[], const char *digits)
|
|
{
|
|
int len;
|
|
char *cp;
|
|
tone_gen_state_t tone;
|
|
|
|
len = 0;
|
|
while (*digits)
|
|
{
|
|
cp = strchr(dtmf_positions, *digits);
|
|
if (cp)
|
|
{
|
|
tone_gen_init(&tone, &my_dtmf_digit_tones[cp - dtmf_positions]);
|
|
len += tone_gen(&tone, amp + len, 1000);
|
|
}
|
|
digits++;
|
|
}
|
|
return len;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void digit_delivery(void *data, const char *digits, int len)
|
|
{
|
|
int i;
|
|
int seg;
|
|
const char *s = ALL_POSSIBLE_DIGITS;
|
|
const char *t;
|
|
|
|
callback_hit = TRUE;
|
|
if (data == (void *) 0x12345678)
|
|
{
|
|
t = s + callback_roll;
|
|
seg = 16 - callback_roll;
|
|
for (i = 0; i < len; i += seg, seg = 16)
|
|
{
|
|
if (i + seg > len)
|
|
seg = len - i;
|
|
if (memcmp(digits + i, t, seg))
|
|
{
|
|
callback_ok = FALSE;
|
|
printf("Fail at %d %d\n", i, seg);
|
|
break;
|
|
}
|
|
t = s;
|
|
callback_roll = (callback_roll + seg)%16;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
callback_ok = FALSE;
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void digit_status(void *data, int signal, int level, int delay)
|
|
{
|
|
const char *s = ALL_POSSIBLE_DIGITS;
|
|
int len;
|
|
static int last_step = 0;
|
|
static int first = TRUE;
|
|
|
|
//printf("Digit status %d %d %d\n", signal, level, delay);
|
|
callback_hit = TRUE;
|
|
len = step - last_step;
|
|
if (data == (void *) 0x12345678)
|
|
{
|
|
if (len < 320 || len > 480)
|
|
{
|
|
if (first)
|
|
{
|
|
/* At the beginning the apparent duration is expected to be wrong */
|
|
first = FALSE;
|
|
}
|
|
else
|
|
{
|
|
printf("Failed for signal %s length %d at %d\n", (callback_roll & 1) ? "on" : "off", len, step);
|
|
callback_ok = FALSE;
|
|
}
|
|
}
|
|
if (callback_roll & 1)
|
|
{
|
|
if (signal != 0)
|
|
{
|
|
printf("Failed for signal 0x%X instead of 0\n", signal);
|
|
callback_ok = FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (signal != s[callback_roll >> 1])
|
|
{
|
|
printf("Failed for signal 0x%X instead of 0x%X\n", signal, s[callback_roll >> 1]);
|
|
callback_ok = FALSE;
|
|
}
|
|
if (level < DEFAULT_DTMF_TX_LEVEL + 3 - 1 || level > DEFAULT_DTMF_TX_LEVEL + 3 + 1)
|
|
{
|
|
printf("Failed for level %d instead of %d\n", level, DEFAULT_DTMF_TX_LEVEL + 3);
|
|
callback_ok = FALSE;
|
|
}
|
|
}
|
|
if (++callback_roll >= 32)
|
|
callback_roll = 0;
|
|
}
|
|
else
|
|
{
|
|
callback_ok = FALSE;
|
|
}
|
|
last_step = step;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void mitel_cm7291_side_1_tests(void)
|
|
{
|
|
int i;
|
|
int j;
|
|
int len;
|
|
int sample;
|
|
const char *s;
|
|
char digit[2];
|
|
char buf[128 + 1];
|
|
int actual;
|
|
int nplus;
|
|
int nminus;
|
|
float rrb;
|
|
float rcfo;
|
|
dtmf_rx_state_t *dtmf_state;
|
|
awgn_state_t noise_source;
|
|
logging_state_t *logging;
|
|
|
|
dtmf_state = dtmf_rx_init(NULL, NULL, NULL);
|
|
if (use_dialtone_filter || max_forward_twist >= 0 || max_reverse_twist >= 0)
|
|
dtmf_rx_parms(dtmf_state, use_dialtone_filter, max_forward_twist, max_reverse_twist, -99);
|
|
logging = dtmf_rx_get_logging_state(dtmf_state);
|
|
span_log_set_level(logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW);
|
|
span_log_set_tag(logging, "DTMF-rx");
|
|
|
|
/* Test 1: Mitel's test 1 isn't really a test. Its a calibration step,
|
|
which has no meaning here. */
|
|
printf("Test 1: Calibration\n");
|
|
printf(" Passed\n");
|
|
|
|
/* Test 2: Decode check
|
|
This is a sanity check, that all digits are reliably detected
|
|
under ideal conditions. Each possible digit repeated 10 times,
|
|
with 50ms bursts. The level of each tone is about 6dB down from clip.
|
|
6dB down actually causes trouble with G.726, so we use 7dB down. */
|
|
printf("Test 2: Decode check\n");
|
|
my_dtmf_gen_init(0.0f, -4, 0.0f, -4, 50, 50);
|
|
s = ALL_POSSIBLE_DIGITS;
|
|
digit[1] = '\0';
|
|
while (*s)
|
|
{
|
|
digit[0] = *s++;
|
|
for (i = 0; i < 10; i++)
|
|
{
|
|
len = my_dtmf_generate(amp, digit);
|
|
codec_munge(munge, amp, len);
|
|
dtmf_rx(dtmf_state, amp, len);
|
|
|
|
actual = dtmf_rx_get(dtmf_state, buf, 128);
|
|
|
|
if (actual != 1 || buf[0] != digit[0])
|
|
{
|
|
printf(" Sent '%s'\n", digit);
|
|
printf(" Received '%s'\n", buf);
|
|
printf(" Failed\n");
|
|
exit(2);
|
|
}
|
|
}
|
|
}
|
|
printf(" Passed\n");
|
|
|
|
/* Test 3: Recognition bandwidth and channel centre frequency check.
|
|
Use only the diagonal pairs of tones (digits 1, 5, 9 and D). Each
|
|
tone pair requires four test to complete the check, making 16
|
|
sections overall. Each section contains 40 pulses of
|
|
50ms duration, with an amplitude of -20dB from clip per
|
|
frequency.
|
|
|
|
Four sections covering the tests for one tone (1 digit) are:
|
|
a. H frequency at 0% deviation from center, L frequency at +0.1%.
|
|
L frequency is then increments in +01.% steps up to +4%. The
|
|
number of tone bursts is noted and designated N+.
|
|
b. H frequency at 0% deviation, L frequency at -0.1%. L frequency
|
|
is then incremental in -0.1% steps, up to -4%. The number of
|
|
tone bursts is noted and designated N-.
|
|
c. The test in (a) is repeated with the L frequency at 0% and the
|
|
H frequency varied up to +4%.
|
|
d. The test in (b) is repeated with the L frequency and 0% and the
|
|
H frequency varied to -4%.
|
|
|
|
Receiver Recognition Bandwidth (RRB) is calculated as follows:
|
|
RRB% = (N+ + N-)/10
|
|
Receiver Center Frequency Offset (RCFO) is calculated as follows:
|
|
RCFO% = X + (N+ - N-)/20
|
|
|
|
Note that this test doesn't test what it says it is testing at all,
|
|
and the results are quite inaccurate, if not a downright lie! However,
|
|
it follows the Mitel procedure, so how can it be bad? :)
|
|
*/
|
|
printf("Test 3: Recognition bandwidth and channel centre frequency check\n");
|
|
s = "159D";
|
|
digit[1] = '\0';
|
|
while (*s)
|
|
{
|
|
digit[0] = *s++;
|
|
for (nplus = 0, i = 1; i <= 60; i++)
|
|
{
|
|
my_dtmf_gen_init((float) i/1000.0f, -17, 0.0f, -17, 50, 50);
|
|
len = my_dtmf_generate(amp, digit);
|
|
codec_munge(munge, amp, len);
|
|
dtmf_rx(dtmf_state, amp, len);
|
|
nplus += dtmf_rx_get(dtmf_state, buf, 128);
|
|
}
|
|
for (nminus = 0, i = -1; i >= -60; i--)
|
|
{
|
|
my_dtmf_gen_init((float) i/1000.0f, -17, 0.0f, -17, 50, 50);
|
|
len = my_dtmf_generate(amp, digit);
|
|
codec_munge(munge, amp, len);
|
|
dtmf_rx(dtmf_state, amp, len);
|
|
nminus += dtmf_rx_get(dtmf_state, buf, 128);
|
|
}
|
|
rrb = (float) (nplus + nminus)/10.0f;
|
|
rcfo = (float) (nplus - nminus)/10.0f;
|
|
printf(" %c (low) rrb = %5.2f%%, rcfo = %5.2f%%, max -ve = %5.2f, max +ve = %5.2f\n",
|
|
digit[0],
|
|
rrb,
|
|
rcfo,
|
|
(float) nminus/10.0f,
|
|
(float) nplus/10.0f);
|
|
if (rrb < 3.0f + rcfo || rrb >= 15.0f + rcfo)
|
|
{
|
|
printf(" Failed\n");
|
|
exit(2);
|
|
}
|
|
|
|
for (nplus = 0, i = 1; i <= 60; i++)
|
|
{
|
|
my_dtmf_gen_init(0.0f, -17, (float) i/1000.0f, -17, 50, 50);
|
|
len = my_dtmf_generate(amp, digit);
|
|
codec_munge(munge, amp, len);
|
|
dtmf_rx(dtmf_state, amp, len);
|
|
nplus += dtmf_rx_get(dtmf_state, buf, 128);
|
|
}
|
|
for (nminus = 0, i = -1; i >= -60; i--)
|
|
{
|
|
my_dtmf_gen_init(0.0f, -17, (float) i/1000.0f, -17, 50, 50);
|
|
len = my_dtmf_generate(amp, digit);
|
|
codec_munge(munge, amp, len);
|
|
dtmf_rx(dtmf_state, amp, len);
|
|
nminus += dtmf_rx_get(dtmf_state, buf, 128);
|
|
}
|
|
rrb = (float) (nplus + nminus)/10.0f;
|
|
rcfo = (float) (nplus - nminus)/10.0f;
|
|
printf(" %c (high) rrb = %5.2f%%, rcfo = %5.2f%%, max -ve = %5.2f, max +ve = %5.2f\n",
|
|
digit[0],
|
|
rrb,
|
|
rcfo,
|
|
(float) nminus/10.0f,
|
|
(float) nplus/10.0f);
|
|
if (rrb < 3.0f + rcfo || rrb >= 15.0f + rcfo)
|
|
{
|
|
printf(" Failed\n");
|
|
exit(2);
|
|
}
|
|
}
|
|
printf(" Passed\n");
|
|
|
|
/* Test 4: Acceptable amplitude ratio (twist).
|
|
Use only the diagonal pairs of tones (digits 1, 5, 9 and D).
|
|
There are eight sections to the test. Each section contains 200
|
|
pulses with a 50ms duration for each pulse. Initially the amplitude
|
|
of both tones is 6dB down from clip. The two sections to test one
|
|
tone pair are:
|
|
|
|
a. Standard Twist: H tone amplitude is maintained at -6dB from clip,
|
|
L tone amplitude is attenuated gradually until the amplitude ratio
|
|
L/H is -20dB. Note the number of responses from the receiver.
|
|
b. Reverse Twist: L tone amplitude is maintained at -6dB from clip,
|
|
H tone amplitude is attenuated gradually until the amplitude ratio
|
|
is 20dB. Note the number of responses from the receiver.
|
|
|
|
All tone bursts are of 50ms duration.
|
|
|
|
The Acceptable Amplitude Ratio in dB is equal to the number of
|
|
responses registered in (a) or (b), divided by 10.
|
|
|
|
TODO: This is supposed to work in 1/10dB steps, but here I used 1dB
|
|
steps, as the current tone generator has its amplitude set in
|
|
1dB steps.
|
|
*/
|
|
printf("Test 4: Acceptable amplitude ratio (twist)\n");
|
|
s = "159D";
|
|
digit[1] = '\0';
|
|
while (*s)
|
|
{
|
|
digit[0] = *s++;
|
|
for (nplus = 0, i = -30; i >= -230; i--)
|
|
{
|
|
my_dtmf_gen_init(0.0f, -3, 0.0f, i/10, 50, 50);
|
|
|
|
len = my_dtmf_generate(amp, digit);
|
|
codec_munge(munge, amp, len);
|
|
dtmf_rx(dtmf_state, amp, len);
|
|
nplus += dtmf_rx_get(dtmf_state, buf, 128);
|
|
}
|
|
printf(" %c normal twist = %.2fdB\n", digit[0], (float) nplus/10.0);
|
|
if (nplus < 80)
|
|
{
|
|
printf(" Failed\n");
|
|
exit(2);
|
|
}
|
|
for (nminus = 0, i = -30; i >= -230; i--)
|
|
{
|
|
my_dtmf_gen_init(0.0f, i/10, 0.0f, -3, 50, 50);
|
|
|
|
len = my_dtmf_generate(amp, digit);
|
|
codec_munge(munge, amp, len);
|
|
dtmf_rx(dtmf_state, amp, len);
|
|
nminus += dtmf_rx_get(dtmf_state, buf, 128);
|
|
}
|
|
printf(" %c reverse twist = %.2fdB\n", digit[0], (float) nminus/10.0);
|
|
if (nminus < 40)
|
|
{
|
|
printf(" Failed\n");
|
|
exit(2);
|
|
}
|
|
}
|
|
printf(" Passed\n");
|
|
|
|
/* Test 5: Dynamic range
|
|
This test utilizes tone pair L1 H1 (digit 1). Thirty-five tone pair
|
|
pulses are transmitted, with both frequencies stating at -6dB from
|
|
clip. The amplitude of each is gradually attenuated by -35dB at a
|
|
rate of 1dB per pulse. The Dynamic Range in dB is equal to the
|
|
number of responses from the receiver during the test.
|
|
|
|
Well not really, but that is the Mitel test. Lets sweep a bit further,
|
|
and see what the real range is */
|
|
printf("Test 5: Dynamic range\n");
|
|
for (nplus = 0, i = +3; i >= -50; i--)
|
|
{
|
|
my_dtmf_gen_init(0.0f, i, 0.0f, i, 50, 50);
|
|
|
|
len = my_dtmf_generate(amp, "1");
|
|
codec_munge(munge, amp, len);
|
|
dtmf_rx(dtmf_state, amp, len);
|
|
nplus += dtmf_rx_get(dtmf_state, buf, 128);
|
|
}
|
|
printf(" Dynamic range = %ddB\n", nplus);
|
|
/* We ought to set some pass/fail condition, even if Mitel did not. If
|
|
we don't, regression testing is weakened. */
|
|
if (nplus < 35)
|
|
{
|
|
printf(" Failed\n");
|
|
exit(2);
|
|
}
|
|
printf(" Passed\n");
|
|
|
|
/* Test 6: Guard time
|
|
This test utilizes tone pair L1 H1 (digit 1). Four hundred pulses
|
|
are transmitted at an amplitude of -6dB from clip per frequency.
|
|
Pulse duration starts at 49ms and is gradually reduced to 10ms.
|
|
Guard time in ms is equal to (500 - number of responses)/10.
|
|
|
|
That is the Mitel test, and we will follow it. Its totally bogus,
|
|
though. Just what the heck is a pass or fail here? */
|
|
|
|
printf("Test 6: Guard time\n");
|
|
for (nplus = 0, i = 490; i >= 100; i--)
|
|
{
|
|
my_dtmf_gen_init(0.0f, -3, 0.0f, -3, i/10, 50);
|
|
|
|
len = my_dtmf_generate(amp, "1");
|
|
codec_munge(munge, amp, len);
|
|
dtmf_rx(dtmf_state, amp, len);
|
|
nplus += dtmf_rx_get(dtmf_state, buf, 128);
|
|
}
|
|
printf(" Guard time = %dms\n", (500 - nplus)/10);
|
|
printf(" Passed\n");
|
|
|
|
/* Test 7: Acceptable signal to noise ratio
|
|
This test utilizes tone pair L1 H1, transmitted on a noise background.
|
|
The test consists of three sections in which the tone pair is
|
|
transmitted 1000 times at an amplitude -6dB from clip per frequency,
|
|
but with a different white noise level for each section. The first
|
|
level is -24dBV, the second -18dBV and the third -12dBV.. The
|
|
acceptable signal to noise ratio is the lowest ratio of signal
|
|
to noise in the test where the receiver responds to all 1000 pulses.
|
|
|
|
Well, that is the Mitel test, but it doesn't tell you what the
|
|
decoder can really do. Lets do a more comprehensive test */
|
|
|
|
printf("Test 7: Acceptable signal to noise ratio\n");
|
|
my_dtmf_gen_init(0.0f, -4, 0.0f, -4, 50, 50);
|
|
|
|
for (j = -13; j > -50; j--)
|
|
{
|
|
awgn_init_dbm0(&noise_source, 1234567, (float) j);
|
|
for (i = 0; i < 1000; i++)
|
|
{
|
|
len = my_dtmf_generate(amp, "1");
|
|
|
|
// TODO: Clip
|
|
for (sample = 0; sample < len; sample++)
|
|
amp[sample] = saturate(amp[sample] + awgn(&noise_source));
|
|
|
|
codec_munge(munge, amp, len);
|
|
dtmf_rx(dtmf_state, amp, len);
|
|
|
|
if (dtmf_rx_get(dtmf_state, buf, 128) != 1)
|
|
break;
|
|
}
|
|
if (i == 1000)
|
|
break;
|
|
}
|
|
printf(" Acceptable S/N ratio is %ddB\n", -4 - j);
|
|
if (-4 - j > 26)
|
|
{
|
|
printf(" Failed\n");
|
|
exit(2);
|
|
}
|
|
printf(" Passed\n");
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void mitel_cm7291_side_2_and_bellcore_tests(void)
|
|
{
|
|
int i;
|
|
int j;
|
|
int len;
|
|
int hits;
|
|
int hit_types[256];
|
|
char buf[128 + 1];
|
|
SNDFILE *inhandle;
|
|
int frames;
|
|
dtmf_rx_state_t *dtmf_state;
|
|
logging_state_t *logging;
|
|
|
|
dtmf_state = dtmf_rx_init(NULL, NULL, NULL);
|
|
if (use_dialtone_filter || max_forward_twist >= 0 || max_reverse_twist >= 0)
|
|
dtmf_rx_parms(dtmf_state, use_dialtone_filter, max_forward_twist, max_reverse_twist, -99);
|
|
logging = dtmf_rx_get_logging_state(dtmf_state);
|
|
span_log_set_level(logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW);
|
|
span_log_set_tag(logging, "DTMF-rx");
|
|
|
|
/* The remainder of the Mitel tape is the talk-off test */
|
|
/* Here we use the Bellcore test tapes (much tougher), in six
|
|
files - 1 from each side of the original 3 cassette tapes */
|
|
/* Bellcore say you should get no more than 470 false detections with
|
|
a good receiver. Dialogic claim 20. Of course, we can do better than
|
|
that, eh? */
|
|
printf("Test 8: Talk-off test\n");
|
|
memset(hit_types, '\0', sizeof(hit_types));
|
|
for (j = 0; bellcore_files[j][0]; j++)
|
|
{
|
|
if ((inhandle = sf_open_telephony_read(bellcore_files[j], 1)) == NULL)
|
|
{
|
|
printf(" Cannot open speech file '%s'\n", bellcore_files[j]);
|
|
exit(2);
|
|
}
|
|
hits = 0;
|
|
while ((frames = sf_readf_short(inhandle, amp, SAMPLE_RATE)))
|
|
{
|
|
dtmf_rx(dtmf_state, amp, frames);
|
|
len = dtmf_rx_get(dtmf_state, buf, 128);
|
|
if (len > 0)
|
|
{
|
|
for (i = 0; i < len; i++)
|
|
hit_types[(int) buf[i]]++;
|
|
hits += len;
|
|
}
|
|
}
|
|
if (sf_close_telephony(inhandle))
|
|
{
|
|
printf(" Cannot close speech file '%s'\n", bellcore_files[j]);
|
|
exit(2);
|
|
}
|
|
printf(" File %d gave %d false hits.\n", j + 1, hits);
|
|
}
|
|
for (i = 0, j = 0; i < 256; i++)
|
|
{
|
|
if (hit_types[i])
|
|
{
|
|
printf(" Digit %c had %d false hits\n", i, hit_types[i]);
|
|
j += hit_types[i];
|
|
}
|
|
}
|
|
printf(" %d hits in total\n", j);
|
|
if (j > 470)
|
|
{
|
|
printf(" Failed\n");
|
|
exit(2);
|
|
}
|
|
printf(" Passed\n");
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void dial_tone_tolerance_tests(void)
|
|
{
|
|
int i;
|
|
int j;
|
|
int len;
|
|
int sample;
|
|
char buf[128 + 1];
|
|
dtmf_rx_state_t *dtmf_state;
|
|
tone_gen_descriptor_t dial_tone_desc;
|
|
tone_gen_state_t dial_tone;
|
|
logging_state_t *logging;
|
|
|
|
dtmf_state = dtmf_rx_init(NULL, NULL, NULL);
|
|
if (use_dialtone_filter || max_forward_twist >= 0 || max_reverse_twist >= 0)
|
|
dtmf_rx_parms(dtmf_state, use_dialtone_filter, max_forward_twist, max_reverse_twist, -99);
|
|
logging = dtmf_rx_get_logging_state(dtmf_state);
|
|
span_log_set_level(logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW);
|
|
span_log_set_tag(logging, "DTMF-rx");
|
|
|
|
/* Test dial tone tolerance */
|
|
printf("Test: Dial tone tolerance.\n");
|
|
my_dtmf_gen_init(0.0f, -15, 0.0f, -15, DEFAULT_DTMF_TX_ON_TIME, DEFAULT_DTMF_TX_OFF_TIME);
|
|
|
|
for (j = -30; j < -3; j++)
|
|
{
|
|
tone_gen_descriptor_init(&dial_tone_desc, 350, j, 440, j, 1, 0, 0, 0, TRUE);
|
|
tone_gen_init(&dial_tone, &dial_tone_desc);
|
|
for (i = 0; i < 10; i++)
|
|
{
|
|
len = my_dtmf_generate(amp, ALL_POSSIBLE_DIGITS);
|
|
tone_gen(&dial_tone, amp2, len);
|
|
|
|
for (sample = 0; sample < len; sample++)
|
|
amp[sample] = saturate(amp[sample] + amp2[sample]);
|
|
codec_munge(munge, amp, len);
|
|
dtmf_rx(dtmf_state, amp, len);
|
|
|
|
if (dtmf_rx_get(dtmf_state, buf, 128) != strlen(ALL_POSSIBLE_DIGITS))
|
|
break;
|
|
}
|
|
if (i != 10)
|
|
break;
|
|
}
|
|
printf(" Acceptable signal to dial tone ratio is %ddB\n", -15 - j);
|
|
if ((use_dialtone_filter && (-15 - j) > -12)
|
|
||
|
|
(!use_dialtone_filter && (-15 - j) > 10))
|
|
{
|
|
printf(" Failed\n");
|
|
exit(2);
|
|
}
|
|
printf(" Passed\n");
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void callback_function_tests(void)
|
|
{
|
|
int i;
|
|
int j;
|
|
int len;
|
|
int sample;
|
|
dtmf_rx_state_t *dtmf_state;
|
|
logging_state_t *logging;
|
|
|
|
/* Test the callback mode for delivering detected digits */
|
|
printf("Test: Callback digit delivery mode.\n");
|
|
callback_hit = FALSE;
|
|
callback_ok = TRUE;
|
|
callback_roll = 0;
|
|
dtmf_state = dtmf_rx_init(NULL, digit_delivery, (void *) 0x12345678);
|
|
if (use_dialtone_filter || max_forward_twist >= 0 || max_reverse_twist >= 0)
|
|
dtmf_rx_parms(dtmf_state, use_dialtone_filter, max_forward_twist, max_reverse_twist, -99);
|
|
logging = dtmf_rx_get_logging_state(dtmf_state);
|
|
span_log_set_level(logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW);
|
|
span_log_set_tag(logging, "DTMF-rx");
|
|
|
|
my_dtmf_gen_init(0.0f, DEFAULT_DTMF_TX_LEVEL, 0.0f, DEFAULT_DTMF_TX_LEVEL, DEFAULT_DTMF_TX_ON_TIME, DEFAULT_DTMF_TX_OFF_TIME);
|
|
for (i = 1; i < 10; i++)
|
|
{
|
|
len = 0;
|
|
for (j = 0; j < i; j++)
|
|
len += my_dtmf_generate(amp + len, ALL_POSSIBLE_DIGITS);
|
|
dtmf_rx(dtmf_state, amp, len);
|
|
if (!callback_hit || !callback_ok)
|
|
break;
|
|
}
|
|
if (!callback_hit || !callback_ok)
|
|
{
|
|
printf(" Failed\n");
|
|
exit(2);
|
|
}
|
|
printf(" Passed\n");
|
|
|
|
/* Test the realtime callback mode for reporting detected digits */
|
|
printf("Test: Realtime callback digit delivery mode.\n");
|
|
callback_hit = FALSE;
|
|
callback_ok = TRUE;
|
|
callback_roll = 0;
|
|
dtmf_rx_init(dtmf_state, NULL, NULL);
|
|
dtmf_rx_set_realtime_callback(dtmf_state, digit_status, (void *) 0x12345678);
|
|
if (use_dialtone_filter || max_forward_twist >= 0 || max_reverse_twist >= 0)
|
|
dtmf_rx_parms(dtmf_state, use_dialtone_filter, max_forward_twist, max_reverse_twist, -99);
|
|
logging = dtmf_rx_get_logging_state(dtmf_state);
|
|
span_log_set_level(logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW);
|
|
span_log_set_tag(logging, "DTMF-rx");
|
|
|
|
my_dtmf_gen_init(0.0f, DEFAULT_DTMF_TX_LEVEL, 0.0f, DEFAULT_DTMF_TX_LEVEL, DEFAULT_DTMF_TX_ON_TIME, DEFAULT_DTMF_TX_OFF_TIME);
|
|
step = 0;
|
|
for (i = 1; i < 10; i++)
|
|
{
|
|
len = 0;
|
|
for (j = 0; j < i; j++)
|
|
len += my_dtmf_generate(amp + len, ALL_POSSIBLE_DIGITS);
|
|
for (sample = 0, j = SAMPLES_PER_CHUNK; sample < len; sample += SAMPLES_PER_CHUNK, j = ((len - sample) >= SAMPLES_PER_CHUNK) ? SAMPLES_PER_CHUNK : (len - sample))
|
|
{
|
|
dtmf_rx(dtmf_state, &[sample], j);
|
|
if (!callback_ok)
|
|
break;
|
|
step += j;
|
|
}
|
|
if (!callback_hit || !callback_ok)
|
|
break;
|
|
}
|
|
if (!callback_hit || !callback_ok)
|
|
{
|
|
printf(" Failed\n");
|
|
exit(2);
|
|
}
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
static void decode_test(const char *test_file)
|
|
{
|
|
int16_t amp[SAMPLES_PER_CHUNK];
|
|
SNDFILE *inhandle;
|
|
dtmf_rx_state_t *dtmf_state;
|
|
char buf[128 + 1];
|
|
int actual;
|
|
int samples;
|
|
int total;
|
|
logging_state_t *logging;
|
|
|
|
dtmf_state = dtmf_rx_init(NULL, NULL, NULL);
|
|
if (use_dialtone_filter || max_forward_twist >= 0 || max_reverse_twist >= 0)
|
|
dtmf_rx_parms(dtmf_state, use_dialtone_filter, max_forward_twist, max_reverse_twist, -99);
|
|
logging = dtmf_rx_get_logging_state(dtmf_state);
|
|
span_log_set_level(logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW);
|
|
span_log_set_tag(logging, "DTMF-rx");
|
|
|
|
/* We will decode the audio from a file. */
|
|
|
|
if ((inhandle = sf_open_telephony_read(decode_test_file, 1)) == NULL)
|
|
{
|
|
fprintf(stderr, " Cannot open audio file '%s'\n", decode_test_file);
|
|
exit(2);
|
|
}
|
|
|
|
total = 0;
|
|
while ((samples = sf_readf_short(inhandle, amp, SAMPLES_PER_CHUNK)) > 0)
|
|
{
|
|
codec_munge(munge, amp, samples);
|
|
dtmf_rx(dtmf_state, amp, samples);
|
|
//printf("Status 0x%X\n", dtmf_rx_status(dtmf_state));
|
|
if ((actual = dtmf_rx_get(dtmf_state, buf, 128)) > 0)
|
|
printf("Received '%s'\n", buf);
|
|
total += actual;
|
|
}
|
|
printf("%d digits received\n", total);
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
int duration;
|
|
time_t now;
|
|
int channel_codec;
|
|
int opt;
|
|
|
|
use_dialtone_filter = FALSE;
|
|
channel_codec = MUNGE_CODEC_NONE;
|
|
decode_test_file = NULL;
|
|
max_forward_twist = -1;
|
|
max_reverse_twist = -1;
|
|
while ((opt = getopt(argc, argv, "c:d:F:fR:")) != -1)
|
|
{
|
|
switch (opt)
|
|
{
|
|
case 'c':
|
|
channel_codec = atoi(optarg);
|
|
break;
|
|
case 'd':
|
|
decode_test_file = optarg;
|
|
break;
|
|
case 'F':
|
|
max_forward_twist = atoi(optarg);
|
|
break;
|
|
case 'f':
|
|
use_dialtone_filter = TRUE;
|
|
break;
|
|
case 'R':
|
|
max_reverse_twist = atoi(optarg);
|
|
break;
|
|
default:
|
|
//usage();
|
|
exit(2);
|
|
break;
|
|
}
|
|
}
|
|
munge = codec_munge_init(channel_codec, 0);
|
|
|
|
if (decode_test_file)
|
|
{
|
|
decode_test(decode_test_file);
|
|
}
|
|
else
|
|
{
|
|
time(&now);
|
|
mitel_cm7291_side_1_tests();
|
|
mitel_cm7291_side_2_and_bellcore_tests();
|
|
dial_tone_tolerance_tests();
|
|
callback_function_tests();
|
|
printf(" Passed\n");
|
|
duration = time(NULL) - now;
|
|
printf("Tests passed in %ds\n", duration);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
/*- End of function --------------------------------------------------------*/
|
|
/*- End of file ------------------------------------------------------------*/
|