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" |
Connect to festival.
Definition in file app_festival.c.
#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.
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 }
char* app = "Festival" [static] |
Definition at line 79 of file app_festival.c.