b8dd634fdd65df9400236372bfb32915a8a1a868
[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 (!result)
137                 return NULL;
138
139         if ((tmp = strchr(result_num, '/'))) {
140                 *tmp++ = '\0';
141                 nbest_num = atoi(result_num);
142                 wanted_num = atoi(tmp);
143         } else {
144                 wanted_num = atoi(result_num);
145         }
146
147         do {
148                 if (result->nbest_num != nbest_num)
149                         continue;
150                 if (i == wanted_num)
151                         break;
152                 i++;
153         } while ((result = AST_LIST_NEXT(result, list)));
154
155         return result;
156 }
157
158 /*! \brief SPEECH_SCORE() Dialplan Function */
159 static int speech_score(struct ast_channel *chan, const char *cmd, char *data,
160                        char *buf, size_t len)
161 {
162         struct ast_speech_result *result = NULL;
163         struct ast_speech *speech = find_speech(chan);
164         char tmp[128] = "";
165
166         if (data == NULL || speech == NULL || !(result = find_result(speech->results, data)))
167                 return -1;
168         
169         snprintf(tmp, sizeof(tmp), "%d", result->score);
170         
171         ast_copy_string(buf, tmp, len);
172
173         return 0;
174 }
175
176 static struct ast_custom_function speech_score_function = {
177         .name = "SPEECH_SCORE",
178         .synopsis = "Gets the confidence score of a result.",
179         .syntax = "SPEECH_SCORE([nbest number/]result number)",
180         .desc =
181         "Gets the confidence score of a result.\n",
182         .read = speech_score,
183         .write = NULL,
184 };
185
186 /*! \brief SPEECH_TEXT() Dialplan Function */
187 static int speech_text(struct ast_channel *chan, const char *cmd, char *data,
188                         char *buf, size_t len)
189 {
190         struct ast_speech_result *result = NULL;
191         struct ast_speech *speech = find_speech(chan);
192
193         if (data == NULL || speech == NULL || !(result = find_result(speech->results, data)))
194                 return -1;
195
196         if (result->text != NULL)
197                 ast_copy_string(buf, result->text, len);
198
199         return 0;
200 }
201
202 static struct ast_custom_function speech_text_function = {
203         .name = "SPEECH_TEXT",
204         .synopsis = "Gets the recognized text of a result.",
205         .syntax = "SPEECH_TEXT([nbest number/]result number)",
206         .desc =
207         "Gets the recognized text of a result.\n",
208         .read = speech_text,
209         .write = NULL,
210 };
211
212 /*! \brief SPEECH_GRAMMAR() Dialplan Function */
213 static int speech_grammar(struct ast_channel *chan, const char *cmd, char *data,
214                         char *buf, size_t len)
215 {
216         struct ast_speech_result *result = NULL;
217         struct ast_speech *speech = find_speech(chan);
218
219         if (data == NULL || speech == NULL || !(result = find_result(speech->results, data)))
220                 return -1;
221
222         if (result->grammar != NULL)
223                 ast_copy_string(buf, result->grammar, len);
224
225         return 0;
226 }
227
228 static struct ast_custom_function speech_grammar_function = {
229         .name = "SPEECH_GRAMMAR",
230         .synopsis = "Gets the matched grammar of a result if available.",
231         .syntax = "SPEECH_GRAMMAR([nbest number/]result number)",
232         .desc =
233         "Gets the matched grammar of a result if available.\n",
234         .read = speech_grammar,
235         .write = NULL,
236 };
237
238 /*! \brief SPEECH_ENGINE() Dialplan Function */
239 static int speech_engine_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
240 {
241         struct ast_speech *speech = find_speech(chan);
242
243         if (data == NULL || speech == NULL)
244                 return -1;
245
246         ast_speech_change(speech, data, value);
247
248         return 0;
249 }
250
251 static struct ast_custom_function speech_engine_function = {
252         .name = "SPEECH_ENGINE",
253         .synopsis = "Change a speech engine specific attribute.",
254         .syntax = "SPEECH_ENGINE(name)=value",
255         .desc =
256         "Changes a speech engine specific attribute.\n",
257         .read = NULL,
258         .write = speech_engine_write,
259 };
260
261 /*! \brief SPEECH_RESULTS_TYPE() Dialplan Function */
262 static int speech_results_type_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
263 {
264         struct ast_speech *speech = find_speech(chan);
265
266         if (data == NULL || speech == NULL)
267                 return -1;
268
269         if (!strcasecmp(value, "normal"))
270                 ast_speech_change_results_type(speech, AST_SPEECH_RESULTS_TYPE_NORMAL);
271         else if (!strcasecmp(value, "nbest"))
272                 ast_speech_change_results_type(speech, AST_SPEECH_RESULTS_TYPE_NBEST);
273
274         return 0;
275 }
276
277 static struct ast_custom_function speech_results_type_function = {
278         .name = "SPEECH_RESULTS_TYPE",
279         .synopsis = "Sets the type of results that will be returned.",
280         .syntax = "SPEECH_RESULTS_TYPE()=results type",
281         .desc =
282         "Sets the type of results that will be returned. Valid options are normal or nbest.",
283         .read = NULL,
284         .write = speech_results_type_write,
285 };
286
287 /*! \brief SPEECH() Dialplan Function */
288 static int speech_read(struct ast_channel *chan, const char *cmd, char *data,
289                         char *buf, size_t len)
290 {
291         int results = 0;
292         struct ast_speech_result *result = NULL;
293         struct ast_speech *speech = find_speech(chan);
294         char tmp[128] = "";
295
296         /* Now go for the various options */
297         if (!strcasecmp(data, "status")) {
298                 if (speech != NULL)
299                         ast_copy_string(buf, "1", len);
300                 else
301                         ast_copy_string(buf, "0", len);
302                 return 0;
303         }
304
305         /* Make sure we have a speech structure for everything else */
306         if (speech == NULL) {
307                 return -1;
308         }
309
310         /* Check to see if they are checking for silence */
311         if (!strcasecmp(data, "spoke")) {
312                 if (ast_test_flag(speech, AST_SPEECH_SPOKE))
313                         ast_copy_string(buf, "1", len);
314                 else
315                         ast_copy_string(buf, "0", len);
316         } else if (!strcasecmp(data, "results")) {
317                 /* Count number of results */
318                 for (result = speech->results; result; result = AST_LIST_NEXT(result, list))
319                         results++;
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, chan->nativeformats);
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                 ast_free(speech->processing_sound);
512                 speech->processing_sound = NULL;
513         }
514
515         speech->processing_sound = ast_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         ast_playstream(fs);
534
535         return 0;
536 }
537
538 /*! \brief SpeechBackground(Sound File|Timeout) Dialplan Application */
539 static int speech_background(struct ast_channel *chan, void *data)
540 {
541         unsigned int timeout = 0;
542         int res = 0, done = 0, argc = 0, started = 0, quieted = 0, max_dtmf_len = 0;
543         struct ast_module_user *u = NULL;
544         struct ast_speech *speech = find_speech(chan);
545         struct ast_frame *f = NULL;
546         int oldreadformat = AST_FORMAT_SLINEAR;
547         char dtmf[AST_MAX_EXTENSION] = "";
548         time_t start, current;
549         struct ast_datastore *datastore = NULL;
550         char *argv[2], *args = NULL, *filename_tmp = NULL, *filename = NULL, tmp[2] = "";
551         const char *tmp2 = NULL;
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, speech->format)) {
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         /* See if the maximum DTMF length variable is set... we use a variable in case they want to carry it through their entire dialplan */
590         if ((tmp2 = pbx_builtin_getvar_helper(chan, "SPEECH_DTMF_MAXLEN")) && !ast_strlen_zero(tmp2))
591                 max_dtmf_len = atoi(tmp2);
592
593         /* Before we go into waiting for stuff... make sure the structure is ready, if not - start it again */
594         if (speech->state == AST_SPEECH_STATE_NOT_READY || speech->state == AST_SPEECH_STATE_DONE) {
595                 ast_speech_change_state(speech, AST_SPEECH_STATE_NOT_READY);
596                 ast_speech_start(speech);
597         }
598
599         /* Ensure no streams are currently running */
600         ast_stopstream(chan);
601
602         /* Okay it's streaming so go into a loop grabbing frames! */
603         while (done == 0) {
604                 /* If the filename is null and stream is not running, start up a new sound file */
605                 if (!quieted && (chan->streamid == -1 && chan->timingfunc == NULL) && (filename = strsep(&filename_tmp, "&"))) {
606                         /* Discard old stream information */
607                         ast_stopstream(chan);
608                         /* Start new stream */
609                         speech_streamfile(chan, filename, chan->language);
610                 }
611
612                 /* Run scheduled stuff */
613                 ast_sched_runq(chan->sched);
614
615                 /* Yay scheduling */
616                 res = ast_sched_wait(chan->sched);
617                 if (res < 0) {
618                         res = 1000;
619                 }
620
621                 /* If there is a frame waiting, get it - if not - oh well */
622                 if (ast_waitfor(chan, res) > 0) {
623                         f = ast_read(chan);
624                         if (f == NULL) {
625                                 /* The channel has hung up most likely */
626                                 done = 3;
627                                 break;
628                         }
629                 }
630
631                 /* Do timeout check (shared between audio/dtmf) */
632                 if ((!quieted || strlen(dtmf)) && started == 1) {
633                         time(&current);
634                         if ((current-start) >= timeout) {
635                                 done = 1;
636                                 if (f)
637                                         ast_frfree(f);
638                                 break;
639                         }
640                 }
641
642                 /* Do checks on speech structure to see if it's changed */
643                 ast_mutex_lock(&speech->lock);
644                 if (ast_test_flag(speech, AST_SPEECH_QUIET)) {
645                         if (chan->stream)
646                                 ast_stopstream(chan);
647                         ast_clear_flag(speech, AST_SPEECH_QUIET);
648                         quieted = 1;
649                 }
650                 /* Check state so we can see what to do */
651                 switch (speech->state) {
652                 case AST_SPEECH_STATE_READY:
653                         /* If audio playback has stopped do a check for timeout purposes */
654                         if (chan->streamid == -1 && chan->timingfunc == NULL)
655                                 ast_stopstream(chan);
656                         if (!quieted && chan->stream == NULL && timeout && started == 0 && !filename_tmp) {
657                                 if (timeout == -1) {
658                                         done = 1;
659                                         if (f)
660                                                 ast_frfree(f);
661                                         break;
662                                 }
663                                 time(&start);
664                                 started = 1;
665                         }
666                         /* Write audio frame out to speech engine if no DTMF has been received */
667                         if (!strlen(dtmf) && f != NULL && f->frametype == AST_FRAME_VOICE) {
668                                 ast_speech_write(speech, f->data, f->datalen);
669                         }
670                         break;
671                 case AST_SPEECH_STATE_WAIT:
672                         /* Cue up waiting sound if not already playing */
673                         if (!strlen(dtmf)) {
674                                 if (chan->stream == NULL) {
675                                         if (speech->processing_sound != NULL) {
676                                                 if (strlen(speech->processing_sound) > 0 && strcasecmp(speech->processing_sound,"none")) {
677                                                         speech_streamfile(chan, speech->processing_sound, chan->language);
678                                                 }
679                                         }
680                                 } else if (chan->streamid == -1 && chan->timingfunc == NULL) {
681                                         ast_stopstream(chan);
682                                         if (speech->processing_sound != NULL) {
683                                                 if (strlen(speech->processing_sound) > 0 && strcasecmp(speech->processing_sound,"none")) {
684                                                         speech_streamfile(chan, speech->processing_sound, chan->language);
685                                                 }
686                                         }
687                                 }
688                         }
689                         break;
690                 case AST_SPEECH_STATE_DONE:
691                         /* Now that we are done... let's switch back to not ready state */
692                         ast_speech_change_state(speech, AST_SPEECH_STATE_NOT_READY);
693                         if (!strlen(dtmf)) {
694                                 /* Copy to speech structure the results, if available */
695                                 speech->results = ast_speech_results_get(speech);
696                                 /* Break out of our background too */
697                                 done = 1;
698                                 /* Stop audio playback */
699                                 if (chan->stream != NULL) {
700                                         ast_stopstream(chan);
701                                 }
702                         }
703                         break;
704                 default:
705                         break;
706                 }
707                 ast_mutex_unlock(&speech->lock);
708
709                 /* Deal with other frame types */
710                 if (f != NULL) {
711                         /* Free the frame we received */
712                         switch (f->frametype) {
713                         case AST_FRAME_DTMF:
714                                 if (f->subclass == '#') {
715                                         done = 1;
716                                 } else {
717                                         if (chan->stream != NULL) {
718                                                 ast_stopstream(chan);
719                                         }
720                                         if (!started) {
721                                                 /* Change timeout to be 5 seconds for DTMF input */
722                                                 timeout = (chan->pbx && chan->pbx->dtimeout) ? chan->pbx->dtimeout : 5;
723                                                 started = 1;
724                                         }
725                                         time(&start);
726                                         snprintf(tmp, sizeof(tmp), "%c", f->subclass);
727                                         strncat(dtmf, tmp, sizeof(dtmf));
728                                         /* If the maximum length of the DTMF has been reached, stop now */
729                                         if (max_dtmf_len && strlen(dtmf) == max_dtmf_len)
730                                                 done = 1;
731                                 }
732                                 break;
733                         case AST_FRAME_CONTROL:
734                                 switch (f->subclass) {
735                                 case AST_CONTROL_HANGUP:
736                                         /* Since they hung up we should destroy the speech structure */
737                                         done = 3;
738                                 default:
739                                         break;
740                                 }
741                         default:
742                                 break;
743                         }
744                         ast_frfree(f);
745                         f = NULL;
746                 }
747         }
748
749         if (strlen(dtmf)) {
750                 /* We sort of make a results entry */
751                 speech->results = ast_calloc(1, sizeof(*speech->results));
752                 if (speech->results != NULL) {
753                         speech->results->score = 1000;
754                         speech->results->text = ast_strdup(dtmf);
755                         speech->results->grammar = ast_strdup("dtmf");
756                 }
757         }
758
759         /* See if it was because they hung up */
760         if (done == 3) {
761                 /* Destroy speech structure */
762                 ast_speech_destroy(speech);
763                 datastore = ast_channel_datastore_find(chan, &speech_datastore, NULL);
764                 if (datastore != NULL) {
765                         ast_channel_datastore_remove(chan, datastore);
766                 }
767         } else {
768                 /* Channel is okay so restore read format */
769                 ast_set_read_format(chan, oldreadformat);
770         }
771
772         ast_module_user_remove(u);
773
774         return 0;
775 }
776
777
778 /*! \brief SpeechDestroy() Dialplan Application */
779 static int speech_destroy(struct ast_channel *chan, void *data)
780 {
781         int res = 0;
782         struct ast_module_user *u = NULL;
783         struct ast_speech *speech = find_speech(chan);
784         struct ast_datastore *datastore = NULL;
785
786         u = ast_module_user_add(chan);
787
788         if (speech == NULL) {
789                 ast_module_user_remove(u);
790                 return -1;
791         }
792
793         /* Destroy speech structure */
794         ast_speech_destroy(speech);
795
796         datastore = ast_channel_datastore_find(chan, &speech_datastore, NULL);
797         if (datastore != NULL) {
798                 ast_channel_datastore_remove(chan, datastore);
799         }
800
801         ast_module_user_remove(u);
802
803         return res;
804 }
805
806 static int unload_module(void)
807 {
808         int res = 0;
809
810         res = ast_unregister_application("SpeechCreate");
811         res |= ast_unregister_application("SpeechLoadGrammar");
812         res |= ast_unregister_application("SpeechUnloadGrammar");
813         res |= ast_unregister_application("SpeechActivateGrammar");
814         res |= ast_unregister_application("SpeechDeactivateGrammar");
815         res |= ast_unregister_application("SpeechStart");
816         res |= ast_unregister_application("SpeechBackground");
817         res |= ast_unregister_application("SpeechDestroy");
818         res |= ast_unregister_application("SpeechProcessingSound");
819         res |= ast_custom_function_unregister(&speech_function);
820         res |= ast_custom_function_unregister(&speech_score_function);
821         res |= ast_custom_function_unregister(&speech_text_function);
822         res |= ast_custom_function_unregister(&speech_grammar_function);
823         res |= ast_custom_function_unregister(&speech_engine_function);
824         res |= ast_custom_function_unregister(&speech_results_type_function);
825
826         return res;     
827 }
828
829 static int load_module(void)
830 {
831         int res = 0;
832
833         res = ast_register_application("SpeechCreate", speech_create, "Create a Speech Structure", speechcreate_descrip);
834         res |= ast_register_application("SpeechLoadGrammar", speech_load, "Load a Grammar", speechload_descrip);
835         res |= ast_register_application("SpeechUnloadGrammar", speech_unload, "Unload a Grammar", speechunload_descrip);
836         res |= ast_register_application("SpeechActivateGrammar", speech_activate, "Activate a Grammar", speechactivategrammar_descrip);
837         res |= ast_register_application("SpeechDeactivateGrammar", speech_deactivate, "Deactivate a Grammar", speechdeactivategrammar_descrip);
838         res |= ast_register_application("SpeechStart", speech_start, "Start recognizing voice in the audio stream", speechstart_descrip);
839         res |= ast_register_application("SpeechBackground", speech_background, "Play a sound file and wait for speech to be recognized", speechbackground_descrip);
840         res |= ast_register_application("SpeechDestroy", speech_destroy, "End speech recognition", speechdestroy_descrip);
841         res |= ast_register_application("SpeechProcessingSound", speech_processing_sound, "Change background processing sound", speechprocessingsound_descrip);
842         res |= ast_custom_function_register(&speech_function);
843         res |= ast_custom_function_register(&speech_score_function);
844         res |= ast_custom_function_register(&speech_text_function);
845         res |= ast_custom_function_register(&speech_grammar_function);
846         res |= ast_custom_function_register(&speech_engine_function);
847         res |= ast_custom_function_register(&speech_results_type_function);
848
849         return res;
850 }
851
852 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Dialplan Speech Applications");