2 * Asterisk -- A telephony toolkit for Linux.
6 * Copyright (C) 2002, Christos Ricudis
8 * Christos Ricudis <ricudis@itc.auth.gr>
10 * This program is free software, distributed under the terms of
11 * the GNU General Public License
14 #include <sys/types.h>
15 #include <asterisk/file.h>
16 #include <asterisk/logger.h>
17 #include <asterisk/channel.h>
18 #include <asterisk/pbx.h>
19 #include <asterisk/module.h>
20 #include <asterisk/md5.h>
21 #include <asterisk/config.h>
22 #include <asterisk/utils.h>
23 #include <asterisk/lock.h>
28 #include <sys/types.h>
29 #include <sys/socket.h>
31 #include <netinet/in.h>
32 #include <arpa/inet.h>
41 #define FESTIVAL_CONFIG "festival.conf"
43 static char *tdesc = "Simple Festival Interface";
45 static char *app = "Festival";
47 static char *synopsis = "Say text to the user";
49 static char *descrip =
50 " Festival(text[|intkeys]): Connect to Festival, send the argument, get back the waveform,"
51 "play it to the user, allowing any given interrupt keys to immediately terminate and return\n"
52 "the value, or 'any' to allow any number back (useful in dialplan)\n";
58 static char *socket_receive_file_to_buff(int fd,int *size)
60 /* Receive file (probably a waveform file) from socket using */
61 /* Festival key stuff technique, but long winded I know, sorry */
62 /* but will receive any file without closeing the stream or */
64 static char *file_stuff_key = "ft_StUfF_key"; /* must == Festival's key */
71 buff = (char *)malloc(bufflen);
74 for (k=0; file_stuff_key[k] != '\0';)
77 if (n==0) break; /* hit stream eof before end of file */
78 if ((*size)+k+1 >= bufflen)
79 { /* +1 so you can add a NULL if you want */
81 buff = (char *)realloc(buff,bufflen);
83 if (file_stuff_key[k] == c)
85 else if ((c == 'X') && (file_stuff_key[k+1] == '\0'))
86 { /* It looked like the key but wasn't */
87 for (i=0; i < k; i++,(*size)++)
88 buff[*size] = file_stuff_key[i];
90 /* omit the stuffed 'X' */
94 for (i=0; i < k; i++,(*size)++)
95 buff[*size] = file_stuff_key[i];
106 static int send_waveform_to_fd(char *waveform, int length, int fd) {
116 ast_log(LOG_WARNING, "Fork failed\n");
119 for (x=0;x<256;x++) {
125 for( x=0; x<length; x+=2)
128 *(waveform+x+1)=*(waveform+x);
133 write(fd,waveform,length);
140 static int send_waveform_to_channel(struct ast_channel *chan, char *waveform, int length, char *intkeys) {
150 char offset[AST_FRIENDLY_OFFSET];
155 ast_log(LOG_WARNING, "Unable to create pipe\n");
159 /* Answer if it's not already going */
160 if (chan->_state != AST_STATE_UP)
162 ast_stopstream(chan);
164 owriteformat = chan->writeformat;
165 res = ast_set_write_format(chan, AST_FORMAT_SLINEAR);
167 ast_log(LOG_WARNING, "Unable to set write format to signed linear\n");
171 res=send_waveform_to_fd(waveform,length,fds[1]);
174 /* Order is important -- there's almost always going to be mp3... we want to prioritize the
178 res = ast_waitfor(chan, ms);
185 ast_log(LOG_WARNING, "Null frame == hangup() detected\n");
189 if (f->frametype == AST_FRAME_DTMF) {
190 ast_log(LOG_DEBUG, "User pressed a key\n");
191 if (intkeys && strchr(intkeys, f->subclass)) {
197 if (f->frametype == AST_FRAME_VOICE) {
198 /* Treat as a generator */
199 needed = f->samples * 2;
200 if (needed > sizeof(myf.frdata)) {
201 ast_log(LOG_WARNING, "Only able to deliver %d of %d requested samples\n",
202 (int)sizeof(myf.frdata) / 2, needed/2);
203 needed = sizeof(myf.frdata);
205 res = read(fds[0], myf.frdata, needed);
207 myf.f.frametype = AST_FRAME_VOICE;
208 myf.f.subclass = AST_FORMAT_SLINEAR;
210 myf.f.samples = res / 2;
212 myf.f.offset = AST_FRIENDLY_OFFSET;
213 myf.f.src = __PRETTY_FUNCTION__;
214 myf.f.data = myf.frdata;
215 if (ast_write(chan, &myf.f) < 0) {
219 if (res < needed) { // last frame
220 ast_log(LOG_DEBUG, "Last frame\n");
225 ast_log(LOG_DEBUG, "No more waveform\n");
235 // kill(pid, SIGKILL);
236 if (!res && owriteformat)
237 ast_set_write_format(chan, owriteformat);
242 #define MAXFESTLEN 2048
247 static int festival_exec(struct ast_channel *chan, void *vdata)
252 struct sockaddr_in serv_addr;
253 struct hostent *serverhost;
254 struct ast_hostent ahp;
260 char *festivalcommand;
267 char bigstring[MAXFESTLEN];
269 struct MD5Context md5ctx;
270 unsigned char MD5Res[16];
271 char MD5Hex[33] = "";
273 char cachefile[MAXFESTLEN]="";
283 struct ast_config *cfg;
284 cfg = ast_load(FESTIVAL_CONFIG);
286 ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
289 if (!(host = ast_variable_retrieve(cfg, "general", "host"))) {
292 if (!(temp = ast_variable_retrieve(cfg, "general", "port"))) {
297 if (!(temp = ast_variable_retrieve(cfg, "general", "usecache"))) {
300 usecache = ast_true(temp);
302 if (!(cachedir = ast_variable_retrieve(cfg, "general", "cachedir"))) {
305 if (!(festivalcommand = ast_variable_retrieve(cfg, "general", "festivalcommand"))) {
306 festivalcommand = "(tts_textasterisk \"%s\" 'file)(quit)\n";
309 if (!vdata || ast_strlen_zero(vdata)) {
310 ast_log(LOG_WARNING, "festival requires an argument (text)\n");
313 strncpy(data, vdata, sizeof(data) - 1);
314 if ((intstr = strchr(data, '|'))) {
317 if (!strcasecmp(intstr, "any"))
318 intstr = AST_DIGIT_ANY;
321 ast_log(LOG_DEBUG, "Text passed to festival server : %s\n",(char *)data);
322 /* Connect to local festival server */
324 fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
327 ast_log(LOG_WARNING,"festival_client: can't get socket\n");
330 memset(&serv_addr, 0, sizeof(serv_addr));
331 if ((serv_addr.sin_addr.s_addr = inet_addr(host)) == -1) {
332 /* its a name rather than an ipnum */
333 serverhost = ast_gethostbyname(host, &ahp);
334 if (serverhost == (struct hostent *)0) {
335 ast_log(LOG_WARNING,"festival_client: gethostbyname failed\n");
338 memmove(&serv_addr.sin_addr,serverhost->h_addr, serverhost->h_length);
340 serv_addr.sin_family = AF_INET;
341 serv_addr.sin_port = htons(port);
343 if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0) {
344 ast_log(LOG_WARNING,"festival_client: connect to server failed\n");
348 /* Compute MD5 sum of string */
350 MD5Update(&md5ctx,(unsigned char const *)data,strlen(data));
351 MD5Final(MD5Res,&md5ctx);
354 /* Convert to HEX and look if there is any matching file in the cache
357 snprintf(koko, sizeof(koko), "%X",MD5Res[i]);
358 strncat(MD5Hex, koko, sizeof(MD5Hex) - strlen(MD5Hex) - 1);
362 if (strlen(cachedir)+strlen(MD5Hex)+1<=MAXFESTLEN && (usecache==-1)) {
363 snprintf(cachefile, sizeof(cachefile), "%s/%s", cachedir, MD5Hex);
364 fdesc=open(cachefile,O_RDWR);
366 fdesc=open(cachefile,O_CREAT|O_RDWR,0777);
369 strln=strlen((char *)data);
370 ast_log(LOG_DEBUG,"line length : %d\n",strln);
371 write(fdesc,&strln,sizeof(int));
372 write(fdesc,data,strln);
373 seekpos=lseek(fdesc,0,SEEK_CUR);
374 ast_log(LOG_DEBUG,"Seek position : %d\n",seekpos);
377 read(fdesc,&strln,sizeof(int));
378 ast_log(LOG_DEBUG,"Cache file exists, strln=%d, strlen=%d\n",strln,(int)strlen((char *)data));
379 if (strlen((char *)data)==strln) {
380 ast_log(LOG_DEBUG,"Size OK\n");
381 read(fdesc,&bigstring,strln);
382 bigstring[strln] = 0;
383 if (strcmp(bigstring,data)==0) {
386 ast_log(LOG_WARNING,"Strings do not match\n");
389 ast_log(LOG_WARNING,"Size mismatch\n");
397 ast_log(LOG_DEBUG,"Reading from cache...\n");
399 ast_log(LOG_DEBUG,"Passing text to festival...\n");
400 fs=fdopen(dup(fd),"wb");
401 fprintf(fs,festivalcommand,(char *)data);
406 /* Write to cache and then pass it down */
408 ast_log(LOG_DEBUG,"Writing result to cache...\n");
409 while ((strln=read(fd,buffer,16384))!=0) {
410 write(fdesc,buffer,strln);
414 fd=open(cachefile,O_RDWR);
415 lseek(fd,seekpos,SEEK_SET);
418 ast_log(LOG_DEBUG,"Passing data to channel...\n");
420 /* Read back info from server */
421 /* This assumes only one waveform will come back, also LP is unlikely */
425 n += read(fd,ack+n,3-n);
427 if (strcmp(ack,"WV\n") == 0) { /* receive a waveform */
428 ast_log(LOG_DEBUG,"Festival WV command\n");
429 waveform = socket_receive_file_to_buff(fd,&filesize);
430 res = send_waveform_to_channel(chan,waveform,filesize, intstr);
434 else if (strcmp(ack,"LP\n") == 0) { /* receive an s-expr */
435 ast_log(LOG_DEBUG,"Festival LP command\n");
436 waveform = socket_receive_file_to_buff(fd,&filesize);
437 waveform[filesize]='\0';
438 ast_log(LOG_WARNING,"Festival returned LP : %s\n",waveform);
440 } else if (strcmp(ack,"ER\n") == 0) { /* server got an error */
441 ast_log(LOG_WARNING,"Festival returned ER\n");
445 } while (strcmp(ack,"OK\n") != 0);
447 LOCAL_USER_REMOVE(u);
452 int unload_module(void)
454 STANDARD_HANGUP_LOCALUSERS;
455 return ast_unregister_application(app);
458 int load_module(void)
461 return ast_register_application(app, festival_exec, synopsis, descrip);
464 char *description(void)
472 STANDARD_USECOUNT(res);
478 return ASTERISK_GPL_KEY;