Fri Nov 12 11:49:29 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"
Include dependency graph for app_festival.c:

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 = "0901e4e500243c855563a2d78b0c03e4" , .load = load_module, .unload = unload_module, }
static char * app = "Festival"
static struct ast_module_infoast_module_info = &__mod_info

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 557 of file app_festival.c.

static void __unreg_module ( void   )  [static]

Definition at line 557 of file app_festival.c.

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

Definition at line 268 of file app_festival.c.

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

Referenced by load_module().

00269 {
00270    int usecache;
00271    int res = 0;
00272    struct sockaddr_in serv_addr;
00273    struct hostent *serverhost;
00274    struct ast_hostent ahp;
00275    int fd;
00276    FILE *fs;
00277    const char *host;
00278    const char *cachedir;
00279    const char *temp;
00280    const char *festivalcommand;
00281    int port = 1314;
00282    int n;
00283    char ack[4];
00284    char *waveform;
00285    int filesize;
00286    int wave;
00287    char bigstring[MAXFESTLEN];
00288    int i;
00289    struct MD5Context md5ctx;
00290    unsigned char MD5Res[16];
00291    char MD5Hex[33] = "";
00292    char koko[4] = "";
00293    char cachefile[MAXFESTLEN]="";
00294    int readcache = 0;
00295    int writecache = 0;
00296    int strln;
00297    int fdesc = -1;
00298    char buffer[16384];
00299    int seekpos = 0;  
00300    char *data; 
00301    struct ast_config *cfg;
00302    char *newfestivalcommand;
00303    struct ast_flags config_flags = { 0 };
00304    AST_DECLARE_APP_ARGS(args,
00305       AST_APP_ARG(text);
00306       AST_APP_ARG(interrupt);
00307    );
00308 
00309    if (ast_strlen_zero(vdata)) {
00310       ast_log(LOG_WARNING, "festival requires an argument (text)\n");
00311       return -1;
00312    }
00313 
00314    cfg = ast_config_load(FESTIVAL_CONFIG, config_flags);
00315    if (!cfg) {
00316       ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
00317       return -1;
00318    } else if (cfg == CONFIG_STATUS_FILEINVALID) {
00319       ast_log(LOG_ERROR, "Config file " FESTIVAL_CONFIG " is in an invalid format.  Aborting.\n");
00320       return -1;
00321    }
00322 
00323    if (!(host = ast_variable_retrieve(cfg, "general", "host"))) {
00324       host = "localhost";
00325    }
00326    if (!(temp = ast_variable_retrieve(cfg, "general", "port"))) {
00327       port = 1314;
00328    } else {
00329       port = atoi(temp);
00330    }
00331    if (!(temp = ast_variable_retrieve(cfg, "general", "usecache"))) {
00332       usecache = 0;
00333    } else {
00334       usecache = ast_true(temp);
00335    }
00336    if (!(cachedir = ast_variable_retrieve(cfg, "general", "cachedir"))) {
00337       cachedir = "/tmp/";
00338    }
00339 
00340    data = ast_strdupa(vdata);
00341    AST_STANDARD_APP_ARGS(args, data);
00342 
00343    if (!(festivalcommand = ast_variable_retrieve(cfg, "general", "festivalcommand"))) {
00344       const char *startcmd = "(tts_textasterisk \"";
00345       const char *endcmd = "\" 'file)(quit)\n";
00346 
00347       strln = strlen(startcmd) + strlen(args.text) + strlen(endcmd) + 1;
00348       newfestivalcommand = alloca(strln);
00349       snprintf(newfestivalcommand, strln, "%s%s%s", startcmd, args.text, endcmd);
00350       festivalcommand = newfestivalcommand;
00351    } else { /* This else parses the festivalcommand that we're sent from the config file for \n's, etc */
00352       int x, j;
00353       newfestivalcommand = alloca(strlen(festivalcommand) + strlen(args.text) + 1);
00354 
00355       for (x = 0, j = 0; x < strlen(festivalcommand); x++) {
00356          if (festivalcommand[x] == '\\' && festivalcommand[x + 1] == 'n') {
00357             newfestivalcommand[j++] = '\n';
00358             x++;
00359          } else if (festivalcommand[x] == '\\') {
00360             newfestivalcommand[j++] = festivalcommand[x + 1];
00361             x++;
00362          } else if (festivalcommand[x] == '%' && festivalcommand[x + 1] == 's') {
00363             sprintf(&newfestivalcommand[j], "%s", args.text); /* we know it is big enough */
00364             j += strlen(args.text);
00365             x++;
00366          } else
00367             newfestivalcommand[j++] = festivalcommand[x];
00368       }
00369       newfestivalcommand[j] = '\0';
00370       festivalcommand = newfestivalcommand;
00371    }
00372    
00373    if (args.interrupt && !strcasecmp(args.interrupt, "any"))
00374       args.interrupt = AST_DIGIT_ANY;
00375 
00376    ast_debug(1, "Text passed to festival server : %s\n", args.text);
00377    /* Connect to local festival server */
00378    
00379    fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
00380 
00381    if (fd < 0) {
00382       ast_log(LOG_WARNING, "festival_client: can't get socket\n");
00383       ast_config_destroy(cfg);
00384       return -1;
00385    }
00386 
00387    memset(&serv_addr, 0, sizeof(serv_addr));
00388 
00389    if ((serv_addr.sin_addr.s_addr = inet_addr(host)) == -1) {
00390       /* its a name rather than an ipnum */
00391       serverhost = ast_gethostbyname(host, &ahp);
00392 
00393       if (serverhost == NULL) {
00394          ast_log(LOG_WARNING, "festival_client: gethostbyname failed\n");
00395          ast_config_destroy(cfg);
00396          return -1;
00397       }
00398       memmove(&serv_addr.sin_addr, serverhost->h_addr, serverhost->h_length);
00399    }
00400 
00401    serv_addr.sin_family = AF_INET;
00402    serv_addr.sin_port = htons(port);
00403 
00404    if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0) {
00405       ast_log(LOG_WARNING, "festival_client: connect to server failed\n");
00406       ast_config_destroy(cfg);
00407       return -1;
00408    }
00409 
00410    /* Compute MD5 sum of string */
00411    MD5Init(&md5ctx);
00412    MD5Update(&md5ctx, (unsigned char *)args.text, strlen(args.text));
00413    MD5Final(MD5Res, &md5ctx);
00414    MD5Hex[0] = '\0';
00415 
00416    /* Convert to HEX and look if there is any matching file in the cache 
00417       directory */
00418    for (i = 0; i < 16; i++) {
00419       snprintf(koko, sizeof(koko), "%X", MD5Res[i]);
00420       strncat(MD5Hex, koko, sizeof(MD5Hex) - strlen(MD5Hex) - 1);
00421    }
00422    readcache = 0;
00423    writecache = 0;
00424    if (strlen(cachedir) + strlen(MD5Hex) + 1 <= MAXFESTLEN && (usecache == -1)) {
00425       snprintf(cachefile, sizeof(cachefile), "%s/%s", cachedir, MD5Hex);
00426       fdesc = open(cachefile, O_RDWR);
00427       if (fdesc == -1) {
00428          fdesc = open(cachefile, O_CREAT | O_RDWR, AST_FILE_MODE);
00429          if (fdesc != -1) {
00430             writecache = 1;
00431             strln = strlen(args.text);
00432             ast_debug(1, "line length : %d\n", strln);
00433                if (write(fdesc,&strln,sizeof(int)) < 0) {
00434                ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno));
00435             }
00436                if (write(fdesc,data,strln) < 0) {
00437                ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno));
00438             }
00439             seekpos = lseek(fdesc, 0, SEEK_CUR);
00440             ast_debug(1, "Seek position : %d\n", seekpos);
00441          }
00442       } else {
00443             if (read(fdesc,&strln,sizeof(int)) != sizeof(int)) {
00444             ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno));
00445          }
00446          ast_debug(1, "Cache file exists, strln=%d, strlen=%d\n", strln, (int)strlen(args.text));
00447          if (strlen(args.text) == strln) {
00448             ast_debug(1, "Size OK\n");
00449                if (read(fdesc,&bigstring,strln) != strln) {
00450                ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno));
00451             }
00452             bigstring[strln] = 0;
00453             if (strcmp(bigstring, args.text) == 0) { 
00454                readcache = 1;
00455             } else {
00456                ast_log(LOG_WARNING, "Strings do not match\n");
00457             }
00458          } else {
00459             ast_log(LOG_WARNING, "Size mismatch\n");
00460          }
00461       }
00462    }
00463 
00464    if (readcache == 1) {
00465       close(fd);
00466       fd = fdesc;
00467       ast_debug(1, "Reading from cache...\n");
00468    } else {
00469       ast_debug(1, "Passing text to festival...\n");
00470       fs = fdopen(dup(fd), "wb");
00471 
00472       fprintf(fs, "%s", festivalcommand);
00473       fflush(fs);
00474       fclose(fs);
00475    }
00476    
00477    /* Write to cache and then pass it down */
00478    if (writecache == 1) {
00479       ast_debug(1, "Writing result to cache...\n");
00480       while ((strln = read(fd, buffer, 16384)) != 0) {
00481          if (write(fdesc,buffer,strln) < 0) {
00482             ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno));
00483          }
00484       }
00485       close(fd);
00486       close(fdesc);
00487       fd = open(cachefile, O_RDWR);
00488       lseek(fd, seekpos, SEEK_SET);
00489    }
00490    
00491    ast_debug(1, "Passing data to channel...\n");
00492 
00493    /* Read back info from server */
00494    /* This assumes only one waveform will come back, also LP is unlikely */
00495    wave = 0;
00496    do {
00497       int read_data;
00498       for (n = 0; n < 3; ) {
00499          read_data = read(fd, ack + n, 3 - n);
00500          /* this avoids falling in infinite loop
00501           * in case that festival server goes down
00502           */
00503          if (read_data == -1) {
00504             ast_log(LOG_WARNING, "Unable to read from cache/festival fd\n");
00505             close(fd);
00506             ast_config_destroy(cfg);
00507             return -1;
00508          }
00509          n += read_data;
00510       }
00511       ack[3] = '\0';
00512       if (strcmp(ack, "WV\n") == 0) {         /* receive a waveform */
00513          ast_debug(1, "Festival WV command\n");
00514          if ((waveform = socket_receive_file_to_buff(fd, &filesize))) {
00515             res = send_waveform_to_channel(chan, waveform, filesize, args.interrupt);
00516             ast_free(waveform);
00517          }
00518          break;
00519       } else if (strcmp(ack, "LP\n") == 0) {   /* receive an s-expr */
00520          ast_debug(1, "Festival LP command\n");
00521          if ((waveform = socket_receive_file_to_buff(fd, &filesize))) {
00522             waveform[filesize] = '\0';
00523             ast_log(LOG_WARNING, "Festival returned LP : %s\n", waveform);
00524             ast_free(waveform);
00525          }
00526       } else if (strcmp(ack, "ER\n") == 0) {    /* server got an error */
00527          ast_log(LOG_WARNING, "Festival returned ER\n");
00528          res = -1;
00529          break;
00530       }
00531    } while (strcmp(ack, "OK\n") != 0);
00532    close(fd);
00533    ast_config_destroy(cfg);
00534    return res;
00535 }

