Mon Mar 20 08:20:06 2006

Asterisk developer's documentation


Main Page | Modules | Alphabetical List | Data Structures | Directories | File List | Data Fields | Globals | Related Pages

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

Generated on Mon Mar 20 08:20:06 2006 for Asterisk - the Open Source PBX by  doxygen 1.3.9.1