Mon May 14 04:42:50 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$")
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 closing 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    
00183         if (pipe(fds)) {
00184                  ast_log(LOG_WARNING, "Unable to create pipe\n");
00185          return -1;
00186         }
00187                                                    
00188    /* Answer if it's not already going */
00189    if (chan->_state != AST_STATE_UP)
00190       ast_answer(chan);
00191    ast_stopstream(chan);
00192    ast_indicate(chan, -1);
00193    
00194    owriteformat = chan->writeformat;
00195    res = ast_set_write_format(chan, AST_FORMAT_SLINEAR);
00196    if (res < 0) {
00197       ast_log(LOG_WARNING, "Unable to set write format to signed linear\n");
00198       return -1;
00199    }
00200    
00201    res=send_waveform_to_fd(waveform,length,fds[1]);
00202    if (res >= 0) {
00203       pid = res;
00204       /* Order is important -- there's almost always going to be mp3...  we want to prioritize the
00205          user */
00206       for (;;) {
00207          ms = 1000;
00208          res = ast_waitfor(chan, ms);
00209          if (res < 1) {
00210             res = -1;
00211             break;
00212          }
00213          f = ast_read(chan);
00214          if (!f) {
00215             ast_log(LOG_WARNING, "Null frame == hangup() detected\n");
00216             res = -1;
00217             break;
00218          }
00219          if (f->frametype == AST_FRAME_DTMF) {
00220             ast_log(LOG_DEBUG, "User pressed a key\n");
00221             if (intkeys && strchr(intkeys, f->subclass)) {
00222                res = f->subclass;
00223                ast_frfree(f);
00224                break;
00225             }
00226          }
00227          if (f->frametype == AST_FRAME_VOICE) {
00228             /* Treat as a generator */
00229             needed = f->samples * 2;
00230             if (needed > sizeof(myf.frdata)) {
00231                ast_log(LOG_WARNING, "Only able to deliver %d of %d requested samples\n",
00232                   (int)sizeof(myf.frdata) / 2, needed/2);
00233                needed = sizeof(myf.frdata);
00234             }
00235             res = read(fds[0], myf.frdata, needed);
00236             if (res > 0) {
00237                myf.f.frametype = AST_FRAME_VOICE;
00238                myf.f.subclass = AST_FORMAT_SLINEAR;
00239                myf.f.datalen = res;
00240                myf.f.samples = res / 2;
00241                myf.f.mallocd = 0;
00242                myf.f.offset = AST_FRIENDLY_OFFSET;
00243                myf.f.src = __PRETTY_FUNCTION__;
00244                myf.f.data = myf.frdata;
00245                if (ast_write(chan, &myf.f) < 0) {
00246                   res = -1;
00247                   ast_frfree(f);
00248                   break;
00249                }
00250                if (res < needed) { /* last frame */
00251                   ast_log(LOG_DEBUG, "Last frame\n");
00252                   res=0;
00253                   ast_frfree(f);
00254                   break;
00255                }
00256             } else {
00257                ast_log(LOG_DEBUG, "No more waveform\n");
00258                res = 0;
00259             }
00260          }
00261          ast_frfree(f);
00262       }
00263    }
00264    close(fds[0]);
00265    close(fds[1]);
00266 
00267 /* if (pid > -1) */
00268 /*    kill(pid, SIGKILL); */
00269    if (!res && owriteformat)
00270       ast_set_write_format(chan, owriteformat);
00271    return res;
00272 }
00273 
00274 #define MAXLEN 180
00275 #define MAXFESTLEN 2048
00276 
00277 
00278 
00279 
00280 static int festival_exec(struct ast_channel *chan, void *vdata)
00281 {
00282    int usecache;
00283    int res=0;
00284    struct ast_module_user *u;
00285    struct sockaddr_in serv_addr;
00286    struct hostent *serverhost;
00287    struct ast_hostent ahp;
00288    int fd;
00289    FILE *fs;
00290    const char *host;
00291    const char *cachedir;
00292    const char *temp;
00293    const char *festivalcommand;
00294    int port=1314;
00295    int n;
00296    char ack[4];
00297    char *waveform;
00298    int filesize;
00299    int wave;
00300    char bigstring[MAXFESTLEN];
00301    int i;
00302    struct MD5Context md5ctx;
00303    unsigned char MD5Res[16];
00304    char MD5Hex[33] = "";
00305    char koko[4] = "";
00306    char cachefile[MAXFESTLEN]="";
00307    int readcache=0;
00308    int writecache=0;
00309    int strln;
00310    int fdesc = -1;
00311    char buffer[16384];
00312    int seekpos = 0;  
00313    char *data; 
00314    char *intstr;
00315    struct ast_config *cfg;
00316    char *newfestivalcommand;
00317 
00318    if (ast_strlen_zero(vdata)) {
00319       ast_log(LOG_WARNING, "festival requires an argument (text)\n");
00320       return -1;
00321    }
00322 
00323    u = ast_module_user_add(chan);
00324 
00325    cfg = ast_config_load(FESTIVAL_CONFIG);
00326    if (!cfg) {
00327       ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
00328       ast_module_user_remove(u);
00329       return -1;
00330    }
00331    if (!(host = ast_variable_retrieve(cfg, "general", "host"))) {
00332       host = "localhost";
00333    }
00334    if (!(temp = ast_variable_retrieve(cfg, "general", "port"))) {
00335       port = 1314;
00336    } else {
00337       port = atoi(temp);
00338    }
00339    if (!(temp = ast_variable_retrieve(cfg, "general", "usecache"))) {
00340       usecache=0;
00341    } else {
00342       usecache = ast_true(temp);
00343    }
00344    if (!(cachedir = ast_variable_retrieve(cfg, "general", "cachedir"))) {
00345       cachedir = "/tmp/";
00346    }
00347    if (!(festivalcommand = ast_variable_retrieve(cfg, "general", "festivalcommand"))) {
00348       festivalcommand = "(tts_textasterisk \"%s\" 'file)(quit)\n";
00349    } else { /* This else parses the festivalcommand that we're sent from the config file for \n's, etc */
00350       int i, j;
00351       newfestivalcommand = alloca(strlen(festivalcommand) + 1);
00352 
00353       for (i = 0, j = 0; i < strlen(festivalcommand); i++) {
00354          if (festivalcommand[i] == '\\' && festivalcommand[i + 1] == 'n') {
00355             newfestivalcommand[j++] = '\n';
00356             i++;
00357          } else if (festivalcommand[i] == '\\') {
00358             newfestivalcommand[j++] = festivalcommand[i + 1];
00359             i++;
00360          } else
00361             newfestivalcommand[j++] = festivalcommand[i];
00362       }
00363       newfestivalcommand[j] = '\0';
00364       festivalcommand = newfestivalcommand;
00365    }
00366    
00367    data = ast_strdupa(vdata);
00368 
00369    intstr = strchr(data, '|');
00370    if (intstr) {  
00371       *intstr = '\0';
00372       intstr++;
00373       if (!strcasecmp(intstr, "any"))
00374          intstr = AST_DIGIT_ANY;
00375    }
00376    
00377    ast_log(LOG_DEBUG, "Text passed to festival server : %s\n",(char *)data);
00378    /* Connect to local festival server */
00379    
00380       fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
00381 
00382       if (fd < 0) {
00383       ast_log(LOG_WARNING,"festival_client: can't get socket\n");
00384       ast_config_destroy(cfg);
00385       ast_module_user_remove(u);
00386          return -1;
00387    }
00388         memset(&serv_addr, 0, sizeof(serv_addr));
00389         if ((serv_addr.sin_addr.s_addr = inet_addr(host)) == -1) {
00390            /* its a name rather than an ipnum */
00391            serverhost = ast_gethostbyname(host, &ahp);
00392            if (serverhost == (struct hostent *)0) {
00393                ast_log(LOG_WARNING,"festival_client: gethostbyname failed\n");
00394          ast_config_destroy(cfg);
00395          ast_module_user_remove(u);
00396                   return -1;
00397          }
00398            memmove(&serv_addr.sin_addr,serverhost->h_addr, serverhost->h_length);
00399       }
00400    serv_addr.sin_family = AF_INET;
00401    serv_addr.sin_port = htons(port);
00402 
00403    if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0) {
00404       ast_log(LOG_WARNING,"festival_client: connect to server failed\n");
00405       ast_config_destroy(cfg);
00406       ast_module_user_remove(u);
00407          return -1;
00408       }
00409       
00410       /* Compute MD5 sum of string */
00411       MD5Init(&md5ctx);
00412       MD5Update(&md5ctx,(unsigned char const *)data,strlen(data));
00413       MD5Final(MD5Res,&md5ctx);
00414       MD5Hex[0] = '\0';
00415       
00416       /* Convert to HEX and look if there is any matching file in the cache 
00417          directory */
00418       for (i=0;i<16;i++) {
00419          snprintf(koko, sizeof(koko), "%X",MD5Res[i]);
00420          strncat(MD5Hex, koko, sizeof(MD5Hex) - strlen(MD5Hex) - 1);
00421       }
00422       readcache=0;
00423       writecache=0;
00424       if (strlen(cachedir)+strlen(MD5Hex)+1<=MAXFESTLEN && (usecache==-1)) {
00425          snprintf(cachefile, sizeof(cachefile), "%s/%s", cachedir, MD5Hex);
00426          fdesc=open(cachefile,O_RDWR);
00427          if (fdesc==-1) {
00428             fdesc=open(cachefile,O_CREAT|O_RDWR,0777);
00429             if (fdesc!=-1) {
00430                writecache=1;
00431                strln=strlen((char *)data);
00432                ast_log(LOG_DEBUG,"line length : %d\n",strln);
00433                write(fdesc,&strln,sizeof(int));
00434                write(fdesc,data,strln);
00435             seekpos=lseek(fdesc,0,SEEK_CUR);
00436             ast_log(LOG_DEBUG,"Seek position : %d\n",seekpos);
00437             }
00438          } else {
00439             read(fdesc,&strln,sizeof(int));
00440             ast_log(LOG_DEBUG,"Cache file exists, strln=%d, strlen=%d\n",strln,(int)strlen((char *)data));
00441             if (strlen((char *)data)==strln) {
00442                ast_log(LOG_DEBUG,"Size OK\n");
00443                read(fdesc,&bigstring,strln);
00444                bigstring[strln] = 0;
00445             if (strcmp(bigstring,data)==0) { 
00446                   readcache=1;
00447                } else {
00448                   ast_log(LOG_WARNING,"Strings do not match\n");
00449                }
00450             } else {
00451                ast_log(LOG_WARNING,"Size mismatch\n");
00452             }
00453          }
00454    }
00455             
00456    if (readcache==1) {
00457       close(fd);
00458       fd=fdesc;
00459       ast_log(LOG_DEBUG,"Reading from cache...\n");
00460    } else {
00461       ast_log(LOG_DEBUG,"Passing text to festival...\n");
00462          fs=fdopen(dup(fd),"wb");
00463       fprintf(fs,festivalcommand,(char *)data);
00464       fflush(fs);
00465       fclose(fs);
00466    }
00467    
00468    /* Write to cache and then pass it down */
00469    if (writecache==1) {
00470       ast_log(LOG_DEBUG,"Writing result to cache...\n");
00471       while ((strln=read(fd,buffer,16384))!=0) {
00472          write(fdesc,buffer,strln);
00473       }
00474       close(fd);
00475       close(fdesc);
00476       fd=open(cachefile,O_RDWR);
00477       lseek(fd,seekpos,SEEK_SET);
00478    }
00479    
00480    ast_log(LOG_DEBUG,"Passing data to channel...\n");
00481    
00482    /* Read back info from server */
00483    /* This assumes only one waveform will come back, also LP is unlikely */
00484    wave = 0;
00485    do {
00486                int read_data;
00487       for (n=0; n < 3; )
00488                {
00489                        read_data = read(fd,ack+n,3-n);
00490                        /* this avoids falling in infinite loop
00491                         * in case that festival server goes down
00492                         * */
00493                        if ( read_data == -1 )
00494                        {
00495                                ast_log(LOG_WARNING,"Unable to read from cache/festival fd\n");
00496                 close(fd);
00497                 ast_config_destroy(cfg);
00498                 ast_module_user_remove(u);
00499                                return -1;
00500                        }
00501                        n += read_data;
00502                }
00503       ack[3] = '\0';
00504       if (strcmp(ack,"WV\n") == 0) {         /* receive a waveform */
00505          ast_log(LOG_DEBUG,"Festival WV command\n");
00506          waveform = socket_receive_file_to_buff(fd,&filesize);
00507          res = send_waveform_to_channel(chan,waveform,filesize, intstr);
00508          free(waveform);
00509          break;
00510       }
00511       else if (strcmp(ack,"LP\n") == 0) {   /* receive an s-expr */
00512          ast_log(LOG_DEBUG,"Festival LP command\n");
00513          waveform = socket_receive_file_to_buff(fd,&filesize);
00514          waveform[filesize]='\0';
00515          ast_log(LOG_WARNING,"Festival returned LP : %s\n",waveform);
00516          free(waveform);
00517       } else if (strcmp(ack,"ER\n") == 0) {    /* server got an error */
00518          ast_log(LOG_WARNING,"Festival returned ER\n");
00519          res=-1;
00520          break;
00521          }
00522    } while (strcmp(ack,"OK\n") != 0);
00523    close(fd);
00524    ast_config_destroy(cfg);
00525    ast_module_user_remove(u);
00526    return res;
00527 
00528 }
00529 
00530 static int unload_module(void)
00531 {
00532    int res;
00533 
00534    res = ast_unregister_application(app);
00535 
00536    ast_module_user_hangup_all();
00537 
00538    return res;
00539 }
00540 
00541 static int load_module(void)
00542 {
00543    struct ast_config *cfg = ast_config_load(FESTIVAL_CONFIG);
00544    if (!cfg) {
00545       ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
00546       return AST_MODULE_LOAD_DECLINE;
00547    }
00548    ast_config_destroy(cfg);
00549    return ast_register_application(app, festival_exec, synopsis, descrip);
00550 }
00551 
00552 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Simple Festival Interface");

Generated on Mon May 14 04:42:50 2007 for Asterisk - the Open Source PBX by  doxygen 1.5.1