初探机器学习之使用讯飞TTS服务实现在线语音合成
最近在调研使用各个云平台提供的AI服务,有个语音合成的需求因此就使用了一下科大讯飞的TTS服务,也用.NET Core写了一个小示例,下面就是这个小示例及其相关背景知识的介绍。
一、什么是语音合成(TTS)
1.1 What is 语音合成?

将文字信息转化为声音信息,给应用配上“嘴巴”,这就是语音合成。
Note:语音合成和语音识别技术是实现人机语音通信,建立一个有听和讲能力的口语系统所必需的两项关键技术。使电脑具有类似于人一样的说话能力,是当今时代信息产业的重要竞争市场。和语音识别相比,语音合成的技术相对说来要成熟一些,并已开始向产业化方向成功迈进,大规模应用指日可待。
1.2 语音合成的应用场景
目前,语音合成技术在我们生活中具有广泛的应用,如机器人发声、有声读物制作、语音播报等等,这些应用场景都离不开语音合成。

Note:在语音导航应用、新闻类 APP 中,语音合成可以快速生成高质量的播报音频,实现在开车、走路等不方便阅读消息的情况下,音频消息的即时传达。
1.3 语音合成的基本原理
这里借用网易智能的一篇文章中的介绍如下:

简单来说语音合成分为文本分析、韵律分析和声学分析三个部分。通过文本分析提取出文本特征,在此基础上预测基频、时长、节奏等多种韵律特征,然后通过声学模型实现从前端参数到语音参数的映射,最后通过声码器合成语音。整个过程类似于“编码、信息匹配,解码的过程”。

对语音合成有兴趣的朋友,可以阅读以下这篇文章《吴恩达盛赞的Deep Voice详解教程,教你快速理解百度的语音合成原理》。
二、使用.NET Core调用讯飞API
2.1 科大讯飞TTS服务
讯飞提供了众多极具特色的发音人(音库)供您选择。其合成音在音色、自然度等方面的表现均接近甚至超过了人声。这种语音合成体验,达到了真正可商用的标准。在我对百度、阿里、腾讯及讯飞的TTS服务对比中,讯飞的TTS体验好一些,且发音人(音库)最为丰富,还有四川话(准确来说是成都话)音库,碉堡了。大家可以去讯飞TTS服务网页体验一下。
2.2 .NET Core调用示例
(1)首先得去讯飞开放平台注册一个账号,并申请一个勾选有“在线语音合成”的应用

