使用场景:

需求很明确,就是需要做文字播报,即文字内容转音频(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 才能调用正常