Updates to speech recognition API and dialplan utilities. Moved to using dialplan...
[asterisk/asterisk.git] / apps / app_speech_utils.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2006, Digium, Inc.
5  *
6  * Joshua Colp <jcolp@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief Speech Recognition Utility Applications
22  *
23  * \author Joshua Colp <jcolp@digium.com>
24  *
25  * \ingroup applications
26  */
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <unistd.h>
31 #include <string.h>
32
33 #include "asterisk.h"
34
35 ASTERISK_FILE_VERSION(__FILE__, "$Revision$");
36
37 #include "asterisk/file.h"
38 #include "asterisk/logger.h"
39 #include "asterisk/channel.h"
40 #include "asterisk/pbx.h"
41 #include "asterisk/module.h"
42 #include "asterisk/lock.h"
43 #include "asterisk/app.h"
44 #include "asterisk/speech.h"
45
46 static char *tdesc = "Dialplan Speech Applications";
47
48 LOCAL_USER_DECL;
49
50 /* Descriptions for each application */
51 static char *speechcreate_descrip =
52 "SpeechCreate(engine name)\n"
53 "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"
54 "It takes the engine name to use as the argument, if not specified the default engine will be used.\n";
55
56 static char *speechactivategrammar_descrip =
57 "SpeechActivateGrammar(Grammar Name)\n"
58 "This activates the specified grammar to be recognized by the engine. A grammar tells the speech recognition engine what to recognize, \n"
59         "and how to portray it back to you in the dialplan. The grammar name is the only argument to this application.\n";
60
61 static char *speechstart_descrip =
62 "SpeechStart()\n"
63         "Tell the speech recognition engine that it should start trying to get results from audio being fed to it. This has no arguments.\n";
64
65 static char *speechbackground_descrip =
66 "SpeechBackground(Sound File|Timeout)\n"
67 "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"
68 "Once they stop talking the processing sound is played to indicate the speech recognition engine is working.\n"
69 "Once results are available the application returns and results (score and text) are available as dialplan variables.\n"
70 "The first text and score are ${TEXT0} AND ${SCORE0} while the second are ${TEXT1} and ${SCORE1}.\n"
71 "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"
72         "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";
73
74 static char *speechdeactivategrammar_descrip =
75 "SpeechDeactivateGrammar(Grammar Name)\n"
76         "This deactivates the specified grammar so that it is no longer recognized. The only argument is the grammar name to deactivate.\n";
77
78 static char *speechprocessingsound_descrip =
79 "SpeechProcessingSound(Sound File)\n"
80 "This changes the processing sound that SpeechBackground plays back when the speech recognition engine is processing and working to get results.\n"
81         "It takes the sound file as the only argument.\n";
82
83 static char *speechdestroy_descrip =
84 "SpeechDestroy()\n"
85 "This destroys the information used by all the other speech recognition applications.\n"
86 "If you call this application but end up wanting to recognize more speech, you must call SpeechCreate\n"
87         "again before calling any other application. It takes no arguments.\n";
88
89 static char *speechload_descrip =
90 "SpeechLoadGrammar(Grammar Name|Path)\n"
91 "Load a grammar only on the channel, not globally.\n"
92 "It takes the grammar name as first argument and path as second.\n";
93
94 static char *speechunload_descrip =
95 "SpeechUnloadGrammar(Grammar Name)\n"
96 "Unload a grammar. It takes the grammar name as the only argument.\n";
97
98 /*! \brief Helper function used by datastores to destroy the speech structure upon hangup */
99 static void destroy_callback(void *data)
100 {
101         struct ast_speech *speech = (struct ast_speech*)data;
102
103         if (speech == NULL) {
104                 return;
105         }
106
107         /* Deallocate now */
108         ast_speech_destroy(speech);
109
110         return;
111 }
112
113 /*! \brief Static structure for datastore information */
114 static const struct ast_datastore_info speech_datastore = {
115         .type = "speech",
116         .destroy = destroy_callback
117 };
118
119 /*! \brief Helper function used to find the speech structure attached to a channel */
120 static struct ast_speech *find_speech(struct ast_channel *chan)
121 {
122         struct ast_speech *speech = NULL;
123         struct ast_datastore *datastore = NULL;
124         
125         datastore = ast_channel_datastore_find(chan, &speech_datastore, NULL);
126         if (datastore == NULL) {
127                 return NULL;
128         }
129         speech = datastore->data;
130
131         return speech;
132 }
133
134 /* Helper function to find a specific speech recognition result by number */
135 static struct ast_speech_result *find_result(struct ast_speech_result *results, int num)
136 {
137         struct ast_speech_result *result = NULL;
138         int i = 0;
139
140         result = results;
141         while (result) {
142                 if (i == num)
143                         break;
144                 i++;
145                 result = result->next;
146         }
147
148         return result;
149 }
150
151 /*! \brief SPEECH_SCORE() Dialplan Function */
152 static int speech_score(struct ast_channel *chan, char *cmd, char *data,
153                        char *buf, size_t len)
154 {
155         struct ast_speech_result *result = NULL;
156         struct ast_speech *speech = find_speech(chan);
157         char tmp[128] = "";
158
159         if (data == NULL || speech == NULL || !(result = find_result(speech->results, atoi(data))))
160                 return -1;
161         
162         snprintf(tmp, sizeof(tmp), "%d", result->score);
163         
164         ast_copy_string(buf, tmp, len);
165
166         return 0;
167 }
168
169 static struct ast_custom_function speech_score_function = {
170         .name = "SPEECH_SCORE",
171         .synopsis = "Gets the confidence score of a result.\n",
172         .syntax = "SPEECH_SCORE(result number)",
173         .desc =
174         "Gets the confidence score of a result.\n",
175         .read = speech_score,
176         .write = NULL,
177 };
178
179 /*! \brief SPEECH_TEXT() Dialplan Function */
180 static int speech_text(struct ast_channel *chan, char *cmd, char *data,
181                         char *buf, size_t len)
182 {
183         struct ast_speech_result *result = NULL;
184         struct ast_speech *speech = find_speech(chan);
185
186         if (data == NULL || speech == NULL || !(result = find_result(speech->results, atoi(data))))
187                 return -1;
188
189         if (result->text != NULL)
190                 ast_copy_string(buf, result->text, len);
191
192         return 0;
193 }
194
195 static struct ast_custom_function speech_text_function = {
196         .name = "SPEECH_TEXT",
197         .synopsis = "Gets the recognized text of a result.\n",
198         .syntax = "SPEECH_TEXT(result number)",
199         .desc =
200         "Gets the recognized text of a result.\n",
201         .read = speech_text,
202         .write = NULL,
203 };
204
205 /*! \brief SPEECH_GRAMMAR() Dialplan Function */
206 static int speech_grammar(struct ast_channel *chan, char *cmd, char *data,
207                         char *buf, size_t len)
208 {
209         struct ast_speech_result *result = NULL;
210         struct ast_speech *speech = find_speech(chan);
211
212         if (data == NULL || speech == NULL || !(result = find_result(speech->results, atoi(data))))
213                 return -1;
214
215         if (result->grammar != NULL)
216                 ast_copy_string(buf, result->grammar, len);
217
218         return 0;
219 }
220
221 static struct ast_custom_function speech_grammar_function = {
222         .name = "SPEECH_GRAMMAR",
223         .synopsis = "Gets the matched grammar of a result if available.",
224         .syntax = "SPEECH_GRAMMAR(result number)",
225         .desc =
226         "Gets the matched grammar of a result if available.\n",
227         .read = speech_grammar,
228         .write = NULL,
229 };
230
231 /*! \brief SPEECH() Dialplan Function */
232 static int speech_read(struct ast_channel *chan, char *cmd, char *data,
233                         char *buf, size_t len)
234 {
235         int results = 0;
236         struct ast_speech_result *result = NULL;
237         struct ast_speech *speech = find_speech(chan);
238         char tmp[128] = "";
239
240         /* Now go for the various options */
241         if (!strcasecmp(data, "status")) {
242                 if (speech != NULL)
243                         ast_copy_string(buf, "1", len);
244                 else
245                         ast_copy_string(buf, "0", len);
246                 return 0;
247         }
248
249         /* Make sure we have a speech structure for everything else */
250         if (speech == NULL) {
251                 return -1;
252         }
253
254         /* Check to see if they are checking for silence */
255         if (!strcasecmp(data, "spoke")) {
256                 if (ast_test_flag(speech, AST_SPEECH_SPOKE))
257                         ast_copy_string(buf, "1", len);
258                 else
259                         ast_copy_string(buf, "0", len);
260         } else if (!strcasecmp(data, "results")) {
261                 /* Count number of results */
262                 result = speech->results;
263                 while (result) {
264                         results++;
265                         result = result->next;
266                 }
267                 snprintf(tmp, sizeof(tmp), "%d", results);
268                 ast_copy_string(buf, tmp, len);
269         }
270
271         return 0;
272 }
273
274 static struct ast_custom_function speech_function = {
275         .name = "SPEECH",
276         .synopsis = "Gets information about speech recognition results.",
277         .syntax = "SPEECH(argument)",
278         .desc =
279         "Gets information about speech recognition results.\n"
280         "status:   Returns 1 upon speech object existing, or 0 if not\n"
281         "spoke:  Returns 1 if spoker spoke, or 0 if not\n"
282         "results:  Returns number of results that were recognized\n",
283         .read = speech_read,
284         .write = NULL,
285 };
286
287
288
289 /*! \brief SpeechCreate() Dialplan Application */
290 static int speech_create(struct ast_channel *chan, void *data)
291 {
292         struct localuser *u = NULL;
293         struct ast_speech *speech = NULL;
294         struct ast_datastore *datastore = NULL;
295
296         LOCAL_USER_ADD(u);
297
298         /* Request a speech object */
299         speech = ast_speech_new(data, AST_FORMAT_SLINEAR);
300         if (speech == NULL) {
301                 /* Not available */
302                 pbx_builtin_setvar_helper(chan, "ERROR", "1");
303                 LOCAL_USER_REMOVE(u);
304                 return 0;
305         }
306
307         datastore = ast_channel_datastore_alloc(&speech_datastore, NULL);
308         if (datastore == NULL) {
309                 ast_speech_destroy(speech);
310                 pbx_builtin_setvar_helper(chan, "ERROR", "1");
311                 LOCAL_USER_REMOVE(u);
312                 return 0;
313         }
314         datastore->data = speech;
315         ast_channel_datastore_add(chan, datastore);
316
317         LOCAL_USER_REMOVE(u);
318
319         return 0;
320 }
321
322 /*! \brief SpeechLoadGrammar(Grammar Name|Path) Dialplan Application */
323 static int speech_load(struct ast_channel *chan, void *data)
324 {
325         int res = 0, argc = 0;
326         struct localuser *u = NULL;
327         struct ast_speech *speech = find_speech(chan);
328         char *argv[2], *args = NULL, *name = NULL, *path = NULL;
329
330         if (!(args = ast_strdupa(data)))
331                 return -1;
332
333         LOCAL_USER_ADD(u);
334
335         if (speech == NULL) {
336                 LOCAL_USER_REMOVE(u);
337                 return -1;
338         }
339
340         /* Parse out arguments */
341         argc = ast_app_separate_args(args, '|', argv, sizeof(argv) / sizeof(argv[0]));
342         if (argc != 2) {
343                 LOCAL_USER_REMOVE(u);
344                 return -1;
345         }
346         name = argv[0];
347         path = argv[1];
348
349         /* Load the grammar locally on the object */
350         res = ast_speech_grammar_load(speech, name, path);
351
352         LOCAL_USER_REMOVE(u);
353
354         return res;
355 }
356
357 /*! \brief SpeechUnloadGrammar(Grammar Name) Dialplan Application */
358 static int speech_unload(struct ast_channel *chan, void *data)
359 {
360         int res = 0;
361         struct localuser *u = NULL;
362         struct ast_speech *speech = find_speech(chan);
363
364         LOCAL_USER_ADD(u);
365
366         if (speech == NULL) {
367                 LOCAL_USER_REMOVE(u);
368                 return -1;
369         }
370
371         /* Unload the grammar */
372         res = ast_speech_grammar_unload(speech, data);
373
374         LOCAL_USER_REMOVE(u);
375
376         return res;
377 }
378
379 /*! \brief SpeechDeactivateGrammar(Grammar Name) Dialplan Application */
380 static int speech_deactivate(struct ast_channel *chan, void *data)
381 {
382         int res = 0;
383         struct localuser *u = NULL;
384         struct ast_speech *speech = find_speech(chan);
385
386         LOCAL_USER_ADD(u);
387
388         if (speech == NULL) {
389                 LOCAL_USER_REMOVE(u);
390                 return -1;
391         }
392
393         /* Deactivate the grammar on the speech object */
394         res = ast_speech_grammar_deactivate(speech, data);
395
396         LOCAL_USER_REMOVE(u);
397
398         return res;
399 }
400
401 /*! \brief SpeechActivateGrammar(Grammar Name) Dialplan Application */
402 static int speech_activate(struct ast_channel *chan, void *data)
403 {
404         int res = 0;
405         struct localuser *u = NULL;
406         struct ast_speech *speech = find_speech(chan);
407
408         LOCAL_USER_ADD(u);
409
410         if (speech == NULL) {
411                 LOCAL_USER_REMOVE(u);
412                 return -1;
413         }
414
415         /* Activate the grammar on the speech object */
416         res = ast_speech_grammar_activate(speech, data);
417
418         LOCAL_USER_REMOVE(u);
419
420         return res;
421 }
422
423 /*! \brief SpeechStart() Dialplan Application */
424 static int speech_start(struct ast_channel *chan, void *data)
425 {
426         int res = 0;
427         struct localuser *u = NULL;
428         struct ast_speech *speech = find_speech(chan);
429
430         LOCAL_USER_ADD(u);
431
432         if (speech == NULL) {
433                 LOCAL_USER_REMOVE(u);
434                 return -1;
435         }
436
437         ast_speech_start(speech);
438
439         LOCAL_USER_REMOVE(u);
440
441         return res;
442 }
443
444 /*! \brief SpeechProcessingSound(Sound File) Dialplan Application */
445 static int speech_processing_sound(struct ast_channel *chan, void *data)
446 {
447         int res = 0;
448         struct localuser *u = NULL;
449         struct ast_speech *speech = find_speech(chan);
450
451         LOCAL_USER_ADD(u);
452
453         if (speech == NULL) {
454                 LOCAL_USER_REMOVE(u);
455                 return -1;
456         }
457
458         if (speech->processing_sound != NULL) {
459                 free(speech->processing_sound);
460                 speech->processing_sound = NULL;
461         }
462
463         speech->processing_sound = strdup(data);
464
465         LOCAL_USER_REMOVE(u);
466
467         return res;
468 }
469
470 /*! \brief Helper function used by speech_background to playback a soundfile */
471 static int speech_streamfile(struct ast_channel *chan, const char *filename, const char *preflang)
472 {
473         struct ast_filestream *fs;
474         struct ast_filestream *vfs=NULL;
475
476         fs = ast_openstream(chan, filename, preflang);
477         if (fs)
478                 vfs = ast_openvstream(chan, filename, preflang);
479         if (fs){
480                 if (ast_applystream(chan, fs))
481                         return -1;
482                 if (vfs && ast_applystream(chan, vfs))
483                         return -1;
484                 if (ast_playstream(fs))
485                         return -1;
486                 if (vfs && ast_playstream(vfs))
487                         return -1;
488                 return 0;
489         }
490         return -1;
491 }
492
493 /*! \brief SpeechBackground(Sound File|Timeout) Dialplan Application */
494 static int speech_background(struct ast_channel *chan, void *data)
495 {
496         unsigned int timeout = 0;
497         int res = 0, done = 0, argc = 0, started = 0;
498         struct localuser *u = NULL;
499         struct ast_speech *speech = find_speech(chan);
500         struct ast_frame *f = NULL;
501         int oldreadformat = AST_FORMAT_SLINEAR;
502         char dtmf[AST_MAX_EXTENSION] = "";
503         time_t start, current;
504         struct ast_datastore *datastore = NULL;
505         char *argv[2], *args = NULL, *filename = NULL, tmp[2] = "";
506
507         if (!(args = ast_strdupa(data)))
508                 return -1;
509
510         LOCAL_USER_ADD(u);
511
512         if (speech == NULL) {
513                 LOCAL_USER_REMOVE(u);
514                 return -1;
515         }
516
517         /* Record old read format */
518         oldreadformat = chan->readformat;
519
520         /* Change read format to be signed linear */
521         if (ast_set_read_format(chan, AST_FORMAT_SLINEAR)) {
522                 LOCAL_USER_REMOVE(u);
523                 return -1;
524         }
525
526         /* Parse out options */
527         argc = ast_app_separate_args(args, '|', argv, sizeof(argv) / sizeof(argv[0]));
528         if (argc > 0) {
529                 /* Yay sound file */
530                 filename = argv[0];
531                 if (argv[1] != NULL)
532                         timeout = atoi(argv[1]);
533         }
534
535         /* Start streaming the file if possible and specified */
536         if (filename != NULL && ast_streamfile(chan, filename, chan->language)) {
537                 /* An error occured while streaming */
538                 ast_set_read_format(chan, oldreadformat);
539                 LOCAL_USER_REMOVE(u);
540                 return -1;
541         }
542
543         /* Before we go into waiting for stuff... make sure the structure is ready, if not - start it again */
544         if (speech->state == AST_SPEECH_STATE_NOT_READY || speech->state == AST_SPEECH_STATE_DONE) {
545                 ast_speech_change_state(speech, AST_SPEECH_STATE_NOT_READY);
546                 ast_speech_start(speech);
547         }
548
549         /* Okay it's streaming so go into a loop grabbing frames! */
550         while (done == 0) {
551                 /* Run scheduled stuff */
552                 ast_sched_runq(chan->sched);
553
554                 /* Yay scheduling */
555                 res = ast_sched_wait(chan->sched);
556                 if (res < 0) {
557                         res = 1000;
558                 }
559
560                 /* If there is a frame waiting, get it - if not - oh well */
561                 if (ast_waitfor(chan, res) > 0) {
562                         f = ast_read(chan);
563                         if (f == NULL) {
564                                 /* The channel has hung up most likely */
565                                 done = 3;
566                                 break;
567                         }
568                 }
569
570                 /* Do timeout check (shared between audio/dtmf) */
571                 if (started == 1) {
572                         time(&current);
573                         if ((current-start) >= timeout) {
574                                 done = 1;
575                                 break;
576                         }
577                 }
578
579                 /* Do checks on speech structure to see if it's changed */
580                 ast_mutex_lock(&speech->lock);
581                 if (ast_test_flag(speech, AST_SPEECH_QUIET) && chan->stream != NULL) {
582                         ast_stopstream(chan);
583                         ast_clear_flag(speech, AST_SPEECH_QUIET);
584                 }
585                 /* Check state so we can see what to do */
586                 switch (speech->state) {
587                 case AST_SPEECH_STATE_READY:
588                         /* If audio playback has stopped do a check for timeout purposes */
589                         if (chan->streamid == -1 && chan->timingfunc == NULL)
590                                 ast_stopstream(chan);
591                         if (chan->stream == NULL && timeout > 0 && started == 0) {
592                                 time(&start);
593                                 started = 1;
594                         }
595                         /* Deal with audio frames if present */
596                         if (f != NULL && f->frametype == AST_FRAME_VOICE) {
597                                 ast_speech_write(speech, f->data, f->datalen);
598                         }
599                         break;
600                 case AST_SPEECH_STATE_WAIT:
601                         /* Cue up waiting sound if not already playing */
602                         if (chan->stream == NULL) {
603                                 if (speech->processing_sound != NULL) {
604                                         if (strlen(speech->processing_sound) > 0 && strcasecmp(speech->processing_sound,"none")) {
605                                                 speech_streamfile(chan, speech->processing_sound, chan->language);
606                                         }
607                                 }
608                         } else if (chan->streamid == -1 && chan->timingfunc == NULL) {
609                                 ast_stopstream(chan);
610                                 if (speech->processing_sound != NULL) {
611                                         if (strlen(speech->processing_sound) > 0 && strcasecmp(speech->processing_sound,"none")) {
612                                                 speech_streamfile(chan, speech->processing_sound, chan->language);
613                                         }
614                                 }
615                         }
616                         break;
617                 case AST_SPEECH_STATE_DONE:
618                         /* Copy to speech structure the results, if available */
619                         speech->results = ast_speech_results_get(speech);
620                         /* Now that we are done... let's switch back to not ready state */
621                         ast_speech_change_state(speech, AST_SPEECH_STATE_NOT_READY);
622                         /* Break out of our background too */
623                         done = 1;
624                         /* Stop audio playback */
625                         if (chan->stream != NULL) {
626                                 ast_stopstream(chan);
627                         }
628                         break;
629                 default:
630                         break;
631                 }
632                 ast_mutex_unlock(&speech->lock);
633
634                 /* Deal with other frame types */
635                 if (f != NULL) {
636                         /* Free the frame we received */
637                         switch (f->frametype) {
638                         case AST_FRAME_DTMF:
639                                 if (f->subclass == '#') {
640                                         done = 1;
641                                 } else {
642                                         if (chan->stream != NULL) {
643                                                 ast_stopstream(chan);
644                                                 /* Change timeout to be 5 seconds for DTMF input */
645                                                 timeout = 5;
646                                                 time(&start);
647                                                 started = 1;
648                                         }
649                                         snprintf(tmp, sizeof(tmp), "%c", f->subclass);
650                                         strncat(dtmf, tmp, sizeof(dtmf));
651                                 }
652                                 break;
653                         case AST_FRAME_CONTROL:
654                                 switch (f->subclass) {
655                                 case AST_CONTROL_HANGUP:
656                                         /* Since they hung up we should destroy the speech structure */
657                                         done = 3;
658                                 default:
659                                         break;
660                                 }
661                         default:
662                                 break;
663                         }
664                         ast_frfree(f);
665                         f = NULL;
666                 }
667         }
668
669         if (strlen(dtmf) > 0 && speech->results == NULL) {
670                 /* We sort of make a results entry */
671                 speech->results = ast_calloc(1, sizeof(*speech->results));
672                 if (speech->results != NULL) {
673                         speech->results->score = 1000;
674                         speech->results->text = strdup(dtmf);
675                         speech->results->grammar = strdup("dtmf");
676                 }
677         }
678
679         /* See if it was because they hung up */
680         if (done == 3) {
681                 /* Destroy speech structure */
682                 ast_speech_destroy(speech);
683                 datastore = ast_channel_datastore_find(chan, &speech_datastore, NULL);
684                 if (datastore != NULL) {
685                         ast_channel_datastore_remove(chan, datastore);
686                 }
687         } else {
688                 /* Channel is okay so restore read format */
689                 ast_set_read_format(chan, oldreadformat);
690         }
691
692         LOCAL_USER_REMOVE(u);
693
694         return 0;
695 }
696
697
698 /*! \brief SpeechDestroy() Dialplan Application */
699 static int speech_destroy(struct ast_channel *chan, void *data)
700 {
701         int res = 0;
702         struct localuser *u = NULL;
703         struct ast_speech *speech = find_speech(chan);
704         struct ast_datastore *datastore = NULL;
705
706         LOCAL_USER_ADD(u);
707
708         if (speech == NULL) {
709                 LOCAL_USER_REMOVE(u);
710                 return -1;
711         }
712
713         /* Destroy speech structure */
714         ast_speech_destroy(speech);
715
716         datastore = ast_channel_datastore_find(chan, &speech_datastore, NULL);
717         if (datastore != NULL) {
718                 ast_channel_datastore_remove(chan, datastore);
719         }
720
721         LOCAL_USER_REMOVE(u);
722
723         return res;
724 }
725
726 int unload_module(void)
727 {
728         int res = 0;
729
730         res = ast_unregister_application("SpeechCreate");
731         res |= ast_unregister_application("SpeechLoadGrammar");
732         res |= ast_unregister_application("SpeechUnloadGrammar");
733         res |= ast_unregister_application("SpeechActivateGrammar");
734         res |= ast_unregister_application("SpeechDeactivateGrammar");
735         res |= ast_unregister_application("SpeechStart");
736         res |= ast_unregister_application("SpeechBackground");
737         res |= ast_unregister_application("SpeechDestroy");
738         res |= ast_unregister_application("SpeechProcessingSound");
739         res |= ast_custom_function_unregister(&speech_function);
740         res |= ast_custom_function_unregister(&speech_score_function);
741         res |= ast_custom_function_unregister(&speech_text_function);
742         res |= ast_custom_function_unregister(&speech_grammar_function);
743
744         STANDARD_HANGUP_LOCALUSERS;
745
746         return res;     
747 }
748
749 int load_module(void)
750 {
751         int res = 0;
752
753         res = ast_register_application("SpeechCreate", speech_create, "Create a Speech Structure", speechcreate_descrip);
754         res |= ast_register_application("SpeechLoadGrammar", speech_load, "Load a Grammar", speechload_descrip);
755         res |= ast_register_application("SpeechUnloadGrammar", speech_unload, "Unload a Grammar", speechunload_descrip);
756         res |= ast_register_application("SpeechActivateGrammar", speech_activate, "Activate a Grammar", speechactivategrammar_descrip);
757         res |= ast_register_application("SpeechDeactivateGrammar", speech_deactivate, "Deactivate a Grammar", speechdeactivategrammar_descrip);
758         res |= ast_register_application("SpeechStart", speech_start, "Start recognizing", speechstart_descrip);
759         res |= ast_register_application("SpeechBackground", speech_background, "Play a sound file and wait for speech to be recognized", speechbackground_descrip);
760         res |= ast_register_application("SpeechDestroy", speech_destroy, "End speech recognition", speechdestroy_descrip);
761         res |= ast_register_application("SpeechProcessingSound", speech_processing_sound, "Change background processing sound", speechprocessingsound_descrip);
762         res |= ast_custom_function_register(&speech_function);
763         res |= ast_custom_function_register(&speech_score_function);
764         res |= ast_custom_function_register(&speech_text_function);
765         res |= ast_custom_function_register(&speech_grammar_function);
766
767         return res;
768 }
769
770 int reload(void)
771 {
772         return 0;
773 }
774
775 const char *description(void)
776 {
777         return tdesc;
778 }
779
780 int usecount(void)
781 {
782         int res;
783
784         STANDARD_USECOUNT(res);
785
786         return res;
787 }
788
789 const char *key()
790 {
791         return ASTERISK_GPL_KEY;
792 }