]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
dsp.c: Add support for detecting R2 signaling tones.
authorNaveen Albert <asterisk@phreaknet.org>
Sun, 1 Mar 2026 19:20:13 +0000 (14:20 -0500)
committergithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Tue, 3 Mar 2026 22:07:26 +0000 (22:07 +0000)
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

include/asterisk/dsp.h
main/dsp.c

index b641a998e0f695badb7490ecc9b5cf4e736e1524..1f64c40b67fba85a4e92632ec424727ab7af318c 100644 (file)
@@ -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 */
index bf518c9a5ff2c688656f8c8b5a7d5b3775df26bc..e90b5ad41d7c385866e35fea7182e4d407a22d8f 100644 (file)
@@ -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;