【离线语音助手】Xamarin From Android 语音识别 合成 sherpa-onnx

概要

本方详细介绍了如何在Xamarin From中如何实现离线语音识别合生,并提供解决方案与部分代码。这里我们选用 sherpa 开源项目部署语音识别、合成等模型。离线语音识别库有whisper、kaldi、pocketshpinx等,在了解这些库的时候,发现了所谓“下一代Kaldi”的sherpa。从文档和模型名称看,它是一个很新的离线语音识别库,支持中英双语识别,文件和实时语音识别。sherpa是一个基于下一代 Kaldi 和 onnxruntime 的开源项目,专注于语音识别、文本转语音、说话人识别和语音活动检测(VAD)等功能。该项目支持在没有互联网连接的情况下本地运行,适用于嵌入式系统、Android、iOS、Raspberry Pi、RISC-V 和 x86_64 服务器等多种平台。支持流式语音处理。

他有 ncnn、onnx 等平台的子项目:
https://github.com/k2-fsa/sherpa-onnx
https://github.com/k2-fsa/sherpa-ncnn

包含的功能如下:

功能描述
实时语音识别 (Streaming Speech Recognition)在语音输入的同时进行处理和识别,适用于需要即时反馈的场景,如会议和语音助手。
非实时语音识别 (Non-Streaming Speech Recognition)在录制完毕后进行处理,适合需要高准确率的场景,如音频转写和文档生成。
文本转语音 (Text-to-Speech, TTS)将文本内容转换为自然语音输出,广泛应用于语音助手和导航系统。
说话人分离 (Speaker Diarization)识别和区分音频流中的不同说话人,常用于会议记录和多说话人对话分析。
说话人识别 (Speaker Identification)确认说话者的身份,分析声纹特征并与数据库进行比对。
说话人验证 (Speaker Verification)要求说话者提供声纹以确认身份,常用于安全性较高的场合,如银行系统。
口语语言识别 (Spoken Language Identification)识别语音中使用的语言,帮助系统在多语言环境中自动切换语言。
音频标记 (Audio Tagging)为音频内容添加标签,便于分类和搜索,常用于音频库管理和内容推荐。
语音活动检测 (Voice Activity Detection, VAD)检测音频流中是否存在语音活动,提升语音识别准确性并节省带宽和处理资源。
关键词检测 (Keyword Spotting)识别特定关键词或短语,常用于智能助手和语音控制设备,允许用户通过语音命令与设备交互。

官方参考文档:
https://k2-fsa.github.io/sherpa/onnx/index.html

技术细节

步骤一、在Visual Studio中导入nuget包

找到项目 引用 右击引用 管理NuGet程序包
安装 org.k2fsa.sherpa.onnx.runtime.osx-x64
下载 org.k2fsa.sherpa.onnx.runtime.osx-x64
安装 Microsoft.ML.OnnxRuntime
安装 Microsoft.ML.OnnxRuntime**

步骤二、下载官方模型文件与SDK引入到项目

语音识别模型下载与引用
本文使用的是sherpa-onnx-streaming-zipformer-ctc-multi-zh-hans-2023-12-13模型,当然官方还提供了不少其他模型,各位小伙伴根据自己的需求下载
官方模型列表地址:官方模型
本文模型下载地址:sherpa-onnx-streaming-zipformer-ctc-multi-zh-hans-2023-12-13
在这里插入图片描述
模型解压后得到如下目录结构
在这里插入图片描述
复制此目录到项目目录Assets目录下:如果没有请Assets 在此目录下创建Assets文件夹
因为下来文件名sherpa-onnx-streaming-zipformer-ctc-multi-zh-hans-2023-12-13太长了所以我把名字修改成了sherpaonnxSpeechToText
在这里插入图片描述在这里插入图片描述
并修改所有文件属性为AndroidAsset
在这里插入图片描述

