fix festival for big endian. Bug #709
[asterisk/asterisk.git] / apps / app_festival.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * Connect to festival
5  * 
6  * Copyright (C) 2002, Christos Ricudis
7  *
8  * Christos Ricudis <ricudis@paiko.gr>
9  *
10  * This program is free software, distributed under the terms of
11  * the GNU General Public License
12  */
13
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>
22 #include <stdlib.h>
23 #include <unistd.h>
24 #include <string.h>
25 #include <stdlib.h>
26 #include <sys/types.h>
27 #include <sys/socket.h>
28 #include <netdb.h>
29 #include <netinet/in.h>
30 #include <arpa/inet.h>
31 #include <stdio.h>
32 #include <signal.h>
33 #include <stdlib.h>
34 #include <unistd.h>
35 #include <fcntl.h>
36 #include <ctype.h>
37 #include <pthread.h>
38
39
40 #define FESTIVAL_CONFIG "festival.conf"
41
42 static char *tdesc = "Simple Festival Interface";
43
44 static char *app = "Festival";
45
46 static char *synopsis = "Say text to the user";
47
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";
52
53 STANDARD_LOCAL_USER;
54
55 LOCAL_USER_DECL;
56
57 static char *socket_receive_file_to_buff(int fd,int *size)
58 {
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    */
62     /* using OOB data                                              */
63     static char *file_stuff_key = "ft_StUfF_key"; /* must == Festival's key */
64     char *buff;
65     int bufflen;
66     int n,k,i;
67     char c;
68
69     bufflen = 1024;
70     buff = (char *)malloc(bufflen);
71     *size=0;
72
73     for (k=0; file_stuff_key[k] != '\0';)
74     {
75         n = read(fd,&c,1);
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 */
79             bufflen += bufflen/4;
80             buff = (char *)realloc(buff,bufflen);
81         }
82         if (file_stuff_key[k] == c)
83             k++;
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];
88             k=0;
89             /* omit the stuffed 'X' */
90         }
91         else
92         {
93             for (i=0; i < k; i++,(*size)++)
94                 buff[*size] = file_stuff_key[i];
95             k=0;
96             buff[*size] = c;
97             (*size)++;
98         }
99
100     }
101
102     return buff;
103 }
104
105 static int send_waveform_to_fd(char *waveform, int length, int fd) {
106
107         int res;
108         int x;
109 #ifdef __PPC__ 
110         char c;
111 #endif
112
113         res = fork();
114         if (res < 0)
115                 ast_log(LOG_WARNING, "Fork failed\n");
116         if (res)
117                 return res;
118         for (x=0;x<256;x++) {
119                 if (x != fd)
120                         close(x);
121         }
122 //IAS
123 #ifdef __PPC__  
124         for( x=0; x<length; x+=2)
125         {
126                 c = *(waveform+x+1);
127                 *(waveform+x+1)=*(waveform+x);
128                 *(waveform+x)=c;
129         }
130 #endif
131         
132         write(fd,waveform,length);
133         write(fd,"a",1);
134         close(fd);
135         exit(0);
136 }
137
138
139 static int send_waveform_to_channel(struct ast_channel *chan, char *waveform, int length, char *intkeys) {
140         int res=0;
141         int fds[2];
142         int ms = -1;
143         int pid = -1;
144         int needed = 0;
145         int owriteformat;
146         struct ast_frame *f;
147         struct myframe {
148                 struct ast_frame f;
149                 char offset[AST_FRIENDLY_OFFSET];
150                 char frdata[2048];
151         } myf;
152         
153         if (pipe(fds)) {
154                  ast_log(LOG_WARNING, "Unable to create pipe\n");
155                 return -1;
156         }
157                                                         
158         ast_stopstream(chan);
159
160         owriteformat = chan->writeformat;
161         res = ast_set_write_format(chan, AST_FORMAT_SLINEAR);
162         if (res < 0) {
163                 ast_log(LOG_WARNING, "Unable to set write format to signed linear\n");
164                 return -1;
165         }
166         
167         res=send_waveform_to_fd(waveform,length,fds[1]);
168         if (res >= 0) {
169                 pid = res;
170                 /* Order is important -- there's almost always going to be mp3...  we want to prioritize the
171                    user */
172                 for (;;) {
173                         ms = 1000;
174                         res = ast_waitfor(chan, ms);
175                         if (res < 1) {
176                                 res = -1;
177                                 break;
178                         }
179                         f = ast_read(chan);
180                         if (!f) {
181                                 ast_log(LOG_WARNING, "Null frame == hangup() detected\n");
182                                 res = -1;
183                                 break;
184                         }
185                         if (f->frametype == AST_FRAME_DTMF) {
186                                 ast_log(LOG_DEBUG, "User pressed a key\n");
187                                 if (intkeys && strchr(intkeys, f->subclass)) {
188                                         res = f->subclass;
189                                         ast_frfree(f);
190                                         break;
191                                 }
192                         }
193                         if (f->frametype == AST_FRAME_VOICE) {
194                                 /* Treat as a generator */
195                                 needed = f->samples * 2;
196                                 if (needed > sizeof(myf.frdata)) {
197                                         ast_log(LOG_WARNING, "Only able to deliver %d of %d requested samples\n",
198                                                 sizeof(myf.frdata) / 2, needed/2);
199                                         needed = sizeof(myf.frdata);
200                                 }
201                                 res = read(fds[0], myf.frdata, needed);
202                                 if (res > 0) {
203                                         myf.f.frametype = AST_FRAME_VOICE;
204                                         myf.f.subclass = AST_FORMAT_SLINEAR;
205                                         myf.f.datalen = res;
206                                         myf.f.samples = res / 2;
207                                         myf.f.mallocd = 0;
208                                         myf.f.offset = AST_FRIENDLY_OFFSET;
209                                         myf.f.src = __PRETTY_FUNCTION__;
210                                         myf.f.data = myf.frdata;
211                                         if (ast_write(chan, &myf.f) < 0) {
212                                                 res = -1;
213                                                 break;
214                                         }
215                                         if (res < needed) { // last frame
216                                                 ast_log(LOG_DEBUG, "Last frame\n");
217                                                 res=0;
218                                                 break;
219                                         }
220                                 } else {
221                                         ast_log(LOG_DEBUG, "No more waveform\n");
222                                         res = 0;
223                                 }
224                         }
225                         ast_frfree(f);
226                 }
227         }
228         close(fds[0]);
229         close(fds[1]);
230 //      if (pid > -1)
231 //              kill(pid, SIGKILL);
232         if (!res && owriteformat)
233                 ast_set_write_format(chan, owriteformat);
234         return res;
235 }
236
237 #define MAXLEN 180
238 #define MAXFESTLEN 2048
239
240
241
242
243 static int festival_exec(struct ast_channel *chan, void *vdata)
244 {
245         int usecache;
246         int res=0;
247         struct localuser *u;
248         struct sockaddr_in serv_addr;
249         struct hostent *serverhost;
250         int fd;
251         FILE *fs;
252         char *host;
253         char *cachedir;
254         char *temp;
255         char *festivalcommand;
256         int port=1314;
257         int n;
258         char ack[4];
259         char *waveform;
260         int filesize;
261         int wave;
262         char bigstring[MAXFESTLEN];
263         int i;
264         struct MD5Context md5ctx;
265         unsigned char MD5Res[16];
266         char MD5Hex[32];
267         char koko[4];
268         char cachefile[MAXFESTLEN];
269         int readcache=0;
270         int writecache=0;
271         int strln;
272         int fdesc = -1;
273         char buffer[16384];
274         int seekpos = 0;        
275         char data[256] = "";
276         char *intstr;
277         
278         struct ast_config *cfg;
279         cfg = ast_load(FESTIVAL_CONFIG);
280         if (!cfg) {
281                 ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
282                 return -1;
283         }
284         if (!(host = ast_variable_retrieve(cfg, "general", "host"))) {
285                 host = "localhost";
286         }
287         if (!(temp = ast_variable_retrieve(cfg, "general", "port"))) {
288                 port = 1314;
289         } else {
290                 port = atoi(temp);
291         }
292         if (!(temp = ast_variable_retrieve(cfg, "general", "usecache"))) {
293                 usecache=0;
294         } else {
295                 usecache = ast_true(temp);
296         }
297         if (!(cachedir = ast_variable_retrieve(cfg, "general", "cachedir"))) {
298                 cachedir = "/tmp/";
299         }
300         if (!(festivalcommand = ast_variable_retrieve(cfg, "general", "festivalcommand"))) {
301                 festivalcommand = "(tts_textasterisk \"%s\" 'file)(quit)\n";
302         }
303         
304                 
305
306         if (!vdata || !strlen(vdata)) {
307                 ast_log(LOG_WARNING, "festival requires an argument (text)\n");
308                 return -1;
309         }
310         strncpy(data, vdata, sizeof(data) - 1);
311         if ((intstr = strchr(data, '|'))) {
312                 *intstr = '\0';
313                 intstr++;
314                 if (!strcasecmp(intstr, "any"))
315                         intstr = AST_DIGIT_ANY;
316         }
317         LOCAL_USER_ADD(u);
318         ast_log(LOG_DEBUG, "Text passed to festival server : %s\n",(char *)data);
319         /* Connect to local festival server */
320         
321         fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
322
323         if (fd < 0) {
324                 ast_log(LOG_WARNING,"festival_client: can't get socket\n");
325                 return -1;
326         }
327         memset(&serv_addr, 0, sizeof(serv_addr));
328         if ((serv_addr.sin_addr.s_addr = inet_addr(host)) == -1) {
329                 /* its a name rather than an ipnum */
330                 serverhost = gethostbyname(host);
331                 if (serverhost == (struct hostent *)0) {
332                         ast_log(LOG_WARNING,"festival_client: gethostbyname failed\n");
333                         return -1;
334                 }
335                 memmove(&serv_addr.sin_addr,serverhost->h_addr, serverhost->h_length);
336         }
337         serv_addr.sin_family = AF_INET;
338         serv_addr.sin_port = htons(port);
339
340         if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0) {
341                 ast_log(LOG_WARNING,"festival_client: connect to server failed\n");
342                 return -1;
343         }
344         
345         /* Compute MD5 sum of string */
346         MD5Init(&md5ctx);
347         MD5Update(&md5ctx,(unsigned char const *)data,strlen(data));
348         MD5Final(MD5Res,&md5ctx);
349         strcpy(MD5Hex,"");
350         
351         /* Convert to HEX and look if there is any matching file in the cache 
352                 directory */
353         for (i=0;i<16;i++) {
354                 sprintf(koko,"%X",MD5Res[i]);
355                 strcat(MD5Hex,koko);
356         }
357         readcache=0;
358         writecache=0;
359         if (strlen(cachedir)+strlen(MD5Hex)+1<=MAXFESTLEN && (usecache==-1)) {
360                 sprintf(cachefile,"%s/%s",cachedir,MD5Hex);
361                 fdesc=open(cachefile,O_RDWR);
362                 if (fdesc==-1) {
363                         fdesc=open(cachefile,O_CREAT|O_RDWR,0);
364                         if (fdesc!=-1) {
365                                 writecache=1;
366                                 strln=strlen((char *)data);
367                                 ast_log(LOG_DEBUG,"line length : %d\n",strln);
368                                 write(fdesc,&strln,sizeof(int));
369                                 write(fdesc,data,strln);
370                                 seekpos=lseek(fdesc,0,SEEK_CUR);
371                                 ast_log(LOG_DEBUG,"Seek position : %d\n",seekpos);
372                         }
373                 } else {
374                         read(fdesc,&strln,sizeof(int));
375                         ast_log(LOG_DEBUG,"Cache file exists, strln=%d, strlen=%d\n",strln,strlen((char *)data));
376                         if (strlen((char *)data)==strln) {
377                                 ast_log(LOG_DEBUG,"Size OK\n");
378                                 read(fdesc,&bigstring,strln);
379                                 if (strcmp(bigstring,data)==0) { 
380                                         readcache=1;
381                                 } else {
382                                         ast_log(LOG_WARNING,"Strings do not match\n");
383                                 }
384                         } else {
385                                 ast_log(LOG_WARNING,"Size mismatch\n");
386                         }
387                 }
388         }
389                         
390         if (readcache==1) {
391                 close(fd);
392                 fd=fdesc;
393                 ast_log(LOG_DEBUG,"Reading from cache...\n");
394         } else {
395                 ast_log(LOG_DEBUG,"Passing text to festival...\n");
396                 fs=fdopen(dup(fd),"wb");
397                 fprintf(fs,festivalcommand,(char *)data);
398                 fflush(fs);
399                 fclose(fs);
400         }
401         
402         /* Write to cache and then pass it down */
403         if (writecache==1) {
404                 ast_log(LOG_DEBUG,"Writing result to cache...\n");
405                 while ((strln=read(fd,buffer,16384))!=0) {
406                         write(fdesc,buffer,strln);
407                 }
408                 close(fd);
409                 close(fdesc);
410                 fd=open(cachefile,O_RDWR);
411                 lseek(fd,seekpos,SEEK_SET);
412         }
413         
414         ast_log(LOG_DEBUG,"Passing data to channel...\n");
415         
416         /* Read back info from server */
417         /* This assumes only one waveform will come back, also LP is unlikely */
418         wave = 0;
419         do {
420                 for (n=0; n < 3; )
421                         n += read(fd,ack+n,3-n);
422                 ack[3] = '\0';
423                 if (strcmp(ack,"WV\n") == 0) {         /* receive a waveform */
424                         ast_log(LOG_DEBUG,"Festival WV command\n");
425                         waveform = socket_receive_file_to_buff(fd,&filesize);
426                         res = send_waveform_to_channel(chan,waveform,filesize, intstr);
427                         free(waveform);
428                         break;
429                 }
430                 else if (strcmp(ack,"LP\n") == 0) {   /* receive an s-expr */
431                         ast_log(LOG_DEBUG,"Festival LP command\n");
432                         waveform = socket_receive_file_to_buff(fd,&filesize);
433                         waveform[filesize]='\0';
434                         ast_log(LOG_WARNING,"Festival returned LP : %s\n",waveform);
435                         free(waveform);
436                 } else if (strcmp(ack,"ER\n") == 0) {    /* server got an error */
437                         ast_log(LOG_WARNING,"Festival returned ER\n");
438                         res=-1;
439                         break;
440                 }
441         } while (strcmp(ack,"OK\n") != 0);
442         close(fd);
443         LOCAL_USER_REMOVE(u);                                                                                
444         return res;
445
446 }
447
448 int unload_module(void)
449 {
450         STANDARD_HANGUP_LOCALUSERS;
451         return ast_unregister_application(app);
452 }
453
454 int load_module(void)
455 {
456         
457         return ast_register_application(app, festival_exec, synopsis, descrip);
458 }
459
460 char *description(void)
461 {
462         return tdesc;
463 }
464
465 int usecount(void)
466 {
467         int res;
468         STANDARD_USECOUNT(res);
469         return res;
470 }
471
472 char *key()
473 {
474         return ASTERISK_GPL_KEY;
475 }