aboutsummaryrefslogtreecommitdiffstats
path: root/apps/app_speech_utils.c
diff options
context:
space:
mode:
authorfile <file@f38db490-d61c-443f-a65b-d21fe96a405b>2006-04-13 00:18:52 +0000
committerfile <file@f38db490-d61c-443f-a65b-d21fe96a405b>2006-04-13 00:18:52 +0000
commitf51c79cc26059333eccb17bfc84a9c38804bc06a (patch)
tree2df79ba8d344f5eecd8d06832c818a799f7e4492 /apps/app_speech_utils.c
parent6f6ba7555c9d2cd7212060938d7ff8c1fe914b7c (diff)
Updates to speech recognition API and dialplan utilities. Moved to using dialplan functions, and some other misc things.
git-svn-id: http://svn.digium.com/svn/asterisk/trunk@19645 f38db490-d61c-443f-a65b-d21fe96a405b
Diffstat (limited to 'apps/app_speech_utils.c')
-rw-r--r--apps/app_speech_utils.c583
1 files changed, 399 insertions, 184 deletions
diff --git a/apps/app_speech_utils.c b/apps/app_speech_utils.c
index 264f56ba1..babf39b5d 100644
--- a/apps/app_speech_utils.c
+++ b/apps/app_speech_utils.c
@@ -86,6 +86,15 @@ static char *speechdestroy_descrip =
"If you call this application but end up wanting to recognize more speech, you must call SpeechCreate\n"
"again before calling any other application. It takes no arguments.\n";
+static char *speechload_descrip =
+"SpeechLoadGrammar(Grammar Name|Path)\n"
+"Load a grammar only on the channel, not globally.\n"
+"It takes the grammar name as first argument and path as second.\n";
+
+static char *speechunload_descrip =
+"SpeechUnloadGrammar(Grammar Name)\n"
+"Unload a grammar. It takes the grammar name as the only argument.\n";
+
/*! \brief Helper function used by datastores to destroy the speech structure upon hangup */
static void destroy_callback(void *data)
{
@@ -122,6 +131,161 @@ static struct ast_speech *find_speech(struct ast_channel *chan)
return speech;
}
+/* Helper function to find a specific speech recognition result by number */
+static struct ast_speech_result *find_result(struct ast_speech_result *results, int num)
+{
+ struct ast_speech_result *result = NULL;
+ int i = 0;
+
+ result = results;
+ while (result) {
+ if (i == num)
+ break;
+ i++;
+ result = result->next;
+ }
+
+ return result;
+}
+
+/*! \brief SPEECH_SCORE() Dialplan Function */
+static int speech_score(struct ast_channel *chan, char *cmd, char *data,
+ char *buf, size_t len)
+{
+ struct ast_speech_result *result = NULL;
+ struct ast_speech *speech = find_speech(chan);
+ char tmp[128] = "";
+
+ if (data == NULL || speech == NULL || !(result = find_result(speech->results, atoi(data))))
+ return -1;
+
+ snprintf(tmp, sizeof(tmp), "%d", result->score);
+
+ ast_copy_string(buf, tmp, len);
+
+ return 0;
+}
+
+static struct ast_custom_function speech_score_function = {
+ .name = "SPEECH_SCORE",
+ .synopsis = "Gets the confidence score of a result.\n",
+ .syntax = "SPEECH_SCORE(result number)",
+ .desc =
+ "Gets the confidence score of a result.\n",
+ .read = speech_score,
+ .write = NULL,
+};
+
+/*! \brief SPEECH_TEXT() Dialplan Function */
+static int speech_text(struct ast_channel *chan, char *cmd, char *data,
+ char *buf, size_t len)
+{
+ struct ast_speech_result *result = NULL;
+ struct ast_speech *speech = find_speech(chan);
+
+ if (data == NULL || speech == NULL || !(result = find_result(speech->results, atoi(data))))
+ return -1;
+
+ if (result->text != NULL)
+ ast_copy_string(buf, result->text, len);
+
+ return 0;
+}
+
+static struct ast_custom_function speech_text_function = {
+ .name = "SPEECH_TEXT",
+ .synopsis = "Gets the recognized text of a result.\n",
+ .syntax = "SPEECH_TEXT(result number)",
+ .desc =
+ "Gets the recognized text of a result.\n",
+ .read = speech_text,
+ .write = NULL,
+};
+
+/*! \brief SPEECH_GRAMMAR() Dialplan Function */
+static int speech_grammar(struct ast_channel *chan, char *cmd, char *data,
+ char *buf, size_t len)
+{
+ struct ast_speech_result *result = NULL;
+ struct ast_speech *speech = find_speech(chan);
+
+ if (data == NULL || speech == NULL || !(result = find_result(speech->results, atoi(data))))
+ return -1;
+
+ if (result->grammar != NULL)
+ ast_copy_string(buf, result->grammar, len);
+
+ return 0;
+}
+
+static struct ast_custom_function speech_grammar_function = {
+ .name = "SPEECH_GRAMMAR",
+ .synopsis = "Gets the matched grammar of a result if available.",
+ .syntax = "SPEECH_GRAMMAR(result number)",
+ .desc =
+ "Gets the matched grammar of a result if available.\n",
+ .read = speech_grammar,
+ .write = NULL,
+};
+
+/*! \brief SPEECH() Dialplan Function */
+static int speech_read(struct ast_channel *chan, char *cmd, char *data,
+ char *buf, size_t len)
+{
+ int results = 0;
+ struct ast_speech_result *result = NULL;
+ struct ast_speech *speech = find_speech(chan);
+ char tmp[128] = "";
+
+ /* Now go for the various options */
+ if (!strcasecmp(data, "status")) {
+ if (speech != NULL)
+ ast_copy_string(buf, "1", len);
+ else
+ ast_copy_string(buf, "0", len);
+ return 0;
+ }
+
+ /* Make sure we have a speech structure for everything else */
+ if (speech == NULL) {
+ return -1;
+ }
+
+ /* Check to see if they are checking for silence */
+ if (!strcasecmp(data, "spoke")) {
+ if (ast_test_flag(speech, AST_SPEECH_SPOKE))
+ ast_copy_string(buf, "1", len);
+ else
+ ast_copy_string(buf, "0", len);
+ } else if (!strcasecmp(data, "results")) {
+ /* Count number of results */
+ result = speech->results;
+ while (result) {
+ results++;
+ result = result->next;
+ }
+ snprintf(tmp, sizeof(tmp), "%d", results);
+ ast_copy_string(buf, tmp, len);
+ }
+
+ return 0;
+}
+
+static struct ast_custom_function speech_function = {
+ .name = "SPEECH",
+ .synopsis = "Gets information about speech recognition results.",
+ .syntax = "SPEECH(argument)",
+ .desc =
+ "Gets information about speech recognition results.\n"
+ "status: Returns 1 upon speech object existing, or 0 if not\n"
+ "spoke: Returns 1 if spoker spoke, or 0 if not\n"
+ "results: Returns number of results that were recognized\n",
+ .read = speech_read,
+ .write = NULL,
+};
+
+
+
/*! \brief SpeechCreate() Dialplan Application */
static int speech_create(struct ast_channel *chan, void *data)
{
@@ -155,6 +319,63 @@ static int speech_create(struct ast_channel *chan, void *data)
return 0;
}
+/*! \brief SpeechLoadGrammar(Grammar Name|Path) Dialplan Application */
+static int speech_load(struct ast_channel *chan, void *data)
+{
+ int res = 0, argc = 0;
+ struct localuser *u = NULL;
+ struct ast_speech *speech = find_speech(chan);
+ char *argv[2], *args = NULL, *name = NULL, *path = NULL;
+
+ if (!(args = ast_strdupa(data)))
+ return -1;
+
+ LOCAL_USER_ADD(u);
+
+ if (speech == NULL) {
+ LOCAL_USER_REMOVE(u);
+ return -1;
+ }
+
+ /* Parse out arguments */
+ argc = ast_app_separate_args(args, '|', argv, sizeof(argv) / sizeof(argv[0]));
+ if (argc != 2) {
+ LOCAL_USER_REMOVE(u);
+ return -1;
+ }
+ name = argv[0];
+ path = argv[1];
+
+ /* Load the grammar locally on the object */
+ res = ast_speech_grammar_load(speech, name, path);
+
+ LOCAL_USER_REMOVE(u);
+
+ return res;
+}
+
+/*! \brief SpeechUnloadGrammar(Grammar Name) Dialplan Application */
+static int speech_unload(struct ast_channel *chan, void *data)
+{
+ int res = 0;
+ struct localuser *u = NULL;
+ struct ast_speech *speech = find_speech(chan);
+
+ LOCAL_USER_ADD(u);
+
+ if (speech == NULL) {
+ LOCAL_USER_REMOVE(u);
+ return -1;
+ }
+
+ /* Unload the grammar */
+ res = ast_speech_grammar_unload(speech, data);
+
+ LOCAL_USER_REMOVE(u);
+
+ return res;
+}
+
/*! \brief SpeechDeactivateGrammar(Grammar Name) Dialplan Application */
static int speech_deactivate(struct ast_channel *chan, void *data)
{
@@ -272,227 +493,208 @@ static int speech_streamfile(struct ast_channel *chan, const char *filename, con
/*! \brief SpeechBackground(Sound File|Timeout) Dialplan Application */
static int speech_background(struct ast_channel *chan, void *data)
{
- unsigned int timeout = 0;
- int res = 0, done = 0, concepts = 0, argc = 0, started = 0;
- struct localuser *u = NULL;
- struct ast_speech *speech = find_speech(chan);
- struct ast_speech_result *results = NULL, *result = NULL;
- struct ast_frame *f = NULL;
- int oldreadformat = AST_FORMAT_SLINEAR;
- char tmp[256] = "", tmp2[256] = "";
- char dtmf[AST_MAX_EXTENSION] = "";
- time_t start, current;
- struct ast_datastore *datastore = NULL;
- char *argv[2], *args = NULL, *filename = NULL;
-
- if (!(args = ast_strdupa(data)))
+ unsigned int timeout = 0;
+ int res = 0, done = 0, argc = 0, started = 0;
+ struct localuser *u = NULL;
+ struct ast_speech *speech = find_speech(chan);
+ struct ast_frame *f = NULL;
+ int oldreadformat = AST_FORMAT_SLINEAR;
+ char dtmf[AST_MAX_EXTENSION] = "";
+ time_t start, current;
+ struct ast_datastore *datastore = NULL;
+ char *argv[2], *args = NULL, *filename = NULL, tmp[2] = "";
+
+ if (!(args = ast_strdupa(data)))
return -1;
- LOCAL_USER_ADD(u);
+ LOCAL_USER_ADD(u);
- if (speech == NULL) {
- LOCAL_USER_REMOVE(u);
- return -1;
- }
+ if (speech == NULL) {
+ LOCAL_USER_REMOVE(u);
+ return -1;
+ }
- /* Record old read format */
- oldreadformat = chan->readformat;
+ /* Record old read format */
+ oldreadformat = chan->readformat;
- /* Change read format to be signed linear */
- if (ast_set_read_format(chan, AST_FORMAT_SLINEAR)) {
- LOCAL_USER_REMOVE(u);
- return -1;
- }
+ /* Change read format to be signed linear */
+ if (ast_set_read_format(chan, AST_FORMAT_SLINEAR)) {
+ LOCAL_USER_REMOVE(u);
+ return -1;
+ }
- /* Parse out options */
- argc = ast_app_separate_args(args, '|', argv, sizeof(argv) / sizeof(argv[0]));
- if (argc > 0) {
- /* Yay sound file */
- filename = argv[0];
- if (argv[1] != NULL)
- timeout = atoi(argv[1]);
- }
+ /* Parse out options */
+ argc = ast_app_separate_args(args, '|', argv, sizeof(argv) / sizeof(argv[0]));
+ if (argc > 0) {
+ /* Yay sound file */
+ filename = argv[0];
+ if (argv[1] != NULL)
+ timeout = atoi(argv[1]);
+ }
- /* Start streaming the file if possible and specified */
- if (filename != NULL && ast_streamfile(chan, filename, chan->language)) {
- /* An error occured while streaming */
- ast_set_read_format(chan, oldreadformat);
- LOCAL_USER_REMOVE(u);
- return -1;
- }
+ /* Start streaming the file if possible and specified */
+ if (filename != NULL && ast_streamfile(chan, filename, chan->language)) {
+ /* An error occured while streaming */
+ ast_set_read_format(chan, oldreadformat);
+ LOCAL_USER_REMOVE(u);
+ return -1;
+ }
- /* Before we go into waiting for stuff... make sure the structure is ready, if not - start it again */
- if (speech->state == AST_SPEECH_STATE_NOT_READY || speech->state == AST_SPEECH_STATE_DONE) {
- speech->state = AST_SPEECH_STATE_NOT_READY;
- ast_speech_start(speech);
- }
+ /* Before we go into waiting for stuff... make sure the structure is ready, if not - start it again */
+ if (speech->state == AST_SPEECH_STATE_NOT_READY || speech->state == AST_SPEECH_STATE_DONE) {
+ ast_speech_change_state(speech, AST_SPEECH_STATE_NOT_READY);
+ ast_speech_start(speech);
+ }
- /* Okay it's streaming so go into a loop grabbing frames! */
- while (done == 0) {
- /* Run scheduled stuff */
+ /* Okay it's streaming so go into a loop grabbing frames! */
+ while (done == 0) {
+ /* Run scheduled stuff */
ast_sched_runq(chan->sched);
- /* Yay scheduling */
- res = ast_sched_wait(chan->sched);
- if (res < 0) {
- res = 1000;
- }
-
- /* If there is a frame waiting, get it - if not - oh well */
- if (ast_waitfor(chan, res) > 0) {
- f = ast_read(chan);
- if (f == NULL) {
- /* The channel has hung up most likely */
- done = 3;
+ /* Yay scheduling */
+ res = ast_sched_wait(chan->sched);
+ if (res < 0) {
+ res = 1000;
+ }
+
+ /* If there is a frame waiting, get it - if not - oh well */
+ if (ast_waitfor(chan, res) > 0) {
+ f = ast_read(chan);
+ if (f == NULL) {
+ /* The channel has hung up most likely */
+ done = 3;
+ break;
+ }
+ }
+
+ /* Do timeout check (shared between audio/dtmf) */
+ if (started == 1) {
+ time(&current);
+ if ((current-start) >= timeout) {
+ done = 1;
break;
}
}
- /* Do checks on speech structure to see if it's changed */
- ast_mutex_lock(&speech->lock);
- if (ast_test_flag(speech, AST_SPEECH_QUIET) && chan->stream != NULL) {
- ast_stopstream(chan);
+ /* Do checks on speech structure to see if it's changed */
+ ast_mutex_lock(&speech->lock);
+ if (ast_test_flag(speech, AST_SPEECH_QUIET) && chan->stream != NULL) {
+ ast_stopstream(chan);
ast_clear_flag(speech, AST_SPEECH_QUIET);
- }
- /* Check state so we can see what to do */
- switch (speech->state) {
- case AST_SPEECH_STATE_READY:
- /* If audio playback has stopped do a check for timeout purposes */
- if (chan->streamid == -1 && chan->timingfunc == NULL)
- ast_stopstream(chan);
- if (chan->stream == NULL && timeout > 0) {
- /* If start time is not yet done... do it */
- if (started == 0) {
- time(&start);
- started = 1;
- } else {
- time(&current);
- if ((current-start) >= timeout) {
- pbx_builtin_setvar_helper(chan, "SILENCE", "1");
- done = 1;
- break;
- }
- }
- }
- /* Deal with audio frames if present */
- if (f != NULL && f->frametype == AST_FRAME_VOICE) {
- ast_speech_write(speech, f->data, f->datalen);
- }
- break;
- case AST_SPEECH_STATE_WAIT:
- /* Cue up waiting sound if not already playing */
- if (chan->stream == NULL) {
- if (speech->processing_sound != NULL) {
+ }
+ /* Check state so we can see what to do */
+ switch (speech->state) {
+ case AST_SPEECH_STATE_READY:
+ /* If audio playback has stopped do a check for timeout purposes */
+ if (chan->streamid == -1 && chan->timingfunc == NULL)
+ ast_stopstream(chan);
+ if (chan->stream == NULL && timeout > 0 && started == 0) {
+ time(&start);
+ started = 1;
+ }
+ /* Deal with audio frames if present */
+ if (f != NULL && f->frametype == AST_FRAME_VOICE) {
+ ast_speech_write(speech, f->data, f->datalen);
+ }
+ break;
+ case AST_SPEECH_STATE_WAIT:
+ /* Cue up waiting sound if not already playing */
+ if (chan->stream == NULL) {
+ if (speech->processing_sound != NULL) {
if (strlen(speech->processing_sound) > 0 && strcasecmp(speech->processing_sound,"none")) {
speech_streamfile(chan, speech->processing_sound, chan->language);
}
- }
- } else if (chan->streamid == -1 && chan->timingfunc == NULL) {
- ast_stopstream(chan);
+ }
+ } else if (chan->streamid == -1 && chan->timingfunc == NULL) {
+ ast_stopstream(chan);
if (speech->processing_sound != NULL) {
- if (strlen(speech->processing_sound) > 0 && strcasecmp(speech->processing_sound,"none")) {
- speech_streamfile(chan, speech->processing_sound, chan->language);
- }
+ if (strlen(speech->processing_sound) > 0 && strcasecmp(speech->processing_sound,"none")) {
+ speech_streamfile(chan, speech->processing_sound, chan->language);
+ }
}
- }
- break;
- case AST_SPEECH_STATE_DONE:
- /* Assume there will be no results by default */
- pbx_builtin_setvar_helper(chan, "RESULTS", "0");
- pbx_builtin_setvar_helper(chan, "SILENCE", "0");
- /* Decoding is done and over... see if we have results */
- results = ast_speech_results_get(speech);
- if (results != NULL) {
- for (result=results; result!=NULL; result=result->next) {
- /* Text */
- snprintf(tmp, sizeof(tmp), "TEXT%d", concepts);
- pbx_builtin_setvar_helper(chan, tmp, result->text);
- /* Now... score! */
- snprintf(tmp, sizeof(tmp), "SCORE%d", concepts);
- snprintf(tmp2, sizeof(tmp2), "%d", result->score);
- pbx_builtin_setvar_helper(chan, tmp, tmp2);
- concepts++;
- }
- /* Expose number of results to dialplan */
- snprintf(tmp, sizeof(tmp), "%d", concepts);
- pbx_builtin_setvar_helper(chan, "RESULTS", tmp);
- /* Destroy the results since they are now in the dialplan */
- ast_speech_results_free(results);
- }
- /* Now that we are done... let's switch back to not ready state */
- speech->state = AST_SPEECH_STATE_NOT_READY;
- /* Break out of our background too */
- done = 1;
- /* Stop audio playback */
- if (chan->stream != NULL) {
- ast_stopstream(chan);
- }
- break;
- default:
- break;
- }
- ast_mutex_unlock(&speech->lock);
-
- /* Deal with other frame types */
- if (f != NULL) {
- /* Free the frame we received */
- switch (f->frametype) {
- case AST_FRAME_DTMF:
+ }
+ break;
+ case AST_SPEECH_STATE_DONE:
+ /* Copy to speech structure the results, if available */
+ speech->results = ast_speech_results_get(speech);
+ /* Now that we are done... let's switch back to not ready state */
+ ast_speech_change_state(speech, AST_SPEECH_STATE_NOT_READY);
+ /* Break out of our background too */
+ done = 1;
+ /* Stop audio playback */
+ if (chan->stream != NULL) {
+ ast_stopstream(chan);
+ }
+ break;
+ default:
+ break;
+ }
+ ast_mutex_unlock(&speech->lock);
+
+ /* Deal with other frame types */
+ if (f != NULL) {
+ /* Free the frame we received */
+ switch (f->frametype) {
+ case AST_FRAME_DTMF:
if (f->subclass == '#') {
- /* Input is done, throw it into the dialplan */
- pbx_builtin_setvar_helper(chan, "RESULTS", "1");
- pbx_builtin_setvar_helper(chan, "SCORE0", "1000");
- pbx_builtin_setvar_helper(chan, "TEXT0", dtmf);
done = 1;
} else {
if (chan->stream != NULL) {
ast_stopstream(chan);
- }
- /* Start timeout if not already started */
- if (strlen(dtmf) == 0) {
+ /* Change timeout to be 5 seconds for DTMF input */
+ timeout = 5;
time(&start);
+ started = 1;
}
- /* Append to the current information */
snprintf(tmp, sizeof(tmp), "%c", f->subclass);
strncat(dtmf, tmp, sizeof(dtmf));
}
- break;
- case AST_FRAME_CONTROL:
- ast_log(LOG_NOTICE, "Have a control frame of subclass %d\n", f->subclass);
- switch (f->subclass) {
- case AST_CONTROL_HANGUP:
- /* Since they hung up we should destroy the speech structure */
- done = 3;
- default:
- break;
- }
- default:
- break;
- }
- ast_frfree(f);
- f = NULL;
- }
- }
-
- /* See if it was because they hung up */
- if (done == 3) {
- /* Destroy speech structure */
- ast_speech_destroy(speech);
+ break;
+ case AST_FRAME_CONTROL:
+ switch (f->subclass) {
+ case AST_CONTROL_HANGUP:
+ /* Since they hung up we should destroy the speech structure */
+ done = 3;
+ default:
+ break;
+ }
+ default:
+ break;
+ }
+ ast_frfree(f);
+ f = NULL;
+ }
+ }
- datastore = ast_channel_datastore_find(chan, &speech_datastore, NULL);
- if (datastore != NULL) {
- ast_channel_datastore_remove(chan, datastore);
+ if (strlen(dtmf) > 0 && speech->results == NULL) {
+ /* We sort of make a results entry */
+ speech->results = ast_calloc(1, sizeof(*speech->results));
+ if (speech->results != NULL) {
+ speech->results->score = 1000;
+ speech->results->text = strdup(dtmf);
+ speech->results->grammar = strdup("dtmf");
}
- } else {
- /* Channel is okay so restore read format */
- ast_set_read_format(chan, oldreadformat);
}
- LOCAL_USER_REMOVE(u);
+ /* See if it was because they hung up */
+ if (done == 3) {
+ /* Destroy speech structure */
+ ast_speech_destroy(speech);
+ datastore = ast_channel_datastore_find(chan, &speech_datastore, NULL);
+ if (datastore != NULL) {
+ ast_channel_datastore_remove(chan, datastore);
+ }
+ } else {
+ /* Channel is okay so restore read format */
+ ast_set_read_format(chan, oldreadformat);
+ }
- return 0;
+ LOCAL_USER_REMOVE(u);
+
+ return 0;
}
+
/*! \brief SpeechDestroy() Dialplan Application */
static int speech_destroy(struct ast_channel *chan, void *data)
{
@@ -526,11 +728,18 @@ int unload_module(void)
int res = 0;
res = ast_unregister_application("SpeechCreate");
+ res |= ast_unregister_application("SpeechLoadGrammar");
+ res |= ast_unregister_application("SpeechUnloadGrammar");
res |= ast_unregister_application("SpeechActivateGrammar");
res |= ast_unregister_application("SpeechDeactivateGrammar");
res |= ast_unregister_application("SpeechStart");
res |= ast_unregister_application("SpeechBackground");
res |= ast_unregister_application("SpeechDestroy");
+ res |= ast_unregister_application("SpeechProcessingSound");
+ res |= ast_custom_function_unregister(&speech_function);
+ res |= ast_custom_function_unregister(&speech_score_function);
+ res |= ast_custom_function_unregister(&speech_text_function);
+ res |= ast_custom_function_unregister(&speech_grammar_function);
STANDARD_HANGUP_LOCALUSERS;
@@ -542,13 +751,19 @@ int load_module(void)
int res = 0;
res = ast_register_application("SpeechCreate", speech_create, "Create a Speech Structure", speechcreate_descrip);
+ res |= ast_register_application("SpeechLoadGrammar", speech_load, "Load a Grammar", speechload_descrip);
+ res |= ast_register_application("SpeechUnloadGrammar", speech_unload, "Unload a Grammar", speechunload_descrip);
res |= ast_register_application("SpeechActivateGrammar", speech_activate, "Activate a Grammar", speechactivategrammar_descrip);
res |= ast_register_application("SpeechDeactivateGrammar", speech_deactivate, "Deactivate a Grammar", speechdeactivategrammar_descrip);
res |= ast_register_application("SpeechStart", speech_start, "Start recognizing", speechstart_descrip);
res |= ast_register_application("SpeechBackground", speech_background, "Play a sound file and wait for speech to be recognized", speechbackground_descrip);
res |= ast_register_application("SpeechDestroy", speech_destroy, "End speech recognition", speechdestroy_descrip);
res |= ast_register_application("SpeechProcessingSound", speech_processing_sound, "Change background processing sound", speechprocessingsound_descrip);
-
+ res |= ast_custom_function_register(&speech_function);
+ res |= ast_custom_function_register(&speech_score_function);
+ res |= ast_custom_function_register(&speech_text_function);
+ res |= ast_custom_function_register(&speech_grammar_function);
+
return res;
}