Thu Feb 5 16:25:37 2009

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

Generated on Thu Feb 5 16:25:37 2009 for Asterisk - the Open Source PBX by  doxygen 1.4.7