/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 2014 A.Zuccoli * * Andrea Zuccoli http://www.zuccoli.com * * SimpleTTS for Asterisk using AndroTTS app for Android */ /*! \file * * \brief Trivial application to playback a sound file * */ /*** MODULEINFO core ***/ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision: 328209 $") #define AST_18 1 //#if ASTERISK_VERSION_NUM==10810 #include "asterisk/file.h" #include "asterisk/pbx.h" #include "asterisk/module.h" #include "asterisk/app.h" #ifdef AST_18 #include "asterisk/version.h" #endif //#include #include #include #include #include #include #include #include #include #include #include #include /* This file provides config-file based 'say' functions, and implenents * some CLI commands. */ #include "asterisk/cli.h" #ifdef AST_18 #define ast_channel_name(chan) chan->name #define ast_channel_state(chan) chan->_state #define ast_channel_uniqueid(chan) chan->uniqueid #define ast_channel_language(chan) chan->language #define ast_channel_writeformat(chan) chan->writeformat #endif /*** DOCUMENTATION Speack a test using AndroTTS app fro Android. Comma separated list of options Speack a test using AndroTTS app fro Android. The playback command answer the channel if no options are specified. If the the connectiopn fail it will fail This application sets the following channel variable upon completion: The status of the playback attempt as a text string. See Also: Background (application) -- for playing sound files that are interruptible WaitExten (application) -- wait for digits from caller, optionally play music on hold ***/ static char *app = "AndroTTS"; #define ANDROTTS_CONFIG "androtts.conf" static int load_config(int reload); /*! Voicemail time zones */ struct androtts_server { AST_LIST_ENTRY(androtts_server) list; char name[80]; char host[80]; int port; char lang[20]; int TTSsocket; }; static AST_LIST_HEAD(androtts_servers, androtts_server) androtts_servers; unsigned long int Endian_DWord_Conversion(unsigned long int dword); static int ttscheckconnection(struct androtts_server *cur); static int ttsconnect(struct androtts_server *cur); static int ttsdisconnect(struct androtts_server *cur); static int PlayText(char *text,struct androtts_server *as,struct ast_channel *chan); static char *handle_androtts_list(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "androtts list"; e->usage = "Usage: androtts list\n" " androtts list\n" " Report list entryes of androtts servers\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc == 2) { struct androtts_server *cur; //AST_LIST_LOCK(&androtts_servers); ast_cli(a->fd, "\n" "=============================================================\n" "=== Configured AndroTTS Server ==============================\n" "=============================================================\n" "===\n"); AST_LIST_TRAVERSE(&androtts_servers, cur, list) { ast_cli(a->fd, "Name: %s Host: %s Port: %d Language: %s ", cur->name, cur->host,cur->port,cur->lang); ast_cli(a->fd, "Connected: %d \n", ttscheckconnection(cur)); } ast_cli(a->fd, "=============================================================\n" "\n"); //AST_LIST_UNLOCK(&androtts_servers); return CLI_SUCCESS; } else if (a->argc != e->args) return CLI_SHOWUSAGE; return CLI_SUCCESS; } static char *handle_androtts_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "androtts list"; e->usage = "Usage: androtts show [entry]\n" " androtts show entry\n" " Report single entry \n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc == 3) { struct androtts_server *cur; //AST_LIST_LOCK(&androtts_servers); ast_cli(a->fd, "\n" "=============================================================\n" "=== Configured AndroTTS Server %s==============================\n" "=============================================================\n" "===\n", a->argv[2]); AST_LIST_TRAVERSE(&androtts_servers, cur, list) { if (!strcmp(cur->name, a->argv[2])) ast_cli(a->fd, "Name: %s Host: %s Port: %d Language: %s\n", cur->name, cur->host,cur->port,cur->lang); } ast_cli(a->fd, "=============================================================\n" "\n"); //AST_LIST_UNLOCK(&androtts_servers); return CLI_SUCCESS; } else if (a->argc != e->args) return CLI_SHOWUSAGE; return CLI_SUCCESS; } /*! \brief Reload voicemail configuration from the CLI */ static char *handle_androtts_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "androtts reload"; e->usage = "Usage: androtts reload\n" " Reload androtts configuration\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 2) return CLI_SHOWUSAGE; ast_cli(a->fd, "Reloading androtts configuration...\n"); load_config(1); return CLI_SUCCESS; } static int load_config(int reload) { struct ast_config *cfg=NULL; struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; int res=1; char *cat = NULL; struct androtts_server *cur; ast_log(LOG_NOTICE, "Unloading Server.\n"); while ((cur = AST_LIST_REMOVE_HEAD(&androtts_servers,list))) { ast_log(LOG_NOTICE, "Unloading %s. Disconnection...\n",cur->name); ttsdisconnect(cur); ast_free(cur); } if ((cfg = ast_config_load(ANDROTTS_CONFIG, config_flags)) == CONFIG_STATUS_FILEUNCHANGED) { ast_clear_flag(&config_flags, CONFIG_FLAG_FILEUNCHANGED); if ((cfg = ast_config_load(ANDROTTS_CONFIG, config_flags)) == CONFIG_STATUS_FILEINVALID) { ast_log(LOG_ERROR, "Config file " ANDROTTS_CONFIG " is in an invalid format. Aborting.\n"); return 0; } } else if (cfg == CONFIG_STATUS_FILEINVALID) { ast_log(LOG_ERROR, "Config file " ANDROTTS_CONFIG " is in an invalid format. Aborting.\n"); return 0; } else { ast_clear_flag(&config_flags, CONFIG_FLAG_FILEUNCHANGED); } if (cfg==NULL) { ast_log(LOG_NOTICE, "Can't load " ANDROTTS_CONFIG "\n"); } else { //AST_RWLIST_WRLOCK(&androtts_servers); while ((cat = ast_category_browse(cfg, cat))) { const char *host; const char *port; const char *lang; struct androtts_server *as; if (!(as = ast_calloc(1, sizeof(struct androtts_server)))) { ast_log(LOG_ERROR, "Alloc error. Aborting.\n"); //AST_RWLIST_UNLOCK(&androtts_servers); return 0; } ast_log(LOG_NOTICE, "Loading [%s]\n",cat); ast_copy_string(as->name, cat, strlen(cat)+1); host=ast_variable_retrieve (cfg, cat, "host"); if (host) ast_copy_string(as->host, host, strlen(host)+1); else { ast_log(LOG_ERROR, "Error in androtts.conf\n"); continue ; } port=ast_variable_retrieve (cfg, cat, "port"); if (port) { int nport; sscanf(port,"%d",&nport); as->port=nport; } else { ast_log(LOG_ERROR, "Error in androtts.conf\n"); continue ; } lang=ast_variable_retrieve(cfg, cat, "lang"); if (lang) { ast_copy_string(as->lang, lang, strlen(lang)+1); ast_log(LOG_NOTICE, "[%s] host: %s port: %d lang: %s\n",cat,as->host,as->port,as->lang); } else { ast_log(LOG_ERROR, "Error in androtts.conf\n"); continue ; } ttsconnect(as); AST_LIST_INSERT_TAIL(&androtts_servers, as, list); } //AST_RWLIST_UNLOCK(&androtts_servers); } ast_config_destroy(cfg); return res; } static struct ast_cli_entry cli_androtts[] = { AST_CLI_DEFINE(handle_androtts_list, "List defined entryes"), AST_CLI_DEFINE(handle_androtts_show, "Show entry"), AST_CLI_DEFINE(handle_androtts_reload, "Reload androtts configuration"), }; unsigned long int Endian_DWord_Conversion(unsigned long int dword) { return ((dword>>24)&0x000000FF) | ((dword>>8)&0x0000FF00) | ((dword<<8)&0x00FF0000) | ((dword<<24)&0xFF000000); } static int playback_exec(struct ast_channel *chan, const char *data) { int res = 0; int option_skip=0; int option_noanswer = 0; char *argstr; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(text); AST_APP_ARG(lang); AST_APP_ARG(tcpaddr); AST_APP_ARG(tcpport); AST_APP_ARG(options); ); argstr = ast_strdupa((char *) data); AST_STANDARD_APP_ARGS(args, argstr); if (!args.tcpaddr) { struct androtts_server *cur; AST_LIST_TRAVERSE(&androtts_servers, cur, list) { if (!strcmp(cur->name, args.lang)) { if (ttscheckconnection(cur)==0) { if (!ttsconnect(cur)) { pbx_builtin_setvar_helper(chan, "PLAYBACKSTATUS","FAILED"); ast_log(LOG_ERROR, "Connection error\n"); return -1; } } PlayText(args.text,cur,chan); } } return -1; } else { if (ast_strlen_zero(args.text)) { pbx_builtin_setvar_helper(chan, "PLAYBACKSTATUS", "FAILED" ); ast_log(LOG_ERROR, "Playback requires an argument text\n"); return -1; } if (ast_strlen_zero(args.lang)) { pbx_builtin_setvar_helper(chan, "PLAYBACKSTATUS", "FAILED" ); ast_log(LOG_ERROR, "Playback requires an argument lang\n"); return -1; } if (ast_strlen_zero(args.tcpaddr)) { pbx_builtin_setvar_helper(chan, "PLAYBACKSTATUS", "FAILED" ); ast_log(LOG_ERROR, "Playback requires an argument host\n"); return -1; } if (ast_strlen_zero(args.tcpport)) { pbx_builtin_setvar_helper(chan, "PLAYBACKSTATUS","FAILED"); ast_log(LOG_ERROR, "Playback requires an argument port\n"); return -1; } if (args.options) { if (strcasestr(args.options, "skip")) option_skip = 1; if (strcasestr(args.options, "noanswer")) option_noanswer = 1; } if (ast_channel_state(chan) != AST_STATE_UP) { if (option_skip) { ast_log(LOG_ERROR, "Not answered\n"); goto done; } else if (!option_noanswer) { res = ast_answer(chan); } } if (!res) { struct androtts_server *as; if (!(as = ast_calloc(1, sizeof(struct androtts_server)))) { ast_log(LOG_ERROR, "Alloc error. Aborting.\n"); pbx_builtin_setvar_helper(chan, "PLAYBACKSTATUS", "FAILED" ); return 0; } ast_copy_string(as->name, ast_channel_name(chan), strlen(ast_channel_name(chan))+1); ast_copy_string(as->host, args.tcpaddr, strlen(args.tcpaddr)+1); as->port=atoi(args.tcpport); ast_copy_string(as->lang, args.lang, strlen(args.lang)+1); if (0==ttsconnect(as)) { pbx_builtin_setvar_helper(chan, "PLAYBACKSTATUS", "FAILED" ); return 0; } PlayText(args.text,as,chan); ttsdisconnect(as); } } return res; done: pbx_builtin_setvar_helper(chan, "PLAYBACKSTATUS", "FAILED" ); return res; } static int PlayText(char *text,struct androtts_server *as,struct ast_channel *chan) { char Buffer[1024]; int r=0; ast_log(LOG_NOTICE, "AndroTTS %s %s\n", text, as->lang); char cLen[8]; send(as->TTSsocket,text,strlen(text),0); send(as->TTSsocket,"\t",1,0); send(as->TTSsocket,as->lang,strlen(as->lang),0); send(as->TTSsocket,"\n",1,0); long EndFileSize=0; long readbytes=0; readbytes = recv(as->TTSsocket,cLen,8,0); //ast_log(LOG_WARNING, "Byute read %x %x %x %x %x %x %x %x\n",0xFF&cLen[0],0xFF&cLen[1],0xFF&cLen[2],0xFF&cLen[3],0xFF&cLen[4],0xFF&cLen[5],0xFF&cLen[6],0xFF&cLen[7]); memcpy(&EndFileSize,cLen,8); long FileSize=(FileSize=0xFF&cLen[7]) | ((cLen[6]<<8)&0x0000FF00)| ((cLen[5]<<16)&0x00FF0000)| ((cLen[4]<<24)&0xFF000000); //long FileSize=Endian_DWord_Conversion(EndFileSize); //long FileSize=EndFileSize; //ast_log(LOG_WARNING, "try read %ld %ld on %s - %ld\n",FileSize,EndFileSize,ast_channel_name(chan),readbytes ); //goto done; readbytes=0; char TmpWavFileName[256]; char TmpFileName[256]; char OutFileName[256]; sprintf(TmpWavFileName,"/tmp/androtts%s.wav", ast_channel_uniqueid(chan)); sprintf(TmpFileName,"/tmp/androtts%s", ast_channel_uniqueid(chan)); int f=open(TmpWavFileName,O_CREAT|O_TRUNC|O_RDWR,S_IWUSR|S_IRUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH ); if (f<0) { ast_log(LOG_ERROR, "androtts failed open file %s\n", ast_channel_name(chan) ); pbx_builtin_setvar_helper(chan, "PLAYBACKSTATUS", "FAILED" ); return 0; } while (readbytes!=FileSize) { int read=0; read = recv(as->TTSsocket,Buffer,1024,0); if (read==0) break; readbytes += read; if (read!=write(f, Buffer, read)) { ast_log(LOG_ERROR, "androtts failed write file %s\n", ast_channel_name(chan) ); pbx_builtin_setvar_helper(chan, "PLAYBACKSTATUS", "FAILED" ); return 0; } } close(f); // Convertiamo il file nel fomato corretto char cmd [1024]; const char* pFormatName=ast_getformatname (ast_channel_writeformat(chan)) ; ast_log(LOG_NOTICE, "androtts chan format %s\n", pFormatName ); //if (chan->writeformat==8) { if (!strcmp(pFormatName, "alaw")) { sprintf(OutFileName,"%s.alaw",TmpFileName); sprintf(cmd,"sox %s -A -r 8000 %s.au;mv %s.au %s",TmpWavFileName,TmpFileName,TmpFileName,OutFileName); ast_log(LOG_NOTICE, "Execute %s\n", cmd ); r=system(cmd); } else //if (chan->writeformat==2) { if (!strcmp(pFormatName, "gsm")) { sprintf(OutFileName,"%s.gsm",TmpFileName); sprintf(cmd,"sox %s -A -r 8000 %s",TmpWavFileName,OutFileName); ast_log(LOG_NOTICE, "Execute %s\n", cmd ); r=system(cmd); } else //if (chan->writeformat==0x100) { if (!strcmp(pFormatName, "g729")) { sprintf(OutFileName,"%s.g729",TmpFileName); sprintf(cmd,"sox %s -A -r 8000 %s.au;mv %s.au %s.alaw; asterisk -rx \"file convert %s.alaw %s\";rm %s.alaw",TmpWavFileName,TmpFileName,TmpFileName,TmpFileName,TmpFileName,OutFileName,TmpFileName); ast_log(LOG_NOTICE, "Execute %s\n", cmd ); r=system(cmd); } else { sprintf(OutFileName,"%s.slin",TmpFileName); sprintf(cmd,"sox %s -A -r 8000 %s.au;mv %s.au %s.alaw; asterisk -rx \"file convert %s.alaw %s\";rm %s.alaw",TmpWavFileName,TmpFileName,TmpFileName,TmpFileName,TmpFileName,OutFileName,TmpFileName); ast_log(LOG_NOTICE, "Execute %s\n", cmd ); r=system(cmd); } ast_log(LOG_NOTICE, "androtts system return %d\n", r ); unlink(TmpWavFileName); char *back = TmpFileName;//"TmpFileName"; char *front; int res=0; ast_stopstream(chan); while (!res && (front = strsep(&back, "&"))) { res = ast_streamfile(chan, front, ast_channel_language(chan)); if (!res) { res = ast_waitstream(chan, ""); ast_stopstream(chan); } else { ast_log(LOG_ERROR, "ast_streamfile failed on %s for %s\n", ast_channel_name(chan), (char *)back); res = 0; } } unlink(OutFileName); pbx_builtin_setvar_helper(chan, "PLAYBACKSTATUS", res ? "FAILED" : "SUCCESS"); return res; }; static int ttscheckconnection(struct androtts_server *cur) { if (cur->TTSsocket) { int error = 0; socklen_t len = sizeof (error); int retval = getsockopt (cur->TTSsocket, SOL_SOCKET, SO_ERROR, &error, &len ); return retval == 0; } else return 0; } static int ttsconnect(struct androtts_server *cur) { struct sockaddr_in client; int k; cur->TTSsocket = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP); if (cur->TTSsocket<0) { ast_log(LOG_ERROR, "androtts failed on init socket %s on %s\n",cur->name,cur->host); return 0; } memset(&client,0,sizeof(client)); client.sin_family = AF_INET; client.sin_addr.s_addr = inet_addr(cur->host); client.sin_port = htons(cur->port); k = connect(cur->TTSsocket,(struct sockaddr*)&client,sizeof(client)); if (k!=0) { ast_log(LOG_ERROR, "androtts failed to conect %s %s on %d\n", cur->name, cur->host,cur->port ); close(cur->TTSsocket); cur->TTSsocket=0; return 0; } return 1; } static int ttsdisconnect(struct androtts_server *cur) { if (cur->TTSsocket>=0) close(cur->TTSsocket); return 1; } static int reload(void) { return load_config(1); } static int unload_module(void) { int res; res = ast_unregister_application(app); ast_cli_unregister_multiple(cli_androtts, ARRAY_LEN(cli_androtts)); return res; } static int load_module(void) { ast_cli_register_multiple(cli_androtts, ARRAY_LEN(cli_androtts)); load_config(0); return ast_register_application_xml(app, playback_exec); } AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Speack text over channel using AndroTTS app fro Android download from http://www.zuccoli.com/AndroTTS.", .load = load_module, .unload = unload_module, .reload = reload, );