Wed Aug 18 22:33:41 2010

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

Generated on Wed Aug 18 22:33:41 2010 for Asterisk - the Open Source PBX by  doxygen 1.4.7