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