From f4348dccf22bedc458d060f20a269a11a004485d Mon Sep 17 00:00:00 2001 From: mnicholson Date: Tue, 12 Jul 2011 15:23:24 +0000 Subject: [PATCH] do v21 detection instead of CED detection for the fax gateway git-svn-id: http://svn.digium.com/svn/asterisk/trunk@327769 f38db490-d61c-443f-a65b-d21fe96a405b --- include/asterisk/dsp.h | 3 +- main/dsp.c | 147 +++++++++++++++++++++++++++++++++++++++++ res/res_fax.c | 65 ++++++------------ 3 files changed, 170 insertions(+), 45 deletions(-) diff --git a/include/asterisk/dsp.h b/include/asterisk/dsp.h index 333415200..78c9a74ac 100644 --- a/include/asterisk/dsp.h +++ b/include/asterisk/dsp.h @@ -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 diff --git a/main/dsp.c b/main/dsp.c index ee1891823..02cdc9d73 100644 --- a/main/dsp.c +++ b/main/dsp.c @@ -244,6 +244,20 @@ typedef struct } tone_detect_state_t; +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]; @@ -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)) { diff --git a/res/res_fax.c b/res/res_fax.c index 18f3fa133..31aede8a6 100644 --- a/res/res_fax.c +++ b/res/res_fax.c @@ -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 = {