6a8a26f541087dc5a05c1dfc440a6fbc2a952ac2
[asterisk/asterisk.git] / apps / app_festival.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2002, Christos Ricudis
5  *
6  * Christos Ricudis <ricudis@itc.auth.gr>
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 Connect to festival
22  *
23  * \author Christos Ricudis <ricudis@itc.auth.gr>
24  *
25  * \extref  The Festival Speech Synthesis System - http://www.cstr.ed.ac.uk/projects/festival/
26  * 
27  * \ingroup applications
28  */
29
30 #include "asterisk.h"
31
32 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
33
34 #include <sys/types.h>
35 #include <stdlib.h>
36 #include <unistd.h>
37 #include <string.h>
38 #include <stdlib.h>
39 #include <sys/types.h>
40 #include <sys/socket.h>
41 #include <netdb.h>
42 #include <netinet/in.h>
43 #include <arpa/inet.h>
44 #include <stdio.h>
45 #include <signal.h>
46 #include <stdlib.h>
47 #include <unistd.h>
48 #include <fcntl.h>
49 #include <ctype.h>
50
51 #include "asterisk/file.h"
52 #include "asterisk/logger.h"
53 #include "asterisk/channel.h"
54 #include "asterisk/pbx.h"
55 #include "asterisk/module.h"
56 #include "asterisk/md5.h"
57 #include "asterisk/config.h"
58 #include "asterisk/utils.h"
59 #include "asterisk/lock.h"
60 #include "asterisk/options.h"
61
62 #define FESTIVAL_CONFIG "festival.conf"
63
64 static char *app = "Festival";
65
66 static char *synopsis = "Say text to the user";
67
68 static char *descrip = 
69 "  Festival(text[|intkeys]):  Connect to Festival, send the argument, get back the waveform,"
70 "play it to the user, allowing any given interrupt keys to immediately terminate and return\n"
71 "the value, or 'any' to allow any number back (useful in dialplan)\n";
72
73
74 static char *socket_receive_file_to_buff(int fd,int *size)
75 {
76     /* Receive file (probably a waveform file) from socket using   */
77     /* Festival key stuff technique, but long winded I know, sorry */
78     /* but will receive any file without closing the stream or    */
79     /* using OOB data                                              */
80     static char *file_stuff_key = "ft_StUfF_key"; /* must == Festival's key */
81     char *buff;
82     int bufflen;
83     int n,k,i;
84     char c;
85
86     bufflen = 1024;
87     if (!(buff = ast_malloc(bufflen)))
88     {
89         /* TODO: Handle memory allocation failure */
90     }
91     *size=0;
92
93     for (k=0; file_stuff_key[k] != '\0';)
94     {
95         n = read(fd,&c,1);
96         if (n==0) break;  /* hit stream eof before end of file */
97         if ((*size)+k+1 >= bufflen)
98         {   /* +1 so you can add a NULL if you want */
99             bufflen += bufflen/4;
100             if (!(buff = ast_realloc(buff, bufflen)))
101             {
102                 /* TODO: Handle memory allocation failure */
103             }
104         }
105         if (file_stuff_key[k] == c)
106             k++;
107         else if ((c == 'X') && (file_stuff_key[k+1] == '\0'))
108         {   /* It looked like the key but wasn't */
109             for (i=0; i < k; i++,(*size)++)
110                 buff[*size] = file_stuff_key[i];
111             k=0;
112             /* omit the stuffed 'X' */
113         }
114         else
115         {
116             for (i=0; i < k; i++,(*size)++)
117                 buff[*size] = file_stuff_key[i];
118             k=0;
119             buff[*size] = c;
120             (*size)++;
121         }
122
123     }
124
125     return buff;
126 }
127
128 static int send_waveform_to_fd(char *waveform, int length, int fd) {
129
130         int res;
131         int x;
132 #ifdef __PPC__ 
133         char c;
134 #endif
135         sigset_t fullset, oldset;
136
137         sigfillset(&fullset);
138         pthread_sigmask(SIG_BLOCK, &fullset, &oldset);
139
140         res = fork();
141         if (res < 0)
142                 ast_log(LOG_WARNING, "Fork failed\n");
143         if (res) {
144                 pthread_sigmask(SIG_SETMASK, &oldset, NULL);
145                 return res;
146         }
147         for (x=0;x<256;x++) {
148                 if (x != fd)
149                         close(x);
150         }
151         if (ast_opt_high_priority)
152                 ast_set_priority(0);
153         signal(SIGPIPE, SIG_DFL);
154         pthread_sigmask(SIG_UNBLOCK, &fullset, NULL);
155 /*IAS */
156 #ifdef __PPC__  
157         for( x=0; x<length; x+=2)
158         {
159                 c = *(waveform+x+1);
160                 *(waveform+x+1)=*(waveform+x);
161                 *(waveform+x)=c;
162         }
163 #endif
164         
165         write(fd,waveform,length);
166         close(fd);
167         exit(0);
168 }
169
170
171 static int send_waveform_to_channel(struct ast_channel *chan, char *waveform, int length, char *intkeys) {
172         int res=0;
173         int fds[2];
174         int ms = -1;
175         int pid = -1;
176         int needed = 0;
177         int owriteformat;
178         struct ast_frame *f;
179         struct myframe {
180                 struct ast_frame f;
181                 char offset[AST_FRIENDLY_OFFSET];
182                 char frdata[2048];
183         } myf = {
184                 .f = { 0, },
185         };
186         
187         if (pipe(fds)) {
188                  ast_log(LOG_WARNING, "Unable to create pipe\n");
189                 return -1;
190         }
191                                                         
192         /* Answer if it's not already going */
193         if (chan->_state != AST_STATE_UP)
194                 ast_answer(chan);
195         ast_stopstream(chan);
196         ast_indicate(chan, -1);
197         
198         owriteformat = chan->writeformat;
199         res = ast_set_write_format(chan, AST_FORMAT_SLINEAR);
200         if (res < 0) {
201                 ast_log(LOG_WARNING, "Unable to set write format to signed linear\n");
202                 return -1;
203         }
204         
205         res=send_waveform_to_fd(waveform,length,fds[1]);
206         if (res >= 0) {
207                 pid = res;
208                 /* Order is important -- there's almost always going to be mp3...  we want to prioritize the
209                    user */
210                 for (;;) {
211                         ms = 1000;
212                         res = ast_waitfor(chan, ms);
213                         if (res < 1) {
214                                 res = -1;
215                                 break;
216                         }
217                         f = ast_read(chan);
218                         if (!f) {
219                                 ast_log(LOG_WARNING, "Null frame == hangup() detected\n");
220                                 res = -1;
221                                 break;
222                         }
223                         if (f->frametype == AST_FRAME_DTMF) {
224                                 ast_debug(1, "User pressed a key\n");
225                                 if (intkeys && strchr(intkeys, f->subclass)) {
226                                         res = f->subclass;
227                                         ast_frfree(f);
228                                         break;
229                                 }
230                         }
231                         if (f->frametype == AST_FRAME_VOICE) {
232                                 /* Treat as a generator */
233                                 needed = f->samples * 2;
234                                 if (needed > sizeof(myf.frdata)) {
235                                         ast_log(LOG_WARNING, "Only able to deliver %d of %d requested samples\n",
236                                                 (int)sizeof(myf.frdata) / 2, needed/2);
237                                         needed = sizeof(myf.frdata);
238                                 }
239                                 res = read(fds[0], myf.frdata, needed);
240                                 if (res > 0) {
241                                         myf.f.frametype = AST_FRAME_VOICE;
242                                         myf.f.subclass = AST_FORMAT_SLINEAR;
243                                         myf.f.datalen = res;
244                                         myf.f.samples = res / 2;
245                                         myf.f.offset = AST_FRIENDLY_OFFSET;
246                                         myf.f.src = __PRETTY_FUNCTION__;
247                                         myf.f.data = myf.frdata;
248                                         if (ast_write(chan, &myf.f) < 0) {
249                                                 res = -1;
250                                                 ast_frfree(f);
251                                                 break;
252                                         }
253                                         if (res < needed) { /* last frame */
254                                                 ast_debug(1, "Last frame\n");
255                                                 res=0;
256                                                 ast_frfree(f);
257                                                 break;
258                                         }
259                                 } else {
260                                         ast_debug(1, "No more waveform\n");
261                                         res = 0;
262                                 }
263                         }
264                         ast_frfree(f);
265                 }
266         }
267         close(fds[0]);
268         close(fds[1]);
269
270 /*      if (pid > -1) */
271 /*              kill(pid, SIGKILL); */
272         if (!res && owriteformat)
273                 ast_set_write_format(chan, owriteformat);
274         return res;
275 }
276
277 #define MAXLEN 180
278 #define MAXFESTLEN 2048
279
280
281
282
283 static int festival_exec(struct ast_channel *chan, void *vdata)
284 {
285         int usecache;
286         int res=0;
287         struct ast_module_user *u;
288         struct sockaddr_in serv_addr;
289         struct hostent *serverhost;
290         struct ast_hostent ahp;
291         int fd;
292         FILE *fs;
293         const char *host;
294         const char *cachedir;
295         const char *temp;
296         const char *festivalcommand;
297         int port=1314;
298         int n;
299         char ack[4];
300         char *waveform;
301         int filesize;
302         int wave;
303         char bigstring[MAXFESTLEN];
304         int i;
305         struct MD5Context md5ctx;
306         unsigned char MD5Res[16];
307         char MD5Hex[33] = "";
308         char koko[4] = "";
309         char cachefile[MAXFESTLEN]="";
310         int readcache=0;
311         int writecache=0;
312         int strln;
313         int fdesc = -1;
314         char buffer[16384];
315         int seekpos = 0;        
316         char *data;     
317         char *intstr;
318         struct ast_config *cfg;
319         char *newfestivalcommand;
320
321         if (ast_strlen_zero(vdata)) {
322                 ast_log(LOG_WARNING, "festival requires an argument (text)\n");
323                 return -1;
324         }
325
326         u = ast_module_user_add(chan);
327
328         cfg = ast_config_load(FESTIVAL_CONFIG);
329         if (!cfg) {
330                 ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
331                 ast_module_user_remove(u);
332                 return -1;
333         }
334         if (!(host = ast_variable_retrieve(cfg, "general", "host"))) {
335                 host = "localhost";
336         }
337         if (!(temp = ast_variable_retrieve(cfg, "general", "port"))) {
338                 port = 1314;
339         } else {
340                 port = atoi(temp);
341         }
342         if (!(temp = ast_variable_retrieve(cfg, "general", "usecache"))) {
343                 usecache=0;
344         } else {
345                 usecache = ast_true(temp);
346         }
347         if (!(cachedir = ast_variable_retrieve(cfg, "general", "cachedir"))) {
348                 cachedir = "/tmp/";
349         }
350         if (!(festivalcommand = ast_variable_retrieve(cfg, "general", "festivalcommand"))) {
351                 festivalcommand = "(tts_textasterisk \"%s\" 'file)(quit)\n";
352         } else { /* This else parses the festivalcommand that we're sent from the config file for \n's, etc */
353                 int i, j;
354                 newfestivalcommand = alloca(strlen(festivalcommand) + 1);
355
356                 for (i = 0, j = 0; i < strlen(festivalcommand); i++) {
357                         if (festivalcommand[i] == '\\' && festivalcommand[i + 1] == 'n') {
358                                 newfestivalcommand[j++] = '\n';
359                                 i++;
360                         } else if (festivalcommand[i] == '\\') {
361                                 newfestivalcommand[j++] = festivalcommand[i + 1];
362                                 i++;
363                         } else
364                                 newfestivalcommand[j++] = festivalcommand[i];
365                 }
366                 newfestivalcommand[j] = '\0';
367                 festivalcommand = newfestivalcommand;
368         }
369         
370         data = ast_strdupa(vdata);
371
372         intstr = strchr(data, '|');
373         if (intstr) {   
374                 *intstr = '\0';
375                 intstr++;
376                 if (!strcasecmp(intstr, "any"))
377                         intstr = AST_DIGIT_ANY;
378         }
379         
380         ast_debug(1, "Text passed to festival server : %s\n",(char *)data);
381         /* Connect to local festival server */
382         
383         fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
384
385         if (fd < 0) {
386                 ast_log(LOG_WARNING,"festival_client: can't get socket\n");
387                 ast_config_destroy(cfg);
388                 ast_module_user_remove(u);
389                 return -1;
390         }
391
392         memset(&serv_addr, 0, sizeof(serv_addr));
393
394         if ((serv_addr.sin_addr.s_addr = inet_addr(host)) == -1) {
395                 /* its a name rather than an ipnum */
396                 serverhost = ast_gethostbyname(host, &ahp);
397
398                 if (serverhost == (struct hostent *)0) {
399                         ast_log(LOG_WARNING,"festival_client: gethostbyname failed\n");
400                         ast_config_destroy(cfg);
401                         ast_module_user_remove(u);
402                         return -1;
403                 }
404                 memmove(&serv_addr.sin_addr,serverhost->h_addr, serverhost->h_length);
405         }
406
407         serv_addr.sin_family = AF_INET;
408         serv_addr.sin_port = htons(port);
409
410         if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0) {
411                 ast_log(LOG_WARNING,"festival_client: connect to server failed\n");
412                 ast_config_destroy(cfg);
413                 ast_module_user_remove(u);
414                 return -1;
415         }
416         
417         /* Compute MD5 sum of string */
418         MD5Init(&md5ctx);
419         MD5Update(&md5ctx,(unsigned char const *)data,strlen(data));
420         MD5Final(MD5Res,&md5ctx);
421                 MD5Hex[0] = '\0';
422         
423         /* Convert to HEX and look if there is any matching file in the cache 
424                 directory */
425         for (i=0;i<16;i++) {
426                 snprintf(koko, sizeof(koko), "%X",MD5Res[i]);
427                 strncat(MD5Hex, koko, sizeof(MD5Hex) - strlen(MD5Hex) - 1);
428         }
429         readcache=0;
430         writecache=0;
431         if (strlen(cachedir)+strlen(MD5Hex)+1<=MAXFESTLEN && (usecache==-1)) {
432                 snprintf(cachefile, sizeof(cachefile), "%s/%s", cachedir, MD5Hex);
433                 fdesc=open(cachefile,O_RDWR);
434                 if (fdesc==-1) {
435                         fdesc=open(cachefile,O_CREAT|O_RDWR,AST_FILE_MODE);
436                         if (fdesc!=-1) {
437                                 writecache=1;
438                                 strln=strlen((char *)data);
439                                 ast_debug(1,"line length : %d\n",strln);
440                                 write(fdesc,&strln,sizeof(int));
441                                 write(fdesc,data,strln);
442                                 seekpos=lseek(fdesc,0,SEEK_CUR);
443                                 ast_debug(1,"Seek position : %d\n",seekpos);
444                         }
445                 } else {
446                         read(fdesc,&strln,sizeof(int));
447                         ast_debug(1,"Cache file exists, strln=%d, strlen=%d\n",strln,(int)strlen((char *)data));
448                         if (strlen((char *)data)==strln) {
449                                 ast_debug(1,"Size OK\n");
450                                 read(fdesc,&bigstring,strln);
451                                 bigstring[strln] = 0;
452                                 if (strcmp(bigstring,data)==0) { 
453                                         readcache=1;
454                                 } else {
455                                         ast_log(LOG_WARNING,"Strings do not match\n");
456                                 }
457                         } else {
458                                 ast_log(LOG_WARNING,"Size mismatch\n");
459                         }
460                 }
461         }
462                         
463         if (readcache==1) {
464                 close(fd);
465                 fd=fdesc;
466                 ast_debug(1,"Reading from cache...\n");
467         } else {
468                 ast_debug(1,"Passing text to festival...\n");
469                 fs=fdopen(dup(fd),"wb");
470                 fprintf(fs,festivalcommand,(char *)data);
471                 fflush(fs);
472                 fclose(fs);
473         }
474         
475         /* Write to cache and then pass it down */
476         if (writecache==1) {
477                 ast_debug(1,"Writing result to cache...\n");
478                 while ((strln=read(fd,buffer,16384))!=0) {
479                         write(fdesc,buffer,strln);
480                 }
481                 close(fd);
482                 close(fdesc);
483                 fd=open(cachefile,O_RDWR);
484                 lseek(fd,seekpos,SEEK_SET);
485         }
486         
487         ast_debug(1,"Passing data to channel...\n");
488         
489         /* Read back info from server */
490         /* This assumes only one waveform will come back, also LP is unlikely */
491         wave = 0;
492         do {
493                int read_data;
494                 for (n=0; n < 3; )
495                {
496                        read_data = read(fd,ack+n,3-n);
497                        /* this avoids falling in infinite loop
498                         * in case that festival server goes down
499                         * */
500                        if ( read_data == -1 )
501                        {
502                                ast_log(LOG_WARNING,"Unable to read from cache/festival fd\n");
503                                close(fd);
504                                ast_config_destroy(cfg);
505                                ast_module_user_remove(u);
506                                return -1;
507                        }
508                        n += read_data;
509                }
510                 ack[3] = '\0';
511                 if (strcmp(ack,"WV\n") == 0) {         /* receive a waveform */
512                         ast_debug(1,"Festival WV command\n");
513                         waveform = socket_receive_file_to_buff(fd,&filesize);
514                         res = send_waveform_to_channel(chan,waveform,filesize, intstr);
515                         ast_free(waveform);
516                         break;
517                 }
518                 else if (strcmp(ack,"LP\n") == 0) {   /* receive an s-expr */
519                         ast_debug(1,"Festival LP command\n");
520                         waveform = socket_receive_file_to_buff(fd,&filesize);
521                         waveform[filesize]='\0';
522                         ast_log(LOG_WARNING,"Festival returned LP : %s\n",waveform);
523                         ast_free(waveform);
524                 } else if (strcmp(ack,"ER\n") == 0) {    /* server got an error */
525                         ast_log(LOG_WARNING,"Festival returned ER\n");
526                         res=-1;
527                         break;
528                 }
529         } while (strcmp(ack,"OK\n") != 0);
530         close(fd);
531         ast_config_destroy(cfg);
532         ast_module_user_remove(u);
533         return res;
534
535 }
536
537 static int unload_module(void)
538 {
539         int res;
540
541         res = ast_unregister_application(app);
542
543         ast_module_user_hangup_all();
544
545         return res;
546 }
547
548 static int load_module(void)
549 {
550         struct ast_config *cfg = ast_config_load(FESTIVAL_CONFIG);
551         if (!cfg) {
552                 ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
553                 return AST_MODULE_LOAD_DECLINE;
554         }
555         ast_config_destroy(cfg);
556         return ast_register_application(app, festival_exec, synopsis, descrip);
557 }
558
559 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Simple Festival Interface");