Merged revisions 69558 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 (!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 = result->next));
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                 result = speech->results;
319                 while (result) {
320                         results++;
321                         result = result->next;
322                 }
323                 snprintf(tmp, sizeof(tmp), "%d", results);
324                 ast_copy_string(buf, tmp, len);
325         }
326
327         return 0;
328 }
329
330 static struct ast_custom_function speech_function = {
331         .name = "SPEECH",
332         .synopsis = "Gets information about speech recognition results.",
333         .syntax = "SPEECH(argument)",
334         .desc =
335         "Gets information about speech recognition results.\n"
336         "status:   Returns 1 upon speech object existing, or 0 if not\n"
337         "spoke:  Returns 1 if spoker spoke, or 0 if not\n"
338         "results:  Returns number of results that were recognized\n",
339         .read = speech_read,
340         .write = NULL,
341 };
342
343
344
345 /*! \brief SpeechCreate() Dialplan Application */
346 static int speech_create(struct ast_channel *chan, void *data)
347 {
348         struct ast_module_user *u = NULL;
349         struct ast_speech *speech = NULL;
350         struct ast_datastore *datastore = NULL;
351
352         u = ast_module_user_add(chan);
353
354         /* Request a speech object */
355         speech = ast_speech_new(data, AST_FORMAT_SLINEAR);
356         if (speech == NULL) {
357                 /* Not available */
358                 pbx_builtin_setvar_helper(chan, "ERROR", "1");
359                 ast_module_user_remove(u);
360                 return 0;
361         }
362
363         datastore = ast_channel_datastore_alloc(&speech_datastore, NULL);
364         if (datastore == NULL) {
365                 ast_speech_destroy(speech);
366                 pbx_builtin_setvar_helper(chan, "ERROR", "1");
367                 ast_module_user_remove(u);
368                 return 0;
369         }
370         datastore->data = speech;
371         ast_channel_datastore_add(chan, datastore);
372
373         ast_module_user_remove(u);
374
375         return 0;
376 }
377
378 /*! \brief SpeechLoadGrammar(Grammar Name|Path) Dialplan Application */
379 static int speech_load(struct ast_channel *chan, void *data)
380 {
381         int res = 0, argc = 0;
382         struct ast_module_user *u = NULL;
383         struct ast_speech *speech = find_speech(chan);
384         char *argv[2], *args = NULL, *name = NULL, *path = NULL;
385
386         args = ast_strdupa(data);
387
388         u = ast_module_user_add(chan);
389
390         if (speech == NULL) {
391                 ast_module_user_remove(u);
392                 return -1;
393         }
394
395         /* Parse out arguments */
396         argc = ast_app_separate_args(args, '|', argv, sizeof(argv) / sizeof(argv[0]));
397         if (argc != 2) {
398                 ast_module_user_remove(u);
399                 return -1;
400         }
401         name = argv[0];
402         path = argv[1];
403
404         /* Load the grammar locally on the object */
405         res = ast_speech_grammar_load(speech, name, path);
406
407         ast_module_user_remove(u);
408
409         return res;
410 }
411
412 /*! \brief SpeechUnloadGrammar(Grammar Name) Dialplan Application */
413 static int speech_unload(struct ast_channel *chan, void *data)
414 {
415         int res = 0;
416         struct ast_module_user *u = NULL;
417         struct ast_speech *speech = find_speech(chan);
418
419         u = ast_module_user_add(chan);
420
421         if (speech == NULL) {
422                 ast_module_user_remove(u);
423                 return -1;
424         }
425
426         /* Unload the grammar */
427         res = ast_speech_grammar_unload(speech, data);
428
429         ast_module_user_remove(u);
430
431         return res;
432 }
433
434 /*! \brief SpeechDeactivateGrammar(Grammar Name) Dialplan Application */
435 static int speech_deactivate(struct ast_channel *chan, void *data)
436 {
437         int res = 0;
438         struct ast_module_user *u = NULL;
439         struct ast_speech *speech = find_speech(chan);
440
441         u = ast_module_user_add(chan);
442
443         if (speech == NULL) {
444                 ast_module_user_remove(u);
445                 return -1;
446         }
447
448         /* Deactivate the grammar on the speech object */
449         res = ast_speech_grammar_deactivate(speech, data);
450
451         ast_module_user_remove(u);
452
453         return res;
454 }
455
456 /*! \brief SpeechActivateGrammar(Grammar Name) Dialplan Application */
457 static int speech_activate(struct ast_channel *chan, void *data)
458 {
459         int res = 0;
460         struct ast_module_user *u = NULL;
461         struct ast_speech *speech = find_speech(chan);
462
463         u = ast_module_user_add(chan);
464
465         if (speech == NULL) {
466                 ast_module_user_remove(u);
467                 return -1;
468         }
469
470         /* Activate the grammar on the speech object */
471         res = ast_speech_grammar_activate(speech, data);
472
473         ast_module_user_remove(u);
474
475         return res;
476 }
477
478 /*! \brief SpeechStart() Dialplan Application */
479 static int speech_start(struct ast_channel *chan, void *data)
480 {
481         int res = 0;
482         struct ast_module_user *u = NULL;
483         struct ast_speech *speech = find_speech(chan);
484
485         u = ast_module_user_add(chan);
486
487         if (speech == NULL) {
488                 ast_module_user_remove(u);
489                 return -1;
490         }
491
492         ast_speech_start(speech);
493
494         ast_module_user_remove(u);
495
496         return res;
497 }
498
499 /*! \brief SpeechProcessingSound(Sound File) Dialplan Application */
500 static int speech_processing_sound(struct ast_channel *chan, void *data)
501 {
502         int res = 0;
503         struct ast_module_user *u = NULL;
504         struct ast_speech *speech = find_speech(chan);
505
506         u = ast_module_user_add(chan);
507
508         if (speech == NULL) {
509                 ast_module_user_remove(u);
510                 return -1;
511         }
512
513         if (speech->processing_sound != NULL) {
514                 ast_free(speech->processing_sound);
515                 speech->processing_sound = NULL;
516         }
517
518         speech->processing_sound = ast_strdup(data);
519
520         ast_module_user_remove(u);
521
522         return res;
523 }
524
525 /*! \brief Helper function used by speech_background to playback a soundfile */
526 static int speech_streamfile(struct ast_channel *chan, const char *filename, const char *preflang)
527 {
528         struct ast_filestream *fs = NULL;
529
530         if (!(fs = ast_openstream(chan, filename, preflang)))
531                 return -1;
532         
533         if (ast_applystream(chan, fs))
534                 return -1;
535         
536         if (ast_playstream(fs))
537                 return -1;
538
539         return 0;
540 }
541
542 /*! \brief SpeechBackground(Sound File|Timeout) Dialplan Application */
543 static int speech_background(struct ast_channel *chan, void *data)
544 {
545         unsigned int timeout = 0;
546         int res = 0, done = 0, argc = 0, started = 0, quieted = 0, max_dtmf_len = 0;
547         struct ast_module_user *u = NULL;
548         struct ast_speech *speech = find_speech(chan);
549         struct ast_frame *f = NULL;
550         int oldreadformat = AST_FORMAT_SLINEAR;
551         char dtmf[AST_MAX_EXTENSION] = "";
552         time_t start, current;
553         struct ast_datastore *datastore = NULL;
554         char *argv[2], *args = NULL, *filename_tmp = NULL, *filename = NULL, tmp[2] = "";
555         const char *tmp2 = NULL;
556
557         args = ast_strdupa(data);
558
559         u = ast_module_user_add(chan);
560
561         if (speech == NULL) {
562                 ast_module_user_remove(u);
563                 return -1;
564         }
565
566         /* If channel is not already answered, then answer it */
567         if (chan->_state != AST_STATE_UP && ast_answer(chan)) {
568                 ast_module_user_remove(u);
569                 return -1;
570         }
571
572         /* Record old read format */
573         oldreadformat = chan->readformat;
574
575         /* Change read format to be signed linear */
576         if (ast_set_read_format(chan, AST_FORMAT_SLINEAR)) {
577                 ast_module_user_remove(u);
578                 return -1;
579         }
580
581         /* Parse out options */
582         argc = ast_app_separate_args(args, '|', argv, sizeof(argv) / sizeof(argv[0]));
583         if (argc > 0) {
584                 /* Yay sound file */
585                 filename_tmp = ast_strdupa(argv[0]);
586                 if (!ast_strlen_zero(argv[1])) {
587                         if ((timeout = atoi(argv[1])) == 0)
588                                 timeout = -1;
589                 } else
590                         timeout = 0;
591         }
592
593         /* See if the maximum DTMF length variable is set... we use a variable in case they want to carry it through their entire dialplan */
594         if ((tmp2 = pbx_builtin_getvar_helper(chan, "SPEECH_DTMF_MAXLEN")) && !ast_strlen_zero(tmp2))
595                 max_dtmf_len = atoi(tmp2);
596
597         /* Before we go into waiting for stuff... make sure the structure is ready, if not - start it again */
598         if (speech->state == AST_SPEECH_STATE_NOT_READY || speech->state == AST_SPEECH_STATE_DONE) {
599                 ast_speech_change_state(speech, AST_SPEECH_STATE_NOT_READY);
600                 ast_speech_start(speech);
601         }
602
603         /* Ensure no streams are currently running */
604         ast_stopstream(chan);
605
606         /* Okay it's streaming so go into a loop grabbing frames! */
607         while (done == 0) {
608                 /* If the filename is null and stream is not running, start up a new sound file */
609                 if (!quieted && (chan->streamid == -1 && chan->timingfunc == NULL) && (filename = strsep(&filename_tmp, "&"))) {
610                         /* Discard old stream information */
611                         ast_stopstream(chan);
612                         /* Start new stream */
613                         speech_streamfile(chan, filename, chan->language);
614                 }
615
616                 /* Run scheduled stuff */
617                 ast_sched_runq(chan->sched);
618
619                 /* Yay scheduling */
620                 res = ast_sched_wait(chan->sched);
621                 if (res < 0) {
622                         res = 1000;
623                 }
624
625                 /* If there is a frame waiting, get it - if not - oh well */
626                 if (ast_waitfor(chan, res) > 0) {
627                         f = ast_read(chan);
628                         if (f == NULL) {
629                                 /* The channel has hung up most likely */
630                                 done = 3;
631                                 break;
632                         }
633                 }
634
635                 /* Do timeout check (shared between audio/dtmf) */
636                 if ((!quieted || strlen(dtmf)) && started == 1) {
637                         time(&current);
638                         if ((current-start) >= timeout) {
639                                 done = 1;
640                                 if (f)
641                                         ast_frfree(f);
642                                 break;
643                         }
644                 }
645
646                 /* Do checks on speech structure to see if it's changed */
647                 ast_mutex_lock(&speech->lock);
648                 if (ast_test_flag(speech, AST_SPEECH_QUIET)) {
649                         if (chan->stream)
650                                 ast_stopstream(chan);
651                         ast_clear_flag(speech, AST_SPEECH_QUIET);
652                         quieted = 1;
653                 }
654                 /* Check state so we can see what to do */
655                 switch (speech->state) {
656                 case AST_SPEECH_STATE_READY:
657                         /* If audio playback has stopped do a check for timeout purposes */
658                         if (chan->streamid == -1 && chan->timingfunc == NULL)
659                                 ast_stopstream(chan);
660                         if (!quieted && chan->stream == NULL && timeout && started == 0 && !filename_tmp) {
661                                 if (timeout == -1) {
662                                         done = 1;
663                                         if (f)
664                                                 ast_frfree(f);
665                                         break;
666                                 }
667                                 time(&start);
668                                 started = 1;
669                         }
670                         /* Write audio frame out to speech engine if no DTMF has been received */
671                         if (!strlen(dtmf) && f != NULL && f->frametype == AST_FRAME_VOICE) {
672                                 ast_speech_write(speech, f->data, f->datalen);
673                         }
674                         break;
675                 case AST_SPEECH_STATE_WAIT:
676                         /* Cue up waiting sound if not already playing */
677                         if (!strlen(dtmf)) {
678                                 if (chan->stream == NULL) {
679                                         if (speech->processing_sound != NULL) {
680                                                 if (strlen(speech->processing_sound) > 0 && strcasecmp(speech->processing_sound,"none")) {
681                                                         speech_streamfile(chan, speech->processing_sound, chan->language);
682                                                 }
683                                         }
684                                 } else if (chan->streamid == -1 && chan->timingfunc == NULL) {
685                                         ast_stopstream(chan);
686                                         if (speech->processing_sound != NULL) {
687                                                 if (strlen(speech->processing_sound) > 0 && strcasecmp(speech->processing_sound,"none")) {
688                                                         speech_streamfile(chan, speech->processing_sound, chan->language);
689                                                 }
690                                         }
691                                 }
692                         }
693                         break;
694                 case AST_SPEECH_STATE_DONE:
695                         /* Now that we are done... let's switch back to not ready state */
696                         ast_speech_change_state(speech, AST_SPEECH_STATE_NOT_READY);
697                         if (!strlen(dtmf)) {
698                                 /* Copy to speech structure the results, if available */
699                                 speech->results = ast_speech_results_get(speech);
700                                 /* Break out of our background too */
701                                 done = 1;
702                                 /* Stop audio playback */
703                                 if (chan->stream != NULL) {
704                                         ast_stopstream(chan);
705                                 }
706                         }
707                         break;
708                 default:
709                         break;
710                 }
711                 ast_mutex_unlock(&speech->lock);
712
713                 /* Deal with other frame types */
714                 if (f != NULL) {
715                         /* Free the frame we received */
716                         switch (f->frametype) {
717                         case AST_FRAME_DTMF:
718                                 if (f->subclass == '#') {
719                                         done = 1;
720                                 } else {
721                                         if (chan->stream != NULL) {
722                                                 ast_stopstream(chan);
723                                         }
724                                         if (!started) {
725                                                 /* Change timeout to be 5 seconds for DTMF input */
726                                                 timeout = (chan->pbx && chan->pbx->dtimeout) ? chan->pbx->dtimeout : 5;
727                                                 started = 1;
728                                         }
729                                         time(&start);
730                                         snprintf(tmp, sizeof(tmp), "%c", f->subclass);
731                                         strncat(dtmf, tmp, sizeof(dtmf));
732                                         /* If the maximum length of the DTMF has been reached, stop now */
733                                         if (max_dtmf_len && strlen(dtmf) == max_dtmf_len)
734                                                 done = 1;
735                                 }
736                                 break;
737                         case AST_FRAME_CONTROL:
738                                 switch (f->subclass) {
739                                 case AST_CONTROL_HANGUP:
740                                         /* Since they hung up we should destroy the speech structure */
741                                         done = 3;
742                                 default:
743                                         break;
744                                 }
745                         default:
746                                 break;
747                         }
748                         ast_frfree(f);
749                         f = NULL;
750                 }
751         }
752
753         if (strlen(dtmf)) {
754                 /* We sort of make a results entry */
755                 speech->results = ast_calloc(1, sizeof(*speech->results));
756                 if (speech->results != NULL) {
757                         speech->results->score = 1000;
758                         speech->results->text = ast_strdup(dtmf);
759                         speech->results->grammar = ast_strdup("dtmf");
760                 }
761         }
762
763         /* See if it was because they hung up */
764         if (done == 3) {
765                 /* Destroy speech structure */
766                 ast_speech_destroy(speech);
767                 datastore = ast_channel_datastore_find(chan, &speech_datastore, NULL);
768                 if (datastore != NULL) {
769                         ast_channel_datastore_remove(chan, datastore);
770                 }
771         } else {
772                 /* Channel is okay so restore read format */
773                 ast_set_read_format(chan, oldreadformat);
774         }
775
776         ast_module_user_remove(u);
777
778         return 0;
779 }
780
781
782 /*! \brief SpeechDestroy() Dialplan Application */
783 static int speech_destroy(struct ast_channel *chan, void *data)
784 {
785         int res = 0;
786         struct ast_module_user *u = NULL;
787         struct ast_speech *speech = find_speech(chan);
788         struct ast_datastore *datastore = NULL;
789
790         u = ast_module_user_add(chan);
791
792         if (speech == NULL) {
793                 ast_module_user_remove(u);
794                 return -1;
795         }
796
797         /* Destroy speech structure */
798         ast_speech_destroy(speech);
799
800         datastore = ast_channel_datastore_find(chan, &speech_datastore, NULL);
801         if (datastore != NULL) {
802                 ast_channel_datastore_remove(chan, datastore);
803         }
804
805         ast_module_user_remove(u);
806
807         return res;
808 }
809
810 static int unload_module(void)
811 {
812         int res = 0;
813
814         res = ast_unregister_application("SpeechCreate");
815         res |= ast_unregister_application("SpeechLoadGrammar");
816         res |= ast_unregister_application("SpeechUnloadGrammar");
817         res |= ast_unregister_application("SpeechActivateGrammar");
818         res |= ast_unregister_application("SpeechDeactivateGrammar");
819         res |= ast_unregister_application("SpeechStart");
820         res |= ast_unregister_application("SpeechBackground");
821         res |= ast_unregister_application("SpeechDestroy");
822         res |= ast_unregister_application("SpeechProcessingSound");
823         res |= ast_custom_function_unregister(&speech_function);
824         res |= ast_custom_function_unregister(&speech_score_function);
825         res |= ast_custom_function_unregister(&speech_text_function);
826         res |= ast_custom_function_unregister(&speech_grammar_function);
827         res |= ast_custom_function_unregister(&speech_engine_function);
828         res |= ast_custom_function_unregister(&speech_results_type_function);
829
830         ast_module_user_hangup_all();
831
832         return res;     
833 }
834
835 static int load_module(void)
836 {
837         int res = 0;
838
839         res = ast_register_application("SpeechCreate", speech_create, "Create a Speech Structure", speechcreate_descrip);
840         res |= ast_register_application("SpeechLoadGrammar", speech_load, "Load a Grammar", speechload_descrip);
841         res |= ast_register_application("SpeechUnloadGrammar", speech_unload, "Unload a Grammar", speechunload_descrip);
842         res |= ast_register_application("SpeechActivateGrammar", speech_activate, "Activate a Grammar", speechactivategrammar_descrip);
843         res |= ast_register_application("SpeechDeactivateGrammar", speech_deactivate, "Deactivate a Grammar", speechdeactivategrammar_descrip);
844         res |= ast_register_application("SpeechStart", speech_start, "Start recognizing voice in the audio stream", speechstart_descrip);
845         res |= ast_register_application("SpeechBackground", speech_background, "Play a sound file and wait for speech to be recognized", speechbackground_descrip);
846         res |= ast_register_application("SpeechDestroy", speech_destroy, "End speech recognition", speechdestroy_descrip);
847         res |= ast_register_application("SpeechProcessingSound", speech_processing_sound, "Change background processing sound", speechprocessingsound_descrip);
848         res |= ast_custom_function_register(&speech_function);
849         res |= ast_custom_function_register(&speech_score_function);
850         res |= ast_custom_function_register(&speech_text_function);
851         res |= ast_custom_function_register(&speech_grammar_function);
852         res |= ast_custom_function_register(&speech_engine_function);
853         res |= ast_custom_function_register(&speech_results_type_function);
854
855         return res;
856 }
857
858 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Dialplan Speech Applications");