UnityWebGL使用sherpa-ncnn实时语音识别

k2-fsa/sherpa-ncnn:在没有互联网连接的情况下使用带有 ncnn 的下一代 Kaldi 进行实时语音识别。支持iOS、Android、Raspberry Pi、VisionFive2、LicheePi4A等。 (github.com)

如果是PC端可以直接使用ssssssilver大佬的 https://github.com/ssssssilver/sherpa-ncnn-unity.git

我这边要折腾的是WebGL版本的,所以修改了一番

1、WebSocket,客户端使用了psygames/UnityWebSocket: :whale: The Best Unity WebSocket Plugin for All Platforms. (github.com)

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityWebSocket;

public class uSherpaWebGL : MonoBehaviour
{
    IWebSocket ws;
    public Text text;
    Queue<string> msgs = new Queue<string>();

    // Start is called before the first frame update
    void Start()
    {
        ws = new WebSocket("ws://127.0.0.1:9999");
        ws.OnOpen += OnOpen;
        ws.OnMessage += OnMessage;
        ws.OnError += OnError;
        ws.OnClose += OnClose;
        ws.ConnectAsync();
    }

    // Update is called once per frame
    void Update()
    {
        if (msgs.Count > 0)
        {
            string msg = msgs.Dequeue();
            text.text += msg;
        }
    }

    byte[] desArray;
    public void OnData(float[] input)
    {
        Debug.Log("input.Length:" + input.Length);
        SendData(input);
    }

    void SendData(float[] input)
    {
        var desArraySize = Buffer.ByteLength(input);
        IntPtr srcArrayPtr = Marshal.UnsafeAddrOfPinnedArrayElement(input, 0);
        desArray = new byte[desArraySize];
        Marshal.Copy(srcArrayPtr, desArray, 0, desArraySize);
        if (ws != null && ws.ReadyState == WebSocketState.Open)
        {
            ws.SendAsync(desArray);
        }
    }

    void OnOpen(object sender, OpenEventArgs e)
    {
        Debug.Log("WS connected!");
    }

    void OnMessage(object sender, MessageEventArgs e)
    {
        if (e.IsBinary)
        {
            string str = Encoding.UTF8.GetString(e.RawData);
            Debug.Log("WS received message: " + str);
            msgs.Enqueue(str);
        }
        else if (e.IsText)
        {

        }
    }

    void OnError(object sender, ErrorEventArgs e)
    {
        Debug.Log("WS error: " + e.Message);
    }

    void OnClose(object sender, CloseEventArgs e)
    {
        Debug.Log(string.Format("Closed: StatusCode: {0}, Reason: {1}", e.StatusCode, e.Reason));
    }

    private void OnApplicationQuit()
    {
        if (ws != null && ws.ReadyState != WebSocketState.Closed)
        {
            ws.CloseAsync();
        }
    }
}

服务器端使用了Fleck

// See https://aka.ms/new-console-template for more information
using Fleck;
using System.Text;

namespace uSherpaServer
{
    internal class Program
    {
        // 声明配置和识别器变量
        static SherpaNcnn.OnlineRecognizer recognizer;
        static SherpaNcnn.OnlineStream onlineStream;

        static string tokensPath = "tokens.txt";
        static string encoderParamPath = "encoder_jit_trace-pnnx.ncnn.param";
        static string encoderBinPath = "encoder_jit_trace-pnnx.ncnn.bin";
        static string decoderParamPath = "decoder_jit_trace-pnnx.ncnn.param";
        static string decoderBinPath = "decoder_jit_trace-pnnx.ncnn.bin";
        static string joinerParamPath = "joiner_jit_trace-pnnx.ncnn.param";
        static string joinerBinPath = "joiner_jit_trace-pnnx.ncnn.bin";
        static int numThreads = 1;
        static string decodingMethod = "greedy_search";

        static string modelPath;
        static float sampleRate = 16000;

