freeswitch原生支持的tts功能中文一般是使用的ekho,但是那合成的效果简直惨不忍睹,于是我想自己做一个TTS服务器。
首先是找到比较满意的TTS引擎,科大讯飞的效果当然是没话说,但是价格不菲,其他商业的引擎中文合成也不是很流畅,偶然发现windows7自带的合成引擎还算过得去,windows10带的合成引擎就更好了(有兴趣的可以先测试一下,直接在windows控制面板中的语音设置里面有测试,但是测试的中英文混读很蛋疼)。
那么问题来了,怎么把这个引擎用到我的FS上边呢?
思路,debian上的freeswitch(后续简称de-FS)需要TTS的时候,通过桥接到另外一个windows版本的freeswitch(后续简称win-FS),在SIP消息X-header中附加上需要合成的文字,然后win-FS接收到文字后,通过lua脚本调用一个应用程序转换成语音文件,win-FS再播放出来,这样呼入原de-FS的主叫就听到了文本转换后的语音了。
折腾了一个星期,终于把这个TTS服务搭建好了,分享出来,顺便记一下,免得下次要用时找不到。
废话不多说了,直接上脚本(注:de-FS使用fusionpbx搭建的,数据源调用直接用了fusionpbx的方法,深究的可以搭一个fusionpbx查查里面的脚本)
1、de-FS端查数、外呼、转接脚本:
function createIconv(from,to,text) -- 本函数用于将utf8编码和gbk编码相互转换 -- apt-get install lua-iconv #需要先安装lua-iconv local iconv = require("iconv") local cd = iconv.new(to .. "//TRANSLIT", from) local ostr, err = cd:iconv(text) if err == iconv.ERROR_INCOMPLETE then return "ERROR: Incomplete input." elseif err == iconv.ERROR_INVALID then return "ERROR: Invalid input." elseif err == iconv.ERROR_NO_MEMORY then return "ERROR: Failed to allocate memory." elseif err == iconv.ERROR_UNKNOWN then return "ERROR: There was an unknown error." end return ostr end function inser_str_for(in_num) --本函数用在长串数字中插入空格 if in_num == nil then out_str = ‘error in_num‘ else -- print("正在转换:" .. in_num .. "\n") in_num_len = string.len(in_num) tmp_str = string.sub(in_num,1,1) for start_len=2,in_num_len,1 do sub_str = string.sub(in_num,start_len,start_len) tmp_str = tmp_str .. " " .. sub_str end out_str = tmp_str end return out_str end -- require "resources.functions.config"; local Database = require "resources.functions.database"; dbh = Database.new(‘switch‘); --调用freeswitch数据库 api = freeswitch.API(); session:answer(); if (session:ready() == true) then -- orderid = session:playAndGetDigits(8, 8, 3, 5000, ‘#‘, ‘first sound:#‘, ‘error_try sound‘, ‘\\d+|#‘); orderid = session:playAndGetDigits(12, 12, 3, 8000, ‘#‘, ‘misc/enter_carryid.wav‘, ‘misc/erro_carryid.wav‘, ‘\\d+|#‘) end if (orderid== nil) then error_code = 0 else error_code = 1 end if (error_code == 1) then session:execute( "playback", "misc/carry_querying.wav" ) local params = {carry_id_in = orderid} local sql = "SELECT * from carry_staut t where t.carry_id= :carry_id_in;" dbh:query(sql, params, function(row) carry_staut = row.info; --返回字段映射 end); if carry_staut==nil then session:streamFile("misc/erro_carryid.wav") else -- out_orderid=inser_str_for(orderid) -- 当使用微软音库时需转换 out_orderid=orderid -- 当使用"VM Hui"音库时无需转换 tts_text = "您好,单号:" .. out_orderid ..",7月11日,您从广州寄往深圳的快件当前状态:".. carry_staut ..",签收人李先生,感谢使用顺丰!" tts_text = createIconv("utf-8","gbk",tts_text) --调用函数将字符集转换为gbk session:setVariable("sip_h_X-Text",tts_text) tts_session = freeswitch.Session("sofia/gateway/80fd35d7-d767-4fb3-907f-72daaa7896ea/1098", session) if (tts_session:ready() == true) then freeswitch.bridge(session,tts_session) tts_session:hangup() end end else session:streamFile("misc/erro_carryid.wav") end session:sleep(500) local digits = session:playAndGetDigits(1, 1, 3, 8000, ‘‘, ‘misc/continue_carryid.wav‘, ‘ivr/ivr-that_was_an_invalid_entry‘, ‘[\\d{1}|*]‘) if (digits == "1") then session:execute("transfer","6200"); else if (digits == "2") then session:execute("transfer","6002"); else if (digits == "*") then session:execute("transfer","6000"); else session:execute("transfer","6002"); end end end
2、win-FS接收端脚本:
session:answer(); session:ready(); uuid = session:getVariable("uuid"); sounds_dir = session:getVariable("sounds_dir"); text1 = session:getVariable("sip_h_X-Text"); if text1 == nil then text1 = session:getVariable("caller_id_name"); end os.execute("G:\\fusionpbx\\TTS\\tts_app\\tts_app.exe" .. " -to" .. " \"" .. text1 .. "\" " .. " \"" .. sounds_dir .. "/" .. uuid .. ".wav" .. "\""); session:streamFile(sounds_dir .. "/" .. uuid .. ".wav"); session:sleep(1000); os.remove(sounds_dir .. "/" .. uuid .. ".wav"); session:hangup();
3、调用windows的tts程序代码(用C#写的一个控制台程序,可实现列出语音库,直接放音,和将语音保存到文件,直接调用windows接口):
using System; using System.Speech.Synthesis; using System.Configuration; using System.Speech.AudioFormat; namespace tts_app { class Program { static void Main(string[] args) { // Encoding utf8 = Encoding.GetEncoding("utf8"); // Encoding gb2312 = Encoding.GetEncoding("gb2312"); String tts_text; String outputfile; String Voice_out = ConfigurationManager.AppSettings["Voice"]; String Speed =ConfigurationManager.AppSettings["Speed"]; String Volume =ConfigurationManager.AppSettings["Volume"]; int Speed_true=0; int Volume_true=0; int Speed_out, Volume_out; int.TryParse(Speed, out Speed_true); if (Speed_true >= -10 && Speed_true <= 10) { Speed_out = int.Parse(Speed); } else { Console.WriteLine("语速配置错误,将以默认语速0输出"); Speed_out = 0; } int.TryParse(Volume, out Volume_true); if (Volume_true >= 0 && Volume_true <= 100) { Volume_out = int.Parse(Volume); } else { Console.WriteLine("音量配置错误,将以默认音量100输出"); Volume_out = 100; } if (args.Length == 1 && args[0] == "-l")//判断输入参数等于1,且等于-l时列出语音库信息 { SpeechSynthesizer speech = new SpeechSynthesizer(); Console.WriteLine("本机安装的语音包如下:"); foreach (InstalledVoice voice in speech.GetInstalledVoices()) { VoiceInfo info = voice.VoiceInfo; string AudioFormats = ""; foreach (SpeechAudioFormatInfo fmt in info.SupportedAudioFormats) { AudioFormats += String.Format("{0}\n", fmt.EncodingFormat.ToString()); } Console.WriteLine(" 音库名称: " + info.Name); Console.WriteLine(" 语种: " + info.Culture); Console.WriteLine(" 年龄: " + info.Age); Console.WriteLine(" 性别: " + info.Gender); Console.WriteLine(" 描述: " + info.Description); Console.WriteLine(" ID: " + info.Id); Console.WriteLine(" 是否启用: " + voice.Enabled); if (info.SupportedAudioFormats.Count != 0) { Console.WriteLine("语音格式:" + AudioFormats); } else { Console.WriteLine(" 不支持的语音格式:"); } string AdditionalInfo = ""; foreach (string key in info.AdditionalInfo.Keys) { AdditionalInfo += String.Format(" {0}: {1}\n", key, info.AdditionalInfo[key]); } Console.WriteLine(" 其他信息: " + AdditionalInfo); } speech.SetOutputToNull(); speech.Dispose(); Console.WriteLine("读取语音列表完成!"); } else if (args.Length == 2 && args[0] == "-t") { tts_text = args[1]; // 以下代码用于将linux传过来的UTF8转码成正常文字 //byte[] buffer1 = Encoding.Default.GetBytes(tts_text); //byte[] buffer2 = Encoding.Convert(Encoding.UTF8, Encoding.Default, buffer1, 0, buffer1.Length); //tts_text = Encoding.Default.GetString(buffer2, 0, buffer2.Length); SpeechSynthesizer speech = new SpeechSynthesizer(); String defult_voice; speech.GetInstalledVoices(); speech.SelectVoice(Voice_out); //语音类型 speech.Rate = Speed_out; //语速 speech.Volume = Volume_out; //音量 Console.WriteLine("正在合成: "); Console.WriteLine(tts_text); speech.Speak(tts_text); Console.WriteLine("合成完成"); speech.Dispose(); } else if (args.Length == 3 && args[0] == "-to") { tts_text = args[1]; outputfile = args[2]; // 以下代码用于将linux传过来的UTF8转码成正常文字 // byte[] buffer1 = Encoding.Default.GetBytes(tts_text); // byte[] buffer2 = Encoding.Convert(Encoding.UTF8, Encoding.Default, buffer1, 0, buffer1.Length); // tts_text = Encoding.Default.GetString(buffer2, 0, buffer2.Length); SpeechSynthesizer speech = new SpeechSynthesizer(); speech.SelectVoice(Voice_out); //语音类型 speech.Rate = Speed_out; //语速 speech.Volume = Volume_out; //音量 Console.Write("正在合成: "); Console.WriteLine(tts_text); Console.Write("输出文件名: "); Console.WriteLine(outputfile); speech.SetOutputToWaveFile(outputfile); speech.Speak(tts_text); Console.WriteLine("合成完成"); speech.SetOutputToNull(); speech.Dispose(); } else { Console.WriteLine("启动参数错误,请使用“-t 文本内容,或使用“-to 文本内容 输出文件名”,“-l”列出本机安装的语音库"); } } } }
4、TTS程序配置文件(用来调整语速,音量和选择音库):
<?xml version="1.0" encoding="utf-8"?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> </startup> <appSettings> <add key="Voice" value="Microsoft Lili"> </add> <!-- --> <add key="Speed" value="0"> </add> <!-- --> <add key="Volume" value="100"> </add> <!-- --> </appSettings> </configuration>
附件包含了两个语音合成效果和我编译好了的tts_app应用(需要安装.net运行库)
应用的使用方式:
1、列出语音库:tts_app.exe -l (如果放不出来,则需要查询语音库有哪些,然后将音库名称填入到配置文件的voice参数里面)
2、直接合成:
tts_app.exe -t “需要合成的文字”
3、保存到wav文件
tts_app.exe -to “需要合成的文字” "D:\存放路径\文件名.wav"
本文出自 “折腾笔记” 博客,转载请与作者联系!
原文地址:http://908405.blog.51cto.com/898405/1954060