Unity对接科大讯飞实时语音转写WebAPI(Windows平台)

科大讯飞官方文档:实时语音转写 API 文档 | 讯飞开放平台文档中心 (xfyun.cn)

参考文章:unity通过WebAPI连接Websocket实现讯飞语音识别与合成。_unity websocket audio-CSDN博客

        要实现语音转文字。首先我们需要从麦克风获取到语音数据,这里用到了Microphone类,Unity自带;其次,需要将语音数据发送给讯飞,这里用到的是WebSocketSharp.WebSocket,用习惯了。然后就是按照文档一步步踩坑了。

        直接贴代码了。代码主要实现握手阶段参数签名,实时通信阶段的数据传输以及结果解析。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using WebSocketSharp;
using System.Text;
using System.Security.Cryptography;
using LitJson;
using Newtonsoft.Json;public class SpeechHelper : MonoBehaviour
{public event Action<string> 语音识别完成事件;   //语音识别回调事件public AudioClip RecordedClip;private string micphoneName = string.Empty;WebSocket speechWebSocket;private System.Action<string> resultCallback;public void InitSpeechHelper(System.Action<string> textCallback){resultCallback = textCallback;}public void StartSpeech(){if (speechWebSocket != null && speechWebSocket.ReadyState == WebSocketState.Open){Debug.LogWarning("开始语音识别失败!,等待上次识别连接结束");return;}if(Microphone.devices.Length <= 0){Debug.LogWarning("找不到麦克风");return;}messageQueue.Clear();micphoneName = Microphone.devices[0];Debug.Log("micphoneName:" + micphoneName);try{RecordedClip = Microphone.Start(micphoneName, false, 60, 16000);ConnectSpeechWebSocket();}catch(Exception ex){Debug.LogError(ex.Message);}}public void StopSpeech(){Microphone.End(micphoneName);Debug.Log("识别结束,停止录音");}void ConnectSpeechWebSocket(){try{speechWebSocket = new WebSocket(GetWebSocketUrl());}catch (Exception ex){UnityEngine.Debug.LogError(ex.Message);return;}speechWebSocket.OnOpen += (sender, e) =>{Debug.Log("OnOpen");speechWebSocket.OnClose += OnWebSocketClose;};speechWebSocket.OnMessage += OnInitMessage;speechWebSocket.OnError += OnError;speechWebSocket.ConnectAsync();StartCoroutine(SendVoiceData());}void OnWebSocketClose(object sender, CloseEventArgs e){Debug.Log("OnWebSocketClose");}private static Queue<string> messageQueue = new Queue<string>();void OnInitMessage(object sender, MessageEventArgs e){UnityEngine.Debug.Log("qqqqqqqqqqqqqWebSocket数据返回:" + e.Data);messageQueue.Enqueue(e.Data);}private void MainThreadOnMessage(string message){try{XFResponse response = JsonConvert.DeserializeObject<XFResponse>(message);if (0 != response.code){return;}if (response.action.Equals("result")){var result = ParseXunfeiRecognitionResult(response.data);if(result.IsFinal){Debug.Log("Text最终:" + result.Text);resultCallback?.Invoke(result.Text);}else{Debug.Log("Text中间:" + result.Text);}}}catch (Exception ex){Debug.LogError(ex.Message);}}void OnError(object sender, ErrorEventArgs e){UnityEngine.Debug.Log("WebSoclet:发生错误:" + e.Message);}public SpeechRecognitionResult ParseXunfeiRecognitionResult(string dataJson){StringBuilder builder = new StringBuilder();SpeechRecognitionResult res = new SpeechRecognitionResult();try{JsonData data = JsonMapper.ToObject(dataJson);JsonData cn = data["cn"];JsonData st = cn["st"];if (st["ed"].ToString().Equals("0")){res.IsFinal = false;}else{res.IsFinal = true;}JsonData rtArry = st["rt"];foreach (JsonData rtObject in rtArry){JsonData wsArr = rtObject["ws"];foreach (JsonData wsObject in wsArr){JsonData cwArr = wsObject["cw"];foreach (JsonData cwObject in cwArr){builder.Append(cwObject["w"].ToString());}}}}catch(Exception ex){Debug.LogError(ex.Message);}res.Text = builder.ToString();return res;}void SendData(byte[] voiceData){Debug.Log("SendData:" + voiceData.Length + ",time:" + Time.realtimeSinceStartup);if (speechWebSocket.ReadyState != WebSocketState.Open){return;}try{if (speechWebSocket != null && speechWebSocket.IsAlive){speechWebSocket.SendAsync(voiceData, success =>{if (success){UnityEngine.Debug.Log("WebSoclet:发送成功:" + voiceData.Length);}else{UnityEngine.Debug.Log("WebSoclet:发送失败:");}});}}catch{}}void SendEndMsg(System.Action callback){string endMsg = "{\"end\": true}";byte[] data = Encoding.UTF8.GetBytes(endMsg);try{if (speechWebSocket != null && speechWebSocket.IsAlive){speechWebSocket.SendAsync(data, success =>{if (success){UnityEngine.Debug.Log("WebSoclet:发送END成功:" + data.Length);}else{UnityEngine.Debug.Log("WebSoclet:发送END失败:");}callback?.Invoke();});}}catch{}}IEnumerator SendVoiceData(){yield return new WaitUntil(()=> (speechWebSocket.ReadyState == WebSocketState.Open));yield return new WaitWhile(() => Microphone.GetPosition(micphoneName) <= 0);float t = 0;int position = Microphone.GetPosition(micphoneName);const float waitTime = 0.04f;//每隔40ms发送音频int lastPosition = 0;const int Maxlength = 640;//最大发送长度//Debug.Log("position:" + position + ",samples:" + RecordedClip.samples);while (position < RecordedClip.samples && speechWebSocket.ReadyState == WebSocketState.Open){t += waitTime;yield return new WaitForSecondsRealtime(waitTime);if (Microphone.IsRecording(micphoneName)) position = Microphone.GetPosition(micphoneName);//Debug.Log("录音时长:" + t + "position=" + position + ",lastPosition=" + lastPosition);if (position <= lastPosition){Debug.LogWarning("字节流发送完毕!强制结束!");break;}int length = position - lastPosition > Maxlength ? Maxlength : position - lastPosition;byte[] date = GetClipData(lastPosition, length, RecordedClip);SendData(date);lastPosition = lastPosition + length;}yield return new WaitForSecondsRealtime(waitTime);SendEndMsg(null);Microphone.End(micphoneName);}public byte[] GetClipData(int star, int length, AudioClip recordedClip){float[] soundata = new float[length];recordedClip.GetData(soundata, star);int rescaleFactor = 32767;byte[] outData = new byte[soundata.Length * 2];for (int i = 0; i < soundata.Length; i++){short temshort = (short)(soundata[i] * rescaleFactor);byte[] temdata = BitConverter.GetBytes(temshort);outData[i * 2] = temdata[0];outData[i * 2 + 1] = temdata[1];}return outData;}private string GetWebSocketUrl(){string appid = "appid";string ts = GetCurrentUnixTimestampMillis().ToString();string baseString = appid + ts;string md5 = GetMD5Hash(baseString);UnityEngine.Debug.Log("baseString:" + baseString + ",md5:" + md5);string sha1 = CalculateHmacSha1(md5, "appkey");string signa = sha1;string url = string.Format("ws://rtasr.xfyun.cn/v1/ws?appid={0}&ts={1}&signa={2}", appid, ts, signa);UnityEngine.Debug.Log(url);return url;}private long GetCurrentUnixTimestampMillis(){DateTime unixStartTime = new DateTime(1970, 1, 1).ToLocalTime();DateTime now = DateTime.Now;// DateTime.UtcNow;TimeSpan timeSpan = now - unixStartTime;long timestamp = (long)timeSpan.TotalSeconds;return timestamp;}public string GetMD5Hash(string input){MD5 md5Hasher = MD5.Create();byte[] data = md5Hasher.ComputeHash(Encoding.Default.GetBytes(input));StringBuilder sBuilder = new StringBuilder();for (int i = 0; i < data.Length; i++){sBuilder.Append(data[i].ToString("x2"));}return sBuilder.ToString();}public string CalculateHmacSha1(string data, string key){HMACSHA1 hmac = new HMACSHA1(Encoding.UTF8.GetBytes(key));byte[] hashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(data));return Convert.ToBase64String(hashBytes);}private void Update(){if(messageQueue.Count > 0){MainThreadOnMessage(messageQueue.Dequeue());}}
}

