Wed Aug 7 17:15:47 2019

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

 AST_MODULE_INFO_STANDARD (ASTERISK_GPL_KEY,"Simple Festival Interface")
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 char * app = "Festival"

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

AST_MODULE_INFO_STANDARD ( ASTERISK_GPL_KEY  ,
"Simple Festival Interface"   
)
static int festival_exec ( struct ast_channel chan,
const char *  vdata 
) [static]

Definition at line 267 of file app_festival.c.

References args, ast_alloca, 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_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 = ast_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 = ast_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", (unsigned)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_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, ast_frame::frametype, ast_frame_subclass::integer, LOG_WARNING, ast_frame::offset, ast_frame::samples, send_waveform_to_fd(), ast_frame::subclass, and ast_channel::writeformat.

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

char* app = "Festival" [static]

Definition at line 79 of file app_festival.c.


Generated on 7 Aug 2019 for Asterisk - The Open Source Telephony Project by  doxygen 1.6.1