        static IWebSocketConnection client;
        static void Main(string[] args)
        {
            //需要将此文件夹拷贝到exe所在的目录
            modelPath = Environment.CurrentDirectory + "/sherpa-ncnn-streaming-zipformer-small-bilingual-zh-en-2023-02-16";
            // 初始化配置
            SherpaNcnn.OnlineRecognizerConfig config = new SherpaNcnn.OnlineRecognizerConfig
            {
                FeatConfig = { SampleRate = sampleRate, FeatureDim = 80 },
                ModelConfig = {
                Tokens = Path.Combine(modelPath,tokensPath),
                EncoderParam =  Path.Combine(modelPath,encoderParamPath),
                EncoderBin =Path.Combine(modelPath, encoderBinPath),
                DecoderParam =Path.Combine(modelPath, decoderParamPath),
                DecoderBin = Path.Combine(modelPath, decoderBinPath),
                JoinerParam = Path.Combine(modelPath,joinerParamPath),
                JoinerBin =Path.Combine(modelPath,joinerBinPath),
                UseVulkanCompute = 0,
                NumThreads = numThreads
            },
                DecoderConfig = {
                DecodingMethod = decodingMethod,
                NumActivePaths = 4
            },
                EnableEndpoint = 1,
                Rule1MinTrailingSilence = 2.4F,
                Rule2MinTrailingSilence = 1.2F,
                Rule3MinUtteranceLength = 20.0F
            };

            // 创建识别器和在线流
            recognizer = new SherpaNcnn.OnlineRecognizer(config);

            onlineStream = recognizer.CreateStream();

            StartWebServer();
            Update();
            Console.ReadLine();
        }

        static void StartWebServer()
        {
            //存储连接对象的池
            var connectSocketPool = new List<IWebSocketConnection>();
            //创建WebSocket服务端实例并监听本机的9999端口
            var server = new WebSocketServer("ws://127.0.0.1:9999");
            //开启监听
            server.Start(socket =>
            {
                //注册客户端连接建立事件
                socket.OnOpen = () =>
                {
                    client = socket;
                    Console.WriteLine("Open");
                    //将当前客户端连接对象放入连接池中
                    connectSocketPool.Add(socket);
                };
                //注册客户端连接关闭事件
                socket.OnClose = () =>
                {
                    client = null;
                    Console.WriteLine("Close");
                    //将当前客户端连接对象从连接池中移除
                    connectSocketPool.Remove(socket);
                };
                //注册客户端发送信息事件
                socket.OnBinary = message =>
                {
                    float[] floatArray = new float[message.Length / 4];
                    Buffer.BlockCopy(message, 0, floatArray, 0, message.Length);
                    // 将采集到的音频数据传递给识别器
                    onlineStream.AcceptWaveform(sampleRate, floatArray);
                };
            });
        }

        static string lastText = "";

        static void Update()
        {
            while (true)
            {
                // 每帧更新识别器状态
                if (recognizer.IsReady(onlineStream))
                {
                    recognizer.Decode(onlineStream);
                }

                var text = recognizer.GetResult(onlineStream).Text;
                bool isEndpoint = recognizer.IsEndpoint(onlineStream);
                if (!string.IsNullOrWhiteSpace(text) && lastText != text)
                {
                    if (string.IsNullOrWhiteSpace(lastText))
                    {
                        lastText = text;
                        if (client != null)
                        {
                            client.Send(Encoding.UTF8.GetBytes(text));
                            //Console.WriteLine("text1:" + text);
                        }
                    }
                    else
                    {
                        if (client != null)
                        {
                            client.Send(Encoding.UTF8.GetBytes(text.Replace(lastText, "")));
                            lastText = text;
                        }
                    }
                }

                if (isEndpoint)
                {
                    if (!string.IsNullOrWhiteSpace(text))
                    {
                        if (client != null)
                        {
                            client.Send(Encoding.UTF8.GetBytes("。"));
                        }
                       // Console.WriteLine("text2:" + text);
                    }
                    recognizer.Reset(onlineStream);
                    //Console.WriteLine("Reset");
                }
                Thread.Sleep(200); // ms
            }
        }
    }
}

2、Unity录音插件使用了uMicrophoneWebGL 绑定DataEvent事件实时获取话筒数据(float数组)

最后放上工程地址

客户端 uSherpa: fork from https://github.com/ssssssilver/sherpa-ncnn-unity.git改成 Unity WebGL版

