Commit 7d3fb10c02 for asterisk.org
commit 7d3fb10c02c76296776f5123ba4c0ae2b733495e
Author: Naveen Albert <asterisk@phreaknet.org>
Date: Sun Mar 1 14:20:13 2026 -0500
dsp.c: Add support for detecting R2 signaling tones.
Extend the existing DTMF/MF tone detection support by adding support
for R2 tones, another variant of MF (R1) signaling. Both forward
and backward signaling are supported.
Resolves: #1804
diff --git a/include/asterisk/dsp.h b/include/asterisk/dsp.h
index b641a998e0..1f64c40b67 100644
--- a/include/asterisk/dsp.h
+++ b/include/asterisk/dsp.h
@@ -30,6 +30,8 @@
#define DSP_DIGITMODE_DTMF 0 /*!< Detect DTMF digits */
#define DSP_DIGITMODE_MF 1 /*!< Detect MF digits */
+#define DSP_DIGITMODE_R2_FORWARD (1 << 2) /*!< Detect R2 forward signaling */
+#define DSP_DIGITMODE_R2_BACKWARD (1 << 3) /*!< Detect R2 backward signaling */
#define DSP_DIGITMODE_NOQUELCH (1 << 8) /*!< Do not quelch DTMF from in-band */
#define DSP_DIGITMODE_MUTECONF (1 << 9) /*!< Mute conference */
diff --git a/main/dsp.c b/main/dsp.c
index bf518c9a5f..e90b5ad41d 100644
--- a/main/dsp.c
+++ b/main/dsp.c
@@ -201,6 +201,10 @@ enum gsamp_thresh {
#define BELL_MF_TWIST 4.0 /* 6dB */
#define BELL_MF_RELATIVE_PEAK 12.6 /* 11dB */
+#define R2_MF_THRESHOLD 5.0e8f
+#define R2_MF_TWIST 5.0f /* 7dB */
+#define R2_MF_RELATIVE_PEAK 12.6f /* 11dB */
+
#if defined(BUSYDETECT_TONEONLY) && defined(BUSYDETECT_COMPARE_TONE_AND_SILENCE)
#error You cant use BUSYDETECT_TONEONLY together with BUSYDETECT_COMPARE_TONE_AND_SILENCE
#endif
@@ -228,6 +232,9 @@ enum gsamp_thresh {
/* DTMF goertzel size */
#define DTMF_GSIZE 102
+/* R2 goertzel size */
+#define R2_GSIZE 133
+
/* How many successive hits needed to consider begin of a digit
* IE. Override with dtmf_hits_to_begin=4 in dsp.conf
*/
@@ -325,8 +332,15 @@ static const float dtmf_col[] = {
static const float mf_tones[] = {
700.0, 900.0, 1100.0, 1300.0, 1500.0, 1700.0
};
+static const float r2_forward_tones[] = {
+ 1380.0, 1500.0, 1620.0, 1740.0, 1860.0, 1980.0
+};
+static const float r2_backward_tones[] = {
+ 1140.0, 1020.0, 900.0, 780.0, 660.0, 540.0
+};
static const char dtmf_positions[] = "123A" "456B" "789C" "*0#D";
static const char bell_mf_positions[] = "1247C-358A--69*---0B----#";
+static const char r2_mf_positions[] = "1247B-358C--69D---0E----F"; /* Use codes '1' to 'F' for the R2 signals 1 to 15, except for signal 'A'. Use '0' for this, so the codes match the digits 0-9. */
static int thresholds[THRESHOLD_MAX];
static float dtmf_normal_twist; /* AT&T = 8dB */
static float dtmf_reverse_twist; /* AT&T = 4dB */
@@ -558,14 +572,30 @@ static void ast_mf_detect_init(mf_detect_state_t *s, unsigned int sample_rate)
s->current_hit = 0;
}
-static void ast_digit_detect_init(digit_detect_state_t *s, int mf, unsigned int sample_rate)
+static void ast_r2_detect_init(mf_detect_state_t *s, unsigned int sample_rate, int backward)
+{
+ int i;
+
+ for (i = 0; i < 6; i++) {
+ goertzel_init(&s->tone_out[i], backward ? r2_backward_tones[i] : r2_forward_tones[i], sample_rate);
+ }
+ s->hits[0] = s->hits[1] = s->hits[2] = s->hits[3] = s->hits[4] = 0;
+ s->current_sample = 0;
+ s->current_hit = 0;
+}
+
+static void ast_digit_detect_init(digit_detect_state_t *s, int digitmode, unsigned int sample_rate)
{
s->current_digits = 0;
s->detected_digits = 0;
s->lost_digits = 0;
s->digits[0] = '\0';
- if (mf) {
+ if (digitmode & DSP_DIGITMODE_R2_FORWARD) {
+ ast_r2_detect_init(&s->td.mf, sample_rate, 0);
+ } else if (digitmode & DSP_DIGITMODE_R2_BACKWARD) {
+ ast_r2_detect_init(&s->td.mf, sample_rate, 1);
+ } else if (digitmode & DSP_DIGITMODE_MF) {
ast_mf_detect_init(&s->td.mf, sample_rate);
} else {
ast_dtmf_detect_init(&s->td.dtmf, sample_rate);
@@ -1055,6 +1085,156 @@ static int mf_detect(struct ast_dsp *dsp, digit_detect_state_t *s, int16_t amp[]
return (s->td.mf.current_hit); /* return the debounced hit */
}
+static int r2_detect(struct ast_dsp *dsp, digit_detect_state_t *s, int16_t amp[],
+ int samples, int squelch, int relax, const char *r2_positions)
+{
+ float energy[6];
+ int best;
+ int second_best;
+ int i;
+ int j;
+ int sample;
+ short samp;
+ int hit;
+ int limit;
+ fragment_t mute = {0, 0};
+
+ if (squelch && s->td.mf.mute_samples > 0) {
+ mute.end = (s->td.mf.mute_samples < samples) ? s->td.mf.mute_samples : samples;
+ s->td.mf.mute_samples -= mute.end;
+ }
+
+ hit = 0;
+ for (sample = 0; sample < samples; sample = limit) {
+ if ((samples - sample) >= (R2_GSIZE - s->td.mf.current_sample)) {
+ limit = sample + (R2_GSIZE - s->td.mf.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++) {
+ /* With GCC 2.95, the following unrolled code seems to take about 35%
+ (rough estimate) as long as a neat little 0-3 loop */
+ samp = amp[j];
+ goertzel_sample(s->td.mf.tone_out, samp);
+ goertzel_sample(s->td.mf.tone_out + 1, samp);
+ goertzel_sample(s->td.mf.tone_out + 2, samp);
+ goertzel_sample(s->td.mf.tone_out + 3, samp);
+ goertzel_sample(s->td.mf.tone_out + 4, samp);
+ goertzel_sample(s->td.mf.tone_out + 5, samp);
+ }
+ s->td.mf.current_sample += (limit - sample);
+ if (s->td.mf.current_sample < R2_GSIZE) {
+ continue;
+ }
+ /* We're at the end of an MF detection block. */
+ /* Find the two highest energies. The spec says to look for
+ two tones and two tones only. Taking this literally -ie
+ only two tones pass the minimum threshold - doesn't work
+ well. The sinc function mess, due to rectangular windowing
+ ensure that! Find the two highest energies and ensure they
+ are considerably stronger than any of the others. */
+ energy[0] = goertzel_result(&s->td.mf.tone_out[0]);
+ energy[1] = goertzel_result(&s->td.mf.tone_out[1]);
+ if (energy[0] > energy[1]) {
+ best = 0;
+ second_best = 1;
+ } else {
+ best = 1;
+ second_best = 0;
+ }
+ /*endif*/
+ for (i = 2; i < 6; i++) {
+ energy[i] = goertzel_result(&s->td.mf.tone_out[i]);
+ if (energy[i] >= energy[best]) {
+ second_best = best;
+ best = i;
+ } else if (energy[i] >= energy[second_best]) {
+ second_best = i;
+ }
+ }
+ /* Basic signal level and twist tests */
+ hit = 0;
+ if (energy[best] >= R2_MF_THRESHOLD && energy[second_best] >= R2_MF_THRESHOLD
+ && energy[best] < energy[second_best]*R2_MF_TWIST
+ && energy[best] * R2_MF_TWIST > energy[second_best]) {
+ /* Relative peak test */
+ hit = -1;
+ for (i = 0; i < 6; i++) {
+ if (i != best && i != second_best) {
+ if (energy[i]*R2_MF_RELATIVE_PEAK >= energy[second_best]) {
+ /* The best two are not clearly the best */
+ hit = 0;
+ break;
+ }
+ }
+ }
+ }
+ if (hit) {
+ /* Get the values into ascending order */
+ if (second_best < best) {
+ i = best;
+ best = second_best;
+ second_best = i;
+ }
+ best = best * 5 + second_best - 1;
+ hit = r2_positions[best];
+
+ if (relax) {
+ /* Continuous tone detection.
+ * The application is responsible for debouncing. */
+ store_digit(s, hit);
+ } else {
+ /* Discrete digit detection.
+ * Look for two successive similar results, with something different preceding.
+ * This is a subset of the MF logic and seems to ensure discrete digit detection. */
+ if (hit == s->td.mf.hits[4] && hit == s->td.mf.hits[3] && hit != s->td.mf.hits[2]) {
+ store_digit(s, hit);
+ }
+ }
+ } else {
+ hit = 0;
+ }
+
+ s->td.mf.current_hit = hit;
+
+ if (!relax) {
+ s->td.mf.hits[0] = s->td.mf.hits[1];
+ s->td.mf.hits[1] = s->td.mf.hits[2];
+ s->td.mf.hits[2] = s->td.mf.hits[3];
+ s->td.mf.hits[3] = s->td.mf.hits[4];
+ s->td.mf.hits[4] = hit;
+ }
+
+ /* If we had a hit in this block, include it into mute fragment */
+ if (squelch && hit) {
+ if (mute.end < sample - R2_GSIZE) {
+ /* There is a gap between fragments */
+ mute_fragment(dsp, &mute);
+ mute.start = (sample > R2_GSIZE) ? (sample - R2_GSIZE) : 0;
+ }
+ mute.end = limit + R2_GSIZE;
+ }
+
+ /* Reinitialise the detector for the next block */
+ for (i = 0; i < 6; i++) {
+ goertzel_reset(&s->td.mf.tone_out[i]);
+ }
+ s->td.mf.current_sample = 0;
+ }
+
+ if (squelch && mute.end) {
+ if (mute.end > samples) {
+ s->td.mf.mute_samples = mute.end - samples;
+ mute.end = samples;
+ }
+ mute_fragment(dsp, &mute);
+ }
+
+ return (s->td.mf.current_hit);
+}
+
static inline int pair_there(float p1, float p2, float i1, float i2, float e)
{
/* See if p1 and p2 are there, relative to i1 and i2 and total energy */
@@ -1592,7 +1772,9 @@ struct ast_frame *ast_dsp_process(struct ast_channel *chan, struct ast_dsp *dsp,
}
if (dsp->features & (DSP_FEATURE_DIGIT_DETECT | DSP_FEATURE_BUSY_DETECT)) {
- if (dsp->digitmode & DSP_DIGITMODE_MF) {
+ if (dsp->digitmode & (DSP_DIGITMODE_R2_FORWARD | DSP_DIGITMODE_R2_BACKWARD)) {
+ digit = r2_detect(dsp, &dsp->digit_state, shortdata, len, (dsp->digitmode & DSP_DIGITMODE_NOQUELCH) == 0, (dsp->digitmode & DSP_DIGITMODE_RELAXDTMF), r2_mf_positions);
+ } else if (dsp->digitmode & DSP_DIGITMODE_MF) {
digit = mf_detect(dsp, &dsp->digit_state, shortdata, len, (dsp->digitmode & DSP_DIGITMODE_NOQUELCH) == 0, (dsp->digitmode & DSP_DIGITMODE_RELAXDTMF));
} else {
digit = dtmf_detect(dsp, &dsp->digit_state, shortdata, len, (dsp->digitmode & DSP_DIGITMODE_NOQUELCH) == 0, (dsp->digitmode & DSP_DIGITMODE_RELAXDTMF));
@@ -1610,7 +1792,6 @@ struct ast_frame *ast_dsp_process(struct ast_channel *chan, struct ast_dsp *dsp,
event_digit = dsp->digit_state.digits[0];
}
dsp->dtmf_began = 1;
-
} else if (dsp->digit_state.current_digits > 1 || digit != dsp->digit_state.digits[0]) {
/* Digit changed. This means digit we have reported with DTMF_BEGIN ended */
if (dsp->features & DSP_FEATURE_DIGIT_DETECT) {
@@ -1749,7 +1930,7 @@ static struct ast_dsp *__ast_dsp_new(unsigned int sample_rate)
dsp->sample_rate = sample_rate;
dsp->freqcount = 0;
/* Initialize digit detector */
- ast_digit_detect_init(&dsp->digit_state, dsp->digitmode & DSP_DIGITMODE_MF, dsp->sample_rate);
+ ast_digit_detect_init(&dsp->digit_state, dsp->digitmode, dsp->sample_rate);
dsp->display_inband_dtmf_warning = 1;
/* Initialize initial DSP progress detect parameters */
ast_dsp_prog_reset(dsp);
@@ -1816,7 +1997,7 @@ void ast_dsp_digitreset(struct ast_dsp *dsp)
int i;
dsp->dtmf_began = 0;
- if (dsp->digitmode & DSP_DIGITMODE_MF) {
+ if (dsp->digitmode & (DSP_DIGITMODE_MF | DSP_DIGITMODE_R2_FORWARD | DSP_DIGITMODE_R2_BACKWARD)) {
mf_detect_state_t *s = &dsp->digit_state.td.mf;
/* Reinitialise the detector for the next block */
for (i = 0; i < 6; i++) {
@@ -1858,16 +2039,18 @@ void ast_dsp_reset(struct ast_dsp *dsp)
dsp->ringtimeout = 0;
}
+#define DSP_DIGITMODES (DSP_DIGITMODE_DTMF | DSP_DIGITMODE_MF | DSP_DIGITMODE_R2_FORWARD | DSP_DIGITMODE_R2_BACKWARD)
+
int ast_dsp_set_digitmode(struct ast_dsp *dsp, int digitmode)
{
int new;
int old;
- old = dsp->digitmode & (DSP_DIGITMODE_DTMF | DSP_DIGITMODE_MF | DSP_DIGITMODE_MUTECONF | DSP_DIGITMODE_MUTEMAX);
- new = digitmode & (DSP_DIGITMODE_DTMF | DSP_DIGITMODE_MF | DSP_DIGITMODE_MUTECONF | DSP_DIGITMODE_MUTEMAX);
+ old = dsp->digitmode & (DSP_DIGITMODES | DSP_DIGITMODE_MUTECONF | DSP_DIGITMODE_MUTEMAX);
+ new = digitmode & (DSP_DIGITMODES | DSP_DIGITMODE_MUTECONF | DSP_DIGITMODE_MUTEMAX);
if (old != new) {
/* Must initialize structures if switching from MF to DTMF or vice-versa */
- ast_digit_detect_init(&dsp->digit_state, new & DSP_DIGITMODE_MF, dsp->sample_rate);
+ ast_digit_detect_init(&dsp->digit_state, new, dsp->sample_rate);
}
dsp->digitmode = digitmode;
return 0;