Add generic faxdetect framehook to res_fax
authorGregory Nietsky <gregory@distrotech.co.za>
Wed, 5 Oct 2011 06:50:18 +0000 (06:50 +0000)
committerGregory Nietsky <gregory@distrotech.co.za>
Wed, 5 Oct 2011 06:50:18 +0000 (06:50 +0000)
Added func FAXOPT(faxdetect)=yes,cng,t38[,timeout]/no
to enable dialplan faxdetect allowing more flexibility.

as soon as a fax tone is detected the framehook is removed.
there is a penalty involved in running this framehook on
non G711 channels as they will be transcoded.

CNG tone is suppresed using the SQUELCH flag to allow
WaitForNoise to be run on the channel to detect Voice.

(Closes issue ASTERISK-18569)
Reported by: Myself
Reviewed by: Matthew Nicholson, Kevin Fleming

Review: https://reviewboard.asterisk.org/r/1116/

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@339465 65c4cc65-6c06-0410-ace0-fbb531ad65f3

CHANGES
include/asterisk/res_fax.h
res/res_fax.c

diff --git a/CHANGES b/CHANGES
index 3f7315e..bdd89b8 100644 (file)
--- 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 -------------------
 ------------------------------------------------------------------------------
index b296321..ba20944 100644 (file)
@@ -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;
index 8966e01..c914edd 100644 (file)
@@ -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")) {