服务器端 GitHub - xue-fei/uSherpaServer: uSherpaServer 给Unity提供流式语音识别的websocket服务

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

unity入门——按钮点击了却无法调用函数

查阅了一番都没有解决问题&#xff0c;最后发现问题是由button的Onclick()事件绑定了代码脚本而不是游戏对象导致的。 如果Onclick()事件绑定的是代码脚本&#xff0c;则下拉框里没有函数&#xff0c;但是点击MonoScript后能手动填入函数名&#xff08;本以为这样就能实现调用…

使用Python的Tkinter库创建你的第一个桌面应用程序

文章目录 准备工作创建窗口和按钮代码解释运行你的应用程序结论 在本教程中&#xff0c;我们将介绍如何使用Python的Tkinter库创建一个简单的桌面应用程序。我们将会创建一个包含一个按钮的窗口&#xff0c;点击按钮时会在窗口上显示一条消息。 准备工作 首先&#xff0c;确保…

【Python】常用数据结构

1、熟悉字典和列表 2、使用条件判断语句 3、list列表中计算 1、从键盘输人一个正整数列表,以-1结束,分别计算列表中奇数和偶数的和。 &#xff08;1&#xff09;源代码&#xff1a; # 初始化奇数和偶数的和为0 odd_sum 0 even_sum 0 #输入 while True:num int(input(&qu…

java+jsp+Oracle+Tomcat 记账管理系统论文(二)

⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️ ➡️点击免费下载全套资料:源码、数据库、部署教程、论文、答辩ppt一条龙服务 ➡️有部署问题可私信联系 ⬆️⬆️⬆️​​​​​​​⬆️…

分布式链路追踪工具Sky walking详解

1&#xff0c;为什么要使用分布式链路追踪工具 随着分布式系统和微服务架构的出现&#xff0c;且伴随着用户量的增加&#xff0c;项目的体量变得十分庞大&#xff0c;一次用户请求会经过多个系统&#xff0c;不同服务之间调用关系十分复杂&#xff0c;一旦一个系统出现错误都可…

微软如何打造数字零售力航母系列科普06 - 如何使用微软的Copilot人工智能

如何使用微软的Copilot人工智能&#xff1f; Copilot和ChatGPT有很多相似之处&#xff0c;但微软的聊天机器人本身就有一定的优势。以下是如何对其进行旋转&#xff0c;并查看其最引人注目的功能。 ​​​​​​​ &#xff08;资料来源&#xff1a;Lance Whitney/微软&…

C++ | 类和对象(中) (构造函数 | 析构函数 | 拷贝构造函数 | 赋值运算符重载 | 取地址 | const取地址)

目录 默认成员函数 构造函数 构造函数是什么 构造函数特征 什么是默认构造函数 注意事项 编译器自动生成的默认构造 缺省值 对象如何传值给构造函数 初始化列表 析构函数 析构函数的特征 编译器默认生成的析构函数 总结 拷贝构造函数 拷贝构造函数的使用场景 拷…

使用ipxe安装现有的装机环境

iPXE和传统PXE区别 iPXE和传统PXE&#xff08;Pre-boot Execution Environment&#xff0c;预启动执行环境&#xff09;的主要区别在于它们的功能和协议支持。以下是两者的主要区别&#xff1a; 协议支持&#xff1a; PXE仅支持TFTP&#xff08;trivial file transfer protoco…

【linuxC语言】空洞文件

文章目录 前言一、空洞文件1.1 空洞文件的介绍1.2 用途 二、示例代码总结 前言 在 Linux 系统编程中&#xff0c;空洞文件是一种特殊类型的文件&#xff0c;它包含了逻辑上的空洞&#xff0c;也就是说文件中的某些部分并没有实际写入数据。尽管文件在逻辑上可能非常大&#xf…

向eclipse中的项目导入jdk、tomcat

前言&#xff1a; 有些项目无法正常启动可能是因为他的基础配置不正确或者没配置&#xff0c;eclipse中的javaweb项目常见的配置就是jdk、tomcat&#xff0c;这三者配置的方式大概相同&#xff0c;以下是相关操作。我的环境是eclipse2018。 一、jdk 在项目上右键选中propert…