static int load_module ( void   )  [static]

Definition at line 542 of file app_festival.c.

References ast_config_destroy(), ast_config_load, ast_log(), AST_MODULE_LOAD_DECLINE, ast_register_application_xml, CONFIG_STATUS_FILEINVALID, FESTIVAL_CONFIG, festival_exec(), LOG_ERROR, and LOG_WARNING.

00543 {
00544    struct ast_flags config_flags = { 0 };
00545    struct ast_config *cfg = ast_config_load(FESTIVAL_CONFIG, config_flags);
00546    if (!cfg) {
00547       ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
00548       return AST_MODULE_LOAD_DECLINE;
00549    } else if (cfg == CONFIG_STATUS_FILEINVALID) {
00550       ast_log(LOG_ERROR, "Config file " FESTIVAL_CONFIG " is in an invalid format.  Aborting.\n");
00551       return AST_MODULE_LOAD_DECLINE;
00552    }
00553    ast_config_destroy(cfg);
00554    return ast_register_application_xml(app, festival_exec);
00555 }

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

Definition at line 161 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(), f, ast_frame::frametype, LOG_WARNING, ast_frame::offset, ast_frame::samples, send_waveform_to_fd(), ast_frame::subclass, and ast_channel::writeformat.

Referenced by festival_exec().