Json解析类。

[Serializable]
public struct XFResponse
{public string action;public int code;public string data;public string desc;public string sid;
}
[Serializable]
public struct SpeechRecognitionResult
{public string Text;        public bool IsFinal;        
}

值得注意的问题。

1、Microphone使用时传默认设备名比传null好使

2、握手阶段时间戳用的是秒(不是毫秒)

3、上传结束标志时,也要间隔40ms,否则讯飞像是没收到一样

遗留问题:

yield return new WaitForSecondsRealtime(0.04f)实际间隔时间0.1s左右,导致消息发送得很慢

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/6415.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redisson 分布式锁和同步器

系列文章目录 文章目录 系列文章目录前言前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 redisson 是基于redis的扩展库,使得redis除了应用于缓存以外,还能做队列…

FloodFill-----洪水灌溉算法(DFS例题详解)

目录 一.图像渲染&#xff1a; 代码详解&#xff1a; 二.岛屿数量&#xff1a; 代码详解&#xff1a; 三.岛屿的最大面积&#xff1a; 代码详解&#xff1a; 四.被围绕的区域&#xff1a; 代码详解&#xff1a; 五.太平洋大西洋水流问题&#xff1a; 代码详解&#x…

基于SpringBoot的教务管理系统 - 源码免费(私信领取)

1. 研究目的 本项目旨在设计并实现一个基于Spring Boot的教务管理系统&#xff0c;以提升学校教务管理效率&#xff0c;优化教学资源配置&#xff0c;提高教学质量&#xff0c;满足学生、教师和管理人员的需求。 2. 研究要求 a. 需求分析 通过调研学校教务管理流程和现有系…

嵌入式单片机中必会的50个电路分享

单片机 电源 声音模块 收音机 485

操作系统-进程管理

1.进程的定义 2.进程的组成 3.进程的特点 4.进程控制结构 5.进程状态 6.进程挂起 6.线程优缺点 7.线程进程的比较 8 .为什么要使用线程 9.用户线程 9.内核线程 10.进程上下文切换信息储存在PCB中 11.fork()

83. 删除排序链表中的重复元素

Problem: 83. 删除排序链表中的重复元素 文章目录 思路解题方法复杂度Code 思路 遍历 解题方法 节点数范围[0, 300] 需要判断节点数是否为0 如果为0则直接返回设置当前节点指针cur&#xff0c;初始化curhead当cur.next非空时&#xff1a; 判断cur.val 与 cur.next.val 是否相等…

typescript-字面量

typescript-字面量 在计算机科学中&#xff0c;字面量用于在源代码中表示某个固定值。在JavaScript程序中&#xff0c;字面量不是变量&#xff0c;它是直接给出的固定值。 Null字面量 Null字面量只有一个&#xff0c;记作null Boolean字面量 Boolean的字面量有两个&#x…

JavaEE初阶Day 15:文件IO(1)

目录 Day 15&#xff1a;文件IO&#xff08;1&#xff09;IO文件1. 路径2. 文件的分类3. 使用Java针对文件系统进行操作3.1 属性3.2 构造方法3.3 方法 Day 15&#xff1a;文件IO&#xff08;1&#xff09; IO I&#xff1a;Input输入 O&#xff1a;Output输出 输入输出规则…

深入解析Spring Boot中的JWT令牌校验:安全身份验证与授权实践

在现代的Web应用程序中&#xff0c;身份验证和授权是至关重要的功能。 JSON Web Token&#xff08;JWT&#xff09;是一种用于安全传输信息的开放标准&#xff0c;它通过在用户和服务器之间传递被声明的对象来安全地传输信息。Spring Boot作为一种流行的Java开发框架&#xff…

golang 确保并发安全性