语音合成模型下载与引用
本文使用的是sherpa-onnx-vits-zh-ll模型,当然官方还提供了不少其他模型,各位小伙伴根据自己的需求下载
官方模型列表地址:官方模型
本文模型下载地址:sherpa-onnx-vits-zh-l
在这里插入图片描述
解压后文件目录
在这里插入图片描述
和上面一样复制此目录到Accets目录下 并修改所有目录文件属性为AndroidAsset
此项目中sherpa-onnx-vits-zh-ll改名成sherpaonnxvitszhll
在这里插入图片描述
引用so文件
官方模型列表地址:官方模型
这里选择的是v1.10.20java8的版本
在这里插入图片描述
解压后目录
在这里插入图片描述
在这里插入图片描述
在项目中创建libs目录 按照解压后的文件把so分别放到项目对应的目录中
在这里插入图片描述
设置所有so文件属性AndroidNativeLibrary
在这里插入图片描述
导入jar包
和模型一起下载的还有一个jar包 我们需要把此jar包引用到项目中,犹豫vs项目不支持直接引用jar包,所以我们要创建一个jar项目,再通过引用jar项目来引用jar包
在这里插入图片描述
引用此jar包项目
在这里插入图片描述
上面准备工作做好了现在我们就可以写代码运行了
这里是我的代码结构
在这里插入图片描述
ArsTools功能是把Assets资源中的模型导入到app内部目录(直接访问外部目录可能导致系统找不到模型文件)
ArsTools代码

using Android.App;
using Android.Content;
using System;
using System.IO;
using Android.Content.Res;
using Android.Content;
using Android.Media;
using Xamarin.Essentials;
using static Android.Provider.MediaStore;

namespace Your.Namespace
{

    public class ArsTools
    {
        private static readonly string TAG = typeof(Tools).FullName;

        // 设置 context
        private static Context _context;

        public static void SetContext(Context context)
        {
            _context = context;
        }

        // 递归复制文件
        public static void CopyAsset(string assetPath, string root)
        {
            AssetManager assetManager = _context.Assets;
            string[] files = null;

            try
            {
                // 获取指定目录下的所有文件和目录
                files = assetManager.List(assetPath);
            }
            catch (IOException e)
            {
                Console.WriteLine(e.Message);
            }

            if (files != null)
            {
                foreach (var filename in files)
                {
                    string assetFilePath = Path.Combine(assetPath, filename); // 资源文件的完整路径
                    string destFilePath = Path.Combine(root, assetPath) + "/" + filename; // 目标文件的完整路径

                    try
                    {
                        if (!File.Exists(Path.Combine(root, assetPath)))
                        {
                            // 如果是目录,则创建目录并递归复制
                            Directory.CreateDirectory(Path.Combine(root, assetPath));
                        }
                        // 判断是否为目录
                        if (assetManager.List(assetFilePath)?.Length > 0)
                        {
                            if (!File.Exists(destFilePath))
                            {
                                // 如果是目录,则创建目录并递归复制
                                Directory.CreateDirectory(Path.GetDirectoryName(destFilePath));
                            }
                            CopyAsset(assetFilePath, root);
                        }
                        else
                        {
                            if (!File.Exists(destFilePath))
                            {
                                using (System.IO.Stream input = assetManager.Open(assetFilePath))
                                using (System.IO.Stream output = File.Create(destFilePath))
                                {
                                    // 复制文件
                                    input.CopyTo(output);
                                }
                            }
                        }

                        Console.WriteLine($"文件复制:{destFilePath},成功:{File.Exists(destFilePath)}");
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(e.Message);
                    }
                }
            }
        }
        //}



        // 播放 wav 文件
        //public static async void PlayWav(string wavPath)
        //{
        //    try
        //    {
        //        await Audio.PlayAsync(wavPath);
        //    }
        //    catch (Exception e)
        //    {
        //        Console.WriteLine(e.Message);
        //    }
        //}

        // 获取 app 存储路径
        public static string GetAppStoragePath()
        {
            return FileSystem.AppDataDirectory;
        }
    }

}