00162 {
00163    int res = 0;
00164    int fds[2];
00165    int pid = -1;
00166    int needed = 0;
00167    int owriteformat;
00168    struct ast_frame *f;
00169    struct myframe {
00170       struct ast_frame f;
00171       char offset[AST_FRIENDLY_OFFSET];
00172       char frdata[2048];
00173    } myf = {
00174       .f = { 0, },
00175    };
00176 
00177    if (pipe(fds)) {
00178       ast_log(LOG_WARNING, "Unable to create pipe\n");
00179       return -1;
00180    }
00181 
00182    /* Answer if it's not already going */
00183    if (chan->_state != AST_STATE_UP)
00184       ast_answer(chan);
00185    ast_stopstream(chan);
00186    ast_indicate(chan, -1);
00187    
00188    owriteformat = chan->writeformat;
00189    res = ast_set_write_format(chan, AST_FORMAT_SLINEAR);
00190    if (res < 0) {
00191       ast_log(LOG_WARNING, "Unable to set write format to signed linear\n");
00192       return -1;
00193    }
00194    
00195    res = send_waveform_to_fd(waveform, length, fds[1]);
00196    if (res >= 0) {
00197       pid = res;
00198       /* Order is important -- there's almost always going to be mp3...  we want to prioritize the
00199          user */
00200       for (;;) {
00201          res = ast_waitfor(chan, 1000);
00202          if (res < 1) {
00203             res = -1;
00204             break;
00205          }
00206          f = ast_read(chan);
00207          if (!f) {
00208             ast_log(LOG_WARNING, "Null frame == hangup() detected\n");
00209             res = -1;
00210             break;
00211          }
00212          if (f->frametype == AST_FRAME_DTMF) {
00213             ast_debug(1, "User pressed a key\n");
00214             if (intkeys && strchr(intkeys, f->subclass)) {
00215                res = f->subclass;
00216                ast_frfree(f);
00217                break;
00218             }
00219          }
00220          if (f->frametype == AST_FRAME_VOICE) {
00221             /* Treat as a generator */
00222             needed = f->samples * 2;
00223             if (needed > sizeof(myf.frdata)) {
00224                ast_log(LOG_WARNING, "Only able to deliver %d of %d requested samples\n",
00225                   (int)sizeof(myf.frdata) / 2, needed/2);
00226                needed = sizeof(myf.frdata);
00227             }
00228             res = read(fds[0], myf.frdata, needed);
00229             if (res > 0) {
00230                myf.f.frametype = AST_FRAME_VOICE;
00231                myf.f.subclass = AST_FORMAT_SLINEAR;
00232                myf.f.datalen = res;
00233                myf.f.samples = res / 2;
00234                myf.f.offset = AST_FRIENDLY_OFFSET;
00235                myf.f.src = __PRETTY_FUNCTION__;
00236                myf.f.data.ptr = myf.frdata;
00237                if (ast_write(chan, &myf.f) < 0) {
00238                   res = -1;
00239                   ast_frfree(f);
00240                   break;
00241                }
00242                if (res < needed) { /* last frame */
00243                   ast_debug(1, "Last frame\n");
00244                   res = 0;
00245                   ast_frfree(f);
00246                   break;
00247                }
00248             } else {
00249                ast_debug(1, "No more waveform\n");
00250                res = 0;
00251             }
00252          }
00253          ast_frfree(f);
00254       }
00255    }
00256    close(fds[0]);
00257    close(fds[1]);
00258 
00259 #if 0
00260    if (pid > -1)
00261       kill(pid, SIGKILL);
00262 #endif
00263    if (!res && owriteformat)
00264       ast_set_write_format(chan, owriteformat);
00265    return res;
00266 }

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

