Presenting a revised data stores and oh my, a generic speech recognition API! I wonde...
authorJoshua Colp <jcolp@digium.com>
Mon, 10 Apr 2006 23:29:50 +0000 (23:29 +0000)
committerJoshua Colp <jcolp@digium.com>
Mon, 10 Apr 2006 23:29:50 +0000 (23:29 +0000)
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@18979 65c4cc65-6c06-0410-ace0-fbb531ad65f3

.cleancount
apps/app_speech_utils.c [new file with mode: 0644]
channel.c
doc/datastores.txt [new file with mode: 0644]
doc/speechrec.txt [new file with mode: 0644]
include/asterisk/channel.h
include/asterisk/speech.h [new file with mode: 0644]
res/res_speech.c [new file with mode: 0644]

index b1bd38b..8351c19 100644 (file)
@@ -1 +1 @@
-13
+14
diff --git a/apps/app_speech_utils.c b/apps/app_speech_utils.c
new file mode 100644 (file)
index 0000000..0c5c323
--- /dev/null
@@ -0,0 +1,575 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2006, Digium, Inc.
+ *
+ * Joshua Colp <jcolp@digium.com>
+ *
+ * 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.
+ */
+
+/*! \file
+ *
+ * \brief Speech Recognition Utility Applications
+ *
+ * \author Joshua Colp <jcolp@digium.com>
+ *
+ * \ingroup applications
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$");
+
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/lock.h"
+#include "asterisk/app.h"
+#include "asterisk/speech.h"
+
+static char *tdesc = "Dialplan Speech Applications";
+
+LOCAL_USER_DECL;
+
+/* Descriptions for each application */
+static char *speechcreate_descrip =
+"SpeechCreate(engine name)\n"
+"This application creates information to be used by all the other applications. It must be called before doing any speech recognition activities such as activating a grammar.\n"
+"It takes the engine name to use as the argument, if not specified the default engine will be used.\n";
+
+static char *speechactivategrammar_descrip =
+"SpeechActivateGrammar(Grammar Name)\n"
+"This activates the specified grammar to be recognized by the engine. A grammar tells the speech recognition engine what to recognize, \n"
+       "and how to portray it back to you in the dialplan. The grammar name is the only argument to this application.\n";
+
+static char *speechstart_descrip =
+"SpeechStart()\n"
+       "Tell the speech recognition engine that it should start trying to get results from audio being fed to it. This has no arguments.\n";
+
+static char *speechbackground_descrip =
+"SpeechBackground(Sound File|Timeout)\n"
+"This application plays a sound file and waits for the person to speak. Once they start speaking playback of the file stops, and silence is heard.\n"
+"Once they stop talking the processing sound is played to indicate the speech recognition engine is working.\n"
+"Once results are available the application returns and results (score and text) are available as dialplan variables.\n"
+"The first text and score are ${TEXT0} AND ${SCORE0} while the second are ${TEXT1} and ${SCORE1}.\n"
+"This may change in the future, however, to use a dialplan function instead of dialplan variables. Note it is possible to have more then one result.\n"
+       "The first argument is the sound file and the second is the timeout. Note the timeout will only start once the sound file has stopped playing.\n";
+
+static char *speechdeactivategrammar_descrip =
+"SpeechDeactivateGrammar(Grammar Name)\n"
+       "This deactivates the specified grammar so that it is no longer recognized. The only argument is the grammar name to deactivate.\n";
+
+static char *speechprocessingsound_descrip =
+"SpeechProcessingSound(Sound File)\n"
+"This changes the processing sound that SpeechBackground plays back when the speech recognition engine is processing and working to get results.\n"
+       "It takes the sound file as the only argument.\n";
+
+static char *speechdestroy_descrip =
+"SpeechDestroy()\n"
+"This destroys the information used by all the other speech recognition applications.\n"
+"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";
+
+/*! \brief Helper function used by datastores to destroy the speech structure upon hangup */
+static void destroy_callback(void *data)
+{
+       struct ast_speech *speech = (struct ast_speech*)data;
+
+       if (speech == NULL) {
+               return;
+       }
+
+       /* Deallocate now */
+       ast_speech_destroy(speech);
+
+       return;
+}
+
+/*! \brief Static structure for datastore information */
+static const struct ast_datastore_info speech_datastore = {
+       .type = "speech",
+       .destroy = destroy_callback
+};
+
+/*! \brief Helper function used to find the speech structure attached to a channel */
+static struct ast_speech *find_speech(struct ast_channel *chan)
+{
+       struct ast_speech *speech = NULL;
+       struct ast_datastore *datastore = NULL;
+       
+       datastore = ast_channel_datastore_find(chan, &speech_datastore, NULL);
+       if (datastore == NULL) {
+               return NULL;
+       }
+       speech = datastore->data;
+
+       return speech;
+}
+
+/*! \brief SpeechCreate() Dialplan Application */
+static int speech_create(struct ast_channel *chan, void *data)
+{
+       struct localuser *u = NULL;
+       struct ast_speech *speech = NULL;
+       struct ast_datastore *datastore = NULL;
+
+       LOCAL_USER_ADD(u);
+
+       /* Request a speech object */
+       speech = ast_speech_new(data, AST_FORMAT_SLINEAR);
+       if (speech == NULL) {
+               /* Not available */
+               pbx_builtin_setvar_helper(chan, "ERROR", "1");
+               LOCAL_USER_REMOVE(u);
+               return 0;
+       }
+
+       datastore = ast_channel_datastore_alloc(&speech_datastore, NULL);
+       if (datastore == NULL) {
+               ast_speech_destroy(speech);
+               pbx_builtin_setvar_helper(chan, "ERROR", "1");
+               LOCAL_USER_REMOVE(u);
+               return 0;
+       }
+       datastore->data = speech;
+       ast_channel_datastore_add(chan, datastore);
+
+       LOCAL_USER_REMOVE(u);
+
+       return 0;
+}
+
+/*! \brief SpeechDeactivateGrammar(Grammar Name) Dialplan Application */
+static int speech_deactivate(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;
+        }
+
+        /* Deactivate the grammar on the speech object */
+        res = ast_speech_grammar_deactivate(speech, data);
+
+        LOCAL_USER_REMOVE(u);
+
+        return res;
+}
+
+/*! \brief SpeechActivateGrammar(Grammar Name) Dialplan Application */
+static int speech_activate(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;
+       }
+
+       /* Activate the grammar on the speech object */
+       res = ast_speech_grammar_activate(speech, data);
+
+       LOCAL_USER_REMOVE(u);
+
+       return res;
+}
+
+/*! \brief SpeechStart() Dialplan Application */
+static int speech_start(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;
+       }
+
+       ast_speech_start(speech);
+
+       LOCAL_USER_REMOVE(u);
+
+       return res;
+}
+
+/*! \brief SpeechProcessingSound(Sound File) Dialplan Application */
+static int speech_processing_sound(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;
+        }
+
+       if (speech->processing_sound != NULL) {
+               free(speech->processing_sound);
+               speech->processing_sound = NULL;
+       }
+
+       speech->processing_sound = strdup(data);
+
+        LOCAL_USER_REMOVE(u);
+
+        return res;
+}
+
+/*! \brief Helper function used by speech_background to playback a soundfile */
+static int speech_streamfile(struct ast_channel *chan, const char *filename, const char *preflang)
+{
+        struct ast_filestream *fs;
+        struct ast_filestream *vfs=NULL;
+
+        fs = ast_openstream(chan, filename, preflang);
+        if (fs)
+                vfs = ast_openvstream(chan, filename, preflang);
+        if (fs){
+                if (ast_applystream(chan, fs))
+                        return -1;
+                if (vfs && ast_applystream(chan, vfs))
+                        return -1;
+                if (ast_playstream(fs))
+                        return -1;
+                if (vfs && ast_playstream(vfs))
+                        return -1;
+                return 0;
+        }
+        return -1;
+}
+
+/*! \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)))
+                return -1;
+
+       LOCAL_USER_ADD(u);
+
+       if (speech == NULL) {
+               LOCAL_USER_REMOVE(u);
+               return -1;
+       }
+
+       /* 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;
+       }
+
+       /* 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;
+       }
+
+       /* 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);
+       }
+
+       /* 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;
+                               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);
+                       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) {
+                                               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) {
+                                        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);
+                                if (speech->processing_sound != NULL) {
+                                       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");
+                       /* 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:
+                               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) {
+                                               time(&start);
+                                       }
+                                       /* 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);
+
+               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);
+       }
+
+       LOCAL_USER_REMOVE(u);
+
+       return 0;
+}
+
+/*! \brief SpeechDestroy() Dialplan Application */
+static int speech_destroy(struct ast_channel *chan, void *data)
+{
+       int res = 0;
+        struct localuser *u = NULL;
+       struct ast_speech *speech = find_speech(chan);
+       struct ast_datastore *datastore = NULL;
+
+       LOCAL_USER_ADD(u);
+
+       if (speech == NULL) {
+               LOCAL_USER_REMOVE(u);
+               return -1;
+       }
+
+       /* 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);
+       }
+
+       LOCAL_USER_REMOVE(u);
+
+       return res;
+}
+
+int unload_module(void)
+{
+       int res = 0;
+
+       res = ast_unregister_application("SpeechCreate");
+       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");
+
+       STANDARD_HANGUP_LOCALUSERS;
+
+       return res;     
+}
+
+int load_module(void)
+{
+       int res = 0;
+
+       res = ast_register_application("SpeechCreate", speech_create, "Create a Speech Structure", speechcreate_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);
+       
+       return res;
+}
+
+int reload(void)
+{
+       return 0;
+}
+
+const char *description(void)
+{
+       return tdesc;
+}
+
+int usecount(void)
+{
+       int res;
+
+       STANDARD_USECOUNT(res);
+
+       return res;
+}
+
+const char *key()
+{
+       return ASTERISK_GPL_KEY;
+}
index d554b3c..a0f15f2 100644 (file)
--- a/channel.c
+++ b/channel.c
@@ -653,6 +653,7 @@ struct ast_channel *ast_channel_alloc(int needqueue)
        headp = &tmp->varshead;
        ast_mutex_init(&tmp->lock);
        AST_LIST_HEAD_INIT_NOLOCK(headp);
+       AST_LIST_HEAD_INIT_NOLOCK(&tmp->datastores);
        strcpy(tmp->context, "default");
        ast_string_field_set(tmp, language, defaultlanguage);
        strcpy(tmp->exten, "s");
@@ -928,6 +929,7 @@ void ast_channel_free(struct ast_channel *chan)
        struct ast_var_t *vardata;
        struct ast_frame *f, *fp;
        struct varshead *headp;
+       struct ast_datastore *datastore = NULL;
        char name[AST_CHANNEL_NAME];
        
        headp=&chan->varshead;
@@ -981,6 +983,18 @@ void ast_channel_free(struct ast_channel *chan)
                ast_frfree(fp);
        }
        
+       /* Get rid of each of the data stores on the channel */
+       AST_LIST_LOCK(&chan->datastores);
+       AST_LIST_TRAVERSE_SAFE_BEGIN(&chan->datastores, datastore, list) {
+               /* Remove from the list */
+               AST_LIST_REMOVE_CURRENT(&chan->datastores, list);
+               /* Free the data store */
+               ast_channel_datastore_free(datastore);
+       }
+       AST_LIST_TRAVERSE_SAFE_END
+       AST_LIST_UNLOCK(&chan->datastores);
+       AST_LIST_HEAD_DESTROY(&chan->datastores);
+
        /* loop over the variables list, freeing all data and deleting list items */
        /* no need to lock the list, as the channel is already locked */
        
@@ -994,6 +1008,111 @@ void ast_channel_free(struct ast_channel *chan)
        ast_device_state_changed_literal(name);
 }
 