这里主要是拿到AppID及ApiKey,并且可以通过发音人管理增加发音库,当然,高级版的音库都是要单独收费的。免费版本的每天有500次的免费API调用机会。
(2)参考官方API文档和C# DEMO
PS:由于讯飞官方提供的C# DEMO是一个基于.NET Framework的比较老的DEMO,在.NET Core下无法正常使用,我将其简单地改写成了.NET Core版本,并封装了一个XunFeiCloudTtsService类如下:(每个属性都有注释,不再赘述,只是对API调用需要的一些属性的简单封装)
public class XunFeiCloudTtsService
{
// Header Type : audio/mpeg
private const string AUDIO_MPEG_TYPE = "audio/mpeg";
// AppID, AppKey 从讯飞开放云平台获取
public string AppID { get; set; } = "your AppID";
public string ApiKey { get; set; } = "your ApiKey";
// 要进行合成的文字
public string TextToSpeech { get; set; } = "这是一段测试文字";
// 讯飞TTS服务API地址
public string ServiceUrl { get; set; } = "http://api.xfyun.cn/v1/service/v1/tts";
// aue = raw, 音频文件保存类型为 wav
// aue = lame, 音频文件保存类型为 mp3
public string AUE { get; set; } = "raw";
// 音频采样率,可选值:audio/L16;rate=8000,audio/L16;rate=16000
public string AUF { get; set; } = "audio/L16;rate=16000";
// 发音人,可选值:详见控制台-我的应用-在线语音合成服务管理-发音人授权管理
public string VoiceName { get; set; } = "xiaoyan";
// 引擎类型,可选值:aisound(普通效果),intp65(中文),intp65_en(英文),
// mtts(小语种,需配合小语种发音人使用),x(优化效果),
// 默认为intp65
public string EngineType { get; set; } = "intp65";
// 语速,可选值:[0-100],默认为50
public string Speed { get; set; } = "";
// 音量,可选值:[0-100],默认为50
public string Volume { get; set; } = "";
// 音高,可选值:[0-100],默认为50
public string Pitch { get; set; } = "";
// URL加密后的TextToSpeech
public string Bodys { get; set; }
// 要保存的文件夹路径
public string SavePath { get; set; } = "./Output/";
// 要保存的文件名
public string FileName { get; set; } = $"TTS-{ Guid.NewGuid().ToString() }"; static XunFeiCloudTtsService()
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
} public XunFeiCloudTtsService()
{ } public XunFeiCloudTtsService(string appID, string apiKey, string serviceUrl)
{
AppID = appID;
ApiKey = apiKey;
ServiceUrl = serviceUrl;
} public string GetTtsResult()
{
SetTextDataBodys(); string param = "{\"aue\":\"" + AUE + "\",\"auf\":\"" + AUF + "\",\"voice_name\":\"" + VoiceName + "\",\"engine_type\":\""
+ EngineType + "\",\"speed\":\"" + Speed + "\",\"volume\":\"" + Volume + "\",\"pitch\":\"" + Pitch + "\"}";
// 获取十位的时间戳
TimeSpan ts = DateTime.UtcNow - new DateTime(, , , , , , );
string curTime = Convert.ToInt64(ts.TotalSeconds).ToString();
// 对参数先utf-8然后用base64编码
byte[] paramData = Encoding.UTF8.GetBytes(param);
string paraBase64 = Convert.ToBase64String(paramData);
// 形成签名
string checkSum = Md5(ApiKey + curTime + paraBase64);
// 设置HttpHeaders
var ttsRequest = (HttpWebRequest)WebRequest.Create(ServiceUrl);
ttsRequest.Method = "POST";
ttsRequest.ContentType = "application/x-www-form-urlencoded";
ttsRequest.Headers.Add("X-Param", paraBase64);
ttsRequest.Headers.Add("X-CurTime", curTime);
ttsRequest.Headers.Add("X-Appid", AppID);
ttsRequest.Headers.Add("X-CheckSum", checkSum); using (Stream requestStream = ttsRequest.GetRequestStream())
{
using (StreamWriter streamWriter = new StreamWriter(requestStream, Encoding.GetEncoding("gb2312")))
{
streamWriter.Write(Bodys);
}
} string responseText = string.Empty;
HttpWebResponse ttsResponse = ttsRequest.GetResponse() as HttpWebResponse;
using (Stream responseStream = ttsResponse.GetResponseStream())
{
using (StreamReader streamReader = new StreamReader(responseStream, Encoding.GetEncoding("UTF-8")))
{
string headerType = ttsResponse.Headers["Content-Type"];
if (headerType.Equals(AUDIO_MPEG_TYPE))
{
responseText = GetSuccessResponseText(ttsResponse);
}
else
{
responseText = streamReader.ReadToEnd();
}
}
} return responseText;
} #region 私有辅助方法
/// <summary>
/// 获取请求成功的响应文本
/// </summary>
/// <param name="ttsResponse">HttpWebResponse</param>
/// <returns>响应Headers文本</returns>
private string GetSuccessResponseText(HttpWebResponse ttsResponse)
{
string responseText = string.Empty;
using (Stream stream = ttsResponse.GetResponseStream())
{
MemoryStream memoryStream = StreamToMemoryStream(stream);
if (!Directory.Exists(SavePath))
{
Directory.CreateDirectory(SavePath);
} string fileType = string.Empty;
switch (AUE.ToLower())
{
case "raw":
fileType = "wav";
break;
case "lame":
fileType = "lame";
break;
} File.WriteAllBytes($"{SavePath}{FileName}.{fileType}", streamTobyte(memoryStream));
responseText = ttsResponse.Headers.ToString();
} return responseText;
} /// <summary>
/// 对要合成语音的文字先用utf-8然后进行URL加密
/// </summary>
private void SetTextDataBodys()
{
byte[] textData = Encoding.UTF8.GetBytes(TextToSpeech);
TextToSpeech = HttpUtility.UrlEncode(textData);
Bodys = string.Format("text={0}", TextToSpeech);
} /// <summary>
/// 生成令牌 :X-CheckSum
/// 计算方法:MD5(apiKey + curTime + param),三个值拼接的字符串,进行MD5哈希计算(32位小写),其中apiKey由讯飞提供,调用方管理。
/// </summary>
/// <param name="token">apiKey + curTime + param</param>
/// <returns>X-CheckSum</returns>
private string Md5(string token)
{
MD5 md5 = new MD5CryptoServiceProvider();
byte[] bytes = Encoding.UTF8.GetBytes(token);
bytes = md5.ComputeHash(bytes);
md5.Clear(); StringBuilder sb = new StringBuilder();
for (int i = ; i < bytes.Length; i++)
{
sb.Append(Convert.ToString(bytes[i], ).PadLeft(, ''));
} return sb.ToString().PadLeft(, '');
} /// <summary>
/// 将流转换为缓存流
/// </summary>
/// <param name="instream">输入流</param>
/// <returns>输出流</returns>
private MemoryStream StreamToMemoryStream(Stream instream)
{
MemoryStream outstream = new MemoryStream();
const int bufferLen = ;
byte[] buffer = new byte[bufferLen];
int count = ;
while ((count = instream.Read(buffer, , bufferLen)) > )
{
outstream.Write(buffer, , count);
} return outstream;
} /// <summary>
/// 把缓存流转换成字节组
/// </summary>
/// <param name="memoryStream">缓存刘</param>
/// <returns>字节数组</returns>
private byte[] streamTobyte(MemoryStream memoryStream)
{
byte[] buffer = new byte[memoryStream.Length];
memoryStream.Seek(, SeekOrigin.Begin);
memoryStream.Read(buffer, , buffer.Length); return buffer;
}
#endregion
}
*.由于.NET Core下Encoding默认不支持GB2312,因此需要事先引入一个包:System.Text.Encoding.CodePages,然后在程序启动时注册一下,这里已经封装静态构造函数里面了。
NuGet>Install-Package System.Text.Encoding.CodePages
客户端调用:
public class Program
{
public static void Main(string[] args)
{
XunFeiCloudTtsService ttsService = new XunFeiCloudTtsService();
ttsService.AppID = "5c3806f12121";
ttsService.ApiKey = "a99fff9fc537d883a181231231a37bf56dc8f28";
ttsService.TextToSpeech = "大家好我是第一个语音合成!";
ttsService.SavePath = "./TTS-Result/";
ttsService.FileName = "Hello-TTS";
ttsService.Speed = ""; var result = ttsService.GetTtsResult();
Console.WriteLine(result); Console.ReadKey();
}
}
*.这里AppID和ApiKey请使用你自己的,我博文这里是乱写的。
调用结果:
(1)响应结果

