Add C# API for FunASR Nano (#3031)

This commit is contained in:
Fangjun Kuang authored and GitHub committed 2026-01-12 18:19:48 +08:00
1 parent 95fbb44cd5
commit c19c471579
13 files changed
+323 -15

No files matched your search

+13 -1
View File
@@ -4,7 +4,19 @@ set -ex
cd dotnet-examples/
cd ./version-test
cd ./vad-non-streaming-funasr-nano
./run-ten-vad.sh
rm -fv *.onnx
./run.sh
rm -fv *.onnx
cd ../non-streaming-funasr-nano-decode-files
./run.sh
ls -lh
rm -rf sherpa-onnx-funasr-*
cd ../version-test
./run.sh
ls -lh
@@ -0,0 +1,35 @@
// Copyright (c) 2026 Xiaomi Corporation
//
// This file shows how to use a FunASR Nano model for speech recognition.
//
// You can find the model doc at
// https://k2-fsa.github.io/sherpa/onnx/funasr-nano.html
using SherpaOnnx;
class NonStreamingFunAsrNano
{
static void Main(string[] args)
{
// please download model files from
// https://github.com/k2-fsa/sherpa-onnx/releases/tag/asr-models
var config = new OfflineRecognizerConfig();
config.ModelConfig.FunAsrNano.EncoderAdaptor = "./sherpa-onnx-funasr-nano-int8-2025-12-30/encoder_adaptor.int8.onnx";
config.ModelConfig.FunAsrNano.LLM = "./sherpa-onnx-funasr-nano-int8-2025-12-30/llm.int8.onnx";
config.ModelConfig.FunAsrNano.Embedding = "./sherpa-onnx-funasr-nano-int8-2025-12-30/embedding.int8.onnx";
config.ModelConfig.FunAsrNano.Tokenizer = "./sherpa-onnx-funasr-nano-int8-2025-12-30/Qwen3-0.6B";
config.ModelConfig.Tokens = "";
config.ModelConfig.Debug = 1;
var recognizer = new OfflineRecognizer(config);
var testWaveFilename = "./sherpa-onnx-funasr-nano-int8-2025-12-30/test_wavs/lyrics.wav";
var reader = new WaveReader(testWaveFilename);
var stream = recognizer.CreateStream();
stream.AcceptWaveform(reader.SampleRate, reader.Samples);
recognizer.Decode(stream);
var text = stream.Result.Text;
Console.WriteLine("Text: {0}", text);
}
}
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>non_streaming_funasr_nano_decode_files</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Common\Common.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -ex
if [ ! -f ./sherpa-onnx-funasr-nano-int8-2025-12-30/embedding.int8.onnx ]; then
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-funasr-nano-int8-2025-12-30.tar.bz2
tar xvf sherpa-onnx-funasr-nano-int8-2025-12-30.tar.bz2
rm sherpa-onnx-funasr-nano-int8-2025-12-30.tar.bz2
fi
dotnet run
+12
View File
@@ -47,6 +47,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "kitten-tts-play", "kitten-t
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "offline-audio-tagging", "offline-audio-tagging\offline-audio-tagging.csproj", "{0EBE2CE5-8940-4472-8A38-6A0E976E678F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "non-streaming-funasr-nano-decode-files", "non-streaming-funasr-nano-decode-files\non-streaming-funasr-nano-decode-files.csproj", "{32F7534B-117E-4D1D-BAED-A1D1A6C6A62C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "vad-non-streaming-funasr-nano", "vad-non-streaming-funasr-nano\vad-non-streaming-funasr-nano.csproj", "{32C8C12B-D7DB-455E-B35C-945A745520CC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -141,6 +145,14 @@ Global
{0EBE2CE5-8940-4472-8A38-6A0E976E678F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0EBE2CE5-8940-4472-8A38-6A0E976E678F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0EBE2CE5-8940-4472-8A38-6A0E976E678F}.Release|Any CPU.Build.0 = Release|Any CPU
{32F7534B-117E-4D1D-BAED-A1D1A6C6A62C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{32F7534B-117E-4D1D-BAED-A1D1A6C6A62C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{32F7534B-117E-4D1D-BAED-A1D1A6C6A62C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{32F7534B-117E-4D1D-BAED-A1D1A6C6A62C}.Release|Any CPU.Build.0 = Release|Any CPU
{32C8C12B-D7DB-455E-B35C-945A745520CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{32C8C12B-D7DB-455E-B35C-945A745520CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{32C8C12B-D7DB-455E-B35C-945A745520CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{32C8C12B-D7DB-455E-B35C-945A745520CC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -0,0 +1,122 @@
// Copyright (c) 2026 Xiaomi Corporation
//
// This file shows how to use a silero_vad model or ten-vad model
// with a non-streaming FunASR Nano for speech recognition.
using SherpaOnnx;
using System.IO;
class VadNonStreamingFunAsrNano
{
static void Main(string[] args)
{
// please download model files from
// https://github.com/k2-fsa/sherpa-onnx/releases/tag/asr-models
var config = new OfflineRecognizerConfig();
config.ModelConfig.FunAsrNano.EncoderAdaptor = "./sherpa-onnx-funasr-nano-int8-2025-12-30/encoder_adaptor.int8.onnx";
config.ModelConfig.FunAsrNano.LLM = "./sherpa-onnx-funasr-nano-int8-2025-12-30/llm.int8.onnx";
config.ModelConfig.FunAsrNano.Embedding = "./sherpa-onnx-funasr-nano-int8-2025-12-30/embedding.int8.onnx";
config.ModelConfig.FunAsrNano.Tokenizer = "./sherpa-onnx-funasr-nano-int8-2025-12-30/Qwen3-0.6B";
config.ModelConfig.Tokens = "";
config.ModelConfig.Debug = 0;
var recognizer = new OfflineRecognizer(config);
var vadModelConfig = new VadModelConfig();
if (File.Exists("./silero_vad.onnx"))
{
Console.WriteLine("Use silero-vad");
vadModelConfig.SileroVad.Model = "./silero_vad.onnx";
vadModelConfig.SileroVad.Threshold = 0.3F;
vadModelConfig.SileroVad.MinSilenceDuration = 0.5F;
vadModelConfig.SileroVad.MinSpeechDuration = 0.25F;
vadModelConfig.SileroVad.MaxSpeechDuration = 5.0F;
vadModelConfig.SileroVad.WindowSize = 512;
}
else if (File.Exists("./ten-vad.onnx"))
{
Console.WriteLine("Use ten-vad");
vadModelConfig.TenVad.Model = "./ten-vad.onnx";
vadModelConfig.TenVad.Threshold = 0.3F;
vadModelConfig.TenVad.MinSilenceDuration = 0.5F;
vadModelConfig.TenVad.MinSpeechDuration = 0.25F;
vadModelConfig.TenVad.MaxSpeechDuration = 5.0F;
vadModelConfig.TenVad.WindowSize = 256;
}
else
{
Console.WriteLine("Please download ./silero_vad.onnx or ./ten-vad.onnx");
return;
}
vadModelConfig.Debug = 0;
var vad = new VoiceActivityDetector(vadModelConfig, 60);
var testWaveFilename = "./lei-jun-test.wav";
var reader = new WaveReader(testWaveFilename);
int numSamples = reader.Samples.Length;
int windowSize = vadModelConfig.SileroVad.WindowSize;
if (vadModelConfig.TenVad.Model != "")
{
windowSize = vadModelConfig.TenVad.WindowSize;
}
int sampleRate = vadModelConfig.SampleRate;
int numIter = numSamples / windowSize;
for (int i = 0; i != numIter; ++i)
{
int start = i * windowSize;
var samples = new float[windowSize];
Array.Copy(reader.Samples, start, samples, 0, windowSize);
vad.AcceptWaveform(samples);
if (vad.IsSpeechDetected())
{
while (!vad.IsEmpty())
{
SpeechSegment segment = vad.Front();
var startTime = segment.Start / (float)sampleRate;
var duration = segment.Samples.Length / (float)sampleRate;
OfflineStream stream = recognizer.CreateStream();
stream.AcceptWaveform(sampleRate, segment.Samples);
recognizer.Decode(stream);
var text = stream.Result.Text;
if (!string.IsNullOrEmpty(text))
{
Console.WriteLine("{0}--{1}: {2}", string.Format("{0:0.00}", startTime),
string.Format("{0:0.00}", startTime + duration), text);
}
vad.Pop();
}
}
}
vad.Flush();
while (!vad.IsEmpty())
{
var segment = vad.Front();
float startTime = segment.Start / (float)sampleRate;
float duration = segment.Samples.Length / (float)sampleRate;
var stream = recognizer.CreateStream();
stream.AcceptWaveform(sampleRate, segment.Samples);
recognizer.Decode(stream);
var text = stream.Result.Text;
if (!string.IsNullOrEmpty(text))
{
Console.WriteLine("{0}--{1}: {2}", string.Format("{0:0.00}", startTime),
string.Format("{0:0.00}", startTime + duration), text);
}
vad.Pop();
}
}
}
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
set -ex
if [ ! -f ./ten-vad.onnx ]; then
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/ten-vad.onnx
fi
if [ ! -f ./lei-jun-test.wav ]; then
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/lei-jun-test.wav
fi
if [ ! -f ./sherpa-onnx-funasr-nano-int8-2025-12-30/embedding.int8.onnx ]; then
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-funasr-nano-int8-2025-12-30.tar.bz2
tar xvf sherpa-onnx-funasr-nano-int8-2025-12-30.tar.bz2
rm sherpa-onnx-funasr-nano-int8-2025-12-30.tar.bz2
fi
dotnet run
+19
View File
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
set -ex
if [ ! -f ./silero_vad.onnx ]; then
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/silero_vad.onnx
fi
if [ ! -f ./lei-jun-test.wav ]; then
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/lei-jun-test.wav
fi
if [ ! -f ./sherpa-onnx-funasr-nano-int8-2025-12-30/embedding.int8.onnx ]; then
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-funasr-nano-int8-2025-12-30.tar.bz2
tar xvf sherpa-onnx-funasr-nano-int8-2025-12-30.tar.bz2
rm sherpa-onnx-funasr-nano-int8-2025-12-30.tar.bz2
fi
dotnet run
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>vad_non_streaming_funasr_nano</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Common\Common.csproj" />
</ItemGroup>
</Project>
+48
View File
@@ -0,0 +1,48 @@
/// Copyright (c) 2025 Xiaomi Corporation (authors: Fangjun Kuang)
using System.Runtime.InteropServices;
namespace SherpaOnnx
{
[StructLayout(LayoutKind.Sequential)]
public struct OfflineFunAsrNanoModelConfig
{
public OfflineFunAsrNanoModelConfig()
{
EncoderAdaptor = "";
LLM = "";
Embedding = "";
Tokenizer = "";
SystemPrompt = "You are a helpful assistant.";
UserPrompt = "语音转写:";
MaxNewTokens = 512;
Temperature = 1e-6F;
TopP = 0.8F;
Seed = 42;
}
[MarshalAs(UnmanagedType.LPStr)]
public string EncoderAdaptor;
[MarshalAs(UnmanagedType.LPStr)]
public string LLM;
[MarshalAs(UnmanagedType.LPStr)]
public string Embedding;
[MarshalAs(UnmanagedType.LPStr)]
public string Tokenizer;
[MarshalAs(UnmanagedType.LPStr)]
public string SystemPrompt;
[MarshalAs(UnmanagedType.LPStr)]
public string UserPrompt;
public int MaxNewTokens;
public float Temperature;
public float TopP;
public int Seed;
}
}
+2
View File
@@ -32,6 +32,7 @@ namespace SherpaOnnx
WenetCtc = new OfflineWenetCtcModelConfig();
Omnilingual = new OfflineOmnilingualAsrCtcModelConfig();
MedAsr = new OfflineMedAsrCtcModelConfig();
FunAsrNano = new OfflineFunAsrNanoModelConfig();
}
public OfflineTransducerModelConfig Transducer;
public OfflineParaformerModelConfig Paraformer;
@@ -70,5 +71,6 @@ namespace SherpaOnnx
public OfflineWenetCtcModelConfig WenetCtc;
public OfflineOmnilingualAsrCtcModelConfig Omnilingual;
public OfflineMedAsrCtcModelConfig MedAsr;
public OfflineFunAsrNanoModelConfig FunAsrNano;
}
}
+10 -12
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# Copyright (c) 2023 Xiaomi Corporation
import argparse
import glob
import os
import re
from pathlib import Path
@@ -28,7 +28,6 @@ def read_proj_file(filename):
def get_dict():
version = get_version()
return {
"version": get_version(),
}
@@ -55,17 +54,18 @@ def process_linux(s, rid):
def process_macos(s, rid):
libs = [
"libonnxruntime.1.17.1.dylib",
"libsherpa-onnx-c-api.dylib",
]
prefix = f"{src_dir}/macos-{rid}/"
libs = [prefix + lib for lib in libs]
libs = "\n ;".join(libs)
lib_dir = os.path.join(src_dir, f"macos-{rid}")
onnx_libs = glob.glob(os.path.join(lib_dir, "libonnxruntime*.dylib"))
if not onnx_libs:
raise FileNotFoundError(f"No libonnxruntime*.dylib found in {lib_dir}")
other_libs = [os.path.join(lib_dir, "libsherpa-onnx-c-api.dylib")]
libs = onnx_libs + other_libs
libs_str = "\n ;".join(libs)
d = get_dict()
d["dotnet_rid"] = f"osx-{rid}"
d["libs"] = libs
d["libs"] = libs_str
environment = jinja2.Environment()
template = environment.from_string(s)
@@ -80,8 +80,6 @@ def process_windows(s, rid):
"sherpa-onnx-c-api.dll",
]
version = get_version()
prefix = f"{src_dir}/windows-{rid}/"
libs = [prefix + lib for lib in libs]
libs = "\n ;".join(libs)
+2 -2
View File
@@ -94,7 +94,7 @@ function osx() {
cp -v sherpa_onnx/lib/*.dylib $dst/
pushd $dst
cp -v libonnxruntime.1.17.1.dylib libonnxruntime.dylib
cp -v libonnxruntime.*.dylib libonnxruntime.dylib
popd
cd ..
@@ -112,7 +112,7 @@ function osx() {
cp -v sherpa_onnx/lib/*.dylib $dst/
pushd $dst
cp -v libonnxruntime.1.17.1.dylib libonnxruntime.dylib
cp -v libonnxruntime.*.dylib libonnxruntime.dylib
popd
cd ..