Wed Aug 15 01:24:13 2007

Asterisk developer's documentation


app_festival.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 2002, Christos Ricudis
00005  *
00006  * Christos Ricudis <ricudis@itc.auth.gr>
00007  *
00008  * See http://www.asterisk.org for more information about
00009  * the Asterisk project. Please do not directly contact
00010  * any of the maintainers of this project for assistance;
00011  * the project provides a web site, mailing lists and IRC
00012  * channels for your use.
00013  *
00014  * This program is free software, distributed under the terms of
00015  * the GNU General Public License Version 2. See the LICENSE file
00016  * at the top of the source tree.
00017  */
00018 
00019 /*! \file
00020  *
00021  * \brief Connect to festival
00022  *
00023  * \author Christos Ricudis <ricudis@itc.auth.gr>
00024  * 
00025  * \ingroup applications
00026  */
00027 
00028 #include "asterisk.h"
00029 
00030 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 65853 $")
00031 
00032 #include <sys/types.h>
00033 #include <stdlib.h>
00034 #include <unistd.h>
00035 #include <string.h>
00036 #include <stdlib.h>
00037 #include <sys/types.h>
00038 #include <sys/socket.h>
00039 #include <netdb.h>
00040 #include <netinet/in.h>
00041 #include <arpa/inet.h>
00042 #include <stdio.h>
00043 #include <signal.h>
00044 #include <stdlib.h>
00045 #include <unistd.h>
00046 #include <fcntl.h>
00047 #include <ctype.h>
00048 
00049 #include "asterisk/file.h"
00050 #include "asterisk/logger.h"
00051 #include "asterisk/channel.h"
00052 #include "asterisk/pbx.h"
00053 #include "asterisk/module.h"
00054 #include "asterisk/md5.h"
00055 #include "asterisk/config.h"
00056 #include "asterisk/utils.h"
00057 #include "asterisk/lock.h"
00058 #include "asterisk/options.h"
00059 
00060 #define FESTIVAL_CONFIG "festival.conf"
00061 
00062 static char *app = "Festival";
00063 
00064 static char *synopsis = "Say text to the user";
00065 
00066 static char *descrip = 
00067 "  Festival(text[|intkeys]):  Connect to Festival, send the argument, get back the waveform,"
00068 "play it to the user, allowing any given interrupt keys to immediately terminate and return\n"
00069 "the value, or 'any' to allow any number back (useful in dialplan)\n";
00070 
00071 
00072 static char *socket_receive_file_to_buff(int fd,int *size)
00073 {
00074     /* Receive file (probably a waveform file) from socket using   */
00075     /* Festival key stuff technique, but long winded I know, sorry */
00076     /* but will receive any file without closeing the stream or    */
00077     /* using OOB data                                              */
00078     static char *file_stuff_key = "ft_StUfF_key"; /* must == Festival's key */
00079     char *buff;
00080     int bufflen;
00081     int n,k,i;
00082     char c;
00083 
00084     bufflen = 1024;
00085     if (!(buff = ast_malloc(bufflen)))
00086     {
00087         /* TODO: Handle memory allocation failure */
00088     }
00089     *size=0;
00090 
00091     for (k=0; file_stuff_key[k] != '\0';)
00092     {
00093         n = read(fd,&c,1);
00094         if (n==0) break;  /* hit stream eof before end of file */
00095         if ((*size)+k+1 >= bufflen)
00096         {   /* +1 so you can add a NULL if you want */
00097             bufflen += bufflen/4;
00098             if (!(buff = ast_realloc(buff, bufflen)))
00099             {
00100                 /* TODO: Handle memory allocation failure */
00101             }
00102         }
00103         if (file_stuff_key[k] == c)
00104             k++;
00105         else if ((c == 'X') && (file_stuff_key[k+1] == '\0'))
00106         {   /* It looked like the key but wasn't */
00107             for (i=0; i < k; i++,(*size)++)
00108                 buff[*size] = file_stuff_key[i];
00109             k=0;
00110             /* omit the stuffed 'X' */
00111         }
00112         else
00113         {
00114             for (i=0; i < k; i++,(*size)++)
00115                 buff[*size] = file_stuff_key[i];
00116             k=0;
00117             buff[*size] = c;
00118             (*size)++;
00119         }
00120 
00121     }
00122 
00123     return buff;
00124 }
00125 
00126 static int send_waveform_to_fd(char *waveform, int length, int fd) {
00127 
00128         int res;
00129         int x;
00130 #ifdef __PPC__ 
00131    char c;
00132 #endif
00133    sigset_t fullset, oldset;
00134 
00135    sigfillset(&fullset);
00136    pthread_sigmask(SIG_BLOCK, &fullset, &oldset);
00137 
00138         res = fork();
00139         if (res < 0)
00140                 ast_log(LOG_WARNING, "Fork failed\n");
00141         if (res) {
00142       pthread_sigmask(SIG_SETMASK, &oldset, NULL);
00143                 return res;
00144    }
00145         for (x=0;x<256;x++) {
00146                 if (x != fd)
00147                         close(x);
00148         }
00149    if (ast_opt_high_priority)
00150       ast_set_priority(0);
00151    signal(SIGPIPE, SIG_DFL);
00152    pthread_sigmask(SIG_UNBLOCK, &fullset, NULL);
00153 /*IAS */
00154 #ifdef __PPC__  
00155    for( x=0; x<length; x+=2)
00156    {
00157       c = *(waveform+x+1);
00158       *(waveform+x+1)=*(waveform+x);
00159       *(waveform+x)=c;
00160    }
00161 #endif
00162    
00163    write(fd,waveform,length);
00164    close(fd);
00165    exit(0);
00166 }
00167 
00168 
00169 static int send_waveform_to_channel(struct ast_channel *chan, char *waveform, int length, char *intkeys) {
00170    int res=0;
00171    int fds[2];
00172    int ms = -1;
00173    int pid = -1;
00174    int needed = 0;
00175    int owriteformat;
00176    struct ast_frame *f;
00177    struct myframe {
00178       struct ast_frame f;
00179       char offset[AST_FRIENDLY_OFFSET];
00180       char frdata[2048];
00181    } myf = {
00182       .f = { 0, },
00183    };
00184    
00185         if (pipe(fds)) {
00186                  ast_log(LOG_WARNING, "Unable to create pipe\n");
00187          return -1;
00188         }
00189                                                    
00190    /* Answer if it's not already going */
00191    if (chan->_state != AST_STATE_UP)
00192       ast_answer(chan);
00193    ast_stopstream(chan);
00194    ast_indicate(chan, -1);
00195    
00196    owriteformat = chan->writeformat;
00197    res = ast_set_write_format(chan, AST_FORMAT_SLINEAR);
00198    if (res < 0) {
00199       ast_log(LOG_WARNING, "Unable to set write format to signed linear\n");
00200       return -1;
00201    }
00202    
00203    res=send_waveform_to_fd(waveform,length,fds[1]);
00204    if (res >= 0) {
00205       pid = res;
00206       /* Order is important -- there's almost always going to be mp3...  we want to prioritize the
00207          user */
00208       for (;;) {
00209          ms = 1000;
00210          res = ast_waitfor(chan, ms);
00211          if (res < 1) {
00212             res = -1;
00213             break;
00214          }
00215          f = ast_read(chan);
00216          if (!f) {
00217             ast_log(LOG_WARNING, "Null frame == hangup() detected\n");
00218             res = -1;
00219             break;
00220          }
00221          if (f->frametype == AST_FRAME_DTMF) {
00222             ast_log(LOG_DEBUG, "User pressed a key\n");
00223             if (intkeys && strchr(intkeys, f->subclass)) {
00224                res = f->subclass;
00225                ast_frfree(f);
00226                break;
00227             }
00228          }
00229          if (f->frametype == AST_FRAME_VOICE) {
00230             /* Treat as a generator */
00231             needed = f->samples * 2;
00232             if (needed > sizeof(myf.frdata)) {
00233                ast_log(LOG_WARNING, "Only able to deliver %d of %d requested samples\n",
00234                   (int)sizeof(myf.frdata) / 2, needed/2);
00235                needed = sizeof(myf.frdata);
00236             }
00237             res = read(fds[0], myf.frdata, needed);
00238             if (res > 0) {
00239                myf.f.frametype = AST_FRAME_VOICE;
00240                myf.f.subclass = AST_FORMAT_SLINEAR;
00241                myf.f.datalen = res;
00242                myf.f.samples = res / 2;
00243                myf.f.offset = AST_FRIENDLY_OFFSET;
00244                myf.f.src = __PRETTY_FUNCTION__;
00245                myf.f.data = myf.frdata;
00246                if (ast_write(chan, &myf.f) < 0) {
00247                   res = -1;
00248                   ast_frfree(f);
00249                   break;
00250                }
00251                if (res < needed) { /* last frame */
00252                   ast_log(LOG_DEBUG, "Last frame\n");
00253                   res=0;
00254                   ast_frfree(f);
00255                   break;
00256                }
00257             } else {
00258                ast_log(LOG_DEBUG, "No more waveform\n");
00259                res = 0;
00260             }
00261          }
00262          ast_frfree(f);
00263       }
00264    }
00265    close(fds[0]);
00266    close(fds[1]);
00267 
00268 /* if (pid > -1) */
00269 /*    kill(pid, SIGKILL); */
00270    if (!res && owriteformat)
00271       ast_set_write_format(chan, owriteformat);
00272    return res;
00273 }
00274 
00275 #define MAXLEN 180
00276 #define MAXFESTLEN 2048
00277 
00278 
00279 
00280 
00281 static int festival_exec(struct ast_channel *chan, void *vdata)
00282 {
00283    int usecache;
00284    int res=0;
00285    struct ast_module_user *u;
00286    struct sockaddr_in serv_addr;
00287    struct hostent *serverhost;
00288    struct ast_hostent ahp;
00289    int fd;
00290    FILE *fs;
00291    const char *host;
00292    const char *cachedir;
00293    const char *temp;
00294    const char *festivalcommand;
00295    int port=1314;
00296    int n;
00297    char ack[4];
00298    char *waveform;
00299    int filesize;
00300    int wave;
00301    char bigstring[MAXFESTLEN];
00302    int i;
00303    struct MD5Context md5ctx;
00304    unsigned char MD5Res[16];
00305    char MD5Hex[33] = "";
00306    char koko[4] = "";
00307    char cachefile[MAXFESTLEN]="";
00308    int readcache=0;
00309    int writecache=0;
00310    int strln;
00311    int fdesc = -1;
00312    char buffer[16384];
00313    int seekpos = 0;  
00314    char *data; 
00315    char *intstr;
00316    struct ast_config *cfg;
00317    char *newfestivalcommand;
00318 
00319    if (ast_strlen_zero(vdata)) {
00320       ast_log(LOG_WARNING, "festival requires an argument (text)\n");
00321       return -1;
00322    }
00323 
00324    u = ast_module_user_add(chan);
00325 
00326    cfg = ast_config_load(FESTIVAL_CONFIG);
00327    if (!cfg) {
00328       ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
00329       ast_module_user_remove(u);
00330       return -1;
00331    }
00332    if (!(host = ast_variable_retrieve(cfg, "general", "host"))) {
00333       host = "localhost";
00334    }
00335    if (!(temp = ast_variable_retrieve(cfg, "general", "port"))) {
00336       port = 1314;
00337    } else {
00338       port = atoi(temp);
00339    }
00340    if (!(temp = ast_variable_retrieve(cfg, "general", "usecache"))) {
00341       usecache=0;
00342    } else {
00343       usecache = ast_true(temp);
00344    }
00345    if (!(cachedir = ast_variable_retrieve(cfg, "general", "cachedir"))) {
00346       cachedir = "/tmp/";
00347    }
00348    if (!(festivalcommand = ast_variable_retrieve(cfg, "general", "festivalcommand"))) {
00349       festivalcommand = "(tts_textasterisk \"%s\" 'file)(quit)\n";
00350    } else { /* This else parses the festivalcommand that we're sent from the config file for \n's, etc */
00351       int i, j;
00352       newfestivalcommand = alloca(strlen(festivalcommand) + 1);
00353 
00354       for (i = 0, j = 0; i < strlen(festivalcommand); i++) {
00355          if (festivalcommand[i] == '\\' && festivalcommand[i + 1] == 'n') {
00356             newfestivalcommand[j++] = '\n';
00357             i++;
00358          } else if (festivalcommand[i] == '\\') {
00359             newfestivalcommand[j++] = festivalcommand[i + 1];
00360             i++;
00361          } else
00362             newfestivalcommand[j++] = festivalcommand[i];
00363       }
00364       newfestivalcommand[j] = '\0';
00365       festivalcommand = newfestivalcommand;
00366    }
00367    
00368    data = ast_strdupa(vdata);
00369 
00370    intstr = strchr(data, '|');
00371    if (intstr) {  
00372       *intstr = '\0';
00373       intstr++;
00374       if (!strcasecmp(intstr, "any"))
00375          intstr = AST_DIGIT_ANY;
00376    }
00377    
00378    ast_log(LOG_DEBUG, "Text passed to festival server : %s\n",(char *)data);
00379    /* Connect to local festival server */
00380    
00381       fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
00382 
00383       if (fd < 0) {
00384       ast_log(LOG_WARNING,"festival_client: can't get socket\n");
00385       ast_config_destroy(cfg);
00386       ast_module_user_remove(u);
00387          return -1;
00388    }
00389         memset(&serv_addr, 0, sizeof(serv_addr));
00390         if ((serv_addr.sin_addr.s_addr = inet_addr(host)) == -1) {
00391            /* its a name rather than an ipnum */
00392            serverhost = ast_gethostbyname(host, &ahp);
00393            if (serverhost == (struct hostent *)0) {
00394                ast_log(LOG_WARNING,"festival_client: gethostbyname failed\n");
00395          ast_config_destroy(cfg);
00396          ast_module_user_remove(u);
00397                   return -1;
00398          }
00399            memmove(&serv_addr.sin_addr,serverhost->h_addr, serverhost->h_length);
00400       }
00401    serv_addr.sin_family = AF_INET;
00402    serv_addr.sin_port = htons(port);
00403 
00404    if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0) {
00405       ast_log(LOG_WARNING,"festival_client: connect to server failed\n");
00406       ast_config_destroy(cfg);
00407       ast_module_user_remove(u);
00408          return -1;
00409       }
00410       
00411       /* Compute MD5 sum of string */
00412       MD5Init(&md5ctx);
00413       MD5Update(&md5ctx,(unsigned char const *)data,strlen(data));
00414       MD5Final(MD5Res,&md5ctx);
00415       MD5Hex[0] = '\0';
00416       
00417       /* Convert to HEX and look if there is any matching file in the cache 
00418          directory */
00419       for (i=0;i<16;i++) {
00420          snprintf(koko, sizeof(koko), "%X",MD5Res[i]);
00421          strncat(MD5Hex, koko, sizeof(MD5Hex) - strlen(MD5Hex) - 1);
00422       }
00423       readcache=0;
00424       writecache=0;
00425       if (strlen(cachedir)+strlen(MD5Hex)+1<=MAXFESTLEN && (usecache==-1)) {
00426          snprintf(cachefile, sizeof(cachefile), "%s/%s", cachedir, MD5Hex);
00427          fdesc=open(cachefile,O_RDWR);
00428          if (fdesc==-1) {
00429             fdesc=open(cachefile,O_CREAT|O_RDWR,0777);
00430             if (fdesc!=-1) {
00431                writecache=1;
00432                strln=strlen((char *)data);
00433                ast_log(LOG_DEBUG,"line length : %d\n",strln);
00434                write(fdesc,&strln,sizeof(int));
00435                write(fdesc,data,strln);
00436             seekpos=lseek(fdesc,0,SEEK_CUR);
00437             ast_log(LOG_DEBUG,"Seek position : %d\n",seekpos);
00438             }
00439          } else {
00440             read(fdesc,&strln,sizeof(int));
00441             ast_log(LOG_DEBUG,"Cache file exists, strln=%d, strlen=%d\n",strln,(int)strlen((char *)data));
00442             if (strlen((char *)data)==strln) {
00443                ast_log(LOG_DEBUG,"Size OK\n");
00444                read(fdesc,&bigstring,strln);
00445                bigstring[strln] = 0;
00446             if (strcmp(bigstring,data)==0) { 
00447                   readcache=1;
00448                } else {
00449                   ast_log(LOG_WARNING,"Strings do not match\n");
00450                }
00451             } else {
00452                ast_log(LOG_WARNING,"Size mismatch\n");
00453             }
00454          }
00455    }
00456             
00457    if (readcache==1) {
00458       close(fd);
00459       fd=fdesc;
00460       ast_log(LOG_DEBUG,"Reading from cache...\n");
00461    } else {
00462       ast_log(LOG_DEBUG,"Passing text to festival...\n");
00463          fs=fdopen(dup(fd),"wb");
00464       fprintf(fs,festivalcommand,(char *)data);
00465       fflush(fs);
00466       fclose(fs);
00467    }
00468    
00469    /* Write to cache and then pass it down */
00470    if (writecache==1) {
00471       ast_log(LOG_DEBUG,"Writing result to cache...\n");
00472       while ((strln=read(fd,buffer,16384))!=0) {
00473          write(fdesc,buffer,strln);
00474       }
00475       close(fd);
00476       close(fdesc);
00477       fd=open(cachefile,O_RDWR);
00478       lseek(fd,seekpos,SEEK_SET);
00479    }
00480    
00481    ast_log(LOG_DEBUG,"Passing data to channel...\n");
00482    
00483    /* Read back info from server */
00484    /* This assumes only one waveform will come back, also LP is unlikely */
00485    wave = 0;
00486    do {
00487                int read_data;
00488       for (n=0; n < 3; )
00489                {
00490                        read_data = read(fd,ack+n,3-n);
00491                        /* this avoids falling in infinite loop
00492                         * in case that festival server goes down
00493                         * */
00494                        if ( read_data == -1 )
00495                        {
00496                                ast_log(LOG_WARNING,"Unable to read from cache/festival fd\n");
00497                 close(fd);
00498                 ast_config_destroy(cfg);
00499                 ast_module_user_remove(u);
00500                                return -1;
00501                        }
00502                        n += read_data;
00503                }
00504       ack[3] = '\0';
00505       if (strcmp(ack,"WV\n") == 0) {         /* receive a waveform */
00506          ast_log(LOG_DEBUG,"Festival WV command\n");
00507          waveform = socket_receive_file_to_buff(fd,&filesize);
00508          res = send_waveform_to_channel(chan,waveform,filesize, intstr);
00509          free(waveform);
00510          break;
00511       }
00512       else if (strcmp(ack,"LP\n") == 0) {   /* receive an s-expr */
00513          ast_log(LOG_DEBUG,"Festival LP command\n");
00514          waveform = socket_receive_file_to_buff(fd,&filesize);
00515          waveform[filesize]='\0';
00516          ast_log(LOG_WARNING,"Festival returned LP : %s\n",waveform);
00517          free(waveform);
00518       } else if (strcmp(ack,"ER\n") == 0) {    /* server got an error */
00519          ast_log(LOG_WARNING,"Festival returned ER\n");
00520          res=-1;
00521          break;
00522          }
00523    } while (strcmp(ack,"OK\n") != 0);
00524    close(fd);
00525    ast_config_destroy(cfg);
00526    ast_module_user_remove(u);
00527    return res;
00528 
00529 }
00530 
00531 static int unload_module(void)
00532 {
00533    int res;
00534 
00535    res = ast_unregister_application(app);
00536 
00537    ast_module_user_hangup_all();
00538 
00539    return res;
00540 }
00541 
00542 static int load_module(void)
00543 {
00544    struct ast_config *cfg = ast_config_load(FESTIVAL_CONFIG);
00545    if (!cfg) {
00546       ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
00547       return AST_MODULE_LOAD_DECLINE;
00548    }
00549    ast_config_destroy(cfg);
00550    return ast_register_application(app, festival_exec, synopsis, descrip);
00551 }
00552 
00553 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Simple Festival Interface");

Generated on Wed Aug 15 01:24:13 2007 for Asterisk - the Open Source PBX by  doxygen 1.5.3