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