Definition at line 127 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().

00128 {
00129    int res;
00130 #ifdef __PPC__ 
00131    int x;
00132    char c;
00133 #endif
00134 
00135    res = ast_safe_fork(0);
00136    if (res < 0)
00137       ast_log(LOG_WARNING, "Fork failed\n");
00138    if (res) {
00139       return res;
00140    }
00141    dup2(fd, 0);
00142    ast_close_fds_above_n(0);
00143    if (ast_opt_high_priority)
00144       ast_set_priority(0);
00145 #ifdef __PPC__  
00146    for (x = 0; x < length; x += 2) {
00147       c = *(waveform + x + 1);
00148       *(waveform + x + 1) = *(waveform + x);
00149       *(waveform + x) = c;
00150    }
00151 #endif
00152    
00153    if (write(fd, waveform, length) < 0) {
00154       ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno));
00155    }
00156 
00157    close(fd);
00158    exit(0);
00159 }

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

Definition at line 76 of file app_festival.c.

References ast_free, ast_malloc, ast_realloc, and buff.

Referenced by festival_exec().

00077 {
00078    /* Receive file (probably a waveform file) from socket using
00079     * Festival key stuff technique, but long winded I know, sorry
00080     * but will receive any file without closing the stream or
00081     * using OOB data
00082     */
00083    static char *file_stuff_key = "ft_StUfF_key"; /* must == Festival's key */
00084    char *buff, *tmp;
00085    int bufflen;
00086    int n,k,i;
00087    char c;
00088 
00089    bufflen = 1024;
00090    if (!(buff = ast_malloc(bufflen)))
00091       return NULL;
00092    *size = 0;
00093 
00094    for (k = 0; file_stuff_key[k] != '\0';) {
00095       n = read(fd, &c, 1);
00096       if (n == 0)
00097          break;  /* hit stream eof before end of file */
00098       if ((*size) + k + 1 >= bufflen) {
00099          /* +1 so you can add a terminating NULL if you want */
00100          bufflen += bufflen / 4;
00101          if (!(tmp = ast_realloc(buff, bufflen))) {
00102             ast_free(buff);
00103             return NULL;
00104          }
00105          buff = tmp;
00106       }
00107       if (file_stuff_key[k] == c)
00108          k++;
00109       else if ((c == 'X') && (file_stuff_key[k+1] == '\0')) {
00110          /* It looked like the key but wasn't */
00111          for (i = 0; i < k; i++, (*size)++)
00112             buff[*size] = file_stuff_key[i];
00113          k = 0;
00114          /* omit the stuffed 'X' */
00115       } else {
00116          for (i = 0; i < k; i++, (*size)++)
00117             buff[*size] = file_stuff_key[i];
00118          k = 0;
00119          buff[*size] = c;
00120          (*size)++;
00121       }
00122    }
00123 
00124    return buff;
00125 }

static int unload_module ( void   )  [static]

Definition at line 537 of file app_festival.c.

References ast_unregister_application().

00538 {
00539    return ast_unregister_application(app);
00540 }


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 = "0901e4e500243c855563a2d78b0c03e4" , .load = load_module, .unload = unload_module, } [static]

Definition at line 557 of file app_festival.c.

char* app = "Festival" [static]

Definition at line 74 of file app_festival.c.

Definition at line 557 of file app_festival.c.


Generated by  doxygen 1.6.2