2 * Asterisk -- A telephony toolkit for Linux.
6 * Copyright (C) 2002, Christos Ricudis
8 * Christos Ricudis <ricudis@paiko.gr>
10 * This program is free software, distributed under the terms of
11 * the GNU General Public License
14 #include <sys/types.h>
15 #include <asterisk/file.h>
16 #include <asterisk/logger.h>
17 #include <asterisk/channel.h>
18 #include <asterisk/pbx.h>
19 #include <asterisk/module.h>
20 #include <asterisk/md5.h>
21 #include <asterisk/config.h>
26 #include <sys/types.h>
27 #include <sys/socket.h>
29 #include <netinet/in.h>
30 #include <arpa/inet.h>
40 #define FESTIVAL_CONFIG "festival.conf"
42 static char *tdesc = "Simple Festival Interface";
44 static char *app = "Festival";
46 static char *synopsis = "Say text to the user";
48 static char *descrip =
49 " Festival(text[|intkeys]): Connect to Festival, send the argument, get back the waveform,"
50 "play it to the user, allowing any given interrupt keys to immediately terminate and return\n"
51 "the value, or 'any' to allow any number back (useful in dialplan)\n";
57 static char *socket_receive_file_to_buff(int fd,int *size)
59 /* Receive file (probably a waveform file) from socket using */
60 /* Festival key stuff technique, but long winded I know, sorry */
61 /* but will receive any file without closeing the stream or */
63 static char *file_stuff_key = "ft_StUfF_key"; /* must == Festival's key */
70 buff = (char *)malloc(bufflen);
73 for (k=0; file_stuff_key[k] != '\0';)
76 if (n==0) break; /* hit stream eof before end of file */
77 if ((*size)+k+1 >= bufflen)
78 { /* +1 so you can add a NULL if you want */
80 buff = (char *)realloc(buff,bufflen);
82 if (file_stuff_key[k] == c)
84 else if ((c == 'X') && (file_stuff_key[k+1] == '\0'))
85 { /* It looked like the key but wasn't */
86 for (i=0; i < k; i++,(*size)++)
87 buff[*size] = file_stuff_key[i];
89 /* omit the stuffed 'X' */
93 for (i=0; i < k; i++,(*size)++)
94 buff[*size] = file_stuff_key[i];
105 static int send_waveform_to_fd(char *waveform, int length, int fd) {
111 ast_log(LOG_WARNING, "Fork failed\n");
114 for (x=0;x<256;x++) {
118 write(fd,waveform,length);
125 static int send_waveform_to_channel(struct ast_channel *chan, char *waveform, int length, char *intkeys) {
135 char offset[AST_FRIENDLY_OFFSET];
140 ast_log(LOG_WARNING, "Unable to create pipe\n");
144 ast_stopstream(chan);
146 owriteformat = chan->writeformat;
147 res = ast_set_write_format(chan, AST_FORMAT_SLINEAR);
149 ast_log(LOG_WARNING, "Unable to set write format to signed linear\n");
153 res=send_waveform_to_fd(waveform,length,fds[1]);
156 /* Order is important -- there's almost always going to be mp3... we want to prioritize the
160 res = ast_waitfor(chan, ms);
167 ast_log(LOG_DEBUG, "Null frame == hangup() detected\n");
171 if (f->frametype == AST_FRAME_DTMF) {
172 ast_log(LOG_DEBUG, "User pressed a key\n");
173 if (strchr(intkeys, f->subclass)) {
179 if (f->frametype == AST_FRAME_VOICE) {
180 /* Treat as a generator */
181 needed = f->samples * 2;
182 if (needed > sizeof(myf.frdata)) {
183 ast_log(LOG_WARNING, "Only able to deliver %d of %d requested samples\n",
184 sizeof(myf.frdata) / 2, needed/2);
185 needed = sizeof(myf.frdata);
187 res = read(fds[0], myf.frdata, needed);
189 myf.f.frametype = AST_FRAME_VOICE;
190 myf.f.subclass = AST_FORMAT_SLINEAR;
192 myf.f.samples = res / 2;
194 myf.f.offset = AST_FRIENDLY_OFFSET;
195 myf.f.src = __PRETTY_FUNCTION__;
196 myf.f.data = myf.frdata;
197 if (ast_write(chan, &myf.f) < 0) {
201 if (res < needed) { // last frame
202 ast_log(LOG_WARNING, "Last frame\n");
207 ast_log(LOG_WARNING, "No more waveform\n");
217 // kill(pid, SIGKILL);
218 if (!res && owriteformat)
219 ast_set_write_format(chan, owriteformat);
224 #define MAXFESTLEN 2048
229 static int festival_exec(struct ast_channel *chan, void *vdata)
234 struct sockaddr_in serv_addr;
235 struct hostent *serverhost;
241 char *festivalcommand;
248 char bigstring[MAXFESTLEN];
250 struct MD5Context md5ctx;
251 unsigned char MD5Res[16];
254 char cachefile[MAXFESTLEN];
264 struct ast_config *cfg;
265 cfg = ast_load(FESTIVAL_CONFIG);
267 ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
270 if (!(host = ast_variable_retrieve(cfg, "general", "host"))) {
273 if (!(temp = ast_variable_retrieve(cfg, "general", "port"))) {
278 if (!(temp = ast_variable_retrieve(cfg, "general", "usecache"))) {
281 usecache = ast_true(temp);
283 if (!(cachedir = ast_variable_retrieve(cfg, "general", "cachedir"))) {
286 if (!(festivalcommand = ast_variable_retrieve(cfg, "general", "festivalcommand"))) {
287 festivalcommand = "(tts_textasterisk \"%s\" 'file)(quit)\n";
292 if (!vdata || !strlen(vdata)) {
293 ast_log(LOG_WARNING, "festival requires an argument (text)\n");
296 strncpy(data, vdata, sizeof(data) - 1);
297 if ((intstr = strchr(data, '|'))) {
300 if (!strcasecmp(intstr, "any"))
301 intstr = AST_DIGIT_ANY;
304 ast_log(LOG_WARNING, "Text passed to festival server : %s\n",(char *)data);
305 /* Connect to local festival server */
307 fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
310 ast_log(LOG_WARNING,"festival_client: can't get socket\n");
313 memset(&serv_addr, 0, sizeof(serv_addr));
314 if ((serv_addr.sin_addr.s_addr = inet_addr(host)) == -1) {
315 /* its a name rather than an ipnum */
316 serverhost = gethostbyname(host);
317 if (serverhost == (struct hostent *)0) {
318 ast_log(LOG_WARNING,"festival_client: gethostbyname failed\n");
321 memmove(&serv_addr.sin_addr,serverhost->h_addr, serverhost->h_length);
323 serv_addr.sin_family = AF_INET;
324 serv_addr.sin_port = htons(port);
326 if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0) {
327 ast_log(LOG_WARNING,"festival_client: connect to server failed\n");
331 /* Compute MD5 sum of string */
333 MD5Update(&md5ctx,(unsigned char const *)data,strlen(data));
334 MD5Final(MD5Res,&md5ctx);
337 /* Convert to HEX and look if there is any matching file in the cache
340 sprintf(koko,"%X",MD5Res[i]);
345 if (strlen(cachedir)+strlen(MD5Hex)+1<=MAXFESTLEN && (usecache==1)) {
346 sprintf(cachefile,"%s/%s",cachedir,MD5Hex);
347 fdesc=open(cachefile,O_RDWR);
349 fdesc=open(cachefile,O_CREAT|O_RDWR,0);
352 strln=strlen((char *)data);
353 ast_log(LOG_WARNING,"line length : %d\n",strln);
354 write(fdesc,&strln,sizeof(int));
355 write(fdesc,data,strln);
356 seekpos=lseek(fdesc,0,SEEK_CUR);
357 ast_log(LOG_WARNING,"Seek position : %d\n",seekpos);
360 read(fdesc,&strln,sizeof(int));
361 ast_log(LOG_WARNING,"Cache file exists, strln=%d, strlen=%d\n",strln,strlen((char *)data));
362 if (strlen((char *)data)==strln) {
363 ast_log(LOG_WARNING,"Size OK\n");
364 read(fdesc,&bigstring,strln);
365 if (strcmp(bigstring,data)==0) {
368 ast_log(LOG_WARNING,"Strings do not match\n");
371 ast_log(LOG_WARNING,"Size mismatch\n");
379 ast_log(LOG_WARNING,"Reading from cache...\n");
381 ast_log(LOG_WARNING,"Passing text to festival...\n");
382 fs=fdopen(dup(fd),"wb");
383 fprintf(fs,festivalcommand,(char *)data);
388 /* Write to cache and then pass it down */
390 ast_log(LOG_WARNING,"Writing result to cache...\n");
391 while ((strln=read(fd,buffer,16384))!=0) {
392 write(fdesc,buffer,strln);
396 fd=open(cachefile,O_RDWR);
397 lseek(fd,seekpos,SEEK_SET);
400 ast_log(LOG_WARNING,"Passing data to channel...\n");
402 /* Read back info from server */
403 /* This assumes only one waveform will come back, also LP is unlikely */
407 n += read(fd,ack+n,3-n);
409 if (strcmp(ack,"WV\n") == 0) { /* receive a waveform */
410 ast_log(LOG_WARNING,"Festival WV command");
411 waveform = socket_receive_file_to_buff(fd,&filesize);
412 res = send_waveform_to_channel(chan,waveform,filesize, intstr);
416 else if (strcmp(ack,"LP\n") == 0) { /* receive an s-expr */
417 ast_log(LOG_WARNING,"Festival LP command");
418 waveform = socket_receive_file_to_buff(fd,&filesize);
419 waveform[filesize]='\0';
420 ast_log(LOG_WARNING,"Festival returned LP : %s\n",waveform);
422 } else if (strcmp(ack,"ER\n") == 0) { /* server got an error */
423 ast_log(LOG_WARNING,"Festival returned ER\n");
427 } while (strcmp(ack,"OK\n") != 0);
429 LOCAL_USER_REMOVE(u);
434 int unload_module(void)
436 STANDARD_HANGUP_LOCALUSERS;
437 return ast_unregister_application(app);
440 int load_module(void)
443 return ast_register_application(app, festival_exec, synopsis, descrip);
446 char *description(void)
454 STANDARD_USECOUNT(res);
460 return ASTERISK_GPL_KEY;