Mon Mar 19 11:30:32 2012

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 "asterisk/endian.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, const char *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_LOAD_ORDER , .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 = "88eaa8f5c1bd988bedd71113385e0886" , .load = load_module, .unload = unload_module, .load_pri = AST_MODPRI_DEFAULT, }
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 58 of file app_festival.c.

Referenced by festival_exec(), and load_module().

#define MAXFESTLEN   2048

Definition at line 60 of file app_festival.c.

Referenced by festival_exec().

#define MAXLEN   180

Definition at line 59 of file app_festival.c.


Function Documentation

static void __reg_module ( void   )  [static]

Definition at line 554 of file app_festival.c.

static void __unreg_module ( void   )  [static]

Definition at line 554 of file app_festival.c.

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

Definition at line 267 of file app_festival.c.

References ahp, args, 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(), config_flags, CONFIG_STATUS_FILEINVALID, errno, FESTIVAL_CONFIG, LOG_ERROR, LOG_WARNING, MAXFESTLEN, MD5Final(), MD5Init(), MD5Update(), send_waveform_to_channel(), socket_receive_file_to_buff(), and text.

Referenced by load_module().

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

static int load_module ( void   )  [static]

Definition at line 539 of file app_festival.c.

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

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

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

Definition at line 166 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, LOG_WARNING, ast_frame::offset, and send_waveform_to_fd().

Referenced by festival_exec().

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

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

Definition at line 132 of file app_festival.c.

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

Referenced by send_waveform_to_channel().

00133 {
00134    int res;
00135 #if __BYTE_ORDER == __BIG_ENDIAN
00136    int x;
00137    char c;
00138 #endif
00139 
00140    res = ast_safe_fork(0);
00141    if (res < 0)
00142       ast_log(LOG_WARNING, "Fork failed\n");
00143    if (res) {
00144       return res;
00145    }
00146    dup2(fd, 0);
00147    ast_close_fds_above_n(0);
00148    if (ast_opt_high_priority)
00149       ast_set_priority(0);
00150 #if __BYTE_ORDER == __BIG_ENDIAN
00151    for (x = 0; x < length; x += 2) {
00152       c = *(waveform + x + 1);
00153       *(waveform + x + 1) = *(waveform + x);
00154       *(waveform + x) = c;
00155    }
00156 #endif
00157 
00158    if (write(0, waveform, length) < 0) {
00159       /* Cannot log -- all FDs are already closed */
00160    }
00161 
00162    close(fd);
00163    _exit(0);
00164 }

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

Definition at line 81 of file app_festival.c.

References ast_free, ast_malloc, ast_realloc, and buff.

Referenced by festival_exec().

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

static int unload_module ( void   )  [static]

Definition at line 534 of file app_festival.c.

References ast_unregister_application().

00535 {
00536    return ast_unregister_application(app);
00537 }


Variable Documentation

struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_LOAD_ORDER , .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 = "88eaa8f5c1bd988bedd71113385e0886" , .load = load_module, .unload = unload_module, .load_pri = AST_MODPRI_DEFAULT, } [static]

Definition at line 554 of file app_festival.c.

char* app = "Festival" [static]

Definition at line 79 of file app_festival.c.

struct ast_module_info* ast_module_info = &__mod_info [static]

Definition at line 554 of file app_festival.c.


Generated on Mon Mar 19 11:30:32 2012 for Asterisk - The Open Source Telephony Project by  doxygen 1.4.7