#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 = "8586c2a7d357cb591cc3a6607a8f62d1" , .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 55 of file app_festival.c.
static void __reg_module | ( | void | ) | [static] |
Definition at line 558 of file app_festival.c.
static void __unreg_module | ( | void | ) | [static] |
Definition at line 558 of file app_festival.c.
static int festival_exec | ( | struct ast_channel * | chan, | |
const char * | vdata | |||
) | [static] |
Definition at line 269 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(), text, and wave.
Referenced by load_module().
00270 { 00271 int usecache; 00272 int res = 0; 00273 struct sockaddr_in serv_addr; 00274 struct hostent *serverhost; 00275 struct ast_hostent ahp; 00276 int fd; 00277 FILE *fs; 00278 const char *host; 00279 const char *cachedir; 00280 const char *temp; 00281 const char *festivalcommand; 00282 int port = 1314; 00283 int n; 00284 char ack[4]; 00285 char *waveform; 00286 int filesize; 00287 int wave; 00288 char bigstring[MAXFESTLEN]; 00289 int i; 00290 struct MD5Context md5ctx; 00291 unsigned char MD5Res[16]; 00292 char MD5Hex[33] = ""; 00293 char koko[4] = ""; 00294 char cachefile[MAXFESTLEN]=""; 00295 int readcache = 0; 00296 int writecache = 0; 00297 int strln; 00298 int fdesc = -1; 00299 char buffer[16384]; 00300 int seekpos = 0; 00301 char *data; 00302 struct ast_config *cfg; 00303 char *newfestivalcommand; 00304 struct ast_flags config_flags = { 0 }; 00305 AST_DECLARE_APP_ARGS(args, 00306 AST_APP_ARG(text); 00307 AST_APP_ARG(interrupt); 00308 ); 00309 00310 if (ast_strlen_zero(vdata)) { 00311 ast_log(LOG_WARNING, "festival requires an argument (text)\n"); 00312 return -1; 00313 } 00314 00315 cfg = ast_config_load(FESTIVAL_CONFIG, config_flags); 00316 if (!cfg) { 00317 ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG); 00318 return -1; 00319 } else if (cfg == CONFIG_STATUS_FILEINVALID) { 00320 ast_log(LOG_ERROR, "Config file " FESTIVAL_CONFIG " is in an invalid format. Aborting.\n"); 00321 return -1; 00322 } 00323 00324 if (!(host = ast_variable_retrieve(cfg, "general", "host"))) { 00325 host = "localhost"; 00326 } 00327 if (!(temp = ast_variable_retrieve(cfg, "general", "port"))) { 00328 port = 1314; 00329 } else { 00330 port = atoi(temp); 00331 } 00332 if (!(temp = ast_variable_retrieve(cfg, "general", "usecache"))) { 00333 usecache = 0; 00334 } else { 00335 usecache = ast_true(temp); 00336 } 00337 if (!(cachedir = ast_variable_retrieve(cfg, "general", "cachedir"))) { 00338 cachedir = "/tmp/"; 00339 } 00340 00341 data = ast_strdupa(vdata); 00342 AST_STANDARD_APP_ARGS(args, data); 00343 00344 if (!(festivalcommand = ast_variable_retrieve(cfg, "general", "festivalcommand"))) { 00345 const char *startcmd = "(tts_textasterisk \""; 00346 const char *endcmd = "\" 'file)(quit)\n"; 00347 00348 strln = strlen(startcmd) + strlen(args.text) + strlen(endcmd) + 1; 00349 newfestivalcommand = alloca(strln); 00350 snprintf(newfestivalcommand, strln, "%s%s%s", startcmd, args.text, endcmd); 00351 festivalcommand = newfestivalcommand; 00352 } else { /* This else parses the festivalcommand that we're sent from the config file for \n's, etc */ 00353 int x, j; 00354 newfestivalcommand = alloca(strlen(festivalcommand) + strlen(args.text) + 1); 00355 00356 for (x = 0, j = 0; x < strlen(festivalcommand); x++) { 00357 if (festivalcommand[x] == '\\' && festivalcommand[x + 1] == 'n') { 00358 newfestivalcommand[j++] = '\n'; 00359 x++; 00360 } else if (festivalcommand[x] == '\\') { 00361 newfestivalcommand[j++] = festivalcommand[x + 1]; 00362 x++; 00363 } else if (festivalcommand[x] == '%' && festivalcommand[x + 1] == 's') { 00364 sprintf(&newfestivalcommand[j], "%s", args.text); /* we know it is big enough */ 00365 j += strlen(args.text); 00366 x++; 00367 } else 00368 newfestivalcommand[j++] = festivalcommand[x]; 00369 } 00370 newfestivalcommand[j] = '\0'; 00371 festivalcommand = newfestivalcommand; 00372 } 00373 00374 if (args.interrupt && !strcasecmp(args.interrupt, "any")) 00375 args.interrupt = AST_DIGIT_ANY; 00376 00377 ast_debug(1, "Text passed to festival server : %s\n", args.text); 00378 /* Connect to local festival server */ 00379 00380 fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 00381 00382 if (fd < 0) { 00383 ast_log(LOG_WARNING, "festival_client: can't get socket\n"); 00384 ast_config_destroy(cfg); 00385 return -1; 00386 } 00387 00388 memset(&serv_addr, 0, sizeof(serv_addr)); 00389 00390 if ((serv_addr.sin_addr.s_addr = inet_addr(host)) == -1) { 00391 /* its a name rather than an ipnum */ 00392 serverhost = ast_gethostbyname(host, &ahp); 00393 00394 if (serverhost == NULL) { 00395 ast_log(LOG_WARNING, "festival_client: gethostbyname failed\n"); 00396 ast_config_destroy(cfg); 00397 return -1; 00398 } 00399 memmove(&serv_addr.sin_addr, serverhost->h_addr, serverhost->h_length); 00400 } 00401 00402 serv_addr.sin_family = AF_INET; 00403 serv_addr.sin_port = htons(port); 00404 00405 if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0) { 00406 ast_log(LOG_WARNING, "festival_client: connect to server failed\n"); 00407 ast_config_destroy(cfg); 00408 return -1; 00409 } 00410 00411 /* Compute MD5 sum of string */ 00412 MD5Init(&md5ctx); 00413 MD5Update(&md5ctx, (unsigned char *)args.text, strlen(args.text)); 00414 MD5Final(MD5Res, &md5ctx); 00415 MD5Hex[0] = '\0'; 00416 00417 /* Convert to HEX and look if there is any matching file in the cache 00418 directory */ 00419 for (i = 0; i < 16; i++) { 00420 snprintf(koko, sizeof(koko), "%X", MD5Res[i]); 00421 strncat(MD5Hex, koko, sizeof(MD5Hex) - strlen(MD5Hex) - 1); 00422 } 00423 readcache = 0; 00424 writecache = 0; 00425 if (strlen(cachedir) + strlen(MD5Hex) + 1 <= MAXFESTLEN && (usecache == -1)) { 00426 snprintf(cachefile, sizeof(cachefile), "%s/%s", cachedir, MD5Hex); 00427 fdesc = open(cachefile, O_RDWR); 00428 if (fdesc == -1) { 00429 fdesc = open(cachefile, O_CREAT | O_RDWR, AST_FILE_MODE); 00430 if (fdesc != -1) { 00431 writecache = 1; 00432 strln = strlen(args.text); 00433 ast_debug(1, "line length : %d\n", strln); 00434 if (write(fdesc,&strln,sizeof(int)) < 0) { 00435 ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno)); 00436 } 00437 if (write(fdesc,data,strln) < 0) { 00438 ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno)); 00439 } 00440 seekpos = lseek(fdesc, 0, SEEK_CUR); 00441 ast_debug(1, "Seek position : %d\n", seekpos); 00442 } 00443 } else { 00444 if (read(fdesc,&strln,sizeof(int)) != sizeof(int)) { 00445 ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno)); 00446 } 00447 ast_debug(1, "Cache file exists, strln=%d, strlen=%d\n", strln, (int)strlen(args.text)); 00448 if (strlen(args.text) == strln) { 00449 ast_debug(1, "Size OK\n"); 00450 if (read(fdesc,&bigstring,strln) != strln) { 00451 ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno)); 00452 } 00453 bigstring[strln] = 0; 00454 if (strcmp(bigstring, args.text) == 0) { 00455 readcache = 1; 00456 } else { 00457 ast_log(LOG_WARNING, "Strings do not match\n"); 00458 } 00459 } else { 00460 ast_log(LOG_WARNING, "Size mismatch\n"); 00461 } 00462 } 00463 } 00464 00465 if (readcache == 1) { 00466 close(fd); 00467 fd = fdesc; 00468 ast_debug(1, "Reading from cache...\n"); 00469 } else { 00470 ast_debug(1, "Passing text to festival...\n"); 00471 fs = fdopen(dup(fd), "wb"); 00472 00473 fprintf(fs, "%s", festivalcommand); 00474 fflush(fs); 00475 fclose(fs); 00476 } 00477 00478 /* Write to cache and then pass it down */ 00479 if (writecache == 1) { 00480 ast_debug(1, "Writing result to cache...\n"); 00481 while ((strln = read(fd, buffer, 16384)) != 0) { 00482 if (write(fdesc,buffer,strln) < 0) { 00483 ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno)); 00484 } 00485 } 00486 close(fd); 00487 close(fdesc); 00488 fd = open(cachefile, O_RDWR); 00489 lseek(fd, seekpos, SEEK_SET); 00490 } 00491 00492 ast_debug(1, "Passing data to channel...\n"); 00493 00494 /* Read back info from server */ 00495 /* This assumes only one waveform will come back, also LP is unlikely */ 00496 wave = 0; 00497 do { 00498 int read_data; 00499 for (n = 0; n < 3; ) { 00500 read_data = read(fd, ack + n, 3 - n); 00501 /* this avoids falling in infinite loop 00502 * in case that festival server goes down 00503 */ 00504 if (read_data == -1) { 00505 ast_log(LOG_WARNING, "Unable to read from cache/festival fd\n"); 00506 close(fd); 00507 ast_config_destroy(cfg); 00508 return -1; 00509 } 00510 n += read_data; 00511 } 00512 ack[3] = '\0'; 00513 if (strcmp(ack, "WV\n") == 0) { /* receive a waveform */ 00514 ast_debug(1, "Festival WV command\n"); 00515 if ((waveform = socket_receive_file_to_buff(fd, &filesize))) { 00516 res = send_waveform_to_channel(chan, waveform, filesize, args.interrupt); 00517 ast_free(waveform); 00518 } 00519 break; 00520 } else if (strcmp(ack, "LP\n") == 0) { /* receive an s-expr */ 00521 ast_debug(1, "Festival LP command\n"); 00522 if ((waveform = socket_receive_file_to_buff(fd, &filesize))) { 00523 waveform[filesize] = '\0'; 00524 ast_log(LOG_WARNING, "Festival returned LP : %s\n", waveform); 00525 ast_free(waveform); 00526 } 00527 } else if (strcmp(ack, "ER\n") == 0) { /* server got an error */ 00528 ast_log(LOG_WARNING, "Festival returned ER\n"); 00529 res = -1; 00530 break; 00531 } 00532 } while (strcmp(ack, "OK\n") != 0); 00533 close(fd); 00534 ast_config_destroy(cfg); 00535 return res; 00536 }
static int load_module | ( | void | ) | [static] |
Definition at line 543 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.
00544 { 00545 struct ast_flags config_flags = { 0 }; 00546 struct ast_config *cfg = ast_config_load(FESTIVAL_CONFIG, config_flags); 00547 if (!cfg) { 00548 ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG); 00549 return AST_MODULE_LOAD_DECLINE; 00550 } else if (cfg == CONFIG_STATUS_FILEINVALID) { 00551 ast_log(LOG_ERROR, "Config file " FESTIVAL_CONFIG " is in an invalid format. Aborting.\n"); 00552 return AST_MODULE_LOAD_DECLINE; 00553 } 00554 ast_config_destroy(cfg); 00555 return ast_register_application_xml(app, festival_exec); 00556 }
static int send_waveform_to_channel | ( | struct ast_channel * | chan, | |
char * | waveform, | |||
int | length, | |||
char * | intkeys | |||
) | [static] |
Definition at line 162 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().
00163 { 00164 int res = 0; 00165 int fds[2]; 00166 int pid = -1; 00167 int needed = 0; 00168 int owriteformat; 00169 struct ast_frame *f; 00170 struct myframe { 00171 struct ast_frame f; 00172 char offset[AST_FRIENDLY_OFFSET]; 00173 char frdata[2048]; 00174 } myf = { 00175 .f = { 0, }, 00176 }; 00177 00178 if (pipe(fds)) { 00179 ast_log(LOG_WARNING, "Unable to create pipe\n"); 00180 return -1; 00181 } 00182 00183 /* Answer if it's not already going */ 00184 if (chan->_state != AST_STATE_UP) 00185 ast_answer(chan); 00186 ast_stopstream(chan); 00187 ast_indicate(chan, -1); 00188 00189 owriteformat = chan->writeformat; 00190 res = ast_set_write_format(chan, AST_FORMAT_SLINEAR); 00191 if (res < 0) { 00192 ast_log(LOG_WARNING, "Unable to set write format to signed linear\n"); 00193 return -1; 00194 } 00195 00196 res = send_waveform_to_fd(waveform, length, fds[1]); 00197 if (res >= 0) { 00198 pid = res; 00199 /* Order is important -- there's almost always going to be mp3... we want to prioritize the 00200 user */ 00201 for (;;) { 00202 res = ast_waitfor(chan, 1000); 00203 if (res < 1) { 00204 res = -1; 00205 break; 00206 } 00207 f = ast_read(chan); 00208 if (!f) { 00209 ast_log(LOG_WARNING, "Null frame == hangup() detected\n"); 00210 res = -1; 00211 break; 00212 } 00213 if (f->frametype == AST_FRAME_DTMF) { 00214 ast_debug(1, "User pressed a key\n"); 00215 if (intkeys && strchr(intkeys, f->subclass.integer)) { 00216 res = f->subclass.integer; 00217 ast_frfree(f); 00218 break; 00219 } 00220 } 00221 if (f->frametype == AST_FRAME_VOICE) { 00222 /* Treat as a generator */ 00223 needed = f->samples * 2; 00224 if (needed > sizeof(myf.frdata)) { 00225 ast_log(LOG_WARNING, "Only able to deliver %d of %d requested samples\n", 00226 (int)sizeof(myf.frdata) / 2, needed/2); 00227 needed = sizeof(myf.frdata); 00228 } 00229 res = read(fds[0], myf.frdata, needed); 00230 if (res > 0) { 00231 myf.f.frametype = AST_FRAME_VOICE; 00232 myf.f.subclass.codec = AST_FORMAT_SLINEAR; 00233 myf.f.datalen = res; 00234 myf.f.samples = res / 2; 00235 myf.f.offset = AST_FRIENDLY_OFFSET; 00236 myf.f.src = __PRETTY_FUNCTION__; 00237 myf.f.data.ptr = myf.frdata; 00238 if (ast_write(chan, &myf.f) < 0) { 00239 res = -1; 00240 ast_frfree(f); 00241 break; 00242 } 00243 if (res < needed) { /* last frame */ 00244 ast_debug(1, "Last frame\n"); 00245 res = 0; 00246 ast_frfree(f); 00247 break; 00248 } 00249 } else { 00250 ast_debug(1, "No more waveform\n"); 00251 res = 0; 00252 } 00253 } 00254 ast_frfree(f); 00255 } 00256 } 00257 close(fds[0]); 00258 close(fds[1]); 00259 00260 #if 0 00261 if (pid > -1) 00262 kill(pid, SIGKILL); 00263 #endif 00264 if (!res && owriteformat) 00265 ast_set_write_format(chan, owriteformat); 00266 return res; 00267 }
static int send_waveform_to_fd | ( | char * | waveform, | |
int | length, | |||
int | fd | |||
) | [static] |
Definition at line 128 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().
00129 { 00130 int res; 00131 #if __BYTE_ORDER == __BIG_ENDIAN 00132 int x; 00133 char c; 00134 #endif 00135 00136 res = ast_safe_fork(0); 00137 if (res < 0) 00138 ast_log(LOG_WARNING, "Fork failed\n"); 00139 if (res) { 00140 return res; 00141 } 00142 dup2(fd, 0); 00143 ast_close_fds_above_n(0); 00144 if (ast_opt_high_priority) 00145 ast_set_priority(0); 00146 #if __BYTE_ORDER == __BIG_ENDIAN 00147 for (x = 0; x < length; x += 2) { 00148 c = *(waveform + x + 1); 00149 *(waveform + x + 1) = *(waveform + x); 00150 *(waveform + x) = c; 00151 } 00152 #endif 00153 00154 if (write(0, waveform, length) < 0) { 00155 /* Cannot log -- all FDs are already closed */ 00156 } 00157 00158 close(fd); 00159 _exit(0); 00160 }
static char* socket_receive_file_to_buff | ( | int | fd, | |
int * | size | |||
) | [static] |
Definition at line 77 of file app_festival.c.
References ast_free, ast_malloc, ast_realloc, and buff.
Referenced by festival_exec().
00078 { 00079 /* Receive file (probably a waveform file) from socket using 00080 * Festival key stuff technique, but long winded I know, sorry 00081 * but will receive any file without closing the stream or 00082 * using OOB data 00083 */ 00084 static char *file_stuff_key = "ft_StUfF_key"; /* must == Festival's key */ 00085 char *buff, *tmp; 00086 int bufflen; 00087 int n,k,i; 00088 char c; 00089 00090 bufflen = 1024; 00091 if (!(buff = ast_malloc(bufflen))) 00092 return NULL; 00093 *size = 0; 00094 00095 for (k = 0; file_stuff_key[k] != '\0';) { 00096 n = read(fd, &c, 1); 00097 if (n == 0) 00098 break; /* hit stream eof before end of file */ 00099 if ((*size) + k + 1 >= bufflen) { 00100 /* +1 so you can add a terminating NULL if you want */ 00101 bufflen += bufflen / 4; 00102 if (!(tmp = ast_realloc(buff, bufflen))) { 00103 ast_free(buff); 00104 return NULL; 00105 } 00106 buff = tmp; 00107 } 00108 if (file_stuff_key[k] == c) 00109 k++; 00110 else if ((c == 'X') && (file_stuff_key[k+1] == '\0')) { 00111 /* It looked like the key but wasn't */ 00112 for (i = 0; i < k; i++, (*size)++) 00113 buff[*size] = file_stuff_key[i]; 00114 k = 0; 00115 /* omit the stuffed 'X' */ 00116 } else { 00117 for (i = 0; i < k; i++, (*size)++) 00118 buff[*size] = file_stuff_key[i]; 00119 k = 0; 00120 buff[*size] = c; 00121 (*size)++; 00122 } 00123 } 00124 00125 return buff; 00126 }
static int unload_module | ( | void | ) | [static] |
Definition at line 538 of file app_festival.c.
References ast_unregister_application().
00539 { 00540 return ast_unregister_application(app); 00541 }
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 = "8586c2a7d357cb591cc3a6607a8f62d1" , .load = load_module, .unload = unload_module, .load_pri = AST_MODPRI_DEFAULT, } [static] |
Definition at line 558 of file app_festival.c.
char* app = "Festival" [static] |
Definition at line 75 of file app_festival.c.
struct ast_module_info* ast_module_info = &__mod_info [static] |
Definition at line 558 of file app_festival.c.