diff --git a/.github/scripts/test-nodejs-addon-npm.sh b/.github/scripts/test-nodejs-addon-npm.sh index 938e447595..8317ac3be7 100755 --- a/.github/scripts/test-nodejs-addon-npm.sh +++ b/.github/scripts/test-nodejs-addon-npm.sh @@ -10,6 +10,15 @@ arch=$(node -p "require('os').arch()") platform=$(node -p "require('os').platform()") node_version=$(node -p "process.versions.node.split('.')[0]") +echo "----------non-streaming ASR FunASR Nano----------" + +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 + +node ./test_asr_non_streaming_funasr_nano.js +rm -rf sherpa-onnx-funasr-nano-int8-2025-12-30 + echo "----------non-streaming ASR Google MedASR CTC----------" wget https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-medasr-ctc-en-int8-2025-12-25.tar.bz2 diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/non-streaming-asr.cc b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/non-streaming-asr.cc index 6996d0798f..1513b71751 100644 --- a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/non-streaming-asr.cc +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/non-streaming-asr.cc @@ -110,6 +110,31 @@ static SherpaOnnxOfflineMedAsrCtcModelConfig GetOfflineMedAsrCtcModelConfig( return c; } +static SherpaOnnxOfflineFunASRNanoModelConfig GetOfflineFunAsrNanoModelConfig( + Napi::Object obj) { + SherpaOnnxOfflineFunASRNanoModelConfig c; + memset(&c, 0, sizeof(c)); + + if (!obj.Has("funasrNano") || !obj.Get("funasrNano").IsObject()) { + return c; + } + + Napi::Object o = obj.Get("funasrNano").As(); + + SHERPA_ONNX_ASSIGN_ATTR_STR(encoder_adaptor, encoderAdaptor); + SHERPA_ONNX_ASSIGN_ATTR_STR(llm, llm); + SHERPA_ONNX_ASSIGN_ATTR_STR(embedding, embedding); + SHERPA_ONNX_ASSIGN_ATTR_STR(tokenizer, tokenizer); + SHERPA_ONNX_ASSIGN_ATTR_STR(system_prompt, systemPrompt); + SHERPA_ONNX_ASSIGN_ATTR_STR(user_prompt, userPrompt); + SHERPA_ONNX_ASSIGN_ATTR_INT32(max_new_tokens, maxNewTokens); + SHERPA_ONNX_ASSIGN_ATTR_FLOAT(temperature, temperature); + SHERPA_ONNX_ASSIGN_ATTR_FLOAT(top_p, topP); + SHERPA_ONNX_ASSIGN_ATTR_INT32(seed, seed); + + return c; +} + static SherpaOnnxOfflineDolphinModelConfig GetOfflineDolphinModelConfig( Napi::Object obj) { SherpaOnnxOfflineDolphinModelConfig c; @@ -277,6 +302,7 @@ static SherpaOnnxOfflineModelConfig GetOfflineModelConfig(Napi::Object obj) { c.wenet_ctc = GetOfflineWenetCtcModelConfig(o); c.omnilingual = GetOfflineOmnilingualAsrCtcModelConfig(o); c.medasr = GetOfflineMedAsrCtcModelConfig(o); + c.funasr_nano = GetOfflineFunAsrNanoModelConfig(o); SHERPA_ONNX_ASSIGN_ATTR_STR(tokens, tokens); SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads); @@ -373,6 +399,13 @@ static void FreeConfig(const SherpaOnnxOfflineRecognizerConfig &c) { SHERPA_ONNX_DELETE_C_STR(c.model_config.omnilingual.model); SHERPA_ONNX_DELETE_C_STR(c.model_config.medasr.model); + SHERPA_ONNX_DELETE_C_STR(c.model_config.funasr_nano.user_prompt); + SHERPA_ONNX_DELETE_C_STR(c.model_config.funasr_nano.system_prompt); + SHERPA_ONNX_DELETE_C_STR(c.model_config.funasr_nano.tokenizer); + SHERPA_ONNX_DELETE_C_STR(c.model_config.funasr_nano.embedding); + SHERPA_ONNX_DELETE_C_STR(c.model_config.funasr_nano.llm); + SHERPA_ONNX_DELETE_C_STR(c.model_config.funasr_nano.encoder_adaptor); + SHERPA_ONNX_DELETE_C_STR(c.model_config.tokens); SHERPA_ONNX_DELETE_C_STR(c.model_config.provider); SHERPA_ONNX_DELETE_C_STR(c.model_config.model_type); diff --git a/nodejs-addon-examples/README.md b/nodejs-addon-examples/README.md index c6afe1ed74..52151e0292 100644 --- a/nodejs-addon-examples/README.md +++ b/nodejs-addon-examples/README.md @@ -127,6 +127,7 @@ The following tables list the examples in this folder. |[./test_asr_non_streaming_wenet_ctc.js](./test_asr_non_streaming_wenet_ctc.js)|Non-streaming speech recognition from a file using a [u2pp_conformer_yue](https://huggingface.co/ASLP-lab/WSYue-ASR/tree/main/u2pp_conformer_yue) CTC model with greedy search| |[./test_asr_non_streaming_omnilingual_asr_ctc.js](./test_asr_non_streaming_omnilingual_asr_ctc.js)|Non-streaming speech recognition from a file using a [Omnilingual-ASR](https://github.com/facebookresearch/omnilingual-asr) CTC model with greedy search| |[./test_asr_non_streaming_medasr_ctc.js](./test_asr_non_streaming_medasr_ctc.js)|Non-streaming speech recognition from a file using a [Google MedASR](https://github.com/google-health/medasr) CTC model with greedy search| +|[./test_asr_non_streaming_funasr_nano.js](./test_asr_non_streaming_funasr_nano.js)|Non-streaming speech recognition from a file using a [FunASR Nano](https://modelscope.cn/models/FunAudioLLM/Fun-ASR-Nano-2512) model| |[./test_asr_non_streaming_nemo_canary.js](./test_asr_non_streaming_nemo_canary.js)|Non-streaming speech recognition from a file using a [NeMo](https://github.com/NVIDIA/NeMo) [Canary](https://k2-fsa.github.io/sherpa/onnx/nemo/canary.html#sherpa-onnx-nemo-canary-180m-flash-en-es-de-fr-int8-english-spanish-german-french) model| |[./test_asr_non_streaming_zipformer_ctc.js](./test_asr_non_streaming_zipformer_ctc.js)|Non-streaming speech recognition from a file using a Zipformer CTC model with greedy search| |[./test_asr_non_streaming_nemo_parakeet_tdt_v2.js](./test_asr_non_streaming_nemo_parakeet_tdt_v2.js)|Non-streaming speech recognition from a file using a [NeMo](https://github.com/NVIDIA/NeMo) [parakeet-tdt-0.6b-v2](https://k2-fsa.github.io/sherpa/onnx/pretrained_models/offline-transducer/nemo-transducer-models.html#sherpa-onnx-nemo-parakeet-tdt-0-6b-v2-int8-english) model with greedy search| @@ -429,6 +430,16 @@ npm install naudiodon2 node ./test_vad_asr_non_streaming_nemo_ctc_microphone.js ``` +### Non-streaming speech recognition with FunASR Nano models + +```bash +wget 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 + +node ./test_asr_non_streaming_funasr_nano.js +``` + ### Non-streaming speech recognition with Google MedASR CTC models ```bash diff --git a/nodejs-addon-examples/test_asr_non_streaming_funasr_nano.js b/nodejs-addon-examples/test_asr_non_streaming_funasr_nano.js new file mode 100644 index 0000000000..069712ad01 --- /dev/null +++ b/nodejs-addon-examples/test_asr_non_streaming_funasr_nano.js @@ -0,0 +1,51 @@ +// Copyright (c) 2026 Xiaomi Corporation +const sherpa_onnx = require('sherpa-onnx-node'); + +// Please download test files from +// https://github.com/k2-fsa/sherpa-onnx/releases/tag/asr-models +const config = { + 'featConfig': { + 'sampleRate': 16000, + 'featureDim': 80, + }, + 'modelConfig': { + 'funasrNano': { + 'encoderAdaptor': + './sherpa-onnx-funasr-nano-int8-2025-12-30/encoder_adaptor.int8.onnx', + 'llm': './sherpa-onnx-funasr-nano-int8-2025-12-30/llm.int8.onnx', + 'embedding': + './sherpa-onnx-funasr-nano-int8-2025-12-30/embedding.int8.onnx', + 'tokenizer': './sherpa-onnx-funasr-nano-int8-2025-12-30/Qwen3-0.6B', + }, + 'tokens': '', + 'numThreads': 2, + 'provider': 'cpu', + 'debug': 1, + } +}; + +const waveFilename = + './sherpa-onnx-funasr-nano-int8-2025-12-30/test_wavs/lyrics.wav'; + +const recognizer = new sherpa_onnx.OfflineRecognizer(config); +console.log('Started') +let start = Date.now(); +const stream = recognizer.createStream(); +const wave = sherpa_onnx.readWave(waveFilename); +stream.acceptWaveform({sampleRate: wave.sampleRate, samples: wave.samples}); + +recognizer.decode(stream); +const result = recognizer.getResult(stream); +let stop = Date.now(); +console.log('Done') + +const elapsed_seconds = (stop - start) / 1000; +const duration = wave.samples.length / wave.sampleRate; +const real_time_factor = elapsed_seconds / duration; +console.log('Wave duration', duration.toFixed(3), 'seconds') +console.log('Elapsed', elapsed_seconds.toFixed(3), 'seconds') +console.log( + `RTF = ${elapsed_seconds.toFixed(3)}/${duration.toFixed(3)} =`, + real_time_factor.toFixed(3)) +console.log(waveFilename) +console.log('result\n', result)