*.如果提示illegal client ip,请到控制台中加入白名单列表:

(2)语音文件

So,播放一下?

三、小结
有了语音合成,我们可以在我们的业务系统或者App中有了更多的玩法,虽然我们不了解语音合成的具体实现原理。此文只是一个简单的使用示例,无更多的内容,希望对你有帮助,本文的示例代码可以点击这里。
参考资料
(1)科大讯飞开放云平台,《在线语音合成服务》
(2)科大讯飞TTS服务,API说明文档
(3)网易智能,《让机器说话更自然,语音合成还能干什么》
初探机器学习之使用讯飞TTS服务实现在线语音合成的更多相关文章
- 使用讯飞tts+ffmpeg自动生成视频
参考 FFmpeg 讯飞离线语音合成 起因 某日,看到一个营销号的视频说做视频日进斗金,大意是用软件识别文章小说,搭配一些图片转换成自己的视频.看完当时脑海里冒出一个念头,我也可以,于是有了这番尝试. ...
- Android Studio快速集成讯飞SDK实现文字朗读功能
今天,我们来学习一下怎么在Android Studio快速集成讯飞SDK实现文字朗读功能,先看一下效果图: 第一步 :了解TTS语音服务 TTS的全称为Text To Speech,即“从文本到语音” ...
- android用讯飞实现TTS语音合成 实现中文版
Android系统从1.6版本开始就支持TTS(Text-To-Speech),即语音合成.但是android系统默认的TTS引擎:Pic TTS不支持中文.所以我们得安装自己的TTS引擎和语音包. ...
- Android自带语音播报+讯飞语音播报封装(直接用)
一.Android自带的语音播报 1.查看是否支持中文,在测试的设备中打开‘设置’ -->找到 '语言和输入法'-->查看语音选项,是否支持中文,默认仅支持英文. 使用如下: public ...
- 关于讯飞语音SDK开发学习
前奏,浑浑噩噩已经工作一年多,这一年多收获还是挺多的.逛园子应该有两年多了,工作后基本上是天天都会来园子逛逛,园子 里还是有很多牛人写了一些不错的博客,帮我解决很多问题.但是一直没写过博客,归根到底一 ...
- 一百元的智能家居——Asp.Net Mvc Api+讯飞语音+Android+Arduino
大半夜的,先说些废话提提神 如今智能家居已经不再停留在概念阶段,高大上的科技公司都已经推出了自己的部分或全套的智能家居解决方案,不过就目前的现状而言,大多还停留在展厅阶段,还没有广泛的推广起来,有人说 ...
- Android讯飞语音云语音听写学习
讯飞语音云语音听写学习 这几天两个舍友都买了iPhone 6S,玩起了"Hey, Siri",我依旧对我的Nexus 5喊着"OK,Google" ...
- android讯飞语音开发常遇到的问题
场景:android项目中共使用了3个语音组件:在线语音听写.离线语音合成.离线语音识别 11208:遇到这个错误,授权应用失败,先检查装机量(3台测试权限),以及appid的申请时间(35天期限), ...
- iOS开发讯飞语音的集成
1.进入官网注册账号,登陆,注册,应用. 2,下载sdk 导入系统库. 3,关闭bitcode 4,初始化讯飞语音. NSString * initString = [[NSString alloc ...
随机推荐
- memcached command
http://lzone.de/cheat-sheet/memcached memcached Cheat Sheet Telnet Interface How To Connect Use &quo ...
- Leetcode_删除排序数组中的重复项
Leetcode 删除排序数组中的重复项 题目: 给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度. 不要使用 额外的数组空间,你必须在原地修改输入数 ...
- Java中浮点数的精度问题 【转】
当您在计算Money的时候,请看好了!!!要不损失了别后悔!!! 现象1: public static void main(String[] args) { System.out.println(0. ...
- 最近面了不少java开发,据此来说下我的感受:哪怕事先只准备1小时,成功概率也能大大提升
本人最近几年一直在做java后端方面的技术面试官,而在最近两周,又密集了面试了一些java初级和高级开发的候选人,在面试过程中,我自认为比较慎重,遇到问题回答不好的候选人,我总会再三从不同方面提问,只 ...
- Python并发编程之线程消息通信机制任务协调(四)
大家好,并发编程 进入第四篇. 本文目录 前言 Event事件 Condition Queue队列 总结 . 前言 前面我已经向大家介绍了,如何使用创建线程,启动线程.相信大家都会有这样一个想法,线程 ...
- Python_字符串之删除空白字符或某字符或字符串
''' strip().rstrip().lstrip()分别用来删除两端.右端.左端.连续的空白字符或字符集 ''' s='abc ' s2=s.strip() #删除空白字符 print(s2) ...
- Coding theano under remote ubuntu server from local Mac (在本地mac机器上,写、跑、调试、看-远程ubuntu上的theano代码)
本人是奇葩,最近鼓捣了一套在mac上coding远程ubuntu上的theano代码的东东,记之以期造福后人. Overview: 下图是我的编程环境和网络环境 我期望能在本地mac机器上对远程的ub ...
- 重写equals时,遵守的规定
0 正确的equals方法 public class MyClass { // 主要属性1 private int primaryAttr1; // 主要属性2 private int prima ...
- MongoDB的安装启动及做成windows服务
直接上干货. 官网地址:https://www.mongodb.com/download-center?jmp=nav#community 点击图中链接进入所有版本的下载列表 我下载的是3.6.5版本 ...
- Websql,应用程序缓存,WebWorkers,SSE,WebSocket
①什么是 Web Worker? 当在 HTML 页面中执行脚本时,页面的状态是不可响应的,直到脚本已完成. web worker 是运行在后台的 JavaScript,独立于其他脚本,不会影响页面的 ...