diff --git a/CHANGES b/CHANGES index 3f7315ea8ae10a46e33a51910966668bf34724da..bdd89b808f25dfa20f38411542159bcfa65be883 100644 --- a/CHANGES +++ b/CHANGES @@ -45,6 +45,11 @@ AMI (Asterisk Manager Interface) changes * Originate now generates an error response if the extension given is not found in the dialplan +FAX changes +----------- + * FAXOPT(faxdetect) will enable a generic fax detect framehook for dialplan + control of faxdetect. + ------------------------------------------------------------------------------ --- Functionality changes from Asterisk 1.8 to Asterisk 10 ------------------- ------------------------------------------------------------------------------ diff --git a/include/asterisk/res_fax.h b/include/asterisk/res_fax.h index b296321b254ec9744fe8abdf778396cbb9d48070..ba20944e40267f1f38c6e29049482e213ba5e2b6 100644 --- a/include/asterisk/res_fax.h +++ b/include/asterisk/res_fax.h @@ -174,6 +174,8 @@ struct ast_fax_session_details { int gateway_id; /*! the timeout for this gateway in seconds */ int gateway_timeout; + /*! the id of the faxdetect framehook for this channel */ + int faxdetect_id; }; struct ast_fax_tech; diff --git a/res/res_fax.c b/res/res_fax.c index 8966e01bf5b5778e9f104c48b44e0a75832708e3..c914edd4992c68b0c06729c55013cd19456dbeba 100644 --- a/res/res_fax.c +++ b/res/res_fax.c @@ -192,6 +192,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <enum name="gateway"> <para>R/W T38 fax gateway, with optional fax activity timeout in seconds (yes[,timeout]/no)</para> </enum> + <enum name="faxdetect"> + <para>R/W Enable FAX detect with optional timeout in seconds (yes,t38,cng[,timeout]/no)</para> + </enum> <enum name="pages"> <para>R/O Number of pages transferred.</para> </enum> @@ -268,6 +271,27 @@ struct fax_gateway { struct ast_format peer_write_format; }; +/*! \brief used for fax detect framehook */ +struct fax_detect { + /*! \brief the start of our timeout counter */ + struct timeval timeout_start; + /*! \brief faxdetect timeout */ + int timeout; + /*! \brief DSP Processor */ + struct ast_dsp *dsp; + /*! \brief original audio formats */ + struct ast_format orig_format; + /*! \brief fax session details */ + struct ast_fax_session_details *details; + /*! \brief mode */ + int flags; +}; + +/*! \brief FAX Detect flags */ +#define FAX_DETECT_MODE_CNG (1 << 0) +#define FAX_DETECT_MODE_T38 (1 << 1) +#define FAX_DETECT_MODE_BOTH (FAX_DETECT_MODE_CNG | FAX_DETECT_MODE_T38) + static int fax_logger_level = -1; /*! \brief maximum buckets for res_fax ao2 containers */ @@ -452,6 +476,7 @@ static struct ast_fax_session_details *session_details_new(void) d->minrate = general_options.minrate; d->maxrate = general_options.maxrate; d->gateway_id = -1; + d->faxdetect_id = -1; d->gateway_timeout = 0; return d; @@ -3124,6 +3149,242 @@ static int fax_gateway_attach(struct ast_channel *chan, struct ast_fax_session_d return gateway->framehook; } +/*! \brief destroy a FAX detect structure */ +static void destroy_faxdetect(void *data) +{ + struct fax_detect *faxdetect = data; + + if (faxdetect->dsp) { + ast_dsp_free(faxdetect->dsp); + faxdetect->dsp = NULL; + } + ao2_ref(faxdetect->details, -1); +} + +/*! \brief Create a new fax detect object. + * \param chan the channel attaching to + * \param timeout remove framehook in this time if set + * \param flags required options + * \return NULL or a fax gateway object + */ +static struct fax_detect *fax_detect_new(struct ast_channel *chan, int timeout, int flags) +{ + struct fax_detect *faxdetect = ao2_alloc(sizeof(*faxdetect), destroy_faxdetect); + if (!faxdetect) { + return NULL; + } + + faxdetect->flags = flags; + + if (timeout) { + faxdetect->timeout_start = ast_tvnow(); + } else { + faxdetect->timeout_start.tv_sec = 0; + faxdetect->timeout_start.tv_usec = 0; + } + + if (faxdetect->flags & FAX_DETECT_MODE_CNG) { + faxdetect->dsp = ast_dsp_new(); + if (!faxdetect->dsp) { + ao2_ref(faxdetect, -1); + return NULL; + } + ast_dsp_set_features(faxdetect->dsp, DSP_FEATURE_FAX_DETECT); + ast_dsp_set_faxmode(faxdetect->dsp, DSP_FAXMODE_DETECT_CNG | DSP_FAXMODE_DETECT_SQUELCH); + } else { + faxdetect->dsp = NULL; + } + + return faxdetect; +} + +/*! \brief Deref the faxdetect data structure when the faxdetect framehook is detached + * \param data framehook data (faxdetect data)*/ +static void fax_detect_framehook_destroy(void *data) { + struct fax_detect *faxdetect = data; + + ao2_ref(faxdetect, -1); +} + +/*! \brief Fax Detect Framehook + * + * Listen for fax tones in audio path and enable jumping to a extension when detected. + * + * \param chan channel + * \param f frame to handle may be NULL + * \param event framehook event + * \param data framehook data (struct fax_detect *) + * + * \return processed frame or NULL when f is NULL or a null frame + */ +static struct ast_frame *fax_detect_framehook(struct ast_channel *chan, struct ast_frame *f, enum ast_framehook_event event, void *data) { + struct fax_detect *faxdetect = data; + struct ast_fax_session_details *details; + struct ast_control_t38_parameters *control_params; + struct ast_channel *peer; + int result = 0; + + details = faxdetect->details; + + switch (event) { + case AST_FRAMEHOOK_EVENT_ATTACHED: + /* Setup format for DSP on ATTACH*/ + ast_format_copy(&faxdetect->orig_format, &chan->readformat); + switch (chan->readformat.id) { + case AST_FORMAT_SLINEAR: + case AST_FORMAT_ALAW: + case AST_FORMAT_ULAW: + break; + default: + if (ast_set_read_format_by_id(chan, AST_FORMAT_SLINEAR)) { + ast_framehook_detach(chan, details->faxdetect_id); + details->faxdetect_id = -1; + return f; + } + } + return NULL; + case AST_FRAMEHOOK_EVENT_DETACHED: + /* restore audio formats when we are detached */ + ast_set_read_format(chan, &faxdetect->orig_format); + if ((peer = ast_bridged_channel(chan))) { + ast_channel_make_compatible(chan, peer); + } + return NULL; + case AST_FRAMEHOOK_EVENT_READ: + if (f) { + break; + } + default: + return f; + }; + + if (details->faxdetect_id < 0) { + return f; + } + + if ((!ast_tvzero(faxdetect->timeout_start) && + (ast_tvdiff_ms(ast_tvnow(), faxdetect->timeout_start) > faxdetect->timeout))) { + ast_framehook_detach(chan, details->faxdetect_id); + details->faxdetect_id = -1; + return f; + } + + /* only handle VOICE and CONTROL frames*/ + switch (f->frametype) { + case AST_FRAME_VOICE: + /* we have no DSP this means we not detecting CNG */ + if (!faxdetect->dsp) { + break; + } + /* We can only process some formats*/ + switch (f->subclass.format.id) { + case AST_FORMAT_SLINEAR: + case AST_FORMAT_ALAW: + case AST_FORMAT_ULAW: + break; + default: + return f; + } + break; + case AST_FRAME_CONTROL: + if ((f->subclass.integer == AST_CONTROL_T38_PARAMETERS) && + (faxdetect->flags & FAX_DETECT_MODE_T38)) { + break; + } + return f; + default: + return f; + } + + if (f->frametype == AST_FRAME_VOICE) { + f = ast_dsp_process(chan, faxdetect->dsp, f); + if (f->frametype == AST_FRAME_DTMF) { + result = f->subclass.integer; + } + } else if ((f->frametype == AST_FRAME_CONTROL) && (f->datalen == sizeof(struct ast_control_t38_parameters))) { + control_params = f->data.ptr; + switch (control_params->request_response) { + case AST_T38_NEGOTIATED: + case AST_T38_REQUEST_NEGOTIATE: + result = 't'; + break; + default: + break; + } + } + + if (result) { + const char *target_context = S_OR(chan->macrocontext, chan->context); + switch (result) { + case 'f': + case 't': + ast_channel_unlock(chan); + if (ast_exists_extension(chan, target_context, "fax", 1, + S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL))) { + ast_channel_lock(chan); + ast_verbose(VERBOSE_PREFIX_2 "Redirecting '%s' to fax extension due to %s detection\n", + chan->name, (result == 'f') ? "CNG" : "T38"); + pbx_builtin_setvar_helper(chan, "FAXEXTEN", chan->exten); + if (ast_async_goto(chan, target_context, "fax", 1)) { + ast_log(LOG_NOTICE, "Failed to async goto '%s' into fax of '%s'\n", chan->name, target_context); + } + ast_frfree(f); + f = &ast_null_frame; + } else { + ast_channel_lock(chan); + ast_log(LOG_NOTICE, "FAX %s detected but no fax extension in context (%s)\n", + (result == 'f') ? "CNG" : "T38", target_context); + } + } + ast_framehook_detach(chan, details->faxdetect_id); + details->faxdetect_id = -1; + } + + return f; +} + +/*! \brief Attach a faxdetect framehook object to a channel. + * \param chan the channel to attach to + * \param timeout remove framehook in this time if set + * \return the faxdetect structure or NULL on error + * \param flags required options + * \retval -1 error + */ +static int fax_detect_attach(struct ast_channel *chan, int timeout, int flags) +{ + struct fax_detect *faxdetect; + struct ast_fax_session_details *details; + struct ast_framehook_interface fr_hook = { + .version = AST_FRAMEHOOK_INTERFACE_VERSION, + .event_cb = fax_detect_framehook, + .destroy_cb = fax_detect_framehook_destroy, + }; + + if (!(details = find_or_create_details(chan))) { + ast_log(LOG_ERROR, "System cannot provide memory for session requirements.\n"); + return -1; + } + + /* set up the frame hook*/ + faxdetect = fax_detect_new(chan, timeout, flags); + if (!faxdetect) { + ao2_ref(details, -1); + return -1; + } + + fr_hook.data = faxdetect; + faxdetect->details = details; + ast_channel_lock(chan); + details->faxdetect_id = ast_framehook_attach(chan, &fr_hook); + ast_channel_unlock(chan); + + if (details->faxdetect_id < 0) { + ao2_ref(faxdetect, -1); + } + + return details->faxdetect_id; +} + /*! \brief hash callback for ao2 */ static int session_hash_cb(const void *obj, const int flags) { @@ -3525,6 +3786,8 @@ static int acf_faxopt_read(struct ast_channel *chan, const char *cmd, char *data } else if (!strcasecmp(data, "t38gateway") || !strcasecmp(data, "gateway") || !strcasecmp(data, "t38_gateway") || !strcasecmp(data, "faxgateway")) { ast_copy_string(buf, details->gateway_id != -1 ? "yes" : "no", len); + } else if (!strcasecmp(data, "faxdetect")) { + ast_copy_string(buf, details->faxdetect_id != -1 ? "yes" : "no", len); } else if (!strcasecmp(data, "error")) { ast_copy_string(buf, details->error, len); } else if (!strcasecmp(data, "filename")) { @@ -3636,6 +3899,51 @@ static int acf_faxopt_write(struct ast_channel *chan, const char *cmd, char *dat } else { ast_log(LOG_WARNING, "Unsupported value '%s' passed to FAXOPT(%s).\n", value, data); } + } else if (!strcasecmp(data, "faxdetect")) { + const char *val = ast_skip_blanks(value); + char *timeout = strchr(val, ','); + unsigned int fdtimeout = 0; + int flags; + int faxdetect; + + if (timeout) { + *timeout++ = '\0'; + } + + if (ast_true(val) || !strcasecmp(val, "t38") || !strcasecmp(val, "cng")) { + if (details->faxdetect_id < 0) { + if (timeout && (sscanf(timeout, "%u", &fdtimeout) == 1)) { + if (fdtimeout > 0) { + fdtimeout = fdtimeout * 1000; + } else { + ast_log(LOG_WARNING, "Timeout cannot be negative ignoring timeout\n"); + } + } + + if (!strcasecmp(val, "t38")) { + flags = FAX_DETECT_MODE_T38; + } else if (!strcasecmp(val, "cng")) { + flags = FAX_DETECT_MODE_CNG; + } else { + flags = FAX_DETECT_MODE_BOTH; + } + + faxdetect = fax_detect_attach(chan, fdtimeout, flags); + if (faxdetect < 0) { + ast_log(LOG_ERROR, "Error attaching FAX detect to channel %s.\n", chan->name); + res = -1; + } else { + ast_debug(1, "Attached FAX detect to channel %s.\n", chan->name); + } + } else { + ast_log(LOG_WARNING, "Attempt to attach a FAX detect on channel (%s) with FAX detect already running.\n", chan->name); + } + } else if (ast_false(val)) { + ast_framehook_detach(chan, details->faxdetect_id); + details->faxdetect_id = -1; + } else { + ast_log(LOG_WARNING, "Unsupported value '%s' passed to FAXOPT(%s).\n", value, data); + } } else if (!strcasecmp(data, "headerinfo")) { ast_string_field_set(details, headerinfo, value); } else if (!strcasecmp(data, "localstationid")) {