+struct ast_datastore *ast_channel_datastore_alloc(const struct ast_datastore_info *info, char *uid)
+{
+       struct ast_datastore *datastore = NULL;
+
+       /* Make sure we at least have type so we can identify this */
+       if (info == NULL) {
+               return NULL;
+       }
+
+       /* Allocate memory for datastore and clear it */
+       datastore = ast_calloc(1, sizeof(*datastore));
+       if (datastore == NULL) {
+               return NULL;
+       }
+
+       datastore->info = info;
+
+       if (uid != NULL) {
+               datastore->uid = ast_strdup(uid);
+       }
+
+       return datastore;
+}
+
+int ast_channel_datastore_free(struct ast_datastore *datastore)
+{
+       int res = 0;
+
+       /* Using the destroy function (if present) destroy the data */
+       if (datastore->info->destroy != NULL && datastore->data != NULL) {
+               datastore->info->destroy(datastore->data);
+               datastore->data = NULL;
+       }
+
+       /* Free allocated UID memory */
+       if (datastore->uid != NULL) {
+               free(datastore->uid);
+               datastore->uid = NULL;
+       }
+
+       /* Finally free memory used by ourselves */
+       free(datastore);
+       datastore = NULL;
+
+       return res;
+}
+
+int ast_channel_datastore_add(struct ast_channel *chan, struct ast_datastore *datastore)
+{
+       int res = 0;
+
+       AST_LIST_LOCK(&chan->datastores);
+       AST_LIST_INSERT_HEAD(&chan->datastores, datastore, list);
+       AST_LIST_UNLOCK(&chan->datastores);
+
+       return res;
+}
+
+int ast_channel_datastore_remove(struct ast_channel *chan, struct ast_datastore *datastore)
+{
+       struct ast_datastore *datastore2 = NULL;
+       int res = -1;
+
+       /* Find our position and remove ourselves */
+       AST_LIST_LOCK(&chan->datastores);
+       AST_LIST_TRAVERSE_SAFE_BEGIN(&chan->datastores, datastore2, list) {
+               if (datastore2 == datastore) {
+                       AST_LIST_REMOVE_CURRENT(&chan->datastores, list);
+                       res = 0;
+                       break;
+               }
+       }
+       AST_LIST_TRAVERSE_SAFE_END
+       AST_LIST_UNLOCK(&chan->datastores);
+
+       return res;
+}
+
+struct ast_datastore *ast_channel_datastore_find(struct ast_channel *chan, const struct ast_datastore_info *info, char *uid)
+{
+       struct ast_datastore *datastore = NULL;
+       
+       if (info == NULL)
+               return NULL;
+
+       AST_LIST_LOCK(&chan->datastores);
+       AST_LIST_TRAVERSE_SAFE_BEGIN(&chan->datastores, datastore, list) {
+               if (datastore->info == info) {
+                       if (uid != NULL && datastore->uid != NULL) {
+                               if (!strcasecmp(uid, datastore->uid)) {
+                                       /* Matched by type AND uid */
+                                       break;
+                               }
+                       } else {
+                               /* Matched by type at least */
+                               break;
+                       }
+               }
+       }
+       AST_LIST_TRAVERSE_SAFE_END
+       AST_LIST_UNLOCK(&chan->datastores);
+
+       return datastore;
+}
+
 int ast_channel_spy_add(struct ast_channel *chan, struct ast_channel_spy *spy)
 {
        if (!ast_test_flag(spy, CHANSPY_FORMAT_AUDIO)) {
@@ -3048,6 +3167,11 @@ int ast_do_masquerade(struct ast_channel *original)
                if (x != AST_GENERATOR_FD)
                        original->fds[x] = clone->fds[x];
        }
+       /* Move data stores over */
+       if (AST_LIST_FIRST(&clone->datastores))
+                AST_LIST_INSERT_TAIL(&original->datastores, AST_LIST_FIRST(&clone->datastores), list);
+       AST_LIST_HEAD_INIT_NOLOCK(&clone->datastores);
+
        clone_variables(original, clone);
        AST_LIST_HEAD_INIT_NOLOCK(&clone->varshead);
        /* Presense of ADSI capable CPE follows clone */
diff --git a/doc/datastores.txt b/doc/datastores.txt
new file mode 100644 (file)
index 0000000..a3aa9c2
--- /dev/null
@@ -0,0 +1,68 @@
+Asterisk Channel Data Stores
+============================
+
+* What is a data store?
+
+A data store is a way of storing complex data (such as a structure) on a channel
+so it can be retrieved at a later time by another application, or the same application.
+
+If the data store is not freed by said application though, a callback to a destroy function
+occurs which frees the memory used by the data in the data store so no memory loss occurs.
+
+* A datastore info structure
+static const struct example_datastore {
+       .type = "example",
+       .destroy = callback_destroy
+};
+
+This is a needed structure that contains information about a datastore, it's used by many API calls.
+
+* How do you create a data store?
+
+1. Use ast_channel_datastore_alloc function to return a pre-allocated structure
+   Ex: datastore = ast_channel_datastore_alloc(&example_datastore, "uid");
+   This function takes two arguments: (datastore info structure, uid)
+2. Attach data and destroy callback to pre-allocated structure.
+   Ex: datastore->data = mysillydata;
+       datastore->destroy = callback_destroy;
+3. Add datastore to the channel
+   Ex: ast_channel_datastore_add(chan, datastore);
+   This function takes two arguments: (pointer to channel, pointer to data store)
+
+Full Example:
+
+void callback_destroy(void *data)
+{
+       free(data);
+}
+
+struct ast_datastore *datastore = NULL;
+datastore = ast_channel_datastore_alloc(&example_datastore, NULL);
+datastore->data = mysillydata;
+datastore->destroy = callback_destroy;
+ast_channel_datastore_add(chan, datastore);
+
+NOTE: Because you're passing a pointer to a function in your module, you'll want to include
+this in your use count. When allocated increment, when destroyed decrement.
+
+* How do you remove a data store?
+
+1. Find the data store
+   Ex: datastore = ast_channel_datastore_find(chan, &example_datastore, NULL);
+   This function takes three arguments: (pointer to channel, datastore info structure, uid)
+2. Remove the data store from the channel
+   Ex: ast_channel_datastore_remove(chan, datastore);
+   This function takes two arguments: (pointer to channel, pointer to data store)
+3. If we want to now, free the memory or do stuff to the data on the data store
+   If we do then we will want to unset the data and callback
+   Ex: datastore->data = NULL;
+       datastore->destroy = NULL;
+4. Free the data store
+   Ex: ast_channel_datastore_free(datastore);
+   This function takes one argument: (pointer to data store)
+
+* How do you find a data store?
+
+1. Find the data store
+   Ex: datastore = ast_channel_datastore_find(chan, &example_datastore, NULL);
+   This function takes three arguments: (pointer to channel, datastore info structure, uid)
diff --git a/doc/speechrec.txt b/doc/speechrec.txt
new file mode 100644 (file)
index 0000000..08892dd
--- /dev/null
@@ -0,0 +1,204 @@
+Generic Speech Recognition API
+
+Dialplan Applications:
+
+The dialplan API is based around a single speech utilities application file, which exports many applications to be used for speech recognition. These include an application to prepare for speech recognition, activate a grammar, and play back a sound file while waiting for the person to speak. Using a combination of these applications you can easily make a dialplan use speech recognition without worrying about what speech recognition engine is being used.
+
+SpeechCreate(Engine Name):
+
+This application creates information to be used by all the other applications. It must be called before doing any speech recognition activities such as activating a grammar. It takes the engine name to use as the argument, if not specified the default engine will be used.
+
+If an error occurs are you are not able to create an object, the variable ERROR will be set to 1. You can then exit your speech recognition specific context and play back an error message, or resort to a DTMF based IVR.
+
+SpeechActivateGrammar(Grammar Name):
+
+This activates the specified grammar to be recognized by the engine. A grammar tells the speech recognition engine what to recognize, and how to portray it back to you in the dialplan. The grammar name is the only argument to this application.
+
+SpeechStart():
+
+Tell the speech recognition engine that it should start trying to get results from audio being fed to it. This has no arguments.
+
+SpeechBackground(Sound File|Timeout):
+
+This application plays a sound file and waits for the person to speak. Once they start speaking playback of the file stops, and silence is heard. Once they stop talking the processing sound is played to indicate the speech recognition engine is working. Once results are available the application returns and results (score and text) are available as dialplan variables. The first text and score are ${TEXT0} AND ${SCORE0} while the second are ${TEXT1} and ${SCORE1}. This may change in the future, however, to use a dialplan function instead of dialplan variables. Note it is possible to have more then one result. The first argument is the sound file and the second is the timeout. Note the timeout will only start once the sound file has stopped playing.
+
+SpeechDeactivateGrammar(Grammar Name):
+
+This deactivates the specified grammar so that it is no longer recognized. The only argument is the grammar name to deactivate.
+
+SpeechProcessingSound(Sound File):
+
+This changes the processing sound that SpeechBackground plays back when the speech recognition engine is processing and working to get results. It takes the sound file as the only argument.
+
+SpeechDestroy():
+
+This destroys the information used by all the other speech recognition applications. If you call this application but end up wanting to recognize more speech, you must call SpeechCreate again before calling any other application. It takes no arguments.
+
+Dialplan Flow:
+
+1. Create a speech recognition object using SpeechCreate()
+2. Activate your grammars using SpeechActivateGrammar(Grammar Name)
+3. Call SpeechStart() to indicate you are going to do speech recognition immediately
+4. Play back your audio and wait for recognition using SpeechBackground(Sound File|Timeout)
+5. Check the results and do things based on them
+6. Deactivate your grammars using SpeechDeactivateGrammar(Grammar Name)
+7. Destroy your speech recognition object using SpeechDestroy()
+
+Dialplan Examples:
+
+This is pretty cheeky in that it does not confirmation of results. As well the way the grammar is written it returns the person's extension instead of their name so we can just do a Goto based on the result text.
+
+company-directory.gram
+
+#ABNF 1.0;
+language en-US;
+mode voice;
+tag-format <lumenvox/1.0>;
+
+root $company_directory;
+
+$josh = Joshua Colp:"6066";
+$mark = Mark Spencer:"4569";
+$kevin = Kevin Fleming:"2567";
+
+$company_directory = ($josh | $mark | $kevin) { $ = parseInt($$) };
+
+dialplan logic
+
+[dial-by-name]
+exten => s,1,SpeechCreate()
+exten => s,2,SpeechActivateGrammar(company-directory)
+exten => s,3,SpeechStart()
+exten => s,4,SpeechBackground(who-would-you-like-to-dial)
+exten => s,5,SpeechDeactivateGrammar(company-directory)
+exten => s,6,SpeechDestroy()
+exten => s,7,Goto(internal-extensions-${TEXT0})
+
+Useful Dialplan Tidbits:
+
+A simple macro that can be used for confirm of a result. Requires some sound files. ARG1 is equal to the file to play back after "I heard..." is played.
+
+[macro-speech-confirm]
+exten => s,1,SpeechActivateGrammar(yes_no)
+exten => s,2,Set(OLDTEXT0=${TEXT0})
+exten => s,3,Playback(heard)
+exten => s,4,Playback(${ARG1})
+exten => s,5,SpeechStart()
+exten => s,6,SpeechBackground(correct)
+exten => s,7,Set(CONFIRM=${TEXT0})
+exten => s,8,GotoIf($["${TEXT0}" = "1"]?9:10)
+exten => s,9,Set(CONFIRM=yes)
+exten => s,10,Set(${TEXT0}=${OLDTEXT0})
+exten => s,11,SpeechDeactivateGrammar(yes_no)
+
+C API
+
+The module res_speech.so exports a C based API that any developer can use to speech recognize enable their application. The API gives greater control, but requires the developer to do more on their end in comparison to the dialplan speech utilities.
+
+For all API calls that return an integer value a non-zero value indicates an error has occured.
+
+Creating a speech structure:
+
+struct ast_speech *ast_speech_new(char *engine_name, int format)
+
+struct ast_speech *speech = ast_speech_new(NULL, AST_FORMAT_SLINEAR);
+
+This will create a new speech structure that will be returned to you. The speech recognition engine name is optional and if NULL the default one will be used. As well for now format should always be AST_FORMAT_SLINEAR.
+
+Activating a grammar:
+
+int ast_speech_grammar_activate(struct ast_speech *speech, char *grammar_name)
+
+res = ast_speech_grammar_activate(speech, "yes_no");
+
+This activates the specified grammar on the speech structure passed to it.
+
+Start recognizing audio:
+
+void ast_speech_start(struct ast_speech *speech)
+
+ast_speech_start(speech);
+
+This essentially tells the speech recognition engine that you will be feeding audio to it from then on. It MUST be called every time before you start feeding audio to the speech structure.
+
+Send audio to be recognized:
+
+int ast_speech_write(struct ast_speech *speech, void *data, int len)
+
+res = ast_speech_write(speech, fr->data, fr->datalen);
+
+This writes audio to the speech structure that will then be recognized. It must be written signed linear only at this time. In the future other formats may be supported.
+
+Checking for results:
+
+The way the generic speech recognition API is written is that the speech structure will undergo state changes to indicate progress of recognition. The states are outlined below:
+
+AST_SPEECH_STATE_NOT_READY - The speech structure is not ready to accept audio
+AST_SPEECH_STATE_READY - You may write audio to the speech structure
+AST_SPEECH_STATE_WAIT - No more audio should be written, and results will be available soon.
+AST_SPEECH_STATE_DONE - Results are available and the speech structure can only be used again by calling ast_speech_start
+
+It is up to you to monitor these states. Current state is available via a variable on the speech structure. (state)
+
+Knowing when to stop playback:
+
+If you are playing back a sound file to the user and you want to know when to stop play back because the individual started talking use the following.
+
+ast_test_flag(speech, AST_SPEECH_QUIET) - This will return a positive value when the person has started talking.
+
+Getting results:
+
+struct ast_speech_result *ast_speech_results_get(struct ast_speech *speech)
+
+struct ast_speech_result *results = ast_speech_results_get(speech);
+
+This will return a linked list of result structures. A result structure looks like the following:
+
+struct ast_speech_result {
+        /*! Recognized text */
+        char *text;
+        /*! Result score */
+        int score;
+        /*! Next result (may not always be present) */
+        struct ast_speech_result *next;
+};
+
+Freeing a set of results:
+
+int ast_speech_results_free(struct ast_speech_result *result)
+
+res = ast_speech_results_free(results);
+
+This will free all results on a linked list. Results MAY NOT be used as the memory will have been freed.
+
+Deactivating a grammar:
+
+int ast_speech_grammar_deactivate(struct ast_speech *speech, char *grammar_name)
+
+res = ast_speech_grammar_deactivate(speech, "yes_no");
+
+This deactivates the specified grammar on the speech structure.
+
+Destroying a speech structure:
+
+int ast_speech_destroy(struct ast_speech *speech)
+
+res = ast_speech_destroy(speech);
+
+This will free all associated memory with the speech structure and destroy it with the speech recognition engine.
+
+Loading a grammar on a speech structure:
+
+int ast_speech_grammar_load(struct ast_speech *speech, char *grammar_name, char *grammar)
+
+res = ast_speech_grammar_load(speech, "builtin:yes_no", "yes_no");
+
+Unloading a grammar on a speech structure:
+
+If you load a grammar on a speech structure it is preferred that you unload it as well, or you may cause a memory leak. Don't say I didn't warn you.
+
+int ast_speech_grammar_unload(struct ast_speech *speech, char *grammar_name)
+
+res = ast_speech_grammar_unload(speech, "yes_no");
+
+This unloads the specified grammar from the speech structure.
index 44f70aa..4d1826e 100644 (file)
@@ -144,6 +144,26 @@ struct ast_generator {
        int (*generate)(struct ast_channel *chan, void *data, int len, int samples);
 };
 
+/*! Structure for a data store type */
+struct ast_datastore_info {
+       /*! Type of data store */
+       const char *type;
+       /*! Destroy function */
+       void (*destroy)(void *data);
+};
+
+/*! Structure for a channel data store */
+struct ast_datastore {
+       /*! Unique data store identifier */
+       char *uid;
+       /*! Contained data */
+       void *data;
+       /*! Data store type information */
+       const struct ast_datastore_info *info;
+       /*! Used for easy linking */
+       AST_LIST_ENTRY(ast_datastore) list;
+};
+
 /*! Structure for all kinds of caller ID identifications */
 struct ast_callerid {
        /*! Malloc'd Dialed Number Identifier */
@@ -423,6 +443,9 @@ struct ast_channel {
        /*! Chan Spy stuff */
        struct ast_channel_spy_list *spies;
 
+       /*! Data stores on the channel */
+       AST_LIST_HEAD(datastores, ast_datastore) datastores;
+
        /*! For easy linking */
        AST_LIST_ENTRY(ast_channel) chan_list;
 };
@@ -553,6 +576,21 @@ enum channelreloadreason {
        CHANNEL_MANAGER_RELOAD,
 };
 
+/*! \brief Create a channel datastore structure */
+struct ast_datastore *ast_channel_datastore_alloc(const struct ast_datastore_info *info, char *uid);
+
+/*! \brief Free a channel datastore structure */
+int ast_channel_datastore_free(struct ast_datastore *datastore);
+
+/*! \brief Add a datastore to a channel */
+int ast_channel_datastore_add(struct ast_channel *chan, struct ast_datastore *datastore);
+
+/*! \brief Remove a datastore from a channel */
+int ast_channel_datastore_remove(struct ast_channel *chan, struct ast_datastore *datastore);
+
+/*! \brief Find a datastore on a channel */
+struct ast_datastore *ast_channel_datastore_find(struct ast_channel *chan, const struct ast_datastore_info *info, char *uid);
+
 /*! \brief Change the state of a channel */
 int ast_setstate(struct ast_channel *chan, int state);
 
diff --git a/include/asterisk/speech.h b/include/asterisk/speech.h
new file mode 100644 (file)
index 0000000..f6aa2b0
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2006, Digium, Inc.
+ *
+ * Joshua Colp <jcolp@digium.com>
+ *
+ * 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.
+ */
+
+/*! \file
+ * \brief Generic Speech Recognition API
+ */
+
+#ifndef _ASTERISK_SPEECH_H
+#define _ASTERISK_SPEECH_H
+
+#if defined(__cplusplus) || defined(c_plusplus)
+extern "C" {
+#endif
+
+/* Speech structure flags */
+#define AST_SPEECH_QUIET (1 << 0) /* Quiet down output... they are talking */
+
+/* Speech structure states - in order of expected change */
+#define AST_SPEECH_STATE_NOT_READY 0 /* Not ready to accept audio */
+#define AST_SPEECH_STATE_READY 1 /* Accepting audio */
+#define AST_SPEECH_STATE_WAIT 2 /* Wait for results to become available */
+#define AST_SPEECH_STATE_DONE 3 /* Processing is done */
+
+/* Speech structure */
+struct ast_speech {
+       /*! Structure lock */
+       ast_mutex_t lock;
+       /*! Set flags */
+       unsigned int flags;
+       /*! Processing sound (used when engine is processing audio and getting results) */
+       char *processing_sound;
+       /*! Current state of structure */
+       int state;
+       /*! Expected write format */
+       int format;
+       /*! Data for speech engine */
+       void *data;
+       /*! Pointer to the engine used by this speech structure */
+       struct ast_speech_engine *engine;
+};
+  
+/* Speech recognition engine structure */
+struct ast_speech_engine {
+       /*! Name of speech engine */
+       char *name;
+       /*! Set up the speech structure within the engine */
+       int (*new)(struct ast_speech *speech);
+       /*! Destroy any data set on the speech structure by the engine */
+       int (*destroy)(struct ast_speech *speech);
+       /*! Load a local grammar on the speech structure */
+       int (*load)(struct ast_speech *speech, char *grammar_name, char *grammar);
+       /*! Unload a local grammar */
+       int (*unload)(struct ast_speech *speech, char *grammar_name);
+       /*! Activate a loaded grammar */
+       int (*activate)(struct ast_speech *speech, char *grammar_name);
+       /*! Deactivate a loaded grammar */
+       int (*deactivate)(struct ast_speech *speech, char *grammar_name);
+       /*! Write audio to the speech engine */
+       int (*write)(struct ast_speech *speech, void *data, int len);
+       /*! Prepare engine to accept audio */
+       int (*start)(struct ast_speech *speech);
+       /*! Try to get results */
+       struct ast_speech_result *(*get)(struct ast_speech *speech);
+       /*! Accepted formats by the engine */
+       int formats;
+       AST_LIST_ENTRY(ast_speech_engine) list;
+};
+
+/* Result structure */
+struct ast_speech_result {
+       /*! Recognized text */
+       char *text;
+       /*! Result score */
+       int score;
+       /*! Next result (may not always be present) */
+       struct ast_speech_result *next;
+};
+
+/*! \brief Activate a grammar on a speech structure */
+int ast_speech_grammar_activate(struct ast_speech *speech, char *grammar_name);
+/*! \brief Deactivate a grammar on a speech structure */
+int ast_speech_grammar_deactivate(struct ast_speech *speech, char *grammar_name);
+/*! \brief Load a grammar on a speech structure (not globally) */
+int ast_speech_grammar_load(struct ast_speech *speech, char *grammar_name, char *grammar);
+/*! \brief Unload a grammar */
+int ast_speech_grammar_unload(struct ast_speech *speech, char *grammar_name);
+/*! \brief Get speech recognition results */
+struct ast_speech_result *ast_speech_results_get(struct ast_speech *speech);
+/*! \brief Free a set of results */
+int ast_speech_results_free(struct ast_speech_result *result);
+/*! \brief Indicate to the speech engine that audio is now going to start being written */
+void ast_speech_start(struct ast_speech *speech);
+/*! \brief Create a new speech structure */
+struct ast_speech *ast_speech_new(char *engine_name, int format);
+/*! \brief Destroy a speech structure */
+int ast_speech_destroy(struct ast_speech *speech);
+/*! \brief Write audio to the speech engine */
+int ast_speech_write(struct ast_speech *speech, void *data, int len);
+/*! \brief Change state of a speech structure */
+int ast_speech_change_state(struct ast_speech *speech, int state);
+/*! \brief Register a speech recognition engine */
+int ast_speech_register(struct ast_speech_engine *engine);
+/*! \brief Unregister a speech recognition engine */
+int ast_speech_unregister(char *engine_name);
+
+#if defined(__cplusplus) || defined(c_plusplus)
+}
+#endif
+
+#endif /* _ASTERISK_SPEECH_H */
diff --git a/res/res_speech.c b/res/res_speech.c
new file mode 100644 (file)
index 0000000..a185768
--- /dev/null
@@ -0,0 +1,346 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2006, Digium, Inc.
+ *
+ * Joshua Colp <jcolp@digium.com>
+ *
+ * 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.
+ */
+
+/*! \file
+ *
+ * \brief Generic Speech Recognition API
+ *
+ * \author Joshua Colp <jcolp@digium.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$");
+
+#include "asterisk/channel.h"
+#include "asterisk/module.h"
+#include "asterisk/lock.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/cli.h"
+#include "asterisk/term.h"
+#include "asterisk/options.h"
+#include "asterisk/speech.h"
+
+static char *tdesc = "Generic Speech Recognition API";
+
+static AST_LIST_HEAD_STATIC(engines, ast_speech_engine);
+static struct ast_speech_engine *default_engine = NULL;
+
+/*! \brief Find a speech recognition engine of specified name, if NULL then use the default one */
+static struct ast_speech_engine *find_engine(char *engine_name)
+{
+       struct ast_speech_engine *engine = NULL;
+
+       /* If no name is specified -- use the default engine */
+       if (engine_name == NULL || strlen(engine_name) == 0) {
+               return default_engine;
+       }
+
+       AST_LIST_LOCK(&engines);
+       AST_LIST_TRAVERSE_SAFE_BEGIN(&engines, engine, list) {
+               if (!strcasecmp(engine->name, engine_name)) {
+                       break;
+               }
+       }
+       AST_LIST_TRAVERSE_SAFE_END
+       AST_LIST_UNLOCK(&engines);
+
+       return engine;
+}
+
+/*! \brief Activate a loaded (either local or global) grammar */
+int ast_speech_grammar_activate(struct ast_speech *speech, char *grammar_name)
+{
+       int res = 0;
+
+       if (speech->engine->activate != NULL) {
+               res = speech->engine->activate(speech, grammar_name);
+       }
+
+       return res;
+}
+
+/*! \brief Deactivate a loaded grammar on a speech structure */
+int ast_speech_grammar_deactivate(struct ast_speech *speech, char *grammar_name)
+{
+       int res = 0;
+
+        if (speech->engine->deactivate != NULL) {
+                res = speech->engine->deactivate(speech, grammar_name);
+        }
+
+       return res;
+}
+
+/*! \brief Load a local grammar on a speech structure */
+int ast_speech_grammar_load(struct ast_speech *speech, char *grammar_name, char *grammar)
+{
+       int res = 0;
+
+       if (speech->engine->load != NULL) {
+               res = speech->engine->load(speech, grammar_name, grammar);
+       }
+
+       return res;
+}
+
+/*! \brief Unload a local grammar from a speech structure */
+int ast_speech_grammar_unload(struct ast_speech *speech, char *grammar_name)
+{
+        int res = 0;
+
+        if (speech->engine->unload != NULL) {
+                res = speech->engine->unload(speech, grammar_name);
+        }
+
+        return res;
+}
+
+/*! \brief Return the results of a recognition from the speech structure */
+struct ast_speech_result *ast_speech_results_get(struct ast_speech *speech)
+{
+       struct ast_speech_result *result = NULL;
+
+       if (speech->engine->get != NULL) {
+               result = speech->engine->get(speech);
+       }
+
+       return result;
+}
+
+/*! \brief Free a list of results */
+int ast_speech_results_free(struct ast_speech_result *result)
+{
+       struct ast_speech_result *current_result = result, *prev_result = NULL;
+       int res = 0;
+
+       while (current_result != NULL) {
+               prev_result = current_result;
+               /* Deallocate what we can */
+               if (current_result->text != NULL) {
+                       free(current_result->text);
+                       current_result->text = NULL;
+               }
+               /* Move on and then free ourselves */
+               current_result = current_result->next;
+               free(prev_result);
+               prev_result = NULL;
+       }
+
+       return res;
+}
+
+/*! \brief Start speech recognition on a speech structure */
+void ast_speech_start(struct ast_speech *speech)
+{
+       /* If the engine needs to start stuff up, do it */
+       if (speech->engine->start != NULL) {
+               speech->engine->start(speech);
+       }
+
+       return;
+}
+
+/*! \brief Write in signed linear audio to be recognized */
+int ast_speech_write(struct ast_speech *speech, void *data, int len)
+{
+       int res = 0;
+
+       /* Make sure the speech engine is ready to accept audio */
+       if (speech->state != AST_SPEECH_STATE_READY) {
+               return -1;
+       }
+
+       if (speech->engine->write != NULL) {
+               speech->engine->write(speech, data, len);
+       }
+
+       return res;
+}
+
+/*! \brief Create a new speech structure using the engine specified */
+struct ast_speech *ast_speech_new(char *engine_name, int format)
+{
+       struct ast_speech_engine *engine = NULL;
+       struct ast_speech *new_speech = NULL;
+
+       /* Try to find the speech recognition engine that was requested */
+       engine = find_engine(engine_name);
+       if (engine == NULL) {
+               /* Invalid engine or no engine available */
+               return NULL;
+       }
+
+       /* Allocate our own speech structure, and try to allocate a structure from the engine too */
+       new_speech = ast_calloc(1, sizeof(*new_speech));
+       if (new_speech == NULL) {
+               /* Ran out of memory while trying to allocate some for a speech structure */
+               return NULL;
+       }
+
+       /* Initialize the lock */
+       ast_mutex_init(&new_speech->lock);
+
+       /* Copy over our engine pointer */
+       new_speech->engine = engine;
+
+       /* We are not ready to accept audio yet */
+       ast_speech_change_state(new_speech, AST_SPEECH_STATE_NOT_READY);
+
+       /* Pass ourselves to the engine so they can set us up some more */
+       engine->new(new_speech);
+
+       return new_speech;
+}
+
+/*! \brief Destroy a speech structure */
+int ast_speech_destroy(struct ast_speech *speech)
+{
+       int res = 0;
+
+       /* Call our engine so we are destroyed properly */
+       speech->engine->destroy(speech);
+
+       /* Deinitialize the lock */
+       ast_mutex_destroy(&speech->lock);
+
+       /* If a processing sound is set - free the memory used by it */
+       if (speech->processing_sound != NULL) {
+               free(speech->processing_sound);
+               speech->processing_sound = NULL;
+       }
+
+       /* Aloha we are done */
+       free(speech);
+       speech = NULL;
+
+       return res;
+}
+
+/*! \brief Change state of a speech structure */
+int ast_speech_change_state(struct ast_speech *speech, int state)
+{
+       int res = 0;
+
+       speech->state = state;
+
+       return res;
+}
+
+/*! \brief Register a speech recognition engine */
+int ast_speech_register(struct ast_speech_engine *engine)
+{
+       struct ast_speech_engine *existing_engine = NULL;
+       int res = 0;
+
+       existing_engine = find_engine(engine->name);
+       if (existing_engine != NULL) {
+               /* Engine already loaded */
+               return -1;
+       }
+
+       if (option_verbose > 1)
+               ast_verbose(VERBOSE_PREFIX_2 "Registered speech recognition engine '%s'\n", engine->name);
+
+       /* Add to the engine linked list and make default if needed */
+       AST_LIST_LOCK(&engines);
+       AST_LIST_INSERT_HEAD(&engines, engine, list);
+       if (default_engine == NULL) {
+               default_engine = engine;
+               if (option_verbose > 1)
+                       ast_verbose(VERBOSE_PREFIX_2 "Made '%s' the default speech recognition engine\n", engine->name);
+       }
+       AST_LIST_UNLOCK(&engines);
+
+       return res;
+}
+
+/*! \brief Unregister a speech recognition engine */
+int ast_speech_unregister(char *engine_name)
+{
+       struct ast_speech_engine *engine = NULL;
+       int res = -1;
+
+       if (engine_name == NULL) {
+               return res;
+       }
+
+       AST_LIST_LOCK(&engines);
+       AST_LIST_TRAVERSE_SAFE_BEGIN(&engines, engine, list) {
+               if (!strcasecmp(engine->name, engine_name)) {
+                       /* We have our engine... removed it */
+                       AST_LIST_REMOVE_CURRENT(&engines, list);
+                       /* If this was the default engine, we need to pick a new one */
+                       if (default_engine == engine) {
+                               default_engine = AST_LIST_FIRST(&engines);
+                       }
+                       if (option_verbose > 1)
+                               ast_verbose(VERBOSE_PREFIX_2 "Unregistered speech recognition engine '%s'\n", engine_name);
+                       /* All went well */
+                       res = 0;
+                       break;
+               }
+       }
+       AST_LIST_TRAVERSE_SAFE_END
+       AST_LIST_UNLOCK(&engines);
+
+       return res;
+}
+
+int unload_module(void)
+{
+       /* We can not be unloaded */
+       return -1;
+}
+
+int load_module(void)
+{
+       int res = 0;
+
+       /* Initialize our list of engines */
+       AST_LIST_HEAD_INIT_NOLOCK(&engines);
+
+       return res;
+}
+
+int reload(void)
+{
+       return 0;
+}
+
+const char *description(void)
+{
+       return tdesc;
+}
+
+int usecount(void)
+{
+       int res = 0;
+
+       return res;
+}
+
+const char *key()
+{
+       return ASTERISK_GPL_KEY;
+}