2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 2002, Christos Ricudis
6 * Christos Ricudis <ricudis@itc.auth.gr>
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.
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.
21 * \brief Connect to festival
23 * \author Christos Ricudis <ricudis@itc.auth.gr>
25 * \ingroup applications
30 ASTERISK_FILE_VERSION(__FILE__
, "$Revision$")
32 #include <sys/types.h>
37 #include <sys/types.h>
38 #include <sys/socket.h>
40 #include <netinet/in.h>
41 #include <arpa/inet.h>
49 #include "asterisk/file.h"
50 #include "asterisk/logger.h"
51 #include "asterisk/channel.h"
52 #include "asterisk/pbx.h"
53 #include "asterisk/module.h"
54 #include "asterisk/md5.h"
55 #include "asterisk/config.h"
56 #include "asterisk/utils.h"
57 #include "asterisk/lock.h"
58 #include "asterisk/options.h"
60 #define FESTIVAL_CONFIG "festival.conf"
62 static char *app
= "Festival";
64 static char *synopsis
= "Say text to the user";
66 static char *descrip
=
67 " Festival(text[|intkeys]): Connect to Festival, send the argument, get back the waveform,"
68 "play it to the user, allowing any given interrupt keys to immediately terminate and return\n"
69 "the value, or 'any' to allow any number back (useful in dialplan)\n";
72 static char *socket_receive_file_to_buff(int fd
,int *size
)
74 /* Receive file (probably a waveform file) from socket using */
75 /* Festival key stuff technique, but long winded I know, sorry */
76 /* but will receive any file without closeing the stream or */
78 static char *file_stuff_key
= "ft_StUfF_key"; /* must == Festival's key */
85 if (!(buff
= ast_malloc(bufflen
)))
87 /* TODO: Handle memory allocation failure */
91 for (k
=0; file_stuff_key
[k
] != '\0';)
94 if (n
==0) break; /* hit stream eof before end of file */
95 if ((*size
)+k
+1 >= bufflen
)
96 { /* +1 so you can add a NULL if you want */
98 if (!(buff
= ast_realloc(buff
, bufflen
)))
100 /* TODO: Handle memory allocation failure */
103 if (file_stuff_key
[k
] == c
)
105 else if ((c
== 'X') && (file_stuff_key
[k
+1] == '\0'))
106 { /* It looked like the key but wasn't */
107 for (i
=0; i
< k
; i
++,(*size
)++)
108 buff
[*size
] = file_stuff_key
[i
];
110 /* omit the stuffed 'X' */
114 for (i
=0; i
< k
; i
++,(*size
)++)
115 buff
[*size
] = file_stuff_key
[i
];
126 static int send_waveform_to_fd(char *waveform
, int length
, int fd
) {
133 sigset_t fullset
, oldset
;
135 sigfillset(&fullset
);
136 pthread_sigmask(SIG_BLOCK
, &fullset
, &oldset
);
140 ast_log(LOG_WARNING
, "Fork failed\n");
142 pthread_sigmask(SIG_SETMASK
, &oldset
, NULL
);
145 for (x
=0;x
<256;x
++) {
149 if (ast_opt_high_priority
)
151 signal(SIGPIPE
, SIG_DFL
);
152 pthread_sigmask(SIG_UNBLOCK
, &fullset
, NULL
);
155 for( x
=0; x
<length
; x
+=2)
158 *(waveform
+x
+1)=*(waveform
+x
);
163 write(fd
,waveform
,length
);
169 static int send_waveform_to_channel(struct ast_channel
*chan
, char *waveform
, int length
, char *intkeys
) {
179 char offset
[AST_FRIENDLY_OFFSET
];
186 ast_log(LOG_WARNING
, "Unable to create pipe\n");
190 /* Answer if it's not already going */
191 if (chan
->_state
!= AST_STATE_UP
)
193 ast_stopstream(chan
);
194 ast_indicate(chan
, -1);
196 owriteformat
= chan
->writeformat
;
197 res
= ast_set_write_format(chan
, AST_FORMAT_SLINEAR
);
199 ast_log(LOG_WARNING
, "Unable to set write format to signed linear\n");
203 res
=send_waveform_to_fd(waveform
,length
,fds
[1]);
206 /* Order is important -- there's almost always going to be mp3... we want to prioritize the
210 res
= ast_waitfor(chan
, ms
);
217 ast_log(LOG_WARNING
, "Null frame == hangup() detected\n");
221 if (f
->frametype
== AST_FRAME_DTMF
) {
222 ast_log(LOG_DEBUG
, "User pressed a key\n");
223 if (intkeys
&& strchr(intkeys
, f
->subclass
)) {
229 if (f
->frametype
== AST_FRAME_VOICE
) {
230 /* Treat as a generator */
231 needed
= f
->samples
* 2;
232 if (needed
> sizeof(myf
.frdata
)) {
233 ast_log(LOG_WARNING
, "Only able to deliver %d of %d requested samples\n",
234 (int)sizeof(myf
.frdata
) / 2, needed
/2);
235 needed
= sizeof(myf
.frdata
);
237 res
= read(fds
[0], myf
.frdata
, needed
);
239 myf
.f
.frametype
= AST_FRAME_VOICE
;
240 myf
.f
.subclass
= AST_FORMAT_SLINEAR
;
242 myf
.f
.samples
= res
/ 2;
243 myf
.f
.offset
= AST_FRIENDLY_OFFSET
;
244 myf
.f
.src
= __PRETTY_FUNCTION__
;
245 myf
.f
.data
= myf
.frdata
;
246 if (ast_write(chan
, &myf
.f
) < 0) {
251 if (res
< needed
) { /* last frame */
252 ast_log(LOG_DEBUG
, "Last frame\n");
258 ast_log(LOG_DEBUG
, "No more waveform\n");
269 /* kill(pid, SIGKILL); */
270 if (!res
&& owriteformat
)
271 ast_set_write_format(chan
, owriteformat
);
276 #define MAXFESTLEN 2048
281 static int festival_exec(struct ast_channel
*chan
, void *vdata
)
285 struct ast_module_user
*u
;
286 struct sockaddr_in serv_addr
;
287 struct hostent
*serverhost
;
288 struct ast_hostent ahp
;
292 const char *cachedir
;
294 const char *festivalcommand
;
301 char bigstring
[MAXFESTLEN
];
303 struct MD5Context md5ctx
;
304 unsigned char MD5Res
[16];
305 char MD5Hex
[33] = "";
307 char cachefile
[MAXFESTLEN
]="";
316 struct ast_config
*cfg
;
317 char *newfestivalcommand
;
319 if (ast_strlen_zero(vdata
)) {
320 ast_log(LOG_WARNING
, "festival requires an argument (text)\n");
324 u
= ast_module_user_add(chan
);
326 cfg
= ast_config_load(FESTIVAL_CONFIG
);
328 ast_log(LOG_WARNING
, "No such configuration file %s\n", FESTIVAL_CONFIG
);
329 ast_module_user_remove(u
);
332 if (!(host
= ast_variable_retrieve(cfg
, "general", "host"))) {
335 if (!(temp
= ast_variable_retrieve(cfg
, "general", "port"))) {
340 if (!(temp
= ast_variable_retrieve(cfg
, "general", "usecache"))) {
343 usecache
= ast_true(temp
);
345 if (!(cachedir
= ast_variable_retrieve(cfg
, "general", "cachedir"))) {
348 if (!(festivalcommand
= ast_variable_retrieve(cfg
, "general", "festivalcommand"))) {
349 festivalcommand
= "(tts_textasterisk \"%s\" 'file)(quit)\n";
350 } else { /* This else parses the festivalcommand that we're sent from the config file for \n's, etc */
352 newfestivalcommand
= alloca(strlen(festivalcommand
) + 1);
354 for (i
= 0, j
= 0; i
< strlen(festivalcommand
); i
++) {
355 if (festivalcommand
[i
] == '\\' && festivalcommand
[i
+ 1] == 'n') {
356 newfestivalcommand
[j
++] = '\n';
358 } else if (festivalcommand
[i
] == '\\') {
359 newfestivalcommand
[j
++] = festivalcommand
[i
+ 1];
362 newfestivalcommand
[j
++] = festivalcommand
[i
];
364 newfestivalcommand
[j
] = '\0';
365 festivalcommand
= newfestivalcommand
;
368 data
= ast_strdupa(vdata
);
370 intstr
= strchr(data
, '|');
374 if (!strcasecmp(intstr
, "any"))
375 intstr
= AST_DIGIT_ANY
;
378 ast_log(LOG_DEBUG
, "Text passed to festival server : %s\n",(char *)data
);
379 /* Connect to local festival server */
381 fd
= socket(AF_INET
, SOCK_STREAM
, IPPROTO_TCP
);
384 ast_log(LOG_WARNING
,"festival_client: can't get socket\n");
385 ast_config_destroy(cfg
);
386 ast_module_user_remove(u
);
389 memset(&serv_addr
, 0, sizeof(serv_addr
));
390 if ((serv_addr
.sin_addr
.s_addr
= inet_addr(host
)) == -1) {
391 /* its a name rather than an ipnum */
392 serverhost
= ast_gethostbyname(host
, &ahp
);
393 if (serverhost
== (struct hostent
*)0) {
394 ast_log(LOG_WARNING
,"festival_client: gethostbyname failed\n");
395 ast_config_destroy(cfg
);
396 ast_module_user_remove(u
);
399 memmove(&serv_addr
.sin_addr
,serverhost
->h_addr
, serverhost
->h_length
);
401 serv_addr
.sin_family
= AF_INET
;
402 serv_addr
.sin_port
= htons(port
);
404 if (connect(fd
, (struct sockaddr
*)&serv_addr
, sizeof(serv_addr
)) != 0) {
405 ast_log(LOG_WARNING
,"festival_client: connect to server failed\n");
406 ast_config_destroy(cfg
);
407 ast_module_user_remove(u
);
411 /* Compute MD5 sum of string */
413 MD5Update(&md5ctx
,(unsigned char const *)data
,strlen(data
));
414 MD5Final(MD5Res
,&md5ctx
);
417 /* Convert to HEX and look if there is any matching file in the cache
420 snprintf(koko
, sizeof(koko
), "%X",MD5Res
[i
]);
421 strncat(MD5Hex
, koko
, sizeof(MD5Hex
) - strlen(MD5Hex
) - 1);
425 if (strlen(cachedir
)+strlen(MD5Hex
)+1<=MAXFESTLEN
&& (usecache
==-1)) {
426 snprintf(cachefile
, sizeof(cachefile
), "%s/%s", cachedir
, MD5Hex
);
427 fdesc
=open(cachefile
,O_RDWR
);
429 fdesc
=open(cachefile
,O_CREAT
|O_RDWR
,0777);
432 strln
=strlen((char *)data
);
433 ast_log(LOG_DEBUG
,"line length : %d\n",strln
);
434 write(fdesc
,&strln
,sizeof(int));
435 write(fdesc
,data
,strln
);
436 seekpos
=lseek(fdesc
,0,SEEK_CUR
);
437 ast_log(LOG_DEBUG
,"Seek position : %d\n",seekpos
);
440 read(fdesc
,&strln
,sizeof(int));
441 ast_log(LOG_DEBUG
,"Cache file exists, strln=%d, strlen=%d\n",strln
,(int)strlen((char *)data
));
442 if (strlen((char *)data
)==strln
) {
443 ast_log(LOG_DEBUG
,"Size OK\n");
444 read(fdesc
,&bigstring
,strln
);
445 bigstring
[strln
] = 0;
446 if (strcmp(bigstring
,data
)==0) {
449 ast_log(LOG_WARNING
,"Strings do not match\n");
452 ast_log(LOG_WARNING
,"Size mismatch\n");
460 ast_log(LOG_DEBUG
,"Reading from cache...\n");
462 ast_log(LOG_DEBUG
,"Passing text to festival...\n");
463 fs
=fdopen(dup(fd
),"wb");
464 fprintf(fs
,festivalcommand
,(char *)data
);
469 /* Write to cache and then pass it down */
471 ast_log(LOG_DEBUG
,"Writing result to cache...\n");
472 while ((strln
=read(fd
,buffer
,16384))!=0) {
473 write(fdesc
,buffer
,strln
);
477 fd
=open(cachefile
,O_RDWR
);
478 lseek(fd
,seekpos
,SEEK_SET
);
481 ast_log(LOG_DEBUG
,"Passing data to channel...\n");
483 /* Read back info from server */
484 /* This assumes only one waveform will come back, also LP is unlikely */
490 read_data
= read(fd
,ack
+n
,3-n
);
491 /* this avoids falling in infinite loop
492 * in case that festival server goes down
494 if ( read_data
== -1 )
496 ast_log(LOG_WARNING
,"Unable to read from cache/festival fd\n");
498 ast_config_destroy(cfg
);
499 ast_module_user_remove(u
);
505 if (strcmp(ack
,"WV\n") == 0) { /* receive a waveform */
506 ast_log(LOG_DEBUG
,"Festival WV command\n");
507 waveform
= socket_receive_file_to_buff(fd
,&filesize
);
508 res
= send_waveform_to_channel(chan
,waveform
,filesize
, intstr
);
512 else if (strcmp(ack
,"LP\n") == 0) { /* receive an s-expr */
513 ast_log(LOG_DEBUG
,"Festival LP command\n");
514 waveform
= socket_receive_file_to_buff(fd
,&filesize
);
515 waveform
[filesize
]='\0';
516 ast_log(LOG_WARNING
,"Festival returned LP : %s\n",waveform
);
518 } else if (strcmp(ack
,"ER\n") == 0) { /* server got an error */
519 ast_log(LOG_WARNING
,"Festival returned ER\n");
523 } while (strcmp(ack
,"OK\n") != 0);
525 ast_config_destroy(cfg
);
526 ast_module_user_remove(u
);
531 static int unload_module(void)
535 res
= ast_unregister_application(app
);
537 ast_module_user_hangup_all();
542 static int load_module(void)
544 struct ast_config
*cfg
= ast_config_load(FESTIVAL_CONFIG
);
546 ast_log(LOG_WARNING
, "No such configuration file %s\n", FESTIVAL_CONFIG
);
547 return AST_MODULE_LOAD_DECLINE
;
549 ast_config_destroy(cfg
);
550 return ast_register_application(app
, festival_exec
, synopsis
, descrip
);
553 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY
, "Simple Festival Interface");