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 ...
随机推荐
- Omi教程-组件通讯
组件通讯 Omi框架组建间的通讯非常遍历灵活,因为有许多可选方案进行通讯: 通过在组件上声明 data-* 传递给子节点 通过在组件上声明 data 传递给子节点 父容器设置 childrenData ...
- 关于hibernate注解的简单应用
@Override 用途:重写父类的同名方法 单元测试注解 @Test 用途:用于测试 @Before 用途:单测方法走之前执行 @After 用途:单测方法走之后执行 注解的目标:替换小配置.替换h ...
- 覆写hashCode equal方法
1.为什么要重写hashCode方法? 当自己要新建一个class,并要把这个类放到HashMap的时候,需要覆写这两个办法.如果不覆写,放入两个新的对象,可能会是不相等的. 在java的集合中,判断 ...
- ios跳转到系统设置
在项目中,我们经常会碰到使用位置的需求.当用户设置app不允许使用位置的时候,最好的用户体验就是直接调转到系统的位置设置界面,进行设置. 第一 跳转到自己项目(在需要调转的按钮动作中添加如下的代码,就 ...
- 1305 Pairwise Sum and Divide
1305 Pairwise Sum and Divide 题目来源: HackerRank 基准时间限制:1 秒 空间限制:131072 KB 分值: 5 难度:1级算法题 有这样一段程序,fun会对 ...
- Javascript学习九
计时器setInterval() 在执行时,从载入页面后每隔指定的时间执行代码. 语法: setInterval(代码,交互时间); 参数说明: 1. 代码:要调用的函数或要执行的代码串. 2. 交互 ...
- UIView和layer的关系
UIView和layer的关系 UIView是layer的代理 View的根layer ---也就是说view本身自带一个layer, 这个layer 我们叫它根layer 所有视图显示是因为继承UI ...
- 使用 Bitbucket Pipelines 持续交付托管项目
简介 Bitbucket Pipelines 是Atlassian公司为Bitbucket Cloud产品添加的一个新功能, 它为托管在Bitbucket上的项目提供了一个良好的持续集成/交付的服务. ...
- 使用jQuery快速高效制作网页特效-----------------------------之jQuery事件与动画
1.基础事件 分为三个事件 1.1 window事件 所谓window事件,就是当用户执行某些会影响浏览器的操作时,而触发的事件. 1.2 鼠标事件 鼠标事件顾名思义就是当用户在文档上移动或单击鼠标时 ...
- LeetCode 笔记总结
前言 之前把一些LeetCode题目的思路写在了本子上,现在把这些全都放到博客上,以后翻阅比较方便. 题目 99.Recover Binary Search Tree 题意 Two elements ...