Wed Aug 18 22:33:58 2010

Asterisk developer's documentation


app_festival.c File Reference

Connect to festival. More...

#include "asterisk.h"
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <errno.h>
#include "asterisk/file.h"
#include "asterisk/channel.h"
#include "asterisk/pbx.h"
#include "asterisk/module.h"
#include "asterisk/md5.h"
#include "asterisk/config.h"
#include "asterisk/utils.h"
#include "asterisk/lock.h"
#include "asterisk/app.h"

Go to the source code of this file.

Defines

#define FESTIVAL_CONFIG   "festival.conf"
#define MAXFESTLEN   2048
#define MAXLEN   180

Functions

static void __reg_module (void)
static void __unreg_module (void)
static int festival_exec (struct ast_channel *chan, void *vdata)
static int load_module (void)
static int send_waveform_to_channel (struct ast_channel *chan, char *waveform, int length, char *intkeys)
static int send_waveform_to_fd (char *waveform, int length, int fd)
static char * socket_receive_file_to_buff (int fd, int *size)
static int unload_module (void)

Variables

static struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_DEFAULT , .description = "Simple Festival Interface" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = "a9c98e5d177805051735cb5b0b16b0a0" , .load = load_module, .unload = unload_module, }
static char * app = "Festival"
static struct ast_module_infoast_module_info = &__mod_info
static char * descrip
static char * synopsis = "Say text to the user"


Detailed Description

Connect to festival.

Author:
Christos Ricudis <ricudis@itc.auth.gr>
ExtRef:
The Festival Speech Synthesis System - http://www.cstr.ed.ac.uk/projects/festival/

Definition in file app_festival.c.


Define Documentation

#define FESTIVAL_CONFIG   "festival.conf"

Definition at line 53 of file app_festival.c.

Referenced by festival_exec(), and load_module().

#define MAXFESTLEN   2048

Definition at line 55 of file app_festival.c.

Referenced by festival_exec().

#define MAXLEN   180

Definition at line 54 of file app_festival.c.


Function Documentation

static void __reg_module ( void   )  [static]

Definition at line 541 of file app_festival.c.

static void __unreg_module ( void   )  [static]

Definition at line 541 of file app_festival.c.

static int festival_exec ( struct ast_channel chan,
void *  vdata 
) [static]

Definition at line 259 of file app_festival.c.

References ahp, AST_APP_ARG, ast_config_destroy(), ast_config_load, ast_debug, AST_DECLARE_APP_ARGS, AST_DIGIT_ANY, AST_FILE_MODE, ast_free, ast_gethostbyname(), ast_log(), AST_STANDARD_APP_ARGS, ast_strdupa, ast_strlen_zero(), ast_true(), ast_variable_retrieve(), chan, config_flags, errno, FESTIVAL_CONFIG, LOG_WARNING, MAXFESTLEN, MD5Final(), MD5Init(), MD5Update(), send_waveform_to_channel(), socket_receive_file_to_buff(), text, and wave.

Referenced by load_module().

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 }

static int load_module ( void   )  [static]

Definition at line 529 of file app_festival.c.

References ast_config_destroy(), ast_config_load, ast_log(), AST_MODULE_LOAD_DECLINE, ast_register_application, config_flags, FESTIVAL_CONFIG, festival_exec(), and LOG_WARNING.

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 }

static int send_waveform_to_channel ( struct ast_channel chan,
char *  waveform,
int  length,
char *  intkeys 
) [static]

Definition at line 152 of file app_festival.c.

References ast_channel::_state, ast_answer(), ast_debug, AST_FORMAT_SLINEAR, AST_FRAME_DTMF, AST_FRAME_VOICE, ast_frfree, AST_FRIENDLY_OFFSET, ast_indicate(), ast_log(), ast_read(), ast_set_write_format(), AST_STATE_UP, ast_stopstream(), ast_waitfor(), ast_write(), chan, f, LOG_WARNING, ast_frame::offset, and send_waveform_to_fd().

Referenced by festival_exec().

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 }

static int send_waveform_to_fd ( char *  waveform,
int  length,
int  fd 
) [static]

Definition at line 118 of file app_festival.c.

References ast_close_fds_above_n(), ast_log(), ast_opt_high_priority, ast_safe_fork(), ast_set_priority(), errno, and LOG_WARNING.

Referenced by send_waveform_to_channel().

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 }

static char* socket_receive_file_to_buff ( int  fd,
int *  size 
) [static]

Definition at line 67 of file app_festival.c.

References ast_free, ast_malloc, ast_realloc, and buff.

Referenced by festival_exec().

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 }

static int unload_module ( void   )  [static]

Definition at line 524 of file app_festival.c.

References ast_unregister_application().

00525 {
00526    return ast_unregister_application(app);
00527 }


Variable Documentation

struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_DEFAULT , .description = "Simple Festival Interface" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = "a9c98e5d177805051735cb5b0b16b0a0" , .load = load_module, .unload = unload_module, } [static]

Definition at line 541 of file app_festival.c.

char* app = "Festival" [static]

Definition at line 57 of file app_festival.c.

struct ast_module_info* ast_module_info = &__mod_info [static]

Definition at line 541 of file app_festival.c.

char* descrip [static]

Initial value:

 
"  Festival(text[,intkeys]):  Connect to Festival, send the argument, get back the waveform,\n"
"play it to the user, allowing any given interrupt keys to immediately terminate and return\n"
"the value, or 'any' to allow any number back (useful in dialplan)\n"

Definition at line 61 of file app_festival.c.

char* synopsis = "Say text to the user" [static]

Definition at line 59 of file app_festival.c.


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