diff --git a/src/include/switch_core.h b/src/include/switch_core.h index 7b9b4fc205..cf4cced79a 100644 --- a/src/include/switch_core.h +++ b/src/include/switch_core.h @@ -1936,6 +1936,15 @@ SWITCH_DECLARE(switch_status_t) switch_core_asr_check_results(switch_asr_handle_ */ SWITCH_DECLARE(switch_status_t) switch_core_asr_get_results(switch_asr_handle_t *ah, char **xmlstr, switch_asr_flag_t *flags); +/*! + \brief Get result headers from an asr handle + \param ah the handle to get results from + \param headers a pointer to dynamically allocate an switch_event_t result to + \param flags flags to influence behaviour + \return SWITCH_STATUS_SUCCESS +*/ +SWITCH_DECLARE(switch_status_t) switch_core_asr_get_result_headers(switch_asr_handle_t *ah, switch_event_t **headers, switch_asr_flag_t *flags); + /*! \brief Load a grammar to an asr handle \param ah the handle to load to diff --git a/src/include/switch_module_interfaces.h b/src/include/switch_module_interfaces.h index cdfb9eb2e9..b4ea5a4bc8 100644 --- a/src/include/switch_module_interfaces.h +++ b/src/include/switch_module_interfaces.h @@ -377,6 +377,8 @@ struct switch_asr_interface { switch_status_t (*asr_check_results) (switch_asr_handle_t *ah, switch_asr_flag_t *flags); /*! function to read results from the ASR */ switch_status_t (*asr_get_results) (switch_asr_handle_t *ah, char **xmlstr, switch_asr_flag_t *flags); + /*! function to read result headers from the ASR */ + switch_status_t (*asr_get_result_headers) (switch_asr_handle_t *ah, switch_event_t **headers, switch_asr_flag_t *flags); /*! function to start ASR input timers */ switch_status_t (*asr_start_input_timers) (switch_asr_handle_t *ah); void (*asr_text_param) (switch_asr_handle_t *ah, char *param, const char *val); diff --git a/src/mod/asr_tts/mod_unimrcp/mod_unimrcp.c b/src/mod/asr_tts/mod_unimrcp/mod_unimrcp.c index d5007ee020..600ce37fe5 100644 --- a/src/mod/asr_tts/mod_unimrcp/mod_unimrcp.c +++ b/src/mod/asr_tts/mod_unimrcp/mod_unimrcp.c @@ -450,6 +450,8 @@ struct recognizer_data { switch_hash_t *enabled_grammars; /** recognize result */ char *result; + /** recognize result headers */ + switch_event_t *result_headers; /** true, if voice has started */ int start_of_input; /** true, if input timers have started */ @@ -483,6 +485,7 @@ static switch_status_t recog_asr_resume(switch_asr_handle_t *ah); static switch_status_t recog_asr_pause(switch_asr_handle_t *ah); static switch_status_t recog_asr_check_results(switch_asr_handle_t *ah, switch_asr_flag_t *flags); static switch_status_t recog_asr_get_results(switch_asr_handle_t *ah, char **xmlstr, switch_asr_flag_t *flags); +static switch_status_t recog_asr_get_result_headers(switch_asr_handle_t *ah, switch_event_t **headers, switch_asr_flag_t *flags); static switch_status_t recog_asr_start_input_timers(switch_asr_handle_t *ah); static void recog_asr_text_param(switch_asr_handle_t *ah, char *param, const char *val); static void recog_asr_numeric_param(switch_asr_handle_t *ah, char *param, int val); @@ -505,7 +508,9 @@ static switch_status_t recog_channel_check_results(speech_channel_t *schannel); static switch_status_t recog_channel_set_start_of_input(speech_channel_t *schannel); static switch_status_t recog_channel_start_input_timers(speech_channel_t *schannel); static switch_status_t recog_channel_set_results(speech_channel_t *schannel, const char *results); +static switch_status_t recog_channel_set_result_headers(speech_channel_t *schannel, mrcp_recog_header_t *recog_hdr); static switch_status_t recog_channel_get_results(speech_channel_t *schannel, char **results); +static switch_status_t recog_channel_get_result_headers(speech_channel_t *schannel, switch_event_t **result_headers); static switch_status_t recog_channel_set_params(speech_channel_t *schannel, mrcp_message_t *msg, mrcp_generic_header_t *gen_hdr, mrcp_recog_header_t *recog_hdr); static switch_status_t recog_channel_set_header(speech_channel_t *schannel, int id, char *val, mrcp_message_t *msg, mrcp_recog_header_t *recog_hdr); @@ -2156,9 +2161,9 @@ static switch_status_t recog_channel_start(speech_channel_t *schannel) recognizer_data_t *r; char *start_input_timers; const char *mime_type; - char *key; + char *key = NULL; switch_size_t len; - grammar_t *grammar; + grammar_t *grammar = NULL; switch_size_t grammar_uri_count = 0; switch_size_t grammar_uri_list_len = 0; char *grammar_uri_list = NULL; @@ -2176,6 +2181,9 @@ static switch_status_t recog_channel_start(speech_channel_t *schannel) } r = (recognizer_data_t *) schannel->data; r->result = NULL; + if (r->result_headers) { + switch_event_destroy(&r->result_headers); + } r->start_of_input = 0; /* input timers are started by default unless the start-input-timers=false param is set */ @@ -2598,6 +2606,141 @@ static switch_status_t recog_channel_set_results(speech_channel_t *schannel, con return status; } +/** + * Find a parameter from a ;-separated string + * + * @param str the input string to find data in + * @param param the parameter to to look for + * @return a pointer in the str if successful, or NULL. + */ +static char *find_parameter(const char *str, const char *param) +{ + char *ptr = (char *) str; + + while (ptr) { + if (!strncasecmp(ptr, param, strlen(param))) + return ptr; + + if ((ptr = strchr(ptr, ';'))) + ptr++; + + while (ptr && *ptr == ' ') { + ptr++; + } + } + + return NULL; +} + +/** + * Get a parameter value from a ;-separated string + * + * @param str the input string to parse data from + * @param param the parameter to to look for + * @return a malloc'ed char* if successful, or NULL. + */ +static char *get_parameter_value(const char *str, const char *param) +{ + const char *param_ptr; + char *param_value = NULL; + char *tmp; + switch_size_t param_len; + char *param_tmp; + + if (zstr(str) || zstr(param)) return NULL; + + /* Append "=" to the end of the string */ + param_tmp = switch_mprintf("%s=", param); + if (!param_tmp) return NULL; + param = param_tmp; + + param_len = strlen(param); + param_ptr = find_parameter(str, param); + + if (zstr(param_ptr)) goto fail; + + param_value = strdup(param_ptr + param_len); + + if (zstr(param_value)) goto fail; + + if ((tmp = strchr(param_value, ';'))) *tmp = '\0'; + + switch_safe_free(param_tmp); + return param_value; + + fail: + switch_safe_free(param_tmp); + switch_safe_free(param_value); + return NULL; +} + +/** + * Set the recognition result headers + * + * @param schannel the channel whose results are set + * @param recog_hdr the recognition headers + * @return SWITCH_STATUS_SUCCESS if successful + */ +static switch_status_t recog_channel_set_result_headers(speech_channel_t *schannel, mrcp_recog_header_t *recog_hdr) +{ + switch_status_t status = SWITCH_STATUS_SUCCESS; + recognizer_data_t *r; + + switch_mutex_lock(schannel->mutex); + + r = (recognizer_data_t *) schannel->data; + + if (r->result_headers) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "(%s) result headers are already set\n", schannel->name); + status = SWITCH_STATUS_FALSE; + goto done; + } + + if (!recog_hdr) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "(%s) result headers are NULL\n", schannel->name); + status = SWITCH_STATUS_FALSE; + goto done; + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "(%s) ASR adding result headers\n", schannel->name); + + if ((status = switch_event_create(&r->result_headers, SWITCH_EVENT_CLONE)) == SWITCH_STATUS_SUCCESS) { + + switch_event_add_header(r->result_headers, SWITCH_STACK_BOTTOM, "ASR-Completion-Cause", "%d", recog_hdr->completion_cause); + + if (!zstr(recog_hdr->completion_reason.buf)) { + switch_event_add_header_string(r->result_headers, SWITCH_STACK_BOTTOM, "ASR-Completion-Reason", recog_hdr->completion_reason.buf); + } + + if (!zstr(recog_hdr->waveform_uri.buf)) { + char *tmp; + + if ((tmp = strdup(recog_hdr->waveform_uri.buf))) { + char *tmp2; + if ((tmp2 = strchr(tmp, ';'))) *tmp2 = '\0'; + switch_event_add_header_string(r->result_headers, SWITCH_STACK_BOTTOM, "ASR-Waveform-URI", tmp); + free(tmp); + } + + if ((tmp = get_parameter_value(recog_hdr->waveform_uri.buf, "size"))) { + switch_event_add_header_string(r->result_headers, SWITCH_STACK_BOTTOM, "ASR-Waveform-Size", tmp); + free(tmp); + } + + if ((tmp = get_parameter_value(recog_hdr->waveform_uri.buf, "duration"))) { + switch_event_add_header_string(r->result_headers, SWITCH_STACK_BOTTOM, "ASR-Waveform-Duration", tmp); + free(tmp); + } + } + } + + done: + + switch_mutex_unlock(schannel->mutex); + + return status; +} + /** * Get the recognition results. * @@ -2627,6 +2770,29 @@ static switch_status_t recog_channel_get_results(speech_channel_t *schannel, cha return status; } +/** + * Get the recognition result headers. + * + * @param schannel the channel to get results from + * @param result_headers the recognition result headers. switch_event_destroy() the results when finished with them. + * @return SWITCH_STATUS_SUCCESS will always be returned, since this is just optional data. + */ +static switch_status_t recog_channel_get_result_headers(speech_channel_t *schannel, switch_event_t **result_headers) +{ + recognizer_data_t *r = (recognizer_data_t *) schannel->data; + + switch_mutex_lock(schannel->mutex); + + if (r->result_headers && result_headers) { + *result_headers = r->result_headers; + r->result_headers = NULL; + } + + switch_mutex_unlock(schannel->mutex); + + return SWITCH_STATUS_SUCCESS; +} + /** * Set parameters in a recognizer MRCP header * @@ -3207,6 +3373,9 @@ static switch_status_t recog_asr_close(switch_asr_handle_t *ah, switch_asr_flag_ r->dtmf_generator_active = 0; mpf_dtmf_generator_destroy(r->dtmf_generator); } + if (r->result_headers) { + switch_event_destroy(&r->result_headers); + } switch_mutex_unlock(schannel->mutex); speech_channel_destroy(schannel); } @@ -3326,6 +3495,19 @@ static switch_status_t recog_asr_get_results(switch_asr_handle_t *ah, char **xml return recog_channel_get_results(schannel, xmlstr); } +/** + * Process asr_get_result_headers request from FreeSWITCH. Return the headers back + * to FreeSWITCH. FreeSWITCH will switch_event_destroy() the headers. + * + * @param ah the FreeSWITCH speech recognition handle + * @param flags other flags + */ +static switch_status_t recog_asr_get_result_headers(switch_asr_handle_t *ah, switch_event_t **headers, switch_asr_flag_t *flags) +{ + speech_channel_t *schannel = (speech_channel_t *) ah->private_info; + return recog_channel_get_result_headers(schannel, headers); +} + /** * Send START-INPUT-TIMERS to executing recognition request * @param ah the handle to start timers on @@ -3473,6 +3655,7 @@ static apt_bool_t recog_on_message_receive(mrcp_application_t *application, mrcp recog_hdr->completion_cause); if (message->body.length > 0) { if (message->body.buf[message->body.length - 1] == '\0') { + recog_channel_set_result_headers(schannel, recog_hdr); recog_channel_set_results(schannel, message->body.buf); } else { /* string is not null terminated */ @@ -3481,11 +3664,13 @@ static apt_bool_t recog_on_message_receive(mrcp_application_t *application, mrcp "(%s) Recognition result is not null-terminated. Appending null terminator.\n", schannel->name); strncpy(result, message->body.buf, message->body.length); result[message->body.length] = '\0'; + recog_channel_set_result_headers(schannel, recog_hdr); recog_channel_set_results(schannel, result); } } else { char *completion_cause = switch_mprintf("Completion-Cause: %03d", recog_hdr->completion_cause); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "(%s) No result\n", schannel->name); + recog_channel_set_result_headers(schannel, recog_hdr); recog_channel_set_results(schannel, completion_cause); switch_safe_free(completion_cause); } @@ -3584,6 +3769,7 @@ static switch_status_t recog_load(switch_loadable_module_interface_t *module_int asr_interface->asr_pause = recog_asr_pause; asr_interface->asr_check_results = recog_asr_check_results; asr_interface->asr_get_results = recog_asr_get_results; + asr_interface->asr_get_result_headers = recog_asr_get_result_headers; asr_interface->asr_start_input_timers = recog_asr_start_input_timers; asr_interface->asr_text_param = recog_asr_text_param; asr_interface->asr_numeric_param = recog_asr_numeric_param; @@ -3620,7 +3806,7 @@ static switch_status_t recog_load(switch_loadable_module_interface_t *module_int switch_core_hash_insert(globals.recog.param_id_map, "N-Best-List-Length", unimrcp_param_id_create(RECOGNIZER_HEADER_N_BEST_LIST_LENGTH, pool)); switch_core_hash_insert(globals.recog.param_id_map, "No-Input-Timeout", unimrcp_param_id_create(RECOGNIZER_HEADER_NO_INPUT_TIMEOUT, pool)); switch_core_hash_insert(globals.recog.param_id_map, "Recognition-Timeout", unimrcp_param_id_create(RECOGNIZER_HEADER_RECOGNITION_TIMEOUT, pool)); - switch_core_hash_insert(globals.recog.param_id_map, "Waveform-Url", unimrcp_param_id_create(RECOGNIZER_HEADER_WAVEFORM_URI, pool)); + switch_core_hash_insert(globals.recog.param_id_map, "Waveform-Uri", unimrcp_param_id_create(RECOGNIZER_HEADER_WAVEFORM_URI, pool)); switch_core_hash_insert(globals.recog.param_id_map, "Completion-Cause", unimrcp_param_id_create(RECOGNIZER_HEADER_COMPLETION_CAUSE, pool)); switch_core_hash_insert(globals.recog.param_id_map, "Recognizer-Context-Block", unimrcp_param_id_create(RECOGNIZER_HEADER_RECOGNIZER_CONTEXT_BLOCK, pool)); diff --git a/src/switch_core_asr.c b/src/switch_core_asr.c index 6c13c7b697..08590d6a3d 100644 --- a/src/switch_core_asr.c +++ b/src/switch_core_asr.c @@ -299,6 +299,18 @@ SWITCH_DECLARE(switch_status_t) switch_core_asr_get_results(switch_asr_handle_t return ah->asr_interface->asr_get_results(ah, xmlstr, flags); } +SWITCH_DECLARE(switch_status_t) switch_core_asr_get_result_headers(switch_asr_handle_t *ah, switch_event_t **headers, switch_asr_flag_t *flags) +{ + switch_assert(ah != NULL); + + if (ah->asr_interface->asr_get_result_headers) { + return ah->asr_interface->asr_get_result_headers(ah, headers, flags); + } else { + /* Since this is not always implemented, return success if the function can't be called */ + return SWITCH_STATUS_SUCCESS; + } +} + SWITCH_DECLARE(switch_status_t) switch_core_asr_start_input_timers(switch_asr_handle_t *ah) { switch_status_t status = SWITCH_STATUS_SUCCESS; diff --git a/src/switch_ivr_async.c b/src/switch_ivr_async.c index 0be6c07d59..f610fdadb1 100644 --- a/src/switch_ivr_async.c +++ b/src/switch_ivr_async.c @@ -3855,6 +3855,7 @@ static void *SWITCH_THREAD_FUNC speech_thread(switch_thread_t *thread, void *obj while (switch_channel_up_nosig(channel) && !switch_test_flag(sth->ah, SWITCH_ASR_FLAG_CLOSED)) { char *xmlstr = NULL; + switch_event_t *headers = NULL; switch_thread_cond_wait(sth->cond, sth->mutex); @@ -3868,6 +3869,9 @@ static void *SWITCH_THREAD_FUNC speech_thread(switch_thread_t *thread, void *obj if (status != SWITCH_STATUS_SUCCESS && status != SWITCH_STATUS_BREAK) { goto done; + } else if (status == SWITCH_STATUS_SUCCESS) { + /* Try to fetch extra information for this result, the return value doesn't really matter here - it's just optional data. */ + switch_core_asr_get_result_headers(sth->ah, &headers, &flags); } if (status == SWITCH_STATUS_SUCCESS && switch_true(switch_channel_get_variable(channel, "asr_intercept_dtmf"))) { @@ -3917,6 +3921,11 @@ static void *SWITCH_THREAD_FUNC speech_thread(switch_thread_t *thread, void *obj if (switch_event_create(&event, SWITCH_EVENT_DETECTED_SPEECH) == SWITCH_STATUS_SUCCESS) { if (status == SWITCH_STATUS_SUCCESS) { switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Speech-Type", "detected-speech"); + + if (headers) { + switch_event_merge(event, headers); + } + switch_event_add_body(event, "%s", xmlstr); } else { switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Speech-Type", "begin-speaking"); @@ -3940,6 +3949,10 @@ static void *SWITCH_THREAD_FUNC speech_thread(switch_thread_t *thread, void *obj } switch_safe_free(xmlstr); + + if (headers) { + switch_event_destroy(&headers); + } } } done: