FreeSWITCH折腾笔记4——自己做一个TTS服务器
更新:HHH   时间:2023-1-7


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://down.51cto.com/data/2366909
返回开发技术教程...