用Unity制作一个Siri问答软件
发表于2017-10-18
大家好,我是云图。 之前项目有个需求要做一个简单的siri类似的问答系统,经过一番周折,终于还是实现出来了,软件不单可以在pc上面运行,也可以打包到安卓,ios平台,真正实现一键跨平台打包。
现在我们先说一些实现的思路,所谓问答系统,第一步就是用户首先说一句话,然后我们的软件把听到的话转换成文字。那现在问题有了,
第二步我们就需要一个地方,把我们的问题文字转化成我们软件回答的答案内容。
第三步就是把要回答的文字读出来。
思路有了,开始动手,首先,我们把用户输入的语音,要转成文字,经过我几个挑选,我发现百度语音和讯飞语音都不错,只不过讯飞语音的接入麻烦了点,
所以就采用了百度的语音识别
我相信到这一步很多做类似项目的也有不少人做到了,可就是这里没办法做下去了,为什么呢,
现在我们先说一些实现的思路,所谓问答系统,第一步就是用户首先说一句话,然后我们的软件把听到的话转换成文字。那现在问题有了,
第二步我们就需要一个地方,把我们的问题文字转化成我们软件回答的答案内容。
第三步就是把要回答的文字读出来。
思路有了,开始动手,首先,我们把用户输入的语音,要转成文字,经过我几个挑选,我发现百度语音和讯飞语音都不错,只不过讯飞语音的接入麻烦了点,
所以就采用了百度的语音识别
private string token; //access_token private string cuid = "随便写的d"; //用户标识 private string format = "pcm"; //语音格式 private int rate = 8000; //采样率 private int channel = 1; //声道数 private string speech; //语音数据,进行base64编码 private int len; //原始语音长度 private string lan = "zh"; //语种 private string grant_Type = "client_credentials"; private string client_ID = "9152186"; //百度appkey private string client_Secret = "14c703ce0f900eae40e95b2cdd564472"; //百度Secret Key private string baiduAPI = "http://vop.baidu.com/server_api"; private string getTokenAPIPath = "https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=ekGb1G5XHY4BIVSA8nLzX5cA&client_secret=14c703ce0f900eae40e95b2cdd564472"; private Byte[] clipByte; /// <summary> /// 转换出来的TEXT /// </summary> public static string audioToString; private AudioSource aud; private int audioLength;//录音的长度 public void StartRecord() { Debug.Log("开始说话"); if (Microphone.devices.Length == 0) return; Microphone.End(null); aud.clip = Microphone.Start(null, false, 10, rate); } public void EndRecord() { Debug.Log("结束说话"); int lastPos = Microphone.GetPosition(null); if (Microphone.IsRecording(null)) audioLength = lastPos / rate;//录音时长 else audioLength = 10; Microphone.End(null); clipByte = GetClipData(); len = clipByte.Length; speech = Convert.ToBase64String(clipByte); StartCoroutine(GetToken(getTokenAPIPath)); StartCoroutine(GetAudioString(baiduAPI)); } /// <summary> /// 把录音转换为Byte[] /// </summary> /// <returns></returns> public Byte[] GetClipData() { if (aud.clip == null) { Debug.LogError("录音数据为空"); return null; } float[] samples = new float[aud.clip.samples]; aud.clip.GetData(samples, 0); Byte[] outData = new byte[samples.Length * 2]; int rescaleFactor = 32767; //to convert float to Int16 for (int i = 0; i < samples.Length; i ) { short temshort = (short)(samples[i] * rescaleFactor); Byte[] temdata = System.BitConverter.GetBytes(temshort); outData[i * 2] = temdata[0]; outData[i * 2 1] = temdata[1]; } if (outData == null || outData.Length <= 0) { Debug.LogError("录音数据为空"); return null; } return outData; } /// <summary> /// 获取百度用户令牌 /// </summary> /// <param name="url">获取的url</param> /// <returns></returns> private IEnumerator GetToken(string url) { WWW getTW = new WWW(url); yield return getTW; if (getTW.isDone) { if (getTW.error == null) { token = getTW.text; StartCoroutine(GetAudioString(baiduAPI)); } else { Debug.LogError("获取令牌出错" getTW.error); } } else { Debug.LogError("下载出错" getTW.error); } } /// <summary> /// 把语音转换为文字 /// </summary> /// <param name="url"></param> /// <returns></returns> private IEnumerator GetAudioString(string url) { JsonWriter jw = new JsonWriter(); jw.WriteObjectStart(); jw.WritePropertyName("format"); jw.Write(format); jw.WritePropertyName("rate"); jw.Write(rate); jw.WritePropertyName("channel"); jw.Write(channel); jw.WritePropertyName("token"); jw.Write(token); jw.WritePropertyName("cuid"); jw.Write(cuid); jw.WritePropertyName("len"); jw.Write(len); jw.WritePropertyName("speech"); jw.Write(speech); jw.WriteObjectEnd(); WWW getASW = new WWW(url, Encoding.Default.GetBytes(jw.ToString())); yield return getASW; if (getASW.isDone) { if (getASW.error == null) { JsonData getASWJson = JsonMapper.ToObject(getASW.text); if (getASWJson["err_msg"].ToString() == "success.") { audioToString = getASWJson["result"][0].ToString(); if (audioToString.Substring(audioToString.Length - 1) == ",") audioToString = audioToString.Substring(0, audioToString.Length - 1); Debug.Log("说话的问题是:" audioToString); GetAnswer(audioToString); } else { Debug.LogWarning("没有成功:" getASWJson["err_msg"].ToString()); } } else { Debug.LogError(getASW.error); } } }
首先你要去百度语音官网申请一个开发者权限,这些细节在这里就不讨论了,很简单。重要的就是两个参数 client_ID和client_Secret 的内容,填写到获取token的网页中发上去,获取到token后百度就知道我们是哪个用户哪个软件在调用语音识别了。这里在ios运行的时候有时候会显示获取不到token,原因是苹果不给我们这样发信息获取内容,说不安全,需要用其他办法。其他办法有很多,最笨的方法就是提前获取到token,然后写死在程序,坏处就是一个月会变一次,比较麻烦。好了,现在我们的语音已经传上百度然后转成文字下来, 下一步我采用的是图灵的文字问答系统。图灵这个做的也是非常好用的,直接把我们的问题文字写到url里面打开链接,就可以返回答案了。
private string url = "http://www.tuling123.com/openapi/api?key=d91b25b8866fef13f82cd28c0d523c8a&info="; private string QuestionUrl= "http://www.tuling123.com/openapi/api?key=d91b25b8866fef13f82cd28c0d523c8a&info="; public string msg = ""; /// <summary> /// 获取图灵返回的答案 /// </summary> /// <param name="msg">提问的问题</param> public void GetAnswer(string msg) { StartCoroutine(GetTuLingtoken(url msg)); } private string TuLingtoken = ""; /// <summary> /// 图灵的问答系统 /// </summary> /// <param name="Question">要问的问题</param> /// <returns></returns> private IEnumerator GetTuLingtoken(string url) { WWW getTW = new WWW(url); yield return getTW; if (getTW.isDone) { if (getTW.error == null) { TuLingtoken = getTW.text; TuLingtoken = JsonMapper.ToObject(getTW.text)["text"].ToString(); PlayAudio(TuLingtoken); } else { Debug.LogError(getTW.error); } } }
到这里我们就获取到我们要回答用户答案的音频了,回答的答案特别骚气。
因为要用unity把网络的音频下载下来播放是非常麻烦的事情,我至今都没找到办法,就算用c#的办法解决了,可是安卓和ios呢,根本调用不了,再者就是就算你下载下来了,unity好像没有提供外部播放音频的功能,我不知道是我能力不足还是unity的问题,感觉很基本的事情居然没有解决方案。自我怀疑中。。。。
你可能要问了,那么有没有一种办法能解决这个多平台播放音频的问题呢? 当然有了,我用了FMOD这个插件,可以直接播放网页的在线音频,
用了一个插件集成进来,最后一步就把网页音频地址传进去,就解决了播放的问题,而且是跨平台的,ios ,安卓,pc都能用。
//要播放的语音文字 public string AudioMsg = ""; private string urlForward = @"http://tsn.baidu.com/text2audio?tex="; public string AudioUrl = ""; public AudioStreamDemo asDemo; public void PlayAudio(string content) { Debug.Log(content); AudioUrl = @"http://tsn.baidu.com/text2audio?tex=" content "&lan=zh&cuid=随便写的&ctp=1&tok=" token;// string[] arrPunc = { ",", "。", "”", ";", "“", " " }; for (int i = 0; i < arrPunc.Length; i) { //用空白字符来替换指定的标点符号,也就相当于删除掉了标点符号 AudioUrl = AudioUrl.Replace(arrPunc[i], " "); } asDemo.SetUrlContext(AudioUrl); asDemo.OnPlay(); }
就这么点代码,当初找不到方案的时候头疼了2个星期。 好在在不放弃的坚持下还是搞定了,中间走了不少弯路,好在有 MemoryC 大神的帮助下,
也一个个解决了,为 MemoryC大神打call
我把这三步骤已经缩减到一个类里面,方便大家查阅,提一下目前这个功能的一点短处,就是说完话以后要尽快点结束,让语音不浪费那么多空间,
增快识别速度,最快的时候可以在2秒内读出答案,网速好的话比较好。我测试在安卓读的最快,然后是pc,最慢是ios,测试的机器是iphone5,不知道和这个有没有关系。再说点这个东西的拓展,比如一些项目有一些运行了windows系统的机器人,展厅的一些全息 投影之类的项目,都可以接入这个功能,让项目更有可玩性。基本就是这些,如果有什么问题可以加群52864182 和我一起玩耍。
源码这里不能提供下载,需要的朋友就加我的群下载吧