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

news2024/11/18 21:41:21

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.coloradmin.cn/o/1637108.html

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

相关文章

Mybatis进阶(动态SQL)

文章目录 1.动态SQL1.基本介绍1.为什么需要动态SQL2.基本说明3.动态SQL常用标签 2.环境搭建1.新建子模块2.删除不必要的两个文件夹3.创建基本结构4.父模块的pom.xml5.jdbc.properties6.mybatis-config.xml7.MyBatisUtils.java8.MonsterMapper.java9.MonsterMapper.xml10.测试Mo…

经典机器学习法---感知模型机

优质博文&#xff1a;IT-BLOG-CN 1、模型形式 感知机模型主要用于解决二分类问题&#xff0c;即响应变量Y是个二分类变量&#xff08;如性别&#xff09;。其基本思想是拟找出一个超平面S&#xff0c;将样本空间中的训练集分为两个部分&#xff0c;使得位于超平面S合一侧的点具…

匠心精神与创新力量:构筑网络安全的新防线

一、匠心精神在网络安全中的重要性 匠心精神代表着对工作的专注和对质量的极致追求。在网络安全领域&#xff0c;这意味着对每一个安全漏洞的深入挖掘&#xff0c;对每一项安全技术的精心打磨。亿林网络李璐昆的提名&#xff0c;正是对其在网络安全领域匠心精神的认可。 二、…

手把手教数据结构与算法:优先级队列(银行排队问题)

队列 基本概念 队列的定义 队列&#xff08;Queue&#xff09;&#xff1a;队列是一种常见的数据结构&#xff0c;遵循先进先出&#xff08;First-In-First-Out, FIFO&#xff09;的原则。在队列中&#xff0c;元素按照进入队列的顺序排列。队列是一个线性的数据结构&#x…

【PPT设计】颜色对比、渐变填充、简化框线、放大镜效果、渐变形状配图、线条的使用

目录 图表颜色对比、渐变填充、简化框线放大镜效果渐变形状配图 线条的使用区分标题与说明信息区分标题与正文,区分不同含义的内容**聚焦****引导****注解****装饰** 图表 颜色对比、渐变填充、简化框线 小米汽车正式亮相&#xff01;你们都在讨论价格&#xff0c;我全程只关…

【C语言】指针篇- 深度解析Sizeof和Strlen:热门面试题探究(5/5)

&#x1f308;个人主页&#xff1a;是店小二呀 &#x1f308;C语言笔记专栏&#xff1a;C语言笔记 &#x1f308;C笔记专栏&#xff1a; C笔记 &#x1f308;喜欢的诗句:无人扶我青云志 我自踏雪至山巅 文章目录 一、简单介绍Sizeof和Strlen1.1 Sizeof1.2 Strlen函数1.3 Sie…

几个容器网络问题实战解析

容器云平台和容器网络紧密结合&#xff0c;共同构建了容器化应用程序的网络基础设施&#xff0c;实现了容器之间的通信、隔离和安全性。文中容器云平台采用的容器网络组件是calico&#xff0c;这个是业界普遍采用的一种方案&#xff0c;性能及安全性在同类产品中都是比较好的。…

【UnityRPG游戏制作】Unity_RPG项目_玩家逻辑相关

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;就业…

Android Studio 调试:快速入门指南

作为一名Android应用开发人员&#xff0c;调试是你不可或缺的技能之一。通过调试&#xff0c;你可以定位和解决各种问题&#xff0c;包括崩溃、性能问题、UI错误等。在本文中&#xff0c;我们将分享一些实用的Android调试技巧&#xff0c;帮助你提高应用开发效率。 Android St…

Delta lake with Java--将数据保存到Minio

今天看了之前发的文章&#xff0c;居然有1条评论&#xff0c;看到我写的东西还是有点用。 今天要解决的问题是如何将 Delta产生的数据保存到Minio里面。 1、安装Minio&#xff0c;去官网下载最新版本的Minio&#xff0c;进入下载目录&#xff0c;运行如下命令&#xff0c;曾经…

