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