Sat Aug 6 00:39:20 2011

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

Generated on Sat Aug 6 00:39:20 2011 for Asterisk - the Open Source PBX by  doxygen 1.4.7