golang并发安全性 在Golang中&#xff0c;并发安全性通常指的是当多个goroutines同时访问同一个数据结构或资源时&#xff0c;能够保证数据的一致性和完整性&#xff0c;避免数据竞争、死锁等问题 并发安全性案例 案例1 创建 count&#xff0c;起1000个goroutines&#xff0c;…

Java中自定义异常指南

在Java编程中&#xff0c;异常处理是一个非常重要的部分&#xff0c;它允许我们在程序运行时捕获和处理错误情况。除了Java标准库提供的异常类外&#xff0c;我们还可以根据实际需要创建自定义的异常类。自定义异常可以帮助我们更好地描述和处理特定的错误情况。下面&#xff0…

【python的魅力】:教你如何用几行代码实现文本语音识别

文章目录 引言一、运行效果二、文本转换为语音2.1 使用pyttsx32.2 使用SAPI实现文本转换语音2.3 使用 SpeechLib实现文本转换语音 三、语音转换为文本3.1 使用 PocketSphinx实现语音转换文本 引言 语音识别技术&#xff0c;也被称为自动语音识别&#xff0c;目标是以电脑自动将…

Tomcat启动闪退怎么解决(文末附终极解决方案)

AI是这么告诉我的 Tomcat启动时出现闪退问题可能由多种原因引起&#xff0c;以下是解决此类问题的一些通用方法&#xff1a; 检查环境变量&#xff1a; 确保已经正确设置了JAVA_HOME和JRE_HOME环境变量&#xff0c;并指向正确的Java安装路径。将Java的bin目录添加到系统的PATH…

c语言题目

一些关于c语言的题目 文章目录 一、计算程序输出二、以下程序运行时&#xff0c;若输入1abcedf2df<回车>输出结果是将flag的第二个bit置0结构体大小下列C程序执行后c输出结果为&#xff08;&#xff09;设有定义char *p[]{"Shanghai","Beijing",&quo…

使用独立的 centos 7 安装软件后 commit 为新的镜像并自启动进程

使用独立的 centos 7 安装软件后 commit 为新的镜像&#xff0c;在 dockerfile 里通过添加 CMD ["/usr/sbin/init"] 这个命令来实现程序在容器中的开机自启动&#xff0c;并在 docker run 时添加参数 --privileged 获取容器内真正的 root 权限。 在 docker run 命令…

英语四级备考之名词的定义

名词是人、动物、事物、地方、状态、品质或动作的名称。它可以 表示具体的东西&#xff0c;也可表示抽象的东西。下面斜体字都是名词&#xff1a; John is my friend&#xff0e;My children love their teachers&#xff0e;&#xff08;人&#xff09; The dog is running a…

富格林:细节决定能否安全出金

富格林悉知&#xff0c;投资者都希望在现货黄金交易市场中获利&#xff0c;但并非所有投资者都能实现获利的心愿&#xff0c;有时候忽略一些细节问题也会影响最终的投资效果。投资者应该注重细节实现安全出金才是我们进行投资的最终目标。下面富格林将总结一些注重细节实现安全…

scikit-learn:Python中的机器学习-1

简介&#xff1a;问题设置 什么是机器学习&#xff1f; 机器学习是关于构建具有可调参数的程序&#xff0c;这些参数可以自动调整&#xff0c;以便通过适应先前看到的数据来改善其行为。机器学习可以被认为是人工智能的一个子领域&#xff0c;因为这些算法可以被视为构建模块…

Python量化炒股的获取数据函数—get_index_stocks()

Python量化炒股的获取数据函数—get_index_stocks() 利用get_industry_stocks()函数可以获取在给定日期一个行业的所有股票代码列表&#xff0c;其语法格式如下&#xff1a; get_industry_stocks(industry_code, dateNone)各项参数的意义 参数date和返回值&#xff0c;都与g…

你知道什么是Charles吗?

什么是Charles? Charles中文名叫青花瓷&#xff0c;它是一款基于HTTP协议的代理服务器&#xff0c;通过成为电脑或者浏览器的代理&#xff0c;然后截取请求和请求结果达到分析抓包的目的。它跨平台、半免费&#xff0c;与免费版本不同的是&#xff0c;半免费版本的Charles重启…