Unity利用Sapi进行windows语音开发
软件中的语音技术主要包含两种:语音识别speech recognition和语音合成speech synthesis。一般地,开发者会因为技术实力和资金实力等各方面的问题无力完成专业的语音引擎,因此通常选择现有的较为专业的语音引擎来完成相关的开发,比如国内非常出名的科大讯飞,百度语音等等。当然国外的还有Google语音,微软有SAPI等等。
在VR开发过程中,由于运行在Windows环境下,那么自然而然,我们首选SAPI来进行语音开发。一是和Windows原生,二是离线不需要网络,三是不需要任何插件。另外就是SAPI发音,尤其是英文发音,还是相对来说质量不错的。(Win7以上自带)
使用SAPI,需要使用到的是System.Speech.dll文件。由于Unity需要将Dll文件放在Asset目录下,而这样的结果会发现sapi failed to initialize。原因怀疑为需要特定的上下文环境才能运行dll的api,以至于拷贝到Asset目录导致上下文环境缺失而无法运行。
但是如果做过这方面开发的知道,在C#的其他应用里面引用System.Speech.dll是完全没有问题的。那么是不是我们可以开发一个专门的第三方程序,然后unity进行调用呢?按照这个思路,我们开发了一个控制台程序Speech.exe,主要功能是根据输入文本进行语音合成。
代码较为简单
/*简单的SAPI语音合成控制台程序*/
using System.Speech.Synthesis;
using SpeechTest.Properties; namespace SpeechTest
{
class Program
{
static void Main(string[] args)
{
var speaker = new SpeechSynthesizer();
speaker.Speak(“test”);
}
}
}
OK,运行就可以听到机器发音Test了。
我们修改一下,改为从参数中读取,这样的话,我们可以在unity中利用Process运行Speech.exe,并传给Speech参数。
/*从参数读取需要发音的文本*/
using System.Speech.Synthesis;
using SpeechTest.Properties; namespace SpeechTest
{
class Program
{
static void Main(string[] args)
{
var speaker = new SpeechSynthesizer();
var res = args.Length == ? "请说" : args[];
speaker.Speak(res);
}
}
}
我们先使用CMD命令行,cd到Speech.exe所在的目录,然后输入Speech.exe test,如我们预想的那般,机器发音test。测试通过。
为了能够更改发音的配置,增加一些代码,从Setting中读取相关的配置数据,代码更改如下:
/*能够配置的控制台程序*/
using System.Speech.Synthesis;
using SpeechTest.Properties; namespace SpeechTest
{
class Program
{
static void Main(string[] args)
{
var speaker = new SpeechSynthesizer();
speaker.Volume = Settings.Default.SpeakVolume;
speaker.Rate = Settings.Default.SpeakRate;
var voice = Settings.Default.SpeakVoice;
if (!string.IsNullOrEmpty(voice))
speaker.SelectVoice(voice);
var res = args.Length == ? "请说" : args[];
speaker.Speak(res);
}
}
}
接下来我们在Unity中使用Process来开启这个Speech.exe,代码如下:
/*Unity中开启Speech.exe进程*/
using System.Diagnostics; public class Speecher: MonoBehaviour
{
public static void Speak(string str)
{
var proc = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "speech.exe",
Arguments = "\"" + str + "\"",
}
};
proc.Start();
} /***测试代码,可删除Start***/
protected void Start()
{
Speak("test");
}
/***测试代码,可删除End***/
}
将脚本挂在任何一个GO(GameObject)上,运行,黑框出现,同时听到发音,测试完成。
接下来我们隐藏这个黑框。代码修改如下:
/*Unity开启无框的Speech.exe进程*/
using System.Diagnostics; public class Speecher: MonoBehaviour
{
public static void Speak(string str)
{
var proc = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "speech.exe",
Arguments = "\"" + str + "\"",
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
}
};
proc.Start();
}
/***测试代码,可删除Start***/
protected void Start()
{
Speak("test");
}
/***测试代码,可删除End***/
}
其实到了这一步,主要的功能都完成了。但是细心的会发现,这样不断创建进程然后关闭进程的方式会不会太笨了。可不可以让Speech这个进程一直开启着,收到unity的信息时就发音呢?这就涉及到进程间通信了。
Windows的进程是相互独立的,各自有各自的分配空间。但是并不意味这不能相互通信。方法有很多,比如读写文件,发送消息(hook),Socket等等。其中Socket实现起来相对简单,尤其是我们已经拥有Socket封装库的情况下,只要少量代码就行了。
于是在Speech改成一个Socket服务器,代码如下:
/*Speech 服务端*/
using System;
using System.Linq;
using System.Speech.Synthesis;
using System.Text;
using Speech.Properties; namespace Speech
{
class Program
{
static void Main(string[] args)
{
var server = new NetServer();
server.StartServer(); while (true)
{
var res = Console.ReadLine();
if (res == "exit")
break;
}
}
} public class NetServer : SocketExtra.INetComponent
{
private readonly Speecher m_speecher; private readonly SocketExtra m_socket; public NetServer()
{
m_speecher = new Speecher();
m_socket = new SocketExtra(this);
} public void StartServer()
{
m_socket.Bind("127.0.0.1", Settings.Default.Port);
} public bool NetSendMsg(byte[] sendbuffer)
{
return true;
} public bool NetReciveMsg(byte[] recivebuffer)
{
var str = Encoding.Default.GetString(recivebuffer);
Console.WriteLine(str);
m_speecher.Speak(str);
return true;
} public bool Connected { get { return m_socket.Connected; } }
} public class Speecher
{
private readonly SpeechSynthesizer m_speaker; public Speecher()
{
m_speaker = new SpeechSynthesizer();
var installs = m_speaker.GetInstalledVoices(); m_speaker.Volume = Settings.Default.SpeakVolume;
m_speaker.Rate = Settings.Default.SpeakRate;
var voice = Settings.Default.SpeakVoice; var selected = false;
if (!string.IsNullOrEmpty(voice))
{
if (installs.Any(install => install.VoiceInfo.Name == voice))
{
m_speaker.SelectVoice(voice);
selected = true;
}
}
if (!selected)
{
foreach (var install in installs.Where(install => install.VoiceInfo.Culture.Name == "en-US"))
{
m_speaker.SelectVoice(install.VoiceInfo.Name);
break;
}
}
} public void Speak(string msg)
{
m_speaker.Speak(msg);
}
}
}
同时修改Unity代码,增加Socket相关代码:
/*Unity客户端代码*/
using System.Collections;
using System.Diagnostics;
using System.Text;
using UnityEngine; public class Speecher : MonoBehaviour, SocketExtra.INetComponent
{
private SocketExtra m_socket;
private Process m_process; protected void Awake()
{
Ins = this;
m_process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "speech.exe",
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden
},
};
m_process.Start();
} /***测试代码,可删除Start***/
protected IEnumerator Start()
{
yield return StartCoroutine(Connect());
Speak("test");
}
/***测试代码,可删除End***/ public IEnumerator Connect()
{
m_socket = new SocketExtra(this);
m_socket.Connect("127.0.0.1", );
while (!m_socket.Connected)
{
yield return ;
}
} protected void OnDestroy()
{
if (m_process != null && !m_process.HasExited)
m_process.Kill();
m_process = null;
} public static Speecher Ins; public static void Speak(string str)
{
#if UNITY_EDITOR||UNITY_STANDALONE_WIN
Ins.Speech(str);
#endif
} public void Speech(string str)
{
if (m_socket.Connected)
{
var bytes = Encoding.Default.GetBytes(str);
m_socket.SendMsg(bytes);
}
} public bool NetReciveMsg(byte[] recivebuffer)
{
return true;
} public bool NetSendMsg(byte[] sendbuffer)
{
return true;
}
}
OK,大功告成。工程见Github
https://github.com/CodeGize/UnitySapi/
转载请注明出处www.codegize.com
Unity利用Sapi进行windows语音开发的更多相关文章
- Kinect for Windows SDK开发学习相关资源
Kinect for Windows SDK(K4W)将Kinect的体感操作带到了平常的应用学习中,提供了一种不同于传统的鼠标,键盘及触摸的无接触的交互方式,在某种程度上实现了自然交互界面的理想,即 ...
- Spark:利用Eclipse构建Spark集成开发环境
前一篇文章“Apache Spark学习:将Spark部署到Hadoop 2.2.0上”介绍了如何使用Maven编译生成可直接运行在Hadoop 2.2.0上的Spark jar包,而本文则在此基础上 ...
- Kinect for Windows SDK开发入门(15):进阶指引 下
Kinect for Windows SDK开发入门(十五):进阶指引 下 上一篇文章介绍了Kinect for Windows SDK进阶开发需要了解的一些内容,包括影像处理Coding4Fun K ...
- Windows Phone开发人员必看资料
win phone开发必看资料,下载地址收藏啦!收藏后可有选择性的下载,希望大家喜欢! 完整附件下载:http://down.51cto.com/data/414417 附件预览: Windows E ...
- Windows Phone开发(46):与Socket有个约会
原文:Windows Phone开发(46):与Socket有个约会 不知道大家有没有"谈Socket色变"的经历?就像我一位朋友所说的,Socket这家伙啊,不得已而用之.哈,S ...
- Windows Phone开发(43):推送通知第一集——Toast推送
原文:Windows Phone开发(43):推送通知第一集--Toast推送 好像有好几天没更新了,抱歉抱歉,最近"光荣"地失业,先是忙于寻找新去处,唉,暂时没有下文.而后又有一 ...
- Windows Phone开发(15):资源
原文:Windows Phone开发(15):资源 活字印刷术是我国"四大发明"之一,毕昇在发明活字印刷术之后,他很快发现一个问题,随着要印刷资料的不断增加,要用到的汉字数目越来越 ...
- windows phone开发-Webbrowser使用技巧
原文:windows phone开发-Webbrowser使用技巧 5月份开发了脸萌WP版,其中需要使用web技术来绘制图像,于是就使用了原生webbrowser控件.在使用webbrowser co ...
- 利用cygwin创建windows下的crontab定时任务
要求 必备知识 熟悉基本编程环境搭建. 运行环境 windows 7(64位); Cygwin-1.7.35 下载地址 环境下载 什么是Cygwin Cygwin是一个在windows平台上运行的类U ...
随机推荐
- 【.Net Framework 体积大?】不安装.net framework 也能运行!?原理简介-2
接上一篇 [.Net Framework 体积大?]不安装.net framework 也能运行!?开篇叙述-1 昨天写了一个引子,还是有读者对这套“小把戏”感兴趣.那么不辜负大家的希望,争取博主不做 ...
- Canny边缘检测算法的实现
图像边缘信息主要集中在高频段,通常说图像锐化或检测边缘,实质就是高频滤波.我们知道微分运算是求信号的变化率,具有加强高频分量的作用.在空域运算中来说,对图像的锐化就是计算微分.由于数字图像的离散信号, ...
- JavaScript 事件总结
本文总结自<JavaScript高级程序设计>以及自己平时的经验,针对较新浏览器以及 DOM3 级事件标准(2016年8月),对少部分内容作了更正,增加了各种例子及解析. 如无特殊说明,本 ...
- Python爬虫爬取qq视频等动态网页全代码
环境:py3.4.4 32位 需要插件:selenium BeautifulSoup xlwt # coding = utf-8 from selenium import webdriverfrom ...
- jQuery ajax() 参数,回调函数,数据类型,发送数据到服务器,高级选项
$.ajax({ options:/*类型:Object; 可选.AJAX 请求设置.所有选项都是可选的.*/ async:/*类型:Boolean; 默认值: true.默认设置下,所有请求均为异 ...
- Kosaraju算法解析: 求解图的强连通分量
Kosaraju算法解析: 求解图的强连通分量 欢迎探讨,如有错误敬请指正 如需转载,请注明出处 http://www.cnblogs.com/nullzx/ 1. 定义 连通分量:在无向图中,即为连 ...
- 【开源.NET】轻量级内容管理框架Grissom.CMS(第三篇解析配置文件和数据以转换成 sql)
该篇是 Grissom.CMS 框架系列文章的第三篇, 主要介绍框架用到的核心库 EasyJsonToSql, 把标准的配置文件和数据结构解析成可执行的 sql. 该框架能实现自动化增删改查得益于 E ...
- 【新建项目&使用viewPager】实现一个Android电子书阅读APP
本章结尾处已放出应用DEMO,已经实现所有本文及后续文章所述全部功能,大家可以先下载下来玩玩看,欢迎在本文下方评论,小方很需要鼓励支持!!! 新建一个项目 呼-我们即将步入安卓开发之旅了,首先要新建一 ...
- jquery小测
1.在div元素中,包含了一个<span>元素,通过has选择器获取<div>元素中的<span>元素的语法是? 提示使用has() $("div:has ...
- Python拉勾爬虫——以深圳地区数据分析师为例
拉勾因其结构化的数据比较多因此过去常常被爬,所以在其多次改版之下变得难爬.不过只要清楚它的原理,依然比较好爬.其机制主要就是AJAX异步加载JSON数据,所以至少在搜索页面里翻页url不会变化,而且数 ...