do v21 detection instead of CED detection for the fax gateway
authorMatthew Nicholson <mnicholson@digium.com>
Tue, 12 Jul 2011 15:23:24 +0000 (15:23 +0000)
committerMatthew Nicholson <mnicholson@digium.com>
Tue, 12 Jul 2011 15:23:24 +0000 (15:23 +0000)
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@327769 65c4cc65-6c06-0410-ace0-fbb531ad65f3

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

index 3334152..78c9a74 100644 (file)
@@ -45,7 +45,8 @@
 
 #define DSP_FAXMODE_DETECT_CNG (1 << 0)
 #define DSP_FAXMODE_DETECT_CED (1 << 1)
-#define DSP_FAXMODE_DETECT_ALL (DSP_FAXMODE_DETECT_CNG | DSP_FAXMODE_DETECT_CED)
+#define DSP_FAXMODE_DETECT_V21 (1 << 2)
+#define DSP_FAXMODE_DETECT_ALL (DSP_FAXMODE_DETECT_CNG | DSP_FAXMODE_DETECT_CED | DSP_FAXMODE_DETECT_V21)
 
 #define DSP_TONE_STATE_SILENCE  0
 #define DSP_TONE_STATE_RINGING  1
index ee18918..02cdc9d 100644 (file)
@@ -246,6 +246,20 @@ typedef struct
 
 typedef struct
 {
+       int block_size;
+       goertzel_state_t tone;
+       float energy;           /* Accumulated energy of the current block */
+       int samples_pending;    /* Samples remain to complete the current block */
+
+       float threshold;        /* Energy of the tone relative to energy from all other signals to consider a hit */
+
+       int hit_count;
+       int miss_count;
+
+} v21_detect_state_t;
+
+typedef struct
+{
        goertzel_state_t row_out[4];
        goertzel_state_t col_out[4];
        int hits_to_begin;              /* How many successive hits needed to consider begin of a digit */
@@ -391,6 +405,7 @@ struct ast_dsp {
        digit_detect_state_t digit_state;
        tone_detect_state_t cng_tone_state;
        tone_detect_state_t ced_tone_state;
+       v21_detect_state_t v21_state;
 };
 
 static void mute_fragment(struct ast_dsp *dsp, fragment_t *fragment)
@@ -463,10 +478,55 @@ static void ast_tone_detect_init(tone_detect_state_t *s, int freq, int duration,
        ast_debug(1, "Setup tone %d Hz, %d ms, block_size=%d, hits_required=%d\n", freq, duration, s->block_size, s->hits_required);
 }
 
+static void ast_v21_detect_init(v21_detect_state_t *s, unsigned int sample_rate)
+{
+       float x;
+       int periods_in_block;
+
+       /* If we want to remove tone, it is important to have block size not
+          to exceed frame size. Otherwise by the moment tone is detected it is too late
+          to squelch it from previous frames. Block size is 20ms at the given sample rate.*/
+       s->block_size = (20 * sample_rate) / 1000;
+
+       periods_in_block = s->block_size * 1850 / sample_rate;
+
+       /* Make sure we will have at least 5 periods at target frequency for analisys.
+          This may make block larger than expected packet and will make squelching impossible
+          but at least we will be detecting the tone */
+       if (periods_in_block < 5)
+               periods_in_block = 5;
+
+       /* Now calculate final block size. It will contain integer number of periods */
+       s->block_size = periods_in_block * sample_rate / 1850;
+
+       goertzel_init(&s->tone, 1850.0, s->block_size, sample_rate);
+
+       s->samples_pending = s->block_size;
+       s->hit_count = 0;
+       s->miss_count = 0;
+       s->energy = 0.0;
+
+       /* We want tone energy to be amp decibels above the rest of the signal (the noise).
+          According to Parseval's theorem the energy computed in time domain equals to energy
+          computed in frequency domain. So subtracting energy in the frequency domain (Goertzel result)
+          from the energy in the time domain we will get energy of the remaining signal (without the tone
+          we are detecting). We will be checking that
+               10*log(Ew / (Et - Ew)) > amp
+          Calculate threshold so that we will be actually checking
+               Ew > Et * threshold
+       */
+
+       x = pow(10.0, 16 / 10.0);
+       s->threshold = x / (x + 1);
+
+       ast_debug(1, "Setup v21 detector, block_size=%d\n", s->block_size);
+}
+
 static void ast_fax_detect_init(struct ast_dsp *s)
 {
        ast_tone_detect_init(&s->cng_tone_state, FAX_TONE_CNG_FREQ, FAX_TONE_CNG_DURATION, FAX_TONE_CNG_DB, s->sample_rate);
        ast_tone_detect_init(&s->ced_tone_state, FAX_TONE_CED_FREQ, FAX_TONE_CED_DURATION, FAX_TONE_CED_DB, s->sample_rate);
+       ast_v21_detect_init(&s->v21_state, s->sample_rate);
 }
 
 static void ast_dtmf_detect_init (dtmf_detect_state_t *s, unsigned int sample_rate)
@@ -513,6 +573,89 @@ static void ast_digit_detect_init(digit_detect_state_t *s, int mf, unsigned int
        }
 }
 
