#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 = AST_BUILDOPT_SUM, .load = load_module, .unload = unload_module, .load_pri = AST_MODPRI_DEFAULT, } |
| static char * | app = "Festival" |
| static struct ast_module_info * | ast_module_info = &__mod_info |
Definition in file app_festival.c.
| #define FESTIVAL_CONFIG "festival.conf" |
| #define MAXFESTLEN 2048 |
| #define MAXLEN 180 |
Definition at line 59 of file app_festival.c.
| static void __reg_module | ( | void | ) | [static] |
Definition at line 555 of file app_festival.c.
| static void __unreg_module | ( | void | ) | [static] |
Definition at line 555 of file app_festival.c.
| static int festival_exec | ( | struct ast_channel * | chan, | |
| const char * | vdata | |||
| ) | [static] |
Definition at line 268 of file app_festival.c.
References 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_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().
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 char bigstring[MAXFESTLEN]; 00287 int i; 00288 struct MD5Context md5ctx; 00289 unsigned char MD5Res[16]; 00290 char MD5Hex[33] = ""; 00291 char koko[4] = ""; 00292 char cachefile[MAXFESTLEN]=""; 00293 int readcache = 0; 00294 int writecache = 0; 00295 int strln; 00296 int fdesc = -1; 00297 char buffer[16384]; 00298 int seekpos = 0; 00299 char *data; 00300 struct ast_config *cfg; 00301 char *newfestivalcommand; 00302 struct ast_flags config_flags = { 0 }; 00303 AST_DECLARE_APP_ARGS(args, 00304 AST_APP_ARG(text); 00305 AST_APP_ARG(interrupt); 00306 ); 00307 00308 if (ast_strlen_zero(vdata)) { 00309 ast_log(LOG_WARNING, "festival requires an argument (text)\n"); 00310 return -1; 00311 } 00312 00313 cfg = ast_config_load(FESTIVAL_CONFIG, config_flags); 00314 if (!cfg) { 00315 ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG); 00316 return -1; 00317 } else if (cfg == CONFIG_STATUS_FILEINVALID) { 00318 ast_log(LOG_ERROR, "Config file " FESTIVAL_CONFIG " is in an invalid format. Aborting.\n"); 00319 return -1; 00320 } 00321 00322 if (!(host = ast_variable_retrieve(cfg, "general", "host"))) { 00323 host = "localhost"; 00324 } 00325 if (!(temp = ast_variable_retrieve(cfg, "general", "port"))) { 00326 port = 1314; 00327 } else { 00328 port = atoi(temp); 00329 } 00330 if (!(temp = ast_variable_retrieve(cfg, "general", "usecache"))) { 00331 usecache = 0; 00332 } else { 00333 usecache = ast_true(temp); 00334 } 00335 if (!(cachedir = ast_variable_retrieve(cfg, "general", "cachedir"))) { 00336 cachedir = "/tmp/"; 00337 } 00338 00339 data = ast_strdupa(vdata); 00340 AST_STANDARD_APP_ARGS(args, data); 00341 00342 if (!(festivalcommand = ast_variable_retrieve(cfg, "general", "festivalcommand"))) { 00343 const char *startcmd = "(tts_textasterisk \""; 00344 const char *endcmd = "\" 'file)(quit)\n"; 00345 00346 strln = strlen(startcmd) + strlen(args.text) + strlen(endcmd) + 1; 00347 newfestivalcommand = alloca(strln); 00348 snprintf(newfestivalcommand, strln, "%s%s%s", startcmd, args.text, endcmd); 00349 festivalcommand = newfestivalcommand; 00350 } else { /* This else parses the festivalcommand that we're sent from the config file for \n's, etc */ 00351 int x, j; 00352 newfestivalcommand = alloca(strlen(festivalcommand) + strlen(args.text) + 1); 00353 00354 for (x = 0, j = 0; x < strlen(festivalcommand); x++) { 00355 if (festivalcommand[x] == '\\' && festivalcommand[x + 1] == 'n') { 00356 newfestivalcommand[j++] = '\n'; 00357 x++; 00358 } else if (festivalcommand[x] == '\\') { 00359 newfestivalcommand[j++] = festivalcommand[x + 1]; 00360 x++; 00361 } else if (festivalcommand[x] == '%' && festivalcommand[x + 1] == 's') { 00362 sprintf(&newfestivalcommand[j], "%s", args.text); /* we know it is big enough */ 00363 j += strlen(args.text); 00364 x++; 00365 } else 00366 newfestivalcommand[j++] = festivalcommand[x]; 00367 } 00368 newfestivalcommand[j] = '\0'; 00369 festivalcommand = newfestivalcommand; 00370 } 00371 00372 if (args.interrupt && !strcasecmp(args.interrupt, "any")) 00373 args.interrupt = AST_DIGIT_ANY; 00374 00375 ast_debug(1, "Text passed to festival server : %s\n", args.text); 00376 /* Connect to local festival server */ 00377 00378 fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 00379 00380 if (fd < 0) { 00381 ast_log(LOG_WARNING, "festival_client: can't get socket\n"); 00382 ast_config_destroy(cfg); 00383 return -1; 00384 } 00385 00386 memset(&serv_addr, 0, sizeof(serv_addr)); 00387 00388 if ((serv_addr.sin_addr.s_addr = inet_addr(host)) == -1) { 00389 /* its a name rather than an ipnum */ 00390 serverhost = ast_gethostbyname(host, &ahp); 00391 00392 if (serverhost == NULL) { 00393 ast_log(LOG_WARNING, "festival_client: gethostbyname failed\n"); 00394 ast_config_destroy(cfg); 00395 return -1; 00396 } 00397 memmove(&serv_addr.sin_addr, serverhost->h_addr, serverhost->h_length); 00398 } 00399 00400 serv_addr.sin_family = AF_INET; 00401 serv_addr.sin_port = htons(port); 00402 00403 if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0) { 00404 ast_log(LOG_WARNING, "festival_client: connect to server failed\n"); 00405 ast_config_destroy(cfg); 00406 return -1; 00407 } 00408 00409 /* Compute MD5 sum of string */ 00410 MD5Init(&md5ctx); 00411 MD5Update(&md5ctx, (unsigned char *)args.text, strlen(args.text)); 00412 MD5Final(MD5Res, &md5ctx); 00413 MD5Hex[0] = '\0'; 00414 00415 /* Convert to HEX and look if there is any matching file in the cache 00416 directory */ 00417 for (i = 0; i < 16; i++) { 00418 snprintf(koko, sizeof(koko), "%X", MD5Res[i]); 00419 strncat(MD5Hex, koko, sizeof(MD5Hex) - strlen(MD5Hex) - 1); 00420 } 00421 readcache = 0; 00422 writecache = 0; 00423 if (strlen(cachedir) + strlen(MD5Hex) + 1 <= MAXFESTLEN && (usecache == -1)) { 00424 snprintf(cachefile, sizeof(cachefile), "%s/%s", cachedir, MD5Hex); 00425 fdesc = open(cachefile, O_RDWR); 00426 if (fdesc == -1) { 00427 fdesc = open(cachefile, O_CREAT | O_RDWR, AST_FILE_MODE); 00428 if (fdesc != -1) { 00429 writecache = 1; 00430 strln = strlen(args.text); 00431 ast_debug(1, "line length : %d\n", strln); 00432 if (write(fdesc,&strln,sizeof(int)) < 0) { 00433 ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno)); 00434 } 00435 if (write(fdesc,data,strln) < 0) { 00436 ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno)); 00437 } 00438 seekpos = lseek(fdesc, 0, SEEK_CUR); 00439 ast_debug(1, "Seek position : %d\n", seekpos); 00440 } 00441 } else { 00442 if (read(fdesc,&strln,sizeof(int)) != sizeof(int)) { 00443 ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno)); 00444 } 00445 ast_debug(1, "Cache file exists, strln=%d, strlen=%d\n", strln, (int)strlen(args.text)); 00446 if (strlen(args.text) == strln) { 00447 ast_debug(1, "Size OK\n"); 00448 if (read(fdesc,&bigstring,strln) != strln) { 00449 ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno)); 00450 } 00451 bigstring[strln] = 0; 00452 if (strcmp(bigstring, args.text) == 0) { 00453 readcache = 1; 00454 } else { 00455 ast_log(LOG_WARNING, "Strings do not match\n"); 00456 } 00457 } else { 00458 ast_log(LOG_WARNING, "Size mismatch\n"); 00459 } 00460 } 00461 } 00462 00463 if (readcache == 1) { 00464 close(fd); 00465 fd = fdesc; 00466 ast_debug(1, "Reading from cache...\n"); 00467 } else { 00468 ast_debug(1, "Passing text to festival...\n"); 00469 fs = fdopen(dup(fd), "wb"); 00470 00471 fprintf(fs, "%s", festivalcommand); 00472 fflush(fs); 00473 fclose(fs); 00474 } 00475 00476 /* Write to cache and then pass it down */ 00477 if (writecache == 1) { 00478 ast_debug(1, "Writing result to cache...\n"); 00479 while ((strln = read(fd, buffer, 16384)) != 0) { 00480 if (write(fdesc,buffer,strln) < 0) { 00481 ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno)); 00482 } 00483 } 00484 close(fd); 00485 close(fdesc); 00486 fd = open(cachefile, O_RDWR); 00487 lseek(fd, seekpos, SEEK_SET); 00488 } 00489 00490 ast_debug(1, "Passing data to channel...\n"); 00491 00492 /* Read back info from server */ 00493 /* This assumes only one waveform will come back, also LP is unlikely */ 00494 do { 00495 int read_data; 00496 for (n = 0; n < 3; ) { 00497 read_data = read(fd, ack + n, 3 - n); 00498 /* this avoids falling in infinite loop 00499 * in case that festival server goes down 00500 */ 00501 if (read_data == -1) { 00502 ast_log(LOG_WARNING, "Unable to read from cache/festival fd\n"); 00503 close(fd); 00504 ast_config_destroy(cfg); 00505 return -1; 00506 } 00507 n += read_data; 00508 } 00509 ack[3] = '\0'; 00510 if (strcmp(ack, "WV\n") == 0) { /* receive a waveform */ 00511 ast_debug(1, "Festival WV command\n"); 00512 if ((waveform = socket_receive_file_to_buff(fd, &filesize))) { 00513 res = send_waveform_to_channel(chan, waveform, filesize, args.interrupt); 00514 ast_free(waveform); 00515 } 00516 break; 00517 } else if (strcmp(ack, "LP\n") == 0) { /* receive an s-expr */ 00518 ast_debug(1, "Festival LP command\n"); 00519 if ((waveform = socket_receive_file_to_buff(fd, &filesize))) { 00520 waveform[filesize] = '\0'; 00521 ast_log(LOG_WARNING, "Festival returned LP : %s\n", waveform); 00522 ast_free(waveform); 00523 } 00524 } else if (strcmp(ack, "ER\n") == 0) { /* server got an error */ 00525 ast_log(LOG_WARNING, "Festival returned ER\n"); 00526 res = -1; 00527 break; 00528 } 00529 } while (strcmp(ack, "OK\n") != 0); 00530 close(fd); 00531 ast_config_destroy(cfg); 00532 return res; 00533 }
| static int load_module | ( | void | ) | [static] |
Definition at line 540 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.
00541 { 00542 struct ast_flags config_flags = { 0 }; 00543 struct ast_config *cfg = ast_config_load(FESTIVAL_CONFIG, config_flags); 00544 if (!cfg) { 00545 ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG); 00546 return AST_MODULE_LOAD_DECLINE; 00547 } else if (cfg == CONFIG_STATUS_FILEINVALID) { 00548 ast_log(LOG_ERROR, "Config file " FESTIVAL_CONFIG " is in an invalid format. Aborting.\n"); 00549 return AST_MODULE_LOAD_DECLINE; 00550 } 00551 ast_config_destroy(cfg); 00552 return ast_register_application_xml(app, festival_exec); 00553 }
| 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_clear(), ast_format_copy(), ast_format_set(), 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_set_write_format_by_id(), AST_STATE_UP, ast_stopstream(), ast_waitfor(), ast_write(), f, ast_frame::frametype, ast_format::id, 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 struct ast_format 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 ast_format_clear(&owriteformat); 00182 if (pipe(fds)) { 00183 ast_log(LOG_WARNING, "Unable to create pipe\n"); 00184 return -1; 00185 } 00186 00187 /* Answer if it's not already going */ 00188 if (chan->_state != AST_STATE_UP) 00189 ast_answer(chan); 00190 ast_stopstream(chan); 00191 ast_indicate(chan, -1); 00192 00193 ast_format_copy(&owriteformat, &chan->writeformat); 00194 res = ast_set_write_format_by_id(chan, AST_FORMAT_SLINEAR); 00195 if (res < 0) { 00196 ast_log(LOG_WARNING, "Unable to set write format to signed linear\n"); 00197 return -1; 00198 } 00199 00200 res = send_waveform_to_fd(waveform, length, fds[1]); 00201 if (res >= 0) { 00202 /* Order is important -- there's almost always going to be mp3... we want to prioritize the 00203 user */ 00204 for (;;) { 00205 res = ast_waitfor(chan, 1000); 00206 if (res < 1) { 00207 res = -1; 00208 break; 00209 } 00210 f = ast_read(chan); 00211 if (!f) { 00212 ast_log(LOG_WARNING, "Null frame == hangup() detected\n"); 00213 res = -1; 00214 break; 00215 } 00216 if (f->frametype == AST_FRAME_DTMF) { 00217 ast_debug(1, "User pressed a key\n"); 00218 if (intkeys && strchr(intkeys, f->subclass.integer)) { 00219 res = f->subclass.integer; 00220 ast_frfree(f); 00221 break; 00222 } 00223 } 00224 if (f->frametype == AST_FRAME_VOICE) { 00225 /* Treat as a generator */ 00226 needed = f->samples * 2; 00227 if (needed > sizeof(myf.frdata)) { 00228 ast_log(LOG_WARNING, "Only able to deliver %d of %d requested samples\n", 00229 (int)sizeof(myf.frdata) / 2, needed/2); 00230 needed = sizeof(myf.frdata); 00231 } 00232 res = read(fds[0], myf.frdata, needed); 00233 if (res > 0) { 00234 myf.f.frametype = AST_FRAME_VOICE; 00235 ast_format_set(&myf.f.subclass.format, AST_FORMAT_SLINEAR, 0); 00236 myf.f.datalen = res; 00237 myf.f.samples = res / 2; 00238 myf.f.offset = AST_FRIENDLY_OFFSET; 00239 myf.f.src = __PRETTY_FUNCTION__; 00240 myf.f.data.ptr = myf.frdata; 00241 if (ast_write(chan, &myf.f) < 0) { 00242 res = -1; 00243 ast_frfree(f); 00244 break; 00245 } 00246 if (res < needed) { /* last frame */ 00247 ast_debug(1, "Last frame\n"); 00248 res = 0; 00249 ast_frfree(f); 00250 break; 00251 } 00252 } else { 00253 ast_debug(1, "No more waveform\n"); 00254 res = 0; 00255 } 00256 } 00257 ast_frfree(f); 00258 } 00259 } 00260 close(fds[0]); 00261 close(fds[1]); 00262 00263 if (!res && owriteformat.id) 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 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 535 of file app_festival.c.
References ast_unregister_application().
00536 { 00537 return ast_unregister_application(app); 00538 }
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 = AST_BUILDOPT_SUM, .load = load_module, .unload = unload_module, .load_pri = AST_MODPRI_DEFAULT, } [static] |
Definition at line 555 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 555 of file app_festival.c.
1.5.6