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

Generated on Fri Jun 19 12:09:25 2009 for Asterisk - the Open Source PBX by  doxygen 1.4.7