软件中的语音技术主要包含两种:语音识别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语音开发的更多相关文章

  1. Kinect for Windows SDK开发学习相关资源

    Kinect for Windows SDK(K4W)将Kinect的体感操作带到了平常的应用学习中,提供了一种不同于传统的鼠标,键盘及触摸的无接触的交互方式,在某种程度上实现了自然交互界面的理想,即 ...

  2. Spark:利用Eclipse构建Spark集成开发环境

    前一篇文章“Apache Spark学习:将Spark部署到Hadoop 2.2.0上”介绍了如何使用Maven编译生成可直接运行在Hadoop 2.2.0上的Spark jar包,而本文则在此基础上 ...

  3. Kinect for Windows SDK开发入门(15):进阶指引 下

    Kinect for Windows SDK开发入门(十五):进阶指引 下 上一篇文章介绍了Kinect for Windows SDK进阶开发需要了解的一些内容,包括影像处理Coding4Fun K ...

  4. Windows Phone开发人员必看资料

    win phone开发必看资料,下载地址收藏啦!收藏后可有选择性的下载,希望大家喜欢! 完整附件下载:http://down.51cto.com/data/414417 附件预览: Windows E ...

  5. Windows Phone开发(46):与Socket有个约会

    原文:Windows Phone开发(46):与Socket有个约会 不知道大家有没有"谈Socket色变"的经历?就像我一位朋友所说的,Socket这家伙啊,不得已而用之.哈,S ...

  6. Windows Phone开发(43):推送通知第一集——Toast推送

    原文:Windows Phone开发(43):推送通知第一集--Toast推送 好像有好几天没更新了,抱歉抱歉,最近"光荣"地失业,先是忙于寻找新去处,唉,暂时没有下文.而后又有一 ...

  7. Windows Phone开发(15):资源

    原文:Windows Phone开发(15):资源 活字印刷术是我国"四大发明"之一,毕昇在发明活字印刷术之后,他很快发现一个问题,随着要印刷资料的不断增加,要用到的汉字数目越来越 ...

  8. windows phone开发-Webbrowser使用技巧

    原文:windows phone开发-Webbrowser使用技巧 5月份开发了脸萌WP版,其中需要使用web技术来绘制图像,于是就使用了原生webbrowser控件.在使用webbrowser co ...

  9. 利用cygwin创建windows下的crontab定时任务

    要求 必备知识 熟悉基本编程环境搭建. 运行环境 windows 7(64位); Cygwin-1.7.35 下载地址 环境下载 什么是Cygwin Cygwin是一个在windows平台上运行的类U ...

随机推荐

  1. 使用node-inspector调试nodejs程序<nodejs>

    1.npm install -g node-inspector  // -g 导入安装路径到环境变量 一般是c盘下AppData目录下 2.node-inspector & //启动node- ...

  2. 矢量切片(Vector tile)番外一:Proj4js

    说明:番外篇是对正篇矢量切片(Vector tile)中提到的一些值得继续延伸的关注点继续进行探索和学习,所涉及的内容以解决实际问题为主要导向. 一.新的需求? 在完成了矢量切片的工作后,新的需求出现 ...

  3. 提升iOS审核通过率之“IPv6兼容测试”

    作者:jingle 腾讯系统测试工程师 商业转载请联系腾讯WeTest授权,非商业转载请注明出处. 原文链接:http://wetest.qq.com/lab/view/285.html 一.背景 在 ...

  4. Raspberry树莓派学习笔记1—基本介绍

    树莓派的简单介绍 一个名片大小的迷你个人电脑主机,还有wifi/蓝牙... 运行完整的Linux操作系统(注意关键字:完整,不是精简过的嵌入式Linux) 开源的硬件平台.与普通主机不同的是,它带有简 ...

  5. 从C#到TypeScript - 装饰器

    总目录 从C#到TypeScript - 类型 从C#到TypeScript - 高级类型 从C#到TypeScript - 变量 从C#到TypeScript - 接口 从C#到TypeScript ...

  6. block循环饮用解决

    在block中使用self会引起循环引用导致无法释放. 解决: __weak typeof(self) weakSelf = self; 例如: NSLog(@"init--> val ...

  7. 49-Group Anagrams-(Medium) 题解

    1.题目 Given an array of strings, group anagrams together. For example, given: ["eat", " ...

  8. iOS 访问URL转码

    访问URL时,需要对字符串进行转码: urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; ...

  9. 从CMOS到触发器(二)

    PS:可以转载,转载请标明出处:http://www.cnblogs.com/IClearner/ 前面说了CMOS器件,现在就接着来聊聊锁存器跟触发器吧,下面是这次博文要介绍的主要内容: ·双稳态器 ...

  10. 用DotRas来连接VPN网络

    最近要用程序来不断的连接VPN(为什么要这样就不讨论了),开始用的是如下代码: public static bool ADSL() { bool flag = true; do { Console.W ...