a38fed12bd6771e93d846babd15bb91ba4130334
[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 using dialplan functions.\n"
70 "The first text and score are ${SPEECH_TEXT(0)} AND ${SPEECH_SCORE(0)} while the second are ${SPEECH_TEXT(1)} and ${SPEECH_SCORE(1)}.\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.",
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.",
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         args = ast_strdupa(data);
331
332         LOCAL_USER_ADD(u);
333
334         if (speech == NULL) {
335                 LOCAL_USER_REMOVE(u);
336                 return -1;
337         }
338
339         /* Parse out arguments */
340         argc = ast_app_separate_args(args, '|', argv, sizeof(argv) / sizeof(argv[0]));
341         if (argc != 2) {
342                 LOCAL_USER_REMOVE(u);
343                 return -1;
344         }
345         name = argv[0];
346         path = argv[1];
347
348         /* Load the grammar locally on the object */
349         res = ast_speech_grammar_load(speech, name, path);
350
351         LOCAL_USER_REMOVE(u);
352
353         return res;
354 }
355
356 /*! \brief SpeechUnloadGrammar(Grammar Name) Dialplan Application */
357 static int speech_unload(struct ast_channel *chan, void *data)
358 {
359         int res = 0;
360         struct localuser *u = NULL;
361         struct ast_speech *speech = find_speech(chan);
362
363         LOCAL_USER_ADD(u);
364
365         if (speech == NULL) {
366                 LOCAL_USER_REMOVE(u);
367                 return -1;
368         }
369
370         /* Unload the grammar */
371         res = ast_speech_grammar_unload(speech, data);
372
373         LOCAL_USER_REMOVE(u);
374
375         return res;
376 }
377
378 /*! \brief SpeechDeactivateGrammar(Grammar Name) Dialplan Application */
379 static int speech_deactivate(struct ast_channel *chan, void *data)
380 {
381         int res = 0;
382         struct localuser *u = NULL;
383         struct ast_speech *speech = find_speech(chan);
384
385         LOCAL_USER_ADD(u);
386
387         if (speech == NULL) {
388                 LOCAL_USER_REMOVE(u);
389                 return -1;
390         }
391
392         /* Deactivate the grammar on the speech object */
393         res = ast_speech_grammar_deactivate(speech, data);
394
395         LOCAL_USER_REMOVE(u);
396
397         return res;
398 }
399
400 /*! \brief SpeechActivateGrammar(Grammar Name) Dialplan Application */
401 static int speech_activate(struct ast_channel *chan, void *data)
402 {
403         int res = 0;
404         struct localuser *u = NULL;
405         struct ast_speech *speech = find_speech(chan);
406
407         LOCAL_USER_ADD(u);
408
409         if (speech == NULL) {
410                 LOCAL_USER_REMOVE(u);
411                 return -1;
412         }
413
414         /* Activate the grammar on the speech object */
415         res = ast_speech_grammar_activate(speech, data);
416
417         LOCAL_USER_REMOVE(u);
418
419         return res;
420 }
421
422 /*! \brief SpeechStart() Dialplan Application */
423 static int speech_start(struct ast_channel *chan, void *data)
424 {
425         int res = 0;
426         struct localuser *u = NULL;
427         struct ast_speech *speech = find_speech(chan);
428
429         LOCAL_USER_ADD(u);
430
431         if (speech == NULL) {
432                 LOCAL_USER_REMOVE(u);
433                 return -1;
434         }
435
436         ast_speech_start(speech);
437
438         LOCAL_USER_REMOVE(u);
439
440         return res;
441 }
442
443 /*! \brief SpeechProcessingSound(Sound File) Dialplan Application */
444 static int speech_processing_sound(struct ast_channel *chan, void *data)
445 {
446         int res = 0;
447         struct localuser *u = NULL;
448         struct ast_speech *speech = find_speech(chan);
449
450         LOCAL_USER_ADD(u);
451
452         if (speech == NULL) {
453                 LOCAL_USER_REMOVE(u);
454                 return -1;
455         }
456
457         if (speech->processing_sound != NULL) {
458                 free(speech->processing_sound);
459                 speech->processing_sound = NULL;
460         }
461
462         speech->processing_sound = strdup(data);
463
464         LOCAL_USER_REMOVE(u);
465
466         return res;
467 }
468
469 /*! \brief Helper function used by speech_background to playback a soundfile */
470 static int speech_streamfile(struct ast_channel *chan, const char *filename, const char *preflang)
471 {
472         struct ast_filestream *fs;
473         struct ast_filestream *vfs=NULL;
474
475         fs = ast_openstream(chan, filename, preflang);
476         if (fs)
477                 vfs = ast_openvstream(chan, filename, preflang);
478         if (fs){
479                 if (ast_applystream(chan, fs))
480                         return -1;
481                 if (vfs && ast_applystream(chan, vfs))
482                         return -1;
483                 if (ast_playstream(fs))
484                         return -1;
485                 if (vfs && ast_playstream(vfs))
486                         return -1;
487                 return 0;
488         }
489         return -1;
490 }
491
492 /*! \brief SpeechBackground(Sound File|Timeout) Dialplan Application */
493 static int speech_background(struct ast_channel *chan, void *data)
494 {
495         unsigned int timeout = 0;
496         int res = 0, done = 0, argc = 0, started = 0;
497         struct localuser *u = NULL;
498         struct ast_speech *speech = find_speech(chan);
499         struct ast_frame *f = NULL;
500         int oldreadformat = AST_FORMAT_SLINEAR;
501         char dtmf[AST_MAX_EXTENSION] = "";
502         time_t start, current;
503         struct ast_datastore *datastore = NULL;
504         char *argv[2], *args = NULL, *filename = NULL, tmp[2] = "";
505
506         args = ast_strdupa(data);
507
508         LOCAL_USER_ADD(u);
509
510         if (speech == NULL) {
511                 LOCAL_USER_REMOVE(u);
512                 return -1;
513         }
514
515         /* If channel is not already answered, then answer it */
516         if (chan->_state != AST_STATE_UP && ast_answer(chan)) {
517                 LOCAL_USER_REMOVE(u);
518                 return -1;
519         }
520
521         /* Record old read format */
522         oldreadformat = chan->readformat;
523
524         /* Change read format to be signed linear */
525         if (ast_set_read_format(chan, AST_FORMAT_SLINEAR)) {
526                 LOCAL_USER_REMOVE(u);
527                 return -1;
528         }
529
530         /* Parse out options */
531         argc = ast_app_separate_args(args, '|', argv, sizeof(argv) / sizeof(argv[0]));
532         if (argc > 0) {
533                 /* Yay sound file */
534                 filename = argv[0];
535                 if (argv[1] != NULL)
536                         timeout = atoi(argv[1]);
537         }
538
539         /* Start streaming the file if possible and specified */
540         if (filename != NULL && ast_streamfile(chan, filename, chan->language)) {
541                 /* An error occured while streaming */
542                 ast_set_read_format(chan, oldreadformat);
543                 LOCAL_USER_REMOVE(u);
544                 return -1;
545         }
546
547         /* Before we go into waiting for stuff... make sure the structure is ready, if not - start it again */
548         if (speech->state == AST_SPEECH_STATE_NOT_READY || speech->state == AST_SPEECH_STATE_DONE) {
549                 ast_speech_change_state(speech, AST_SPEECH_STATE_NOT_READY);
550                 ast_speech_start(speech);
551         }
552
553         /* Okay it's streaming so go into a loop grabbing frames! */
554         while (done == 0) {
555                 /* Run scheduled stuff */
556                 ast_sched_runq(chan->sched);
557
558                 /* Yay scheduling */
559                 res = ast_sched_wait(chan->sched);
560                 if (res < 0) {
561                         res = 1000;
562                 }
563
564                 /* If there is a frame waiting, get it - if not - oh well */
565                 if (ast_waitfor(chan, res) > 0) {
566                         f = ast_read(chan);
567                         if (f == NULL) {
568                                 /* The channel has hung up most likely */
569                                 done = 3;
570                                 break;
571                         }
572                 }
573
574                 /* Do timeout check (shared between audio/dtmf) */
575                 if (started == 1) {
576                         time(&current);
577                         if ((current-start) >= timeout) {
578                                 done = 1;
579                                 break;
580                         }
581                 }
582
583                 /* Do checks on speech structure to see if it's changed */
584                 ast_mutex_lock(&speech->lock);
585                 if (ast_test_flag(speech, AST_SPEECH_QUIET) && chan->stream != NULL) {
586                         ast_stopstream(chan);
587                         ast_clear_flag(speech, AST_SPEECH_QUIET);
588                 }
589                 /* Check state so we can see what to do */
590                 switch (speech->state) {
591                 case AST_SPEECH_STATE_READY:
592                         /* If audio playback has stopped do a check for timeout purposes */
593                         if (chan->streamid == -1 && chan->timingfunc == NULL)
594                                 ast_stopstream(chan);
595                         if (chan->stream == NULL && timeout > 0 && started == 0) {
596                                 time(&start);
597                                 started = 1;
598                         }
599                         /* Deal with audio frames if present */
600                         if (f != NULL && f->frametype == AST_FRAME_VOICE) {
601                                 ast_speech_write(speech, f->data, f->datalen);
602                         }
603                         break;
604                 case AST_SPEECH_STATE_WAIT:
605                         /* Cue up waiting sound if not already playing */
606                         if (chan->stream == NULL) {
607                                 if (speech->processing_sound != NULL) {
608                                         if (strlen(speech->processing_sound) > 0 && strcasecmp(speech->processing_sound,"none")) {
609                                                 speech_streamfile(chan, speech->processing_sound, chan->language);
610                                         }
611                                 }
612                         } else if (chan->streamid == -1 && chan->timingfunc == NULL) {
613                                 ast_stopstream(chan);
614                                 if (speech->processing_sound != NULL) {
615                                         if (strlen(speech->processing_sound) > 0 && strcasecmp(speech->processing_sound,"none")) {
616                                                 speech_streamfile(chan, speech->processing_sound, chan->language);
617                                         }
618                                 }
619                         }
620                         break;
621                 case AST_SPEECH_STATE_DONE:
622                         /* Copy to speech structure the results, if available */
623                         speech->results = ast_speech_results_get(speech);
624                         /* Now that we are done... let's switch back to not ready state */
625                         ast_speech_change_state(speech, AST_SPEECH_STATE_NOT_READY);
626                         /* Break out of our background too */
627                         done = 1;
628                         /* Stop audio playback */
629                         if (chan->stream != NULL) {
630                                 ast_stopstream(chan);
631                         }
632                         break;
633                 default:
634                         break;
635                 }
636                 ast_mutex_unlock(&speech->lock);
637
638                 /* Deal with other frame types */
639                 if (f != NULL) {
640                         /* Free the frame we received */
641                         switch (f->frametype) {
642                         case AST_FRAME_DTMF:
643                                 if (f->subclass == '#') {
644                                         done = 1;
645                                 } else {
646                                         if (chan->stream != NULL) {
647                                                 ast_stopstream(chan);
648                                                 /* Change timeout to be 5 seconds for DTMF input */
649                                                 timeout = 5;
650                                                 time(&start);
651                                                 started = 1;
652                                         }
653                                         snprintf(tmp, sizeof(tmp), "%c", f->subclass);
654                                         strncat(dtmf, tmp, sizeof(dtmf));
655                                 }
656                                 break;
657                         case AST_FRAME_CONTROL:
658                                 switch (f->subclass) {
659                                 case AST_CONTROL_HANGUP:
660                                         /* Since they hung up we should destroy the speech structure */
661                                         done = 3;
662                                 default:
663                                         break;
664                                 }
665                         default:
666                                 break;
667                         }
668                         ast_frfree(f);
669                         f = NULL;
670                 }
671         }
672
673         if (strlen(dtmf) > 0 && speech->results == NULL) {
674                 /* We sort of make a results entry */
675                 speech->results = ast_calloc(1, sizeof(*speech->results));
676                 if (speech->results != NULL) {
677                         speech->results->score = 1000;
678                         speech->results->text = strdup(dtmf);
679                         speech->results->grammar = strdup("dtmf");
680                 }
681         }
682
683         /* See if it was because they hung up */
684         if (done == 3) {
685                 /* Destroy speech structure */
686                 ast_speech_destroy(speech);
687                 datastore = ast_channel_datastore_find(chan, &speech_datastore, NULL);
688                 if (datastore != NULL) {
689                         ast_channel_datastore_remove(chan, datastore);
690                 }
691         } else {
692                 /* Channel is okay so restore read format */
693                 ast_set_read_format(chan, oldreadformat);
694         }
695
696         LOCAL_USER_REMOVE(u);
697
698         return 0;
699 }
700
701
702 /*! \brief SpeechDestroy() Dialplan Application */
703 static int speech_destroy(struct ast_channel *chan, void *data)
704 {
705         int res = 0;
706         struct localuser *u = NULL;
707         struct ast_speech *speech = find_speech(chan);
708         struct ast_datastore *datastore = NULL;
709
710         LOCAL_USER_ADD(u);
711
712         if (speech == NULL) {
713                 LOCAL_USER_REMOVE(u);
714                 return -1;
715         }
716
717         /* Destroy speech structure */
718         ast_speech_destroy(speech);
719
720         datastore = ast_channel_datastore_find(chan, &speech_datastore, NULL);
721         if (datastore != NULL) {
722                 ast_channel_datastore_remove(chan, datastore);
723         }
724
725         LOCAL_USER_REMOVE(u);
726
727         return res;
728 }
729
730 static int unload_module(void *mod)
731 {
732         int res = 0;
733
734         res = ast_unregister_application("SpeechCreate");
735         res |= ast_unregister_application("SpeechLoadGrammar");
736         res |= ast_unregister_application("SpeechUnloadGrammar");
737         res |= ast_unregister_application("SpeechActivateGrammar");
738         res |= ast_unregister_application("SpeechDeactivateGrammar");
739         res |= ast_unregister_application("SpeechStart");
740         res |= ast_unregister_application("SpeechBackground");
741         res |= ast_unregister_application("SpeechDestroy");
742         res |= ast_unregister_application("SpeechProcessingSound");
743         res |= ast_custom_function_unregister(&speech_function);
744         res |= ast_custom_function_unregister(&speech_score_function);
745         res |= ast_custom_function_unregister(&speech_text_function);
746         res |= ast_custom_function_unregister(&speech_grammar_function);
747
748         STANDARD_HANGUP_LOCALUSERS;
749
750         return res;     
751 }
752
753 static int load_module(void *mod)
754 {
755         int res = 0;
756
757         res = ast_register_application("SpeechCreate", speech_create, "Create a Speech Structure", speechcreate_descrip);
758         res |= ast_register_application("SpeechLoadGrammar", speech_load, "Load a Grammar", speechload_descrip);
759         res |= ast_register_application("SpeechUnloadGrammar", speech_unload, "Unload a Grammar", speechunload_descrip);
760         res |= ast_register_application("SpeechActivateGrammar", speech_activate, "Activate a Grammar", speechactivategrammar_descrip);
761         res |= ast_register_application("SpeechDeactivateGrammar", speech_deactivate, "Deactivate a Grammar", speechdeactivategrammar_descrip);
762         res |= ast_register_application("SpeechStart", speech_start, "Start recognizing", speechstart_descrip);
763         res |= ast_register_application("SpeechBackground", speech_background, "Play a sound file and wait for speech to be recognized", speechbackground_descrip);
764         res |= ast_register_application("SpeechDestroy", speech_destroy, "End speech recognition", speechdestroy_descrip);
765         res |= ast_register_application("SpeechProcessingSound", speech_processing_sound, "Change background processing sound", speechprocessingsound_descrip);
766         res |= ast_custom_function_register(&speech_function);
767         res |= ast_custom_function_register(&speech_score_function);
768         res |= ast_custom_function_register(&speech_text_function);
769         res |= ast_custom_function_register(&speech_grammar_function);
770
771         return res;
772 }
773
774 static const char *description(void)
775 {
776         return tdesc;
777 }
778
779 static const char *key(void)
780 {
781         return ASTERISK_GPL_KEY;
782 }
783
784 STD_MOD1;