使用场景:
需求很明确,就是需要做文字播报,即文字内容转音频(Text To Speak),在业内的解决方案就是 语音合成 的功能
很多第三方都提供了 TTS 的服务,比如科大讯飞,百度,腾讯,阿里,都提供了现有的完整解决方案,不过这一次,我们不对接第三方
而是直接通过微软提供的语音库(System.Speech)来实现
差异对比:
第三方TTS:
拥有比较完善的SDK和API,并且提供了较多的语音库,丰富的声源,也提供了转音频文件和推流,和 ws 协议的方式,不过接口每次的文本内容转换有字数限制,其中科大讯飞支持最多,单次请求接口可支持 2000 中文,其他的平均在 500 中文内,另一个就是第三方需要付费,当然他们也都提供了免费的额度,具体找他们官方查看说明就好
微软:
通过 .Net 自带的 System.Speech 库来实现,简单易用,不过缺点就是,默认情况下音源只有女声,如果想支持其他的音源需要自己去下载音源库(但是也很少),以及生成的音频文件较大(十个字可能就百kb以上了),所以还需要自己额外的音频压缩来节省存储空间的压力和带宽压力。并且会踩不少坑…
具体实现:
TTS 实现分为两个部分,一个是控制台版本,一个是 Web API 版本,其中功能核心代码是一样的,不过在某些配置上会有差异
TTS 都用到 System.Speech
音频转码压缩使用 nuget 的 NAudio.Lame
1.控制台版:
之所以还做个控制台版本,是因为到时候也能方便其他应用来调用 exe 来生成音频(最终放到服务器上我是使用调用 exe 的方式实现,因为 web api 出了蜜汁bug,但是这个 bug 在我本地却不会发生)
using System.IO; using System.Speech.Synthesis; using NAudio.Wave; using NAudio.Lame; using System.Configuration; namespace Text2Speck { class Program { private static SpeechSynthesizer synth = null; private static SpeechSynthesizer GetSpeechSynthesizerInstance() { if (synth == null) { synth = new SpeechSynthesizer(); } return synth; } //在 app.config 文件配置音频文件的位置 private static string path = ConfigurationManager.AppSettings.Get("savepath"); static void Main(string[] args) { if (!Directory.Exists(path)) { Directory.CreateDirectory(path); 目录若是不存在则创建目录 } //文本内容通过外部传入 比如在cmd命令里面执行 Text2Speck.exe 我是要播报的内容 音频文件名称 string text = System.Web.HttpUtility.UrlDecode(args[0]); string filename = args[1]; SaveMp3(text, filename); } // 保存语音文件 public static string SaveMp3(string text, string filename) { synth = GetSpeechSynthesizerInstance(); synth.Rate = 1; //语音速率设置 synth.Volume = 100; //语音音量设置 string str = path + filename + ".wav"; synth.SetOutputToWaveFile(str); synth.Speak(text.Trim()); synth.SetOutputToNull(); WaveToMP3(str, path + filename + ".mp3"); return filename + ".mp3"; } //音频转码压缩,这里将码率设置到 64 public static void WaveToMP3(string waveFileName, string mp3FileName, int bitRate = 64) { using (var reader = new AudioFileReader(waveFileName)) using (var writer = new LameMP3FileWriter(mp3FileName, reader.WaveFormat, bitRate)) reader.CopyTo(writer); //把生成的原音频文件删除,留下体积小的 mp3 文件 File.Delete(waveFileName); } } }
App.Config
2.Web API 版
using NAudio.Lame; using NAudio.Wave; using System; using System.IO; using System.Speech.Synthesis; using System.Threading.Tasks; using System.Web.Mvc; namespace Text2SpeakAPI.Controllers { public class TTSController : Controller { private static SpeechSynthesizer synth = null; private static SpeechSynthesizer GetSpeechSynthesizerInstance() { if (synth == null) { synth = new SpeechSynthesizer(); } return synth; } public async Task Test2SpeakAPI(string text) { var guid = Guid.NewGuid(); string path = Server.MapPath($"/voice/{guid}"); await Test2SpeakHandle(text, path); return "/voice/" + guid + ".mp3"; } public async Task Test2SpeakHandle(string text, string path) { var task = await Task.Run(() => { if (!Directory.Exists(Path.GetDirectoryName(path + ".wav"))) { Directory.CreateDirectory(Path.GetDirectoryName(path + ".wav")); } synth = GetSpeechSynthesizerInstance(); synth.Rate = 0; synth.Volume = 100; synth.SetOutputToWaveFile(path + ".wav"); synth.Speak(text.Trim()); synth.SetOutputToNull(); WaveToMP3(path + ".wav", path + ".mp3"); return path; }); return task; } public void WaveToMP3(string waveFileName, string mp3FileName, int bitRate = 64) { using (var reader = new AudioFileReader(waveFileName)) using (var writer = new LameMP3FileWriter(mp3FileName, reader.WaveFormat, bitRate)) reader.CopyTo(writer); System.IO.File.Delete(waveFileName); } } }
遇到的问题:
其中,控制台版本上,无论在本地还是服务器环境,都能正常使用
但是在 Web API 版本中,会遇到两个问题
1.在音频转码压缩的模块中,提示找不到 libmp3lame.32.dll / libmp3lame.64.dll
解决方案:在 web.config 文件中添加
< system.web> < hostingEnvironment shadowCopyBinAssemblies="false" /> < /system.web>
参考:https://stackoverflow.com/questions/20088743/mvc4-app-unable-to-load-dll-libmp3lame-32-dll
2.在 Windows Server 2012 服务器环境中,生成音频的时候,提示 未将对象引用到对象实例 错误,本地环境正常
解决方案:暂时找不到原因,只能替代使用 exe 版本来生成,也就是在 web api 中调用控制台的版本来实现
其中调用 .Net 调用 exe 程序方法
public string StartProcess(string exePath, params string[] args) { try { string s = ""; foreach (string arg in args) { s = s + arg + " "; } s = s.Trim(); Process process = new Process();//创建进程对象 ProcessStartInfo startInfo = new ProcessStartInfo(exePath, s); // 括号里是(程序路径,参数) process.StartInfo = startInfo; process.Start(); return "true"; } catch (Exception e) { return e.Message; } }
并且,在服务器环境中,需要将项目对应的 应用程序池 的 标识 改为 LocalSystem 才能调用正常