+/*! \brief Detect a v21 preamble.
+ * This code is derived from the tone_detect code and detects a pattern of 1850
+ * Hz tone found in a v21 preamble.
+ */
+static int v21_detect(struct ast_dsp *dsp, v21_detect_state_t *s, int16_t *amp, int samples)
+{
+       float tone_energy;
+       int i;
+       int hit = 0;
+       int limit;
+       int res = 0;
+       int16_t *ptr;
+       int start, end;
+
+       for (start = 0;  start < samples;  start = end) {
+               /* Process in blocks. */
+               limit = samples - start;
+               if (limit > s->samples_pending) {
+                       limit = s->samples_pending;
+               }
+               end = start + limit;
+
+               for (i = limit, ptr = amp ; i > 0; i--, ptr++) {
+                       /* signed 32 bit int should be enough to suqare any possible signed 16 bit value */
+                       s->energy += (int32_t) *ptr * (int32_t) *ptr;
+
+                       goertzel_sample(&s->tone, *ptr);
+               }
+
+               s->samples_pending -= limit;
+
+               if (s->samples_pending) {
+                       /* Finished incomplete (last) block */
+                       break;
+               }
+
+               tone_energy = goertzel_result(&s->tone);
+
+               /* Scale to make comparable */
+               tone_energy *= 2.0;
+               s->energy *= s->block_size;
+
+               ast_debug(10, "v21 1850 Ew=%.2E, Et=%.2E, s/n=%10.2f\n", tone_energy, s->energy, tone_energy / (s->energy - tone_energy));
+
+               hit = 0;
+               if (tone_energy > s->energy * s->threshold) {
+                       ast_debug(10, "Hit! count=%d; miss_count=%d\n", s->hit_count, s->miss_count);
+                       hit = 1;
+               }
+
+               if (hit) {
+                       if (s->hit_count == 0 || s->miss_count == 3) {
+                               s->hit_count++;
+                       } else {
+                               s->hit_count = 0;
+                       }
+
+                       s->miss_count = 0;
+               } else {
+                       s->miss_count++;
+                       if (s->miss_count > 3) {
+                               s->hit_count = 0;
+                       }
+               }
+
+               if (s->hit_count == 4) {
+                       ast_debug(1, "v21 preamble detected\n");
+                       res = 1;
+               }
+
+               /* Reinitialise the detector for the next block */
+               goertzel_reset(&s->tone);
+
+               /* Advance to the next block */
+               s->energy = 0.0;
+               s->samples_pending = s->block_size;
+
+               amp += limit;
+       }
+
+       return res;
+}
+
 static int tone_detect(struct ast_dsp *dsp, tone_detect_state_t *s, int16_t *amp, int samples)
 {
        float tone_energy;
@@ -1441,6 +1584,10 @@ struct ast_frame *ast_dsp_process(struct ast_channel *chan, struct ast_dsp *dsp,
                if ((dsp->faxmode & DSP_FAXMODE_DETECT_CED) && tone_detect(dsp, &dsp->ced_tone_state, shortdata, len)) {
                        fax_digit = 'e';
                }
+
+               if ((dsp->faxmode & DSP_FAXMODE_DETECT_V21) && v21_detect(dsp, &dsp->v21_state, shortdata, len)) {
+                       fax_digit = 'g';
+               }
        }
 
        if (dsp->features & (DSP_FEATURE_DIGIT_DETECT | DSP_FEATURE_BUSY_DETECT)) {
index 18f3fa1..31aede8 100644 (file)
@@ -246,8 +246,6 @@ struct fax_gateway {
        struct ast_fax_tech_token *token;
        /*! \brief the start of our timeout counter */
        struct timeval timeout_start;
-       /*! \brief the start of our ced timeout */
-       struct timeval ced_timeout_start;
        /*! \brief DSP Processor */
        struct ast_dsp *chan_dsp;
        struct ast_dsp *peer_dsp;
@@ -255,8 +253,8 @@ struct fax_gateway {
        int framehook;
        /*! \brief bridged */
        int bridged:1;
-       /*! \brief 1 if the ced tone came from chan, 0 if it came from peer */
-       int ced_chan:1;
+       /*! \brief 1 if a v21 preamble has been detected */
+       int detected_v21:1;
        /*! \brief a flag to track the state of our negotiation */
        enum ast_t38_state t38_state;
        /*! \brief original audio formats */
@@ -273,7 +271,6 @@ static int fax_logger_level = -1;
 
 #define RES_FAX_TIMEOUT 10000
 #define FAX_GATEWAY_TIMEOUT RES_FAX_TIMEOUT
-#define FAX_GATEWAY_CED_TIMEOUT 3000
 
 /*! \brief The faxregistry is used to manage information and statistics for all FAX sessions. */
 static struct {
@@ -2412,10 +2409,10 @@ static struct fax_gateway *fax_gateway_new(struct ast_fax_session_details *detai
        gateway->framehook = -1;
 
        ast_dsp_set_features(gateway->chan_dsp, DSP_FEATURE_FAX_DETECT);
-       ast_dsp_set_faxmode(gateway->chan_dsp, DSP_FAXMODE_DETECT_CED);
+       ast_dsp_set_faxmode(gateway->chan_dsp, DSP_FAXMODE_DETECT_V21);
 
        ast_dsp_set_features(gateway->peer_dsp, DSP_FEATURE_FAX_DETECT);
-       ast_dsp_set_faxmode(gateway->peer_dsp, DSP_FAXMODE_DETECT_CED);
+       ast_dsp_set_faxmode(gateway->peer_dsp, DSP_FAXMODE_DETECT_V21);
 
        details->caps = AST_FAX_TECH_GATEWAY;
        if (!(gateway->s = fax_session_reserve(details, &gateway->token))) {
@@ -2503,21 +2500,17 @@ static struct ast_frame *fax_gateway_request_t38(struct fax_gateway *gateway, st
        gateway->t38_state = T38_STATE_NEGOTIATING;
        gateway->timeout_start = ast_tvnow();
 
-       gateway->ced_timeout_start.tv_sec = 0;
-       gateway->ced_timeout_start.tv_usec = 0;
-
        ast_debug(1, "requesting T.38 for gateway session for %s\n", chan->name);
        return fp;
 }
 
-static struct ast_frame *fax_gateway_detect_ced(struct fax_gateway *gateway, struct ast_channel *chan, struct ast_channel *peer, struct ast_channel *active, struct ast_frame *f)
+static struct ast_frame *fax_gateway_detect_v21(struct fax_gateway *gateway, struct ast_channel *chan, struct ast_channel *peer, struct ast_channel *active, struct ast_frame *f)
 {
        struct ast_frame *dfr = ast_frdup(f);
        struct ast_dsp *active_dsp = (active == chan) ? gateway->chan_dsp : gateway->peer_dsp;
        struct ast_channel *other = (active == chan) ? peer : chan;
 
-       /* if we have already detected a CED tone, don't waste time here */
-       if (!ast_tvzero(gateway->ced_timeout_start)) {
+       if (gateway->detected_v21) {
                return f;
        }
 
@@ -2529,17 +2522,13 @@ static struct ast_frame *fax_gateway_detect_ced(struct fax_gateway *gateway, str
                return f;
        }
 
-       if (dfr->frametype == AST_FRAME_DTMF && dfr->subclass.integer == 'e') {
+       if (dfr->frametype == AST_FRAME_DTMF && dfr->subclass.integer == 'g') {
+               gateway->detected_v21 = 1;
                if (ast_channel_get_t38_state(other) == T38_STATE_UNKNOWN) {
-                       if (ast_channel_get_t38_state(active) == T38_STATE_UNKNOWN) {
-                               gateway->ced_timeout_start = ast_tvnow();
-                               gateway->ced_chan = (active == chan);
-                               ast_debug(1, "detected CED tone from %s; will schedule T.38 request on %s\n", active->name, other->name);
-                       } else {
-                               return fax_gateway_request_t38(gateway, chan, f);
-                       }
+                       ast_debug(1, "detected v21 preamble from %s\n", active->name);
+                       return fax_gateway_request_t38(gateway, chan, f);
                } else {
-                       ast_debug(1, "detected CED tone on %s, but %s does not support T.38 for T.38 gateway session\n", active->name, other->name);
+                       ast_debug(1, "detected v21 preamble on %s, but %s does not support T.38 for T.38 gateway session\n", active->name, other->name);
                }
        }
 
@@ -2593,9 +2582,6 @@ static struct ast_frame *fax_gateway_detect_t38(struct fax_gateway *gateway, str
        if (control_params->request_response == AST_T38_REQUEST_NEGOTIATE) {
                enum ast_t38_state state = ast_channel_get_t38_state(other);
 
-               gateway->ced_timeout_start.tv_sec = 0;
-               gateway->ced_timeout_start.tv_usec = 0;
-
                if (state == T38_STATE_UNKNOWN) {
                        /* we detected a request to negotiate T.38 and the
                         * other channel appears to support T.38, we'll pass
@@ -2635,7 +2621,7 @@ static struct ast_frame *fax_gateway_detect_t38(struct fax_gateway *gateway, str
                        return &ast_null_frame;
                } else if (gateway->t38_state == T38_STATE_NEGOTIATING) {
                        /* we got a request to negotiate T.38 after we already
-                        * sent one to the other party based on CED tone
+                        * sent one to the other party based on v21 preamble
                         * detection. We'll just pretend we passed this request
                         * through in the first place. */
 
@@ -2643,12 +2629,12 @@ static struct ast_frame *fax_gateway_detect_t38(struct fax_gateway *gateway, str
                        gateway->t38_state = T38_STATE_UNKNOWN;
                        gateway->timeout_start = ast_tvnow();
 
-                       ast_debug(1, "%s is attempting to negotiate T.38 after we already sent a negotiation request based on CED detection\n", active->name);
+                       ast_debug(1, "%s is attempting to negotiate T.38 after we already sent a negotiation request based on v21 preamble detection\n", active->name);
                        ao2_ref(details, -1);
                        return &ast_null_frame;
                } else if (gateway->t38_state == T38_STATE_NEGOTIATED) {
                        /* we got a request to negotiate T.38 after we already
-                        * sent one to the other party based on CED tone
+                        * sent one to the other party based on v21 preamble
                         * detection and received a response. We need to
                         * respond to this and shut down the gateway. */
 
@@ -2869,7 +2855,8 @@ static struct ast_frame *fax_gateway_framehook(struct ast_channel *chan, struct
 
                gateway->timeout_start = ast_tvnow();
 
-               /* we are bridged, change r/w formats to SLIN for CED detection and T.30 */
+               /* we are bridged, change r/w formats to SLIN for v21 preamble
+                * detection and T.30 */
                ast_format_copy(&gateway->chan_read_format, &chan->readformat);
                ast_format_copy(&gateway->chan_write_format, &chan->readformat);
 
@@ -2944,20 +2931,10 @@ static struct ast_frame *fax_gateway_framehook(struct ast_channel *chan, struct
                return fax_gateway_detect_t38(gateway, chan, peer, active, f);
        }
 
-       /* handle the ced timeout delay */
-       if (!ast_tvzero(gateway->ced_timeout_start)) {
-               if (ast_tvdiff_ms(ast_tvnow(), gateway->ced_timeout_start) > FAX_GATEWAY_CED_TIMEOUT) {
-                       if (gateway->ced_chan && chan == active) {
-                               return fax_gateway_request_t38(gateway, chan, f);
-                       } else if (!gateway->ced_chan && peer == active) {
-                               return fax_gateway_request_t38(gateway, chan, f);
-                       }
-               }
-       } else if (gateway->t38_state == T38_STATE_UNAVAILABLE && f->frametype == AST_FRAME_VOICE) {
-               /* not in gateway mode and have not detected CED yet, listen
-                * for CED */
-               /* XXX this should detect a v21 preamble instead of CED */
-               return fax_gateway_detect_ced(gateway, chan, peer, active, f);
+       if (!gateway->detected_v21 && gateway->t38_state == T38_STATE_UNAVAILABLE && f->frametype == AST_FRAME_VOICE) {
+               /* not in gateway mode and have not detected v21 yet, listen
+                * for v21 */
+               return fax_gateway_detect_v21(gateway, chan, peer, active, f);
        }
 
        /* in gateway mode, gateway some packets */
@@ -2981,7 +2958,7 @@ static struct ast_frame *fax_gateway_framehook(struct ast_channel *chan, struct
        }
 
        /* force silence on the line if T.38 negotiation might be taking place */
-       if (!ast_tvzero(gateway->ced_timeout_start) || (gateway->t38_state != T38_STATE_UNAVAILABLE && gateway->t38_state != T38_STATE_REJECTED)) {
+       if (gateway->t38_state != T38_STATE_UNAVAILABLE && gateway->t38_state != T38_STATE_REJECTED) {
                if (f->frametype == AST_FRAME_VOICE && f->subclass.format.id == AST_FORMAT_SLINEAR) {
                        short silence_buf[f->samples];
                        struct ast_frame silence_frame = {