diff --git a/src/include/switch_ivr.h b/src/include/switch_ivr.h index 01c82f4f31..f8e18b3d92 100644 --- a/src/include/switch_ivr.h +++ b/src/include/switch_ivr.h @@ -238,7 +238,7 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_resume_detect_speech(switch_core_sess \param name the grammar name \return SWITCH_STATUS_SUCCESS if all is well */ -SWITCH_DECLARE(switch_status_t) switch_ivr_detect_speech_load_grammar(switch_core_session_t *session, char *grammar, char *name); +SWITCH_DECLARE(switch_status_t) switch_ivr_detect_speech_load_grammar(switch_core_session_t *session, const char *grammar, const char *name); /*! \brief Unload a grammar on a background speech detection handle diff --git a/src/mod/event_handlers/mod_rayo/rayo_input_component.c b/src/mod/event_handlers/mod_rayo/rayo_input_component.c index a9412ee2b6..b4549b454f 100644 --- a/src/mod/event_handlers/mod_rayo/rayo_input_component.c +++ b/src/mod/event_handlers/mod_rayo/rayo_input_component.c @@ -413,14 +413,141 @@ static int validate_call_input(iks *input, const char **error) return 1; } +static char *setup_grammars_pocketsphinx(struct input_component *component, switch_core_session_t *session, iks *input, const struct xmpp_error **stanza_error, const char **error_detail) +{ + const char *jsgf_path; + switch_stream_handle_t grammar = { 0 }; + SWITCH_STANDARD_STREAM(grammar); + + /* transform SRGS grammar to JSGF */ + if (!(component->grammar = srgs_parse(globals.parser, iks_find_cdata(input, "grammar")))) { + *stanza_error = STANZA_ERROR_BAD_REQUEST; + *error_detail = "Failed to parse grammar body"; + return NULL; + } + + jsgf_path = srgs_grammar_to_jsgf_file(component->grammar, SWITCH_GLOBAL_dirs.grammar_dir, "gram"); + if (!jsgf_path) { + *stanza_error = STANZA_ERROR_BAD_REQUEST; + *error_detail = "Grammar conversion to JSGF error"; + return NULL; + } + + /* build pocketsphinx grammar string */ + grammar.write_function(&grammar, + "{start-input-timers=%s,no-input-timeout=%d,speech-timeout=%d,confidence-threshold=%d}%s", + component->start_timers ? "true" : "false", + component->initial_timeout, + component->max_silence, + (int)ceil(component->min_confidence * 100.0), + jsgf_path); + + return (char *)grammar.data; +} + +static char *setup_grammars_unimrcp(struct input_component *component, switch_core_session_t *session, iks *input, const struct xmpp_error **stanza_error, const char **error_detail) +{ + iks *grammar_tag; + switch_stream_handle_t grammar_uri_list = { 0 }; + SWITCH_STANDARD_STREAM(grammar_uri_list); + + /* unlock handler mutex, otherwise deadlock will happen when switch_ivr_detect_speech_init adds a new media bug */ + switch_mutex_unlock(component->handler->mutex); + if (switch_ivr_detect_speech_init(session, component->recognizer, "", NULL) != SWITCH_STATUS_SUCCESS) { + switch_mutex_lock(component->handler->mutex); + *stanza_error = STANZA_ERROR_INTERNAL_SERVER_ERROR; + *error_detail = "Failed to initialize recognizer"; + return NULL; + } + switch_mutex_lock(component->handler->mutex); + + /* load unimrcp grammars and return uri-list */ + grammar_uri_list.write_function(&grammar_uri_list, "{start-recognize=true,start-input-timers=%s,confidence-threshold=%f,sensitivity-level=%f", + component->start_timers ? "true" : "false", + component->min_confidence, + component->sensitivity); + if (component->initial_timeout > 0) { + grammar_uri_list.write_function(&grammar_uri_list, ",no-input-timeout=%d", + component->initial_timeout); + } + + if (component->max_silence > 0) { + grammar_uri_list.write_function(&grammar_uri_list, ",speech-complete-timeout=%d,speech-incomplete-timeout=%d", + component->max_silence, + component->max_silence); + } + + if (!zstr(component->language)) { + grammar_uri_list.write_function(&grammar_uri_list, ",speech-language=%s", component->language); + } + + if (!strcmp(iks_find_attrib_soft(input, "mode"), "any") || !strcmp(iks_find_attrib_soft(input, "mode"), "dtmf")) { + /* set dtmf params */ + if (component->inter_digit_timeout > 0) { + grammar_uri_list.write_function(&grammar_uri_list, ",dtmf-interdigit-timeout=%d", component->inter_digit_timeout); + } + if (component->term_digit) { + grammar_uri_list.write_function(&grammar_uri_list, ",dtmf-term-char=%c", component->term_digit); + } + } + grammar_uri_list.write_function(&grammar_uri_list, "}"); + + for (grammar_tag = iks_find(input, "grammar"); grammar_tag; grammar_tag = iks_next_tag(grammar_tag)) { + const char *grammar_name; + iks *grammar_cdata; + const char *grammar; + + /* is this a grammar? */ + if (strcmp("grammar", iks_name(grammar_tag))) { + continue; + } + + /* get the srgs contained in this grammar */ + if (!(grammar_cdata = iks_child(grammar_tag)) || iks_type(grammar_cdata) != IKS_CDATA) { + *stanza_error = STANZA_ERROR_BAD_REQUEST; + *error_detail = "Missing grammar"; + switch_safe_free(grammar_uri_list.data); + return NULL; + } + + /* load the grammar */ + grammar = switch_core_sprintf(RAYO_POOL(component), "{start-recognize=false}inline:%s", iks_cdata(grammar_cdata)); + grammar_name = switch_core_sprintf(RAYO_POOL(component), "grammar-%d", rayo_actor_seq_next(RAYO_ACTOR(component))); + /* unlock handler mutex, otherwise deadlock will happen if switch_ivr_detect_speech_load_grammar removes the media bug */ + switch_mutex_unlock(component->handler->mutex); + if (switch_ivr_detect_speech_load_grammar(session, grammar, grammar_name) != SWITCH_STATUS_SUCCESS) { + switch_mutex_lock(component->handler->mutex); + *stanza_error = STANZA_ERROR_INTERNAL_SERVER_ERROR; + *error_detail = "Failed to load grammar"; + switch_safe_free(grammar_uri_list.data); + return NULL; + } + switch_mutex_lock(component->handler->mutex); + + /* add grammar to uri-list */ + grammar_uri_list.write_function(&grammar_uri_list, "session:%s\r\n", grammar_name); + } + + return (char *)grammar_uri_list.data; +} + +static char *setup_grammars_unknown(struct input_component *component, switch_core_session_t *session, iks *input, const struct xmpp_error **stanza_error, const char **error_detail) +{ + switch_stream_handle_t grammar = { 0 }; + SWITCH_STANDARD_STREAM(grammar); + grammar.write_function(&grammar, "%s", iks_find_cdata(input, "grammar")); + return (char *)grammar.data; +} + /** * Start call input on voice resource */ -static iks *start_call_voice_input(struct input_component *component, switch_core_session_t *session, iks *input, iks *iq, const char *output_file, int barge_in) +static iks *start_call_voice_input(struct input_component *component, switch_core_session_t *session, iks *input, iks *iq, int barge_in) { struct input_handler *handler = component->handler; - switch_stream_handle_t grammar = { 0 }; - SWITCH_STANDARD_STREAM(grammar); + char *grammar = NULL; + const struct xmpp_error *stanza_error = NULL; + const char *error_detail = NULL; if (component->speech_mode && handler->voice_component) { /* don't allow multi voice input */ @@ -441,72 +568,23 @@ static iks *start_call_voice_input(struct input_component *component, switch_cor RAYO_UNLOCK(component); RAYO_DESTROY(component); return iks_new_error_detailed(iq, STANZA_ERROR_BAD_REQUEST, "Must use the same recognizer for the entire call"); + } else if (zstr(handler->last_recognizer)) { + handler->last_recognizer = switch_core_session_strdup(session, component->recognizer); } - handler->last_recognizer = switch_core_session_strdup(session, component->recognizer); if (!strcmp(component->recognizer, "pocketsphinx")) { - const char *jsgf_path; - - /* transform SRGS grammar to JSGF */ - if (!(component->grammar = srgs_parse(globals.parser, iks_find_cdata(input, "grammar")))) { - switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Failed to parse grammar body\n"); - handler->voice_component = NULL; - RAYO_UNLOCK(component); - RAYO_DESTROY(component); - return iks_new_error_detailed(iq, STANZA_ERROR_BAD_REQUEST, "Failed to parse grammar body"); - } - jsgf_path = srgs_grammar_to_jsgf_file(component->grammar, SWITCH_GLOBAL_dirs.grammar_dir, "gram"); - if (!jsgf_path) { - handler->voice_component = NULL; - RAYO_UNLOCK(component); - RAYO_DESTROY(component); - return iks_new_error_detailed(iq, STANZA_ERROR_INTERNAL_SERVER_ERROR, "Grammar conversion to JSGF error"); - } - - /* build pocketsphinx grammar string */ - grammar.write_function(&grammar, - "{start-input-timers=%s,no-input-timeout=%d,speech-timeout=%d,confidence-threshold=%d}%s", - component->start_timers ? "true" : "false", - component->initial_timeout, - component->max_silence, - (int)ceil(component->min_confidence * 100.0), - jsgf_path); - } else if (!strncmp(component->recognizer, "unimrcp", strlen("unimrcp"))) { - /* send inline grammar to unimrcp */ - grammar.write_function(&grammar, "{start-input-timers=%s,confidence-threshold=%f,sensitivity-level=%f", - component->start_timers ? "true" : "false", - component->min_confidence, - component->sensitivity); - - if (component->initial_timeout > 0) { - grammar.write_function(&grammar, ",no-input-timeout=%d", - component->initial_timeout); - } - - if (component->max_silence > 0) { - grammar.write_function(&grammar, ",speech-complete-timeout=%d,speech-incomplete-timeout=%d", - component->max_silence, - component->max_silence); - } - - if (!zstr(component->language)) { - grammar.write_function(&grammar, ",speech-language=%s", component->language); - } - - if (!strcmp(iks_find_attrib_soft(input, "mode"), "any")) { - /* set dtmf params */ - if (component->inter_digit_timeout > 0) { - grammar.write_function(&grammar, ",dtmf-interdigit-timeout=%d", component->inter_digit_timeout); - } - if (component->term_digit) { - grammar.write_function(&grammar, ",dtmf-term-char=%c", component->term_digit); - } - } - - grammar.write_function(&grammar, "}inline:%s", iks_find_cdata(input, "grammar")); + grammar = setup_grammars_pocketsphinx(component, session, input, &stanza_error, &error_detail); + } else if (!strcmp(component->recognizer, "unimrcp")) { + grammar = setup_grammars_unimrcp(component, session, input, &stanza_error, &error_detail); } else { - /* passthrough to unknown ASR module */ - grammar.write_function(&grammar, "%s", iks_find_cdata(input, "grammar")); + grammar = setup_grammars_unknown(component, session, input, &stanza_error, &error_detail); + } + + if (!grammar) { + handler->voice_component = NULL; + RAYO_UNLOCK(component); + RAYO_DESTROY(component); + return iks_new_error_detailed(iq, stanza_error, error_detail); } /* acknowledge command */ @@ -514,15 +592,15 @@ static iks *start_call_voice_input(struct input_component *component, switch_cor /* start speech detection */ switch_channel_set_variable(switch_core_session_get_channel(session), "fire_asr_events", "true"); - switch_mutex_unlock(handler->mutex); /* unlock handler mutex, otherwise deadlock will happen when switch_ivr_detect_speech adds a new media bug */ - if (switch_ivr_detect_speech(session, component->recognizer, grammar.data, "mod_rayo_grammar", "", NULL) != SWITCH_STATUS_SUCCESS) { + /* unlock handler mutex, otherwise deadlock will happen if switch_ivr_detect_speech adds a media bug */ + switch_mutex_unlock(handler->mutex); + if (switch_ivr_detect_speech(session, component->recognizer, grammar, "mod_rayo_grammar", "", NULL) != SWITCH_STATUS_SUCCESS) { switch_mutex_lock(handler->mutex); handler->voice_component = NULL; rayo_component_send_complete(RAYO_COMPONENT(component), COMPONENT_COMPLETE_ERROR); - } else { - switch_mutex_lock(handler->mutex); } - switch_safe_free(grammar.data); + switch_mutex_lock(handler->mutex); + switch_safe_free(grammar); return NULL; } @@ -530,7 +608,7 @@ static iks *start_call_voice_input(struct input_component *component, switch_cor /** * Start call input on DTMF resource */ -static iks *start_call_dtmf_input(struct input_component *component, switch_core_session_t *session, iks *input, iks *iq, const char *output_file, int barge_in) +static iks *start_call_dtmf_input(struct input_component *component, switch_core_session_t *session, iks *input, iks *iq, int barge_in) { /* parse the grammar */ if (!(component->grammar = srgs_parse(globals.parser, iks_find_cdata(input, "grammar")))) { @@ -558,7 +636,7 @@ static iks *start_call_dtmf_input(struct input_component *component, switch_core * @param input the input request * @param iq the original input/prompt request */ -static iks *start_call_input(struct input_component *component, switch_core_session_t *session, iks *input, iks *iq, const char *output_file, int barge_in) +static iks *start_call_input(struct input_component *component, switch_core_session_t *session, iks *input, iks *iq, int barge_in) { iks *result = NULL; @@ -610,9 +688,9 @@ static iks *start_call_input(struct input_component *component, switch_core_sess component->speech_mode = strcmp(iks_find_attrib_soft(input, "mode"), "dtmf"); if (component->speech_mode) { - result = start_call_voice_input(component, session, input, iq, output_file, barge_in); + result = start_call_voice_input(component, session, input, iq, barge_in); } else { - result = start_call_dtmf_input(component, session, input, iq, output_file, barge_in); + result = start_call_dtmf_input(component, session, input, iq, barge_in); } switch_mutex_unlock(handler->mutex); @@ -671,7 +749,7 @@ static iks *start_call_input_component(struct rayo_actor *call, struct rayo_mess switch_core_destroy_memory_pool(&pool); return iks_new_error_detailed(iq, STANZA_ERROR_INTERNAL_SERVER_ERROR, "Failed to create input entity"); } - return start_call_input(input_component, session, input, iq, NULL, 0); + return start_call_input(input_component, session, input, iq, 0); } /** diff --git a/src/switch_ivr_async.c b/src/switch_ivr_async.c index 95b167af27..ea034e8e50 100644 --- a/src/switch_ivr_async.c +++ b/src/switch_ivr_async.c @@ -4042,7 +4042,7 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_resume_detect_speech(switch_core_sess return SWITCH_STATUS_FALSE; } -SWITCH_DECLARE(switch_status_t) switch_ivr_detect_speech_load_grammar(switch_core_session_t *session, char *grammar, char *name) +SWITCH_DECLARE(switch_status_t) switch_ivr_detect_speech_load_grammar(switch_core_session_t *session, const char *grammar, const char *name) { switch_channel_t *channel = switch_core_session_get_channel(session); struct speech_thread_handle *sth = switch_channel_get_private(channel, SWITCH_SPEECH_KEY);