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