业务代码
ArsSpeechHelper

using AGVS.HIMS.Androids.Business.util;
using AGVS.HIMS.Forms.AppServer;
using Android.Content;
using Com.K2fsa.Sherpa.Onnx;
using NPOI.POIFS.Properties;
using System;
using System.Diagnostics;
using System.IO;

namespace Your.Namespace
{
    public class ArsSpeechHelper
    {
        Context ArsContext;
        public ArsSpeechHelper(Context context)
        {
            ArsContext = context;
        }

        public void SpeechToText()
        {
            try
            {
                //https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-streaming-zipformer-ctc-multi-zh-hans-2023-12-13.tar.bz2
                //数据库名称
                var sqliteFilename = "sherpaonnxSpeechToText";

                ArsTools.SetContext(ArsContext);
                ArsTools.CopyAsset(sqliteFilename, Tools.GetAppStoragePath());
                string model = ArsTools.GetAppStoragePath() + "/sherpaonnxSpeechToText/ctc-epoch-20-avg-1-chunk-16-left-128.onnx";
                string tokens = ArsTools.GetAppStoragePath() + "/sherpaonnxSpeechToText/tokens.txt";
                string waveFilename = ArsTools.GetAppStoragePath() + "/sherpaonnxSpeechToText/test_wavs/DEV_T0000000000.wav";
                
                WaveReader reader = new WaveReader(waveFilename);
                OnlineZipformer2CtcModelConfig ctc = OnlineZipformer2CtcModelConfig.InvokeBuilder()
                    .SetModel(model).Build();
                OnlineModelConfig modelConfig = OnlineModelConfig.InvokeBuilder()
                                .SetZipformer2Ctc(ctc)
                                .SetTokens(tokens)
                                .SetNumThreads(1)
                                .SetDebug(true)
                                .Build();
                OnlineRecognizerConfig config = OnlineRecognizerConfig.InvokeBuilder()
                                .SetOnlineModelConfig(modelConfig)
                                .SetDecodingMethod("greedy_search")
                                .Build();
                OnlineRecognizer recognizer = new OnlineRecognizer(config);
                OnlineStream stream = recognizer.CreateStream();
                stream.AcceptWaveform(reader.GetSamples(), reader.SampleRate);
                float[] tailPaddings = new float[(int)(0.3 * reader.SampleRate)];
                stream.AcceptWaveform(tailPaddings, reader.SampleRate);
                while (recognizer.IsReady(stream))
                {
                    recognizer.Decode(stream);
                }
                string text = recognizer.GetResult(stream).Text;
                //System.out.printf("filename:%s\nresult:%s\n", waveFilename, text);
                stream.Release();
                recognizer.Release();

            }
            catch (Exception ex) { }
        }

