/* * Asterisk -- A telephony toolkit for Linux. * * app_cepstral Text To Speech Application * * Copyright (C) 2006, Anthony Minessale II * * Anthony Minessale II * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include #include #include #include #include /* Asterisk Stuff */ static char *tdesc = "Text To Speech"; static char *app = "Cepstral"; static char *synopsis = "TTS conversion and playback"; static char *descrip = "Cepstral([|]):\n\nConverts text to speech using Cepstral\n"; STANDARD_LOCAL_USER; LOCAL_USER_DECL; /* structure to hold session data */ struct uobj { struct ast_channel *chan; /* The channel */ unsigned char buf[8192]; /* A data buffer */ void *end; /* pointer to the end of the buffer */ int remain; /* bytes remaining in the buffer */ int loops; /* how many times we have looped */ int head_start; /* how many loops to skip before pulling data from the buffer */ struct ast_frame fr; /* asterisk frame for writing */ }; static swift_result_t write_audio(swift_event *event, swift_event_t type, void *udata) { struct uobj *obj = udata; struct ast_channel *chan = obj->chan; swift_event_t rv = SWIFT_SUCCESS; void *buf = NULL; int len = 0; /* Only proceed when we have success */ if (!SWIFT_FAILED((rv = swift_event_get_audio(event, &buf, &len)))) { /* copy the data into our buffer and store the size */ memcpy(obj->end, buf, len); obj->remain += len; /* skip a few loops to get a head start (if desired) */ if (obj->loops < obj->head_start) { obj->end = obj->buf + (obj->remain + 2); obj->loops++; return rv; } /* Because of the way asterisk is designed we must always wait for a read event and read a frame before we can write because we are not permitted to read in 1 thread and write in another. We can at least use the size of the inbound frame to judge how big to make the outbound frame. We will try here to exhaust as much of the buffer as we can. When we have an odd amount left (less than we need), we have to save it for the next loop and break out of this callback. It would be better if you could configure the callback engine to fire with a paticular number of bytes based on samples. For example, you could set the engine to run the callback whenever it has 160 samples (320 bytes) or to be more efficient ask for 1600 samples (3200 bytes) at a time so you can distribute them evenly. */ /* We loop here until the buffer is too small or there is an error on the channel */ while(obj->remain > 0 && ast_waitfor(chan, -1) > -1) { struct ast_frame *f = NULL; int datalen = 0; if (!(f = ast_read(chan))) { break; } /* all we need to do is drop the inbound frames to keep things moving. We save the length for later */ datalen = f->datalen; ast_frfree(f); f = NULL; /* Adjust the frame length to match the inbound datalen and write the audio */ obj->fr.datalen = datalen; obj->fr.samples = obj->fr.datalen / 2; ast_write(chan, &obj->fr); /* remove the old data and recalculate the end of the buffer */ memmove(obj->buf, (obj->fr.data + obj->fr.datalen), sizeof(obj->buf) - obj->fr.datalen); obj->remain -= obj->fr.datalen; obj->end = obj->buf + (obj->remain + 2); /* like we said, gotta have enough data to perform a write */ if (obj->remain < datalen) { break; } } obj->loops++; } return rv; } static int cepstral_speak(struct ast_channel *chan, char *voice_name, char *text) { swift_engine *engine = NULL; swift_port *port = NULL; swift_voice *voice = NULL; swift_params *params = NULL; int event_mask = 0; int oldr = 0, oldw = 0; struct uobj obj = {0}; /* Set the channel to read/write Signed Linear and save the current format so * we can restore it when we are done */ oldr = chan->readformat; oldw = chan->writeformat; if (ast_set_write_format(chan, AST_FORMAT_SLINEAR) < 0) { ast_log(LOG_WARNING, "Unable to set '%s' to write linear mode\n", chan->name); return -1; } if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0) { ast_log(LOG_WARNING, "Unable to set '%s' to read linear mode\n", chan->name); ast_set_write_format(chan, oldw); return -1; } /* Open the Swift TTS Engine */ if ( SWIFT_FAILED(engine = swift_engine_open(NULL)) ) { ast_log(LOG_ERROR, "Failed to open Swift Engine."); goto all_done; } /* Setup the params 16 bit Signed Linear 8k to match the channel */ params = swift_params_new(NULL); swift_params_set_string(params, "audio/encoding", "pcm16"); swift_params_set_string(params, "audio/sampling-rate", "8000"); /* Open a Swift Port through which to make TTS calls */ if (SWIFT_FAILED(port = swift_port_open(engine, params))) { ast_log(LOG_ERROR, "Failed to open Swift Port."); goto all_done; } if (voice_name && SWIFT_FAILED(swift_port_set_voice_by_name(port, voice_name))) { ast_log(LOG_WARNING, "Invalid voice %s!\n", voice_name); voice_name = NULL; } if (!voice_name) { /* Find the first voice on the system */ if ((voice = swift_port_find_first_voice(port, NULL, NULL)) == NULL) { ast_log(LOG_ERROR, "Failed to find any voices!\n"); goto all_done; } /* Set the voice found by find_first_voice() as the port's current voice */ if ( SWIFT_FAILED(swift_port_set_voice(port, voice)) ) { ast_log(LOG_ERROR, "Failed to set voice.\n"); goto all_done; } } /* prepare our user data */ obj.chan = chan; obj.end = obj.buf; obj.head_start = 2; obj.fr.frametype = AST_FRAME_VOICE; obj.fr.subclass = AST_FORMAT_SLINEAR; obj.fr.data = obj.buf; /* Other event masks are defined in swift_defs.h */ event_mask = SWIFT_EVENT_AUDIO; swift_port_set_callback(port, &write_audio, event_mask, &obj); swift_port_speak_text(port, text, 0, NULL, NULL, NULL); all_done: /* Close the Swift Port and Engine */ if (NULL != port) swift_port_close(port); if (NULL != engine) swift_engine_close(engine); /* restore the channel's read/write format */ ast_set_write_format(chan, oldw); ast_set_read_format(chan, oldr); return 0; } static int cepstral_exec(struct ast_channel *chan, void *data) { int res = 0; char *text = NULL, *voice_name = NULL; struct localuser *u; LOCAL_USER_ADD(u); if (!data || ast_strlen_zero((char *)data)) { ast_log(LOG_WARNING, "Cepstral requires an argument voice_name or text\n"); return -1; } if (!(voice_name = ast_strdupa((char *)data))) { ast_log(LOG_ERROR, "Memory Error!\n"); LOCAL_USER_REMOVE(u); return -1; } if ((text = strchr(voice_name, '|'))) { *text++ = '\0'; } else { text = voice_name; voice_name = NULL; } /* generate the audio */ res = cepstral_speak(chan, voice_name, text); LOCAL_USER_REMOVE(u); return res; } int unload_module(void) { STANDARD_HANGUP_LOCALUSERS; return ast_unregister_application(app); } int load_module(void) { return ast_register_application(app, cepstral_exec, synopsis, descrip); } char *description(void) { return tdesc; } int usecount(void) { int res; STANDARD_USECOUNT(res); return res; } char INTEROPERABILITY_KEY[] = { \ 0x54, 0x68, 0x69, 0x73, 0x20, 0x70, 0x61, 0x72, 0x61, 0x67, 0x72, 0x61, 0x70, 0x68, 0x20, 0x69, \ 0x73, 0x20, 0x43, 0x6F, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x28, 0x43, 0x29, 0x20, \ 0x32, 0x30, 0x30, 0x30, 0x2C, 0x20, 0x4C, 0x69, 0x6E, 0x75, 0x78, 0x20, 0x53, 0x75, 0x70, 0x70, \ 0x6F, 0x72, 0x74, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2C, 0x20, 0x49, 0x6E, \ 0x63, 0x2E, 0x20, 0x20, 0x49, 0x6E, 0x20, 0x6F, 0x72, 0x64, 0x65, 0x72, 0x20, 0x66, 0x6F, 0x72, \ 0x20, 0x79, 0x6F, 0x75, 0x72, 0x20, 0x6D, 0x6F, 0x64, 0x75, 0x6C, 0x65, 0x20, 0x74, 0x6F, 0x20, \ 0x6C, 0x6F, 0x61, 0x64, 0x2C, 0x20, 0x69, 0x74, 0x20, 0x6D, 0x75, 0x73, 0x74, 0x20, 0x72, 0x65, \ 0x74, 0x75, 0x72, 0x6E, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x6B, 0x65, 0x79, 0x20, 0x76, 0x69, \ 0x61, 0x20, 0x61, 0x20, 0x66, 0x75, 0x6E, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x63, 0x61, 0x6C, \ 0x6C, 0x65, 0x64, 0x20, 0x22, 0x6B, 0x65, 0x79, 0x22, 0x2E, 0x20, 0x20, 0x41, 0x6E, 0x79, 0x20, \ 0x63, 0x6F, 0x64, 0x65, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x69, 0x6E, 0x63, 0x6C, 0x75, \ 0x64, 0x65, 0x73, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x70, 0x61, 0x72, 0x61, 0x67, 0x72, 0x61, \ 0x70, 0x68, 0x20, 0x6D, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x6C, 0x69, 0x63, 0x65, 0x6E, \ 0x73, 0x65, 0x64, 0x20, 0x75, 0x6E, 0x64, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x4E, \ 0x55, 0x20, 0x47, 0x65, 0x6E, 0x65, 0x72, 0x61, 0x6C, 0x20, 0x50, 0x75, 0x62, 0x6C, 0x69, 0x63, \ 0x20, 0x4C, 0x69, 0x63, 0x65, 0x6E, 0x73, 0x65, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, \ 0x20, 0x32, 0x20, 0x6F, 0x72, 0x20, 0x6C, 0x61, 0x74, 0x65, 0x72, 0x20, 0x28, 0x61, 0x74, 0x20, \ 0x79, 0x6F, 0x75, 0x72, 0x20, 0x6F, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x29, 0x2E, 0x20, 0x20, 0x20, \ 0x4C, 0x69, 0x6E, 0x75, 0x78, 0x20, 0x53, 0x75, 0x70, 0x70, 0x6F, 0x72, 0x74, 0x20, 0x53, 0x65, \ 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2C, 0x20, 0x49, 0x6E, 0x63, 0x2E, 0x20, 0x72, 0x65, 0x73, \ 0x65, 0x72, 0x76, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, \ 0x74, 0x6F, 0x20, 0x61, 0x6C, 0x6C, 0x6F, 0x77, 0x20, 0x6F, 0x74, 0x68, 0x65, 0x72, 0x20, 0x70, \ 0x61, 0x72, 0x74, 0x69, 0x65, 0x73, 0x20, 0x74, 0x6F, 0x20, 0x6C, 0x69, 0x63, 0x65, 0x6E, 0x73, \ 0x65, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x70, 0x61, 0x72, 0x61, 0x67, 0x72, 0x61, 0x70, 0x68, \ 0x20, 0x75, 0x6E, 0x64, 0x65, 0x72, 0x20, 0x6F, 0x74, 0x68, 0x65, 0x72, 0x20, 0x74, 0x65, 0x72, \ 0x6D, 0x73, 0x20, 0x61, 0x73, 0x20, 0x77, 0x65, 0x6C, 0x6C, 0x2E, 0x00 }; char *key() { return INTEROPERABILITY_KEY; }