java-Spring-mvc-(请求和响应)

目录 &#x1f4cc;HTTP协议 超文本传输协议 请求 Request 响应 Response &#x1f3a8;请求方法 GET请求 POST请求 &#x1f4cc;HTTP协议 超文本传输协议 HTTP协议是浏览器与服务器通讯的应用层协议&#xff0c;规定了浏览器与服务器之间的交互规则以及交互数据的格式…

Swiper轮播图

版本&#xff1a;“swiper”: “^6.8.4”, 处理每分钟重新请求数据后&#xff0c;播放卡顿&#xff0c;快速闪&#xff0c;没按照设置时间播放等bug 以下是直接vue2 完整的组件代码 使用&#xff1a; <SwiperV :imgList“swiperList” / <template><div class"…

键盘更新计划

作为 IT 搬砖人&#xff0c;一直都认为键盘没有什么太大关系。 每次都是公司发什么用什么。 但随着用几年后&#xff0c;发现现在的键盘经常出问题&#xff0c;比如说调节音量的时候通常莫名其妙的卡死&#xff0c;要不就是最大音量要不就是最小音量。 按键 M 不知道什么原因…

hadoop学习---基于hive的聊天数据分析报表可视化案例

背景介绍&#xff1a; 聊天平台每天都会有大量的用户在线&#xff0c;会出现大量的聊天数据&#xff0c;通过对聊天数据的统计分析&#xff0c;可以更好的对用户构建精准的用户画像&#xff0c;为用户提供更好的服务以及实现高ROI的平台运营推广&#xff0c;给公司的发展决策提…

50. 【Android教程】xml 数据解析

xml 是一种标记扩展语言&#xff08;Extension Mark-up Language&#xff09;&#xff0c;学到这里大家对 xml 语言一定不陌生&#xff0c;但是它在 Android 中的运用其实只是冰山一角。抛开 Android&#xff0c;XML 也被广泛运用于各种数据结构中。在运用 xml 编写 Android 布…

自动化机器学习——网格搜索法:寻找最佳超参数组合

自动化机器学习——网格搜索法&#xff1a;寻找最佳超参数组合 在机器学习中&#xff0c;选择合适的超参数是模型调优的关键步骤之一。然而&#xff0c;由于超参数的组合空间通常非常庞大&#xff0c;手动调整超参数往往是一项耗时且困难的任务。为了解决这个问题&#xff0c;…

Meditron:基于 Llama 完全开源的医学大语言模型

健康危机就在眼前&#xff0c;当医疗资源有限时&#xff0c;每一秒钟都至关重要&#xff01;Meditron 就像一位忠实的医疗助手&#xff0c;提供基于证据的护理建议和情境意识的推荐&#xff0c;帮助医疗工作者在诊断和治疗过程中做出更准确的决策。 在资源有限的医疗环境中&am…

CTFHub-Web-文件上传

CTFHub-Web-文件上传-WP 一、无验证 1.编写一段PHP木马脚本 2.将编写好的木马进行上传 3.显示上传成功了 4.使用文件上传工具进行尝试 5.连接成功进入文件管理 6.上翻目录找到flag文件 7.打开文件查看flag 二、前端验证 1.制作payload进行上传发现不允许这种类型的文件上传 …

React | React.cloneElement 的使用

我看到同事的代码里有 cloneElement&#xff0c;于是去了解了一下这个函数。 就跟它的名字一样&#xff0c;克隆元素&#xff0c;可以基于一个元素创建一个新的元素&#xff0c;并且为新元素添加新的属性或者覆盖已有的属性。 下面是一个简单例子&#xff1a; .node1 {backg…

使用qemu调试NVME driver

参考nvme驱动相关的博客&#xff0c;可以使用qemu buildroot进行nvme驱动的流程debug。 一、QEMU编译 首先需要编译qemu&#xff0c;可以参考QEMU编译。wget下载最新版本的QEMU&#xff0c;编译之前&#xff0c;最好检查下依赖包是否安装&#xff0c;避免安装过程出现各种错…