Creating new doxygen macro "\extref" to create page that lists
[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         
185         if (pipe(fds)) {
186                  ast_log(LOG_WARNING, "Unable to create pipe\n");
187                 return -1;
188         }
189                                                         
190         /* Answer if it's not already going */
191         if (chan->_state != AST_STATE_UP)
192                 ast_answer(chan);
193         ast_stopstream(chan);
194         ast_indicate(chan, -1);
195         
196         owriteformat = chan->writeformat;
197         res = ast_set_write_format(chan, AST_FORMAT_SLINEAR);
198         if (res < 0) {
199                 ast_log(LOG_WARNING, "Unable to set write format to signed linear\n");
200                 return -1;
201         }
202         
203         res=send_waveform_to_fd(waveform,length,fds[1]);
204         if (res >= 0) {
205                 pid = res;
206                 /* Order is important -- there's almost always going to be mp3...  we want to prioritize the
207                    user */
208                 for (;;) {
209                         ms = 1000;
210                         res = ast_waitfor(chan, ms);
211                         if (res < 1) {
212                                 res = -1;
213                                 break;
214                         }
215                         f = ast_read(chan);
216                         if (!f) {
217                                 ast_log(LOG_WARNING, "Null frame == hangup() detected\n");
218                                 res = -1;
219                                 break;
220                         }
221                         if (f->frametype == AST_FRAME_DTMF) {
222                                 if (option_debug)
223                                         ast_log(LOG_DEBUG, "User pressed a key\n");
224                                 if (intkeys && strchr(intkeys, f->subclass)) {
225                                         res = f->subclass;
226                                         ast_frfree(f);
227                                         break;
228                                 }
229                         }
230                         if (f->frametype == AST_FRAME_VOICE) {
231                                 /* Treat as a generator */
232                                 needed = f->samples * 2;
233                                 if (needed > sizeof(myf.frdata)) {
234                                         ast_log(LOG_WARNING, "Only able to deliver %d of %d requested samples\n",
235                                                 (int)sizeof(myf.frdata) / 2, needed/2);
236                                         needed = sizeof(myf.frdata);
237                                 }
238                                 res = read(fds[0], myf.frdata, needed);
239                                 if (res > 0) {
240                                         myf.f.frametype = AST_FRAME_VOICE;
241                                         myf.f.subclass = AST_FORMAT_SLINEAR;
242                                         myf.f.datalen = res;
243                                         myf.f.samples = res / 2;
244                                         myf.f.mallocd = 0;
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                                                 if (option_debug)
255                                                         ast_log(LOG_DEBUG, "Last frame\n");
256                                                 res=0;
257                                                 ast_frfree(f);
258                                                 break;
259                                         }
260                                 } else {
261                                         if (option_debug)
262                                                 ast_log(LOG_DEBUG, "No more waveform\n");
263                                         res = 0;
264                                 }
265                         }
266                         ast_frfree(f);
267                 }
268         }
269         close(fds[0]);
270         close(fds[1]);
271
272 /*      if (pid > -1) */
273 /*              kill(pid, SIGKILL); */
274         if (!res && owriteformat)
275                 ast_set_write_format(chan, owriteformat);
276         return res;
277 }
278
279 #define MAXLEN 180
280 #define MAXFESTLEN 2048
281
282
283
284
285 static int festival_exec(struct ast_channel *chan, void *vdata)
286 {
287         int usecache;
288         int res=0;
289         struct ast_module_user *u;
290         struct sockaddr_in serv_addr;
291         struct hostent *serverhost;
292         struct ast_hostent ahp;
293         int fd;
294         FILE *fs;
295         const char *host;
296         const char *cachedir;
297         const char *temp;
298         const char *festivalcommand;
299         int port=1314;
300         int n;
301         char ack[4];
302         char *waveform;
303         int filesize;
304         int wave;
305         char bigstring[MAXFESTLEN];
306         int i;
307         struct MD5Context md5ctx;
308         unsigned char MD5Res[16];
309         char MD5Hex[33] = "";
310         char koko[4] = "";
311         char cachefile[MAXFESTLEN]="";
312         int readcache=0;
313         int writecache=0;
314         int strln;
315         int fdesc = -1;
316         char buffer[16384];
317         int seekpos = 0;        
318         char *data;     
319         char *intstr;
320         struct ast_config *cfg;
321         char *newfestivalcommand;
322
323         if (ast_strlen_zero(vdata)) {
324                 ast_log(LOG_WARNING, "festival requires an argument (text)\n");
325                 return -1;
326         }
327
328         u = ast_module_user_add(chan);
329
330         cfg = ast_config_load(FESTIVAL_CONFIG);
331         if (!cfg) {
332                 ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
333                 ast_module_user_remove(u);
334                 return -1;
335         }
336         if (!(host = ast_variable_retrieve(cfg, "general", "host"))) {
337                 host = "localhost";
338         }
339         if (!(temp = ast_variable_retrieve(cfg, "general", "port"))) {
340                 port = 1314;
341         } else {
342                 port = atoi(temp);
343         }
344         if (!(temp = ast_variable_retrieve(cfg, "general", "usecache"))) {
345                 usecache=0;
346         } else {
347                 usecache = ast_true(temp);
348         }
349         if (!(cachedir = ast_variable_retrieve(cfg, "general", "cachedir"))) {
350                 cachedir = "/tmp/";
351         }
352         if (!(festivalcommand = ast_variable_retrieve(cfg, "general", "festivalcommand"))) {
353                 festivalcommand = "(tts_textasterisk \"%s\" 'file)(quit)\n";
354         } else { /* This else parses the festivalcommand that we're sent from the config file for \n's, etc */
355                 int i, j;
356                 newfestivalcommand = alloca(strlen(festivalcommand) + 1);
357
358                 for (i = 0, j = 0; i < strlen(festivalcommand); i++) {
359                         if (festivalcommand[i] == '\\' && festivalcommand[i + 1] == 'n') {
360                                 newfestivalcommand[j++] = '\n';
361                                 i++;
362                         } else if (festivalcommand[i] == '\\') {
363                                 newfestivalcommand[j++] = festivalcommand[i + 1];
364                                 i++;
365                         } else
366                                 newfestivalcommand[j++] = festivalcommand[i];
367                 }
368                 newfestivalcommand[j] = '\0';
369                 festivalcommand = newfestivalcommand;
370         }
371         
372         data = ast_strdupa(vdata);
373
374         intstr = strchr(data, '|');
375         if (intstr) {   
376                 *intstr = '\0';
377                 intstr++;
378                 if (!strcasecmp(intstr, "any"))
379                         intstr = AST_DIGIT_ANY;
380         }
381         
382         if (option_debug)
383                 ast_log(LOG_DEBUG, "Text passed to festival server : %s\n",(char *)data);
384         /* Connect to local festival server */
385         
386         fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
387
388         if (fd < 0) {
389                 ast_log(LOG_WARNING,"festival_client: can't get socket\n");
390                 ast_config_destroy(cfg);
391                 ast_module_user_remove(u);
392                 return -1;
393         }
394
395         memset(&serv_addr, 0, sizeof(serv_addr));
396
397         if ((serv_addr.sin_addr.s_addr = inet_addr(host)) == -1) {
398                 /* its a name rather than an ipnum */
399                 serverhost = ast_gethostbyname(host, &ahp);
400
401                 if (serverhost == (struct hostent *)0) {
402                         ast_log(LOG_WARNING,"festival_client: gethostbyname failed\n");
403                         ast_config_destroy(cfg);
404                         ast_module_user_remove(u);
405                         return -1;
406                 }
407                 memmove(&serv_addr.sin_addr,serverhost->h_addr, serverhost->h_length);
408         }
409
410         serv_addr.sin_family = AF_INET;
411         serv_addr.sin_port = htons(port);
412
413         if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0) {
414                 ast_log(LOG_WARNING,"festival_client: connect to server failed\n");
415                 ast_config_destroy(cfg);
416                 ast_module_user_remove(u);
417                 return -1;
418         }
419         
420         /* Compute MD5 sum of string */
421         MD5Init(&md5ctx);
422         MD5Update(&md5ctx,(unsigned char const *)data,strlen(data));
423         MD5Final(MD5Res,&md5ctx);
424                 MD5Hex[0] = '\0';
425         
426         /* Convert to HEX and look if there is any matching file in the cache 
427                 directory */
428         for (i=0;i<16;i++) {
429                 snprintf(koko, sizeof(koko), "%X",MD5Res[i]);
430                 strncat(MD5Hex, koko, sizeof(MD5Hex) - strlen(MD5Hex) - 1);
431         }
432         readcache=0;
433         writecache=0;
434         if (strlen(cachedir)+strlen(MD5Hex)+1<=MAXFESTLEN && (usecache==-1)) {
435                 snprintf(cachefile, sizeof(cachefile), "%s/%s", cachedir, MD5Hex);
436                 fdesc=open(cachefile,O_RDWR);
437                 if (fdesc==-1) {
438                         fdesc=open(cachefile,O_CREAT|O_RDWR,AST_FILE_MODE);
439                         if (fdesc!=-1) {
440                                 writecache=1;
441                                 strln=strlen((char *)data);
442                                 if (option_debug)
443                                         ast_log(LOG_DEBUG,"line length : %d\n",strln);
444                                 write(fdesc,&strln,sizeof(int));
445                                 write(fdesc,data,strln);
446                                 seekpos=lseek(fdesc,0,SEEK_CUR);
447                                 if (option_debug)
448                                         ast_log(LOG_DEBUG,"Seek position : %d\n",seekpos);
449                         }
450                 } else {
451                         read(fdesc,&strln,sizeof(int));
452                         if (option_debug)
453                                 ast_log(LOG_DEBUG,"Cache file exists, strln=%d, strlen=%d\n",strln,(int)strlen((char *)data));
454                         if (strlen((char *)data)==strln) {
455                                 if (option_debug)
456                                         ast_log(LOG_DEBUG,"Size OK\n");
457                                 read(fdesc,&bigstring,strln);
458                                 bigstring[strln] = 0;
459                                 if (strcmp(bigstring,data)==0) { 
460                                         readcache=1;
461                                 } else {
462                                         ast_log(LOG_WARNING,"Strings do not match\n");
463                                 }
464                         } else {
465                                 ast_log(LOG_WARNING,"Size mismatch\n");
466                         }
467                 }
468         }
469                         
470         if (readcache==1) {
471                 close(fd);
472                 fd=fdesc;
473                 if (option_debug)
474                         ast_log(LOG_DEBUG,"Reading from cache...\n");
475         } else {
476                 if (option_debug)
477                         ast_log(LOG_DEBUG,"Passing text to festival...\n");
478                 fs=fdopen(dup(fd),"wb");
479                 fprintf(fs,festivalcommand,(char *)data);
480                 fflush(fs);
481                 fclose(fs);
482         }
483         
484         /* Write to cache and then pass it down */
485         if (writecache==1) {
486                 if (option_debug)
487                         ast_log(LOG_DEBUG,"Writing result to cache...\n");
488                 while ((strln=read(fd,buffer,16384))!=0) {
489                         write(fdesc,buffer,strln);
490                 }
491                 close(fd);
492                 close(fdesc);
493                 fd=open(cachefile,O_RDWR);
494                 lseek(fd,seekpos,SEEK_SET);
495         }
496         
497         if (option_debug)
498                 ast_log(LOG_DEBUG,"Passing data to channel...\n");
499         
500         /* Read back info from server */
501         /* This assumes only one waveform will come back, also LP is unlikely */
502         wave = 0;
503         do {
504                int read_data;
505                 for (n=0; n < 3; )
506                {
507                        read_data = read(fd,ack+n,3-n);
508                        /* this avoids falling in infinite loop
509                         * in case that festival server goes down
510                         * */
511                        if ( read_data == -1 )
512                        {
513                                ast_log(LOG_WARNING,"Unable to read from cache/festival fd\n");
514                                close(fd);
515                                ast_config_destroy(cfg);
516                                ast_module_user_remove(u);
517                                return -1;
518                        }
519                        n += read_data;
520                }
521                 ack[3] = '\0';
522                 if (strcmp(ack,"WV\n") == 0) {         /* receive a waveform */
523                         if (option_debug)
524                                 ast_log(LOG_DEBUG,"Festival WV command\n");
525                         waveform = socket_receive_file_to_buff(fd,&filesize);
526                         res = send_waveform_to_channel(chan,waveform,filesize, intstr);
527                         free(waveform);
528                         break;
529                 }
530                 else if (strcmp(ack,"LP\n") == 0) {   /* receive an s-expr */
531                         if (option_debug)
532                                 ast_log(LOG_DEBUG,"Festival LP command\n");
533                         waveform = socket_receive_file_to_buff(fd,&filesize);
534                         waveform[filesize]='\0';
535                         ast_log(LOG_WARNING,"Festival returned LP : %s\n",waveform);
536                         free(waveform);
537                 } else if (strcmp(ack,"ER\n") == 0) {    /* server got an error */
538                         ast_log(LOG_WARNING,"Festival returned ER\n");
539                         res=-1;
540                         break;
541                 }
542         } while (strcmp(ack,"OK\n") != 0);
543         close(fd);
544         ast_config_destroy(cfg);
545         ast_module_user_remove(u);
546         return res;
547
548 }
549
550 static int unload_module(void)
551 {
552         int res;
553
554         res = ast_unregister_application(app);
555
556         ast_module_user_hangup_all();
557
558         return res;
559 }
560
561 static int load_module(void)
562 {
563         struct ast_config *cfg = ast_config_load(FESTIVAL_CONFIG);
564         if (!cfg) {
565                 ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
566                 return AST_MODULE_LOAD_DECLINE;
567         }
568         ast_config_destroy(cfg);
569         return ast_register_application(app, festival_exec, synopsis, descrip);
570 }
571
572 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Simple Festival Interface");