Index: addons/chan_ooh323.c =================================================================== --- addons/chan_ooh323.c (revision 338894) +++ addons/chan_ooh323.c (working copy) @@ -25,42 +25,6 @@ #include "chan_ooh323.h" #include -/*** DOCUMENTATION - - - Allow Setting / Reading OOH323 Settings - - - - - - Fax Detect [R/W] - Returns 0 or 1 - Write yes or no - - - - - t38support [R/W] - Returns 0 or 1 - Write yes or no - - - - - Returns h323id [R] - - - - - - Read and set channel parameters in the dialplan. - name is one of the above only those with a [W] can be writen to. - - - -***/ - #define FORMAT_STRING_SIZE 512 /* Defaults */ @@ -118,6 +82,8 @@ static int ooh323_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen); static int ooh323_queryoption(struct ast_channel *ast, int option, void *data, int *datalen); static int ooh323_fixup(struct ast_channel *oldchan, struct ast_channel *newchan); +static int function_ooh323_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len); +static int function_ooh323_write(struct ast_channel *chan, const char *cmd, char *data, const char *value); static enum ast_rtp_glue_result ooh323_get_rtp_peer(struct ast_channel *chan, struct ast_rtp_instance **rtp); static enum ast_rtp_glue_result ooh323_get_vrtp_peer(struct ast_channel *chan, struct ast_rtp_instance **rtp); @@ -151,7 +117,8 @@ .queryoption = ooh323_queryoption, .bridge = ast_rtp_instance_bridge, /* XXX chan unlocked ? */ .early_bridge = ast_rtp_instance_early_bridge, - + .func_channel_read = function_ooh323_read, + .func_channel_write = function_ooh323_write, }; static struct ast_rtp_glue ooh323_rtp = { @@ -3518,13 +3485,6 @@ return res; } -/*! \brief Structure to declare a dialplan function: OOH323 */ -static struct ast_custom_function ooh323_function = { - .name = "OOH323", - .read = function_ooh323_read, - .write = function_ooh323_write, -}; - static int load_module(void) { int res; @@ -3693,9 +3653,6 @@ restart_monitor(); } - /* Register dialplan functions */ - ast_custom_function_register(&ooh323_function); - return 0; } @@ -4086,9 +4043,6 @@ } ooH323EpDestroy(); - /* Unregister dial plan functions */ - ast_custom_function_unregister(&ooh323_function); - if (gH323Debug) { ast_verbose("+++ ooh323 unload_module \n"); } Index: funcs/func_channel.c =================================================================== --- funcs/func_channel.c (revision 338894) +++ funcs/func_channel.c (working copy) @@ -254,6 +254,22 @@ The channel is either on hold or a call waiting call. + chan_ooh323 provides the following additional options: + + + Fax Detect [R/W] + Returns 0 or 1 + Write yes or no + + + t38support [R/W] + Returns 0 or 1 + Write yes or no + + + Returns h323id [R] + + Index: include/asterisk/res_fax.h =================================================================== --- include/asterisk/res_fax.h (revision 338894) +++ include/asterisk/res_fax.h (working copy) @@ -174,6 +174,8 @@ 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: res/res_fax.c =================================================================== --- res/res_fax.c (revision 338894) +++ res/res_fax.c (working copy) @@ -192,6 +192,9 @@ R/W T38 fax gateway, with optional fax activity timeout in seconds (yes[,timeout]/no) + + R/W Enable FAX detect with optional timeout in seconds seconds (yes[,timeout]/no) + R/O Number of pages transferred. @@ -225,10 +228,33 @@ SendFax + + + Generic Fax Detect CNG/T.38 (Wait For Fax) + + + + Specifies the number of seconds we attempt to detect a fax tone on the channel + + + Either the tone name defined in the "indications.conf" configuration file, + or a directly specified list of frequencies and durations. + If not specified silence is generated. + + + Number of ms noise detected before proceeding with the dialplan. + + + + This application sets FAXOPT(status) To SUCCESS | FAILURE | ERROR + FAXOPT(statusstr) will be set to CNG | T38 on SUCCESS or reason on FAILURE | ERROR + + ***/ static const char app_receivefax[] = "ReceiveFAX"; static const char app_sendfax[] = "SendFAX"; +static const char app_waitfax[] = "WaitFAX"; struct debug_info_history { unsigned int consec_frames; @@ -268,6 +294,24 @@ 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 Noise limit to end faxdetect */ + int noiselim; + /*! \brief result of the framehook e[CED]/f[CNG]/n[Noise]/t[T38] */ + int result; + /*! \brief fax session details */ + struct ast_fax_session_details *details; +}; + static int fax_logger_level = -1; /*! \brief maximum buckets for res_fax ao2 containers */ @@ -452,6 +496,7 @@ d->minrate = general_options.minrate; d->maxrate = general_options.maxrate; d->gateway_id = -1; + d->faxdetect_id = -1; d->gateway_timeout = 0; return d; @@ -3068,6 +3113,415 @@ 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 noiselim end faxdetect when noiselim ms of noise is detected + * \param dsp_detect_flag dsp faxmode detect flags + * \return NULL or a fax gateway object + */ +static struct fax_detect *fax_detect_new(struct ast_channel *chan, int timeout, int noiselim, int dsp_detect_flag) +{ + struct fax_detect *faxdetect = ao2_alloc(sizeof(*faxdetect), destroy_faxdetect); + if (!faxdetect) { + return NULL; + } + + faxdetect->noiselim = noiselim; + faxdetect->result = 0; + + if (timeout) { + faxdetect->timeout_start = ast_tvnow(); + } else { + faxdetect->timeout_start.tv_sec = 0; + faxdetect->timeout_start.tv_usec = 0; + } + + faxdetect->dsp = ast_dsp_new(); + if (!faxdetect->dsp) { + ao2_ref(faxdetect, -1); + return NULL; + } + + ast_dsp_set_features(faxdetect->dsp, DSP_FEATURE_FAX_DETECT); + if (noiselim) { + ast_dsp_set_faxmode(faxdetect->dsp, dsp_detect_flag || DSP_FAXMODE_DETECT_SQUELCH); + ast_dsp_set_threshold(faxdetect->dsp, ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE)); + } else { + ast_dsp_set_faxmode(faxdetect->dsp, dsp_detect_flag); + } + 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 dspnoise; + + details = faxdetect->details; + + switch (event) { + case AST_FRAMEHOOK_EVENT_ATTACHED: + /* Setup format for DSP on ATTACH*/ + ao2_lock(faxdetect); + 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; + } + } + ao2_unlock(faxdetect); + return NULL; + case AST_FRAMEHOOK_EVENT_DETACHED: + /* restore audio formats when we are detached */ + ao2_lock(faxdetect); + ast_set_read_format(chan, &faxdetect->orig_format); + if ((peer = ast_bridged_channel(chan))) { + ast_channel_make_compatible(chan, peer); + } + ao2_unlock(faxdetect); + return NULL; + case AST_FRAMEHOOK_EVENT_READ: + if (f) { + break; + } + default: + 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; + } + + /* only handle VOICE and CONTROL frames*/ + switch (f->frametype) { + case AST_FRAME_VOICE: + /* 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) { + break; + } + return f; + default: + return f; + } + + ao2_lock(faxdetect); + if (f->frametype == AST_FRAME_VOICE) { + f = ast_dsp_process(chan, faxdetect->dsp, f); + if (f->frametype == AST_FRAME_DTMF) { + faxdetect->result = f->subclass.integer; + } else if ((f->frametype == AST_FRAME_VOICE) && (faxdetect->noiselim > 0)) { + ast_dsp_noise(faxdetect->dsp, f, &dspnoise); + if (dspnoise > faxdetect->noiselim) { + faxdetect->result = 'n'; + } + } + } 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: + faxdetect->result = 't'; + break; + default: + break; + } + } + + if (faxdetect->result) { + const char *target_context = S_OR(chan->macrocontext, chan->context); + switch (faxdetect->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, (faxdetect->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\n", + (faxdetect->result == 'f') ? "CNG" : "T38"); + } + } + ast_framehook_detach(chan, details->faxdetect_id); + details->faxdetect_id = -1; + } + + ao2_unlock(faxdetect); + + 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 + * \param noiselim end faxdetect when noiselim ms of noise is detected + * \param dsp_detect_flag dsp faxmode detect flags + * \return the faxdetect structure or NULL on error + * \retval -1 error + */ +static struct fax_detect* fax_detect_attach(struct ast_channel *chan, int timeout, int noiselim, int dsp_detect_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 NULL; + } + + /* set up the frame hook*/ + faxdetect = fax_detect_new(chan, timeout, noiselim, dsp_detect_flags); + if (!faxdetect) { + ao2_ref(details, -1); + return NULL; + } + + 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(details, -1); + ao2_ref(faxdetect, -1); + return NULL; + } + + /* return it with ref held in framhook and return*/ + ao2_ref(faxdetect, 1); + return faxdetect; +} + +/*! \brief Faxdetect loop used by WaitFAX + * \details Run DSP faxdetect on the channel for timeout seconds or until fax is detected + * \param chan channel to run fax detect on + * \param timeout maximum time to wait for fax detection + * \return 0 if nothing was detected 1 on CNG detected 2 if T38 negotiation is started -1 on error*/ +static int do_waitfax_exec(struct ast_channel *chan, int timeout, int noiselim) +{ + int timeleft = timeout; + int res = 0; + struct ast_frame *f, *dup_f; + AST_LIST_HEAD_NOLOCK(, ast_frame) deferred_frames; + struct fax_detect *faxdetect = NULL; + + if (!(faxdetect = fax_detect_attach(chan, 0, noiselim, DSP_FAXMODE_DETECT_CNG))) { + return -1; + } + + AST_LIST_HEAD_INIT_NOLOCK(&deferred_frames); + + while ((timeleft = ast_waitfor(chan, timeleft))) { + if (!(f = ast_read(chan))) { + break; + } + + ao2_lock(faxdetect); + switch (faxdetect->result) { + case 'f': + res = 1; + break; + case 't': + res = 2; + break; + case 'n': + break; + } + ao2_unlock(faxdetect); + + if (ast_is_deferrable_frame(f)) { + AST_LIST_INSERT_HEAD(&deferred_frames, f, frame_list); + } else { + ast_frfree(f); + } + + if (res) { + break; + } + } + ao2_ref(faxdetect, -1); + + ast_channel_lock(chan); + while ((f = AST_LIST_REMOVE_HEAD(&deferred_frames, frame_list))) { + dup_f = ast_frisolate(f); + ast_queue_frame_head(chan, dup_f); + if (dup_f != f) { + ast_frfree(f); + } + } + ast_channel_unlock(chan); + + return res; +} + +/*! \brief Alternate wait app that listens for CNG + * \details This app answers the channel and waits for CNG tone or T38 negotiation + * if the channel driver supports faxdetect it will proceed to the fax + * extension. + * it will set FAXOPT(status) and FAXOPT(statusstr) allowing dial plan processing where + * the channel does not have fax detect or the Dialplan needs to handle faxdetection + * + * \param chan channel this application is called on + * \param data the paramaters passed to the application + * \return this application will always return 0 + */ +static int waitfax_exec(struct ast_channel *chan, const char *data) +{ + int res = 0; + unsigned int timeout; + unsigned int noiselim; + int ptres = -1; + char *parse; + struct ast_fax_session_details *details; + struct ast_silence_generator *silgen = NULL; + struct ast_tone_zone_sound *ts; + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(timeout); + AST_APP_ARG(tone); + AST_APP_ARG(noise); + ); + + if (!(details = find_or_create_details(chan))) { + ast_log(LOG_ERROR, "System cannot provide memory for session requirements.\n"); + return 0; + } + + parse = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, parse); + + if (!ast_strlen_zero(args.timeout) && sscanf(args.timeout, "%u", &timeout) == 1) { + timeout = timeout * 1000; + } else { + timeout = 0; + } + + if (timeout <= 0) { + ast_string_field_set(details, result, "ERROR"); + ast_string_field_set(details, resultstr, "Invalid timeout in WaitFAX"); + ast_log(LOG_ERROR, "Application WaitFAX requires a valid timeout\n"); + ao2_ref(details, -1); + return 0; + } + + if (ast_strlen_zero(args.noise) || sscanf(args.noise, "%u", &noiselim) != 1) { + noiselim = 0; + } + + if (chan->_state != AST_STATE_UP) { + ast_answer(chan); + } + + /* If no other generator is present, start tone or silencegen while waiting */ + if (!chan->generatordata && !ast_strlen_zero(args.tone)) { + if ((ts = ast_get_indication_tone(chan->zone, args.tone))) { + ptres = ast_playtones_start(chan, 0, ts->data, 0); + ts = ast_tone_zone_sound_unref(ts); + } else { + ptres = ast_playtones_start(chan, 0, args.tone, 0); + } + if (!ptres && ast_opt_transmit_silence) { + silgen = ast_channel_start_silence_generator(chan); + } + } else if (ast_opt_transmit_silence && !chan->generatordata) { + silgen = ast_channel_start_silence_generator(chan); + } + + res = do_waitfax_exec(chan, timeout, noiselim); + + /* stop silgen or tones if present */ + if (silgen) { + ast_channel_stop_silence_generator(chan, silgen); + } else if (!ptres) { + ast_playtones_stop(chan); + } + + if (res > 0) { + ast_string_field_set(details, result, "SUCCESS"); + if (res == 1) { + ast_string_field_set(details, resultstr, "CNG"); + } else { + ast_string_field_set(details, resultstr, "T38"); + } + } else if (res < 0) { + ast_string_field_set(details, result, "ERROR"); + ast_string_field_set(details, resultstr, "DSP Error WaitFAX Failed"); + } else { + ast_string_field_set(details, result, "FAILED"); + ast_string_field_set(details, resultstr, "No CNG Tone Or T38 Detected"); + } + + ao2_ref(details, -1); + return 0; +} + + /*! \brief hash callback for ao2 */ static int session_hash_cb(const void *obj, const int flags) { @@ -3580,6 +4034,40 @@ } 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; + struct fax_detect *faxdetect; + + if (timeout) { + *timeout++ = '\0'; + } + + if (ast_true(val)) { + if (details->faxdetect_id < 0) { + if (timeout && (sscanf(timeout, "%u", &fdtimeout) == 1)) { + fdtimeout = fdtimeout * 1000; + } + + faxdetect = fax_detect_attach(chan, fdtimeout, 0, DSP_FAXMODE_DETECT_CNG); + + if (faxdetect && (details->faxdetect_id >= 0)) { + ast_debug(1, "Attached FAX detect to channel %s.\n", chan->name); + } else { + ast_log(LOG_ERROR, "Error attaching FAX detect to channel %s.\n", chan->name); + res = -1; + } + ao2_ref(faxdetect, -1); + } 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")) { @@ -3630,6 +4118,10 @@ ast_log(LOG_WARNING, "failed to unregister '%s'\n", app_receivefax); } + if (ast_unregister_application(app_waitfax) < 0) { + ast_log(LOG_WARNING, "failed to unregister '%s'\n", app_waitfax); + } + if (fax_logger_level != -1) { ast_logger_unregister_level("FAX"); } @@ -3670,8 +4162,12 @@ return AST_MODULE_LOAD_DECLINE; } + if (ast_register_application_xml(app_waitfax, waitfax_exec) < 0) { + ast_log(LOG_WARNING, "failed to register '%s'.\n", app_waitfax); + } + ast_cli_register_multiple(fax_cli, ARRAY_LEN(fax_cli)); - res = ast_custom_function_register(&acf_faxopt); + res = ast_custom_function_register(&acf_faxopt); fax_logger_level = ast_logger_register_level("FAX"); return res;