From 5eaf39f03a67213aed4de62e8281a3f4ef606fe8 Mon Sep 17 00:00:00 2001 From: Fabiano Zaruch Chinasso Date: Thu, 26 Aug 2021 18:17:49 -0300 Subject: [PATCH 01/16] [BMT-167] First verification prototype --- app-unimrcp/Makefile.am | 4 + app-unimrcp/app_datastore.h | 1 + app-unimrcp/app_mrcpverif.c | 1531 +++++++++++++++++++++ app-unimrcp/app_recogverif.c | 1953 +++++++++++++++++++++++++++ app-unimrcp/app_unimrcp.c | 14 +- app-unimrcp/ast_unimrcp_framework.c | 3 + app-unimrcp/ast_unimrcp_framework.h | 2 + app-unimrcp/speech_channel.c | 71 +- app-unimrcp/speech_channel.h | 3 +- 9 files changed, 3551 insertions(+), 31 deletions(-) create mode 100644 app-unimrcp/app_mrcpverif.c create mode 100644 app-unimrcp/app_recogverif.c diff --git a/app-unimrcp/Makefile.am b/app-unimrcp/Makefile.am index 9d4acb9..11ab238 100644 --- a/app-unimrcp/Makefile.am +++ b/app-unimrcp/Makefile.am @@ -11,13 +11,17 @@ app_unimrcp_la_SOURCES = audio_queue.c \ app_datastore.c \ app_mrcpsynth.c \ app_mrcprecog.c \ + app_mrcpverif.c \ app_synthandrecog.c \ + app_recogverif.c \ app_unimrcp.c app_unimrcp_la_LDFLAGS = -avoid-version -no-undefined -module app_unimrcp_la_LIBADD = $(UNIMRCP_LIBS) XMLDOC_FILES = app_mrcpsynth.c \ app_mrcprecog.c \ + app_mrcpverif.c \ + app_recogverif.c \ app_synthandrecog.c \ app_datastore.c diff --git a/app-unimrcp/app_datastore.h b/app-unimrcp/app_datastore.h index 6be9729..e2de10c 100644 --- a/app-unimrcp/app_datastore.h +++ b/app-unimrcp/app_datastore.h @@ -50,6 +50,7 @@ struct app_session_t { apr_uint32_t schannel_number; /* speech channel number */ speech_channel_t *recog_channel; /* recognition channel */ speech_channel_t *synth_channel; /* synthesis channel, if any */ + speech_channel_t *verif_channel; /* synthesis channel, if any */ ast_format_compat *readformat; /* old read format, to be restored */ ast_format_compat *rawreadformat; /* old raw read format, to be restored (>= Asterisk 13) */ ast_format_compat *writeformat; /* old write format, to be restored */ diff --git a/app-unimrcp/app_mrcpverif.c b/app-unimrcp/app_mrcpverif.c new file mode 100644 index 0000000..cf67464 --- /dev/null +++ b/app-unimrcp/app_mrcpverif.c @@ -0,0 +1,1531 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2009, Molo Afrika Speech Technologies (Pty) Ltd + * + * J.W.F. Thirion + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + * + * Please follow coding guidelines + * http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES + */ + +/* By Molo Afrika Speech Technologies (Pty) Ltd + * See: http://www.molo.co.za + * + * Ideas, concepts and code borrowed from UniMRCP's example programs + * and the FreeSWITCH mod_unimrcp ASR/TTS module. + * + * Authors of these are: + * UniMRCP: + * Arsen Chaloyan + * FreeSWITCH: mod_unimrcp + * Christopher M. Rienzo + * + * See: + * http://www.unimrcp.org + * http://www.freeswitch.org + */ + +/*! \file + * + * \brief MRCPVerif application + * + * \author\verbatim J.W.F. Thirion \endverbatim + * + * MRCPVerif application + * \ingroup applications + */ + +/* Asterisk includes. */ +#include "ast_compat_defs.h" + +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/app.h" + +/* UniMRCP includes. */ +#include "app_datastore.h" + +/*** DOCUMENTATION + + + MRCP verification application. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This application establishes an MRCP session for speech recognition and optionally plays a prompt file. + Once recognition completes, the application exits and returns results to the dialplan. + If recognition completed, the variable ${RECOGSTATUS} is set to "OK". Otherwise, if recognition couldn't be started, + the variable ${RECOGSTATUS} is set to "ERROR". If the caller hung up while recognition was still in-progress, + the variable ${RECOGSTATUS} is set to "INTERRUPTED". + The variable ${RECOG_COMPLETION_CAUSE} indicates whether recognition completed successfully with a match or + an error occurred. ("000" - success, "001" - nomatch, "002" - noinput) + If recognition completed successfully, the variable ${RECOG_RESULT} is set to an NLSML result received + from the MRCP server. Alternatively, the recognition result data can be retrieved by using the following dialplan + functions RECOG_CONFIDENCE(), RECOG_GRAMMAR(), RECOG_INPUT(), and RECOG_INSTANCE(). + + + MRCPSynth + SynthAndRecog + RECOG_CONFIDENCE + RECOG_GRAMMAR + RECOG_INPUT + RECOG_INSTANCE + + + ***/ + +/* The name of the application. */ +static const char *app_recog = "MRCPVerif"; + +/* The application instance. */ +static ast_mrcp_application_t *mrcpverif = NULL; + +/* The enumeration of application options (excluding the MRCP params). */ +enum mrcprecog_option_flags { + MRCPRECOG_PROFILE = (1 << 0), + MRCPRECOG_INTERRUPT = (1 << 1), + MRCPRECOG_FILENAME = (1 << 2), + MRCPRECOG_BARGEIN = (1 << 3), + MRCPRECOG_GRAMMAR_DELIMITERS = (1 << 4), + MRCPRECOG_EXIT_ON_PLAYERROR = (1 << 5), + MRCPRECOG_URI_ENCODED_RESULTS = (1 << 6), + MRCPRECOG_OUTPUT_DELIMITERS = (1 << 7), + MRCPRECOG_INPUT_TIMERS = (1 << 8), + MRCPRECOG_PERSISTENT_LIFETIME = (1 << 9), + MRCPRECOG_DATASTORE_ENTRY = (1 << 10), + MRCPRECOG_INSTANCE_FORMAT = (1 << 11) +}; + +/* The enumeration of option arguments. */ +enum mrcprecog_option_args { + OPT_ARG_PROFILE = 0, + OPT_ARG_INTERRUPT = 1, + OPT_ARG_FILENAME = 2, + OPT_ARG_BARGEIN = 3, + OPT_ARG_GRAMMAR_DELIMITERS = 4, + OPT_ARG_EXIT_ON_PLAYERROR = 5, + OPT_ARG_URI_ENCODED_RESULTS = 6, + OPT_ARG_OUTPUT_DELIMITERS = 7, + OPT_ARG_INPUT_TIMERS = 8, + OPT_ARG_PERSISTENT_LIFETIME = 9, + OPT_ARG_DATASTORE_ENTRY = 10, + OPT_ARG_INSTANCE_FORMAT = 11, + + /* This MUST be the last value in this enum! */ + OPT_ARG_ARRAY_SIZE = 12 +}; + +/* The enumeration of plocies for the use of input timers. */ +enum mrcprecog_it_policies { + IT_POLICY_OFF = 0, /* do not start input timers */ + IT_POLICY_ON = 1, /* start input timers with RECOGNIZE */ + IT_POLICY_AUTO /* start input timers once prompt is finished [default] */ +}; + +/* The structure which holds the application options (including the MRCP params). */ +struct mrcprecog_options_t { + apr_hash_t *recog_hfs; + + int flags; + const char *params[OPT_ARG_ARRAY_SIZE]; +}; + +typedef struct mrcprecog_options_t mrcprecog_options_t; + +/* --- MRCP SPEECH CHANNEL INTERFACE TO UNIMRCP --- */ + +/* Get speech channel associated with provided MRCP session. */ +static APR_INLINE speech_channel_t * get_speech_channel(mrcp_session_t *session) +{ + if (session) + return (speech_channel_t *)mrcp_application_session_object_get(session); + + return NULL; +} + +/* Handle the UniMRCP responses sent to session terminate requests. */ +static apt_bool_t speech_on_session_terminate(mrcp_application_t *application, mrcp_session_t *session, mrcp_sig_status_code_e status) +{ + speech_channel_t *schannel = get_speech_channel(session); + if (!schannel) { + ast_log(LOG_ERROR, "speech_on_session_terminate: unknown channel error!\n"); + return FALSE; + } + + ast_log(LOG_DEBUG, "(%s) speech_on_session_terminate\n", schannel->name); + + if (schannel->dtmf_generator != NULL) { + ast_log(LOG_DEBUG, "(%s) DTMF generator destroyed\n", schannel->name); + mpf_dtmf_generator_destroy(schannel->dtmf_generator); + schannel->dtmf_generator = NULL; + } + + ast_log(LOG_DEBUG, "(%s) Destroying MRCP session\n", schannel->name); + + if (!mrcp_application_session_destroy(session)) + ast_log(LOG_WARNING, "(%s) Unable to destroy application session\n", schannel->name); + + speech_channel_set_state(schannel, SPEECH_CHANNEL_CLOSED); + return TRUE; +} + +/* Handle the UniMRCP responses sent to channel add requests. */ +static apt_bool_t speech_on_channel_add(mrcp_application_t *application, mrcp_session_t *session, mrcp_channel_t *channel, mrcp_sig_status_code_e status) +{ + speech_channel_t *schannel = get_speech_channel(session); + if (!schannel || !channel) { + ast_log(LOG_ERROR, "speech_on_channel_add: unknown channel error!\n"); + return FALSE; + } + + ast_log(LOG_DEBUG, "(%s) speech_on_channel_add\n", schannel->name); + + if (status == MRCP_SIG_STATUS_CODE_SUCCESS) { + const mpf_codec_descriptor_t *descriptor = mrcp_application_source_descriptor_get(channel); + if (!descriptor) { + ast_log(LOG_ERROR, "(%s) Unable to determine codec descriptor\n", schannel->name); + speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); + return FALSE; + } + + if (schannel->stream != NULL) { + schannel->dtmf_generator = mpf_dtmf_generator_create(schannel->stream, schannel->pool); + /* schannel->dtmf_generator = mpf_dtmf_generator_create_ex(schannel->stream, MPF_DTMF_GENERATOR_OUTBAND, 70, 50, schannel->pool); */ + + if (schannel->dtmf_generator != NULL) + ast_log(LOG_DEBUG, "(%s) DTMF generator created\n", schannel->name); + else + ast_log(LOG_WARNING, "(%s) Unable to create DTMF generator\n", schannel->name); + } + + schannel->rate = descriptor->sampling_rate; + const char *codec_name = NULL; + if (descriptor->name.length > 0) + codec_name = descriptor->name.buf; + else + codec_name = "unknown"; + + if (!schannel->session_id) { + const apt_str_t *session_id = mrcp_application_session_id_get(session); + if (session_id && session_id->buf) { + schannel->session_id = apr_pstrdup(schannel->pool, session_id->buf); + } + } + + ast_log(LOG_NOTICE, "(%s) Channel ready codec=%s, sample rate=%d\n", + schannel->name, + codec_name, + schannel->rate); + speech_channel_set_state(schannel, SPEECH_CHANNEL_READY); + } else { + int rc = mrcp_application_session_response_code_get(session); + ast_log(LOG_ERROR, "(%s) Channel error status=%d, response code=%d!\n", schannel->name, status, rc); + speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); + } + + return TRUE; +} + +/* --- MRCP ASR --- */ + +/* Start recognizer's input timers. */ +static int recog_channel_start_input_timers(speech_channel_t *schannel) +{ + int status = 0; + + if (!schannel) { + ast_log(LOG_ERROR, "start_input_timers: unknown channel error!\n"); + return -1; + } + + apr_thread_mutex_lock(schannel->mutex); + + recognizer_data_t *r = (recognizer_data_t *)schannel->data; + + if (r == NULL) { + ast_log(LOG_ERROR, "(%s) Recognizer data struct is NULL\n", schannel->name); + + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + if ((schannel->state == SPEECH_CHANNEL_PROCESSING) && (!r->timers_started)) { + mrcp_message_t *mrcp_message; + ast_log(LOG_DEBUG, "(%s) Sending START-INPUT-TIMERS request\n", schannel->name); + + /* Send START-INPUT-TIMERS to MRCP server. */ + mrcp_message = mrcp_application_message_create(schannel->unimrcp_session, schannel->unimrcp_channel, RECOGNIZER_START_INPUT_TIMERS); + + if (mrcp_message) { + mrcp_application_message_send(schannel->unimrcp_session, schannel->unimrcp_channel, mrcp_message); + } else { + ast_log(LOG_ERROR, "(%s) Failed to create START-INPUT-TIMERS message\n", schannel->name); + status = -1; + } + } + + apr_thread_mutex_unlock(schannel->mutex); + return status; +} + +/* Flag that input has started. */ +static int recog_channel_set_start_of_input(speech_channel_t *schannel) +{ + int status = 0; + + if (!schannel) { + ast_log(LOG_ERROR, "set_start_of_input: unknown channel error!\n"); + return -1; + } + + apr_thread_mutex_lock(schannel->mutex); + + recognizer_data_t *r = (recognizer_data_t *)schannel->data; + + if (r == NULL) { + ast_log(LOG_ERROR, "(%s) Recognizer data struct is NULL\n", schannel->name); + + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + r->start_of_input = 1; + + apr_thread_mutex_unlock(schannel->mutex); + return status; +} + +/* Set the recognition results. */ +static int recog_channel_set_results(speech_channel_t *schannel, int completion_cause, const apt_str_t *result, const apt_str_t *waveform_uri) +{ + int status = 0; + + if (!schannel) { + ast_log(LOG_ERROR, "set_results: unknown channel error!\n"); + return -1; + } + + apr_thread_mutex_lock(schannel->mutex); + + recognizer_data_t *r = (recognizer_data_t *)schannel->data; + + if (r == NULL) { + ast_log(LOG_ERROR, "(%s) Recognizer data struct is NULL\n", schannel->name); + + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + if (r->completion_cause >= 0) { + ast_log(LOG_DEBUG, "(%s) Result is already set\n", schannel->name); + + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + if (result && result->length > 0) { + /* The duplicated string will always be NUL-terminated. */ + r->result = apr_pstrndup(schannel->pool, result->buf, result->length); + ast_log(LOG_DEBUG, "(%s) Set result:\n\n%s\n", schannel->name, r->result); + } + r->completion_cause = completion_cause; + if (waveform_uri && waveform_uri->length > 0) + r->waveform_uri = apr_pstrndup(schannel->pool, waveform_uri->buf, waveform_uri->length); + + apr_thread_mutex_unlock(schannel->mutex); + return status; +} + +/* Get the recognition results. */ +static int recog_channel_get_results(speech_channel_t *schannel, const char **completion_cause, const char **result, const char **waveform_uri) +{ + if (!schannel) { + ast_log(LOG_ERROR, "get_results: unknown channel error!\n"); + return -1; + } + + apr_thread_mutex_lock(schannel->mutex); + + recognizer_data_t *r = (recognizer_data_t *)schannel->data; + + if (r == NULL) { + ast_log(LOG_ERROR, "(%s) Recognizer data struct is NULL\n", schannel->name); + + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + if (r->completion_cause < 0) { + ast_log(LOG_ERROR, "(%s) Recognition terminated prematurely\n", schannel->name); + + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + if (completion_cause) { + *completion_cause = apr_psprintf(schannel->pool, "%03d", r->completion_cause); + ast_log(LOG_DEBUG, "(%s) Completion-Cause: %s\n", schannel->name, *completion_cause); + r->completion_cause = 0; + } + + if (result && r->result && strlen(r->result) > 0) { + *result = apr_pstrdup(schannel->pool, r->result); + ast_log(LOG_NOTICE, "(%s) Result:\n\n%s\n", schannel->name, *result); + r->result = NULL; + } + + if (waveform_uri && r->waveform_uri && (strlen(r->waveform_uri)) > 0) { + *waveform_uri = apr_pstrdup(schannel->pool, r->waveform_uri); + ast_log(LOG_DEBUG, "(%s) Waveform-URI: %s\n", schannel->name, *waveform_uri); + r->waveform_uri = NULL; + } + + apr_thread_mutex_unlock(schannel->mutex); + return 0; +} + +/* Flag that the recognizer channel timers are started. */ +static int recog_channel_set_timers_started(speech_channel_t *schannel) +{ + if (!schannel) { + ast_log(LOG_ERROR, "set_timers_started: unknown channel error!\n"); + return -1; + } + + apr_thread_mutex_lock(schannel->mutex); + + recognizer_data_t *r = (recognizer_data_t *)schannel->data; + + if (r == NULL) { + ast_log(LOG_ERROR, "(%s) Recognizer data struct is NULL\n", schannel->name); + + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + r->timers_started = 1; + + apr_thread_mutex_unlock(schannel->mutex); + return 0; +} + +/* Start VERIFY request. */ +static int verif_channel_start(speech_channel_t *schannel, const char *name, int start_input_timers, apr_hash_t *header_fields) +{ + int status = 0; + mrcp_message_t *mrcp_message = NULL; + mrcp_message_t *verif_message = NULL; + mrcp_generic_header_t *generic_header = NULL; + mrcp_recog_header_t *recog_header = NULL; + recognizer_data_t *r = NULL; + grammar_t *grammar = NULL; + + if (!schannel || !name) { + ast_log(LOG_ERROR, "recog_channel_start: unknown channel error!\n"); + return -1; + } + + apr_thread_mutex_lock(schannel->mutex); + + if (schannel->state != SPEECH_CHANNEL_READY) { + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + if (schannel->data == NULL) { + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + if ((r = (recognizer_data_t *)schannel->data) == NULL) { + ast_log(LOG_ERROR, "(%s) Recognizer data struct is NULL\n", schannel->name); + + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + r->result = NULL; + r->completion_cause = -1; + r->start_of_input = 0; + + r->timers_started = start_input_timers; + + apr_hash_index_t *hi; + void *val; + int length = 0; + + /* Create MRCP message. */ + if ((mrcp_message = mrcp_application_message_create(schannel->unimrcp_session, schannel->unimrcp_channel, VERIFIER_START_SESSION)) == NULL) { + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + /* Allocate generic header. */ + if ((generic_header = (mrcp_generic_header_t *)mrcp_generic_header_prepare(mrcp_message)) == NULL) { + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + /* Set Content-Type to text/uri-list. */ + const char *mime_type = grammar_type_to_mime(GRAMMAR_TYPE_URI, schannel->profile); + apt_string_assign(&generic_header->content_type, mime_type, mrcp_message->pool); + mrcp_generic_header_property_add(mrcp_message, GENERIC_HEADER_CONTENT_TYPE); + + /* Allocate recognizer-specific header. */ + if ((recog_header = (mrcp_recog_header_t *)mrcp_resource_header_prepare(mrcp_message)) == NULL) { + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } +#if 0 + /* Set Cancel-If-Queue. */ + if (mrcp_message->start_line.version == MRCP_VERSION_2) { + recog_header->cancel_if_queue = FALSE; + mrcp_resource_header_property_add(mrcp_message, RECOGNIZER_HEADER_CANCEL_IF_QUEUE); + } +#endif + /* Set Start-Input-Timers. */ + recog_header->start_input_timers = start_input_timers ? TRUE : FALSE; + mrcp_resource_header_property_add(mrcp_message, VERIFIER_HEADER_START_INPUT_TIMERS); + + /* Set parameters. */ + speech_channel_set_params(schannel, mrcp_message, header_fields); +#if 0 + /* Set message body. */ + apt_string_assign_n(&mrcp_message->body, grammar_refs, length, mrcp_message->pool); +#endif + /* Empty audio queue and send RECOGNIZE to MRCP server. */ + audio_queue_clear(schannel->audio_queue); + + if (mrcp_application_message_send(schannel->unimrcp_session, schannel->unimrcp_channel, mrcp_message) == FALSE) { + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + /* Wait for COMPLETE. */ + apr_thread_cond_timedwait(schannel->cond, schannel->mutex, globals.speech_channel_timeout); + + if (schannel->state != SPEECH_CHANNEL_READY) { + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + if ((verif_message = mrcp_application_message_create(schannel->unimrcp_session, schannel->unimrcp_channel, VERIFIER_VERIFY)) == NULL) { + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + if (mrcp_application_message_send(schannel->unimrcp_session, schannel->unimrcp_channel, verif_message) == FALSE) { + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + /* Wait for IN PROGRESS. */ + apr_thread_cond_timedwait(schannel->cond, schannel->mutex, globals.speech_channel_timeout); + + if (schannel->state != SPEECH_CHANNEL_PROCESSING) { + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + apr_thread_mutex_unlock(schannel->mutex); + return status; +} + +/* Load speech recognition grammar. */ +static int recog_channel_load_grammar(speech_channel_t *schannel, const char *name, grammar_type_t type, const char *data) +{ + int status = 0; + grammar_t *g = NULL; + char ldata[256]; + + if (!schannel || !name || !data) { + ast_log(LOG_ERROR, "load_grammar: unknown channel error!\n"); + return -1; + } + + const char *mime_type; + if (((mime_type = grammar_type_to_mime(type, schannel->profile)) == NULL) || (strlen(mime_type) == 0)) { + ast_log(LOG_WARNING, "(%s) Unable to get MIME type: %i\n", schannel->name, type); + return -1; + } + ast_log(LOG_DEBUG, "(%s) Loading grammar name=%s, type=%s, data=%s\n", schannel->name, name, mime_type, data); + + apr_thread_mutex_lock(schannel->mutex); + + if (schannel->state != SPEECH_CHANNEL_READY) { + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + /* If inline, use DEFINE-GRAMMAR to cache it on the server. */ + if (type != GRAMMAR_TYPE_URI) { + mrcp_message_t *mrcp_message; + mrcp_generic_header_t *generic_header; + + /* Create MRCP message. */ + if ((mrcp_message = mrcp_application_message_create(schannel->unimrcp_session, schannel->unimrcp_channel, RECOGNIZER_DEFINE_GRAMMAR)) == NULL) { + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + /* Set Content-Type and Content-ID in message. */ + if ((generic_header = (mrcp_generic_header_t *)mrcp_generic_header_prepare(mrcp_message)) == NULL) { + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + apt_string_assign(&generic_header->content_type, mime_type, mrcp_message->pool); + mrcp_generic_header_property_add(mrcp_message, GENERIC_HEADER_CONTENT_TYPE); + apt_string_assign(&generic_header->content_id, name, mrcp_message->pool); + mrcp_generic_header_property_add(mrcp_message, GENERIC_HEADER_CONTENT_ID); + + /* Put grammar in message body. */ + apt_string_assign(&mrcp_message->body, data, mrcp_message->pool); + + /* Send message and wait for response. */ + speech_channel_set_state_unlocked(schannel, SPEECH_CHANNEL_PROCESSING); + + if (mrcp_application_message_send(schannel->unimrcp_session, schannel->unimrcp_channel, mrcp_message) == FALSE) { + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + apr_thread_cond_timedwait(schannel->cond, schannel->mutex, globals.speech_channel_timeout); + + if (schannel->state != SPEECH_CHANNEL_READY) { + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + /* Set up name, type for future RECOGNIZE requests. We'll reference this cached grammar by name. */ + apr_snprintf(ldata, sizeof(ldata) - 1, "session:%s", name); + ldata[sizeof(ldata) - 1] = '\0'; + + data = ldata; + type = GRAMMAR_TYPE_URI; + } + + /* Create the grammar and save it. */ + if ((status = grammar_create(&g, name, type, data, schannel->pool)) == 0) { + recognizer_data_t *r = (recognizer_data_t *)schannel->data; + + if (r != NULL) + apr_hash_set(r->grammars, apr_pstrdup(schannel->pool, g->name), APR_HASH_KEY_STRING, g); + } + + apr_thread_mutex_unlock(schannel->mutex); + return status; +} + +/* Process messages from UniMRCP for the recognizer application. */ +static apt_bool_t recog_message_handler(const mrcp_app_message_t *app_message) +{ + /* Call the appropriate callback in the dispatcher function table based on the app_message received. */ + if (app_message) + return mrcp_application_message_dispatch(&mrcpverif->dispatcher, app_message); + + ast_log(LOG_ERROR, "(unknown) app_message error!\n"); + return TRUE; +} + +/* Handle the MRCP responses/events. */ +static apt_bool_t verif_on_message_receive(mrcp_application_t *application, mrcp_session_t *session, mrcp_channel_t *channel, mrcp_message_t *message) +{ + speech_channel_t *schannel = get_speech_channel(session); + if (!schannel || !message) { + ast_log(LOG_ERROR, "recog_on_message_receive: unknown channel error!\n"); + return FALSE; + } + + mrcp_verifier_header_t *recog_hdr = (mrcp_verifier_header_t *)mrcp_resource_header_get(message); + if (message->start_line.message_type == MRCP_MESSAGE_TYPE_RESPONSE) { + /* Received MRCP response. */ + if (message->start_line.method_id == VERIFIER_VERIFY) { + /* Received the response to RECOGNIZE request. */ + if (message->start_line.request_state == MRCP_REQUEST_STATE_INPROGRESS) { + /* RECOGNIZE in progress. */ + ast_log(LOG_NOTICE, "(%s) VERIFY IN PROGRESS\n", schannel->name); + speech_channel_set_state(schannel, SPEECH_CHANNEL_PROCESSING); + } else if (message->start_line.request_state == MRCP_REQUEST_STATE_COMPLETE) { + /* RECOGNIZE failed to start. */ + if (recog_hdr->completion_cause == RECOGNIZER_COMPLETION_CAUSE_UNKNOWN) + ast_log(LOG_DEBUG, "(%s) RECOGNIZE failed: status = %d\n", schannel->name, message->start_line.status_code); + else { + ast_log(LOG_DEBUG, "(%s) RECOGNIZE failed: status = %d, completion-cause = %03d\n", schannel->name, message->start_line.status_code, recog_hdr->completion_cause); + recog_channel_set_results(schannel, recog_hdr->completion_cause, NULL, NULL); + } + speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); + } else if (message->start_line.request_state == MRCP_REQUEST_STATE_PENDING) + /* RECOGNIZE is queued. */ + ast_log(LOG_NOTICE, "(%s) VERIFY PENDING\n", schannel->name); + else { + /* Received unexpected request_state. */ + ast_log(LOG_DEBUG, "(%s) Unexpected RECOGNIZE request state: %d\n", schannel->name, message->start_line.request_state); + speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); + } + } else if (message->start_line.method_id == VERIFIER_START_SESSION) { + /* Received response to the STOP request. */ + if (message->start_line.request_state == MRCP_REQUEST_STATE_COMPLETE) { + /* Got COMPLETE. */ + ast_log(LOG_DEBUG, "(%s) VERIFIER STARTED\n", schannel->name); + speech_channel_set_state(schannel, SPEECH_CHANNEL_READY); + } else { + /* Received unexpected request state. */ + ast_log(LOG_DEBUG, "(%s) Unexpected VERIFIER START request state: %d\n", schannel->name, message->start_line.request_state); + speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); + } + } else if (message->start_line.method_id == RECOGNIZER_START_INPUT_TIMERS) { + /* Received response to START-INPUT-TIMERS request. */ + if (message->start_line.request_state == MRCP_REQUEST_STATE_COMPLETE) { + if (message->start_line.status_code >= 200 && message->start_line.status_code <= 299) { + ast_log(LOG_DEBUG, "(%s) Timers started\n", schannel->name); + recog_channel_set_timers_started(schannel); + } else + ast_log(LOG_DEBUG, "(%s) Timers failed to start, status code = %d\n", schannel->name, message->start_line.status_code); + } + } else if (message->start_line.method_id == RECOGNIZER_DEFINE_GRAMMAR) { +#if 0 + /* Received response to DEFINE-GRAMMAR request. */ + if (message->start_line.request_state == MRCP_REQUEST_STATE_COMPLETE) { + if (message->start_line.status_code >= 200 && message->start_line.status_code <= 299) { + ast_log(LOG_DEBUG, "(%s) Grammar loaded\n", schannel->name); + speech_channel_set_state(schannel, SPEECH_CHANNEL_READY); + } else { + if (recog_hdr->completion_cause == RECOGNIZER_COMPLETION_CAUSE_UNKNOWN) + ast_log(LOG_DEBUG, "(%s) Grammar failed to load, status code = %d\n", schannel->name, message->start_line.status_code); + else { + ast_log(LOG_DEBUG, "(%s) Grammar failed to load, status code = %d, completion-cause = %03d\n", schannel->name, message->start_line.status_code, recog_hdr->completion_cause); + recog_channel_set_results(schannel, recog_hdr->completion_cause, NULL, NULL); + } + speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); + } + } +#endif + } else { + /* Received unexpected response. */ + ast_log(LOG_DEBUG, "(%s) Unexpected response, method_id = %d\n", schannel->name, (int)message->start_line.method_id); + speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); + } + } else if (message->start_line.message_type == MRCP_MESSAGE_TYPE_EVENT) { + /* Received MRCP event. */ + if (message->start_line.method_id == VERIFIER_VERIFICATION_COMPLETE) { + ast_log(LOG_DEBUG, "(%s) RECOGNITION COMPLETE, Completion-Cause: %03d\n", schannel->name, recog_hdr->completion_cause); + recog_channel_set_results(schannel, recog_hdr->completion_cause, &message->body, &recog_hdr->waveform_uri); + speech_channel_set_state(schannel, SPEECH_CHANNEL_READY); + } else if (message->start_line.method_id == VERIFIER_START_OF_INPUT) { + ast_log(LOG_DEBUG, "(%s) START OF INPUT\n", schannel->name); + recog_channel_set_start_of_input(schannel); + } else { + ast_log(LOG_DEBUG, "(%s) Unexpected event, method_id = %d\n", schannel->name, (int)message->start_line.method_id); + speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); + } + } else { + ast_log(LOG_DEBUG, "(%s) Unexpected message type, message_type = %d\n", schannel->name, message->start_line.message_type); + speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); + } + + return TRUE; +} + +/* UniMRCP callback requesting stream to be opened. */ +static apt_bool_t recog_stream_open(mpf_audio_stream_t* stream, mpf_codec_t *codec) +{ + speech_channel_t* schannel; + + if (stream) + schannel = (speech_channel_t*)stream->obj; + else + schannel = NULL; + + if (!schannel) { + ast_log(LOG_ERROR, "recog_stream_open: unknown channel error!\n"); + return FALSE; + } + + schannel->stream = stream; + return TRUE; +} + +/* UniMRCP callback requesting next frame for speech recognition. */ +static apt_bool_t recog_stream_read(mpf_audio_stream_t *stream, mpf_frame_t *frame) +{ + speech_channel_t *schannel; + + if (stream != NULL) + schannel = (speech_channel_t *)stream->obj; + else + schannel = NULL; + + if (!schannel || !frame) { + ast_log(LOG_ERROR, "recog_stream_read: unknown channel error!\n"); + return FALSE; + } + + if (schannel->dtmf_generator != NULL) { + if (mpf_dtmf_generator_sending(schannel->dtmf_generator)) { + ast_log(LOG_DEBUG, "(%s) DTMF frame written\n", schannel->name); + mpf_dtmf_generator_put_frame(schannel->dtmf_generator, frame); + return TRUE; + } + } + + apr_size_t to_read = frame->codec_frame.size; + + /* Grab the data. Pad it if there isn't enough. */ + if (speech_channel_read(schannel, frame->codec_frame.buffer, &to_read, 0) == 0) { + if (to_read < frame->codec_frame.size) + memset((apr_byte_t *)frame->codec_frame.buffer + to_read, schannel->silence, frame->codec_frame.size - to_read); + + frame->type |= MEDIA_FRAME_TYPE_AUDIO; + } + + return TRUE; +} + +/* Apply application options. */ +static int mrcprecog_option_apply(mrcprecog_options_t *options, const char *key, const char *value) +{ + if (strcasecmp(key, "ct") == 0) { + apr_hash_set(options->recog_hfs, "Confidence-Threshold", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "sva") == 0) { + apr_hash_set(options->recog_hfs, "Speed-vs-Accuracy", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "nb") == 0) { + apr_hash_set(options->recog_hfs, "N-Best-List-Length", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "nit") == 0) { + apr_hash_set(options->recog_hfs, "No-Input-Timeout", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "sct") == 0) { + apr_hash_set(options->recog_hfs, "Speech-Complete-Timeout", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "sint") == 0) { + apr_hash_set(options->recog_hfs, "Speech-Incomplete-Timeout", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "dit") == 0) { + apr_hash_set(options->recog_hfs, "Dtmf-Interdigit-Timeout", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "dtt") == 0) { + apr_hash_set(options->recog_hfs, "Dtmf-Term-Timeout", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "dttc") == 0) { + apr_hash_set(options->recog_hfs, "Dtmf-Term-Char", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "sw") == 0) { + apr_hash_set(options->recog_hfs, "Save-Waveform", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "nac") == 0) { + apr_hash_set(options->recog_hfs, "New-Audio-Channel", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "rm") == 0) { + apr_hash_set(options->recog_hfs, "Recognition-Mode", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "hmaxd") == 0) { + apr_hash_set(options->recog_hfs, "Hotword-Max-Duration", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "hmind") == 0) { + apr_hash_set(options->recog_hfs, "Hotword-Min-Duration", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "cdb") == 0) { + apr_hash_set(options->recog_hfs, "Clear-Dtmf-Buffer", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "enm") == 0) { + apr_hash_set(options->recog_hfs, "Early-No-Match", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "iwu") == 0) { + apr_hash_set(options->recog_hfs, "Input-Waveform-URI", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "sl") == 0) { + apr_hash_set(options->recog_hfs, "Sensitivity-Level", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "spl") == 0) { + apr_hash_set(options->recog_hfs, "Speech-Language", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "mt") == 0) { + apr_hash_set(options->recog_hfs, "Media-Type", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "vsp") == 0) { + apr_hash_set(options->recog_hfs, "Vendor-Specific-Parameters", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "p") == 0) { + options->flags |= MRCPRECOG_PROFILE; + options->params[OPT_ARG_PROFILE] = value; + } else if (strcasecmp(key, "i") == 0) { + options->flags |= MRCPRECOG_INTERRUPT; + options->params[OPT_ARG_INTERRUPT] = value; + } else if (strcasecmp(key, "f") == 0) { + options->flags |= MRCPRECOG_FILENAME; + options->params[OPT_ARG_FILENAME] = value; + } else if (strcasecmp(key, "t") == 0) { + apr_hash_set(options->recog_hfs, "Recognition-Timeout", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "b") == 0) { + options->flags |= MRCPRECOG_BARGEIN; + options->params[OPT_ARG_BARGEIN] = value; + } else if (strcasecmp(key, "gd") == 0) { + options->flags |= MRCPRECOG_GRAMMAR_DELIMITERS; + options->params[OPT_ARG_GRAMMAR_DELIMITERS] = value; + } else if (strcasecmp(key, "epe") == 0) { + options->flags |= MRCPRECOG_EXIT_ON_PLAYERROR; + options->params[OPT_ARG_EXIT_ON_PLAYERROR] = value; + } else if (strcasecmp(key, "uer") == 0) { + options->flags |= MRCPRECOG_URI_ENCODED_RESULTS; + options->params[OPT_ARG_URI_ENCODED_RESULTS] = value; + } else if (strcasecmp(key, "od") == 0) { + options->flags |= MRCPRECOG_OUTPUT_DELIMITERS; + options->params[OPT_ARG_OUTPUT_DELIMITERS] = value; + } else if (strcasecmp(key, "sit") == 0) { + options->flags |= MRCPRECOG_INPUT_TIMERS; + options->params[OPT_ARG_INPUT_TIMERS] = value; + } else if (strcasecmp(key, "plt") == 0) { + options->flags |= MRCPRECOG_PERSISTENT_LIFETIME; + options->params[OPT_ARG_PERSISTENT_LIFETIME] = value; + } else if (strcasecmp(key, "dse") == 0) { + options->flags |= MRCPRECOG_DATASTORE_ENTRY; + options->params[OPT_ARG_DATASTORE_ENTRY] = value; + } else if (strcasecmp(key, "nif") == 0) { + options->flags |= MRCPRECOG_INSTANCE_FORMAT; + options->params[OPT_ARG_INSTANCE_FORMAT] = value; + } else { + ast_log(LOG_WARNING, "Unknown option: %s\n", key); + } + return 0; +} + +/* Parse application options. */ +static int mrcprecog_options_parse(char *str, mrcprecog_options_t *options, apr_pool_t *pool) +{ + char *s; + char *name, *value; + + if (!str) + return 0; + + if ((options->recog_hfs = apr_hash_make(pool)) == NULL) { + return -1; + } + + do { + /* Skip any leading spaces. */ + while (isspace(*str)) + str++; + + if (*str == '<') { + /* Special case -> found an option quoted with < > */ + str++; + s = strsep(&str, ">"); + /* Skip to the next option, if any */ + strsep(&str, "&"); + } + else { + /* Regular processing */ + s = strsep(&str, "&"); + } + + if (s) { + value = s; + if ((name = strsep(&value, "=")) && value) { + ast_log(LOG_DEBUG, "Apply option %s: %s\n", name, value); + mrcprecog_option_apply(options, name, value); + } + } + } + while (str); + + return 0; +} + +/* Return the number of prompts which still need to be played. */ +static APR_INLINE int mrcprecog_prompts_available(app_session_t *app_session) +{ + if(app_session->cur_prompt >= app_session->prompts->nelts) + return 0; + return app_session->prompts->nelts - app_session->cur_prompt; +} + +/* Advance the current prompt index and return the number of prompts remaining. */ +static APR_INLINE int mrcprecog_prompts_advance(app_session_t *app_session) +{ + if (app_session->cur_prompt >= app_session->prompts->nelts) + return -1; + app_session->cur_prompt++; + return app_session->prompts->nelts - app_session->cur_prompt; +} + +/* Start playing the current prompt. */ +static struct ast_filestream* mrcprecog_prompt_play(app_session_t *app_session, mrcprecog_options_t *mrcprecog_options, off_t *max_filelength) +{ + if (app_session->cur_prompt >= app_session->prompts->nelts) { + ast_log(LOG_ERROR, "(%s) Out of bounds prompt index\n", app_session->recog_channel->name); + return NULL; + } + + char *filename = APR_ARRAY_IDX(app_session->prompts, app_session->cur_prompt, char*); + if (!filename) { + ast_log(LOG_ERROR, "(%s) Invalid file name\n", app_session->recog_channel->name); + return NULL; + } + return astchan_stream_file(app_session->recog_channel->chan, filename, max_filelength); +} + +/* Exit the application. */ +static int mrcprecog_exit(struct ast_channel *chan, app_session_t *app_session, speech_channel_status_t status) +{ + if (app_session) { + if (app_session->readformat && app_session->rawreadformat) + ast_set_read_format_path(chan, app_session->rawreadformat, app_session->readformat); + + if (app_session->recog_channel) { + if (app_session->recog_channel->session_id) + pbx_builtin_setvar_helper(chan, "RECOG_SID", app_session->recog_channel->session_id); + + if (app_session->lifetime == APP_SESSION_LIFETIME_DYNAMIC) { + speech_channel_destroy(app_session->recog_channel); + app_session->recog_channel = NULL; + } + } + } + + const char *status_str = speech_channel_status_to_string(status); + pbx_builtin_setvar_helper(chan, "RECOGSTATUS", status_str); + ast_log(LOG_NOTICE, "%s() exiting status: %s on %s\n", app_recog, status_str, ast_channel_name(chan)); + return 0; +} + +/* The entry point of the application. */ +static int app_verif_exec(struct ast_channel *chan, ast_app_data data) +{ + int dtmf_enable; + struct ast_frame *f = NULL; + apr_uint32_t speech_channel_number = get_next_speech_channel_number(); + const char *name; + speech_channel_status_t status = SPEECH_CHANNEL_STATUS_OK; + char *parse; + int i; + mrcprecog_options_t mrcprecog_options; + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(grammar); + AST_APP_ARG(options); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "%s() requires an argument (grammar[,options])\n", app_recog); + return mrcprecog_exit(chan, NULL, SPEECH_CHANNEL_STATUS_ERROR); + } + + /* We need to make a copy of the input string if we are going to modify it! */ + parse = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, parse); + + if (ast_strlen_zero(args.grammar)) { + ast_log(LOG_WARNING, "%s() requires a grammar argument (grammar[,options])\n", app_recog); + return mrcprecog_exit(chan, NULL, SPEECH_CHANNEL_STATUS_ERROR); + } + + args.grammar = normalize_input_string(args.grammar); + ast_log(LOG_NOTICE, "%s() grammar: %s\n", app_recog, args.grammar); + + app_datastore_t* datastore = app_datastore_get(chan); + if (!datastore) { + ast_log(LOG_ERROR, "Unable to retrieve data from app datastore on %s\n", ast_channel_name(chan)); + return mrcprecog_exit(chan, NULL, SPEECH_CHANNEL_STATUS_ERROR); + } + + mrcprecog_options.recog_hfs = NULL; + mrcprecog_options.flags = 0; + for (i=0; ipool, args.options); + mrcprecog_options_parse(options_buf, &mrcprecog_options, datastore->pool); + } + + /* Answer if it's not already going. */ + if (ast_channel_state(chan) != AST_STATE_UP) + ast_answer(chan); + + /* Ensure no streams are currently playing. */ + ast_stopstream(chan); + + /* Set default lifetime to dynamic. */ + int lifetime = APP_SESSION_LIFETIME_DYNAMIC; + + /* Get datastore entry. */ + const char *entry = DEFAULT_DATASTORE_ENTRY; + if ((mrcprecog_options.flags & MRCPRECOG_DATASTORE_ENTRY) == MRCPRECOG_DATASTORE_ENTRY) { + if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_DATASTORE_ENTRY])) { + entry = mrcprecog_options.params[OPT_ARG_DATASTORE_ENTRY]; + lifetime = APP_SESSION_LIFETIME_PERSISTENT; + } + } + + /* Check session lifetime. */ + if ((mrcprecog_options.flags & MRCPRECOG_PERSISTENT_LIFETIME) == MRCPRECOG_PERSISTENT_LIFETIME) { + if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_PERSISTENT_LIFETIME])) { + lifetime = (atoi(mrcprecog_options.params[OPT_ARG_PERSISTENT_LIFETIME]) == 0) ? + APP_SESSION_LIFETIME_DYNAMIC : APP_SESSION_LIFETIME_PERSISTENT; + } + } + + /* Get application datastore. */ + app_session_t *app_session = app_datastore_session_add(datastore, entry); + if (!app_session) { + return mrcprecog_exit(chan, NULL, SPEECH_CHANNEL_STATUS_ERROR); + } + + datastore->last_recog_entry = entry; + app_session->nlsml_result = NULL; + + app_session->prompts = apr_array_make(app_session->pool, 1, sizeof(char*)); + app_session->cur_prompt = 0; + app_session->it_policy = IT_POLICY_AUTO; + app_session->lifetime = lifetime; + + if(!app_session->recog_channel) { + /* Get new read format. */ + app_session->nreadformat = ast_channel_get_speechreadformat(chan, app_session->pool); + + name = apr_psprintf(app_session->pool, "ASR-%lu", (unsigned long int)speech_channel_number); + + /* Create speech channel for recognition. */ + app_session->recog_channel = speech_channel_create( + app_session->pool, + name, + SPEECH_CHANNEL_VERIFIER, + mrcpverif, + app_session->nreadformat, + NULL, + chan); + if (!app_session->recog_channel) { + return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + } + + const char *profile_name = NULL; + if ((mrcprecog_options.flags & MRCPRECOG_PROFILE) == MRCPRECOG_PROFILE) { + if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_PROFILE])) { + profile_name = mrcprecog_options.params[OPT_ARG_PROFILE]; + } + } + + /* Get recognition profile. */ + ast_mrcp_profile_t *profile = get_recog_profile(profile_name); + if (!profile) { + ast_log(LOG_ERROR, "(%s) Can't find profile, %s\n", name, profile_name); + return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + } + + /* Open recognition channel. */ + if (speech_channel_open(app_session->recog_channel, profile) != 0) { + return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + } + } + else { + name = app_session->recog_channel->name; + } + + /* Get old read format. */ + ast_format_compat *oreadformat = ast_channel_get_readformat(chan, app_session->pool); + ast_format_compat *orawreadformat = ast_channel_get_rawreadformat(chan, app_session->pool); + + /* Set read format. */ + ast_set_read_format_path(chan, orawreadformat, app_session->nreadformat); + + /* Store old read format. */ + app_session->readformat = oreadformat; + app_session->rawreadformat = orawreadformat; + + /* Check if barge-in is allowed. */ + int bargein = 1; + if ((mrcprecog_options.flags & MRCPRECOG_BARGEIN) == MRCPRECOG_BARGEIN) { + if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_BARGEIN])) { + bargein = (atoi(mrcprecog_options.params[OPT_ARG_BARGEIN]) == 0) ? 0 : 1; + } + } + + dtmf_enable = 2; + if ((mrcprecog_options.flags & MRCPRECOG_INTERRUPT) == MRCPRECOG_INTERRUPT) { + if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_INTERRUPT])) { + dtmf_enable = 1; + if (strcasecmp(mrcprecog_options.params[OPT_ARG_INTERRUPT], "any") == 0) + mrcprecog_options.params[OPT_ARG_INTERRUPT] = AST_DIGIT_ANY; + else if (strcasecmp(mrcprecog_options.params[OPT_ARG_INTERRUPT], "none") == 0) + dtmf_enable = 2; + else if (strcasecmp(mrcprecog_options.params[OPT_ARG_INTERRUPT], "disable") == 0) + dtmf_enable = 0; + } + } + + /* Get NLSML instance format, if specified */ + if ((mrcprecog_options.flags & MRCPRECOG_INSTANCE_FORMAT) == MRCPRECOG_INSTANCE_FORMAT) { + if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_INSTANCE_FORMAT])) { + const char *format = mrcprecog_options.params[OPT_ARG_INSTANCE_FORMAT]; + if (strcasecmp(format, "xml") == 0) + app_session->instance_format = NLSML_INSTANCE_FORMAT_XML; + else if (strcasecmp(format, "json") == 0) + app_session->instance_format = NLSML_INSTANCE_FORMAT_JSON; + } + } + + const char *filenames = NULL; + if ((mrcprecog_options.flags & MRCPRECOG_FILENAME) == MRCPRECOG_FILENAME) { + if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_FILENAME])) { + filenames = mrcprecog_options.params[OPT_ARG_FILENAME]; + } + } + + if (filenames) { + /* Get output delimiters. */ + const char *output_delimiters = "^"; + if ((mrcprecog_options.flags & MRCPRECOG_OUTPUT_DELIMITERS) == MRCPRECOG_OUTPUT_DELIMITERS) { + if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_OUTPUT_DELIMITERS])) { + output_delimiters = mrcprecog_options.params[OPT_ARG_OUTPUT_DELIMITERS]; + ast_log(LOG_DEBUG, "(%s) Output delimiters: %s\n", output_delimiters, name); + } + } + + /* Parse the file names into a list of files. */ + char *last; + char *filenames_arg = apr_pstrdup(app_session->pool, filenames); + char *filename = apr_strtok(filenames_arg, output_delimiters, &last); + while (filename) { + filename = normalize_input_string(filename); + ast_log(LOG_DEBUG, "(%s) Add prompt: %s\n", name, filename); + APR_ARRAY_PUSH(app_session->prompts, char*) = filename; + + filename = apr_strtok(NULL, output_delimiters, &last); + } + } + + int exit_on_playerror = 0; + if ((mrcprecog_options.flags & MRCPRECOG_EXIT_ON_PLAYERROR) == MRCPRECOG_EXIT_ON_PLAYERROR) { + if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_EXIT_ON_PLAYERROR])) { + exit_on_playerror = atoi(mrcprecog_options.params[OPT_ARG_EXIT_ON_PLAYERROR]); + if ((exit_on_playerror < 0) || (exit_on_playerror > 2)) + exit_on_playerror = 1; + } + } + + int prompt_processing = (mrcprecog_prompts_available(app_session)) ? 1 : 0; + struct ast_filestream *filestream = NULL; + off_t max_filelength; + + /* If bargein is not allowed, play all the prompts and wait for for them to complete. */ + if (!bargein && prompt_processing) { + /* Start playing first prompt. */ + filestream = mrcprecog_prompt_play(app_session, &mrcprecog_options, &max_filelength); + if (!filestream && exit_on_playerror) { + return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + } + + do { + if (filestream) { + if (ast_waitstream(chan, "") != 0) { + f = ast_read(chan); + if (!f) { + ast_log(LOG_DEBUG, "(%s) ast_waitstream failed on %s, channel read is a null frame. Hangup detected\n", name, ast_channel_name(chan)); + return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_INTERRUPTED); + } + ast_frfree(f); + + ast_log(LOG_WARNING, "(%s) ast_waitstream failed on %s\n", name, ast_channel_name(chan)); + return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + } + filestream = NULL; + } + + /* End of current prompt -> advance to the next one. */ + if (mrcprecog_prompts_advance(app_session) > 0) { + /* Start playing current prompt. */ + filestream = mrcprecog_prompt_play(app_session, &mrcprecog_options, &max_filelength); + if (!filestream && exit_on_playerror) { + return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + } + } + else { + /* End of prompts. */ + break; + } + } + while (mrcprecog_prompts_available(app_session)); + + prompt_processing = 0; + } + + /* Check the policy for input timers. */ + if ((mrcprecog_options.flags & MRCPRECOG_INPUT_TIMERS) == MRCPRECOG_INPUT_TIMERS) { + if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_INPUT_TIMERS])) { + switch(atoi(mrcprecog_options.params[OPT_ARG_INPUT_TIMERS])) { + case 0: app_session->it_policy = IT_POLICY_OFF; break; + case 1: app_session->it_policy = IT_POLICY_ON; break; + default: app_session->it_policy = IT_POLICY_AUTO; + } + } + } + + int start_input_timers = !prompt_processing; + if (app_session->it_policy != IT_POLICY_AUTO) + start_input_timers = app_session->it_policy; + recognizer_data_t *r = app_session->recog_channel->data; + + ast_log(LOG_NOTICE, "(%s) Recognizing, enable DTMFs: %d, start input timers: %d\n", name, dtmf_enable, start_input_timers); + + /* Start recognition. */ + if (verif_channel_start(app_session->recog_channel, name, start_input_timers, mrcprecog_options.recog_hfs) != 0) { + ast_log(LOG_ERROR, "(%s) Unable to start recognition\n", name); + + const char *completion_cause = NULL; + recog_channel_get_results(app_session->recog_channel, &completion_cause, NULL, NULL); + if (completion_cause) + pbx_builtin_setvar_helper(chan, "RECOG_COMPLETION_CAUSE", completion_cause); + + return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + } + + if (prompt_processing) { + /* Start playing first prompt. */ + filestream = mrcprecog_prompt_play(app_session, &mrcprecog_options, &max_filelength); + if (!filestream && exit_on_playerror) { + return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + } + } + +#if !AST_VERSION_AT_LEAST(11,0,0) + off_t read_filestep = 0; + off_t read_filelength; +#endif + int waitres; + int recog_processing; + /* Continue with verification. */ + while ((waitres = ast_waitfor(chan, 100)) >= 0) { + recog_processing = 1; + + if (app_session->recog_channel && app_session->recog_channel->mutex) { + apr_thread_mutex_lock(app_session->recog_channel->mutex); + + if (app_session->recog_channel->state != SPEECH_CHANNEL_PROCESSING) { + recog_processing = 0; + } + + apr_thread_mutex_unlock(app_session->recog_channel->mutex); + } + + if (recog_processing == 0) + break; + if (waitres == 0) + continue; + + f = ast_read(chan); + if (!f) { + ast_log(LOG_DEBUG, "(%s) Null frame. Hangup detected\n", name); + status = SPEECH_CHANNEL_STATUS_INTERRUPTED; + break; + } + + if (f->frametype == AST_FRAME_VOICE && f->datalen) { + apr_size_t len = f->datalen; + if (speech_channel_write(app_session->recog_channel, ast_frame_get_data(f), &len) != 0) { + ast_frfree(f); + break; + } + } else if (f->frametype == AST_FRAME_VIDEO) { + /* Ignore. */ + } else if ((dtmf_enable != 0) && (f->frametype == AST_FRAME_DTMF)) { + int dtmfkey = ast_frame_get_dtmfkey(f); + ast_log(LOG_DEBUG, "(%s) User pressed DTMF key (%d)\n", name, dtmfkey); + if (dtmf_enable == 2) { + /* Send DTMF frame to ASR engine. */ + if (app_session->recog_channel->dtmf_generator != NULL) { + char digits[2]; + digits[0] = (char)dtmfkey; + digits[1] = '\0'; + + ast_log(LOG_NOTICE, "(%s) DTMF digit queued (%s)\n", app_session->recog_channel->name, digits); + mpf_dtmf_generator_enqueue(app_session->recog_channel->dtmf_generator, digits); + } + } else if (dtmf_enable == 1) { + /* Stop streaming if within i chars. */ + if (strchr(mrcprecog_options.params[OPT_ARG_INTERRUPT], dtmfkey) || (strcmp(mrcprecog_options.params[OPT_ARG_INTERRUPT],"any"))) { + ast_frfree(f); + mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_OK); + return dtmfkey; + } + + /* Continue if not an i-key. */ + } + } + + ast_frfree(f); + } + + if (prompt_processing) { + ast_log(LOG_DEBUG, "(%s) Stop prompt\n", name); + ast_stopstream(chan); + filestream = NULL; + prompt_processing = 0; + } + + const char *completion_cause = NULL; + const char *result = NULL; + const char *waveform_uri = NULL; + + if (status == SPEECH_CHANNEL_STATUS_OK) { + int uri_encoded_results = 0; + /* Check if the results should be URI-encoded. */ + if ((mrcprecog_options.flags & MRCPRECOG_URI_ENCODED_RESULTS) == MRCPRECOG_URI_ENCODED_RESULTS) { + if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_URI_ENCODED_RESULTS])) { + uri_encoded_results = (atoi(mrcprecog_options.params[OPT_ARG_URI_ENCODED_RESULTS]) == 0) ? 0 : 1; + } + } + + /* Get recognition result. */ + if (recog_channel_get_results(app_session->recog_channel, &completion_cause, &result, &waveform_uri) != 0) { + ast_log(LOG_WARNING, "(%s) Unable to retrieve result\n", name); + return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + } + + if (result) { + /* Store the results for further reference from the dialplan. */ + apr_size_t result_len = strlen(result); + app_session->nlsml_result = nlsml_result_parse(result, result_len, datastore->pool); + + if (uri_encoded_results != 0) { + apr_size_t len = result_len * 2; + char *buf = apr_palloc(app_session->pool, len); + result = ast_uri_encode_http(result, buf, len); + } + } + } + + /* Completion cause should always be available at this stage. */ + if (completion_cause) + pbx_builtin_setvar_helper(chan, "RECOG_COMPLETION_CAUSE", completion_cause); + + /* Result may not be available if recognition completed with nomatch, noinput, or other error cause. */ + pbx_builtin_setvar_helper(chan, "RECOG_RESULT", result ? result : ""); + + /* If Waveform URI is available, pass it further to dialplan. */ + if (waveform_uri) + pbx_builtin_setvar_helper(chan, "RECOG_WAVEFORM_URI", waveform_uri); + + return mrcprecog_exit(chan, app_session, status); +} + +/* Load MRCPVerif application. */ +int load_mrcpverif_app() +{ + apr_pool_t *pool = globals.pool; + + if (pool == NULL) { + ast_log(LOG_ERROR, "Memory pool is NULL\n"); + return -1; + } + + if(mrcpverif) { + ast_log(LOG_ERROR, "Application %s is already loaded\n", app_recog); + return -1; + } + + mrcpverif = (ast_mrcp_application_t*) apr_palloc(pool, sizeof(ast_mrcp_application_t)); + mrcpverif->name = app_recog; + mrcpverif->exec = app_verif_exec; +#if !AST_VERSION_AT_LEAST(1,6,2) + mrcpverif->synopsis = NULL; + mrcpverif->description = NULL; +#endif + + /* Create the recognizer application and link its callbacks */ + if ((mrcpverif->app = mrcp_application_create(recog_message_handler, (void *)0, pool)) == NULL) { + ast_log(LOG_ERROR, "Unable to create recognizer MRCP application %s\n", app_recog); + mrcpverif = NULL; + return -1; + } + + mrcpverif->dispatcher.on_session_update = NULL; + mrcpverif->dispatcher.on_session_terminate = speech_on_session_terminate; + mrcpverif->dispatcher.on_channel_add = speech_on_channel_add; + mrcpverif->dispatcher.on_channel_remove = NULL; + mrcpverif->dispatcher.on_message_receive = verif_on_message_receive; + mrcpverif->dispatcher.on_terminate_event = NULL; + mrcpverif->dispatcher.on_resource_discover = NULL; + mrcpverif->audio_stream_vtable.destroy = NULL; + mrcpverif->audio_stream_vtable.open_rx = recog_stream_open; + mrcpverif->audio_stream_vtable.close_rx = NULL; + mrcpverif->audio_stream_vtable.read_frame = recog_stream_read; + mrcpverif->audio_stream_vtable.open_tx = NULL; + mrcpverif->audio_stream_vtable.close_tx = NULL; + mrcpverif->audio_stream_vtable.write_frame = NULL; + mrcpverif->audio_stream_vtable.trace = NULL; + + if (!mrcp_client_application_register(globals.mrcp_client, mrcpverif->app, app_recog)) { + ast_log(LOG_ERROR, "Unable to register recognizer MRCP application %s\n", app_recog); + if (!mrcp_application_destroy(mrcpverif->app)) + ast_log(LOG_WARNING, "Unable to destroy recognizer MRCP application %s\n", app_recog); + mrcpverif = NULL; + return -1; + } + + apr_hash_set(globals.apps, app_recog, APR_HASH_KEY_STRING, mrcpverif); + + return 0; +} + +/* Unload MRCPVerif application. */ +int unload_mrcpverif_app() +{ + if(!mrcpverif) { + ast_log(LOG_ERROR, "Application %s doesn't exist\n", app_recog); + return -1; + } + + apr_hash_set(globals.apps, app_recog, APR_HASH_KEY_STRING, NULL); + mrcpverif = NULL; + + return 0; +} diff --git a/app-unimrcp/app_recogverif.c b/app-unimrcp/app_recogverif.c new file mode 100644 index 0000000..9bbee75 --- /dev/null +++ b/app-unimrcp/app_recogverif.c @@ -0,0 +1,1953 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2009, Molo Afrika Speech Technologies (Pty) Ltd + * + * J.W.F. Thirion + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + * + * Please follow coding guidelines + * http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES + */ + +/* By Molo Afrika Speech Technologies (Pty) Ltd + * See: http://www.molo.co.za + * + * Ideas, concepts and code borrowed from UniMRCP's example programs + * and the FreeSWITCH mod_unimrcp ASR/TTS module. + * + * Authors of these are: + * UniMRCP: + * Arsen Chaloyan + * FreeSWITCH: mod_unimrcp + * Christopher M. Rienzo + * + * See: + * http://www.unimrcp.org + * http://www.freeswitch.org + */ + +/*! \file + * + * \brief MRCPRecogVerif application + * + * \author\verbatim J.W.F. Thirion \endverbatim + * + * MRCPRecogVerif application + * \ingroup applications + */ + +/* Asterisk includes. */ +#include "ast_compat_defs.h" + +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/app.h" + +/* UniMRCP includes. */ +#include "app_datastore.h" + +/*** DOCUMENTATION + + + MRCP recognition application. + + + + An inline or URI grammar to be used for recognition. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This application establishes an MRCP session for speech recognition and optionally plays a prompt file. + Once recognition completes, the application exits and returns results to the dialplan. + If recognition completed, the variable ${RECOGSTATUS} is set to "OK". Otherwise, if recognition couldn't be started, + the variable ${RECOGSTATUS} is set to "ERROR". If the caller hung up while recognition was still in-progress, + the variable ${RECOGSTATUS} is set to "INTERRUPTED". + The variable ${RECOG_COMPLETION_CAUSE} indicates whether recognition completed successfully with a match or + an error occurred. ("000" - success, "001" - nomatch, "002" - noinput) + If recognition completed successfully, the variable ${RECOG_RESULT} is set to an NLSML result received + from the MRCP server. Alternatively, the recognition result data can be retrieved by using the following dialplan + functions RECOG_CONFIDENCE(), RECOG_GRAMMAR(), RECOG_INPUT(), and RECOG_INSTANCE(). + + + MRCPSynth + SynthAndRecog + RECOG_CONFIDENCE + RECOG_GRAMMAR + RECOG_INPUT + RECOG_INSTANCE + + + ***/ + +/* The name of the application. */ +static const char *app_recog = "MRCPRecogVerif"; + +/* The application instance. */ +static ast_mrcp_application_t *mrcprecog = NULL; + +/* The enumeration of application options (excluding the MRCP params). */ +enum mrcprecog_option_flags { + MRCPRECOG_PROFILE = (1 << 0), + MRCPRECOG_INTERRUPT = (1 << 1), + MRCPRECOG_FILENAME = (1 << 2), + MRCPRECOG_BARGEIN = (1 << 3), + MRCPRECOG_GRAMMAR_DELIMITERS = (1 << 4), + MRCPRECOG_EXIT_ON_PLAYERROR = (1 << 5), + MRCPRECOG_URI_ENCODED_RESULTS = (1 << 6), + MRCPRECOG_OUTPUT_DELIMITERS = (1 << 7), + MRCPRECOG_INPUT_TIMERS = (1 << 8), + MRCPRECOG_PERSISTENT_LIFETIME = (1 << 9), + MRCPRECOG_DATASTORE_ENTRY = (1 << 10), + MRCPRECOG_INSTANCE_FORMAT = (1 << 11) +}; + +/* The enumeration of option arguments. */ +enum mrcprecog_option_args { + OPT_ARG_PROFILE = 0, + OPT_ARG_INTERRUPT = 1, + OPT_ARG_FILENAME = 2, + OPT_ARG_BARGEIN = 3, + OPT_ARG_GRAMMAR_DELIMITERS = 4, + OPT_ARG_EXIT_ON_PLAYERROR = 5, + OPT_ARG_URI_ENCODED_RESULTS = 6, + OPT_ARG_OUTPUT_DELIMITERS = 7, + OPT_ARG_INPUT_TIMERS = 8, + OPT_ARG_PERSISTENT_LIFETIME = 9, + OPT_ARG_DATASTORE_ENTRY = 10, + OPT_ARG_INSTANCE_FORMAT = 11, + + /* This MUST be the last value in this enum! */ + OPT_ARG_ARRAY_SIZE = 12 +}; + +/* The enumeration of plocies for the use of input timers. */ +enum mrcprecog_it_policies { + IT_POLICY_OFF = 0, /* do not start input timers */ + IT_POLICY_ON = 1, /* start input timers with RECOGNIZE */ + IT_POLICY_AUTO /* start input timers once prompt is finished [default] */ +}; + +/* The structure which holds the application options (including the MRCP params). */ +struct mrcprecog_options_t { + apr_hash_t *recog_hfs; + + int flags; + const char *params[OPT_ARG_ARRAY_SIZE]; +}; + +typedef struct mrcprecog_options_t mrcprecog_options_t; + +/* --- MRCP SPEECH CHANNEL INTERFACE TO UNIMRCP --- */ + +/* Get speech channel associated with provided MRCP session. */ +static APR_INLINE speech_channel_t * get_speech_channel(mrcp_session_t *session) +{ + if (session) + return (speech_channel_t *)mrcp_application_session_object_get(session); + + return NULL; +} + +/* Handle the UniMRCP responses sent to session terminate requests. */ +static apt_bool_t speech_on_session_terminate(mrcp_application_t *application, mrcp_session_t *session, mrcp_sig_status_code_e status) +{ + speech_channel_t *schannel = get_speech_channel(session); + if (!schannel) { + ast_log(LOG_ERROR, "speech_on_session_terminate: unknown channel error!\n"); + return FALSE; + } + + ast_log(LOG_DEBUG, "(%s) speech_on_session_terminate\n", schannel->name); + + if (schannel->dtmf_generator != NULL) { + ast_log(LOG_DEBUG, "(%s) DTMF generator destroyed\n", schannel->name); + mpf_dtmf_generator_destroy(schannel->dtmf_generator); + schannel->dtmf_generator = NULL; + } + + ast_log(LOG_DEBUG, "(%s) Destroying MRCP session\n", schannel->name); + + if (!mrcp_application_session_destroy(session)) + ast_log(LOG_WARNING, "(%s) Unable to destroy application session\n", schannel->name); + + speech_channel_set_state(schannel, SPEECH_CHANNEL_CLOSED); + return TRUE; +} + +/* Handle the UniMRCP responses sent to channel add requests. */ +static apt_bool_t speech_on_channel_add(mrcp_application_t *application, mrcp_session_t *session, mrcp_channel_t *channel, mrcp_sig_status_code_e status) +{ + speech_channel_t *schannel = get_speech_channel(session); + if (!schannel || !channel) { + ast_log(LOG_ERROR, "speech_on_channel_add: unknown channel error!\n"); + return FALSE; + } + + ast_log(LOG_DEBUG, "(%s) speech_on_channel_add - status: %d \n", schannel->name, status); + + if (status == MRCP_SIG_STATUS_CODE_SUCCESS) { + const mpf_codec_descriptor_t *descriptor = mrcp_application_source_descriptor_get(channel); + if (!descriptor) { + ast_log(LOG_ERROR, "(%s) Unable to determine codec descriptor\n", schannel->name); + speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); + return FALSE; + } + + if (schannel->stream != NULL) { + schannel->dtmf_generator = mpf_dtmf_generator_create(schannel->stream, schannel->pool); + /* schannel->dtmf_generator = mpf_dtmf_generator_create_ex(schannel->stream, MPF_DTMF_GENERATOR_OUTBAND, 70, 50, schannel->pool); */ + + if (schannel->dtmf_generator != NULL) + ast_log(LOG_DEBUG, "(%s) DTMF generator created\n", schannel->name); + else + ast_log(LOG_WARNING, "(%s) Unable to create DTMF generator\n", schannel->name); + } + + schannel->rate = descriptor->sampling_rate; + const char *codec_name = NULL; + if (descriptor->name.length > 0) + codec_name = descriptor->name.buf; + else + codec_name = "unknown"; + + if (!schannel->session_id) { + const apt_str_t *session_id = mrcp_application_session_id_get(session); + if (session_id && session_id->buf) { + schannel->session_id = apr_pstrdup(schannel->pool, session_id->buf); + } + } + + ast_log(LOG_NOTICE, "(%s) Channel ready codec=%s, sample rate=%d\n", + schannel->name, + codec_name, + schannel->rate); + speech_channel_set_state(schannel, SPEECH_CHANNEL_READY); + } else { + int rc = mrcp_application_session_response_code_get(session); + ast_log(LOG_ERROR, "(%s) Channel error status=%d, response code=%d!\n", schannel->name, status, rc); + speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); + } + + return TRUE; +} + +/* --- MRCP ASR --- */ + +/* Start recognizer's input timers. */ +static int recog_channel_start_input_timers(speech_channel_t *schannel) +{ + int status = 0; + + if (!schannel) { + ast_log(LOG_ERROR, "start_input_timers: unknown channel error!\n"); + return -1; + } + + apr_thread_mutex_lock(schannel->mutex); + + recognizer_data_t *r = (recognizer_data_t *)schannel->data; + + if (r == NULL) { + ast_log(LOG_ERROR, "(%s) Recognizer data struct is NULL\n", schannel->name); + + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + if ((schannel->state == SPEECH_CHANNEL_PROCESSING) && (!r->timers_started)) { + mrcp_message_t *mrcp_message; + ast_log(LOG_DEBUG, "(%s) Sending START-INPUT-TIMERS request\n", schannel->name); + + /* Send START-INPUT-TIMERS to MRCP server. */ + mrcp_message = mrcp_application_message_create(schannel->unimrcp_session, schannel->unimrcp_channel, RECOGNIZER_START_INPUT_TIMERS); + + if (mrcp_message) { + mrcp_application_message_send(schannel->unimrcp_session, schannel->unimrcp_channel, mrcp_message); + } else { + ast_log(LOG_ERROR, "(%s) Failed to create START-INPUT-TIMERS message\n", schannel->name); + status = -1; + } + } + + apr_thread_mutex_unlock(schannel->mutex); + return status; +} + +/* Flag that input has started. */ +static int recog_channel_set_start_of_input(speech_channel_t *schannel) +{ + int status = 0; + + if (!schannel) { + ast_log(LOG_ERROR, "set_start_of_input: unknown channel error!\n"); + return -1; + } + + apr_thread_mutex_lock(schannel->mutex); + + recognizer_data_t *r = (recognizer_data_t *)schannel->data; + + if (r == NULL) { + ast_log(LOG_ERROR, "(%s) Recognizer data struct is NULL\n", schannel->name); + + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + r->start_of_input = 1; + + apr_thread_mutex_unlock(schannel->mutex); + return status; +} + +/* Set the recognition results. */ +static int recog_channel_set_results(speech_channel_t *schannel, int completion_cause, const apt_str_t *result, const apt_str_t *waveform_uri) +{ + int status = 0; + + if (!schannel) { + ast_log(LOG_ERROR, "set_results: unknown channel error!\n"); + return -1; + } + + apr_thread_mutex_lock(schannel->mutex); + + recognizer_data_t *r = (recognizer_data_t *)schannel->data; + + if (r == NULL) { + ast_log(LOG_ERROR, "(%s) Recognizer data struct is NULL\n", schannel->name); + + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + if (r->completion_cause >= 0) { + ast_log(LOG_DEBUG, "(%s) Result is already set\n", schannel->name); + + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + if (result && result->length > 0) { + /* The duplicated string will always be NUL-terminated. */ + r->result = apr_pstrndup(schannel->pool, result->buf, result->length); + ast_log(LOG_DEBUG, "(%s) Set result:\n\n%s\n", schannel->name, r->result); + } + r->completion_cause = completion_cause; + if (waveform_uri && waveform_uri->length > 0) + r->waveform_uri = apr_pstrndup(schannel->pool, waveform_uri->buf, waveform_uri->length); + + apr_thread_mutex_unlock(schannel->mutex); + return status; +} + +/* Get the recognition results. */ +static int recog_channel_get_results(speech_channel_t *schannel, const char **completion_cause, const char **result, const char **waveform_uri) +{ + if (!schannel) { + ast_log(LOG_ERROR, "get_results: unknown channel error!\n"); + return -1; + } + + apr_thread_mutex_lock(schannel->mutex); + + recognizer_data_t *r = (recognizer_data_t *)schannel->data; + + if (r == NULL) { + ast_log(LOG_ERROR, "(%s) Recognizer data struct is NULL\n", schannel->name); + + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + if (r->completion_cause < 0) { + ast_log(LOG_ERROR, "(%s) Recognition terminated prematurely\n", schannel->name); + + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + if (completion_cause) { + *completion_cause = apr_psprintf(schannel->pool, "%03d", r->completion_cause); + ast_log(LOG_DEBUG, "(%s) Completion-Cause: %s\n", schannel->name, *completion_cause); + r->completion_cause = 0; + } + + if (result && r->result && strlen(r->result) > 0) { + *result = apr_pstrdup(schannel->pool, r->result); + ast_log(LOG_NOTICE, "(%s) Result:\n\n%s\n", schannel->name, *result); + r->result = NULL; + } + + if (waveform_uri && r->waveform_uri && (strlen(r->waveform_uri)) > 0) { + *waveform_uri = apr_pstrdup(schannel->pool, r->waveform_uri); + ast_log(LOG_DEBUG, "(%s) Waveform-URI: %s\n", schannel->name, *waveform_uri); + r->waveform_uri = NULL; + } + + apr_thread_mutex_unlock(schannel->mutex); + return 0; +} + +/* Flag that the recognizer channel timers are started. */ +static int recog_channel_set_timers_started(speech_channel_t *schannel) +{ + if (!schannel) { + ast_log(LOG_ERROR, "set_timers_started: unknown channel error!\n"); + return -1; + } + + apr_thread_mutex_lock(schannel->mutex); + + recognizer_data_t *r = (recognizer_data_t *)schannel->data; + + if (r == NULL) { + ast_log(LOG_ERROR, "(%s) Recognizer data struct is NULL\n", schannel->name); + + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + r->timers_started = 1; + + apr_thread_mutex_unlock(schannel->mutex); + return 0; +} + +/* Start RECOGNIZE request. */ +static int recog_channel_start(speech_channel_t *schannel, const char *name, int start_input_timers, apr_hash_t *header_fields) +{ + int status = 0; + mrcp_message_t *mrcp_message = NULL; + mrcp_generic_header_t *generic_header = NULL; + mrcp_recog_header_t *recog_header = NULL; + recognizer_data_t *r = NULL; + grammar_t *grammar = NULL; + + if (!schannel || !name) { + ast_log(LOG_ERROR, "recog_channel_start: unknown channel error!\n"); + return -1; + } + + apr_thread_mutex_lock(schannel->mutex); + + if (schannel->state != SPEECH_CHANNEL_READY) { + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + if (schannel->data == NULL) { + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + if ((r = (recognizer_data_t *)schannel->data) == NULL) { + ast_log(LOG_ERROR, "(%s) Recognizer data struct is NULL\n", schannel->name); + + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + r->result = NULL; + r->completion_cause = -1; + r->start_of_input = 0; + + r->timers_started = start_input_timers; + + apr_hash_index_t *hi; + void *val; + int length = 0; + char grammar_refs[4096]; + for (hi = apr_hash_first(schannel->pool, r->grammars); hi; hi = apr_hash_next(hi)) { + apr_hash_this(hi, NULL, NULL, &val); + grammar = val; + if (!grammar) continue; + + int grammar_len = strlen(grammar->data); + if (length + grammar_len + 2 > sizeof(grammar_refs) - 1) { + break; + } + + if (length) { + grammar_refs[length++] = '\r'; + grammar_refs[length++] = '\n'; + } + memcpy(grammar_refs + length, grammar->data, grammar_len); + length += grammar_len; + } + if (length == 0) { + ast_log(LOG_ERROR, "(%s) No grammars specified\n", schannel->name); + + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + grammar_refs[length] = '\0'; + + /* Create MRCP message. */ + if ((mrcp_message = mrcp_application_message_create(schannel->unimrcp_session, schannel->unimrcp_channel, RECOGNIZER_RECOGNIZE)) == NULL) { + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + /* Allocate generic header. */ + if ((generic_header = (mrcp_generic_header_t *)mrcp_generic_header_prepare(mrcp_message)) == NULL) { + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + /* Set Content-Type to text/uri-list. */ + const char *mime_type = grammar_type_to_mime(GRAMMAR_TYPE_URI, schannel->profile); + apt_string_assign(&generic_header->content_type, mime_type, mrcp_message->pool); + mrcp_generic_header_property_add(mrcp_message, GENERIC_HEADER_CONTENT_TYPE); + + /* Allocate recognizer-specific header. */ + if ((recog_header = (mrcp_recog_header_t *)mrcp_resource_header_prepare(mrcp_message)) == NULL) { + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + /* Set Cancel-If-Queue. */ + if (mrcp_message->start_line.version == MRCP_VERSION_2) { + recog_header->cancel_if_queue = FALSE; + mrcp_resource_header_property_add(mrcp_message, RECOGNIZER_HEADER_CANCEL_IF_QUEUE); + } + + /* Set Start-Input-Timers. */ + recog_header->start_input_timers = start_input_timers ? TRUE : FALSE; + mrcp_resource_header_property_add(mrcp_message, RECOGNIZER_HEADER_START_INPUT_TIMERS); + + /* Set parameters. */ + speech_channel_set_params(schannel, mrcp_message, header_fields); + + /* Set message body. */ + apt_string_assign_n(&mrcp_message->body, grammar_refs, length, mrcp_message->pool); + + /* Empty audio queue and send RECOGNIZE to MRCP server. */ + audio_queue_clear(schannel->audio_queue); + + if (mrcp_application_message_send(schannel->unimrcp_session, schannel->unimrcp_channel, mrcp_message) == FALSE) { + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + /* Wait for IN PROGRESS. */ + apr_thread_cond_timedwait(schannel->cond, schannel->mutex, globals.speech_channel_timeout); + + if (schannel->state != SPEECH_CHANNEL_PROCESSING) { + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + apr_thread_mutex_unlock(schannel->mutex); + return status; +} + +/* Start VERIFY request. */ +static int verif_channel_start(speech_channel_t *schannel, const char *name, int start_input_timers, apr_hash_t *header_fields) +{ + int status = 0; + mrcp_message_t *mrcp_message = NULL; + mrcp_message_t *verif_message = NULL; + mrcp_generic_header_t *generic_header = NULL; + mrcp_recog_header_t *recog_header = NULL; + recognizer_data_t *r = NULL; + grammar_t *grammar = NULL; + + if (!schannel || !name) { + ast_log(LOG_ERROR, "verif_channel_start: unknown channel error!\n"); + return -1; + } + + apr_thread_mutex_lock(schannel->mutex); + + if (schannel->state != SPEECH_CHANNEL_READY) { + ast_log(LOG_ERROR, "verif_channel_start: invalid state!\n"); + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + if (schannel->data == NULL) { + ast_log(LOG_ERROR, "verif_channel_start: invalid data!\n"); + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + if ((r = (recognizer_data_t *)schannel->data) == NULL) { + ast_log(LOG_ERROR, "(%s) Recognizer data struct is NULL\n", schannel->name); + + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + r->result = NULL; + r->completion_cause = -1; + r->start_of_input = 0; + + r->timers_started = start_input_timers; + + apr_hash_index_t *hi; + void *val; + int length = 0; + + /* Create MRCP message. */ + if ((mrcp_message = mrcp_application_message_create(schannel->unimrcp_session, schannel->unimrcp_channel, VERIFIER_START_SESSION)) == NULL) { + apr_thread_mutex_unlock(schannel->mutex); + ast_log(LOG_ERROR, "verif_channel_start: error on VERIFIER_START_SESSION!\n"); + return -1; + } + + /* Allocate generic header. */ + if ((generic_header = (mrcp_generic_header_t *)mrcp_generic_header_prepare(mrcp_message)) == NULL) { + apr_thread_mutex_unlock(schannel->mutex); + ast_log(LOG_ERROR, "verif_channel_start: error on VERIFIER_START_SESSION header!\n"); + return -1; + } + + /* Set Content-Type to text/uri-list. */ + const char *mime_type = grammar_type_to_mime(GRAMMAR_TYPE_URI, schannel->profile); + apt_string_assign(&generic_header->content_type, mime_type, mrcp_message->pool); + mrcp_generic_header_property_add(mrcp_message, GENERIC_HEADER_CONTENT_TYPE); + + /* Allocate recognizer-specific header. */ + if ((recog_header = (mrcp_recog_header_t *)mrcp_resource_header_prepare(mrcp_message)) == NULL) { + ast_log(LOG_ERROR, "verif_channel_start: error on VERIFIER_START_SESSION resource header!\n"); + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } +#if 0 + /* Set Cancel-If-Queue. */ + if (mrcp_message->start_line.version == MRCP_VERSION_2) { + recog_header->cancel_if_queue = FALSE; + mrcp_resource_header_property_add(mrcp_message, RECOGNIZER_HEADER_CANCEL_IF_QUEUE); + } +#endif + /* Set Start-Input-Timers. */ + recog_header->start_input_timers = start_input_timers ? TRUE : FALSE; + mrcp_resource_header_property_add(mrcp_message, VERIFIER_HEADER_START_INPUT_TIMERS); + + /* Set parameters. */ + speech_channel_set_params(schannel, mrcp_message, header_fields); +#if 0 + /* Set message body. */ + apt_string_assign_n(&mrcp_message->body, grammar_refs, length, mrcp_message->pool); +#endif + /* Empty audio queue and send RECOGNIZE to MRCP server. */ + audio_queue_clear(schannel->audio_queue); + + if (mrcp_application_message_send(schannel->unimrcp_session, schannel->unimrcp_channel, mrcp_message) == FALSE) { + apr_thread_mutex_unlock(schannel->mutex); + ast_log(LOG_ERROR, "verif_channel_start: error on VERIFIER_START_SESSION send!\n"); + return -1; + } + + /* Wait for COMPLETE. */ + apr_thread_cond_timedwait(schannel->cond, schannel->mutex, globals.speech_channel_timeout); + + if (schannel->state != SPEECH_CHANNEL_READY) { + ast_log(LOG_ERROR, "verif_channel_start: error on VERIFIER_START_SESSION wait!\n"); + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + if ((verif_message = mrcp_application_message_create(schannel->unimrcp_session, schannel->unimrcp_channel, VERIFIER_VERIFY_FROM_BUFFER)) == NULL) { + ast_log(LOG_ERROR, "verif_channel_start: error on VERIFIER_VERIFY_FROM_BUFFER create!\n"); + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + if (mrcp_application_message_send(schannel->unimrcp_session, schannel->unimrcp_channel, verif_message) == FALSE) { + ast_log(LOG_ERROR, "verif_channel_start: error on VERIFIER_VERIFY_FROM_BUFFER send!\n"); + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + /* Wait for IN PROGRESS. */ + apr_thread_cond_timedwait(schannel->cond, schannel->mutex, globals.speech_channel_timeout); + + if (schannel->state != SPEECH_CHANNEL_PROCESSING) { + ast_log(LOG_ERROR, "verif_channel_start: error on SPEECH_CHANNEL_PROCESSING state!\n"); + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + apr_thread_mutex_unlock(schannel->mutex); + return status; +} + +/* Load speech recognition grammar. */ +static int recog_channel_load_grammar(speech_channel_t *schannel, const char *name, grammar_type_t type, const char *data) +{ + int status = 0; + grammar_t *g = NULL; + char ldata[256]; + + if (!schannel || !name || !data) { + ast_log(LOG_ERROR, "load_grammar: unknown channel error!\n"); + return -1; + } + + const char *mime_type; + if (((mime_type = grammar_type_to_mime(type, schannel->profile)) == NULL) || (strlen(mime_type) == 0)) { + ast_log(LOG_WARNING, "(%s) Unable to get MIME type: %i\n", schannel->name, type); + return -1; + } + ast_log(LOG_DEBUG, "(%s) Loading grammar name=%s, type=%s, data=%s\n", schannel->name, name, mime_type, data); + + apr_thread_mutex_lock(schannel->mutex); + + if (schannel->state != SPEECH_CHANNEL_READY) { + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + /* If inline, use DEFINE-GRAMMAR to cache it on the server. */ + if (type != GRAMMAR_TYPE_URI) { + mrcp_message_t *mrcp_message; + mrcp_generic_header_t *generic_header; + + /* Create MRCP message. */ + if ((mrcp_message = mrcp_application_message_create(schannel->unimrcp_session, schannel->unimrcp_channel, RECOGNIZER_DEFINE_GRAMMAR)) == NULL) { + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + /* Set Content-Type and Content-ID in message. */ + if ((generic_header = (mrcp_generic_header_t *)mrcp_generic_header_prepare(mrcp_message)) == NULL) { + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + apt_string_assign(&generic_header->content_type, mime_type, mrcp_message->pool); + mrcp_generic_header_property_add(mrcp_message, GENERIC_HEADER_CONTENT_TYPE); + apt_string_assign(&generic_header->content_id, name, mrcp_message->pool); + mrcp_generic_header_property_add(mrcp_message, GENERIC_HEADER_CONTENT_ID); + + /* Put grammar in message body. */ + apt_string_assign(&mrcp_message->body, data, mrcp_message->pool); + + /* Send message and wait for response. */ + speech_channel_set_state_unlocked(schannel, SPEECH_CHANNEL_PROCESSING); + + if (mrcp_application_message_send(schannel->unimrcp_session, schannel->unimrcp_channel, mrcp_message) == FALSE) { + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + apr_thread_cond_timedwait(schannel->cond, schannel->mutex, globals.speech_channel_timeout); + + if (schannel->state != SPEECH_CHANNEL_READY) { + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } + + /* Set up name, type for future RECOGNIZE requests. We'll reference this cached grammar by name. */ + apr_snprintf(ldata, sizeof(ldata) - 1, "session:%s", name); + ldata[sizeof(ldata) - 1] = '\0'; + + data = ldata; + type = GRAMMAR_TYPE_URI; + } + + /* Create the grammar and save it. */ + if ((status = grammar_create(&g, name, type, data, schannel->pool)) == 0) { + recognizer_data_t *r = (recognizer_data_t *)schannel->data; + + if (r != NULL) + apr_hash_set(r->grammars, apr_pstrdup(schannel->pool, g->name), APR_HASH_KEY_STRING, g); + } + + apr_thread_mutex_unlock(schannel->mutex); + return status; +} + +/* Process messages from UniMRCP for the recognizer application. */ +static apt_bool_t recog_message_handler(const mrcp_app_message_t *app_message) +{ + /* Call the appropriate callback in the dispatcher function table based on the app_message received. */ + if (app_message) + return mrcp_application_message_dispatch(&mrcprecog->dispatcher, app_message); + + ast_log(LOG_ERROR, "(unknown) app_message error!\n"); + return TRUE; +} + +/* Handle the MRCP responses/events. */ +static apt_bool_t recog_on_message_receive(mrcp_application_t *application, mrcp_session_t *session, mrcp_channel_t *channel, mrcp_message_t *message) +{ + speech_channel_t *schannel = get_speech_channel(session); + if (!schannel || !message) { + ast_log(LOG_ERROR, "recog_on_message_receive: unknown channel error!\n"); + return FALSE; + } + + mrcp_recog_header_t *recog_hdr = (mrcp_recog_header_t *)mrcp_resource_header_get(message); + if (message->start_line.message_type == MRCP_MESSAGE_TYPE_RESPONSE) { + /* Received MRCP response. */ + if (message->start_line.method_id == RECOGNIZER_RECOGNIZE) { + /* Received the response to RECOGNIZE request. */ + if (message->start_line.request_state == MRCP_REQUEST_STATE_INPROGRESS) { + /* RECOGNIZE in progress. */ + ast_log(LOG_DEBUG, "(%s) RECOGNIZE IN PROGRESS\n", schannel->name); + speech_channel_set_state(schannel, SPEECH_CHANNEL_PROCESSING); + } else if (message->start_line.request_state == MRCP_REQUEST_STATE_COMPLETE) { + /* RECOGNIZE failed to start. */ + if (recog_hdr->completion_cause == RECOGNIZER_COMPLETION_CAUSE_UNKNOWN) + ast_log(LOG_DEBUG, "(%s) RECOGNIZE failed: status = %d\n", schannel->name, message->start_line.status_code); + else { + ast_log(LOG_DEBUG, "(%s) RECOGNIZE failed: status = %d, completion-cause = %03d\n", schannel->name, message->start_line.status_code, recog_hdr->completion_cause); + recog_channel_set_results(schannel, recog_hdr->completion_cause, NULL, NULL); + } + speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); + } else if (message->start_line.request_state == MRCP_REQUEST_STATE_PENDING) + /* RECOGNIZE is queued. */ + ast_log(LOG_DEBUG, "(%s) RECOGNIZE PENDING\n", schannel->name); + else { + /* Received unexpected request_state. */ + ast_log(LOG_DEBUG, "(%s) Unexpected RECOGNIZE request state: %d\n", schannel->name, message->start_line.request_state); + speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); + } + } else if (message->start_line.method_id == RECOGNIZER_STOP) { + /* Received response to the STOP request. */ + if (message->start_line.request_state == MRCP_REQUEST_STATE_COMPLETE) { + /* Got COMPLETE. */ + ast_log(LOG_DEBUG, "(%s) RECOGNIZE STOPPED\n", schannel->name); + speech_channel_set_state(schannel, SPEECH_CHANNEL_READY); + } else { + /* Received unexpected request state. */ + ast_log(LOG_DEBUG, "(%s) Unexpected STOP request state: %d\n", schannel->name, message->start_line.request_state); + speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); + } + } else if (message->start_line.method_id == RECOGNIZER_START_INPUT_TIMERS) { + /* Received response to START-INPUT-TIMERS request. */ + if (message->start_line.request_state == MRCP_REQUEST_STATE_COMPLETE) { + if (message->start_line.status_code >= 200 && message->start_line.status_code <= 299) { + ast_log(LOG_DEBUG, "(%s) Timers started\n", schannel->name); + recog_channel_set_timers_started(schannel); + } else + ast_log(LOG_DEBUG, "(%s) Timers failed to start, status code = %d\n", schannel->name, message->start_line.status_code); + } + } else if (message->start_line.method_id == RECOGNIZER_DEFINE_GRAMMAR) { + /* Received response to DEFINE-GRAMMAR request. */ + if (message->start_line.request_state == MRCP_REQUEST_STATE_COMPLETE) { + if (message->start_line.status_code >= 200 && message->start_line.status_code <= 299) { + ast_log(LOG_DEBUG, "(%s) Grammar loaded\n", schannel->name); + speech_channel_set_state(schannel, SPEECH_CHANNEL_READY); + } else { + if (recog_hdr->completion_cause == RECOGNIZER_COMPLETION_CAUSE_UNKNOWN) + ast_log(LOG_DEBUG, "(%s) Grammar failed to load, status code = %d\n", schannel->name, message->start_line.status_code); + else { + ast_log(LOG_DEBUG, "(%s) Grammar failed to load, status code = %d, completion-cause = %03d\n", schannel->name, message->start_line.status_code, recog_hdr->completion_cause); + recog_channel_set_results(schannel, recog_hdr->completion_cause, NULL, NULL); + } + speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); + } + } + } else { + /* Received unexpected response. */ + ast_log(LOG_DEBUG, "(%s) Unexpected response, method_id = %d\n", schannel->name, (int)message->start_line.method_id); + speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); + } + } else if (message->start_line.message_type == MRCP_MESSAGE_TYPE_EVENT) { + /* Received MRCP event. */ + if (message->start_line.method_id == RECOGNIZER_RECOGNITION_COMPLETE) { + ast_log(LOG_NOTICE, "(%s) RECOGNITION COMPLETE, Completion-Cause: %03d\n", schannel->name, recog_hdr->completion_cause); + recog_channel_set_results(schannel, recog_hdr->completion_cause, &message->body, &recog_hdr->waveform_uri); + speech_channel_set_state(schannel, SPEECH_CHANNEL_READY); + } else if (message->start_line.method_id == RECOGNIZER_START_OF_INPUT) { + ast_log(LOG_DEBUG, "(%s) START OF INPUT\n", schannel->name); + recog_channel_set_start_of_input(schannel); + } else { + ast_log(LOG_DEBUG, "(%s) Unexpected event, method_id = %d\n", schannel->name, (int)message->start_line.method_id); + speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); + } + } else { + ast_log(LOG_DEBUG, "(%s) Unexpected message type, message_type = %d\n", schannel->name, message->start_line.message_type); + speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); + } + + return TRUE; +} + +/* Handle the MRCP responses/events. */ +static apt_bool_t verif_on_message_receive(mrcp_application_t *application, mrcp_session_t *session, mrcp_channel_t *channel, mrcp_message_t *message) +{ + speech_channel_t *schannel = get_speech_channel(session); + if (!schannel || !message) { + ast_log(LOG_ERROR, "recog_on_message_receive: unknown channel error!\n"); + return FALSE; + } + + mrcp_verifier_header_t *recog_hdr = (mrcp_verifier_header_t *)mrcp_resource_header_get(message); + if (message->start_line.message_type == MRCP_MESSAGE_TYPE_RESPONSE) { + ast_log(LOG_NOTICE, "(%s) MESSAGE RESPONSE %d\n", schannel->name, message->start_line.method_id); + /* Received MRCP response. */ + if (message->start_line.method_id == VERIFIER_VERIFY || message->start_line.method_id == VERIFIER_VERIFY_FROM_BUFFER) { + /* Received the response to RECOGNIZE request. */ + ast_log(LOG_NOTICE, "(%s) VERIFY RESPONSE\n", schannel->name); + if (message->start_line.request_state == MRCP_REQUEST_STATE_INPROGRESS) { + /* RECOGNIZE in progress. */ + ast_log(LOG_DEBUG, "(%s) VERIFY IN PROGRESS\n", schannel->name); + speech_channel_set_state(schannel, SPEECH_CHANNEL_PROCESSING); + } else if (message->start_line.request_state == MRCP_REQUEST_STATE_COMPLETE) { + /* RECOGNIZE failed to start. */ + if (recog_hdr->completion_cause == RECOGNIZER_COMPLETION_CAUSE_UNKNOWN) + ast_log(LOG_DEBUG, "(%s) RECOGNIZE failed: status = %d\n", schannel->name, message->start_line.status_code); + else { + ast_log(LOG_DEBUG, "(%s) RECOGNIZE failed: status = %d, completion-cause = %03d\n", schannel->name, message->start_line.status_code, recog_hdr->completion_cause); + recog_channel_set_results(schannel, recog_hdr->completion_cause, NULL, NULL); + } + speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); + } else if (message->start_line.request_state == MRCP_REQUEST_STATE_PENDING) + /* RECOGNIZE is queued. */ + ast_log(LOG_DEBUG, "(%s) VERIFY PENDING\n", schannel->name); + else { + /* Received unexpected request_state. */ + ast_log(LOG_DEBUG, "(%s) Unexpected RECOGNIZE request state: %d\n", schannel->name, message->start_line.request_state); + speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); + } + } else if (message->start_line.method_id == VERIFIER_START_SESSION) { + /* Received response to the STOP request. */ + if (message->start_line.request_state == MRCP_REQUEST_STATE_COMPLETE) { + /* Got COMPLETE. */ + ast_log(LOG_DEBUG, "(%s) VERIFIER STARTED\n", schannel->name); + speech_channel_set_state(schannel, SPEECH_CHANNEL_READY); + } else { + /* Received unexpected request state. */ + ast_log(LOG_DEBUG, "(%s) Unexpected VERIFIER START request state: %d\n", schannel->name, message->start_line.request_state); + speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); + } + } else if (message->start_line.method_id == RECOGNIZER_START_INPUT_TIMERS) { + /* Received response to START-INPUT-TIMERS request. */ + if (message->start_line.request_state == MRCP_REQUEST_STATE_COMPLETE) { + if (message->start_line.status_code >= 200 && message->start_line.status_code <= 299) { + ast_log(LOG_DEBUG, "(%s) Timers started\n", schannel->name); + recog_channel_set_timers_started(schannel); + } else + ast_log(LOG_DEBUG, "(%s) Timers failed to start, status code = %d\n", schannel->name, message->start_line.status_code); + } + } else { + /* Received unexpected response. */ + ast_log(LOG_DEBUG, "(%s) Unexpected response, method_id = %d\n", schannel->name, (int)message->start_line.method_id); + speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); + } + } else if (message->start_line.message_type == MRCP_MESSAGE_TYPE_EVENT) { + /* Received MRCP event. */ + if (message->start_line.method_id == VERIFIER_VERIFICATION_COMPLETE) { + ast_log(LOG_DEBUG, "(%s) RECOGNITION COMPLETE, Completion-Cause: %03d\n", schannel->name, recog_hdr->completion_cause); + recog_channel_set_results(schannel, recog_hdr->completion_cause, &message->body, &recog_hdr->waveform_uri); + speech_channel_set_state(schannel, SPEECH_CHANNEL_READY); + } else if (message->start_line.method_id == VERIFIER_START_OF_INPUT) { + ast_log(LOG_DEBUG, "(%s) START OF INPUT\n", schannel->name); + recog_channel_set_start_of_input(schannel); + } else { + ast_log(LOG_DEBUG, "(%s) Unexpected event, method_id = %d\n", schannel->name, (int)message->start_line.method_id); + speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); + } + } else { + ast_log(LOG_DEBUG, "(%s) Unexpected message type, message_type = %d\n", schannel->name, message->start_line.message_type); + speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); + } + + return TRUE; +} + +/* Handle the MRCP responses/events from UniMRCP. */ +static apt_bool_t speech_on_message_receive(mrcp_application_t *application, mrcp_session_t *session, mrcp_channel_t *channel, mrcp_message_t *message) +{ + speech_channel_t *schannel = get_speech_channel(session); + if (!schannel || !message) { + ast_log(LOG_ERROR, "speech_on_message_receive: unknown channel error!\n"); + return FALSE; + } + + ast_log(LOG_NOTICE, "speech_on_message_receive channel: %d\n", schannel->type ); + if(schannel->type == SPEECH_CHANNEL_VERIFIER) + return verif_on_message_receive(application, session, channel, message); + else if(schannel->type == SPEECH_CHANNEL_RECOGNIZER) + return recog_on_message_receive(application, session, channel, message); + + return TRUE; +} + +/* UniMRCP callback requesting stream to be opened. */ +static apt_bool_t recog_stream_open(mpf_audio_stream_t* stream, mpf_codec_t *codec) +{ + speech_channel_t* schannel; + + if (stream) + schannel = (speech_channel_t*)stream->obj; + else + schannel = NULL; + + if (!schannel) { + ast_log(LOG_ERROR, "recog_stream_open: unknown channel error!\n"); + return FALSE; + } + + schannel->stream = stream; + return TRUE; +} + +/* UniMRCP callback requesting next frame for speech recognition. */ +static apt_bool_t recog_stream_read(mpf_audio_stream_t *stream, mpf_frame_t *frame) +{ + speech_channel_t *schannel; + + if (stream != NULL) + schannel = (speech_channel_t *)stream->obj; + else + schannel = NULL; + + if (!schannel || !frame) { + ast_log(LOG_ERROR, "recog_stream_read: unknown channel error!\n"); + return FALSE; + } + + if (schannel->dtmf_generator != NULL) { + if (mpf_dtmf_generator_sending(schannel->dtmf_generator)) { + ast_log(LOG_DEBUG, "(%s) DTMF frame written\n", schannel->name); + mpf_dtmf_generator_put_frame(schannel->dtmf_generator, frame); + return TRUE; + } + } + + apr_size_t to_read = frame->codec_frame.size; + + /* Grab the data. Pad it if there isn't enough. */ + if (speech_channel_read(schannel, frame->codec_frame.buffer, &to_read, 0) == 0) { + if (to_read < frame->codec_frame.size) + memset((apr_byte_t *)frame->codec_frame.buffer + to_read, schannel->silence, frame->codec_frame.size - to_read); + + frame->type |= MEDIA_FRAME_TYPE_AUDIO; + } + + return TRUE; +} + +/* Apply application options. */ +static int mrcprecog_option_apply(mrcprecog_options_t *options, const char *key, const char *value) +{ + if (strcasecmp(key, "ct") == 0) { + apr_hash_set(options->recog_hfs, "Confidence-Threshold", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "sva") == 0) { + apr_hash_set(options->recog_hfs, "Speed-vs-Accuracy", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "nb") == 0) { + apr_hash_set(options->recog_hfs, "N-Best-List-Length", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "nit") == 0) { + apr_hash_set(options->recog_hfs, "No-Input-Timeout", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "sct") == 0) { + apr_hash_set(options->recog_hfs, "Speech-Complete-Timeout", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "sint") == 0) { + apr_hash_set(options->recog_hfs, "Speech-Incomplete-Timeout", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "dit") == 0) { + apr_hash_set(options->recog_hfs, "Dtmf-Interdigit-Timeout", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "dtt") == 0) { + apr_hash_set(options->recog_hfs, "Dtmf-Term-Timeout", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "dttc") == 0) { + apr_hash_set(options->recog_hfs, "Dtmf-Term-Char", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "sw") == 0) { + apr_hash_set(options->recog_hfs, "Save-Waveform", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "nac") == 0) { + apr_hash_set(options->recog_hfs, "New-Audio-Channel", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "rm") == 0) { + apr_hash_set(options->recog_hfs, "Recognition-Mode", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "hmaxd") == 0) { + apr_hash_set(options->recog_hfs, "Hotword-Max-Duration", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "hmind") == 0) { + apr_hash_set(options->recog_hfs, "Hotword-Min-Duration", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "cdb") == 0) { + apr_hash_set(options->recog_hfs, "Clear-Dtmf-Buffer", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "enm") == 0) { + apr_hash_set(options->recog_hfs, "Early-No-Match", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "iwu") == 0) { + apr_hash_set(options->recog_hfs, "Input-Waveform-URI", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "sl") == 0) { + apr_hash_set(options->recog_hfs, "Sensitivity-Level", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "spl") == 0) { + apr_hash_set(options->recog_hfs, "Speech-Language", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "mt") == 0) { + apr_hash_set(options->recog_hfs, "Media-Type", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "vsp") == 0) { + apr_hash_set(options->recog_hfs, "Vendor-Specific-Parameters", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "p") == 0) { + options->flags |= MRCPRECOG_PROFILE; + options->params[OPT_ARG_PROFILE] = value; + } else if (strcasecmp(key, "i") == 0) { + options->flags |= MRCPRECOG_INTERRUPT; + options->params[OPT_ARG_INTERRUPT] = value; + } else if (strcasecmp(key, "f") == 0) { + options->flags |= MRCPRECOG_FILENAME; + options->params[OPT_ARG_FILENAME] = value; + } else if (strcasecmp(key, "t") == 0) { + apr_hash_set(options->recog_hfs, "Recognition-Timeout", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "b") == 0) { + options->flags |= MRCPRECOG_BARGEIN; + options->params[OPT_ARG_BARGEIN] = value; + } else if (strcasecmp(key, "gd") == 0) { + options->flags |= MRCPRECOG_GRAMMAR_DELIMITERS; + options->params[OPT_ARG_GRAMMAR_DELIMITERS] = value; + } else if (strcasecmp(key, "epe") == 0) { + options->flags |= MRCPRECOG_EXIT_ON_PLAYERROR; + options->params[OPT_ARG_EXIT_ON_PLAYERROR] = value; + } else if (strcasecmp(key, "uer") == 0) { + options->flags |= MRCPRECOG_URI_ENCODED_RESULTS; + options->params[OPT_ARG_URI_ENCODED_RESULTS] = value; + } else if (strcasecmp(key, "od") == 0) { + options->flags |= MRCPRECOG_OUTPUT_DELIMITERS; + options->params[OPT_ARG_OUTPUT_DELIMITERS] = value; + } else if (strcasecmp(key, "sit") == 0) { + options->flags |= MRCPRECOG_INPUT_TIMERS; + options->params[OPT_ARG_INPUT_TIMERS] = value; + } else if (strcasecmp(key, "plt") == 0) { + options->flags |= MRCPRECOG_PERSISTENT_LIFETIME; + options->params[OPT_ARG_PERSISTENT_LIFETIME] = value; + } else if (strcasecmp(key, "dse") == 0) { + options->flags |= MRCPRECOG_DATASTORE_ENTRY; + options->params[OPT_ARG_DATASTORE_ENTRY] = value; + } else if (strcasecmp(key, "nif") == 0) { + options->flags |= MRCPRECOG_INSTANCE_FORMAT; + options->params[OPT_ARG_INSTANCE_FORMAT] = value; + } else { + ast_log(LOG_WARNING, "Unknown option: %s\n", key); + } + return 0; +} + +/* Parse application options. */ +static int mrcprecog_options_parse(char *str, mrcprecog_options_t *options, apr_pool_t *pool) +{ + char *s; + char *name, *value; + + if (!str) + return 0; + + if ((options->recog_hfs = apr_hash_make(pool)) == NULL) { + return -1; + } + + do { + /* Skip any leading spaces. */ + while (isspace(*str)) + str++; + + if (*str == '<') { + /* Special case -> found an option quoted with < > */ + str++; + s = strsep(&str, ">"); + /* Skip to the next option, if any */ + strsep(&str, "&"); + } + else { + /* Regular processing */ + s = strsep(&str, "&"); + } + + if (s) { + value = s; + if ((name = strsep(&value, "=")) && value) { + ast_log(LOG_DEBUG, "Apply option %s: %s\n", name, value); + mrcprecog_option_apply(options, name, value); + } + } + } + while (str); + + return 0; +} + +/* Return the number of prompts which still need to be played. */ +static APR_INLINE int mrcprecog_prompts_available(app_session_t *app_session) +{ + if(app_session->cur_prompt >= app_session->prompts->nelts) + return 0; + return app_session->prompts->nelts - app_session->cur_prompt; +} + +/* Advance the current prompt index and return the number of prompts remaining. */ +static APR_INLINE int mrcprecog_prompts_advance(app_session_t *app_session) +{ + if (app_session->cur_prompt >= app_session->prompts->nelts) + return -1; + app_session->cur_prompt++; + return app_session->prompts->nelts - app_session->cur_prompt; +} + +/* Start playing the current prompt. */ +static struct ast_filestream* mrcprecog_prompt_play(app_session_t *app_session, mrcprecog_options_t *mrcprecog_options, off_t *max_filelength) +{ + if (app_session->cur_prompt >= app_session->prompts->nelts) { + ast_log(LOG_ERROR, "(%s) Out of bounds prompt index\n", app_session->recog_channel->name); + return NULL; + } + + char *filename = APR_ARRAY_IDX(app_session->prompts, app_session->cur_prompt, char*); + if (!filename) { + ast_log(LOG_ERROR, "(%s) Invalid file name\n", app_session->recog_channel->name); + return NULL; + } + return astchan_stream_file(app_session->recog_channel->chan, filename, max_filelength); +} + +/* Exit the application. */ +static int mrcprecog_exit(struct ast_channel *chan, app_session_t *app_session, speech_channel_status_t status) +{ + ast_log(LOG_NOTICE, "%s() Will exiting on %s\n", app_recog, ast_channel_name(chan)); + if (app_session) { + if (app_session->readformat && app_session->rawreadformat) + ast_set_read_format_path(chan, app_session->rawreadformat, app_session->readformat); + + if (app_session->recog_channel) { + if (app_session->recog_channel->session_id) + pbx_builtin_setvar_helper(chan, "RECOG_SID", app_session->recog_channel->session_id); + + if (app_session->lifetime == APP_SESSION_LIFETIME_DYNAMIC) { + ast_log(LOG_NOTICE, "%s() Will stop recog on %s\n", app_recog, ast_channel_name(chan)); + //speech_channel_destroy(app_session->recog_channel); + //app_session->recog_channel = NULL; + } + } + if (app_session->verif_channel) { + if (app_session->verif_channel->session_id) + pbx_builtin_setvar_helper(chan, "RECOG_SID", app_session->verif_channel->session_id); + + if (app_session->lifetime == APP_SESSION_LIFETIME_DYNAMIC) { + ast_log(LOG_NOTICE, "%s() Will stop verif on %s\n", app_recog, ast_channel_name(chan)); + speech_channel_destroy(app_session->verif_channel); + app_session->verif_channel = NULL; + } + } + } + + const char *status_str = speech_channel_status_to_string(status); + pbx_builtin_setvar_helper(chan, "RECOGSTATUS", status_str); + ast_log(LOG_NOTICE, "%s() exiting status: %s on %s\n", app_recog, status_str, ast_channel_name(chan)); + return 0; +} + +/* The entry point of the application. */ +static int app_recog_verif_exec(struct ast_channel *chan, ast_app_data data) +{ + int dtmf_enable; + struct ast_frame *f = NULL; + apr_uint32_t speech_channel_number = get_next_speech_channel_number(); + const char *name; + speech_channel_status_t status = SPEECH_CHANNEL_STATUS_OK; + char *parse; + int i; + mrcprecog_options_t mrcprecog_options; + const char *profile_name = NULL; + ast_mrcp_profile_t *profile; + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(grammar); + AST_APP_ARG(options); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "%s() requires an argument (grammar[,options])\n", app_recog); + return mrcprecog_exit(chan, NULL, SPEECH_CHANNEL_STATUS_ERROR); + } + + /* We need to make a copy of the input string if we are going to modify it! */ + parse = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, parse); + + if (ast_strlen_zero(args.grammar)) { + ast_log(LOG_WARNING, "%s() requires a grammar argument (grammar[,options])\n", app_recog); + return mrcprecog_exit(chan, NULL, SPEECH_CHANNEL_STATUS_ERROR); + } + + args.grammar = normalize_input_string(args.grammar); + ast_log(LOG_NOTICE, "%s() grammar: %s\n", app_recog, args.grammar); + + app_datastore_t* datastore = app_datastore_get(chan); + if (!datastore) { + ast_log(LOG_ERROR, "Unable to retrieve data from app datastore on %s\n", ast_channel_name(chan)); + return mrcprecog_exit(chan, NULL, SPEECH_CHANNEL_STATUS_ERROR); + } + + mrcprecog_options.recog_hfs = NULL; + mrcprecog_options.flags = 0; + for (i=0; ipool, args.options); + mrcprecog_options_parse(options_buf, &mrcprecog_options, datastore->pool); + } + + /* Answer if it's not already going. */ + if (ast_channel_state(chan) != AST_STATE_UP) + ast_answer(chan); + + /* Ensure no streams are currently playing. */ + ast_stopstream(chan); + + /* Set default lifetime to dynamic. */ + int lifetime = APP_SESSION_LIFETIME_DYNAMIC; + + /* Get datastore entry. */ + const char *entry = DEFAULT_DATASTORE_ENTRY; + if ((mrcprecog_options.flags & MRCPRECOG_DATASTORE_ENTRY) == MRCPRECOG_DATASTORE_ENTRY) { + if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_DATASTORE_ENTRY])) { + entry = mrcprecog_options.params[OPT_ARG_DATASTORE_ENTRY]; + lifetime = APP_SESSION_LIFETIME_PERSISTENT; + } + } + + /* Check session lifetime. */ + if ((mrcprecog_options.flags & MRCPRECOG_PERSISTENT_LIFETIME) == MRCPRECOG_PERSISTENT_LIFETIME) { + if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_PERSISTENT_LIFETIME])) { + lifetime = (atoi(mrcprecog_options.params[OPT_ARG_PERSISTENT_LIFETIME]) == 0) ? + APP_SESSION_LIFETIME_DYNAMIC : APP_SESSION_LIFETIME_PERSISTENT; + } + } + + /* Get application datastore. */ + app_session_t *app_session = app_datastore_session_add(datastore, entry); + if (!app_session) { + return mrcprecog_exit(chan, NULL, SPEECH_CHANNEL_STATUS_ERROR); + } + + datastore->last_recog_entry = entry; + app_session->nlsml_result = NULL; + + app_session->prompts = apr_array_make(app_session->pool, 1, sizeof(char*)); + app_session->cur_prompt = 0; + app_session->it_policy = IT_POLICY_AUTO; + app_session->lifetime = lifetime; + + if(!app_session->recog_channel) { + /* Get new read format. */ + app_session->nreadformat = ast_channel_get_speechreadformat(chan, app_session->pool); + + name = apr_psprintf(app_session->pool, "ASR-%lu", (unsigned long int)speech_channel_number); + + /* Create speech channel for recognition. */ + app_session->recog_channel = speech_channel_create( + app_session->pool, + name, + SPEECH_CHANNEL_RECOGNIZER, + mrcprecog, + app_session->nreadformat, + NULL, + chan); + if (!app_session->recog_channel) { + return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + } + + if ((mrcprecog_options.flags & MRCPRECOG_PROFILE) == MRCPRECOG_PROFILE) { + if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_PROFILE])) { + profile_name = mrcprecog_options.params[OPT_ARG_PROFILE]; + } + } + + /* Get recognition profile. */ + profile = get_recog_profile(profile_name); + if (!profile) { + ast_log(LOG_ERROR, "(%s) Can't find profile, %s\n", name, profile_name); + return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + } + + /* Open recognition channel. */ + if (speech_channel_open(app_session->recog_channel, profile) != 0) { + return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + } + } + else { + name = app_session->recog_channel->name; + } + + /* Get old read format. */ + ast_format_compat *oreadformat = ast_channel_get_readformat(chan, app_session->pool); + ast_format_compat *orawreadformat = ast_channel_get_rawreadformat(chan, app_session->pool); + + /* Set read format. */ + ast_set_read_format_path(chan, orawreadformat, app_session->nreadformat); + + /* Store old read format. */ + app_session->readformat = oreadformat; + app_session->rawreadformat = orawreadformat; + + /* Check if barge-in is allowed. */ + int bargein = 1; + if ((mrcprecog_options.flags & MRCPRECOG_BARGEIN) == MRCPRECOG_BARGEIN) { + if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_BARGEIN])) { + bargein = (atoi(mrcprecog_options.params[OPT_ARG_BARGEIN]) == 0) ? 0 : 1; + } + } + + dtmf_enable = 2; + if ((mrcprecog_options.flags & MRCPRECOG_INTERRUPT) == MRCPRECOG_INTERRUPT) { + if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_INTERRUPT])) { + dtmf_enable = 1; + if (strcasecmp(mrcprecog_options.params[OPT_ARG_INTERRUPT], "any") == 0) + mrcprecog_options.params[OPT_ARG_INTERRUPT] = AST_DIGIT_ANY; + else if (strcasecmp(mrcprecog_options.params[OPT_ARG_INTERRUPT], "none") == 0) + dtmf_enable = 2; + else if (strcasecmp(mrcprecog_options.params[OPT_ARG_INTERRUPT], "disable") == 0) + dtmf_enable = 0; + } + } + + /* Get NLSML instance format, if specified */ + if ((mrcprecog_options.flags & MRCPRECOG_INSTANCE_FORMAT) == MRCPRECOG_INSTANCE_FORMAT) { + if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_INSTANCE_FORMAT])) { + const char *format = mrcprecog_options.params[OPT_ARG_INSTANCE_FORMAT]; + if (strcasecmp(format, "xml") == 0) + app_session->instance_format = NLSML_INSTANCE_FORMAT_XML; + else if (strcasecmp(format, "json") == 0) + app_session->instance_format = NLSML_INSTANCE_FORMAT_JSON; + } + } + + const char *grammar_delimiters = ","; + /* Get grammar delimiters. */ + if ((mrcprecog_options.flags & MRCPRECOG_GRAMMAR_DELIMITERS) == MRCPRECOG_GRAMMAR_DELIMITERS) { + if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_GRAMMAR_DELIMITERS])) { + grammar_delimiters = mrcprecog_options.params[OPT_ARG_GRAMMAR_DELIMITERS]; + ast_log(LOG_DEBUG, "(%s) Grammar delimiters: %s\n", name, grammar_delimiters); + } + } + /* Parse the grammar argument into a sequence of grammars. */ + char *grammar_arg = apr_pstrdup(app_session->pool, args.grammar); + char *last; + char *grammar_str; + char grammar_name[32]; + int grammar_id = 0; + grammar_str = apr_strtok(grammar_arg, grammar_delimiters, &last); + while (grammar_str) { + const char *grammar_content = NULL; + grammar_type_t grammar_type = GRAMMAR_TYPE_UNKNOWN; + ast_log(LOG_DEBUG, "(%s) Determine grammar type: %s\n", name, grammar_str); + if (determine_grammar_type(app_session->recog_channel, grammar_str, &grammar_content, &grammar_type) != 0) { + ast_log(LOG_WARNING, "(%s) Unable to determine grammar type: %s\n", name, grammar_str); + return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + } + + apr_snprintf(grammar_name, sizeof(grammar_name) - 1, "grammar-%d", grammar_id++); + grammar_name[sizeof(grammar_name) - 1] = '\0'; + /* Load grammar. */ + if (recog_channel_load_grammar(app_session->recog_channel, grammar_name, grammar_type, grammar_content) != 0) { + ast_log(LOG_ERROR, "(%s) Unable to load grammar\n", name); + + const char *completion_cause = NULL; + recog_channel_get_results(app_session->recog_channel, &completion_cause, NULL, NULL); + if (completion_cause) + pbx_builtin_setvar_helper(chan, "RECOG_COMPLETION_CAUSE", completion_cause); + + return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + } + + grammar_str = apr_strtok(NULL, grammar_delimiters, &last); + } + + const char *filenames = NULL; + if ((mrcprecog_options.flags & MRCPRECOG_FILENAME) == MRCPRECOG_FILENAME) { + if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_FILENAME])) { + filenames = mrcprecog_options.params[OPT_ARG_FILENAME]; + } + } + + if (filenames) { + /* Get output delimiters. */ + const char *output_delimiters = "^"; + if ((mrcprecog_options.flags & MRCPRECOG_OUTPUT_DELIMITERS) == MRCPRECOG_OUTPUT_DELIMITERS) { + if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_OUTPUT_DELIMITERS])) { + output_delimiters = mrcprecog_options.params[OPT_ARG_OUTPUT_DELIMITERS]; + ast_log(LOG_DEBUG, "(%s) Output delimiters: %s\n", output_delimiters, name); + } + } + + /* Parse the file names into a list of files. */ + char *last; + char *filenames_arg = apr_pstrdup(app_session->pool, filenames); + char *filename = apr_strtok(filenames_arg, output_delimiters, &last); + while (filename) { + filename = normalize_input_string(filename); + ast_log(LOG_DEBUG, "(%s) Add prompt: %s\n", name, filename); + APR_ARRAY_PUSH(app_session->prompts, char*) = filename; + + filename = apr_strtok(NULL, output_delimiters, &last); + } + } + + int exit_on_playerror = 0; + if ((mrcprecog_options.flags & MRCPRECOG_EXIT_ON_PLAYERROR) == MRCPRECOG_EXIT_ON_PLAYERROR) { + if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_EXIT_ON_PLAYERROR])) { + exit_on_playerror = atoi(mrcprecog_options.params[OPT_ARG_EXIT_ON_PLAYERROR]); + if ((exit_on_playerror < 0) || (exit_on_playerror > 2)) + exit_on_playerror = 1; + } + } + + int prompt_processing = (mrcprecog_prompts_available(app_session)) ? 1 : 0; + struct ast_filestream *filestream = NULL; + off_t max_filelength; + + /* If bargein is not allowed, play all the prompts and wait for for them to complete. */ + if (!bargein && prompt_processing) { + /* Start playing first prompt. */ + filestream = mrcprecog_prompt_play(app_session, &mrcprecog_options, &max_filelength); + if (!filestream && exit_on_playerror) { + return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + } + + do { + if (filestream) { + if (ast_waitstream(chan, "") != 0) { + f = ast_read(chan); + if (!f) { + ast_log(LOG_DEBUG, "(%s) ast_waitstream failed on %s, channel read is a null frame. Hangup detected\n", name, ast_channel_name(chan)); + return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_INTERRUPTED); + } + ast_frfree(f); + + ast_log(LOG_WARNING, "(%s) ast_waitstream failed on %s\n", name, ast_channel_name(chan)); + return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + } + filestream = NULL; + } + + /* End of current prompt -> advance to the next one. */ + if (mrcprecog_prompts_advance(app_session) > 0) { + /* Start playing current prompt. */ + filestream = mrcprecog_prompt_play(app_session, &mrcprecog_options, &max_filelength); + if (!filestream && exit_on_playerror) { + return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + } + } + else { + /* End of prompts. */ + break; + } + } + while (mrcprecog_prompts_available(app_session)); + + prompt_processing = 0; + } + + /* Check the policy for input timers. */ + if ((mrcprecog_options.flags & MRCPRECOG_INPUT_TIMERS) == MRCPRECOG_INPUT_TIMERS) { + if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_INPUT_TIMERS])) { + switch(atoi(mrcprecog_options.params[OPT_ARG_INPUT_TIMERS])) { + case 0: app_session->it_policy = IT_POLICY_OFF; break; + case 1: app_session->it_policy = IT_POLICY_ON; break; + default: app_session->it_policy = IT_POLICY_AUTO; + } + } + } + + int start_input_timers = !prompt_processing; + if (app_session->it_policy != IT_POLICY_AUTO) + start_input_timers = app_session->it_policy; + recognizer_data_t *r = app_session->recog_channel->data; + + ast_log(LOG_NOTICE, "(%s) Recognizing, enable DTMFs: %d, start input timers: %d\n", name, dtmf_enable, start_input_timers); + + /* Start recognition. */ + if (recog_channel_start(app_session->recog_channel, name, start_input_timers, mrcprecog_options.recog_hfs) != 0) { + ast_log(LOG_ERROR, "(%s) Unable to start recognition\n", name); + + const char *completion_cause = NULL; + recog_channel_get_results(app_session->recog_channel, &completion_cause, NULL, NULL); + if (completion_cause) + pbx_builtin_setvar_helper(chan, "RECOG_COMPLETION_CAUSE", completion_cause); + + return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + } + + if (prompt_processing) { + /* Start playing first prompt. */ + filestream = mrcprecog_prompt_play(app_session, &mrcprecog_options, &max_filelength); + if (!filestream && exit_on_playerror) { + ast_log(LOG_ERROR, " Error on prompt processing\n"); + return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + } + } + +#if !AST_VERSION_AT_LEAST(11,0,0) + off_t read_filestep = 0; + off_t read_filelength; +#endif + int waitres; + int recog_processing; + /* Continue with recognition. */ + while ((waitres = ast_waitfor(chan, 100)) >= 0) { + recog_processing = 1; + + if (app_session->recog_channel && app_session->recog_channel->mutex) { + apr_thread_mutex_lock(app_session->recog_channel->mutex); + + if (app_session->recog_channel->state != SPEECH_CHANNEL_PROCESSING) { + recog_processing = 0; + } + + apr_thread_mutex_unlock(app_session->recog_channel->mutex); + } + + if (recog_processing == 0) + break; + + if (prompt_processing) { + if (filestream) { +#if AST_VERSION_AT_LEAST(11,0,0) + if (ast_channel_streamid(chan) == -1 && ast_channel_timingfunc(chan) == NULL) { + ast_stopstream(chan); + filestream = NULL; + } +#else + read_filelength = ast_tellstream(filestream); + if(!read_filestep) + read_filestep = read_filelength; + if (read_filelength + read_filestep > max_filelength) { + ast_log(LOG_DEBUG, "(%s) File is over, read length:%"APR_OFF_T_FMT"\n", name, read_filelength); + filestream = NULL; + read_filestep = 0; + } +#endif + } + + if (!filestream) { + /* End of current prompt -> advance to the next one. */ + if (mrcprecog_prompts_advance(app_session) > 0) { + /* Start playing current prompt. */ + filestream = mrcprecog_prompt_play(app_session, &mrcprecog_options, &max_filelength); + if (!filestream && exit_on_playerror) { + ast_log(LOG_ERROR, " Error on filestream processing\n"); + return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + } + } + else { + /* End of prompts -> start input timers. */ + if (app_session->it_policy == IT_POLICY_AUTO) { + ast_log(LOG_DEBUG, "(%s) Start input timers\n", name); + recog_channel_start_input_timers(app_session->recog_channel); + } + prompt_processing = 0; + } + } + + if (prompt_processing && r && r->start_of_input) { + ast_log(LOG_DEBUG, "(%s) Bargein occurred\n", name); + ast_stopstream(chan); + filestream = NULL; + prompt_processing = 0; + } + } + + if (waitres == 0) + continue; + + f = ast_read(chan); + if (!f) { + ast_log(LOG_DEBUG, "(%s) Null frame. Hangup detected\n", name); + status = SPEECH_CHANNEL_STATUS_INTERRUPTED; + break; + } + + if (f->frametype == AST_FRAME_VOICE && f->datalen) { + apr_size_t len = f->datalen; + if (speech_channel_write(app_session->recog_channel, ast_frame_get_data(f), &len) != 0) { + ast_frfree(f); + break; + } + } else if (f->frametype == AST_FRAME_VIDEO) { + /* Ignore. */ + } else if ((dtmf_enable != 0) && (f->frametype == AST_FRAME_DTMF)) { + int dtmfkey = ast_frame_get_dtmfkey(f); + ast_log(LOG_DEBUG, "(%s) User pressed DTMF key (%d)\n", name, dtmfkey); + if (dtmf_enable == 2) { + /* Send DTMF frame to ASR engine. */ + if (app_session->recog_channel->dtmf_generator != NULL) { + char digits[2]; + digits[0] = (char)dtmfkey; + digits[1] = '\0'; + + ast_log(LOG_NOTICE, "(%s) DTMF digit queued (%s)\n", app_session->recog_channel->name, digits); + mpf_dtmf_generator_enqueue(app_session->recog_channel->dtmf_generator, digits); + } + } else if (dtmf_enable == 1) { + /* Stop streaming if within i chars. */ + if (strchr(mrcprecog_options.params[OPT_ARG_INTERRUPT], dtmfkey) || (strcmp(mrcprecog_options.params[OPT_ARG_INTERRUPT],"any"))) { + ast_frfree(f); + ast_log(LOG_ERROR, " Error on dtmf processing\n"); + mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_OK); + return dtmfkey; + } + + /* Continue if not an i-key. */ + } + } + + ast_frfree(f); + } + + if (prompt_processing) { + ast_log(LOG_DEBUG, "(%s) Stop prompt\n", name); + ast_stopstream(chan); + filestream = NULL; + prompt_processing = 0; + } + + const char *completion_cause = NULL; + const char *result = NULL; + const char *waveform_uri = NULL; + + if (status == SPEECH_CHANNEL_STATUS_OK) { + int uri_encoded_results = 0; + /* Check if the results should be URI-encoded. */ + if ((mrcprecog_options.flags & MRCPRECOG_URI_ENCODED_RESULTS) == MRCPRECOG_URI_ENCODED_RESULTS) { + if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_URI_ENCODED_RESULTS])) { + uri_encoded_results = (atoi(mrcprecog_options.params[OPT_ARG_URI_ENCODED_RESULTS]) == 0) ? 0 : 1; + } + } + + /* Get recognition result. */ + if (recog_channel_get_results(app_session->recog_channel, &completion_cause, &result, &waveform_uri) != 0) { + ast_log(LOG_WARNING, "(%s) Unable to retrieve result\n", name); + return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + } + + if (result) { + /* Store the results for further reference from the dialplan. */ + apr_size_t result_len = strlen(result); + app_session->nlsml_result = nlsml_result_parse(result, result_len, datastore->pool); + + if (uri_encoded_results != 0) { + apr_size_t len = result_len * 2; + char *buf = apr_palloc(app_session->pool, len); + result = ast_uri_encode_http(result, buf, len); + } + } + } + + /* Completion cause should always be available at this stage. */ + if (completion_cause) + pbx_builtin_setvar_helper(chan, "RECOG_COMPLETION_CAUSE", completion_cause); + + /* Result may not be available if recognition completed with nomatch, noinput, or other error cause. */ + pbx_builtin_setvar_helper(chan, "RECOG_RESULT", result ? result : ""); + + /* If Waveform URI is available, pass it further to dialplan. */ + if (waveform_uri) + pbx_builtin_setvar_helper(chan, "RECOG_WAVEFORM_URI", waveform_uri); + + ast_log(LOG_NOTICE, " Will start Verification processing\n"); + /* Create speech channel for Verification. */ + app_session->verif_channel = speech_channel_create( + app_session->pool, + name, + SPEECH_CHANNEL_VERIFIER, + mrcprecog, + app_session->nreadformat, + NULL, + chan); + if (!app_session->verif_channel) { + return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + } + app_session->verif_channel->unimrcp_session = app_session->recog_channel->unimrcp_session; + if (speech_channel_open(app_session->verif_channel, profile) != 0) { + ast_log(LOG_ERROR, " Error on Verification processing\n"); + return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + } + /* Start Verification. */ + if (verif_channel_start(app_session->verif_channel, name, start_input_timers, mrcprecog_options.recog_hfs) != 0) { + ast_log(LOG_ERROR, "(%s) Unable to start verification\n", name); + + const char *completion_cause = NULL; + recog_channel_get_results(app_session->recog_channel, &completion_cause, NULL, NULL); + if (completion_cause) + pbx_builtin_setvar_helper(chan, "RECOG_COMPLETION_CAUSE", completion_cause); + + return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + } + /* Continue with verification. */ + while ((waitres = ast_waitfor(chan, 100)) >= 0) { + recog_processing = 1; + + if (app_session->verif_channel && app_session->verif_channel->mutex) { + apr_thread_mutex_lock(app_session->verif_channel->mutex); + //ast_log(LOG_NOTICE, "(%s) Wait for end of verification: %d\n", name, app_session->verif_channel->state); + if (app_session->verif_channel->state != SPEECH_CHANNEL_PROCESSING) { + recog_processing = 0; + } + + apr_thread_mutex_unlock(app_session->verif_channel->mutex); + } + + if (recog_processing == 0) + break; + } + + speech_channel_set_state(app_session->recog_channel, SPEECH_CHANNEL_CLOSED); + speech_channel_set_state(app_session->verif_channel, SPEECH_CHANNEL_CLOSED); + ast_log(LOG_NOTICE, "It will get the RESULT\n", name); + + /* Get Verification result. */ + if (recog_channel_get_results(app_session->verif_channel, &completion_cause, &result, &waveform_uri) != 0) { + ast_log(LOG_WARNING, "(%s) Unable to retrieve result\n", name); + return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + } + + if (result) { + int uri_encoded_results = 0; + /* Store the results for further reference from the dialplan. */ + apr_size_t result_len = strlen(result); + app_session->nlsml_result = nlsml_result_parse(result, result_len, datastore->pool); + + if (uri_encoded_results != 0) { + apr_size_t len = result_len * 2; + char *buf = apr_palloc(app_session->pool, len); + result = ast_uri_encode_http(result, buf, len); + } + } + + + /* Completion cause should always be available at this stage. */ + if (completion_cause) + pbx_builtin_setvar_helper(chan, "VERIF_COMPLETION_CAUSE", completion_cause); + + /* Result may not be available if recognition completed with nomatch, noinput, or other error cause. */ + pbx_builtin_setvar_helper(chan, "VERIF_RESULT", result ? result : ""); + + return mrcprecog_exit(chan, app_session, status); +} + +/* Load MRCPRecogVerif application. */ +int load_mrcprecogverif_app() +{ + apr_pool_t *pool = globals.pool; + + if (pool == NULL) { + ast_log(LOG_ERROR, "Memory pool is NULL\n"); + return -1; + } + + if(mrcprecog) { + ast_log(LOG_ERROR, "Application %s is already loaded\n", app_recog); + return -1; + } + + mrcprecog = (ast_mrcp_application_t*) apr_palloc(pool, sizeof(ast_mrcp_application_t)); + mrcprecog->name = app_recog; + mrcprecog->exec = app_recog_verif_exec; +#if !AST_VERSION_AT_LEAST(1,6,2) + mrcprecog->synopsis = NULL; + mrcprecog->description = NULL; +#endif + + /* Create the recognizer application and link its callbacks */ + if ((mrcprecog->app = mrcp_application_create(recog_message_handler, (void *)0, pool)) == NULL) { + ast_log(LOG_ERROR, "Unable to create recognizer MRCP application %s\n", app_recog); + mrcprecog = NULL; + return -1; + } + + mrcprecog->dispatcher.on_session_update = NULL; + mrcprecog->dispatcher.on_session_terminate = speech_on_session_terminate; + mrcprecog->dispatcher.on_channel_add = speech_on_channel_add; + mrcprecog->dispatcher.on_channel_remove = NULL; + mrcprecog->dispatcher.on_message_receive = speech_on_message_receive; + mrcprecog->dispatcher.on_terminate_event = NULL; + mrcprecog->dispatcher.on_resource_discover = NULL; + mrcprecog->audio_stream_vtable.destroy = NULL; + mrcprecog->audio_stream_vtable.open_rx = recog_stream_open; + mrcprecog->audio_stream_vtable.close_rx = NULL; + mrcprecog->audio_stream_vtable.read_frame = recog_stream_read; + mrcprecog->audio_stream_vtable.open_tx = NULL; + mrcprecog->audio_stream_vtable.close_tx = NULL; + mrcprecog->audio_stream_vtable.write_frame = NULL; + mrcprecog->audio_stream_vtable.trace = NULL; + + if (!mrcp_client_application_register(globals.mrcp_client, mrcprecog->app, app_recog)) { + ast_log(LOG_ERROR, "Unable to register recognizer MRCP application %s\n", app_recog); + if (!mrcp_application_destroy(mrcprecog->app)) + ast_log(LOG_WARNING, "Unable to destroy recognizer MRCP application %s\n", app_recog); + mrcprecog = NULL; + return -1; + } + + apr_hash_set(globals.apps, app_recog, APR_HASH_KEY_STRING, mrcprecog); + + return 0; +} + +/* Unload MRCPRecogVerif application. */ +int unload_mrcprecogverif_app() +{ + if(!mrcprecog) { + ast_log(LOG_ERROR, "Application %s doesn't exist\n", app_recog); + return -1; + } + + apr_hash_set(globals.apps, app_recog, APR_HASH_KEY_STRING, NULL); + mrcprecog = NULL; + + return 0; +} diff --git a/app-unimrcp/app_unimrcp.c b/app-unimrcp/app_unimrcp.c index 73dcac2..92a0876 100644 --- a/app-unimrcp/app_unimrcp.c +++ b/app-unimrcp/app_unimrcp.c @@ -94,6 +94,14 @@ int unload_mrcprecog_app(); int load_synthandrecog_app(); int unload_synthandrecog_app(); +/* MRCPVerif application. */ +int load_mrcpverif_app(); +int unload_mrcpverif_app(); + +/* MRCPRecogVerif application. */ +int load_mrcprecogverif_app(); +int unload_mrcprecogverif_app(); + /* Connects UniMRCP logging to Asterisk. */ static apt_bool_t unimrcp_log(const char *file, int line, const char *id, apt_log_priority_e priority, const char *format, va_list arg_ptr) { @@ -124,7 +132,7 @@ static apt_bool_t unimrcp_log(const char *file, int line, const char *id, apt_lo level = __LOG_NOTICE; break; case APT_PRIO_DEBUG: - level = __LOG_DEBUG; + level = __LOG_NOTICE; break; default: level = __LOG_DEBUG; @@ -191,6 +199,8 @@ AST_COMPAT_STATIC int load_module(void) /* Load the applications. */ load_mrcpsynth_app(); load_mrcprecog_app(); + load_mrcpverif_app(); + load_mrcprecogverif_app(); load_synthandrecog_app(); /* Start the client stack. */ @@ -257,6 +267,8 @@ AST_COMPAT_STATIC int unload_module(void) /* Unload the applications. */ unload_mrcpsynth_app(); unload_mrcprecog_app(); + unload_mrcpverif_app(); + unload_mrcprecogverif_app(); unload_synthandrecog_app(); /* Stop the MRCP client stack. */ diff --git a/app-unimrcp/ast_unimrcp_framework.c b/app-unimrcp/ast_unimrcp_framework.c index 8798da3..03d25e0 100644 --- a/app-unimrcp/ast_unimrcp_framework.c +++ b/app-unimrcp/ast_unimrcp_framework.c @@ -671,10 +671,13 @@ mrcp_client_t *mod_unimrcp_client_create(apr_pool_t *mod_pool) apt_str_t resource_class_synth; apt_str_t resource_class_recog; + apt_str_t resource_class_verif; apt_string_set(&resource_class_synth, "speechsynth"); apt_string_set(&resource_class_recog, "speechrecog"); + apt_string_set(&resource_class_verif, "speakverify"); mrcp_resource_load(resource_loader, &resource_class_synth); mrcp_resource_load(resource_loader, &resource_class_recog); + mrcp_resource_load(resource_loader, &resource_class_verif); resource_factory = mrcp_resource_factory_get(resource_loader); diff --git a/app-unimrcp/ast_unimrcp_framework.h b/app-unimrcp/ast_unimrcp_framework.h index bc54693..372a6cc 100644 --- a/app-unimrcp/ast_unimrcp_framework.h +++ b/app-unimrcp/ast_unimrcp_framework.h @@ -36,6 +36,8 @@ #include "mrcp_synth_resource.h" #include "mrcp_recog_header.h" #include "mrcp_recog_resource.h" +#include "mrcp_verifier_header.h" +#include "mrcp_verifier_resource.h" #include "uni_version.h" #include "mrcp_resource_loader.h" #include "mpf_engine.h" diff --git a/app-unimrcp/speech_channel.c b/app-unimrcp/speech_channel.c index 12e4ba0..b079168 100644 --- a/app-unimrcp/speech_channel.c +++ b/app-unimrcp/speech_channel.c @@ -81,6 +81,7 @@ static const char *speech_channel_type_to_string(speech_channel_type_t type) switch (type) { case SPEECH_CHANNEL_SYNTHESIZER: return "SYNTHESIZER"; case SPEECH_CHANNEL_RECOGNIZER: return "RECOGNIZER"; + case SPEECH_CHANNEL_VERIFIER: return "VERIFIER"; default: return "UNKNOWN"; } } @@ -117,7 +118,7 @@ void speech_channel_set_state_unlocked(speech_channel_t *schannel, speech_channe /* Set the current channel state. */ void speech_channel_set_state(speech_channel_t *schannel, speech_channel_state_t state) { - if (schannel) { + if (schannel && schannel->mutex) { apr_thread_mutex_lock(schannel->mutex); speech_channel_set_state_unlocked(schannel, state); @@ -431,30 +432,38 @@ int speech_channel_open(speech_channel_t *schannel, ast_mrcp_profile_t *profile) mpf_termination_t *termination = NULL; mrcp_resource_type_e resource_type; - if (!schannel || !profile) + if (!schannel || !profile) { + ast_log(LOG_ERROR, "Invalid paramenters for open channel\n"); return -1; + } apr_thread_mutex_lock(schannel->mutex); - /* Make sure we can open channel. */ - if (schannel->state != SPEECH_CHANNEL_CLOSED) { - apr_thread_mutex_unlock(schannel->mutex); - return -1; - } - schannel->profile = profile; /* Create MRCP session. */ - if ((schannel->unimrcp_session = mrcp_application_session_create(schannel->application->app, profile->name, schannel)) == NULL) { - /* Profile doesn't exist? */ - ast_log(LOG_ERROR, "(%s) Unable to create session with %s\n", schannel->name, profile->name); + if (!schannel->unimrcp_session) { + /* Make sure we can open channel. */ + if (schannel->state != SPEECH_CHANNEL_CLOSED) { + ast_log(LOG_ERROR, "Invalid Speech channel state\n"); + apr_thread_mutex_unlock(schannel->mutex); + return -1; + } - apr_thread_mutex_unlock(schannel->mutex); - return 2; + if ((schannel->unimrcp_session = mrcp_application_session_create(schannel->application->app, profile->name, schannel)) == NULL) { + /* Profile doesn't exist? */ + ast_log(LOG_ERROR, "(%s) Unable to create session with %s\n", schannel->name, profile->name); + + apr_thread_mutex_unlock(schannel->mutex); + return 2; + } + + /* Set session name for logging purposes. */ + mrcp_application_session_name_set(schannel->unimrcp_session, schannel->name); + } else { + schannel->state = SPEECH_CHANNEL_CLOSED; + mrcp_application_session_object_set(schannel->unimrcp_session, schannel); } - - /* Set session name for logging purposes. */ - mrcp_application_session_name_set(schannel->unimrcp_session, schannel->name); /* Create audio termination and add to channel. */ if ((termination = speech_channel_create_mpf_termination(schannel)) == NULL) { @@ -466,11 +475,13 @@ int speech_channel_open(speech_channel_t *schannel, ast_mrcp_profile_t *profile) apr_thread_mutex_unlock(schannel->mutex); return -1; } - + ast_log(LOG_NOTICE, "Termination created\n"); if (schannel->type == SPEECH_CHANNEL_SYNTHESIZER) resource_type = MRCP_SYNTHESIZER_RESOURCE; - else + if (schannel->type == SPEECH_CHANNEL_RECOGNIZER) resource_type = MRCP_RECOGNIZER_RESOURCE; + else + resource_type = MRCP_VERIFIER_RESOURCE; if ((schannel->unimrcp_channel = mrcp_application_channel_create(schannel->unimrcp_session, resource_type, termination, NULL, schannel)) == NULL) { ast_log(LOG_ERROR, "(%s) Unable to create channel with %s\n", schannel->name, profile->name); @@ -481,7 +492,7 @@ int speech_channel_open(speech_channel_t *schannel, ast_mrcp_profile_t *profile) apr_thread_mutex_unlock(schannel->mutex); return -1; } - + ast_log(LOG_NOTICE, "Channel created\n"); /* Add channel to session. This establishes the connection to the MRCP server. */ if (mrcp_application_channel_add(schannel->unimrcp_session, schannel->unimrcp_channel) != TRUE) { ast_log(LOG_ERROR, "(%s) Unable to add channel to session with %s\n", schannel->name, profile->name); @@ -492,11 +503,13 @@ int speech_channel_open(speech_channel_t *schannel, ast_mrcp_profile_t *profile) apr_thread_mutex_unlock(schannel->mutex); return -1; } - + ast_log(LOG_NOTICE, "Channel ADDED\n"); /* Wait for channel to be ready. */ - while (schannel->state == SPEECH_CHANNEL_CLOSED) + while (schannel->state == SPEECH_CHANNEL_CLOSED) { apr_thread_cond_timedwait(schannel->cond, schannel->mutex, globals.speech_channel_timeout); - + ast_log(LOG_NOTICE, "Channel waiting...\n"); + } + ast_log(LOG_NOTICE, "Channel ready\n"); if (schannel->state == SPEECH_CHANNEL_READY) { ast_log(LOG_DEBUG, "(%s) channel is ready\n", schannel->name); } else if (schannel->state == SPEECH_CHANNEL_CLOSED) { @@ -520,16 +533,17 @@ int speech_channel_open(speech_channel_t *schannel, ast_mrcp_profile_t *profile) } } - if (schannel->type == SPEECH_CHANNEL_RECOGNIZER) { + if (schannel->type == SPEECH_CHANNEL_RECOGNIZER || schannel->type == SPEECH_CHANNEL_VERIFIER) { recognizer_data_t *r = (recognizer_data_t *)apr_palloc(schannel->pool, sizeof(recognizer_data_t)); if (r != NULL) { schannel->data = r; memset(r, 0, sizeof(recognizer_data_t)); - - if ((r->grammars = apr_hash_make(schannel->pool)) == NULL) { - ast_log(LOG_ERROR, "Unable to allocate hash for grammars\n"); - status = -1; + if (schannel->type == SPEECH_CHANNEL_RECOGNIZER) { + if ((r->grammars = apr_hash_make(schannel->pool)) == NULL) { + ast_log(LOG_ERROR, "Unable to allocate hash for grammars\n"); + status = -1; + } } } else { ast_log(LOG_ERROR, "Unable to allocate recognizer data structure\n"); @@ -639,10 +653,9 @@ int speech_channel_read(speech_channel_t *schannel, void *data, apr_size_t *len, #if SPEECH_CHANNEL_TRACE apr_size_t req_len = *len; #endif + if (!schannel->mutex) return 1; audio_queue_t *queue = schannel->audio_queue; - apr_thread_mutex_lock(schannel->mutex); - if (schannel->state == SPEECH_CHANNEL_PROCESSING) status = audio_queue_read(queue, data, len, block); else diff --git a/app-unimrcp/speech_channel.h b/app-unimrcp/speech_channel.h index f57069a..34e4b98 100644 --- a/app-unimrcp/speech_channel.h +++ b/app-unimrcp/speech_channel.h @@ -39,7 +39,8 @@ /* Type of MRCP channel. */ enum speech_channel_type_t { SPEECH_CHANNEL_SYNTHESIZER, - SPEECH_CHANNEL_RECOGNIZER + SPEECH_CHANNEL_RECOGNIZER, + SPEECH_CHANNEL_VERIFIER }; typedef enum speech_channel_type_t speech_channel_type_t; From 4c7197e5938d63982645d20526e705ec4454e16a Mon Sep 17 00:00:00 2001 From: Fabiano Zaruch Chinasso Date: Mon, 13 Sep 2021 16:36:26 -0300 Subject: [PATCH 02/16] [BMT-167] Fixes, parameters and Result treatment --- app-unimrcp/app_mrcpverif.c | 595 +++++++++++++---------------------- app-unimrcp/app_recogverif.c | 189 ++++++----- app-unimrcp/speech_channel.c | 1 + 3 files changed, 331 insertions(+), 454 deletions(-) diff --git a/app-unimrcp/app_mrcpverif.c b/app-unimrcp/app_mrcpverif.c index cf67464..3f40777 100644 --- a/app-unimrcp/app_mrcpverif.c +++ b/app-unimrcp/app_mrcpverif.c @@ -73,50 +73,36 @@ and the digit will be returned to dialplan). - - - - - - + + + - - - - - - - + + + - + - - - - This application establishes an MRCP session for speech recognition and optionally plays a prompt file. + This application establishes an MRCP session for speak verification and optionally plays a prompt file. Once recognition completes, the application exits and returns results to the dialplan. If recognition completed, the variable ${RECOGSTATUS} is set to "OK". Otherwise, if recognition couldn't be started, the variable ${RECOGSTATUS} is set to "ERROR". If the caller hung up while recognition was still in-progress, @@ -145,19 +131,18 @@ static const char *app_recog = "MRCPVerif"; static ast_mrcp_application_t *mrcpverif = NULL; /* The enumeration of application options (excluding the MRCP params). */ -enum mrcprecog_option_flags { - MRCPRECOG_PROFILE = (1 << 0), - MRCPRECOG_INTERRUPT = (1 << 1), - MRCPRECOG_FILENAME = (1 << 2), - MRCPRECOG_BARGEIN = (1 << 3), - MRCPRECOG_GRAMMAR_DELIMITERS = (1 << 4), - MRCPRECOG_EXIT_ON_PLAYERROR = (1 << 5), - MRCPRECOG_URI_ENCODED_RESULTS = (1 << 6), - MRCPRECOG_OUTPUT_DELIMITERS = (1 << 7), - MRCPRECOG_INPUT_TIMERS = (1 << 8), - MRCPRECOG_PERSISTENT_LIFETIME = (1 << 9), - MRCPRECOG_DATASTORE_ENTRY = (1 << 10), - MRCPRECOG_INSTANCE_FORMAT = (1 << 11) +enum mrcpverif_option_flags { + MRCPVERIF_PROFILE = (1 << 0), + MRCPVERIF_INTERRUPT = (1 << 1), + MRCPVERIF_FILENAME = (1 << 2), + MRCPVERIF_BARGEIN = (1 << 3), + MRCPVERIF_EXIT_ON_PLAYERROR = (1 << 4), + MRCPVERIF_URI_ENCODED_RESULTS = (1 << 5), + MRCPVERIF_OUTPUT_DELIMITERS = (1 << 6), + MRCPVERIF_INPUT_TIMERS = (1 << 7), + MRCPVERIF_PERSISTENT_LIFETIME = (1 << 8), + MRCPVERIF_DATASTORE_ENTRY = (1 << 9), + MRCPVERIF_INSTANCE_FORMAT = (1 << 10) }; /* The enumeration of option arguments. */ @@ -179,22 +164,23 @@ enum mrcprecog_option_args { OPT_ARG_ARRAY_SIZE = 12 }; -/* The enumeration of plocies for the use of input timers. */ -enum mrcprecog_it_policies { +/* The enumeration of policies for the use of input timers. */ +enum mrcpverif_it_policies { IT_POLICY_OFF = 0, /* do not start input timers */ IT_POLICY_ON = 1, /* start input timers with RECOGNIZE */ IT_POLICY_AUTO /* start input timers once prompt is finished [default] */ }; /* The structure which holds the application options (including the MRCP params). */ -struct mrcprecog_options_t { - apr_hash_t *recog_hfs; +struct mrcpverif_options_t { + apr_hash_t *verif_session_hfs; + apr_hash_t *verif_hfs; int flags; const char *params[OPT_ARG_ARRAY_SIZE]; }; -typedef struct mrcprecog_options_t mrcprecog_options_t; +typedef struct mrcpverif_options_t mrcpverif_options_t; /* --- MRCP SPEECH CHANNEL INTERFACE TO UNIMRCP --- */ @@ -252,16 +238,6 @@ static apt_bool_t speech_on_channel_add(mrcp_application_t *application, mrcp_se return FALSE; } - if (schannel->stream != NULL) { - schannel->dtmf_generator = mpf_dtmf_generator_create(schannel->stream, schannel->pool); - /* schannel->dtmf_generator = mpf_dtmf_generator_create_ex(schannel->stream, MPF_DTMF_GENERATOR_OUTBAND, 70, 50, schannel->pool); */ - - if (schannel->dtmf_generator != NULL) - ast_log(LOG_DEBUG, "(%s) DTMF generator created\n", schannel->name); - else - ast_log(LOG_WARNING, "(%s) Unable to create DTMF generator\n", schannel->name); - } - schannel->rate = descriptor->sampling_rate; const char *codec_name = NULL; if (descriptor->name.length > 0) @@ -292,8 +268,8 @@ static apt_bool_t speech_on_channel_add(mrcp_application_t *application, mrcp_se /* --- MRCP ASR --- */ -/* Start recognizer's input timers. */ -static int recog_channel_start_input_timers(speech_channel_t *schannel) +/* Start verifier's input timers. */ +static int verif_channel_start_input_timers(speech_channel_t *schannel) { int status = 0; @@ -318,7 +294,7 @@ static int recog_channel_start_input_timers(speech_channel_t *schannel) ast_log(LOG_DEBUG, "(%s) Sending START-INPUT-TIMERS request\n", schannel->name); /* Send START-INPUT-TIMERS to MRCP server. */ - mrcp_message = mrcp_application_message_create(schannel->unimrcp_session, schannel->unimrcp_channel, RECOGNIZER_START_INPUT_TIMERS); + mrcp_message = mrcp_application_message_create(schannel->unimrcp_session, schannel->unimrcp_channel, VERIFIER_START_INPUT_TIMERS); if (mrcp_message) { mrcp_application_message_send(schannel->unimrcp_session, schannel->unimrcp_channel, mrcp_message); @@ -333,7 +309,7 @@ static int recog_channel_start_input_timers(speech_channel_t *schannel) } /* Flag that input has started. */ -static int recog_channel_set_start_of_input(speech_channel_t *schannel) +static int verif_channel_set_start_of_input(speech_channel_t *schannel) { int status = 0; @@ -359,8 +335,8 @@ static int recog_channel_set_start_of_input(speech_channel_t *schannel) return status; } -/* Set the recognition results. */ -static int recog_channel_set_results(speech_channel_t *schannel, int completion_cause, const apt_str_t *result, const apt_str_t *waveform_uri) +/* Set the verification results. */ +static int verif_channel_set_results(speech_channel_t *schannel, int completion_cause, const apt_str_t *result, const apt_str_t *waveform_uri) { int status = 0; @@ -400,8 +376,8 @@ static int recog_channel_set_results(speech_channel_t *schannel, int completion_ return status; } -/* Get the recognition results. */ -static int recog_channel_get_results(speech_channel_t *schannel, const char **completion_cause, const char **result, const char **waveform_uri) +/* Get the verification results. */ +static int verif_channel_get_results(speech_channel_t *schannel, const char **completion_cause, const char **result, const char **waveform_uri) { if (!schannel) { ast_log(LOG_ERROR, "get_results: unknown channel error!\n"); @@ -448,8 +424,8 @@ static int recog_channel_get_results(speech_channel_t *schannel, const char **co return 0; } -/* Flag that the recognizer channel timers are started. */ -static int recog_channel_set_timers_started(speech_channel_t *schannel) +/* Flag that the verifier channel timers are started. */ +static int verif_channel_set_timers_started(speech_channel_t *schannel) { if (!schannel) { ast_log(LOG_ERROR, "set_timers_started: unknown channel error!\n"); @@ -474,13 +450,13 @@ static int recog_channel_set_timers_started(speech_channel_t *schannel) } /* Start VERIFY request. */ -static int verif_channel_start(speech_channel_t *schannel, const char *name, int start_input_timers, apr_hash_t *header_fields) +static int verif_channel_start(speech_channel_t *schannel, const char *name, int start_input_timers, mrcpverif_options_t *options) { int status = 0; mrcp_message_t *mrcp_message = NULL; mrcp_message_t *verif_message = NULL; mrcp_generic_header_t *generic_header = NULL; - mrcp_recog_header_t *recog_header = NULL; + mrcp_verifier_header_t *verif_header = NULL; recognizer_data_t *r = NULL; grammar_t *grammar = NULL; @@ -530,33 +506,19 @@ static int verif_channel_start(speech_channel_t *schannel, const char *name, int return -1; } - /* Set Content-Type to text/uri-list. */ - const char *mime_type = grammar_type_to_mime(GRAMMAR_TYPE_URI, schannel->profile); - apt_string_assign(&generic_header->content_type, mime_type, mrcp_message->pool); - mrcp_generic_header_property_add(mrcp_message, GENERIC_HEADER_CONTENT_TYPE); - - /* Allocate recognizer-specific header. */ - if ((recog_header = (mrcp_recog_header_t *)mrcp_resource_header_prepare(mrcp_message)) == NULL) { + /* Allocate verifier-specific header. */ + if ((verif_header = (mrcp_verifier_header_t *)mrcp_resource_header_prepare(mrcp_message)) == NULL) { apr_thread_mutex_unlock(schannel->mutex); return -1; } -#if 0 - /* Set Cancel-If-Queue. */ - if (mrcp_message->start_line.version == MRCP_VERSION_2) { - recog_header->cancel_if_queue = FALSE; - mrcp_resource_header_property_add(mrcp_message, RECOGNIZER_HEADER_CANCEL_IF_QUEUE); - } -#endif + /* Set Start-Input-Timers. */ - recog_header->start_input_timers = start_input_timers ? TRUE : FALSE; + verif_header->start_input_timers = start_input_timers ? TRUE : FALSE; mrcp_resource_header_property_add(mrcp_message, VERIFIER_HEADER_START_INPUT_TIMERS); - /* Set parameters. */ - speech_channel_set_params(schannel, mrcp_message, header_fields); -#if 0 - /* Set message body. */ - apt_string_assign_n(&mrcp_message->body, grammar_refs, length, mrcp_message->pool); -#endif + /* Set parameters for START SESSION. */ + speech_channel_set_params(schannel, mrcp_message, options->verif_session_hfs); + /* Empty audio queue and send RECOGNIZE to MRCP server. */ audio_queue_clear(schannel->audio_queue); @@ -578,6 +540,9 @@ static int verif_channel_start(speech_channel_t *schannel, const char *name, int return -1; } + /* Set parameters for VERIFY. */ + speech_channel_set_params(schannel, verif_message, options->verif_hfs); + if (mrcp_application_message_send(schannel->unimrcp_session, schannel->unimrcp_channel, verif_message) == FALSE) { apr_thread_mutex_unlock(schannel->mutex); return -1; @@ -595,94 +560,8 @@ static int verif_channel_start(speech_channel_t *schannel, const char *name, int return status; } -/* Load speech recognition grammar. */ -static int recog_channel_load_grammar(speech_channel_t *schannel, const char *name, grammar_type_t type, const char *data) -{ - int status = 0; - grammar_t *g = NULL; - char ldata[256]; - - if (!schannel || !name || !data) { - ast_log(LOG_ERROR, "load_grammar: unknown channel error!\n"); - return -1; - } - - const char *mime_type; - if (((mime_type = grammar_type_to_mime(type, schannel->profile)) == NULL) || (strlen(mime_type) == 0)) { - ast_log(LOG_WARNING, "(%s) Unable to get MIME type: %i\n", schannel->name, type); - return -1; - } - ast_log(LOG_DEBUG, "(%s) Loading grammar name=%s, type=%s, data=%s\n", schannel->name, name, mime_type, data); - - apr_thread_mutex_lock(schannel->mutex); - - if (schannel->state != SPEECH_CHANNEL_READY) { - apr_thread_mutex_unlock(schannel->mutex); - return -1; - } - - /* If inline, use DEFINE-GRAMMAR to cache it on the server. */ - if (type != GRAMMAR_TYPE_URI) { - mrcp_message_t *mrcp_message; - mrcp_generic_header_t *generic_header; - - /* Create MRCP message. */ - if ((mrcp_message = mrcp_application_message_create(schannel->unimrcp_session, schannel->unimrcp_channel, RECOGNIZER_DEFINE_GRAMMAR)) == NULL) { - apr_thread_mutex_unlock(schannel->mutex); - return -1; - } - - /* Set Content-Type and Content-ID in message. */ - if ((generic_header = (mrcp_generic_header_t *)mrcp_generic_header_prepare(mrcp_message)) == NULL) { - apr_thread_mutex_unlock(schannel->mutex); - return -1; - } - - apt_string_assign(&generic_header->content_type, mime_type, mrcp_message->pool); - mrcp_generic_header_property_add(mrcp_message, GENERIC_HEADER_CONTENT_TYPE); - apt_string_assign(&generic_header->content_id, name, mrcp_message->pool); - mrcp_generic_header_property_add(mrcp_message, GENERIC_HEADER_CONTENT_ID); - - /* Put grammar in message body. */ - apt_string_assign(&mrcp_message->body, data, mrcp_message->pool); - - /* Send message and wait for response. */ - speech_channel_set_state_unlocked(schannel, SPEECH_CHANNEL_PROCESSING); - - if (mrcp_application_message_send(schannel->unimrcp_session, schannel->unimrcp_channel, mrcp_message) == FALSE) { - apr_thread_mutex_unlock(schannel->mutex); - return -1; - } - - apr_thread_cond_timedwait(schannel->cond, schannel->mutex, globals.speech_channel_timeout); - - if (schannel->state != SPEECH_CHANNEL_READY) { - apr_thread_mutex_unlock(schannel->mutex); - return -1; - } - - /* Set up name, type for future RECOGNIZE requests. We'll reference this cached grammar by name. */ - apr_snprintf(ldata, sizeof(ldata) - 1, "session:%s", name); - ldata[sizeof(ldata) - 1] = '\0'; - - data = ldata; - type = GRAMMAR_TYPE_URI; - } - - /* Create the grammar and save it. */ - if ((status = grammar_create(&g, name, type, data, schannel->pool)) == 0) { - recognizer_data_t *r = (recognizer_data_t *)schannel->data; - - if (r != NULL) - apr_hash_set(r->grammars, apr_pstrdup(schannel->pool, g->name), APR_HASH_KEY_STRING, g); - } - - apr_thread_mutex_unlock(schannel->mutex); - return status; -} - -/* Process messages from UniMRCP for the recognizer application. */ -static apt_bool_t recog_message_handler(const mrcp_app_message_t *app_message) +/* Process messages from UniMRCP for the verifier application. */ +static apt_bool_t verif_message_handler(const mrcp_app_message_t *app_message) { /* Call the appropriate callback in the dispatcher function table based on the app_message received. */ if (app_message) @@ -697,7 +576,7 @@ static apt_bool_t verif_on_message_receive(mrcp_application_t *application, mrcp { speech_channel_t *schannel = get_speech_channel(session); if (!schannel || !message) { - ast_log(LOG_ERROR, "recog_on_message_receive: unknown channel error!\n"); + ast_log(LOG_ERROR, "verif_on_message_receive: unknown channel error!\n"); return FALSE; } @@ -712,11 +591,11 @@ static apt_bool_t verif_on_message_receive(mrcp_application_t *application, mrcp speech_channel_set_state(schannel, SPEECH_CHANNEL_PROCESSING); } else if (message->start_line.request_state == MRCP_REQUEST_STATE_COMPLETE) { /* RECOGNIZE failed to start. */ - if (recog_hdr->completion_cause == RECOGNIZER_COMPLETION_CAUSE_UNKNOWN) - ast_log(LOG_DEBUG, "(%s) RECOGNIZE failed: status = %d\n", schannel->name, message->start_line.status_code); + if (recog_hdr->completion_cause == VERIFIER_COMPLETION_CAUSE_UNKNOWN) + ast_log(LOG_DEBUG, "(%s) VERIFY failed: status = %d\n", schannel->name, message->start_line.status_code); else { - ast_log(LOG_DEBUG, "(%s) RECOGNIZE failed: status = %d, completion-cause = %03d\n", schannel->name, message->start_line.status_code, recog_hdr->completion_cause); - recog_channel_set_results(schannel, recog_hdr->completion_cause, NULL, NULL); + ast_log(LOG_DEBUG, "(%s) VERIFY failed: status = %d, completion-cause = %03d\n", schannel->name, message->start_line.status_code, recog_hdr->completion_cause); + verif_channel_set_results(schannel, recog_hdr->completion_cause, NULL, NULL); } speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); } else if (message->start_line.request_state == MRCP_REQUEST_STATE_PENDING) @@ -724,7 +603,7 @@ static apt_bool_t verif_on_message_receive(mrcp_application_t *application, mrcp ast_log(LOG_NOTICE, "(%s) VERIFY PENDING\n", schannel->name); else { /* Received unexpected request_state. */ - ast_log(LOG_DEBUG, "(%s) Unexpected RECOGNIZE request state: %d\n", schannel->name, message->start_line.request_state); + ast_log(LOG_DEBUG, "(%s) Unexpected VERIFY request state: %d\n", schannel->name, message->start_line.request_state); speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); } } else if (message->start_line.method_id == VERIFIER_START_SESSION) { @@ -738,33 +617,15 @@ static apt_bool_t verif_on_message_receive(mrcp_application_t *application, mrcp ast_log(LOG_DEBUG, "(%s) Unexpected VERIFIER START request state: %d\n", schannel->name, message->start_line.request_state); speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); } - } else if (message->start_line.method_id == RECOGNIZER_START_INPUT_TIMERS) { + } else if (message->start_line.method_id == VERIFIER_START_INPUT_TIMERS) { /* Received response to START-INPUT-TIMERS request. */ if (message->start_line.request_state == MRCP_REQUEST_STATE_COMPLETE) { if (message->start_line.status_code >= 200 && message->start_line.status_code <= 299) { ast_log(LOG_DEBUG, "(%s) Timers started\n", schannel->name); - recog_channel_set_timers_started(schannel); + verif_channel_set_timers_started(schannel); } else ast_log(LOG_DEBUG, "(%s) Timers failed to start, status code = %d\n", schannel->name, message->start_line.status_code); } - } else if (message->start_line.method_id == RECOGNIZER_DEFINE_GRAMMAR) { -#if 0 - /* Received response to DEFINE-GRAMMAR request. */ - if (message->start_line.request_state == MRCP_REQUEST_STATE_COMPLETE) { - if (message->start_line.status_code >= 200 && message->start_line.status_code <= 299) { - ast_log(LOG_DEBUG, "(%s) Grammar loaded\n", schannel->name); - speech_channel_set_state(schannel, SPEECH_CHANNEL_READY); - } else { - if (recog_hdr->completion_cause == RECOGNIZER_COMPLETION_CAUSE_UNKNOWN) - ast_log(LOG_DEBUG, "(%s) Grammar failed to load, status code = %d\n", schannel->name, message->start_line.status_code); - else { - ast_log(LOG_DEBUG, "(%s) Grammar failed to load, status code = %d, completion-cause = %03d\n", schannel->name, message->start_line.status_code, recog_hdr->completion_cause); - recog_channel_set_results(schannel, recog_hdr->completion_cause, NULL, NULL); - } - speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); - } - } -#endif } else { /* Received unexpected response. */ ast_log(LOG_DEBUG, "(%s) Unexpected response, method_id = %d\n", schannel->name, (int)message->start_line.method_id); @@ -773,12 +634,12 @@ static apt_bool_t verif_on_message_receive(mrcp_application_t *application, mrcp } else if (message->start_line.message_type == MRCP_MESSAGE_TYPE_EVENT) { /* Received MRCP event. */ if (message->start_line.method_id == VERIFIER_VERIFICATION_COMPLETE) { - ast_log(LOG_DEBUG, "(%s) RECOGNITION COMPLETE, Completion-Cause: %03d\n", schannel->name, recog_hdr->completion_cause); - recog_channel_set_results(schannel, recog_hdr->completion_cause, &message->body, &recog_hdr->waveform_uri); + ast_log(LOG_DEBUG, "(%s) VERIFICATION COMPLETE, Completion-Cause: %03d\n", schannel->name, recog_hdr->completion_cause); + verif_channel_set_results(schannel, recog_hdr->completion_cause, &message->body, &recog_hdr->waveform_uri); speech_channel_set_state(schannel, SPEECH_CHANNEL_READY); } else if (message->start_line.method_id == VERIFIER_START_OF_INPUT) { ast_log(LOG_DEBUG, "(%s) START OF INPUT\n", schannel->name); - recog_channel_set_start_of_input(schannel); + verif_channel_set_start_of_input(schannel); } else { ast_log(LOG_DEBUG, "(%s) Unexpected event, method_id = %d\n", schannel->name, (int)message->start_line.method_id); speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); @@ -792,7 +653,7 @@ static apt_bool_t verif_on_message_receive(mrcp_application_t *application, mrcp } /* UniMRCP callback requesting stream to be opened. */ -static apt_bool_t recog_stream_open(mpf_audio_stream_t* stream, mpf_codec_t *codec) +static apt_bool_t verif_stream_open(mpf_audio_stream_t* stream, mpf_codec_t *codec) { speech_channel_t* schannel; @@ -802,7 +663,7 @@ static apt_bool_t recog_stream_open(mpf_audio_stream_t* stream, mpf_codec_t *cod schannel = NULL; if (!schannel) { - ast_log(LOG_ERROR, "recog_stream_open: unknown channel error!\n"); + ast_log(LOG_ERROR, "verif_stream_open: unknown channel error!\n"); return FALSE; } @@ -810,8 +671,8 @@ static apt_bool_t recog_stream_open(mpf_audio_stream_t* stream, mpf_codec_t *cod return TRUE; } -/* UniMRCP callback requesting next frame for speech recognition. */ -static apt_bool_t recog_stream_read(mpf_audio_stream_t *stream, mpf_frame_t *frame) +/* UniMRCP callback requesting next frame for speal verification. */ +static apt_bool_t verif_stream_read(mpf_audio_stream_t *stream, mpf_frame_t *frame) { speech_channel_t *schannel; @@ -821,7 +682,7 @@ static apt_bool_t recog_stream_read(mpf_audio_stream_t *stream, mpf_frame_t *fra schannel = NULL; if (!schannel || !frame) { - ast_log(LOG_ERROR, "recog_stream_read: unknown channel error!\n"); + ast_log(LOG_ERROR, "verif_stream_read: unknown channel error!\n"); return FALSE; } @@ -847,87 +708,65 @@ static apt_bool_t recog_stream_read(mpf_audio_stream_t *stream, mpf_frame_t *fra } /* Apply application options. */ -static int mrcprecog_option_apply(mrcprecog_options_t *options, const char *key, const char *value) +static int mrcpverif_option_apply(mrcpverif_options_t *options, const char *key, const char *value) { - if (strcasecmp(key, "ct") == 0) { - apr_hash_set(options->recog_hfs, "Confidence-Threshold", APR_HASH_KEY_STRING, value); - } else if (strcasecmp(key, "sva") == 0) { - apr_hash_set(options->recog_hfs, "Speed-vs-Accuracy", APR_HASH_KEY_STRING, value); - } else if (strcasecmp(key, "nb") == 0) { - apr_hash_set(options->recog_hfs, "N-Best-List-Length", APR_HASH_KEY_STRING, value); + if (strcasecmp(key, "vc") == 0) { + apr_hash_set(options->verif_session_hfs, "Min-Verification-Score", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "minph") == 0) { + apr_hash_set(options->verif_session_hfs, "Num-Min-Verification-Phrases", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "maxph") == 0) { + apr_hash_set(options->verif_session_hfs, "Num-Max-Verification-Phrases", APR_HASH_KEY_STRING, value); } else if (strcasecmp(key, "nit") == 0) { - apr_hash_set(options->recog_hfs, "No-Input-Timeout", APR_HASH_KEY_STRING, value); + apr_hash_set(options->verif_hfs, "No-Input-Timeout", APR_HASH_KEY_STRING, value); } else if (strcasecmp(key, "sct") == 0) { - apr_hash_set(options->recog_hfs, "Speech-Complete-Timeout", APR_HASH_KEY_STRING, value); - } else if (strcasecmp(key, "sint") == 0) { - apr_hash_set(options->recog_hfs, "Speech-Incomplete-Timeout", APR_HASH_KEY_STRING, value); - } else if (strcasecmp(key, "dit") == 0) { - apr_hash_set(options->recog_hfs, "Dtmf-Interdigit-Timeout", APR_HASH_KEY_STRING, value); + apr_hash_set(options->verif_hfs, "Speech-Complete-Timeout", APR_HASH_KEY_STRING, value); + }/* else if (strcasecmp(key, "dit") == 0) { + apr_hash_set(options->verif_hfs, "Dtmf-Interdigit-Timeout", APR_HASH_KEY_STRING, value); } else if (strcasecmp(key, "dtt") == 0) { - apr_hash_set(options->recog_hfs, "Dtmf-Term-Timeout", APR_HASH_KEY_STRING, value); + apr_hash_set(options->verif_hfs, "Dtmf-Term-Timeout", APR_HASH_KEY_STRING, value); } else if (strcasecmp(key, "dttc") == 0) { - apr_hash_set(options->recog_hfs, "Dtmf-Term-Char", APR_HASH_KEY_STRING, value); - } else if (strcasecmp(key, "sw") == 0) { - apr_hash_set(options->recog_hfs, "Save-Waveform", APR_HASH_KEY_STRING, value); - } else if (strcasecmp(key, "nac") == 0) { - apr_hash_set(options->recog_hfs, "New-Audio-Channel", APR_HASH_KEY_STRING, value); - } else if (strcasecmp(key, "rm") == 0) { - apr_hash_set(options->recog_hfs, "Recognition-Mode", APR_HASH_KEY_STRING, value); - } else if (strcasecmp(key, "hmaxd") == 0) { - apr_hash_set(options->recog_hfs, "Hotword-Max-Duration", APR_HASH_KEY_STRING, value); - } else if (strcasecmp(key, "hmind") == 0) { - apr_hash_set(options->recog_hfs, "Hotword-Min-Duration", APR_HASH_KEY_STRING, value); - } else if (strcasecmp(key, "cdb") == 0) { - apr_hash_set(options->recog_hfs, "Clear-Dtmf-Buffer", APR_HASH_KEY_STRING, value); - } else if (strcasecmp(key, "enm") == 0) { - apr_hash_set(options->recog_hfs, "Early-No-Match", APR_HASH_KEY_STRING, value); + apr_hash_set(options->verif_hfs, "Dtmf-Term-Char", APR_HASH_KEY_STRING, value); + }*/ else if (strcasecmp(key, "sw") == 0) { + apr_hash_set(options->verif_hfs, "Ver-Buffer-Utterance", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "vm") == 0) { + apr_hash_set(options->verif_session_hfs, "Verification-Mode", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "rpuri") == 0) { + apr_hash_set(options->verif_session_hfs, "Repository-URI", APR_HASH_KEY_STRING, value); + } else if (strcasecmp(key, "vpid") == 0) { + apr_hash_set(options->verif_session_hfs, "Voiceprint-Identifier", APR_HASH_KEY_STRING, value); } else if (strcasecmp(key, "iwu") == 0) { - apr_hash_set(options->recog_hfs, "Input-Waveform-URI", APR_HASH_KEY_STRING, value); - } else if (strcasecmp(key, "sl") == 0) { - apr_hash_set(options->recog_hfs, "Sensitivity-Level", APR_HASH_KEY_STRING, value); - } else if (strcasecmp(key, "spl") == 0) { - apr_hash_set(options->recog_hfs, "Speech-Language", APR_HASH_KEY_STRING, value); - } else if (strcasecmp(key, "mt") == 0) { - apr_hash_set(options->recog_hfs, "Media-Type", APR_HASH_KEY_STRING, value); + apr_hash_set(options->verif_hfs, "Input-Waveform-URI", APR_HASH_KEY_STRING, value); } else if (strcasecmp(key, "vsp") == 0) { - apr_hash_set(options->recog_hfs, "Vendor-Specific-Parameters", APR_HASH_KEY_STRING, value); + apr_hash_set(options->verif_hfs, "Vendor-Specific-Parameters", APR_HASH_KEY_STRING, value); } else if (strcasecmp(key, "p") == 0) { - options->flags |= MRCPRECOG_PROFILE; + options->flags |= MRCPVERIF_PROFILE; options->params[OPT_ARG_PROFILE] = value; } else if (strcasecmp(key, "i") == 0) { - options->flags |= MRCPRECOG_INTERRUPT; + options->flags |= MRCPVERIF_INTERRUPT; options->params[OPT_ARG_INTERRUPT] = value; } else if (strcasecmp(key, "f") == 0) { - options->flags |= MRCPRECOG_FILENAME; + options->flags |= MRCPVERIF_FILENAME; options->params[OPT_ARG_FILENAME] = value; - } else if (strcasecmp(key, "t") == 0) { - apr_hash_set(options->recog_hfs, "Recognition-Timeout", APR_HASH_KEY_STRING, value); } else if (strcasecmp(key, "b") == 0) { - options->flags |= MRCPRECOG_BARGEIN; + options->flags |= MRCPVERIF_BARGEIN; options->params[OPT_ARG_BARGEIN] = value; - } else if (strcasecmp(key, "gd") == 0) { - options->flags |= MRCPRECOG_GRAMMAR_DELIMITERS; - options->params[OPT_ARG_GRAMMAR_DELIMITERS] = value; } else if (strcasecmp(key, "epe") == 0) { - options->flags |= MRCPRECOG_EXIT_ON_PLAYERROR; + options->flags |= MRCPVERIF_EXIT_ON_PLAYERROR; options->params[OPT_ARG_EXIT_ON_PLAYERROR] = value; } else if (strcasecmp(key, "uer") == 0) { - options->flags |= MRCPRECOG_URI_ENCODED_RESULTS; + options->flags |= MRCPVERIF_URI_ENCODED_RESULTS; options->params[OPT_ARG_URI_ENCODED_RESULTS] = value; } else if (strcasecmp(key, "od") == 0) { - options->flags |= MRCPRECOG_OUTPUT_DELIMITERS; + options->flags |= MRCPVERIF_OUTPUT_DELIMITERS; options->params[OPT_ARG_OUTPUT_DELIMITERS] = value; } else if (strcasecmp(key, "sit") == 0) { - options->flags |= MRCPRECOG_INPUT_TIMERS; + options->flags |= MRCPVERIF_INPUT_TIMERS; options->params[OPT_ARG_INPUT_TIMERS] = value; } else if (strcasecmp(key, "plt") == 0) { - options->flags |= MRCPRECOG_PERSISTENT_LIFETIME; + options->flags |= MRCPVERIF_PERSISTENT_LIFETIME; options->params[OPT_ARG_PERSISTENT_LIFETIME] = value; - } else if (strcasecmp(key, "dse") == 0) { - options->flags |= MRCPRECOG_DATASTORE_ENTRY; - options->params[OPT_ARG_DATASTORE_ENTRY] = value; } else if (strcasecmp(key, "nif") == 0) { - options->flags |= MRCPRECOG_INSTANCE_FORMAT; + options->flags |= MRCPVERIF_INSTANCE_FORMAT; options->params[OPT_ARG_INSTANCE_FORMAT] = value; } else { ast_log(LOG_WARNING, "Unknown option: %s\n", key); @@ -936,7 +775,7 @@ static int mrcprecog_option_apply(mrcprecog_options_t *options, const char *key, } /* Parse application options. */ -static int mrcprecog_options_parse(char *str, mrcprecog_options_t *options, apr_pool_t *pool) +static int mrcpverif_options_parse(char *str, mrcpverif_options_t *options, apr_pool_t *pool) { char *s; char *name, *value; @@ -944,7 +783,11 @@ static int mrcprecog_options_parse(char *str, mrcprecog_options_t *options, apr_ if (!str) return 0; - if ((options->recog_hfs = apr_hash_make(pool)) == NULL) { + if ((options->verif_hfs = apr_hash_make(pool)) == NULL) { + return -1; + } + + if ((options->verif_session_hfs = apr_hash_make(pool)) == NULL) { return -1; } @@ -969,17 +812,26 @@ static int mrcprecog_options_parse(char *str, mrcprecog_options_t *options, apr_ value = s; if ((name = strsep(&value, "=")) && value) { ast_log(LOG_DEBUG, "Apply option %s: %s\n", name, value); - mrcprecog_option_apply(options, name, value); + mrcpverif_option_apply(options, name, value); } } } while (str); + if (!apr_hash_get(options->verif_session_hfs, "Verification-Mode", APR_HASH_KEY_STRING)) + return -1; + + if (!apr_hash_get(options->verif_session_hfs, "Repository-URI", APR_HASH_KEY_STRING)) + return -1; + + if (!apr_hash_get(options->verif_session_hfs, "Voiceprint-Identifier", APR_HASH_KEY_STRING)) + return -1; + return 0; } /* Return the number of prompts which still need to be played. */ -static APR_INLINE int mrcprecog_prompts_available(app_session_t *app_session) +static APR_INLINE int mrcpverif_prompts_available(app_session_t *app_session) { if(app_session->cur_prompt >= app_session->prompts->nelts) return 0; @@ -987,7 +839,7 @@ static APR_INLINE int mrcprecog_prompts_available(app_session_t *app_session) } /* Advance the current prompt index and return the number of prompts remaining. */ -static APR_INLINE int mrcprecog_prompts_advance(app_session_t *app_session) +static APR_INLINE int mrcpverif_prompts_advance(app_session_t *app_session) { if (app_session->cur_prompt >= app_session->prompts->nelts) return -1; @@ -996,41 +848,41 @@ static APR_INLINE int mrcprecog_prompts_advance(app_session_t *app_session) } /* Start playing the current prompt. */ -static struct ast_filestream* mrcprecog_prompt_play(app_session_t *app_session, mrcprecog_options_t *mrcprecog_options, off_t *max_filelength) +static struct ast_filestream* mrcpverif_prompt_play(app_session_t *app_session, mrcpverif_options_t *mrcpverif_options, off_t *max_filelength) { if (app_session->cur_prompt >= app_session->prompts->nelts) { - ast_log(LOG_ERROR, "(%s) Out of bounds prompt index\n", app_session->recog_channel->name); + ast_log(LOG_ERROR, "(%s) Out of bounds prompt index\n", app_session->verif_channel->name); return NULL; } char *filename = APR_ARRAY_IDX(app_session->prompts, app_session->cur_prompt, char*); if (!filename) { - ast_log(LOG_ERROR, "(%s) Invalid file name\n", app_session->recog_channel->name); + ast_log(LOG_ERROR, "(%s) Invalid file name\n", app_session->verif_channel->name); return NULL; } - return astchan_stream_file(app_session->recog_channel->chan, filename, max_filelength); + return astchan_stream_file(app_session->verif_channel->chan, filename, max_filelength); } /* Exit the application. */ -static int mrcprecog_exit(struct ast_channel *chan, app_session_t *app_session, speech_channel_status_t status) +static int mrcpverif_exit(struct ast_channel *chan, app_session_t *app_session, speech_channel_status_t status) { if (app_session) { if (app_session->readformat && app_session->rawreadformat) ast_set_read_format_path(chan, app_session->rawreadformat, app_session->readformat); - if (app_session->recog_channel) { - if (app_session->recog_channel->session_id) - pbx_builtin_setvar_helper(chan, "RECOG_SID", app_session->recog_channel->session_id); + if (app_session->verif_channel) { + if (app_session->verif_channel->session_id) + pbx_builtin_setvar_helper(chan, "VERIF_SID", app_session->verif_channel->session_id); if (app_session->lifetime == APP_SESSION_LIFETIME_DYNAMIC) { - speech_channel_destroy(app_session->recog_channel); - app_session->recog_channel = NULL; + speech_channel_destroy(app_session->verif_channel); + app_session->verif_channel = NULL; } } } const char *status_str = speech_channel_status_to_string(status); - pbx_builtin_setvar_helper(chan, "RECOGSTATUS", status_str); + pbx_builtin_setvar_helper(chan, "VERIFSTATUS", status_str); ast_log(LOG_NOTICE, "%s() exiting status: %s on %s\n", app_recog, status_str, ast_channel_name(chan)); return 0; } @@ -1045,46 +897,41 @@ static int app_verif_exec(struct ast_channel *chan, ast_app_data data) speech_channel_status_t status = SPEECH_CHANNEL_STATUS_OK; char *parse; int i; - mrcprecog_options_t mrcprecog_options; + mrcpverif_options_t mrcpverif_options; AST_DECLARE_APP_ARGS(args, - AST_APP_ARG(grammar); AST_APP_ARG(options); ); if (ast_strlen_zero(data)) { - ast_log(LOG_WARNING, "%s() requires an argument (grammar[,options])\n", app_recog); - return mrcprecog_exit(chan, NULL, SPEECH_CHANNEL_STATUS_ERROR); + ast_log(LOG_WARNING, "%s() requires options\n", app_recog); + return mrcpverif_exit(chan, NULL, SPEECH_CHANNEL_STATUS_ERROR); } /* We need to make a copy of the input string if we are going to modify it! */ parse = ast_strdupa(data); AST_STANDARD_APP_ARGS(args, parse); - if (ast_strlen_zero(args.grammar)) { - ast_log(LOG_WARNING, "%s() requires a grammar argument (grammar[,options])\n", app_recog); - return mrcprecog_exit(chan, NULL, SPEECH_CHANNEL_STATUS_ERROR); - } - - args.grammar = normalize_input_string(args.grammar); - ast_log(LOG_NOTICE, "%s() grammar: %s\n", app_recog, args.grammar); - app_datastore_t* datastore = app_datastore_get(chan); if (!datastore) { ast_log(LOG_ERROR, "Unable to retrieve data from app datastore on %s\n", ast_channel_name(chan)); - return mrcprecog_exit(chan, NULL, SPEECH_CHANNEL_STATUS_ERROR); + return mrcpverif_exit(chan, NULL, SPEECH_CHANNEL_STATUS_ERROR); } - mrcprecog_options.recog_hfs = NULL; - mrcprecog_options.flags = 0; + mrcpverif_options.verif_hfs = NULL; + mrcpverif_options.verif_session_hfs = NULL; + mrcpverif_options.flags = 0; for (i=0; ipool, args.options); - mrcprecog_options_parse(options_buf, &mrcprecog_options, datastore->pool); + if (mrcpverif_options_parse(options_buf, &mrcpverif_options, datastore->pool) < 0) { + ast_log(LOG_ERROR, "%s() Missing mandatory options: Rpository URI, Voiceprint and Verification mode\n", app_recog); + return mrcpverif_exit(chan, NULL, SPEECH_CHANNEL_STATUS_ERROR); + } } /* Answer if it's not already going. */ @@ -1099,17 +946,17 @@ static int app_verif_exec(struct ast_channel *chan, ast_app_data data) /* Get datastore entry. */ const char *entry = DEFAULT_DATASTORE_ENTRY; - if ((mrcprecog_options.flags & MRCPRECOG_DATASTORE_ENTRY) == MRCPRECOG_DATASTORE_ENTRY) { - if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_DATASTORE_ENTRY])) { - entry = mrcprecog_options.params[OPT_ARG_DATASTORE_ENTRY]; + if ((mrcpverif_options.flags & MRCPVERIF_DATASTORE_ENTRY) == MRCPVERIF_DATASTORE_ENTRY) { + if (!ast_strlen_zero(mrcpverif_options.params[OPT_ARG_DATASTORE_ENTRY])) { + entry = mrcpverif_options.params[OPT_ARG_DATASTORE_ENTRY]; lifetime = APP_SESSION_LIFETIME_PERSISTENT; } } /* Check session lifetime. */ - if ((mrcprecog_options.flags & MRCPRECOG_PERSISTENT_LIFETIME) == MRCPRECOG_PERSISTENT_LIFETIME) { - if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_PERSISTENT_LIFETIME])) { - lifetime = (atoi(mrcprecog_options.params[OPT_ARG_PERSISTENT_LIFETIME]) == 0) ? + if ((mrcpverif_options.flags & MRCPVERIF_PERSISTENT_LIFETIME) == MRCPVERIF_PERSISTENT_LIFETIME) { + if (!ast_strlen_zero(mrcpverif_options.params[OPT_ARG_PERSISTENT_LIFETIME])) { + lifetime = (atoi(mrcpverif_options.params[OPT_ARG_PERSISTENT_LIFETIME]) == 0) ? APP_SESSION_LIFETIME_DYNAMIC : APP_SESSION_LIFETIME_PERSISTENT; } } @@ -1117,7 +964,7 @@ static int app_verif_exec(struct ast_channel *chan, ast_app_data data) /* Get application datastore. */ app_session_t *app_session = app_datastore_session_add(datastore, entry); if (!app_session) { - return mrcprecog_exit(chan, NULL, SPEECH_CHANNEL_STATUS_ERROR); + return mrcpverif_exit(chan, NULL, SPEECH_CHANNEL_STATUS_ERROR); } datastore->last_recog_entry = entry; @@ -1128,14 +975,14 @@ static int app_verif_exec(struct ast_channel *chan, ast_app_data data) app_session->it_policy = IT_POLICY_AUTO; app_session->lifetime = lifetime; - if(!app_session->recog_channel) { + if(!app_session->verif_channel) { /* Get new read format. */ app_session->nreadformat = ast_channel_get_speechreadformat(chan, app_session->pool); - name = apr_psprintf(app_session->pool, "ASR-%lu", (unsigned long int)speech_channel_number); + name = apr_psprintf(app_session->pool, "VER-%lu", (unsigned long int)speech_channel_number); /* Create speech channel for recognition. */ - app_session->recog_channel = speech_channel_create( + app_session->verif_channel = speech_channel_create( app_session->pool, name, SPEECH_CHANNEL_VERIFIER, @@ -1143,14 +990,14 @@ static int app_verif_exec(struct ast_channel *chan, ast_app_data data) app_session->nreadformat, NULL, chan); - if (!app_session->recog_channel) { - return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + if (!app_session->verif_channel) { + return mrcpverif_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); } const char *profile_name = NULL; - if ((mrcprecog_options.flags & MRCPRECOG_PROFILE) == MRCPRECOG_PROFILE) { - if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_PROFILE])) { - profile_name = mrcprecog_options.params[OPT_ARG_PROFILE]; + if ((mrcpverif_options.flags & MRCPVERIF_PROFILE) == MRCPVERIF_PROFILE) { + if (!ast_strlen_zero(mrcpverif_options.params[OPT_ARG_PROFILE])) { + profile_name = mrcpverif_options.params[OPT_ARG_PROFILE]; } } @@ -1158,16 +1005,16 @@ static int app_verif_exec(struct ast_channel *chan, ast_app_data data) ast_mrcp_profile_t *profile = get_recog_profile(profile_name); if (!profile) { ast_log(LOG_ERROR, "(%s) Can't find profile, %s\n", name, profile_name); - return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + return mrcpverif_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); } /* Open recognition channel. */ - if (speech_channel_open(app_session->recog_channel, profile) != 0) { - return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + if (speech_channel_open(app_session->verif_channel, profile) != 0) { + return mrcpverif_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); } } else { - name = app_session->recog_channel->name; + name = app_session->verif_channel->name; } /* Get old read format. */ @@ -1183,29 +1030,29 @@ static int app_verif_exec(struct ast_channel *chan, ast_app_data data) /* Check if barge-in is allowed. */ int bargein = 1; - if ((mrcprecog_options.flags & MRCPRECOG_BARGEIN) == MRCPRECOG_BARGEIN) { - if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_BARGEIN])) { - bargein = (atoi(mrcprecog_options.params[OPT_ARG_BARGEIN]) == 0) ? 0 : 1; + if ((mrcpverif_options.flags & MRCPVERIF_BARGEIN) == MRCPVERIF_BARGEIN) { + if (!ast_strlen_zero(mrcpverif_options.params[OPT_ARG_BARGEIN])) { + bargein = (atoi(mrcpverif_options.params[OPT_ARG_BARGEIN]) == 0) ? 0 : 1; } } dtmf_enable = 2; - if ((mrcprecog_options.flags & MRCPRECOG_INTERRUPT) == MRCPRECOG_INTERRUPT) { - if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_INTERRUPT])) { + if ((mrcpverif_options.flags & MRCPVERIF_INTERRUPT) == MRCPVERIF_INTERRUPT) { + if (!ast_strlen_zero(mrcpverif_options.params[OPT_ARG_INTERRUPT])) { dtmf_enable = 1; - if (strcasecmp(mrcprecog_options.params[OPT_ARG_INTERRUPT], "any") == 0) - mrcprecog_options.params[OPT_ARG_INTERRUPT] = AST_DIGIT_ANY; - else if (strcasecmp(mrcprecog_options.params[OPT_ARG_INTERRUPT], "none") == 0) + if (strcasecmp(mrcpverif_options.params[OPT_ARG_INTERRUPT], "any") == 0) + mrcpverif_options.params[OPT_ARG_INTERRUPT] = AST_DIGIT_ANY; + else if (strcasecmp(mrcpverif_options.params[OPT_ARG_INTERRUPT], "none") == 0) dtmf_enable = 2; - else if (strcasecmp(mrcprecog_options.params[OPT_ARG_INTERRUPT], "disable") == 0) + else if (strcasecmp(mrcpverif_options.params[OPT_ARG_INTERRUPT], "disable") == 0) dtmf_enable = 0; } } /* Get NLSML instance format, if specified */ - if ((mrcprecog_options.flags & MRCPRECOG_INSTANCE_FORMAT) == MRCPRECOG_INSTANCE_FORMAT) { - if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_INSTANCE_FORMAT])) { - const char *format = mrcprecog_options.params[OPT_ARG_INSTANCE_FORMAT]; + if ((mrcpverif_options.flags & MRCPVERIF_INSTANCE_FORMAT) == MRCPVERIF_INSTANCE_FORMAT) { + if (!ast_strlen_zero(mrcpverif_options.params[OPT_ARG_INSTANCE_FORMAT])) { + const char *format = mrcpverif_options.params[OPT_ARG_INSTANCE_FORMAT]; if (strcasecmp(format, "xml") == 0) app_session->instance_format = NLSML_INSTANCE_FORMAT_XML; else if (strcasecmp(format, "json") == 0) @@ -1214,18 +1061,18 @@ static int app_verif_exec(struct ast_channel *chan, ast_app_data data) } const char *filenames = NULL; - if ((mrcprecog_options.flags & MRCPRECOG_FILENAME) == MRCPRECOG_FILENAME) { - if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_FILENAME])) { - filenames = mrcprecog_options.params[OPT_ARG_FILENAME]; + if ((mrcpverif_options.flags & MRCPVERIF_FILENAME) == MRCPVERIF_FILENAME) { + if (!ast_strlen_zero(mrcpverif_options.params[OPT_ARG_FILENAME])) { + filenames = mrcpverif_options.params[OPT_ARG_FILENAME]; } } if (filenames) { /* Get output delimiters. */ const char *output_delimiters = "^"; - if ((mrcprecog_options.flags & MRCPRECOG_OUTPUT_DELIMITERS) == MRCPRECOG_OUTPUT_DELIMITERS) { - if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_OUTPUT_DELIMITERS])) { - output_delimiters = mrcprecog_options.params[OPT_ARG_OUTPUT_DELIMITERS]; + if ((mrcpverif_options.flags & MRCPVERIF_OUTPUT_DELIMITERS) == MRCPVERIF_OUTPUT_DELIMITERS) { + if (!ast_strlen_zero(mrcpverif_options.params[OPT_ARG_OUTPUT_DELIMITERS])) { + output_delimiters = mrcpverif_options.params[OPT_ARG_OUTPUT_DELIMITERS]; ast_log(LOG_DEBUG, "(%s) Output delimiters: %s\n", output_delimiters, name); } } @@ -1244,24 +1091,24 @@ static int app_verif_exec(struct ast_channel *chan, ast_app_data data) } int exit_on_playerror = 0; - if ((mrcprecog_options.flags & MRCPRECOG_EXIT_ON_PLAYERROR) == MRCPRECOG_EXIT_ON_PLAYERROR) { - if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_EXIT_ON_PLAYERROR])) { - exit_on_playerror = atoi(mrcprecog_options.params[OPT_ARG_EXIT_ON_PLAYERROR]); + if ((mrcpverif_options.flags & MRCPVERIF_EXIT_ON_PLAYERROR) == MRCPVERIF_EXIT_ON_PLAYERROR) { + if (!ast_strlen_zero(mrcpverif_options.params[OPT_ARG_EXIT_ON_PLAYERROR])) { + exit_on_playerror = atoi(mrcpverif_options.params[OPT_ARG_EXIT_ON_PLAYERROR]); if ((exit_on_playerror < 0) || (exit_on_playerror > 2)) exit_on_playerror = 1; } } - int prompt_processing = (mrcprecog_prompts_available(app_session)) ? 1 : 0; + int prompt_processing = (mrcpverif_prompts_available(app_session)) ? 1 : 0; struct ast_filestream *filestream = NULL; off_t max_filelength; /* If bargein is not allowed, play all the prompts and wait for for them to complete. */ if (!bargein && prompt_processing) { /* Start playing first prompt. */ - filestream = mrcprecog_prompt_play(app_session, &mrcprecog_options, &max_filelength); + filestream = mrcpverif_prompt_play(app_session, &mrcpverif_options, &max_filelength); if (!filestream && exit_on_playerror) { - return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + return mrcpverif_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); } do { @@ -1270,22 +1117,22 @@ static int app_verif_exec(struct ast_channel *chan, ast_app_data data) f = ast_read(chan); if (!f) { ast_log(LOG_DEBUG, "(%s) ast_waitstream failed on %s, channel read is a null frame. Hangup detected\n", name, ast_channel_name(chan)); - return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_INTERRUPTED); + return mrcpverif_exit(chan, app_session, SPEECH_CHANNEL_STATUS_INTERRUPTED); } ast_frfree(f); ast_log(LOG_WARNING, "(%s) ast_waitstream failed on %s\n", name, ast_channel_name(chan)); - return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + return mrcpverif_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); } filestream = NULL; } /* End of current prompt -> advance to the next one. */ - if (mrcprecog_prompts_advance(app_session) > 0) { + if (mrcpverif_prompts_advance(app_session) > 0) { /* Start playing current prompt. */ - filestream = mrcprecog_prompt_play(app_session, &mrcprecog_options, &max_filelength); + filestream = mrcpverif_prompt_play(app_session, &mrcpverif_options, &max_filelength); if (!filestream && exit_on_playerror) { - return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + return mrcpverif_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); } } else { @@ -1293,15 +1140,15 @@ static int app_verif_exec(struct ast_channel *chan, ast_app_data data) break; } } - while (mrcprecog_prompts_available(app_session)); + while (mrcpverif_prompts_available(app_session)); prompt_processing = 0; } /* Check the policy for input timers. */ - if ((mrcprecog_options.flags & MRCPRECOG_INPUT_TIMERS) == MRCPRECOG_INPUT_TIMERS) { - if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_INPUT_TIMERS])) { - switch(atoi(mrcprecog_options.params[OPT_ARG_INPUT_TIMERS])) { + if ((mrcpverif_options.flags & MRCPVERIF_INPUT_TIMERS) == MRCPVERIF_INPUT_TIMERS) { + if (!ast_strlen_zero(mrcpverif_options.params[OPT_ARG_INPUT_TIMERS])) { + switch(atoi(mrcpverif_options.params[OPT_ARG_INPUT_TIMERS])) { case 0: app_session->it_policy = IT_POLICY_OFF; break; case 1: app_session->it_policy = IT_POLICY_ON; break; default: app_session->it_policy = IT_POLICY_AUTO; @@ -1312,27 +1159,27 @@ static int app_verif_exec(struct ast_channel *chan, ast_app_data data) int start_input_timers = !prompt_processing; if (app_session->it_policy != IT_POLICY_AUTO) start_input_timers = app_session->it_policy; - recognizer_data_t *r = app_session->recog_channel->data; + recognizer_data_t *r = app_session->verif_channel->data; ast_log(LOG_NOTICE, "(%s) Recognizing, enable DTMFs: %d, start input timers: %d\n", name, dtmf_enable, start_input_timers); /* Start recognition. */ - if (verif_channel_start(app_session->recog_channel, name, start_input_timers, mrcprecog_options.recog_hfs) != 0) { + if (verif_channel_start(app_session->verif_channel, name, start_input_timers, &mrcpverif_options) != 0) { ast_log(LOG_ERROR, "(%s) Unable to start recognition\n", name); const char *completion_cause = NULL; - recog_channel_get_results(app_session->recog_channel, &completion_cause, NULL, NULL); + verif_channel_get_results(app_session->verif_channel, &completion_cause, NULL, NULL); if (completion_cause) - pbx_builtin_setvar_helper(chan, "RECOG_COMPLETION_CAUSE", completion_cause); + pbx_builtin_setvar_helper(chan, "VERIF_COMPLETION_CAUSE", completion_cause); - return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + return mrcpverif_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); } if (prompt_processing) { /* Start playing first prompt. */ - filestream = mrcprecog_prompt_play(app_session, &mrcprecog_options, &max_filelength); + filestream = mrcpverif_prompt_play(app_session, &mrcpverif_options, &max_filelength); if (!filestream && exit_on_playerror) { - return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + return mrcpverif_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); } } @@ -1346,14 +1193,14 @@ static int app_verif_exec(struct ast_channel *chan, ast_app_data data) while ((waitres = ast_waitfor(chan, 100)) >= 0) { recog_processing = 1; - if (app_session->recog_channel && app_session->recog_channel->mutex) { - apr_thread_mutex_lock(app_session->recog_channel->mutex); + if (app_session->verif_channel && app_session->verif_channel->mutex) { + apr_thread_mutex_lock(app_session->verif_channel->mutex); - if (app_session->recog_channel->state != SPEECH_CHANNEL_PROCESSING) { + if (app_session->verif_channel->state != SPEECH_CHANNEL_PROCESSING) { recog_processing = 0; } - apr_thread_mutex_unlock(app_session->recog_channel->mutex); + apr_thread_mutex_unlock(app_session->verif_channel->mutex); } if (recog_processing == 0) @@ -1370,7 +1217,7 @@ static int app_verif_exec(struct ast_channel *chan, ast_app_data data) if (f->frametype == AST_FRAME_VOICE && f->datalen) { apr_size_t len = f->datalen; - if (speech_channel_write(app_session->recog_channel, ast_frame_get_data(f), &len) != 0) { + if (speech_channel_write(app_session->verif_channel, ast_frame_get_data(f), &len) != 0) { ast_frfree(f); break; } @@ -1381,19 +1228,19 @@ static int app_verif_exec(struct ast_channel *chan, ast_app_data data) ast_log(LOG_DEBUG, "(%s) User pressed DTMF key (%d)\n", name, dtmfkey); if (dtmf_enable == 2) { /* Send DTMF frame to ASR engine. */ - if (app_session->recog_channel->dtmf_generator != NULL) { + if (app_session->verif_channel->dtmf_generator != NULL) { char digits[2]; digits[0] = (char)dtmfkey; digits[1] = '\0'; - ast_log(LOG_NOTICE, "(%s) DTMF digit queued (%s)\n", app_session->recog_channel->name, digits); - mpf_dtmf_generator_enqueue(app_session->recog_channel->dtmf_generator, digits); + ast_log(LOG_NOTICE, "(%s) DTMF digit queued (%s)\n", app_session->verif_channel->name, digits); + mpf_dtmf_generator_enqueue(app_session->verif_channel->dtmf_generator, digits); } } else if (dtmf_enable == 1) { /* Stop streaming if within i chars. */ - if (strchr(mrcprecog_options.params[OPT_ARG_INTERRUPT], dtmfkey) || (strcmp(mrcprecog_options.params[OPT_ARG_INTERRUPT],"any"))) { + if (strchr(mrcpverif_options.params[OPT_ARG_INTERRUPT], dtmfkey) || (strcmp(mrcpverif_options.params[OPT_ARG_INTERRUPT],"any"))) { ast_frfree(f); - mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_OK); + mrcpverif_exit(chan, app_session, SPEECH_CHANNEL_STATUS_OK); return dtmfkey; } @@ -1418,16 +1265,16 @@ static int app_verif_exec(struct ast_channel *chan, ast_app_data data) if (status == SPEECH_CHANNEL_STATUS_OK) { int uri_encoded_results = 0; /* Check if the results should be URI-encoded. */ - if ((mrcprecog_options.flags & MRCPRECOG_URI_ENCODED_RESULTS) == MRCPRECOG_URI_ENCODED_RESULTS) { - if (!ast_strlen_zero(mrcprecog_options.params[OPT_ARG_URI_ENCODED_RESULTS])) { - uri_encoded_results = (atoi(mrcprecog_options.params[OPT_ARG_URI_ENCODED_RESULTS]) == 0) ? 0 : 1; + if ((mrcpverif_options.flags & MRCPVERIF_URI_ENCODED_RESULTS) == MRCPVERIF_URI_ENCODED_RESULTS) { + if (!ast_strlen_zero(mrcpverif_options.params[OPT_ARG_URI_ENCODED_RESULTS])) { + uri_encoded_results = (atoi(mrcpverif_options.params[OPT_ARG_URI_ENCODED_RESULTS]) == 0) ? 0 : 1; } } /* Get recognition result. */ - if (recog_channel_get_results(app_session->recog_channel, &completion_cause, &result, &waveform_uri) != 0) { + if (verif_channel_get_results(app_session->verif_channel, &completion_cause, &result, &waveform_uri) != 0) { ast_log(LOG_WARNING, "(%s) Unable to retrieve result\n", name); - return mrcprecog_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); + return mrcpverif_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); } if (result) { @@ -1445,16 +1292,16 @@ static int app_verif_exec(struct ast_channel *chan, ast_app_data data) /* Completion cause should always be available at this stage. */ if (completion_cause) - pbx_builtin_setvar_helper(chan, "RECOG_COMPLETION_CAUSE", completion_cause); + pbx_builtin_setvar_helper(chan, "VERIF_COMPLETION_CAUSE", completion_cause); /* Result may not be available if recognition completed with nomatch, noinput, or other error cause. */ - pbx_builtin_setvar_helper(chan, "RECOG_RESULT", result ? result : ""); + pbx_builtin_setvar_helper(chan, "VERIF_RESULT", result ? result : ""); /* If Waveform URI is available, pass it further to dialplan. */ if (waveform_uri) - pbx_builtin_setvar_helper(chan, "RECOG_WAVEFORM_URI", waveform_uri); + pbx_builtin_setvar_helper(chan, "VERIF_WAVEFORM_URI", waveform_uri); - return mrcprecog_exit(chan, app_session, status); + return mrcpverif_exit(chan, app_session, status); } /* Load MRCPVerif application. */ @@ -1481,7 +1328,7 @@ int load_mrcpverif_app() #endif /* Create the recognizer application and link its callbacks */ - if ((mrcpverif->app = mrcp_application_create(recog_message_handler, (void *)0, pool)) == NULL) { + if ((mrcpverif->app = mrcp_application_create(verif_message_handler, (void *)0, pool)) == NULL) { ast_log(LOG_ERROR, "Unable to create recognizer MRCP application %s\n", app_recog); mrcpverif = NULL; return -1; @@ -1495,9 +1342,9 @@ int load_mrcpverif_app() mrcpverif->dispatcher.on_terminate_event = NULL; mrcpverif->dispatcher.on_resource_discover = NULL; mrcpverif->audio_stream_vtable.destroy = NULL; - mrcpverif->audio_stream_vtable.open_rx = recog_stream_open; + mrcpverif->audio_stream_vtable.open_rx = verif_stream_open; mrcpverif->audio_stream_vtable.close_rx = NULL; - mrcpverif->audio_stream_vtable.read_frame = recog_stream_read; + mrcpverif->audio_stream_vtable.read_frame = verif_stream_read; mrcpverif->audio_stream_vtable.open_tx = NULL; mrcpverif->audio_stream_vtable.close_tx = NULL; mrcpverif->audio_stream_vtable.write_frame = NULL; diff --git a/app-unimrcp/app_recogverif.c b/app-unimrcp/app_recogverif.c index 9bbee75..65af61f 100644 --- a/app-unimrcp/app_recogverif.c +++ b/app-unimrcp/app_recogverif.c @@ -78,6 +78,9 @@ + + + @@ -90,6 +93,7 @@ + @@ -98,6 +102,8 @@ + + - + @@ -137,12 +137,8 @@ functions RECOG_CONFIDENCE(), RECOG_GRAMMAR(), RECOG_INPUT(), and RECOG_INSTANCE(). - MRCPSynth - SynthAndRecog - RECOG_CONFIDENCE - RECOG_GRAMMAR - RECOG_INPUT - RECOG_INSTANCE + MRCPRecog + MRCPVerif ***/ @@ -1890,6 +1886,7 @@ static int app_recog_verif_exec(struct ast_channel *chan, ast_app_data data) int uri_encoded_results = 0; /* Store the results for further reference from the dialplan. */ apr_size_t result_len = strlen(result); + //app_session->nlsml_verif_result = nlsml_verification_result_parse(result, result_len, datastore->pool); if (uri_encoded_results != 0) { apr_size_t len = result_len * 2; From 44bff9b190a1c1820a847db76b7568dd77300289 Mon Sep 17 00:00:00 2001 From: Fabiano Zaruch Chinasso Date: Fri, 12 Nov 2021 11:39:38 -0300 Subject: [PATCH 15/16] [BMT-167] Fix XML documentation warning during module load --- install/make_asterisk_unimrcp_install.sh | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/install/make_asterisk_unimrcp_install.sh b/install/make_asterisk_unimrcp_install.sh index d46e3df..2a0f70c 100755 --- a/install/make_asterisk_unimrcp_install.sh +++ b/install/make_asterisk_unimrcp_install.sh @@ -42,6 +42,9 @@ echo $REPO_DIR ## Project install target [[ ! -z $3 ]] && { ASTERISK_UNIMRCP_INSTALL_DIR=$3; } || { ASTERISK_UNIMRCP_INSTALL_DIR="/usr/lib/asterisk/modules"; } + +ASTERISK_UNIMRCP_DOC_DIR="/var/lib/asterisk/documentation/" + echo "REPO_INSTALL_DIR=$REPO_INSTALL_DIR" echo "BUILD_DIR=$BUILD_DIR" echo "REPO_DIR=$REPO_DIR" @@ -49,6 +52,7 @@ echo "UNIMRCP_DEPS_DIR=$UNIMRCP_DEPS_DIR" echo "UNIMRCP_DIR=$UNIMRCP_DIR" echo "ASTERISK_UNIMRCP_INSTALL_DIR=$ASTERISK_UNIMRCP_INSTALL_DIR" echo "UNIMRCP_INSTALL_DIR=$UNIMRCP_INSTALL_DIR" +echo "ASTERISK_UNIMRCP_DOC_DIR=$ASTERISK_UNIMRCP_DOC_DIR" APR_LIB_DIR=/usr/local/apr/lib SIP_LIB_DIR=/usr/local/lib @@ -125,7 +129,7 @@ mkdir -p ${TMP_PATH}/$UNIMRCP_INSTALL_DIR/lib mkdir -p ${TMP_PATH}/$UNIMRCP_INSTALL_DIR/var mkdir -p ${TMP_PATH}/$ASTERISK_UNIMRCP_INSTALL_DIR mkdir -p ${TMP_PATH}/$UNIMRCP_INSTALL_DIR/bin - +mkdir -p ${TMP_PATH}/$ASTERISK_UNIMRCP_DOC_DIR # The CPqD MRCP stuff that will be installed README="$REPO_INSTALL_DIR/resources/README.md" @@ -134,7 +138,7 @@ ASTERISK_UNIMRCP_LIB_SO="$(readlink -e $ASTERISK_UNIMRCP_INSTALL_DIR/res_speech_ ASTERISK_UNIMRCP_LIB_LA="$(readlink -e $ASTERISK_UNIMRCP_INSTALL_DIR/res_speech_unimrcp.la)" ASTERISK_UNIMRCP_APP_SO="$(readlink -e $ASTERISK_UNIMRCP_INSTALL_DIR/app_unimrcp.so)" ASTERISK_UNIMRCP_APP_LA="$(readlink -e $ASTERISK_UNIMRCP_INSTALL_DIR/app_unimrcp.la)" -#CPQD_VER_PLUGIN="$(readlink -e $BUILD_DIR/installed/plugin/cpqd_mrcp_ver.so)" +ASTERISK_UNIMRCP_APP_DOC="../app-unimrcp/.xmldocs/app_unimrcp-en_US.xml" ASTERISK_MRCP_CONF_DIR="$REPO_INSTALL_DIR/resources/usr" ASTERISK_ETC_DIR="$REPO_INSTALL_DIR/resources/etc" @@ -160,6 +164,9 @@ UNIMRCP_CLIENT_UMC="$UNIMRCP_INSTALL_DIR/bin/umc" [[ -e ${ASTERISK_UNIMRCP_APP_LA} ]] && { cp ${ASTERISK_UNIMRCP_APP_LA} ${TMP_PATH}/$ASTERISK_UNIMRCP_INSTALL_DIR; } || { echo "Missing ${ASTERISK_UNIMRCP_APP_LA} Exiting..."; exit 1; } +[[ -e ${ASTERISK_UNIMRCP_APP_DOC} ]] && { cp ${ASTERISK_UNIMRCP_APP_DOC} ${TMP_PATH}/$ASTERISK_UNIMRCP_DOC_DIR; } || + { echo "Missing ${ASTERISK_UNIMRCP_APP_LA} Exiting..."; exit 1; } + # Copy configuration files { cp -r ${ASTERISK_MRCP_CONF_DIR} ${TMP_PATH}/; } || { echo "Fail to copy MRCP configuration: ${ASTERISK_MRCP_CONF_DIR}"; exit 1; } { cp -r ${ASTERISK_ETC_DIR} ${TMP_PATH}/; } || { echo "Fail to copy Asterisk configuration: ${ASTERISK_ETC_DIR}"; exit 1; } From bfcedcd4bb02158d384831e01ce3ffa35fca4264 Mon Sep 17 00:00:00 2001 From: Fabiano Zaruch Chinasso Date: Fri, 12 Nov 2021 16:40:39 -0300 Subject: [PATCH 16/16] [BMT-167] Use CPQD Asterisk Unimrcp repository --- docker/build-centos/Dockerfile | 4 ++-- docker/build-ubuntu/Dockerfile | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/build-centos/Dockerfile b/docker/build-centos/Dockerfile index f67b949..e310b91 100644 --- a/docker/build-centos/Dockerfile +++ b/docker/build-centos/Dockerfile @@ -47,9 +47,9 @@ RUN cd /tmp/asterisk/ && \ # Asterisk-unimrcp RUN cd /tmp/ && \ - git clone https://github.com/unispeech/asterisk-unimrcp.git && \ + git clone https://github.com/CPqD/asterisk-unimrcp.git && \ cd asterisk-unimrcp && \ - git checkout tags/asterisk-unimrcp-${UNIMRCP_VER} && \ + git checkout feature/BMT-167 && \ ./bootstrap && \ ./configure --with-asterisk-version=${ASTERISK_VER} && \ make && \ diff --git a/docker/build-ubuntu/Dockerfile b/docker/build-ubuntu/Dockerfile index 6bafaa2..836cc41 100644 --- a/docker/build-ubuntu/Dockerfile +++ b/docker/build-ubuntu/Dockerfile @@ -69,9 +69,9 @@ RUN cd /tmp/asterisk/ && make install-headers || echo "WARNING no-install-header # Asterisk-unimrcp RUN cd /tmp/ && \ - git clone https://github.com/unispeech/asterisk-unimrcp.git && \ + git clone https://github.com/CPqD/asterisk-unimrcp.git && \ cd asterisk-unimrcp && \ - git checkout tags/asterisk-unimrcp-${UNIMRCP_VER} && \ + git checkout feature/BMT-167 && \ ./bootstrap && \ ./configure --with-asterisk-version=${ASTERISK_VER} && \ make && \