app_festival.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. /*
  2. * Asterisk -- An open source telephony toolkit.
  3. *
  4. * Copyright (C) 2002, Christos Ricudis
  5. *
  6. * Christos Ricudis <ricudis@itc.auth.gr>
  7. *
  8. * See http://www.asterisk.org for more information about
  9. * the Asterisk project. Please do not directly contact
  10. * any of the maintainers of this project for assistance;
  11. * the project provides a web site, mailing lists and IRC
  12. * channels for your use.
  13. *
  14. * This program is free software, distributed under the terms of
  15. * the GNU General Public License Version 2. See the LICENSE file
  16. * at the top of the source tree.
  17. */
  18. /*! \file
  19. *
  20. * \brief Connect to festival
  21. *
  22. * \author Christos Ricudis <ricudis@itc.auth.gr>
  23. *
  24. * \extref The Festival Speech Synthesis System - http://www.cstr.ed.ac.uk/projects/festival/
  25. *
  26. * \ingroup applications
  27. */
  28. /*** MODULEINFO
  29. <support_level>extended</support_level>
  30. <defaultenabled>no</defaultenabled>
  31. ***/
  32. #include "asterisk.h"
  33. ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  34. #include <sys/socket.h>
  35. #include <netdb.h>
  36. #include <netinet/in.h>
  37. #include <arpa/inet.h>
  38. #include <signal.h>
  39. #include <fcntl.h>
  40. #include <ctype.h>
  41. #include <errno.h>
  42. #include "asterisk/file.h"
  43. #include "asterisk/channel.h"
  44. #include "asterisk/pbx.h"
  45. #include "asterisk/module.h"
  46. #include "asterisk/md5.h"
  47. #include "asterisk/config.h"
  48. #include "asterisk/utils.h"
  49. #include "asterisk/lock.h"
  50. #include "asterisk/app.h"
  51. #include "asterisk/endian.h"
  52. #define FESTIVAL_CONFIG "festival.conf"
  53. #define MAXLEN 180
  54. #define MAXFESTLEN 2048
  55. /*** DOCUMENTATION
  56. <application name="Festival" language="en_US">
  57. <synopsis>
  58. Say text to the user.
  59. </synopsis>
  60. <syntax>
  61. <parameter name="text" required="true" />
  62. <parameter name="intkeys" />
  63. </syntax>
  64. <description>
  65. <para>Connect to Festival, send the argument, get back the waveform, play it to the user,
  66. allowing any given interrupt keys to immediately terminate and return the value, or
  67. <literal>any</literal> to allow any number back (useful in dialplan).</para>
  68. </description>
  69. </application>
  70. ***/
  71. static char *app = "Festival";
  72. static char *socket_receive_file_to_buff(int fd, int *size)
  73. {
  74. /* Receive file (probably a waveform file) from socket using
  75. * Festival key stuff technique, but long winded I know, sorry
  76. * but will receive any file without closing the stream or
  77. * using OOB data
  78. */
  79. static char *file_stuff_key = "ft_StUfF_key"; /* must == Festival's key */
  80. char *buff, *tmp;
  81. int bufflen;
  82. int n,k,i;
  83. char c;
  84. bufflen = 1024;
  85. if (!(buff = ast_malloc(bufflen)))
  86. return NULL;
  87. *size = 0;
  88. for (k = 0; file_stuff_key[k] != '\0';) {
  89. n = read(fd, &c, 1);
  90. if (n == 0)
  91. break; /* hit stream eof before end of file */
  92. if ((*size) + k + 1 >= bufflen) {
  93. /* +1 so you can add a terminating NULL if you want */
  94. bufflen += bufflen / 4;
  95. if (!(tmp = ast_realloc(buff, bufflen))) {
  96. ast_free(buff);
  97. return NULL;
  98. }
  99. buff = tmp;
  100. }
  101. if (file_stuff_key[k] == c)
  102. k++;
  103. else if ((c == 'X') && (file_stuff_key[k+1] == '\0')) {
  104. /* It looked like the key but wasn't */
  105. for (i = 0; i < k; i++, (*size)++)
  106. buff[*size] = file_stuff_key[i];
  107. k = 0;
  108. /* omit the stuffed 'X' */
  109. } else {
  110. for (i = 0; i < k; i++, (*size)++)
  111. buff[*size] = file_stuff_key[i];
  112. k = 0;
  113. buff[*size] = c;
  114. (*size)++;
  115. }
  116. }
  117. return buff;
  118. }
  119. static int send_waveform_to_fd(char *waveform, int length, int fd)
  120. {
  121. int res;
  122. #if __BYTE_ORDER == __BIG_ENDIAN
  123. int x;
  124. char c;
  125. #endif
  126. res = ast_safe_fork(0);
  127. if (res < 0)
  128. ast_log(LOG_WARNING, "Fork failed\n");
  129. if (res) {
  130. return res;
  131. }
  132. dup2(fd, 0);
  133. ast_close_fds_above_n(0);
  134. if (ast_opt_high_priority)
  135. ast_set_priority(0);
  136. #if __BYTE_ORDER == __BIG_ENDIAN
  137. for (x = 0; x < length; x += 2) {
  138. c = *(waveform + x + 1);
  139. *(waveform + x + 1) = *(waveform + x);
  140. *(waveform + x) = c;
  141. }
  142. #endif
  143. if (write(0, waveform, length) < 0) {
  144. /* Cannot log -- all FDs are already closed */
  145. }
  146. close(fd);
  147. _exit(0);
  148. }
  149. static int send_waveform_to_channel(struct ast_channel *chan, char *waveform, int length, char *intkeys)
  150. {
  151. int res = 0;
  152. int fds[2];
  153. int needed = 0;
  154. int owriteformat;
  155. struct ast_frame *f;
  156. struct myframe {
  157. struct ast_frame f;
  158. char offset[AST_FRIENDLY_OFFSET];
  159. char frdata[2048];
  160. } myf = {
  161. .f = { 0, },
  162. };
  163. if (pipe(fds)) {
  164. ast_log(LOG_WARNING, "Unable to create pipe\n");
  165. return -1;
  166. }
  167. /* Answer if it's not already going */
  168. if (chan->_state != AST_STATE_UP)
  169. ast_answer(chan);
  170. ast_stopstream(chan);
  171. ast_indicate(chan, -1);
  172. owriteformat = chan->writeformat;
  173. res = ast_set_write_format(chan, AST_FORMAT_SLINEAR);
  174. if (res < 0) {
  175. ast_log(LOG_WARNING, "Unable to set write format to signed linear\n");
  176. return -1;
  177. }
  178. res = send_waveform_to_fd(waveform, length, fds[1]);
  179. if (res >= 0) {
  180. /* Order is important -- there's almost always going to be mp3... we want to prioritize the
  181. user */
  182. for (;;) {
  183. res = ast_waitfor(chan, 1000);
  184. if (res < 1) {
  185. res = -1;
  186. break;
  187. }
  188. f = ast_read(chan);
  189. if (!f) {
  190. ast_log(LOG_WARNING, "Null frame == hangup() detected\n");
  191. res = -1;
  192. break;
  193. }
  194. if (f->frametype == AST_FRAME_DTMF) {
  195. ast_debug(1, "User pressed a key\n");
  196. if (intkeys && strchr(intkeys, f->subclass.integer)) {
  197. res = f->subclass.integer;
  198. ast_frfree(f);
  199. break;
  200. }
  201. }
  202. if (f->frametype == AST_FRAME_VOICE) {
  203. /* Treat as a generator */
  204. needed = f->samples * 2;
  205. if (needed > sizeof(myf.frdata)) {
  206. ast_log(LOG_WARNING, "Only able to deliver %d of %d requested samples\n",
  207. (int)sizeof(myf.frdata) / 2, needed/2);
  208. needed = sizeof(myf.frdata);
  209. }
  210. res = read(fds[0], myf.frdata, needed);
  211. if (res > 0) {
  212. myf.f.frametype = AST_FRAME_VOICE;
  213. myf.f.subclass.codec = AST_FORMAT_SLINEAR;
  214. myf.f.datalen = res;
  215. myf.f.samples = res / 2;
  216. myf.f.offset = AST_FRIENDLY_OFFSET;
  217. myf.f.src = __PRETTY_FUNCTION__;
  218. myf.f.data.ptr = myf.frdata;
  219. if (ast_write(chan, &myf.f) < 0) {
  220. res = -1;
  221. ast_frfree(f);
  222. break;
  223. }
  224. if (res < needed) { /* last frame */
  225. ast_debug(1, "Last frame\n");
  226. res = 0;
  227. ast_frfree(f);
  228. break;
  229. }
  230. } else {
  231. ast_debug(1, "No more waveform\n");
  232. res = 0;
  233. }
  234. }
  235. ast_frfree(f);
  236. }
  237. }
  238. close(fds[0]);
  239. close(fds[1]);
  240. if (!res && owriteformat)
  241. ast_set_write_format(chan, owriteformat);
  242. return res;
  243. }
  244. static int festival_exec(struct ast_channel *chan, const char *vdata)
  245. {
  246. int usecache;
  247. int res = 0;
  248. struct sockaddr_in serv_addr;
  249. struct hostent *serverhost;
  250. struct ast_hostent ahp;
  251. int fd;
  252. FILE *fs;
  253. const char *host;
  254. const char *cachedir;
  255. const char *temp;
  256. const char *festivalcommand;
  257. int port = 1314;
  258. int n;
  259. char ack[4];
  260. char *waveform;
  261. int filesize;
  262. char bigstring[MAXFESTLEN];
  263. int i;
  264. struct MD5Context md5ctx;
  265. unsigned char MD5Res[16];
  266. char MD5Hex[33] = "";
  267. char koko[4] = "";
  268. char cachefile[MAXFESTLEN]="";
  269. int readcache = 0;
  270. int writecache = 0;
  271. int strln;
  272. int fdesc = -1;
  273. char buffer[16384];
  274. int seekpos = 0;
  275. char *data;
  276. struct ast_config *cfg;
  277. char *newfestivalcommand;
  278. struct ast_flags config_flags = { 0 };
  279. AST_DECLARE_APP_ARGS(args,
  280. AST_APP_ARG(text);
  281. AST_APP_ARG(interrupt);
  282. );
  283. if (ast_strlen_zero(vdata)) {
  284. ast_log(LOG_WARNING, "festival requires an argument (text)\n");
  285. return -1;
  286. }
  287. cfg = ast_config_load(FESTIVAL_CONFIG, config_flags);
  288. if (!cfg) {
  289. ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
  290. return -1;
  291. } else if (cfg == CONFIG_STATUS_FILEINVALID) {
  292. ast_log(LOG_ERROR, "Config file " FESTIVAL_CONFIG " is in an invalid format. Aborting.\n");
  293. return -1;
  294. }
  295. if (!(host = ast_variable_retrieve(cfg, "general", "host"))) {
  296. host = "localhost";
  297. }
  298. if (!(temp = ast_variable_retrieve(cfg, "general", "port"))) {
  299. port = 1314;
  300. } else {
  301. port = atoi(temp);
  302. }
  303. if (!(temp = ast_variable_retrieve(cfg, "general", "usecache"))) {
  304. usecache = 0;
  305. } else {
  306. usecache = ast_true(temp);
  307. }
  308. if (!(cachedir = ast_variable_retrieve(cfg, "general", "cachedir"))) {
  309. cachedir = "/tmp/";
  310. }
  311. data = ast_strdupa(vdata);
  312. AST_STANDARD_APP_ARGS(args, data);
  313. if (!(festivalcommand = ast_variable_retrieve(cfg, "general", "festivalcommand"))) {
  314. const char *startcmd = "(tts_textasterisk \"";
  315. const char *endcmd = "\" 'file)(quit)\n";
  316. strln = strlen(startcmd) + strlen(args.text) + strlen(endcmd) + 1;
  317. newfestivalcommand = alloca(strln);
  318. snprintf(newfestivalcommand, strln, "%s%s%s", startcmd, args.text, endcmd);
  319. festivalcommand = newfestivalcommand;
  320. } else { /* This else parses the festivalcommand that we're sent from the config file for \n's, etc */
  321. int x, j;
  322. newfestivalcommand = alloca(strlen(festivalcommand) + strlen(args.text) + 1);
  323. for (x = 0, j = 0; x < strlen(festivalcommand); x++) {
  324. if (festivalcommand[x] == '\\' && festivalcommand[x + 1] == 'n') {
  325. newfestivalcommand[j++] = '\n';
  326. x++;
  327. } else if (festivalcommand[x] == '\\') {
  328. newfestivalcommand[j++] = festivalcommand[x + 1];
  329. x++;
  330. } else if (festivalcommand[x] == '%' && festivalcommand[x + 1] == 's') {
  331. sprintf(&newfestivalcommand[j], "%s", args.text); /* we know it is big enough */
  332. j += strlen(args.text);
  333. x++;
  334. } else
  335. newfestivalcommand[j++] = festivalcommand[x];
  336. }
  337. newfestivalcommand[j] = '\0';
  338. festivalcommand = newfestivalcommand;
  339. }
  340. if (args.interrupt && !strcasecmp(args.interrupt, "any"))
  341. args.interrupt = AST_DIGIT_ANY;
  342. ast_debug(1, "Text passed to festival server : %s\n", args.text);
  343. /* Connect to local festival server */
  344. fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  345. if (fd < 0) {
  346. ast_log(LOG_WARNING, "festival_client: can't get socket\n");
  347. ast_config_destroy(cfg);
  348. return -1;
  349. }
  350. memset(&serv_addr, 0, sizeof(serv_addr));
  351. if ((serv_addr.sin_addr.s_addr = inet_addr(host)) == -1) {
  352. /* its a name rather than an ipnum */
  353. serverhost = ast_gethostbyname(host, &ahp);
  354. if (serverhost == NULL) {
  355. ast_log(LOG_WARNING, "festival_client: gethostbyname failed\n");
  356. ast_config_destroy(cfg);
  357. return -1;
  358. }
  359. memmove(&serv_addr.sin_addr, serverhost->h_addr, serverhost->h_length);
  360. }
  361. serv_addr.sin_family = AF_INET;
  362. serv_addr.sin_port = htons(port);
  363. if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0) {
  364. ast_log(LOG_WARNING, "festival_client: connect to server failed\n");
  365. ast_config_destroy(cfg);
  366. return -1;
  367. }
  368. /* Compute MD5 sum of string */
  369. MD5Init(&md5ctx);
  370. MD5Update(&md5ctx, (unsigned char *)args.text, strlen(args.text));
  371. MD5Final(MD5Res, &md5ctx);
  372. MD5Hex[0] = '\0';
  373. /* Convert to HEX and look if there is any matching file in the cache
  374. directory */
  375. for (i = 0; i < 16; i++) {
  376. snprintf(koko, sizeof(koko), "%X", MD5Res[i]);
  377. strncat(MD5Hex, koko, sizeof(MD5Hex) - strlen(MD5Hex) - 1);
  378. }
  379. readcache = 0;
  380. writecache = 0;
  381. if (strlen(cachedir) + strlen(MD5Hex) + 1 <= MAXFESTLEN && (usecache == -1)) {
  382. snprintf(cachefile, sizeof(cachefile), "%s/%s", cachedir, MD5Hex);
  383. fdesc = open(cachefile, O_RDWR);
  384. if (fdesc == -1) {
  385. fdesc = open(cachefile, O_CREAT | O_RDWR, AST_FILE_MODE);
  386. if (fdesc != -1) {
  387. writecache = 1;
  388. strln = strlen(args.text);
  389. ast_debug(1, "line length : %d\n", strln);
  390. if (write(fdesc,&strln,sizeof(int)) < 0) {
  391. ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno));
  392. }
  393. if (write(fdesc,data,strln) < 0) {
  394. ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno));
  395. }
  396. seekpos = lseek(fdesc, 0, SEEK_CUR);
  397. ast_debug(1, "Seek position : %d\n", seekpos);
  398. }
  399. } else {
  400. if (read(fdesc,&strln,sizeof(int)) != sizeof(int)) {
  401. ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno));
  402. }
  403. ast_debug(1, "Cache file exists, strln=%d, strlen=%d\n", strln, (int)strlen(args.text));
  404. if (strlen(args.text) == strln) {
  405. ast_debug(1, "Size OK\n");
  406. if (read(fdesc,&bigstring,strln) != strln) {
  407. ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno));
  408. }
  409. bigstring[strln] = 0;
  410. if (strcmp(bigstring, args.text) == 0) {
  411. readcache = 1;
  412. } else {
  413. ast_log(LOG_WARNING, "Strings do not match\n");
  414. }
  415. } else {
  416. ast_log(LOG_WARNING, "Size mismatch\n");
  417. }
  418. }
  419. }
  420. if (readcache == 1) {
  421. close(fd);
  422. fd = fdesc;
  423. ast_debug(1, "Reading from cache...\n");
  424. } else {
  425. ast_debug(1, "Passing text to festival...\n");
  426. fs = fdopen(dup(fd), "wb");
  427. fprintf(fs, "%s", festivalcommand);
  428. fflush(fs);
  429. fclose(fs);
  430. }
  431. /* Write to cache and then pass it down */
  432. if (writecache == 1) {
  433. ast_debug(1, "Writing result to cache...\n");
  434. while ((strln = read(fd, buffer, 16384)) != 0) {
  435. if (write(fdesc,buffer,strln) < 0) {
  436. ast_log(LOG_WARNING, "write() failed: %s\n", strerror(errno));
  437. }
  438. }
  439. close(fd);
  440. close(fdesc);
  441. fd = open(cachefile, O_RDWR);
  442. lseek(fd, seekpos, SEEK_SET);
  443. }
  444. ast_debug(1, "Passing data to channel...\n");
  445. /* Read back info from server */
  446. /* This assumes only one waveform will come back, also LP is unlikely */
  447. do {
  448. int read_data;
  449. for (n = 0; n < 3; ) {
  450. read_data = read(fd, ack + n, 3 - n);
  451. /* this avoids falling in infinite loop
  452. * in case that festival server goes down
  453. */
  454. if (read_data == -1) {
  455. ast_log(LOG_WARNING, "Unable to read from cache/festival fd\n");
  456. close(fd);
  457. ast_config_destroy(cfg);
  458. return -1;
  459. }
  460. n += read_data;
  461. }
  462. ack[3] = '\0';
  463. if (strcmp(ack, "WV\n") == 0) { /* receive a waveform */
  464. ast_debug(1, "Festival WV command\n");
  465. if ((waveform = socket_receive_file_to_buff(fd, &filesize))) {
  466. res = send_waveform_to_channel(chan, waveform, filesize, args.interrupt);
  467. ast_free(waveform);
  468. }
  469. break;
  470. } else if (strcmp(ack, "LP\n") == 0) { /* receive an s-expr */
  471. ast_debug(1, "Festival LP command\n");
  472. if ((waveform = socket_receive_file_to_buff(fd, &filesize))) {
  473. waveform[filesize] = '\0';
  474. ast_log(LOG_WARNING, "Festival returned LP : %s\n", waveform);
  475. ast_free(waveform);
  476. }
  477. } else if (strcmp(ack, "ER\n") == 0) { /* server got an error */
  478. ast_log(LOG_WARNING, "Festival returned ER\n");
  479. res = -1;
  480. break;
  481. }
  482. } while (strcmp(ack, "OK\n") != 0);
  483. close(fd);
  484. ast_config_destroy(cfg);
  485. return res;
  486. }
  487. static int unload_module(void)
  488. {
  489. return ast_unregister_application(app);
  490. }
  491. static int load_module(void)
  492. {
  493. struct ast_flags config_flags = { 0 };
  494. struct ast_config *cfg = ast_config_load(FESTIVAL_CONFIG, config_flags);
  495. if (!cfg) {
  496. ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
  497. return AST_MODULE_LOAD_DECLINE;
  498. } else if (cfg == CONFIG_STATUS_FILEINVALID) {
  499. ast_log(LOG_ERROR, "Config file " FESTIVAL_CONFIG " is in an invalid format. Aborting.\n");
  500. return AST_MODULE_LOAD_DECLINE;
  501. }
  502. ast_config_destroy(cfg);
  503. return ast_register_application_xml(app, festival_exec);
  504. }
  505. AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Simple Festival Interface");