        public void TextToSpeech(string text = "你好!")
        {
            try
            {
                //测试
                text = "在语音输入的同时进行处理和识别,适用于需要即时反馈的场景,如会议和语音助手。在录制完毕后进行处理,适合需要高准确率的场景,如音频转写和文档生成。";
                // please visit
                //  https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/vits-melo-tts-zh_en.tar.bz2
                // to download model files
                //读取数据库文件
                string documentsPath = (string)Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads);
                //数据库名称
                var sqliteFilename = "sherpaonnxvitszhll";

                ArsTools.SetContext(ArsContext);
                ArsTools.CopyAsset(sqliteFilename, Tools.GetAppStoragePath());

                string model = ArsTools.GetAppStoragePath() + "/sherpaonnxvitszhll/model.onnx";

                string tokens = ArsTools.GetAppStoragePath() + "/sherpaonnxvitszhll/tokens.txt";
                string lexicon = ArsTools.GetAppStoragePath() + "/sherpaonnxvitszhll/lexicon.txt";
                string dictDir = ArsTools.GetAppStoragePath() + "/sherpaonnxvitszhll/dict";
                string ruleFsts =
                         ArsTools.GetAppStoragePath() + "/sherpaonnxvitszhll/phone.fst," +
                         ArsTools.GetAppStoragePath() + "/sherpaonnxvitszhll/date.fst," +
                         ArsTools.GetAppStoragePath() + "/sherpaonnxvitszhll/number.fst";

                OfflineTtsVitsModelConfig vitsModelConfig = OfflineTtsVitsModelConfig.InvokeBuilder()
                                .SetModel(model)
                                .SetTokens(tokens)
                                .SetLexicon(lexicon)
                                .SetDictDir(dictDir)
                                .Build();
                OfflineTtsModelConfig modelConfig = OfflineTtsModelConfig.InvokeBuilder()
                                .SetVits(vitsModelConfig)
                                .SetNumThreads(1)
                                .SetDebug(true)
                                .Build();
                OfflineTtsConfig config = OfflineTtsConfig.InvokeBuilder()
                    .SetModel(modelConfig)
                    .Build();
                OfflineTts tts = new OfflineTts(config);
                int sid = 100;
                float speed = 1.0f;
                // 使用 Stopwatch 记录处理时间
                Stopwatch stopwatch = new Stopwatch();
                stopwatch.Start();
                GeneratedAudio audio = tts.Generate(text, sid, speed);
                stopwatch.Stop();
                float timeElapsedSeconds = stopwatch.ElapsedMilliseconds / 1000.0f;
                float audioDuration = audio.GetSamples().Length / (float)audio.SampleRate;
                float real_time_factor = timeElapsedSeconds / audioDuration;
                string waveFilename = Path.Combine(documentsPath, "tts-vits-zh.wav");
                audio.Save(waveFilename);
                //System.out.printf("-- elapsed : %.3f seconds\n", timeElapsedSeconds);
                //System.out.printf("-- audio duration: %.3f seconds\n", timeElapsedSeconds);
                //System.out.printf("-- real-time factor (RTF): %.3f\n", real_time_factor);
                //System.out.printf("-- text: %s\n", text);
                //System.out.printf("-- Saved to %s\n", waveFilename);
                tts.Release();

            }
            catch (Exception ex)
            {

            }
        }
    }
}

这样我们就可以在MainActivity.cs中使用啦

 [Activity(Label = "测试", Icon = "@drawable/app2", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
 public class MainActivity : FormsAppCompatActivity
 {
  protected override async void OnCreate(Bundle savedInstanceState)
 {
     try
     {
         //设置主题
         this.SetTheme(Resource.Style.MainTheme);
         TabLayoutResource = Resource.Layout.Tabbar;
         ToolbarResource = Resource.Layout.Toolbar;
         //全屏界面
         var uiOpts = SystemUiFlags.LayoutStable
          | SystemUiFlags.LayoutHideNavigation
          | SystemUiFlags.LayoutFullscreen
          | SystemUiFlags.Fullscreen
          | SystemUiFlags.HideNavigation
          | SystemUiFlags.Immersive;
         Window.DecorView.SystemUiVisibility = (StatusBarVisibility)uiOpts;
         base.OnCreate(savedInstanceState);
         //调用接口
         ArsSpeechHelper arsSpeechHelper = new ArsSpeechHelper(BaseContext);
         arsSpeechHelper.TextToSpeech("");
         arsSpeechHelper.SpeechToText();
     }
  	 catch (Exception ex)
  	 {
  	 }
  	 finally
  	 {
      	 try
      	 {
          	 //初始化容器
          	 LoadApplication(new Bootstrapper(IoC.Get<SimpleContainer>()));
      	 }
      catch (System.Exception ex)
      {
      }
  }
 }

//启动程序 语音生成 与 语音合成 也都达到了我想要的结果(亲测有效)
当前还有一些不完美的地方
后期打算把这段ArsSpeechHelper逻辑放到Services中通过后台服务来执行。

GitHub示例代码地址

android 项目示例代码:
ars语音识别示例项目
tts语音合成示例项目

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Doggie.0.0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值