Index: apps/app_ivonacl.c =================================================================== --- apps/app_ivonacl.c (wersja 0) +++ apps/app_ivonacl.c (wersja 0) @@ -0,0 +1,322 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2009 - 2010, IVO Software Sp. z o. o. + * + * Michał Kiedrowicz + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Say text to the user using IVONA TTS. + * + * \author Michał Kiedrowicz + * + * \extref IVONA TTS - http://www.ivona.com + * + * \ingroup applications + */ + +#include "asterisk.h" + +#include "asterisk/app.h" +#include "asterisk/channel.h" +#include "asterisk/file.h" +#include "asterisk/module.h" + +/*** DOCUMENTATION + + + Say text to the user using IVONA TTS. + + + + Use given profile from ivona.conf. Defaults to + 'general'. + + + Text to be read by IVONA TTS. + + + + This will connect to ivonacl daemon using options from + section 'name' ('general' by default) of the configuration file, + send the text string, get back the resulting stream and play the + stream in the real time. If the application receives a DTMF frame, + it stops streaming and returns received DTMF code. + + + ***/ + +#define IVONA_CONFIG "ivona.conf" +#define READ_TIMEOUT 2000 +#define SPEECHLEN 0.1 +#define DATALEN ((size_t) (SPEECHLEN * 8000 * sizeof(short))) + +static char *app = "IVONA"; + +static int connect_addr_port(const char *address, const char *port) +{ + struct addrinfo hints, *ai, *ai0; + int fd = -1; + int gai; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + gai = getaddrinfo(address, port, &hints, &ai0); + if (gai) { + ast_log(LOG_WARNING, + "Cannot get address info for address '%s' and port '%s': %s\n", + address, port, gai_strerror(gai)); + return -1; + } + + for (ai = ai0; ai; ai = ai->ai_next) { + fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (fd < 0) { + ast_log(LOG_WARNING, "Cannot create socket: %s\n", strerror(errno)); + continue; + } + + if (connect(fd, ai->ai_addr, ai->ai_addrlen) < 0) { + ast_log(LOG_WARNING, "Cannot connect to address '%s:%s': %s\n", address, + port, strerror(errno)); + close(fd); + fd = -1; + continue; + } + } + freeaddrinfo(ai0); + + if (fd != -1) + ast_debug(1, "Connected to ivonacl daemon\n"); + + return fd; +} + +static int ivonacl_connect(const char *name) +{ + struct ast_config *cfg; + struct ast_flags config_flags = { 0 }; + const char *address = NULL, *port = NULL; + int fd; + + cfg = ast_config_load(IVONA_CONFIG, config_flags); + if (cfg == CONFIG_STATUS_FILEINVALID) { + ast_log(LOG_ERROR, "Config file " IVONA_CONFIG + " is in an invalid format. Aborting\n"); + return -1; + } else if (cfg) { + ast_debug(1, "Loading configuration from file " IVONA_CONFIG "\n"); + address = ast_variable_retrieve(cfg, name, "address"); + port = ast_variable_retrieve(cfg, name, "port"); + } else { + ast_debug(1, "Using default configuration\n"); + } + + if (!address) + address = "localhost"; + + if (!port) + port = "9123"; + + fd = connect_addr_port(address, port); + + if (cfg) + ast_config_destroy(cfg); + + return fd; +} + +static int ivonacl_send_text(int fd, const char *text) +{ + size_t offset = 0, len = strlen(text) + 1; /* Send also NULL byte. */ + + while (offset < len) { + int rc = write(fd, text + offset, len - offset); + if (rc < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + + ast_log(LOG_WARNING, "Failed to send text to ivonacl daemon: %s\n", + strerror(errno)); + return -1; + } + offset += rc; + } + + ast_debug(1, "Sent %lu bytes to ivonacl daemon\n", (unsigned long) len); + return 0; +} + +static void ivonacl_disconnect(int fd) +{ + ast_debug(1, "Closing connection with ivonacl daemon\n"); + close(fd); +} + +struct myframe { + struct ast_frame f; + char offset[AST_FRIENDLY_OFFSET]; + short data[DATALEN]; +}; + +static int ivonacl_read_frame(int fd, struct myframe *myf) +{ + int rc = ast_wait_for_input(1, READ_TIMEOUT); + if (rc < 0) { + ast_log(LOG_WARNING, "Poll errored with: %s\n", strerror(errno)); + return -1; + } if (rc == 0) { + ast_log(LOG_WARNING, "Poll timed out\n"); + return -1; + } + + rc = read(fd, myf->data, sizeof(myf->data)); + if (rc < 0) { + ast_log(LOG_WARNING, + "Failed to read a frame from ivonacl daemon: %s\n", strerror(errno)); + } else if (rc == 0) { + ast_debug(1, "No more samples\n"); + } else { + AST_FRAME_SET_BUFFER(&myf->f, myf->offset, AST_FRIENDLY_OFFSET, rc); + myf->f.frametype = AST_FRAME_VOICE; + myf->f.subclass.codec = AST_FORMAT_SLINEAR; + myf->f.samples = rc / 2; + myf->f.mallocd = 0; + myf->f.src = __PRETTY_FUNCTION__; + myf->f.delivery.tv_sec = 0; + myf->f.delivery.tv_usec = 0; + } + + return rc; +} + +static int read_and_process_frame(struct ast_channel *chan, int ms) +{ + int rc = ast_waitfor(chan, ms); + if (rc < 0) { + ast_debug(1, "Hangup detected\n"); + return -1; + } else if (rc) { + struct ast_frame *f = ast_read(chan); + if (!f) { + ast_debug(1, "No frame received - hangup detected\n"); + return -1; + } + if (f->frametype == AST_FRAME_DTMF) { + int digit = f->subclass.integer; + ast_debug(1, "User pressed the '%d' key\n", digit); + ast_frfree(f); + return digit; + } + ast_frfree(f); + } + return -2; +} + +static int ivonacl_exec(struct ast_channel *chan, const char *data) +{ + int fd = -1, rc = -1, argc, owriteformat = 0; + struct timeval next; + char *vdata; + AST_DECLARE_APP_ARGS( + args, + AST_APP_ARG(name); + AST_APP_ARG(text); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "IVONA requires an argument (text)\n"); + return -1; + } + + vdata = ast_strdupa(data); + if (!vdata) { + ast_log(LOG_WARNING, "Out of memory!\n"); + return -1; + } + + argc = AST_STANDARD_APP_ARGS(args, vdata); + if (argc < 2) { + args.text = args.name; + args.name = "general"; + } + + fd = ivonacl_connect(args.name); + if (fd == -1) + return -1; + + if (ivonacl_send_text(fd, args.text) < 0) + goto out; + + if (chan->_state != AST_STATE_UP && ast_answer(chan) < 0) + goto out; + + ast_stopstream(chan); + + owriteformat = chan->writeformat; + if (ast_set_write_format(chan, AST_FORMAT_SLINEAR) < 0) { + ast_log(LOG_WARNING, "Unable to set write format to signed linear\n"); + goto out; + } + + /* Wait 1s first. */ + next = ast_tvnow(); + next.tv_sec += 1; + while (1) { + struct myframe myf = {.f = {.frametype = 0} }; + int ms = ast_tvdiff_ms(next, ast_tvnow()); + if (ms <= 0) { + rc = ivonacl_read_frame(fd, &myf); + if (rc <= 0) + break; + + if (ast_write(chan, &myf.f) < 0) { + ast_log(LOG_WARNING, "Failed to write a frame to Asterisk channel\n"); + rc = -1; + break; + } + + next = ast_tvadd(next, ast_samp2tv(myf.f.samples, 8000)); + } else { + rc = read_and_process_frame(chan, ms); + if (rc != -2) + break; + } + } + +out: + if (owriteformat) + ast_set_write_format(chan, owriteformat); + + if (fd != -1) + ivonacl_disconnect(fd); + + return rc; +} + +static int load_module(void) +{ + return ast_register_application_xml(app, ivonacl_exec); +} + +static int unload_module(void) +{ + return ast_unregister_application(app); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "IVONA TTS Application"); Index: configs/ivona.conf.sample =================================================================== --- configs/ivona.conf.sample (wersja 0) +++ configs/ivona.conf.sample (wersja 0) @@ -0,0 +1,29 @@ +; +; ivona.conf +; +; +; Sample configuration file for the IVONA TTS Application. +; + +[general] + +; ivonacl IP address. + +address=localhost + +; ivonacl IP port. + +port=9123 + +; This example configuration defines two profiles, amy and kendra, with two +; different voices. The module will connect to ivonacl at port 9123 for Amy and +; at port 9130 for Kendra. + +; [amy] +; address = localhost +; port = 9123 +; +; [kendra] +; address = localhost +; port = 9130 +