Asterisk - The Open Source Telephony Project  21.4.1
app_speech_utils.c
Go to the documentation of this file.
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 /*** MODULEINFO
29  <depend>res_speech</depend>
30  <support_level>core</support_level>
31  ***/
32 
33 #include "asterisk.h"
34 
35 #include "asterisk/file.h"
36 #include "asterisk/channel.h"
37 #include "asterisk/pbx.h"
38 #include "asterisk/module.h"
39 #include "asterisk/lock.h"
40 #include "asterisk/app.h"
41 #include "asterisk/speech.h"
42 
43 /*** DOCUMENTATION
44  <application name="SpeechCreate" language="en_US">
45  <synopsis>
46  Create a Speech Structure.
47  </synopsis>
48  <syntax>
49  <parameter name="engine_name" required="true" />
50  </syntax>
51  <description>
52  <para>This application creates information to be used by all the other applications.
53  It must be called before doing any speech recognition activities such as activating a grammar.
54  It takes the engine name to use as the argument, if not specified the default engine will be used.</para>
55  <para>Sets the ERROR channel variable to 1 if the engine cannot be used.</para>
56  </description>
57  </application>
58  <application name="SpeechActivateGrammar" language="en_US">
59  <synopsis>
60  Activate a grammar.
61  </synopsis>
62  <syntax>
63  <parameter name="grammar_name" required="true" />
64  </syntax>
65  <description>
66  <para>This activates the specified grammar to be recognized by the engine.
67  A grammar tells the speech recognition engine what to recognize, and how to portray it back to you
68  in the dialplan. The grammar name is the only argument to this application.</para>
69  <para>Hangs up the channel on failure. If this is not desired, use TryExec.</para>
70  </description>
71  </application>
72  <application name="SpeechStart" language="en_US">
73  <synopsis>
74  Start recognizing voice in the audio stream.
75  </synopsis>
76  <syntax />
77  <description>
78  <para>Tell the speech recognition engine that it should start trying to get results from audio being
79  fed to it.</para>
80  <para>Hangs up the channel on failure. If this is not desired, use TryExec.</para>
81  </description>
82  </application>
83  <application name="SpeechBackground" language="en_US">
84  <synopsis>
85  Play a sound file and wait for speech to be recognized.
86  </synopsis>
87  <syntax>
88  <parameter name="sound_file" required="true" argsep="&amp;">
89  <para>Ampersand separated list of filenames. If the filename
90  is a relative filename (it does not begin with a slash), it
91  will be searched for in the Asterisk sounds directory. If the
92  filename is able to be parsed as a URL, Asterisk will
93  download the file and then begin playback on it. To include a
94  literal <literal>&amp;</literal> in the URL you can enclose
95  the URL in single quotes.</para>
96  <argument name="sound_file" required="true" />
97  <argument name="sound_file2" multiple="true" />
98  </parameter>
99  <parameter name="timeout">
100  <para>Timeout integer in seconds. Note the timeout will only start
101  once the sound file has stopped playing.</para>
102  </parameter>
103  <parameter name="options">
104  <optionlist>
105  <option name="n">
106  <para>Don't answer the channel if it has not already been answered.</para>
107  </option>
108  <option name="p">
109  <para>Return partial results when backend is terminated by timeout.</para>
110  </option>
111  </optionlist>
112  </parameter>
113  </syntax>
114  <description>
115  <para>This application plays a sound file and waits for the person to speak. Once they start speaking playback
116  of the file stops, and silence is heard. Once they stop talking the processing sound is played to indicate
117  the speech recognition engine is working. Once results are available the application returns and results
118  (score and text) are available using dialplan functions.</para>
119  <para>The first text and score are ${SPEECH_TEXT(0)} AND ${SPEECH_SCORE(0)} while the second are ${SPEECH_TEXT(1)}
120  and ${SPEECH_SCORE(1)}.</para>
121  <para>The first argument is the sound file and the second is the timeout integer in seconds.</para>
122  <para>Hangs up the channel on failure. If this is not desired, use TryExec.</para>
123 
124  </description>
125  </application>
126  <application name="SpeechDeactivateGrammar" language="en_US">
127  <synopsis>
128  Deactivate a grammar.
129  </synopsis>
130  <syntax>
131  <parameter name="grammar_name" required="true">
132  <para>The grammar name to deactivate</para>
133  </parameter>
134  </syntax>
135  <description>
136  <para>This deactivates the specified grammar so that it is no longer recognized.</para>
137  <para>Hangs up the channel on failure. If this is not desired, use TryExec.</para>
138  </description>
139  </application>
140  <application name="SpeechProcessingSound" language="en_US">
141  <synopsis>
142  Change background processing sound.
143  </synopsis>
144  <syntax>
145  <parameter name="sound_file" required="true" />
146  </syntax>
147  <description>
148  <para>This changes the processing sound that SpeechBackground plays back when the speech recognition engine is
149  processing and working to get results.</para>
150  <para>Hangs up the channel on failure. If this is not desired, use TryExec.</para>
151  </description>
152  </application>
153  <application name="SpeechDestroy" language="en_US">
154  <synopsis>
155  End speech recognition.
156  </synopsis>
157  <syntax />
158  <description>
159  <para>This destroys the information used by all the other speech recognition applications.
160  If you call this application but end up wanting to recognize more speech, you must call SpeechCreate()
161  again before calling any other application.</para>
162  <para>Hangs up the channel on failure. If this is not desired, use TryExec.</para>
163  </description>
164  </application>
165  <application name="SpeechLoadGrammar" language="en_US">
166  <synopsis>
167  Load a grammar.
168  </synopsis>
169  <syntax>
170  <parameter name="grammar_name" required="true" />
171  <parameter name="path" required="true" />
172  </syntax>
173  <description>
174  <para>Load a grammar only on the channel, not globally.</para>
175  <para>Hangs up the channel on failure. If this is not desired, use TryExec.</para>
176  </description>
177  </application>
178  <application name="SpeechUnloadGrammar" language="en_US">
179  <synopsis>
180  Unload a grammar.
181  </synopsis>
182  <syntax>
183  <parameter name="grammar_name" required="true" />
184  </syntax>
185  <description>
186  <para>Unload a grammar.</para>
187  <para>Hangs up the channel on failure. If this is not desired, use TryExec.</para>
188  </description>
189  </application>
190  <function name="SPEECH_SCORE" language="en_US">
191  <synopsis>
192  Gets the confidence score of a result.
193  </synopsis>
194  <syntax argsep="/">
195  <parameter name="nbest_number" />
196  <parameter name="result_number" required="true" />
197  </syntax>
198  <description>
199  <para>Gets the confidence score of a result.</para>
200  </description>
201  </function>
202  <function name="SPEECH_TEXT" language="en_US">
203  <synopsis>
204  Gets the recognized text of a result.
205  </synopsis>
206  <syntax argsep="/">
207  <parameter name="nbest_number" />
208  <parameter name="result_number" required="true" />
209  </syntax>
210  <description>
211  <para>Gets the recognized text of a result.</para>
212  </description>
213  </function>
214  <function name="SPEECH_GRAMMAR" language="en_US">
215  <synopsis>
216  Gets the matched grammar of a result if available.
217  </synopsis>
218  <syntax argsep="/">
219  <parameter name="nbest_number" />
220  <parameter name="result_number" required="true" />
221  </syntax>
222  <description>
223  <para>Gets the matched grammar of a result if available.</para>
224  </description>
225  </function>
226  <function name="SPEECH_ENGINE" language="en_US">
227  <synopsis>
228  Get or change a speech engine specific attribute.
229  </synopsis>
230  <syntax>
231  <parameter name="name" required="true" />
232  </syntax>
233  <description>
234  <para>Changes a speech engine specific attribute.</para>
235  </description>
236  </function>
237  <function name="SPEECH_RESULTS_TYPE" language="en_US">
238  <synopsis>
239  Sets the type of results that will be returned.
240  </synopsis>
241  <syntax />
242  <description>
243  <para>Sets the type of results that will be returned. Valid options are normal or nbest.</para>
244  </description>
245  </function>
246  <function name="SPEECH" language="en_US">
247  <synopsis>
248  Gets information about speech recognition results.
249  </synopsis>
250  <syntax>
251  <parameter name="argument" required="true">
252  <enumlist>
253  <enum name="status">
254  <para>Returns <literal>1</literal> upon speech object existing,
255  or <literal>0</literal> if not</para>
256  </enum>
257  <enum name="spoke">
258  <para>Returns <literal>1</literal> if spoker spoke,
259  or <literal>0</literal> if not</para>
260  </enum>
261  <enum name="results">
262  <para>Returns number of results that were recognized.</para>
263  </enum>
264  </enumlist>
265  </parameter>
266  </syntax>
267  <description>
268  <para>Gets information about speech recognition results.</para>
269  </description>
270  </function>
271  ***/
272 
273 /*! \brief Helper function used by datastores to destroy the speech structure upon hangup */
274 static void destroy_callback(void *data)
275 {
276  struct ast_speech *speech = (struct ast_speech*)data;
277 
278  if (speech == NULL) {
279  return;
280  }
281 
282  /* Deallocate now */
283  ast_speech_destroy(speech);
284 
285  return;
286 }
287 
288 /*! \brief Static structure for datastore information */
289 static const struct ast_datastore_info speech_datastore = {
290  .type = "speech",
291  .destroy = destroy_callback
292 };
293 
294 /*! \brief Helper function used to find the speech structure attached to a channel */
295 static struct ast_speech *find_speech(struct ast_channel *chan)
296 {
297  struct ast_speech *speech = NULL;
298  struct ast_datastore *datastore = NULL;
299 
300  if (!chan) {
301  return NULL;
302  }
303 
304  ast_channel_lock(chan);
305  datastore = ast_channel_datastore_find(chan, &speech_datastore, NULL);
306  ast_channel_unlock(chan);
307  if (datastore == NULL) {
308  return NULL;
309  }
310  speech = datastore->data;
311 
312  return speech;
313 }
314 
315 /*!
316  * \internal
317  * \brief Destroy the speech datastore on the given channel.
318  *
319  * \param chan Channel to destroy speech datastore.
320  *
321  * \retval 0 on success.
322  * \retval -1 not found.
323  */
324 static int speech_datastore_destroy(struct ast_channel *chan)
325 {
326  struct ast_datastore *datastore;
327  int res;
328 
329  ast_channel_lock(chan);
330  datastore = ast_channel_datastore_find(chan, &speech_datastore, NULL);
331  if (datastore) {
332  ast_channel_datastore_remove(chan, datastore);
333  }
334  ast_channel_unlock(chan);
335  if (datastore) {
336  ast_datastore_free(datastore);
337  res = 0;
338  } else {
339  res = -1;
340  }
341  return res;
342 }
343 
344 /* Helper function to find a specific speech recognition result by number and nbest alternative */
345 static struct ast_speech_result *find_result(struct ast_speech_result *results, char *result_num)
346 {
347  struct ast_speech_result *result = results;
348  char *tmp = NULL;
349  int nbest_num = 0, wanted_num = 0, i = 0;
350 
351  if (!result) {
352  return NULL;
353  }
354 
355  if ((tmp = strchr(result_num, '/'))) {
356  *tmp++ = '\0';
357  nbest_num = atoi(result_num);
358  wanted_num = atoi(tmp);
359  } else {
360  wanted_num = atoi(result_num);
361  }
362 
363  do {
364  if (result->nbest_num != nbest_num)
365  continue;
366  if (i == wanted_num)
367  break;
368  i++;
369  } while ((result = AST_LIST_NEXT(result, list)));
370 
371  return result;
372 }
373 
374 /*! \brief SPEECH_SCORE() Dialplan Function */
375 static int speech_score(struct ast_channel *chan, const char *cmd, char *data,
376  char *buf, size_t len)
377 {
378  struct ast_speech_result *result = NULL;
379  struct ast_speech *speech = find_speech(chan);
380  char tmp[128] = "";
381 
382  if (data == NULL || speech == NULL || !(result = find_result(speech->results, data))) {
383  return -1;
384  }
385 
386  snprintf(tmp, sizeof(tmp), "%d", result->score);
387 
388  ast_copy_string(buf, tmp, len);
389 
390  return 0;
391 }
392 
393 static struct ast_custom_function speech_score_function = {
394  .name = "SPEECH_SCORE",
395  .read = speech_score,
396  .write = NULL,
397 };
398 
399 /*! \brief SPEECH_TEXT() Dialplan Function */
400 static int speech_text(struct ast_channel *chan, const char *cmd, char *data,
401  char *buf, size_t len)
402 {
403  struct ast_speech_result *result = NULL;
404  struct ast_speech *speech = find_speech(chan);
405 
406  if (data == NULL || speech == NULL || !(result = find_result(speech->results, data))) {
407  return -1;
408  }
409 
410  if (result->text != NULL) {
411  ast_copy_string(buf, result->text, len);
412  } else {
413  buf[0] = '\0';
414  }
415 
416  return 0;
417 }
418 
419 static struct ast_custom_function speech_text_function = {
420  .name = "SPEECH_TEXT",
421  .read = speech_text,
422  .write = NULL,
423 };
424 
425 /*! \brief SPEECH_GRAMMAR() Dialplan Function */
426 static int speech_grammar(struct ast_channel *chan, const char *cmd, char *data,
427  char *buf, size_t len)
428 {
429  struct ast_speech_result *result = NULL;
430  struct ast_speech *speech = find_speech(chan);
431 
432  if (data == NULL || speech == NULL || !(result = find_result(speech->results, data))) {
433  return -1;
434  }
435 
436  if (result->grammar != NULL) {
437  ast_copy_string(buf, result->grammar, len);
438  } else {
439  buf[0] = '\0';
440  }
441 
442  return 0;
443 }
444 
445 static struct ast_custom_function speech_grammar_function = {
446  .name = "SPEECH_GRAMMAR",
447  .read = speech_grammar,
448  .write = NULL,
449 };
450 
451 /*! \brief SPEECH_ENGINE() Dialplan Set Function */
452 static int speech_engine_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
453 {
454  struct ast_speech *speech = find_speech(chan);
455 
456  if (data == NULL || speech == NULL) {
457  return -1;
458  }
459 
460  ast_speech_change(speech, data, value);
461 
462  return 0;
463 }
464 
465 /*! \brief SPEECH_ENGINE() Dialplan Get Function */
466 static int speech_engine_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
467 {
468  struct ast_speech *speech = find_speech(chan);
469 
470  if (!data || !speech) {
471  return -1;
472  }
473 
474  return ast_speech_get_setting(speech, data, buf, len);
475 }
476 
477 static struct ast_custom_function speech_engine_function = {
478  .name = "SPEECH_ENGINE",
479  .read = speech_engine_read,
480  .write = speech_engine_write,
481 };
482 
483 /*! \brief SPEECH_RESULTS_TYPE() Dialplan Function */
484 static int speech_results_type_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
485 {
486  struct ast_speech *speech = find_speech(chan);
487 
488  if (data == NULL || speech == NULL)
489  return -1;
490 
491  if (!strcasecmp(value, "normal"))
492  ast_speech_change_results_type(speech, AST_SPEECH_RESULTS_TYPE_NORMAL);
493  else if (!strcasecmp(value, "nbest"))
494  ast_speech_change_results_type(speech, AST_SPEECH_RESULTS_TYPE_NBEST);
495 
496  return 0;
497 }
498 
499 static struct ast_custom_function speech_results_type_function = {
500  .name = "SPEECH_RESULTS_TYPE",
501  .read = NULL,
502  .write = speech_results_type_write,
503 };
504 
505 /*! \brief SPEECH() Dialplan Function */
506 static int speech_read(struct ast_channel *chan, const char *cmd, char *data,
507  char *buf, size_t len)
508 {
509  int results = 0;
510  struct ast_speech_result *result = NULL;
511  struct ast_speech *speech = find_speech(chan);
512  char tmp[128] = "";
513 
514  /* Now go for the various options */
515  if (!strcasecmp(data, "status")) {
516  if (speech != NULL)
517  ast_copy_string(buf, "1", len);
518  else
519  ast_copy_string(buf, "0", len);
520  return 0;
521  }
522 
523  /* Make sure we have a speech structure for everything else */
524  if (speech == NULL) {
525  return -1;
526  }
527 
528  /* Check to see if they are checking for silence */
529  if (!strcasecmp(data, "spoke")) {
530  if (ast_test_flag(speech, AST_SPEECH_SPOKE))
531  ast_copy_string(buf, "1", len);
532  else
533  ast_copy_string(buf, "0", len);
534  } else if (!strcasecmp(data, "results")) {
535  /* Count number of results */
536  for (result = speech->results; result; result = AST_LIST_NEXT(result, list))
537  results++;
538  snprintf(tmp, sizeof(tmp), "%d", results);
539  ast_copy_string(buf, tmp, len);
540  } else {
541  buf[0] = '\0';
542  }
543 
544  return 0;
545 }
546 
547 static struct ast_custom_function speech_function = {
548  .name = "SPEECH",
549  .read = speech_read,
550  .write = NULL,
551 };
552 
553 
554 
555 /*! \brief SpeechCreate() Dialplan Application */
556 static int speech_create(struct ast_channel *chan, const char *data)
557 {
558  struct ast_speech *speech = NULL;
559  struct ast_datastore *datastore = NULL;
560 
561  /* Request a speech object */
562  speech = ast_speech_new(data, ast_channel_nativeformats(chan));
563  if (speech == NULL) {
564  /* Not available */
565  pbx_builtin_setvar_helper(chan, "ERROR", "1");
566  return 0;
567  }
568 
569  datastore = ast_datastore_alloc(&speech_datastore, NULL);
570  if (datastore == NULL) {
571  ast_speech_destroy(speech);
572  pbx_builtin_setvar_helper(chan, "ERROR", "1");
573  return 0;
574  }
575  pbx_builtin_setvar_helper(chan, "ERROR", NULL);
576  datastore->data = speech;
577  ast_channel_lock(chan);
578  ast_channel_datastore_add(chan, datastore);
579  ast_channel_unlock(chan);
580 
581  return 0;
582 }
583 
584 /*! \brief SpeechLoadGrammar(Grammar Name,Path) Dialplan Application */
585 static int speech_load(struct ast_channel *chan, const char *vdata)
586 {
587  int res = 0;
588  struct ast_speech *speech = find_speech(chan);
589  char *data;
591  AST_APP_ARG(grammar);
592  AST_APP_ARG(path);
593  );
594 
595  data = ast_strdupa(vdata);
596  AST_STANDARD_APP_ARGS(args, data);
597 
598  if (speech == NULL)
599  return -1;
600 
601  if (args.argc != 2)
602  return -1;
603 
604  /* Load the grammar locally on the object */
605  res = ast_speech_grammar_load(speech, args.grammar, args.path);
606 
607  return res;
608 }
609 
610 /*! \brief SpeechUnloadGrammar(Grammar Name) Dialplan Application */
611 static int speech_unload(struct ast_channel *chan, const char *data)
612 {
613  int res = 0;
614  struct ast_speech *speech = find_speech(chan);
615 
616  if (speech == NULL)
617  return -1;
618 
619  /* Unload the grammar */
620  res = ast_speech_grammar_unload(speech, data);
621 
622  return res;
623 }
624 
625 /*! \brief SpeechDeactivateGrammar(Grammar Name) Dialplan Application */
626 static int speech_deactivate(struct ast_channel *chan, const char *data)
627 {
628  int res = 0;
629  struct ast_speech *speech = find_speech(chan);
630 
631  if (speech == NULL)
632  return -1;
633 
634  /* Deactivate the grammar on the speech object */
635  res = ast_speech_grammar_deactivate(speech, data);
636 
637  return res;
638 }
639 
640 /*! \brief SpeechActivateGrammar(Grammar Name) Dialplan Application */
641 static int speech_activate(struct ast_channel *chan, const char *data)
642 {
643  int res = 0;
644  struct ast_speech *speech = find_speech(chan);
645 
646  if (speech == NULL)
647  return -1;
648 
649  /* Activate the grammar on the speech object */
650  res = ast_speech_grammar_activate(speech, data);
651 
652  return res;
653 }
654 
655 /*! \brief SpeechStart() Dialplan Application */
656 static int speech_start(struct ast_channel *chan, const char *data)
657 {
658  int res = 0;
659  struct ast_speech *speech = find_speech(chan);
660 
661  if (speech == NULL)
662  return -1;
663 
664  ast_speech_start(speech);
665 
666  return res;
667 }
668 
669 /*! \brief SpeechProcessingSound(Sound File) Dialplan Application */
670 static int speech_processing_sound(struct ast_channel *chan, const char *data)
671 {
672  int res = 0;
673  struct ast_speech *speech = find_speech(chan);
674 
675  if (speech == NULL)
676  return -1;
677 
678  if (speech->processing_sound != NULL) {
679  ast_free(speech->processing_sound);
680  speech->processing_sound = NULL;
681  }
682 
683  speech->processing_sound = ast_strdup(data);
684 
685  return res;
686 }
687 
688 /*! \brief Helper function used by speech_background to playback a soundfile */
689 static int speech_streamfile(struct ast_channel *chan, const char *filename, const char *preflang)
690 {
691  struct ast_filestream *fs = NULL;
692 
693  if (!(fs = ast_openstream(chan, filename, preflang)))
694  return -1;
695 
696  if (ast_applystream(chan, fs))
697  return -1;
698 
699  ast_playstream(fs);
700 
701  return 0;
702 }
703 
704 enum {
705  SB_OPT_NOANSWER = (1 << 0),
706  SB_OPT_PARTIALRESULTS = (1 << 1),
707 };
708 
709 AST_APP_OPTIONS(speech_background_options, BEGIN_OPTIONS
710  AST_APP_OPTION('n', SB_OPT_NOANSWER),
711  AST_APP_OPTION('p', SB_OPT_PARTIALRESULTS),
712 END_OPTIONS );
713 
714 /*! \brief SpeechBackground(Sound File,Timeout) Dialplan Application */
715 static int speech_background(struct ast_channel *chan, const char *data)
716 {
717  unsigned int timeout = 0;
718  int res = 0, done = 0, started = 0, quieted = 0, max_dtmf_len = 0;
719  struct ast_speech *speech = find_speech(chan);
720  struct ast_frame *f = NULL;
721  RAII_VAR(struct ast_format *, oldreadformat, NULL, ao2_cleanup);
722  char dtmf[AST_MAX_EXTENSION] = "";
723  struct timeval start = { 0, 0 }, current;
724  char *parse, *filename_tmp = NULL, *filename = NULL, tmp[2] = "", dtmf_terminator = '#';
725  const char *tmp2 = NULL;
726  struct ast_flags options = { 0 };
728  AST_APP_ARG(soundfile);
729  AST_APP_ARG(timeout);
730  AST_APP_ARG(options);
731  );
732 
733  parse = ast_strdupa(data);
734  AST_STANDARD_APP_ARGS(args, parse);
735 
736  if (speech == NULL)
737  return -1;
738 
739  if (!ast_strlen_zero(args.options)) {
740  char *options_buf = ast_strdupa(args.options);
741  ast_app_parse_options(speech_background_options, &options, NULL, options_buf);
742  }
743 
744  /* If channel is not already answered, then answer it */
745  if (ast_channel_state(chan) != AST_STATE_UP && !ast_test_flag(&options, SB_OPT_NOANSWER)
746  && ast_answer(chan)) {
747  return -1;
748  }
749 
750  /* Record old read format */
751  oldreadformat = ao2_bump(ast_channel_readformat(chan));
752 
753  /* Change read format to be signed linear */
754  if (ast_set_read_format(chan, speech->format))
755  return -1;
756 
757  if (!ast_strlen_zero(args.soundfile)) {
758  /* Yay sound file */
759  filename_tmp = ast_strdupa(args.soundfile);
760  if (!ast_strlen_zero(args.timeout)) {
761  if ((timeout = atof(args.timeout) * 1000.0) == 0)
762  timeout = -1;
763  } else
764  timeout = 0;
765  }
766 
767  /* See if the maximum DTMF length variable is set... we use a variable in case they want to carry it through their entire dialplan */
768  ast_channel_lock(chan);
769  if ((tmp2 = pbx_builtin_getvar_helper(chan, "SPEECH_DTMF_MAXLEN")) && !ast_strlen_zero(tmp2)) {
770  max_dtmf_len = atoi(tmp2);
771  }
772 
773  /* See if a terminator is specified */
774  if ((tmp2 = pbx_builtin_getvar_helper(chan, "SPEECH_DTMF_TERMINATOR"))) {
775  if (ast_strlen_zero(tmp2))
776  dtmf_terminator = '\0';
777  else
778  dtmf_terminator = tmp2[0];
779  }
780  ast_channel_unlock(chan);
781 
782  /* Before we go into waiting for stuff... make sure the structure is ready, if not - start it again */
783  if (speech->state == AST_SPEECH_STATE_NOT_READY || speech->state == AST_SPEECH_STATE_DONE) {
784  ast_speech_change_state(speech, AST_SPEECH_STATE_NOT_READY);
785  ast_speech_start(speech);
786  }
787 
788  /* Ensure no streams are currently running */
789  ast_stopstream(chan);
790 
791  /* Okay it's streaming so go into a loop grabbing frames! */
792  while (done == 0) {
793  /* If the filename is null and stream is not running, start up a new sound file */
794  if (!quieted
795  && ast_channel_streamid(chan) == -1
796  && ast_channel_timingfunc(chan) == NULL
797  && (filename = ast_strsep(&filename_tmp, '&', AST_STRSEP_STRIP | AST_STRSEP_TRIM))) {
798  /* Discard old stream information */
799  ast_stopstream(chan);
800  /* Start new stream */
801  speech_streamfile(chan, filename, ast_channel_language(chan));
802  }
803 
804  /* Run scheduled stuff */
805  ast_sched_runq(ast_channel_sched(chan));
806 
807  /* Yay scheduling */
808  res = ast_sched_wait(ast_channel_sched(chan));
809  if (res < 0)
810  res = 1000;
811 
812  /* If there is a frame waiting, get it - if not - oh well */
813  if (ast_waitfor(chan, res) > 0) {
814  f = ast_read(chan);
815  if (f == NULL) {
816  /* The channel has hung up most likely */
817  done = 3;
818  break;
819  }
820  }
821 
822  /* Do timeout check (shared between audio/dtmf) */
823  if ((!quieted || strlen(dtmf)) && started == 1) {
824  current = ast_tvnow();
825  if ((ast_tvdiff_ms(current, start)) >= timeout) {
826  done = 1;
827  if (f)
828  ast_frfree(f);
829  break;
830  }
831  }
832 
833  /* Do checks on speech structure to see if it's changed */
834  ast_mutex_lock(&speech->lock);
835  if (ast_test_flag(speech, AST_SPEECH_QUIET)) {
836  if (ast_channel_stream(chan))
837  ast_stopstream(chan);
838  ast_clear_flag(speech, AST_SPEECH_QUIET);
839  quieted = 1;
840  }
841  /* Check state so we can see what to do */
842  switch (speech->state) {
843  case AST_SPEECH_STATE_READY:
844  /* If audio playback has stopped do a check for timeout purposes */
845  if (ast_channel_streamid(chan) == -1 && ast_channel_timingfunc(chan) == NULL)
846  ast_stopstream(chan);
847  if (!quieted && ast_channel_stream(chan) == NULL && timeout && started == 0 && !filename_tmp) {
848  if (timeout == -1) {
849  done = 1;
850  if (f)
851  ast_frfree(f);
852  break;
853  }
854  start = ast_tvnow();
855  started = 1;
856  }
857  /* Write audio frame out to speech engine if no DTMF has been received */
858  if (!strlen(dtmf) && f != NULL && f->frametype == AST_FRAME_VOICE) {
859  ast_speech_write(speech, f->data.ptr, f->datalen);
860  }
861  break;
862  case AST_SPEECH_STATE_WAIT:
863  /* Cue up waiting sound if not already playing */
864  if (!strlen(dtmf)) {
865  if (ast_channel_stream(chan) == NULL) {
866  if (speech->processing_sound != NULL) {
867  if (strlen(speech->processing_sound) > 0 && strcasecmp(speech->processing_sound, "none")) {
868  speech_streamfile(chan, speech->processing_sound, ast_channel_language(chan));
869  }
870  }
871  } else if (ast_channel_streamid(chan) == -1 && ast_channel_timingfunc(chan) == NULL) {
872  ast_stopstream(chan);
873  if (speech->processing_sound != NULL) {
874  if (strlen(speech->processing_sound) > 0 && strcasecmp(speech->processing_sound, "none")) {
875  speech_streamfile(chan, speech->processing_sound, ast_channel_language(chan));
876  }
877  }
878  }
879  }
880  break;
881  case AST_SPEECH_STATE_DONE:
882  /* Now that we are done... let's switch back to not ready state */
883  ast_speech_change_state(speech, AST_SPEECH_STATE_NOT_READY);
884  if (!strlen(dtmf)) {
885  /* Copy to speech structure the results, if available */
886  speech->results = ast_speech_results_get(speech);
887  /* Break out of our background too */
888  done = 1;
889  /* Stop audio playback */
890  if (ast_channel_stream(chan) != NULL) {
891  ast_stopstream(chan);
892  }
893  }
894  break;
895  default:
896  break;
897  }
898  ast_mutex_unlock(&speech->lock);
899 
900  /* Deal with other frame types */
901  if (f != NULL) {
902  /* Free the frame we received */
903  switch (f->frametype) {
904  case AST_FRAME_DTMF:
905  if (dtmf_terminator != '\0' && f->subclass.integer == dtmf_terminator) {
906  done = 1;
907  } else {
908  quieted = 1;
909  if (ast_channel_stream(chan) != NULL) {
910  ast_stopstream(chan);
911  }
912  if (!started) {
913  /* Change timeout to be 5 seconds for DTMF input */
914  timeout = (ast_channel_pbx(chan) && ast_channel_pbx(chan)->dtimeoutms) ? ast_channel_pbx(chan)->dtimeoutms : 5000;
915  started = 1;
916  }
917  start = ast_tvnow();
918  snprintf(tmp, sizeof(tmp), "%c", f->subclass.integer);
919  strncat(dtmf, tmp, sizeof(dtmf) - strlen(dtmf) - 1);
920  /* If the maximum length of the DTMF has been reached, stop now */
921  if (max_dtmf_len && strlen(dtmf) == max_dtmf_len)
922  done = 1;
923  }
924  break;
925  case AST_FRAME_CONTROL:
926  switch (f->subclass.integer) {
927  case AST_CONTROL_HANGUP:
928  /* Since they hung up we should destroy the speech structure */
929  done = 3;
930  default:
931  break;
932  }
933  default:
934  break;
935  }
936  ast_frfree(f);
937  f = NULL;
938  }
939  }
940 
941  if (ast_strlen_zero(dtmf) && speech->state == AST_SPEECH_STATE_READY && ast_test_flag(&options, SB_OPT_PARTIALRESULTS)) {
942  /* Copy to speech structure the results, even partial ones, if desired and available */
943  speech->results = ast_speech_results_get(speech);
944  } else if (!ast_strlen_zero(dtmf)) {
945  /* We sort of make a results entry */
946  speech->results = ast_calloc(1, sizeof(*speech->results));
947  if (speech->results != NULL) {
948  ast_speech_dtmf(speech, dtmf);
949  speech->results->score = 1000;
950  speech->results->text = ast_strdup(dtmf);
951  speech->results->grammar = ast_strdup("dtmf");
952  }
953  ast_speech_change_state(speech, AST_SPEECH_STATE_NOT_READY);
954  }
955 
956  /* See if it was because they hung up */
957  if (done == 3) {
958  speech_datastore_destroy(chan);
959  } else {
960  /* Channel is okay so restore read format */
961  ast_set_read_format(chan, oldreadformat);
962  }
963 
964  return 0;
965 }
966 
967 
968 /*! \brief SpeechDestroy() Dialplan Application */
969 static int speech_destroy(struct ast_channel *chan, const char *data)
970 {
971  if (!chan) {
972  return -1;
973  }
974  return speech_datastore_destroy(chan);
975 }
976 
977 static int unload_module(void)
978 {
979  int res = 0;
980 
981  res = ast_unregister_application("SpeechCreate");
982  res |= ast_unregister_application("SpeechLoadGrammar");
983  res |= ast_unregister_application("SpeechUnloadGrammar");
984  res |= ast_unregister_application("SpeechActivateGrammar");
985  res |= ast_unregister_application("SpeechDeactivateGrammar");
986  res |= ast_unregister_application("SpeechStart");
987  res |= ast_unregister_application("SpeechBackground");
988  res |= ast_unregister_application("SpeechDestroy");
989  res |= ast_unregister_application("SpeechProcessingSound");
990  res |= ast_custom_function_unregister(&speech_function);
991  res |= ast_custom_function_unregister(&speech_score_function);
992  res |= ast_custom_function_unregister(&speech_text_function);
993  res |= ast_custom_function_unregister(&speech_grammar_function);
994  res |= ast_custom_function_unregister(&speech_engine_function);
995  res |= ast_custom_function_unregister(&speech_results_type_function);
996 
997  return res;
998 }
999 
1000 static int load_module(void)
1001 {
1002  int res = 0;
1003 
1004  res = ast_register_application_xml("SpeechCreate", speech_create);
1005  res |= ast_register_application_xml("SpeechLoadGrammar", speech_load);
1006  res |= ast_register_application_xml("SpeechUnloadGrammar", speech_unload);
1007  res |= ast_register_application_xml("SpeechActivateGrammar", speech_activate);
1008  res |= ast_register_application_xml("SpeechDeactivateGrammar", speech_deactivate);
1009  res |= ast_register_application_xml("SpeechStart", speech_start);
1010  res |= ast_register_application_xml("SpeechBackground", speech_background);
1011  res |= ast_register_application_xml("SpeechDestroy", speech_destroy);
1012  res |= ast_register_application_xml("SpeechProcessingSound", speech_processing_sound);
1013  res |= ast_custom_function_register(&speech_function);
1014  res |= ast_custom_function_register(&speech_score_function);
1015  res |= ast_custom_function_register(&speech_text_function);
1016  res |= ast_custom_function_register(&speech_grammar_function);
1017  res |= ast_custom_function_register(&speech_engine_function);
1018  res |= ast_custom_function_register(&speech_results_type_function);
1019 
1020  return res;
1021 }
1022 
1023 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Dialplan Speech Applications",
1024  .support_level = AST_MODULE_SUPPORT_CORE,
1025  .load = load_module,
1026  .unload = unload_module,
1027  .requires = "res_speech",
1028 );
const char * name
Definition: pbx.h:119
const char * type
Definition: datastore.h:32
int state
Definition: speech.h:62
Main Channel structure associated with a channel.
Asterisk locking-related definitions:
Asterisk main include file. File version handling, generic pbx functions.
static int speech_create(struct ast_channel *chan, const char *data)
SpeechCreate() Dialplan Application.
Generic Speech Recognition API.
static int speech_unload(struct ast_channel *chan, const char *data)
SpeechUnloadGrammar(Grammar Name) Dialplan Application.
int ast_speech_destroy(struct ast_speech *speech)
Destroy a speech structure.
Definition: res_speech.c:251
void ast_speech_start(struct ast_speech *speech)
Indicate to the speech engine that audio is now going to start being written.
Definition: res_speech.c:122
int ast_sched_runq(struct ast_sched_context *con)
Runs the queue.
Definition: sched.c:786
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the 'standard' argument separation process for an application.
static int speech_background(struct ast_channel *chan, const char *data)
SpeechBackground(Sound File,Timeout) Dialplan Application.
static int speech_text(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
SPEECH_TEXT() Dialplan Function.
#define AST_LIST_NEXT(elm, field)
Returns the next entry in the list after the given entry.
Definition: linkedlists.h:439
struct ast_frame * ast_read(struct ast_channel *chan)
Reads a frame.
Definition: channel.c:4257
Structure for a data store type.
Definition: datastore.h:31
ast_channel_state
ast_channel states
Definition: channelstate.h:35
struct ast_speech_result * ast_speech_results_get(struct ast_speech *speech)
Get speech recognition results.
Definition: res_speech.c:90
static int speech_streamfile(struct ast_channel *chan, const char *filename, const char *preflang)
Helper function used by speech_background to playback a soundfile.
Definition of a media format.
Definition: format.c:43
void * data
Definition: speech.h:66
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
Definition: time.h:159
int64_t ast_tvdiff_ms(struct timeval end, struct timeval start)
Computes the difference (in milliseconds) between two struct timeval instances.
Definition: time.h:107
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:241
Structure for a data store object.
Definition: datastore.h:64
static int speech_engine_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
SPEECH_ENGINE() Dialplan Get Function.
struct ast_datastore * ast_channel_datastore_find(struct ast_channel *chan, const struct ast_datastore_info *info, const char *uid)
Find a datastore on a channel.
Definition: channel.c:2399
Generic File Format Support. Should be included by clients of the file handling routines. File service providers should instead include mod_format.h.
static int speech_load(struct ast_channel *chan, const char *vdata)
SpeechLoadGrammar(Grammar Name,Path) Dialplan Application.
int ast_unregister_application(const char *app)
Unregister an application.
Definition: pbx_app.c:392
static int speech_destroy(struct ast_channel *chan, const char *data)
SpeechDestroy() Dialplan Application.
static int speech_results_type_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
SPEECH_RESULTS_TYPE() Dialplan Function.
int ast_custom_function_unregister(struct ast_custom_function *acf)
Unregister a custom function.
int ast_datastore_free(struct ast_datastore *datastore)
Free a data store object.
Definition: datastore.c:68
char * grammar
Definition: speech.h:119
const char * pbx_builtin_getvar_helper(struct ast_channel *chan, const char *name)
Return a pointer to the value of the corresponding channel variable.
struct ast_frame_subclass subclass
int ast_speech_change_results_type(struct ast_speech *speech, enum ast_speech_results_type results_type)
Change the type of results we want.
Definition: res_speech.c:308
#define AST_APP_OPTIONS(holder, options...)
Declares an array of options for an application.
#define ao2_bump(obj)
Bump refcount on an AO2 object by one, returning the object.
Definition: astobj2.h:480
int ast_speech_grammar_deactivate(struct ast_speech *speech, const char *grammar_name)
Deactivate a grammar on a speech structure.
Definition: res_speech.c:72
int ast_playstream(struct ast_filestream *s)
Play a open stream on a channel.
Definition: file.c:1063
General Asterisk PBX channel definitions.
int ast_set_read_format(struct ast_channel *chan, struct ast_format *format)
Sets read format on channel chan.
Definition: channel.c:5762
int ast_speech_change_state(struct ast_speech *speech, int state)
Change state of a speech structure.
Definition: res_speech.c:278
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
static struct ast_speech * find_speech(struct ast_channel *chan)
Helper function used to find the speech structure attached to a channel.
Data structure associated with a custom dialplan function.
Definition: pbx.h:118
#define AST_MAX_EXTENSION
Definition: channel.h:134
static int speech_deactivate(struct ast_channel *chan, const char *data)
SpeechDeactivateGrammar(Grammar Name) Dialplan Application.
char * ast_strsep(char **s, const char sep, uint32_t flags)
Act like strsep but ignore separators inside quotes.
Definition: utils.c:1835
int ast_speech_grammar_unload(struct ast_speech *speech, const char *grammar_name)
Unload a grammar.
Definition: res_speech.c:84
struct ast_speech_result * results
Definition: speech.h:68
int ast_app_parse_options(const struct ast_app_option *options, struct ast_flags *flags, char **args, char *optstr)
Parses a string containing application options and sets flags/arguments.
Definition: main/app.c:3066
Core PBX routines and definitions.
static int speech_activate(struct ast_channel *chan, const char *data)
SpeechActivateGrammar(Grammar Name) Dialplan Application.
static int speech_engine_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
SPEECH_ENGINE() Dialplan Set Function.
static int speech_start(struct ast_channel *chan, const char *data)
SpeechStart() Dialplan Application.
int ast_applystream(struct ast_channel *chan, struct ast_filestream *s)
Applies a open stream to a channel.
Definition: file.c:1057
static void destroy_callback(void *data)
Helper function used by datastores to destroy the speech structure upon hangup.
int ast_speech_grammar_activate(struct ast_speech *speech, const char *grammar_name)
Activate a grammar on a speech structure.
Definition: res_speech.c:66
static int speech_processing_sound(struct ast_channel *chan, const char *data)
SpeechProcessingSound(Sound File) Dialplan Application.
union ast_frame::@224 data
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
struct ast_format * format
Definition: speech.h:64
Structure used to handle boolean flags.
Definition: utils.h:199
int pbx_builtin_setvar_helper(struct ast_channel *chan, const char *name, const char *value)
Add a variable to the channel variable stack, removing the most recently set value for the same name...
static const struct ast_datastore_info speech_datastore
Static structure for datastore information.
ast_mutex_t lock
Definition: speech.h:56
void * data
Definition: datastore.h:66
This structure is allocated by file.c in one chunk, together with buf_size and desc_size bytes of mem...
Definition: mod_format.h:101
int ast_waitfor(struct ast_channel *chan, int ms)
Wait for input on a channel.
Definition: channel.c:3162
int dtimeoutms
Definition: pbx.h:215
int ast_speech_dtmf(struct ast_speech *speech, const char *dtmf)
Signal to the engine that DTMF was received.
Definition: res_speech.c:154
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:425
struct ast_speech * ast_speech_new(const char *engine_name, const struct ast_format_cap *formats)
Create a new speech structure.
Definition: res_speech.c:181
int ast_answer(struct ast_channel *chan)
Answer a channel.
Definition: channel.c:2805
int ast_sched_wait(struct ast_sched_context *con) attribute_warn_unused_result
Determines number of seconds until the next outstanding event to take place.
Definition: sched.c:433
Data structure associated with a single frame of data.
char * processing_sound
Definition: speech.h:60
static int speech_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
SPEECH() Dialplan Function.
enum ast_frame_type frametype
#define AST_APP_OPTION(option, flagno)
Declares an application option that does not accept an argument.
int ast_speech_write(struct ast_speech *speech, void *data, int len)
Write audio to the speech engine.
Definition: res_speech.c:144
int ast_speech_grammar_load(struct ast_speech *speech, const char *grammar_name, const char *grammar)
Load a grammar on a speech structure (not globally)
Definition: res_speech.c:78
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
int ast_speech_get_setting(struct ast_speech *speech, const char *name, char *buf, size_t len)
Get an engine specific attribute.
Definition: res_speech.c:175
Asterisk module definitions.
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
Definition: utils.h:941
int ast_channel_datastore_add(struct ast_channel *chan, struct ast_datastore *datastore)
Add a datastore to a channel.
Definition: channel.c:2385
int ast_speech_change(struct ast_speech *speech, const char *name, const char *value)
Change an engine specific attribute.
Definition: res_speech.c:169
#define AST_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application's arguments.
Application convenience functions, designed to give consistent look and feel to Asterisk apps...
int ast_channel_datastore_remove(struct ast_channel *chan, struct ast_datastore *datastore)
Remove a datastore from a channel.
Definition: channel.c:2394
#define ast_custom_function_register(acf)
Register a custom function.
Definition: pbx.h:1558
static int speech_grammar(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
SPEECH_GRAMMAR() Dialplan Function.
int ast_stopstream(struct ast_channel *c)
Stops a stream.
Definition: file.c:222
#define ast_register_application_xml(app, execute)
Register an application using XML documentation.
Definition: module.h:640
static int speech_score(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
SPEECH_SCORE() Dialplan Function.
struct ast_filestream * ast_openstream(struct ast_channel *chan, const char *filename, const char *preflang)
Opens stream for use in seeking, playing.
Definition: file.c:790
#define AST_APP_ARG(name)
Define an application argument.