动态规划——记忆化递归

1.情景导入 你应该知道斐波那契数列吧&#xff01;就是前两项之和等于这一项。如果你学过递归&#xff0c;你肯定会写这道题&#xff1a;输入一个N代表你要求的项数&#xff0c;然后输出斐波那契的第N项。这道题看似简单&#xff0c;实则也挺简单实则特别困难&#xff08;对于…

C++学习第十五课:类型转换运算符的深度解析

C学习第十五课&#xff1a;类型转换运算符的深度解析 类型转换是编程中常见的需求&#xff0c;C提供了多种类型转换方式&#xff0c;包括静态类型转换和动态类型转换。此外&#xff0c;还可以通过类型转换运算符自定义转换行为。本课将深入探讨C中的类型转换机制&#xff0c;包…

Visual Source Safe 安装与使用教程

1.VSS 的工作原理: Microsott的 vss讲所有的项目源文件(包括各种文件类型)以特有的方式存入数据库。用户成员不能对该数据库中的文件进行直接的修改,而是由版本管理器将该项目的远程序或是子项目的程序拷贝到各个用户成员自己的工作目录下进行调试和修改,然后将修改后的项目…

[ log日志画图]分割模型训练结束生成相关日志运用代码画图

文章目录 [ log日志画图]分割模型训练结束生成相关日志运用代码画图我的log文件&#xff1a;画图&#xff1a;1.loss1.1 loss是干嘛的1.2 代码1.3 生成图 2.DICE.IOU2.1 DICE,IOU是干嘛的(常规介绍)2.2 代码2.3 生成图小白tip [ log日志画图]分割模型训练结束生成相关日志运用代…

《Redis使用手册之Lua脚本》

《Redis使用手册之Lua脚本》 EVAL&#xff1a;执行脚本 127.0.0.1:6379> eval “return ‘hello world’” 0 “hello world” 127.0.0.1:6379> eval “return redis.call(‘set’,KEYS[1],ARGV[1])” 1 “message” “hello world” OK 127.0.0.1:6379> get message…

基于FPGA的数字信号处理(5)--Signed的本质和作用

前言 Verilog中的signed是一个很多人用不好&#xff0c;或者说不太愿意用的一个语法。因为不熟悉它的机制&#xff0c;所以经常会导致运算结果莫名奇妙地出错。其实了解了signed以后&#xff0c;很多时候用起来还是挺方便的。 signed的使用方法主要有两种&#xff0c;其中一种…

【Windows,亲测有效】手动激活Sublime Text

前言 Sublime Text 是一款非常好用的文本编辑器&#xff0c;但是免费版时不时会跳弹窗 本方法无毒无害&#xff0c;简单易上手 2023/12/22 更新&#xff1a;实测从 4143 支持到 4169 开始 先确保你用的是官方版本的 Sublime Text&#xff0c;还没下的可以去官方下载&#…

TiDB系列之:部署TiDB集群常见报错解决方法

TiDB系列之&#xff1a;部署TiDB集群常见报错解决方法 一、部署TiDB集群二、unsupported filesystem ext3三、soft limit of nofile四、THP is enabled五、numactl not usable六、net.ipv4.tcp_syncookies 1七、service irqbalance not found,八、登陆TiDB数据库 一、部署TiDB…

【ARM 裸机】NXP 官方 SDK 使用

在前几节中&#xff0c;学习了如何编写汇编的 led 驱动、C 语言的 led 驱动、模仿 STM32 进行开发&#xff0c;我们都是自己写外设寄存器的结构体&#xff0c;外设非常的多&#xff0c;写起来费时费力&#xff0c;NXP 针对 I.MX6ULL 编写了一个 SDK 包&#xff0c;这个 SDK 包就…

C++ | Leetcode C++题解之第59题螺旋矩阵II

题目&#xff1a; 题解&#xff1a; class Solution { public:vector<vector<int>> generateMatrix(int n) {int num 1;vector<vector<int>> matrix(n, vector<int>(n));int left 0, right n - 1, top 0, bottom n - 1;while (left < r…