#include "asterisk.h"
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <errno.h>
#include <sys/capability.h>
#include "asterisk/file.h"
#include "asterisk/channel.h"
#include "asterisk/pbx.h"
#include "asterisk/module.h"
#include "asterisk/md5.h"
#include "asterisk/config.h"
#include "asterisk/utils.h"
#include "asterisk/lock.h"
#include "asterisk/app.h"
Go to the source code of this file.
Defines | |
#define | FESTIVAL_CONFIG "festival.conf" |
#define | MAXFESTLEN 2048 |
#define | MAXLEN 180 |
Functions | |
static void | __reg_module (void) |
static void | __unreg_module (void) |
static int | festival_exec (struct ast_channel *chan, void *vdata) |
static int | load_module (void) |
static int | send_waveform_to_channel (struct ast_channel *chan, char *waveform, int length, char *intkeys) |
static int | send_waveform_to_fd (char *waveform, int length, int fd) |
static char * | socket_receive_file_to_buff (int fd, int *size) |
static int | unload_module (void) |
Variables | |
static struct ast_module_info | __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_DEFAULT , .description = "Simple Festival Interface" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = "068e67f60f50dd9ee86464c05884a49d" , .load = load_module, .unload = unload_module, } |
static char * | app = "Festival" |
static const struct ast_module_info * | ast_module_info = &__mod_info |
static char * | descrip |
static char * | synopsis = "Say text to the user" |
Definition in file app_festival.c.
#define FESTIVAL_CONFIG "festival.conf" |
#define MAXFESTLEN 2048 |
#define MAXLEN 180 |
Definition at line 61 of file app_festival.c.
static void __reg_module | ( | void | ) | [static] |
Definition at line 569 of file app_festival.c.
static void __unreg_module | ( | void | ) | [static] |
Definition at line 569 of file app_festival.c.
static int festival_exec | ( | struct ast_channel * | chan, | |
void * | vdata | |||
) | [static] |
Definition at line 287 of file app_festival.c.
References ahp, AST_APP_ARG, ast_config_destroy(), ast_config_load, ast_debug, AST_DECLARE_APP_ARGS, AST_DIGIT_ANY, AST_FILE_MODE, ast_free, ast_gethostbyname(), ast_log(), AST_STANDARD_APP_ARGS, ast_strdupa, ast_strlen_zero(), ast_true(), ast_variable_retrieve(), chan, errno, FESTIVAL_CONFIG, LOG_WARNING, MAXFESTLEN, MD5Final(), MD5Init(), MD5Update(), send_waveform_to_channel(), socket_receive_file_to_buff(), text, and wave.
Referenced by load_module().
00288 { 00289 int usecache; 00290 int res = 0; 00291 struct sockaddr_in serv_addr; 00292 struct hostent *serverhost; 00293 struct ast_hostent ahp; 00294 int fd; 00295 FILE *fs; 00296 const char *host; 00297 const char *cachedir; 00298 const char *temp; 00299 const char *festivalcommand; 00300 int port = 1314; 00301 int n; 00302 char ack[4]; 00303 char *waveform; 00304 int filesize; 00305 int wave; 00306 char bigstring[MAXFESTLEN]; 00307 int i; 00308 struct MD5Context md5ctx; 00309 unsigned char MD5Res[16]; 00310 char MD5Hex[33] = ""; 00311 char koko[4] = ""; 00312 char cachefile[MAXFESTLEN]=""; 00313 int readcache = 0; 00314 int writecache = 0; 00315 int strln; 00316 int fdesc = -1; 00317 char buffer[16384]; 00318 int seekpos = 0; 00319 char *data; 00320 struct ast_config *cfg; 00321 char *newfestivalcommand; 00322 struct ast_flags config_flags = { 0 }; 00323 AST_DECLARE_APP_ARGS(args, 00324 AST_APP_ARG(text); 00325 AST_APP_ARG(interrupt); 00326 ); 00327 00328 if (ast_strlen_zero(vdata)) { 00329 ast_log(LOG_WARNING, "festival requires an argument (text)\n"); 00330 return -1; 00331 } 00332 00333 cfg = ast_config_load(FESTIVAL_CONFIG, config_flags); 00334 if (!cfg) { 00335 ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG); 00336 return -1; 00337 } 00338 if (!(host = ast_variable_retrieve(cfg, "general", "host"))) { 00339 host = "localhost"; 00340 } 00341 if (!(temp = ast_variable_retrieve(cfg, "general", "port"))) { 00342 port = 1314; 00343 } else { 00344 port = atoi(temp); 00345 } 00346 if (!(temp = ast_variable_retrieve(cfg, "general", "usecache"))) { 00347 usecache = 0; 00348 } else { 00349 usecache = ast_true(temp); 00350 } 00351 if (!(cachedir = ast_variable_retrieve(cfg, "general", "cachedir"))) { 00352 cachedir = "/tmp/"; 00353 } 00354 00355 data = ast_strdupa(vdata); 00356 AST_STANDARD_APP_ARGS(args, data); 00357 00358 if (!(festivalcommand = ast_variable_retrieve(cfg, "general", "festivalcommand"))) { 00359 const char *startcmd = "(tts_textasterisk \""; 00360 const char *endcmd = "\" 'file)(quit)\n"; 00361 00362 strln = strlen(startcmd) + strlen(args.text) + strlen(endcmd) + 1; 00363 newfestivalcommand = alloca(strln); 00364 snprintf(newfestivalcommand, strln, "%s%s%s", startcmd, args.text, endcmd); 00365 festivalcommand = newfestivalcommand; 00366 } else { /* This else parses the festivalcommand that we're sent from the config file for \n's, etc */ 00367 int i, j; 00368 newfestivalcommand = alloca(strlen(festivalcommand) + strlen(args.text) + 1); 00369 00370 for (i = 0, j = 0; i < strlen(festivalcommand); i++) { 00371 if (festivalcommand[i] == '\\' && festivalcommand[i + 1] == 'n') { 00372 newfestivalcommand[j++] = '\n'; 00373 i++; 00374 } else if (festivalcommand[i] == '\\') { 00375 newfestivalcommand[j++] = festivalcommand[i + 1]; 00376 i++; 00377 } else if (festivalcommand[i] == '%' && festivalcommand[i + 1] == 's') { 00378 sprintf(&newfestivalcommand[j], "%s", args.text); /* we know it is big enough */ 00379 j += strlen(args.text); 00380 i++; 00381 } else 00382 newfestivalcommand[j++] = festivalcommand[i]; 00383 } 00384 newfestivalcommand[j] = '\0'; 00385 festivalcommand = newfestivalcommand; 00386 } 00387 00388 if (args.interrupt && !strcasecmp(args.interrupt, "any")) 00389 args.interrupt = AST_DIGIT_ANY; 00390 00391 ast_debug(1, "Text passed to festival server : %s\n", args.text); 00392 /* Connect to local festival server */ 00393 00394 fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 00395 00396 if (fd < 0) { 00397 ast_log(LOG_WARNING, "festival_client: can't get socket\n"); 00398 ast_config_destroy(cfg); 00399 return -1; 00400 } 00401 00402 memset(&serv_addr, 0, sizeof(serv_addr)); 00403 00404 if ((serv_addr.sin_addr.s_addr = inet_addr(host)) == -1) { 00405 /* its a name rather than an ipnum */ 00406 serverhost = ast_gethostbyname(host, &ahp); 00407 00408 if (serverhost == NULL) { 00409 ast_log(LOG_WARNING, "festival_client: gethostbyname failed\n"); 00410 ast_config_destroy(cfg); 00411 return -1; 00412 } 00413 memmove(&serv_addr.sin_addr, serverhost->h_addr, serverhost->h_length); 00414 } 00415 00416 serv_addr.sin_family = AF_INET; 00417 serv_addr.sin_port = htons(port); 00418 00419 if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0) { 00420 ast_log(LOG_WARNING, "festival_client: connect to server failed\n"); 00421 ast_config_destroy(cfg); 00422 return -1; 00423 } 00424 00425 /* Compute MD5 sum of string */ 00426 MD5Init(&md5ctx); 00427 MD5Update(&md5ctx, (unsigned char *)args.text, strlen(args.text)); 00428 MD5Final(MD5Res, &md5ctx); 00429 MD5Hex[0] = '\0'; 00430 00431 /* Convert to HEX and look if there is any matching file in the cache 00432 directory */ 00433 for (i = 0; i < 16; i++) { 00434 snprintf(koko, sizeof(koko), "%X", MD5Res[i]); 00435 strncat(MD5Hex, koko, sizeof(MD5Hex) - strlen(MD5Hex) - 1); 00436 } 00437 readcache = 0; 00438 writecache = 0; 00439 if (strlen(cachedir) + strlen(MD5Hex) + 1 <= MAXFESTLEN && (usecache == -1)) { 00440 snprintf(cachefile, sizeof(cachefile), "%s/%s", cachedir, MD5Hex); 00441 fdesc = open(cachefile, O_RDWR); 00442 if (fdesc == -1) { 00443 fdesc = open(cachefile, O_CREAT | O_RDWR, AST_FILE_MODE); 00444 if (fdesc != -1) { 00445 writecache = 1; 00446 strln = strlen(args.text); 00447 ast_debug(1, "line length : %d\n", strln); 00448 if (write(fdesc,&strln,sizeof(int)) < 0) { 00449 ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno)); 00450 } 00451 if (write(fdesc,data,strln) < 0) { 00452 ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno)); 00453 } 00454 seekpos = lseek(fdesc, 0, SEEK_CUR); 00455 ast_debug(1, "Seek position : %d\n", seekpos); 00456 } 00457 } else { 00458 if (read(fdesc,&strln,sizeof(int)) != sizeof(int)) { 00459 ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno)); 00460 } 00461 ast_debug(1, "Cache file exists, strln=%d, strlen=%d\n", strln, (int)strlen(args.text)); 00462 if (strlen(args.text) == strln) { 00463 ast_debug(1, "Size OK\n"); 00464 if (read(fdesc,&bigstring,strln) != strln) { 00465 ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno)); 00466 } 00467 bigstring[strln] = 0; 00468 if (strcmp(bigstring, args.text) == 0) { 00469 readcache = 1; 00470 } else { 00471 ast_log(LOG_WARNING, "Strings do not match\n"); 00472 } 00473 } else { 00474 ast_log(LOG_WARNING, "Size mismatch\n"); 00475 } 00476 } 00477 } 00478 00479 if (readcache == 1) { 00480 close(fd); 00481 fd = fdesc; 00482 ast_debug(1, "Reading from cache...\n"); 00483 } else { 00484 ast_debug(1, "Passing text to festival...\n"); 00485 fs = fdopen(dup(fd), "wb"); 00486 00487 fprintf(fs, "%s", festivalcommand); 00488 fflush(fs); 00489 fclose(fs); 00490 } 00491 00492 /* Write to cache and then pass it down */ 00493 if (writecache == 1) { 00494 ast_debug(1, "Writing result to cache...\n"); 00495 while ((strln = read(fd, buffer, 16384)) != 0) { 00496 if (write(fdesc,buffer,strln) < 0) { 00497 ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno)); 00498 } 00499 } 00500 close(fd); 00501 close(fdesc); 00502 fd = open(cachefile, O_RDWR); 00503 lseek(fd, seekpos, SEEK_SET); 00504 } 00505 00506 ast_debug(1, "Passing data to channel...\n"); 00507 00508 /* Read back info from server */ 00509 /* This assumes only one waveform will come back, also LP is unlikely */ 00510 wave = 0; 00511 do { 00512 int read_data; 00513 for (n = 0; n < 3; ) { 00514 read_data = read(fd, ack + n, 3 - n); 00515 /* this avoids falling in infinite loop 00516 * in case that festival server goes down 00517 */ 00518 if (read_data == -1) { 00519 ast_log(LOG_WARNING, "Unable to read from cache/festival fd\n"); 00520 close(fd); 00521 ast_config_destroy(cfg); 00522 return -1; 00523 } 00524 n += read_data; 00525 } 00526 ack[3] = '\0'; 00527 if (strcmp(ack, "WV\n") == 0) { /* receive a waveform */ 00528 ast_debug(1, "Festival WV command\n"); 00529 if ((waveform = socket_receive_file_to_buff(fd, &filesize))) { 00530 res = send_waveform_to_channel(chan, waveform, filesize, args.interrupt); 00531 ast_free(waveform); 00532 } 00533 break; 00534 } else if (strcmp(ack, "LP\n") == 0) { /* receive an s-expr */ 00535 ast_debug(1, "Festival LP command\n"); 00536 if ((waveform = socket_receive_file_to_buff(fd, &filesize))) { 00537 waveform[filesize] = '\0'; 00538 ast_log(LOG_WARNING, "Festival returned LP : %s\n", waveform); 00539 ast_free(waveform); 00540 } 00541 } else if (strcmp(ack, "ER\n") == 0) { /* server got an error */ 00542 ast_log(LOG_WARNING, "Festival returned ER\n"); 00543 res = -1; 00544 break; 00545 } 00546 } while (strcmp(ack, "OK\n") != 0); 00547 close(fd); 00548 ast_config_destroy(cfg); 00549 return res; 00550 }
static int load_module | ( | void | ) | [static] |
Definition at line 557 of file app_festival.c.
References ast_config_destroy(), ast_config_load, ast_log(), AST_MODULE_LOAD_DECLINE, ast_register_application, FESTIVAL_CONFIG, festival_exec(), and LOG_WARNING.
00558 { 00559 struct ast_flags config_flags = { 0 }; 00560 struct ast_config *cfg = ast_config_load(FESTIVAL_CONFIG, config_flags); 00561 if (!cfg) { 00562 ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG); 00563 return AST_MODULE_LOAD_DECLINE; 00564 } 00565 ast_config_destroy(cfg); 00566 return ast_register_application(app, festival_exec, synopsis, descrip); 00567 }
static int send_waveform_to_channel | ( | struct ast_channel * | chan, | |
char * | waveform, | |||
int | length, | |||
char * | intkeys | |||
) | [static] |
Definition at line 180 of file app_festival.c.
References ast_channel::_state, ast_answer(), ast_debug, AST_FORMAT_SLINEAR, AST_FRAME_DTMF, AST_FRAME_VOICE, ast_frfree, AST_FRIENDLY_OFFSET, ast_indicate(), ast_log(), ast_read(), ast_set_write_format(), AST_STATE_UP, ast_stopstream(), ast_waitfor(), ast_write(), chan, f, LOG_WARNING, ast_frame::offset, and send_waveform_to_fd().
Referenced by festival_exec().
00181 { 00182 int res = 0; 00183 int fds[2]; 00184 int pid = -1; 00185 int needed = 0; 00186 int owriteformat; 00187 struct ast_frame *f; 00188 struct myframe { 00189 struct ast_frame f; 00190 char offset[AST_FRIENDLY_OFFSET]; 00191 char frdata[2048]; 00192 } myf = { 00193 .f = { 0, }, 00194 }; 00195 00196 if (pipe(fds)) { 00197 ast_log(LOG_WARNING, "Unable to create pipe\n"); 00198 return -1; 00199 } 00200 00201 /* Answer if it's not already going */ 00202 if (chan->_state != AST_STATE_UP) 00203 ast_answer(chan); 00204 ast_stopstream(chan); 00205 ast_indicate(chan, -1); 00206 00207 owriteformat = chan->writeformat; 00208 res = ast_set_write_format(chan, AST_FORMAT_SLINEAR); 00209 if (res < 0) { 00210 ast_log(LOG_WARNING, "Unable to set write format to signed linear\n"); 00211 return -1; 00212 } 00213 00214 res = send_waveform_to_fd(waveform, length, fds[1]); 00215 if (res >= 0) { 00216 pid = res; 00217 /* Order is important -- there's almost always going to be mp3... we want to prioritize the 00218 user */ 00219 for (;;) { 00220 res = ast_waitfor(chan, 1000); 00221 if (res < 1) { 00222 res = -1; 00223 break; 00224 } 00225 f = ast_read(chan); 00226 if (!f) { 00227 ast_log(LOG_WARNING, "Null frame == hangup() detected\n"); 00228 res = -1; 00229 break; 00230 } 00231 if (f->frametype == AST_FRAME_DTMF) { 00232 ast_debug(1, "User pressed a key\n"); 00233 if (intkeys && strchr(intkeys, f->subclass)) { 00234 res = f->subclass; 00235 ast_frfree(f); 00236 break; 00237 } 00238 } 00239 if (f->frametype == AST_FRAME_VOICE) { 00240 /* Treat as a generator */ 00241 needed = f->samples * 2; 00242 if (needed > sizeof(myf.frdata)) { 00243 ast_log(LOG_WARNING, "Only able to deliver %d of %d requested samples\n", 00244 (int)sizeof(myf.frdata) / 2, needed/2); 00245 needed = sizeof(myf.frdata); 00246 } 00247 res = read(fds[0], myf.frdata, needed); 00248 if (res > 0) { 00249 myf.f.frametype = AST_FRAME_VOICE; 00250 myf.f.subclass = AST_FORMAT_SLINEAR; 00251 myf.f.datalen = res; 00252 myf.f.samples = res / 2; 00253 myf.f.offset = AST_FRIENDLY_OFFSET; 00254 myf.f.src = __PRETTY_FUNCTION__; 00255 myf.f.data = myf.frdata; 00256 if (ast_write(chan, &myf.f) < 0) { 00257 res = -1; 00258 ast_frfree(f); 00259 break; 00260 } 00261 if (res < needed) { /* last frame */ 00262 ast_debug(1, "Last frame\n"); 00263 res = 0; 00264 ast_frfree(f); 00265 break; 00266 } 00267 } else { 00268 ast_debug(1, "No more waveform\n"); 00269 res = 0; 00270 } 00271 } 00272 ast_frfree(f); 00273 } 00274 } 00275 close(fds[0]); 00276 close(fds[1]); 00277 00278 #if 0 00279 if (pid > -1) 00280 kill(pid, SIGKILL); 00281 #endif 00282 if (!res && owriteformat) 00283 ast_set_write_format(chan, owriteformat); 00284 return res; 00285 }
static int send_waveform_to_fd | ( | char * | waveform, | |
int | length, | |||
int | fd | |||
) | [static] |
Definition at line 125 of file app_festival.c.
References ast_log(), ast_opt_high_priority, ast_set_priority(), errno, and LOG_WARNING.
Referenced by send_waveform_to_channel().
00126 { 00127 int res; 00128 int x; 00129 #ifdef __PPC__ 00130 char c; 00131 #endif 00132 sigset_t fullset, oldset; 00133 #ifdef HAVE_CAP 00134 cap_t cap; 00135 #endif 00136 00137 sigfillset(&fullset); 00138 pthread_sigmask(SIG_BLOCK, &fullset, &oldset); 00139 00140 res = fork(); 00141 if (res < 0) 00142 ast_log(LOG_WARNING, "Fork failed\n"); 00143 if (res) { 00144 pthread_sigmask(SIG_SETMASK, &oldset, NULL); 00145 return res; 00146 } 00147 #ifdef HAVE_CAP 00148 cap = cap_from_text("cap_net_admin-eip"); 00149 00150 if (cap_set_proc(cap)) { 00151 /* Careful with order! Logging cannot happen after we close FDs */ 00152 ast_log(LOG_WARNING, "Unable to remove capabilities.\n"); 00153 } 00154 cap_free(cap); 00155 #endif 00156 for (x = 0; x < 256; x++) { 00157 if (x != fd) 00158 close(x); 00159 } 00160 if (ast_opt_high_priority) 00161 ast_set_priority(0); 00162 signal(SIGPIPE, SIG_DFL); 00163 pthread_sigmask(SIG_UNBLOCK, &fullset, NULL); 00164 #ifdef __PPC__ 00165 for (x = 0; x < length; x += 2) { 00166 c = *(waveform + x + 1); 00167 *(waveform + x + 1) = *(waveform + x); 00168 *(waveform + x) = c; 00169 } 00170 #endif 00171 00172 if (write(fd, waveform, length) < 0) { 00173 ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno)); 00174 } 00175 00176 close(fd); 00177 exit(0); 00178 }
static char* socket_receive_file_to_buff | ( | int | fd, | |
int * | size | |||
) | [static] |
Definition at line 74 of file app_festival.c.
References ast_free, ast_malloc, ast_realloc, and buff.
Referenced by festival_exec().
00075 { 00076 /* Receive file (probably a waveform file) from socket using 00077 * Festival key stuff technique, but long winded I know, sorry 00078 * but will receive any file without closing the stream or 00079 * using OOB data 00080 */ 00081 static char *file_stuff_key = "ft_StUfF_key"; /* must == Festival's key */ 00082 char *buff, *tmp; 00083 int bufflen; 00084 int n,k,i; 00085 char c; 00086 00087 bufflen = 1024; 00088 if (!(buff = ast_malloc(bufflen))) 00089 return NULL; 00090 *size = 0; 00091 00092 for (k = 0; file_stuff_key[k] != '\0';) { 00093 n = read(fd, &c, 1); 00094 if (n == 0) 00095 break; /* hit stream eof before end of file */ 00096 if ((*size) + k + 1 >= bufflen) { 00097 /* +1 so you can add a terminating NULL if you want */ 00098 bufflen += bufflen / 4; 00099 if (!(tmp = ast_realloc(buff, bufflen))) { 00100 ast_free(buff); 00101 return NULL; 00102 } 00103 buff = tmp; 00104 } 00105 if (file_stuff_key[k] == c) 00106 k++; 00107 else if ((c == 'X') && (file_stuff_key[k+1] == '\0')) { 00108 /* It looked like the key but wasn't */ 00109 for (i = 0; i < k; i++, (*size)++) 00110 buff[*size] = file_stuff_key[i]; 00111 k = 0; 00112 /* omit the stuffed 'X' */ 00113 } else { 00114 for (i = 0; i < k; i++, (*size)++) 00115 buff[*size] = file_stuff_key[i]; 00116 k = 0; 00117 buff[*size] = c; 00118 (*size)++; 00119 } 00120 } 00121 00122 return buff; 00123 }
static int unload_module | ( | void | ) | [static] |
Definition at line 552 of file app_festival.c.
References ast_unregister_application().
00553 { 00554 return ast_unregister_application(app); 00555 }
struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_DEFAULT , .description = "Simple Festival Interface" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = "068e67f60f50dd9ee86464c05884a49d" , .load = load_module, .unload = unload_module, } [static] |
Definition at line 569 of file app_festival.c.
char* app = "Festival" [static] |
Definition at line 64 of file app_festival.c.
const struct ast_module_info* ast_module_info = &__mod_info [static] |
Definition at line 569 of file app_festival.c.
char* descrip [static] |
Initial value:
" Festival(text[,intkeys]): Connect to Festival, send the argument, get back the waveform,\n" "play it to the user, allowing any given interrupt keys to immediately terminate and return\n" "the value, or 'any' to allow any number back (useful in dialplan)\n"
Definition at line 68 of file app_festival.c.
Definition at line 66 of file app_festival.c.