Skip to content

Conversation

@csukuangfj
Copy link
Collaborator

@csukuangfj csukuangfj commented Sep 10, 2025

Summary by CodeRabbit

  • New Features

    • Added offline Wenet CTC model support and examples across SDKs/bindings (C/C++, Java, Kotlin, Swift, Go, Dart, Node.js, Pascal, HarmonyOS, WASM) enabling non-streaming Cantonese ASR.
  • Documentation

    • Added README and example docs for Wenet CTC usage across platforms.
  • Tests

    • Expanded CI and test scripts to run Wenet CTC tests alongside existing models on multiple workflows.
  • Chores

    • Updated ignore lists and APK/script generators to include the new Wenet CTC model.

@csukuangfj csukuangfj requested a review from Copilot September 10, 2025 10:17
@coderabbitai
Copy link

coderabbitai bot commented Sep 10, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Caution

Review failed

The pull request is closed.

Walkthrough

Adds Wenet CTC offline-model support across core C/C++ APIs, language bindings, examples, CI scripts, and tooling. Introduces new Wenet CTC config structs/fields, wiring and memory management in bindings, example programs and runners, model download entries, and related gitignore/docs updates.

Changes

Cohort / File(s) Summary
Core C/C++ API: public structs & wiring
sherpa-onnx/c-api/c-api.h, sherpa-onnx/c-api/c-api.cc, sherpa-onnx/c-api/cxx-api.h, sherpa-onnx/c-api/cxx-api.cc, sherpa-onnx/c-api/c-api.cc
Add OfflineWenetCtcModelConfig / wenet_ctc field to OfflineModelConfig and wire the model path into conversion logic.
WASM / Node.js-WASM
wasm/asr/sherpa-onnx-asr.js, wasm/nodejs/sherpa-onnx-wasm-nodejs.cc
Pack/unpack wenetCtc config, include its size in buffers, copy/free memory, add size static_assert and print output for wenet_ctc.
Bindings: language support & interop
scripts/go/sherpa_onnx.go, scripts/dotnet/OfflineModelConfig.cs, scripts/dotnet/OfflineWenetCtcModelConfig.cs, flutter/sherpa_onnx/lib/src/sherpa_onnx_bindings.dart, flutter/sherpa_onnx/lib/src/offline_recognizer.dart, sherpa-onnx/pascal-api/sherpa_onnx.pas, sherpa-onnx/kotlin-api/OfflineRecognizer.kt, sherpa-onnx/java-api/src/.../OfflineModelConfig.java, sherpa-onnx/java-api/src/.../OfflineWenetCtcModelConfig.java, sherpa-onnx/jni/offline-recognizer.cc, sherpa-onnx/jni/offline-recognizer.cc
Add OfflineWenetCtcModelConfig types/fields across Go/.NET/Flutter/Pascal/Kotlin/Java bindings; convert and free native pointers; parse wenetCtc in JNI.
Language examples & CLIs
c-api-examples/wenet-ctc-c-api.c, cxx-api-examples/wenet-ctc-cxx-api.cc, cxx-api-examples/wenet-ctc-simulate-streaming-microphone-cxx-api.cc, dart-api-examples/.../wenet-ctc.dart, dart-api-examples/.../run-wenet-ctc.sh, dotnet-examples/.../Program.cs, dotnet-examples/.../run-wenet-ctc.sh, go-api-examples/.../main.go, go-api-examples/.../run-wenet-ctc.sh, java-api-examples/NonStreamingDecodeFileWenetCtc.java, java-api-examples/run-non-streaming-decode-file-wenet-ctc.sh, nodejs-examples/test-offline-wenet-ctc.js, nodejs-addon-examples/test_asr_non_streaming_wenet_ctc.js, nodejs-addon-examples/README.md, nodejs-examples/README.md, kotlin-api-examples/test_offline_wenet_ctc.kt, kotlin-api-examples/run.sh, pascal-api-examples/non-streaming-asr/wenet_ctc.pas
Add Wenet CTC example programs and runner scripts for many languages, CLI options (e.g., --wenet-ctc), and example-specific utilities.
Build targets & CMake
c-api-examples/CMakeLists.txt, cxx-api-examples/CMakeLists.txt
Add wenet-ctc example executables and link them to sherpa-onnx libraries (PortAudio demo included).
CI / workflow additions
.github/workflows/c-api.yaml, .github/workflows/cxx-api.yaml, .github/workflows/test-go.yaml
Add "Test Wenet CTC" steps that build/execute examples, download/extract models, set env vars, and clean up.
Test scripts / runners
.github/scripts/test-dart.sh, .github/scripts/test-dot-net.sh, .github/scripts/test-nodejs-addon-npm.sh, .github/scripts/test-nodejs-npm.sh, .github/scripts/test-swift.sh
Insert run-wenet-ctc invocations (and cleanup rm -rf sherpa-onnx-*) into existing test flows, preserving subsequent tests.
Swift examples & runners
swift-api-examples/SherpaOnnx.swift, swift-api-examples/wenet-ctc-asr.swift, swift-api-examples/run-wenet-ctc-asr.sh, swift-api-examples/.gitignore
Add Swift helper for wenetCtc, an example app, runner script, and ignore entry.
HarmonyOS (ETS & native parsing)
harmony-os/.../Index.ets, .../NonStreamingAsr.ets, .../non-streaming-asr.cc
Export OfflineWenetCtcModelConfig in ETS, add field to OfflineModelConfig, parse wenetCtc in native C++ code, and free string.
APK generation scripts
scripts/apk/generate-asr-2pass-apk-script.py, scripts/apk/generate-vad-asr-apk-script.py
Add new Wenet CTC model entry, include model in combinations, minor popd fix and adjust cleanup commands.
Gitignore & minor docs
.gitignore, pascal-api-examples/non-streaming-asr/.gitignore, nodejs-addon-examples/README.md
Add ignored model dirs, README entries for Wenet tests (some duplicate insertions).
WASM high-impact change
wasm/asr/sherpa-onnx-asr.js (see above)
Significant buffer layout/copy/free updates and new helper for wenetCtc; includes memory-management adjustments.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant App as Language App / Example
  participant Binding as Language Binding
  participant Native as Native C/C++ API
  participant Rec as OfflineRecognizer
  participant Model as Wenet CTC ONNX

  User->>App: supply model, tokens, WAV
  App->>Binding: build OfflineModelConfig (wenetCtc.model)
  Binding->>Native: Convert config (set wenet_ctc.model pointer)
  Native->>Rec: create OfflineRecognizer with model config
  App->>Rec: create OfflineStream
  App->>Rec: acceptWaveform(samples, sr)
  Rec->>Model: run greedy_search decode
  Model-->>Rec: return text
  Rec-->>App: getResult(text)
  App-->>User: print transcription
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

size:XXL

Suggested reviewers

  • Copilot

Poem

A whisker twitch, a hop of glee,
I fetched a model, set model=“wenet”, see?
From C to Swift I stitched the thread,
I nibble bytes and wake the thread.
Cantonese waves sing — I decode with glee.

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cefba26 and 75fc687.

📒 Files selected for processing (2)
  • .github/workflows/test-go.yaml (1 hunks)
  • nodejs-addon-examples/README.md (2 hunks)
✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@dosubot dosubot bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Sep 10, 2025

This comment was marked as outdated.

@csukuangfj csukuangfj requested a review from Copilot September 10, 2025 10:22
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds support for Wenet non-streaming CTC models to sherpa-onnx by introducing the SherpaOnnxOfflineWenetCtcModelConfig struct and integrating it across all language bindings and APIs. The implementation follows the same pattern as other CTC model types like Zipformer CTC.

  • Introduces SherpaOnnxOfflineWenetCtcModelConfig struct with a single model field for the ONNX model path
  • Adds the new config to SherpaOnnxOfflineModelConfig and updates all language bindings (C++, Pascal, Kotlin, Java, Go, C#, Swift, JavaScript, etc.)
  • Provides comprehensive examples and tests across all supported platforms and languages

Reviewed Changes

Copilot reviewed 56 out of 58 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
wasm/nodejs/sherpa-onnx-wasm-nodejs.cc Adds static assertions and debug output for Wenet CTC config
wasm/asr/sherpa-onnx-asr.js Implements JavaScript initialization and memory management for Wenet CTC
swift-api-examples/wenet-ctc-asr.swift Swift example demonstrating Wenet CTC usage
swift-api-examples/SherpaOnnx.swift Swift API bindings for Wenet CTC configuration
sherpa-onnx/pascal-api/sherpa_onnx.pas Pascal language bindings and type definitions
sherpa-onnx/kotlin-api/OfflineRecognizer.kt Kotlin data classes and model configuration
sherpa-onnx/jni/offline-recognizer.cc JNI implementation for Android/Java integration
sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineWenetCtcModelConfig.java Java model configuration class
sherpa-onnx/c-api/c-api.h C API struct definition for Wenet CTC
scripts/go/sherpa_onnx.go Go language bindings and configuration
Various example files Comprehensive examples across all supported languages and platforms

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines 22 to 37
// now output text in German
val config = recognizer.config.copy(modelConfig=recognizer.config.modelConfig.copy(
canary=recognizer.config.modelConfig.canary.copy(
tgtLang="de"
)
))
recognizer.setConfig(config)

stream = recognizer.createStream()
stream.acceptWaveform(samples, sampleRate=sampleRate)
recognizer.decode(stream)

result = recognizer.getResult(stream)
println("German: $result")

stream.release()
Copy link

Copilot AI Sep 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is attempting to use Canary model configuration with a Wenet CTC model, which is incorrect. Wenet CTC models don't support language switching like Canary models. This section should be removed or modified to demonstrate proper Wenet CTC functionality.

Suggested change
// now output text in German
val config = recognizer.config.copy(modelConfig=recognizer.config.modelConfig.copy(
canary=recognizer.config.modelConfig.canary.copy(
tgtLang="de"
)
))
recognizer.setConfig(config)
stream = recognizer.createStream()
stream.acceptWaveform(samples, sampleRate=sampleRate)
recognizer.decode(stream)
result = recognizer.getResult(stream)
println("German: $result")
stream.release()

Copilot uses AI. Check for mistakes.
Comment on lines 187 to +194
rm -fv *.py
ls -lh
popd
""",
),
Model(
Copy link

Copilot AI Sep 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are missing lines in the cmd section. The removal of model.onnx and *.py files appears to be left over from a previous model configuration and should be removed since this is for Wenet CTC which uses model.int8.onnx.

Copilot uses AI. Check for mistakes.
Comment on lines 17 to +19
OfflineMoonshineModelConfig,
OfflineSenseVoiceModelConfig,
OfflineWenetCtcModelConfig,
Copy link

Copilot AI Sep 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The export order is inconsistent. OfflineMoonshineModelConfig and OfflineSenseVoiceModelConfig should be moved after OfflineWenetCtcModelConfig to maintain alphabetical ordering of the CTC model configs.

Suggested change
OfflineMoonshineModelConfig,
OfflineSenseVoiceModelConfig,
OfflineWenetCtcModelConfig,
OfflineWenetCtcModelConfig,
OfflineMoonshineModelConfig,
OfflineSenseVoiceModelConfig,

Copilot uses AI. Check for mistakes.
@csukuangfj
Copy link
Collaborator Author

@coderabbitai please review.

@coderabbitai
Copy link

coderabbitai bot commented Sep 10, 2025

@csukuangfj I'll review the changes in this PR that adds various language bindings for Wenet non-streaming CTC models.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
scripts/go/_internal/non-streaming-decode-files/run-wenet-ctc.sh (1)

1-19: Resolve duplicate execution path and add a shebang.
This file both (a) tries to delegate to the example script and (b) re-implements the workflow inline. Keep a single source of truth and make this a thin wrapper with a proper shebang.

Apply:

-../../../../go-api-examples/non-streaming-decode-files/run-wenet-ctc.sh
-
-set -ex
-
-if [ ! -f sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10/model.int8.onnx ]; then
-  curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2
-  tar xvf sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2
-  rm sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2
-fi
-
-go mod tidy
-go build
-
-./non-streaming-decode-files \
-  --wenet-ctc ./sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10/model.int8.onnx \
-  --tokens ./sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10/tokens.txt \
-  --debug 0 \
-  ./sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10/test_wavs/yue-0.wav
+#!/usr/bin/env bash
+set -Eeuo pipefail
+set -x
+exec bash ../../../../go-api-examples/non-streaming-decode-files/run-wenet-ctc.sh
sherpa-onnx/c-api/cxx-api.cc (4)

152-155: Fix index increment bug in multi-stream decode (online).
The loop increments n instead of i, causing a logic error/possible hang.

Apply:

-  for (int32_t i = 0; i != n; ++n) {
+  for (int32_t i = 0; i != n; ++i) {
     streams[i] = ss[i].Get();
   }

306-308: Destroy uses p_ instead of the provided pointer.
This bypasses MoveOnly’s handoff and risks double-free or leak patterns in edge cases.

Apply:

-void OfflineRecognizer::Destroy(const SherpaOnnxOfflineRecognizer *p) const {
-  SherpaOnnxDestroyOfflineRecognizer(p_);
-}
+void OfflineRecognizer::Destroy(const SherpaOnnxOfflineRecognizer *p) const {
+  SherpaOnnxDestroyOfflineRecognizer(p);
+}

564-566: Remove invalid return in a void function.
Returning a value from a void function is ill-formed in C++.

Apply:

-void KeywordSpotter::Decode(const OnlineStream *s) const {
-  return SherpaOnnxDecodeKeywordStream(p_, s->Get());
-}
+void KeywordSpotter::Decode(const OnlineStream *s) const {
+  SherpaOnnxDecodeKeywordStream(p_, s->Get());
+}

573-576: Fix index increment bug in multi-stream decode (keyword spotter).
Same issue as the online decode path.

Apply:

-  for (int32_t i = 0; i != n; ++n) {
+  for (int32_t i = 0; i != n; ++i) {
     streams[i] = ss[i].Get();
   }
♻️ Duplicate comments (3)
scripts/apk/generate-asr-2pass-apk-script.py (1)

191-192: Resolved: cmd block now closes properly with popd

This addresses the earlier “missing lines in the cmd section” note. One follow-up: since the 2025-09-09 SenseVoice tarball may still ship a float model alongside int8, consider keeping a harmless rm -fv model.onnx here to avoid bloating the APK if present.

Would you confirm whether that archive contains a float model.onnx?

harmony-os/SherpaOnnxHar/sherpa_onnx/Index.ets (1)

18-20: Export list reordering — pick and document a rule.

This reorders exports and adds OfflineWenetCtcModelConfig. Please confirm the reordering is intentional and adopt a consistent rule (e.g., alphabetical or minimal-diff) to reduce churn.

flutter/sherpa_onnx/lib/src/offline_recognizer.dart (1)

447-447: Make toString maintainable and readable (previous feedback still applies)

This very long single-line toString is brittle and easy to forget updating when new fields (like wenetCtc) are added. Prefer serializing to JSON or formatting multi-line.

Apply:

-  @override
-  String toString() {
-    return 'OfflineModelConfig(transducer: $transducer, paraformer: $paraformer, nemoCtc: $nemoCtc, whisper: $whisper, tdnn: $tdnn, senseVoice: $senseVoice, moonshine: $moonshine, fireRedAsr: $fireRedAsr, dolphin: $dolphin, zipformerCtc: $zipformerCtc, canary: $canary, wenetCtc: $wenetCtc, tokens: $tokens, numThreads: $numThreads, debug: $debug, provider: $provider, modelType: $modelType, modelingUnit: $modelingUnit, bpeVocab: $bpeVocab, telespeechCtc: $telespeechCtc)';
-  }
+  @override
+  String toString() => 'OfflineModelConfig(${jsonEncode(toJson())})';
🧹 Nitpick comments (41)
scripts/apk/generate-asr-2pass-apk-script.py (2)

195-199: Short name typo: missing ‘p’ in u2pp

Model name uses u2pp, but short_name says u2p. Align to avoid confusion and accidental collisions in artifacts.

-            short_name="wenetspeech_yue_u2pconformer_ctc_2025_09_10_int8",
+            short_name="wenetspeech_yue_u2pp_conformer_ctc_2025_09_10_int8",

Please check any downstream usage expecting the short_name string.


199-205: Quote model_name in pushd for safety

Model names are long and could contain shell-sensitive chars in future. Quoting is a low-risk hardening.

-            pushd $model_name
+            pushd "$model_name"
.gitignore (1)

154-154: Prefer a wildcard for model folders to avoid date churn

Use a pattern so future date-stamped drops are ignored without editing .gitignore again.

Apply:

-sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10
+sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-*
go-api-examples/non-streaming-decode-files/main.go (1)

32-32: New --wenet-ctc flag wiring — LGTM; add guards to avoid conflicting model flags

Flag works, but multiple model flags can be set simultaneously, causing ambiguous config. Add a check after flag.Parse() to ensure exactly one offline model is provided (and that --tokens is set) to fail-fast with a clear message.

Example snippet to add after flag.Parse():

models := []string{
  config.ModelConfig.ZipformerCtc.Model,
  config.ModelConfig.WenetCtc.Model,
  config.ModelConfig.NemoCTC.Model,
  config.ModelConfig.Paraformer.Model,
  config.ModelConfig.Dolphin.Model,
  config.ModelConfig.FireRedAsr.Encoder, // paired with Decoder
}
nonEmpty := 0
for _, m := range models { if m != "" { nonEmpty++ } }
if nonEmpty != 1 {
  log.Fatalf("Specify exactly one offline model (use one of --zipformer-ctc, --wenet-ctc, --nemo-ctc, --paraformer, --dolphin-model or FireRedAsr pair). Got %d.", nonEmpty)
}
if config.ModelConfig.Tokens == "" {
  log.Fatalf("Please provide --tokens for the selected model")
}
nodejs-examples/test-offline-wenet-ctc.js (1)

3-6: Remove unused imports

fs, stream.Readable, and wav aren’t used. Trim to keep the sample minimal.

-const fs = require('fs');
-const {Readable} = require('stream');
-const wav = require('wav');
+// No extra imports needed
nodejs-examples/README.md (1)

206-221: Add Wenet CTC example docs — LGTM

Instructions match the test script and model naming. Consider mirroring the download style used elsewhere (wget vs curl) for consistency, but not required.

.github/scripts/test-nodejs-npm.sh (1)

12-18: Harden downloads with curl --fail for early exits

If the asset URL 404s, curl may still exit 0; add --fail to fail fast.

-curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2
+curl -fSL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2
 tar xvf sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2
 rm sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2

 node ./test-offline-wenet-ctc.js
 rm -rf sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10
sherpa-onnx/c-api/c-api.h (1)

479-482: New Wenet CTC config type looks good; consider adding a short doc comment.

Add a brief comment (download link / expected files) like other model config structs for consistency and discoverability.

go-api-examples/non-streaming-decode-files/run-wenet-ctc.sh (1)

1-19: Harden the script (quoting and stricter bash options).

  • Use stricter shell options and quote paths/URLs.

Apply:

-#!/usr/bin/env bash
-set -ex
+#!/usr/bin/env bash
+set -Eeuo pipefail
+IFS=$'\n\t'
@@
-if [ ! -f sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10/model.int8.onnx ]; then
-  curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2
-  tar xvf sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2
-  rm sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2
+if [ ! -f "sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10/model.int8.onnx" ]; then
+  curl -fSL -O "https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2"
+  tar xvf "sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2"
+  rm -f "sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2"
 fi
@@
-./non-streaming-decode-files \
-  --wenet-ctc ./sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10/model.int8.onnx \
-  --tokens ./sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10/tokens.txt \
+./non-streaming-decode-files \
+  --wenet-ctc "./sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10/model.int8.onnx" \
+  --tokens "./sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10/tokens.txt" \
   --debug 0 \
-  ./sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10/test_wavs/yue-0.wav
+  "./sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10/test_wavs/yue-0.wav"
scripts/go/sherpa_onnx.go (1)

587-588: Populates C config for wenet_ctc — LGTM; consider nil vs empty semantics.

If empty path is meaningful, you may pass nil instead of empty string for some C callers. Current pattern uses empty strings elsewhere, so keeping consistent is fine.

scripts/dotnet/OfflineWenetCtcModelConfig.cs (1)

1-19: Prefer UTF-8 marshalling and avoid struct ctor if targeting older C#/.NET.

  • File paths can contain non-ASCII; use UTF-8 to match C side expectations.
  • Parameterless struct constructors require modern C#; not strictly needed here.

Apply:

-    [StructLayout(LayoutKind.Sequential)]
+    [StructLayout(LayoutKind.Sequential)]
     public struct OfflineWenetCtcModelConfig
     {
-        public OfflineWenetCtcModelConfig()
-        {
-            Model = "";
-        }
-        [MarshalAs(UnmanagedType.LPStr)]
+        // NOTE: Switch to LPUTF8Str if other structs already use UTF-8.
+        [MarshalAs(UnmanagedType.LPUTF8Str)]
         public string Model;
     }

If the rest of the binding uses LPStr intentionally, keep it consistent and skip the change.

pascal-api-examples/non-streaming-asr/wenet_ctc.pas (1)

35-76: Add basic error handling and guaranteed cleanup.

  • Check that the wave loaded successfully before decoding.
  • Use try/finally to always free Stream/Recognizer.

Apply:

   Wave := SherpaOnnxReadWave(WaveFilename);
-  Recognizer := TSherpaOnnxOfflineRecognizer.Create(Config);
-  Stream := Recognizer.CreateStream();
-  Start := Now;
+  if (Wave.SampleRate = 0) or (Length(Wave.Samples) = 0) then
+  begin
+    WriteLn('Failed to read wave or empty audio: ', WaveFilename);
+    Exit;
+  end;
+
+  Recognizer := TSherpaOnnxOfflineRecognizer.Create(Config);
+  try
+    Stream := Recognizer.CreateStream();
+    try
+      Start := Now;
@@
-  FreeAndNil(Stream);
-  FreeAndNil(Recognizer);
+    finally
+      FreeAndNil(Stream);
+    end;
+  finally
+    FreeAndNil(Recognizer);
+  end;

If TSherpaOnnxWave requires explicit disposal in your wrapper, free it as well.

pascal-api-examples/non-streaming-asr/run-wenet-ctc.sh (1)

1-43: Tighten script robustness and portability.

  • Use stricter shell options; quote paths; parallelize build.

Apply:

-#!/usr/bin/env bash
-set -ex
+#!/usr/bin/env bash
+set -Eeuo pipefail
+IFS=$'\n\t'
@@
-SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
-SHERPA_ONNX_DIR=$(cd $SCRIPT_DIR/../.. && pwd)
+SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
+SHERPA_ONNX_DIR="$(cd "$SCRIPT_DIR"/../.. && pwd)"
@@
-if [[ ! -f ../../build/install/lib/libsherpa-onnx-c-api.dylib  && ! -f ../../build/install/lib/libsherpa-onnx-c-api.so && ! -f ../../build/install/lib/sherpa-onnx-c-api.dll ]]; then
-  mkdir -p ../../build
-  pushd ../../build
+if [[ ! -f ../../build/install/lib/libsherpa-onnx-c-api.dylib  && ! -f ../../build/install/lib/libsherpa-onnx-c-api.so && ! -f ../../build/install/lib/sherpa-onnx-c-api.dll ]]; then
+  mkdir -p ../../build
+  pushd ../../build
   cmake \
     -DCMAKE_INSTALL_PREFIX=./install \
@@
-  cmake --build . --target install --config Release
+  cmake --build . --target install --config Release -- -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 2)"
   ls -lh lib
   popd
 fi
@@
-if [ ! -f sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10/model.int8.onnx ]; then
-  curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2
-  tar xvf sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2
-  rm sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2
+if [ ! -f "sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10/model.int8.onnx" ]; then
+  curl -fSL -O "https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2"
+  tar xvf "sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2"
+  rm -f "sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2"
 fi
@@
-fpc \
+fpc \
   -dSHERPA_ONNX_USE_SHARED_LIBS \
-  -Fu$SHERPA_ONNX_DIR/sherpa-onnx/pascal-api \
-  -Fl$SHERPA_ONNX_DIR/build/install/lib \
+  -Fu"$SHERPA_ONNX_DIR/sherpa-onnx/pascal-api" \
+  -Fl"$SHERPA_ONNX_DIR/build/install/lib" \
   ./wenet_ctc.pas
@@
-export LD_LIBRARY_PATH=$SHERPA_ONNX_DIR/build/install/lib:$LD_LIBRARY_PATH
-export DYLD_LIBRARY_PATH=$SHERPA_ONNX_DIR/build/install/lib:$DYLD_LIBRARY_PATH
+export LD_LIBRARY_PATH="$SHERPA_ONNX_DIR/build/install/lib:${LD_LIBRARY_PATH:-}"
+export DYLD_LIBRARY_PATH="$SHERPA_ONNX_DIR/build/install/lib:${DYLD_LIBRARY_PATH:-}"
.github/scripts/test-dart.sh (1)

73-76: Narrow cleanup to model-specific artifacts to avoid collateral deletions.
Safer to avoid removing unrelated “sherpa-onnx-*” files that may be needed later.

Apply:

-rm -rf sherpa-onnx-*
+rm -rf sherpa-onnx-wenetspeech-*
.github/workflows/c-api.yaml (1)

78-107: Harden the step with strict bash flags

Use -euo pipefail early to fail fast on errors.

-      - name: Test Wenet CTC
-        shell: bash
-        run: |
+      - name: Test Wenet CTC
+        shell: bash
+        run: |
+          set -euo pipefail
           name=wenet-ctc-c-api
.github/scripts/test-dot-net.sh (1)

30-32: Narrow cleanup to reduce re-downloads

Limit deletion to the Wenet model to save CI time/bandwidth.

-./run-wenet-ctc.sh
-rm -rf sherpa-onnx-*
+./run-wenet-ctc.sh
+rm -rf sherpa-onnx-wenetspeech-*
.github/workflows/cxx-api.yaml (1)

81-114: Harden the step with strict bash flags

Add -euo pipefail at the start of the run block.

-      - name: Test Wenet CTC
-        shell: bash
-        run: |
+      - name: Test Wenet CTC
+        shell: bash
+        run: |
+          set -euo pipefail
           name=wenet-ctc-cxx-api
kotlin-api-examples/run.sh (1)

492-511: Kotlin Wenet CTC test — LGTM; add cleanup to keep workspace tidy

Optional: remove the extracted model after the run.

   ls -lh $out_filename
   java -Djava.library.path=../build/lib -jar $out_filename
+
+  rm -rf sherpa-onnx-wenetspeech-*
 }
cxx-api-examples/wenet-ctc-cxx-api.cc (2)

1-1: Fix file header path typo.

Header says wenet-cxx-api.cc but the file is wenet-ctc-cxx-api.cc.

-// cxx-api-examples/wenet-cxx-api.cc
+// cxx-api-examples/wenet-ctc-cxx-api.cc

52-53: Avoid implicit narrowing when passing sample count.

AcceptWaveform usually takes int32_t for num_samples; passing size_t may trigger narrowing. Cast explicitly.

-  stream.AcceptWaveform(wave.sample_rate, wave.samples.data(),
-                        wave.samples.size());
+  stream.AcceptWaveform(wave.sample_rate, wave.samples.data(),
+                        static_cast<int32_t>(wave.samples.size()));
.github/scripts/test-nodejs-addon-npm.sh (1)

15-17: Harden downloads with fail-fast and retries.

Use curl --fail and retries to reduce flakiness.

-curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2
+curl -fSL --retry 3 --retry-delay 2 -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2
dotnet-examples/offline-decode-files/Program.cs (1)

87-89: Add usage snippet for --wenet-ctc in DisplayHelp.

Helps users discover the new mode.

Example to append to the usage string (outside the changed lines):

# Wenet CTC

dotnet run \
  --tokens=./sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10/tokens.txt \
  --wenet-ctc=./sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10/model.int8.onnx \
  --files ./sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10/test_wavs/yue-0.wav
swift-api-examples/run-wenet-ctc-asr.sh (2)

10-14: Improve curl robustness.

Add fail-fast and retries to cut CI flakes.

-  curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2
+  curl -fSL --retry 3 --retry-delay 2 -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2

33-34: Quote DYLD_LIBRARY_PATH to avoid word-splitting.

-export DYLD_LIBRARY_PATH=$PWD/../build-swift-macos/install/lib:$DYLD_LIBRARY_PATH
+export DYLD_LIBRARY_PATH="$PWD/../build-swift-macos/install/lib:${DYLD_LIBRARY_PATH:-}"
dart-api-examples/non-streaming-asr/bin/wenet-ctc.dart (1)

25-28: Validate inputs early for clearer errors.

Check file existence before attempting load to provide actionable messages.

 final model = res['model'] as String;
 final tokens = res['tokens'] as String;
 final inputWav = res['input-wav'] as String;
 
+for (final p in [model, tokens, inputWav]) {
+  if (!File(p).existsSync()) {
+    stderr.writeln('Not found: $p');
+    exit(2);
+  }
+}
nodejs-addon-examples/test_asr_non_streaming_wenet_ctc.js (1)

47-48: Log only the transcription text.

Printing the whole object is noisy; print result.text.

-console.log('result\n', result)
+console.log('text:', result.text)
sherpa-onnx/kotlin-api/OfflineRecognizer.kt (2)

192-223: Update the type documentation block.
The comment enumerating types (0–41) isn’t updated to include type 42 (Wenet CTC). Add an entry to prevent drift between code and docs.

-223  */
+223  42 - sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10 (Cantonese)
+224      Wenet CTC, model int8
+225  */

714-722: Consider setting modelType for Wenet CTC.
Some callers branch on modelType. If applicable, set it (e.g., "wenet_ctc") for parity with other entries that tag their type.

         return OfflineModelConfig(
             wenetCtc = OfflineWenetCtcModelConfig(
                 model = "$modelDir/model.int8.onnx",
             ),
             tokens = "$modelDir/tokens.txt",
+            modelType = "wenet_ctc",
         )
swift-api-examples/wenet-ctc-asr.swift (1)

15-41: Minor: make local paths configurable.
Optional: accept model/tokens/wav via env or args to ease CI reuse.

dart-api-examples/non-streaming-asr/run-wenet-ctc.sh (1)

1-19: Make script path-stable and stricter.
Add cd to script dir and -u -o pipefail for robustness, especially in CI.

-#!/usr/bin/env bash
-set -ex
+#!/usr/bin/env bash
+set -euo pipefail
+set -x
+cd "$(dirname "$0")"
c-api-examples/wenet-ctc-c-api.c (2)

36-41: Comment nit: not Zipformer.
Rename to “Wenet CTC config” to avoid confusion.

-  // Zipformer config
+  // Wenet CTC config

30-35: Add sample rate validation.
Most models expect 16 kHz. Warn early to avoid misleading results.

   const SherpaOnnxWave *wave = SherpaOnnxReadWave(wav_filename);
   if (wave == NULL) {
     fprintf(stderr, "Failed to read %s\n", wav_filename);
     return -1;
   }
+  if (wave->sample_rate != 16000) {
+    fprintf(stderr, "Warning: expected 16000 Hz, got %d Hz\n", wave->sample_rate);
+  }
dotnet-examples/offline-decode-files/run-wenet-ctc.sh (1)

1-15: Optional: make script path-stable.
Add cd to script dir for reliable relative paths.

-#!/usr/bin/env bash
-set -ex
+#!/usr/bin/env bash
+set -ex
+cd "$(dirname "$0")"
kotlin-api-examples/test_offline_wenet_ctc.kt (2)

5-5: Avoid hard-coded, date-stamped paths in examples.

Consider deriving waveFilename from a base dir or CLI arg to reduce churn.

-  val waveFilename = "./sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10/test_wavs/yue-0.wav"
+  val baseDir = System.getenv("WENET_CTC_DIR") ?: "./sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10"
+  val waveFilename = "$baseDir/test_wavs/yue-0.wav"

17-18: Print only the recognized text
In test_offline_wenet_ctc.kt (around line 17), getResult(stream) returns an OfflineRecognizerResult with a text field. Replace

println(result)

with

println(result.text)

to match other examples.

java-api-examples/NonStreamingDecodeFileWenetCtc.java (1)

13-21: Minor: reduce duplication with a base directory.

-    String model =
-        "sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10/model.int8.onnx";
-    String tokens =
-        "sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10/tokens.txt";
-    String waveFilename =
-        "sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10/test_wavs/yue-0.wav";
+    String base = "sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10";
+    String model = base + "/model.int8.onnx";
+    String tokens = base + "/tokens.txt";
+    String waveFilename = base + "/test_wavs/yue-0.wav";
wasm/nodejs/sherpa-onnx-wasm-nodejs.cc (1)

39-41: Keep size assert in sync with the C API struct order.

If fields are added/removed in SherpaOnnxOfflineModelConfig, this sum must be updated.

Consider adding a comment mirroring the exact field order from c-api.h above this assert to reduce drift.

cxx-api-examples/wenet-ctc-simulate-streaming-microphone-cxx-api.cc (3)

127-131: Log the requested mic sample rate after parsing

You print the old value (16000) before parsing the env var.

-  if (sample_rate_str) {
-    fprintf(stderr, "Use sample rate %f for mic\n", mic_sample_rate);
-    mic_sample_rate = atof(sample_rate_str);
-  }
+  if (sample_rate_str) {
+    mic_sample_rate = static_cast<float>(atof(sample_rate_str));
+    fprintf(stderr, "Use sample rate %f for mic\n", mic_sample_rate);
+  }

38-42: Signal-safety: avoid non-async-signal-safe ops in SIGINT handler

condition_variable.notify_one() and fprintf aren’t signal-safe. Prefer setting an atomic flag and let the main thread handle logging/notify.

-std::condition_variable condition_variable;
-std::mutex mutex;
-bool stop = false;
+std::condition_variable condition_variable;
+std::mutex mutex;
+std::atomic<bool> stop{false};

 static void Handler(int32_t /*sig*/) {
-  stop = true;
-  condition_variable.notify_one();
-  fprintf(stderr, "\nCaught Ctrl + C. Exiting...\n");
+  stop.store(true, std::memory_order_relaxed);
 }

Then print and notify in the main loop when stop.load() becomes true.


111-116: Handle PortAudio errors and update guidance text

  • Microphone() calls Pa_Initialize() before Pa_GetDeviceCount(), so ordering is correct.
  • Change the check to <= 0 to catch negative error codes.
  • Update the Linux guidance to reference wenet-ctc-simulate-streaming-alsa-cxx-api.
  PaDeviceIndex num_devices = Pa_GetDeviceCount();
- if (num_devices == 0) {
+ if (num_devices <= 0) {
    std::cerr << "  If you are using Linux, please try "
-               "./build/bin/sense-voice-simulate-streaming-alsa-cxx-api\n";
+               "./build/bin/wenet-ctc-simulate-streaming-alsa-cxx-api\n";
    return -1;
  }
wasm/asr/sherpa-onnx-asr.js (1)

740-756: init offline Wenet CTC config — LGTM; consider deduping 1-field initializers

Implementation matches other single-string model configs. To reduce duplication across zipformerCtc/nemo/dolphin/tdnn/wenetCtc, consider a small helper (e.g., initSinglePathModelConfig) to return {buffer, ptr, len}.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 71f87e1 and cefba26.

📒 Files selected for processing (58)
  • .github/scripts/test-dart.sh (1 hunks)
  • .github/scripts/test-dot-net.sh (2 hunks)
  • .github/scripts/test-nodejs-addon-npm.sh (1 hunks)
  • .github/scripts/test-nodejs-npm.sh (1 hunks)
  • .github/scripts/test-swift.sh (1 hunks)
  • .github/workflows/c-api.yaml (1 hunks)
  • .github/workflows/cxx-api.yaml (1 hunks)
  • .github/workflows/test-go.yaml (1 hunks)
  • .gitignore (1 hunks)
  • c-api-examples/CMakeLists.txt (1 hunks)
  • c-api-examples/wenet-ctc-c-api.c (1 hunks)
  • cxx-api-examples/CMakeLists.txt (2 hunks)
  • cxx-api-examples/wenet-ctc-cxx-api.cc (1 hunks)
  • cxx-api-examples/wenet-ctc-simulate-streaming-microphone-cxx-api.cc (1 hunks)
  • dart-api-examples/non-streaming-asr/bin/wenet-ctc.dart (1 hunks)
  • dart-api-examples/non-streaming-asr/run-wenet-ctc.sh (1 hunks)
  • dotnet-examples/offline-decode-files/Program.cs (2 hunks)
  • dotnet-examples/offline-decode-files/run-wenet-ctc.sh (1 hunks)
  • flutter/sherpa_onnx/lib/src/offline_recognizer.dart (8 hunks)
  • flutter/sherpa_onnx/lib/src/sherpa_onnx_bindings.dart (2 hunks)
  • go-api-examples/non-streaming-decode-files/main.go (1 hunks)
  • go-api-examples/non-streaming-decode-files/run-wenet-ctc.sh (1 hunks)
  • harmony-os/SherpaOnnxHar/sherpa_onnx/Index.ets (1 hunks)
  • harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/non-streaming-asr.cc (3 hunks)
  • harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/ets/components/NonStreamingAsr.ets (2 hunks)
  • java-api-examples/NonStreamingDecodeFileWenetCtc.java (1 hunks)
  • java-api-examples/run-non-streaming-decode-file-wenet-ctc.sh (1 hunks)
  • kotlin-api-examples/run.sh (1 hunks)
  • kotlin-api-examples/test_offline_wenet_ctc.kt (1 hunks)
  • nodejs-addon-examples/README.md (2 hunks)
  • nodejs-addon-examples/test_asr_non_streaming_wenet_ctc.js (1 hunks)
  • nodejs-examples/README.md (1 hunks)
  • nodejs-examples/test-offline-wenet-ctc.js (1 hunks)
  • pascal-api-examples/non-streaming-asr/.gitignore (1 hunks)
  • pascal-api-examples/non-streaming-asr/run-wenet-ctc.sh (1 hunks)
  • pascal-api-examples/non-streaming-asr/wenet_ctc.pas (1 hunks)
  • scripts/apk/generate-asr-2pass-apk-script.py (3 hunks)
  • scripts/apk/generate-vad-asr-apk-script.py (1 hunks)
  • scripts/dotnet/OfflineModelConfig.cs (2 hunks)
  • scripts/dotnet/OfflineWenetCtcModelConfig.cs (1 hunks)
  • scripts/go/_internal/non-streaming-decode-files/run-wenet-ctc.sh (1 hunks)
  • scripts/go/sherpa_onnx.go (4 hunks)
  • sherpa-onnx/c-api/c-api.cc (1 hunks)
  • sherpa-onnx/c-api/c-api.h (2 hunks)
  • sherpa-onnx/c-api/cxx-api.cc (1 hunks)
  • sherpa-onnx/c-api/cxx-api.h (2 hunks)
  • sherpa-onnx/java-api/Makefile (1 hunks)
  • sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineModelConfig.java (5 hunks)
  • sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineWenetCtcModelConfig.java (1 hunks)
  • sherpa-onnx/jni/offline-recognizer.cc (1 hunks)
  • sherpa-onnx/kotlin-api/OfflineRecognizer.kt (3 hunks)
  • sherpa-onnx/pascal-api/sherpa_onnx.pas (7 hunks)
  • swift-api-examples/.gitignore (1 hunks)
  • swift-api-examples/SherpaOnnx.swift (3 hunks)
  • swift-api-examples/run-wenet-ctc-asr.sh (1 hunks)
  • swift-api-examples/wenet-ctc-asr.swift (1 hunks)
  • wasm/asr/sherpa-onnx-asr.js (5 hunks)
  • wasm/nodejs/sherpa-onnx-wasm-nodejs.cc (4 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-06T04:18:47.981Z
Learnt from: litongjava
PR: k2-fsa/sherpa-onnx#2440
File: sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/core/Core.java:4-6
Timestamp: 2025-08-06T04:18:47.981Z
Learning: In sherpa-onnx Java API, the native library names in Core.java (WIN_NATIVE_LIBRARY_NAME = "sherpa-onnx-jni.dll", UNIX_NATIVE_LIBRARY_NAME = "libsherpa-onnx-jni.so", MACOS_NATIVE_LIBRARY_NAME = "libsherpa-onnx-jni.dylib") are copied directly from the compiled binary filenames and should not be changed to match other libraries' naming conventions.

Applied to files:

  • java-api-examples/run-non-streaming-decode-file-wenet-ctc.sh
📚 Learning: 2025-08-06T04:23:50.237Z
Learnt from: litongjava
PR: k2-fsa/sherpa-onnx#2440
File: sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/core/Core.java:4-6
Timestamp: 2025-08-06T04:23:50.237Z
Learning: The sherpa-onnx JNI library files are stored in Hugging Face repository at https://huggingface.co/csukuangfj/sherpa-onnx-libs under versioned directories like jni/1.12.7/, and the actual Windows JNI library filename is "sherpa-onnx-jni.dll" as defined in Core.java constants.

Applied to files:

  • java-api-examples/run-non-streaming-decode-file-wenet-ctc.sh
  • .gitignore
🧬 Code graph analysis (14)
java-api-examples/NonStreamingDecodeFileWenetCtc.java (2)
cxx-api-examples/wenet-ctc-cxx-api.cc (2)
  • main (21-75)
  • main (21-21)
sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineModelConfig.java (1)
  • OfflineModelConfig (5-245)
sherpa-onnx/c-api/cxx-api.h (4)
scripts/go/sherpa_onnx.go (1)
  • OfflineWenetCtcModelConfig (421-423)
sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineWenetCtcModelConfig.java (1)
  • OfflineWenetCtcModelConfig (3-30)
wasm/asr/sherpa-onnx-asr.js (2)
  • model (436-436)
  • model (1257-1257)
sherpa-onnx/kotlin-api/OfflineRecognizer.kt (6)
  • model (23-25)
  • model (27-29)
  • model (31-33)
  • model (35-37)
  • model (39-41)
  • model (71-75)
scripts/go/sherpa_onnx.go (1)
sherpa-onnx/c-api/cxx-api.h (1)
  • OfflineWenetCtcModelConfig (267-269)
c-api-examples/wenet-ctc-c-api.c (1)
cxx-api-examples/wenet-ctc-cxx-api.cc (2)
  • main (21-75)
  • main (21-21)
sherpa-onnx/kotlin-api/OfflineRecognizer.kt (5)
scripts/go/sherpa_onnx.go (2)
  • OfflineWenetCtcModelConfig (421-423)
  • OfflineModelConfig (473-503)
sherpa-onnx/c-api/cxx-api.h (2)
  • OfflineWenetCtcModelConfig (267-269)
  • OfflineModelConfig (278-300)
sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineWenetCtcModelConfig.java (1)
  • OfflineWenetCtcModelConfig (3-30)
sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineModelConfig.java (1)
  • OfflineModelConfig (5-245)
scripts/dotnet/OfflineModelConfig.cs (1)
  • OfflineModelConfig (11-33)
scripts/dotnet/OfflineModelConfig.cs (3)
scripts/go/sherpa_onnx.go (1)
  • OfflineWenetCtcModelConfig (421-423)
scripts/dotnet/OfflineWenetCtcModelConfig.cs (1)
  • OfflineWenetCtcModelConfig (11-14)
sherpa-onnx/c-api/cxx-api.h (1)
  • OfflineWenetCtcModelConfig (267-269)
swift-api-examples/wenet-ctc-asr.swift (1)
swift-api-examples/SherpaOnnx.swift (4)
  • sherpaOnnxOfflineWenetCtcModelConfig (363-369)
  • sherpaOnnxOfflineModelConfig (473-519)
  • sherpaOnnxFeatureConfig (133-140)
  • sherpaOnnxOfflineRecognizerConfig (521-547)
sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineModelConfig.java (2)
sherpa-onnx/c-api/cxx-api.h (1)
  • OfflineWenetCtcModelConfig (267-269)
sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineWenetCtcModelConfig.java (1)
  • OfflineWenetCtcModelConfig (3-30)
sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineWenetCtcModelConfig.java (3)
sherpa-onnx/c-api/cxx-api.h (1)
  • OfflineWenetCtcModelConfig (267-269)
sherpa-onnx/kotlin-api/OfflineRecognizer.kt (6)
  • model (23-25)
  • model (27-29)
  • model (31-33)
  • model (35-37)
  • model (39-41)
  • model (71-75)
sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineModelConfig.java (1)
  • Builder (125-244)
swift-api-examples/SherpaOnnx.swift (2)
wasm/asr/sherpa-onnx-asr.js (3)
  • model (436-436)
  • model (1257-1257)
  • wenetCtc (1108-1109)
sherpa-onnx/kotlin-api/OfflineRecognizer.kt (6)
  • model (23-25)
  • model (27-29)
  • model (31-33)
  • model (35-37)
  • model (39-41)
  • model (71-75)
scripts/apk/generate-vad-asr-apk-script.py (1)
scripts/hap/generate-vad-asr-hap-script.py (1)
  • Model (28-48)
scripts/apk/generate-asr-2pass-apk-script.py (1)
scripts/apk/generate-asr-apk-script.py (1)
  • Model (27-44)
wasm/asr/sherpa-onnx-asr.js (1)
sherpa-onnx/kotlin-api/OfflineRecognizer.kt (1)
  • transducer (77-97)
harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/non-streaming-asr.cc (2)
wasm/asr/sherpa-onnx-asr.js (2)
  • model (436-436)
  • model (1257-1257)
sherpa-onnx/kotlin-api/OfflineRecognizer.kt (6)
  • model (23-25)
  • model (27-29)
  • model (31-33)
  • model (35-37)
  • model (39-41)
  • model (71-75)
🪛 SwiftLint (0.57.0)
swift-api-examples/wenet-ctc-asr.swift

[Error] 45-45: Force tries should be avoided

(force_try)


[Error] 54-54: Force tries should be avoided

(force_try)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: ubuntu-latest Release static tts-OFF
  • GitHub Check: ubuntu-latest Debug shared tts-ON
  • GitHub Check: ubuntu-latest Debug static tts-ON
  • GitHub Check: ubuntu-latest Release shared tts-OFF
  • GitHub Check: ubuntu-latest Release shared tts-ON
  • GitHub Check: rknn shared OFF
  • GitHub Check: rknn shared ON
  • GitHub Check: Debug shared-ON tts-OFF
  • GitHub Check: Debug shared-OFF tts-ON
  • GitHub Check: Debug shared-ON tts-ON
  • GitHub Check: Debug shared-OFF tts-OFF
  • GitHub Check: Release shared-OFF tts-OFF
  • GitHub Check: Release shared-OFF tts-ON
  • GitHub Check: Release shared-ON tts-ON
  • GitHub Check: Release shared-ON tts-OFF
  • GitHub Check: swift (macos-13)
  • GitHub Check: swift (macos-latest)
  • GitHub Check: Debug static tts-OFF
  • GitHub Check: Release static tts-ON
  • GitHub Check: Release shared tts-OFF
🔇 Additional comments (63)
scripts/apk/generate-asr-2pass-apk-script.py (2)

416-417: Good: new 2nd-model listed for zh combos

The Cantonese Wenet CTC model is now included in second_zh; name matches the object defined above.


443-446: Resolved: Kotlin mapping for idx 42 is present
The when (type) block in OfflineRecognizer.kt includes a 42 -> case that returns the new Wenet CTC model config (lines 714–719), so no further changes are needed.

swift-api-examples/.gitignore (1)

22-22: Add ignore for generated Swift example binary — LGTM

Ignoring wenet-ctc-asr aligns with the new Swift example. No issues.

pascal-api-examples/non-streaming-asr/.gitignore (1)

13-13: Add ignore for Pascal Wenet CTC artifact — LGTM

wenet_ctc matches the example naming; good hygiene.

sherpa-onnx/c-api/c-api.cc (1)

509-511: Wire Wenet CTC into offline recognizer config — LGTM

The field mirrors other offline CTC entries and uses the existing defaulting macro. Ensure corresponding headers/structs (c_api.h / cxx-api.*) already expose wenet_ctc.model to keep ABI surface consistent.

nodejs-examples/test-offline-wenet-ctc.js (1)

9-22: Config and recognizer creation — LGTM

Paths and object wiring look correct for offline Wenet CTC.

sherpa-onnx/c-api/c-api.h (1)

508-509: Bindings updated — ABI change verified. All first-party bindings (Go/Java/Kotlin/.NET/Swift/Dart/WASM/Pascal/C++) now include the new wenet_ctc field. Release the C API with a bumped minor/ABI version for external consumers (or note the break) and consider adding padding fields in future to minimize layout churn.

scripts/apk/generate-vad-asr-apk-script.py (2)

714-716: Good catch adding the missing popd.

Prevents directory context leaks in subsequent steps.


717-731: Quote model_name and verify idx mapping

-            pushd $model_name
+            pushd "$model_name"

No occurrences of idx = 42 were found in the Kotlin/Java code paths; ensure your switch/case (or equivalent) handling includes 42 so this model is selectable.

scripts/go/sherpa_onnx.go (3)

421-424: Go: New OfflineWenetCtcModelConfig type — LGTM.

Matches C/C++ shape and naming; minimal and consistent.


485-485: Wires WenetCtc into OfflineModelConfig — LGTM.

Field placement aligns with C struct (after Canary).


737-740: Frees wenet_ctc.model — LGTM.

Balances allocation in newCOfflineRecognizerConfig; avoids leaks.

swift-api-examples/SherpaOnnx.swift (2)

363-369: Adds helper for OfflineWenetCtcModelConfig — LGTM.

Consistent with other config builder helpers.


494-519: Offline model config now includes wenet_ctc — LGTM.

Ordering matches the C struct; no issues spotted.

sherpa-onnx/java-api/Makefile (1)

39-39: Wires OfflineWenetCtcModelConfig into the build — looks good.
The new Java config class will be compiled and packaged with the jar. No further action here.

sherpa-onnx/c-api/cxx-api.cc (1)

268-269: Correct propagation of wenet_ctc model path.
The new field is mapped as expected in Convert(). Good.

.github/scripts/test-swift.sh (1)

22-24: Adds Wenet CTC test stage before Zipformer — looks good.
Sequence and cleanup are consistent with the rest of the script.

sherpa-onnx/c-api/cxx-api.h (2)

267-270: New OfflineWenetCtcModelConfig — OK.
Matches patterns of other offline single-file CTC configs.


299-300: Confirmed C struct matches C++ field order
The C struct SherpaOnnxOfflineModelConfig defines wenet_ctc as its final field (line 508), exactly matching the C++ OfflineModelConfig layout—no ABI mismatches.

sherpa-onnx/jni/offline-recognizer.cc (1)

287-299: JNI: Reads wenetCtc.model correctly.
Field/class lookups and string lifetimes are handled consistently with other configs.

c-api-examples/CMakeLists.txt (1)

83-85: Add Wenet CTC example target — LGTM

Consistent with surrounding examples and linkage.

scripts/dotnet/OfflineModelConfig.cs (2)

32-33: Constructor init — LGTM

Initializing WenetCtc keeps default layout well-defined.


68-69: Remove suggestion: attributes already present – The OfflineWenetCtcModelConfig struct is already annotated with [StructLayout(LayoutKind.Sequential)] and its Model field has [MarshalAs(UnmanagedType.LPStr)], so no changes are needed.

Likely an incorrect or invalid review comment.

.github/scripts/test-dot-net.sh (1)

114-116: Add T-one online test — LGTM

Placement and cleanup are consistent with other steps.

flutter/sherpa_onnx/lib/src/sherpa_onnx_bindings.dart (1)

284-287: New FFI config struct — LGTM

Pointer matches C char* expectation.

kotlin-api-examples/run.sh (1)

515-516: Invocation order — LGTM

Runs early, before other offline ASR tests.

nodejs-addon-examples/README.md (1)

127-128: README table entry for Wenet CTC — LGTM

Matches the new example filename and model link.

.github/scripts/test-nodejs-addon-npm.sh (1)

13-21: LGTM — adds non-streaming Wenet CTC test step.

Sequence and cleanup look correct; runs from the expected cwd.

dotnet-examples/offline-decode-files/Program.cs (2)

87-89: Option wiring looks correct.

New --wenet-ctc option name, help text, and default align with the pattern used elsewhere.


257-260: Correct model selection branch.

Sets config.ModelConfig.WenetCtc.Model when --wenet-ctc is provided. Consistent with other CTC branches.

harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/ets/components/NonStreamingAsr.ets (2)

63-66: LGTM — new OfflineWenetCtcModelConfig type.

Name and shape match other offline single-file CTC configs.


119-119: LGTM — plumbs wenetCtc into OfflineModelConfig.

Keeps parity with other bindings.

sherpa-onnx/kotlin-api/OfflineRecognizer.kt (2)

39-41: New Wenet CTC config looks good.
Simple, consistent with other model config data classes.


77-97: OfflineModelConfig: field wiring is consistent.
wenetCtc default and placement align with other fields. JNI side should already read it.

swift-api-examples/wenet-ctc-asr.swift (1)

47-50: Validate sample format more robustly.
Also guard sampleRate==16000 expected by examples to avoid silent quality issues.

-  assert(audioFormat.channelCount == 1)
-  assert(audioFormat.commonFormat == AVAudioCommonFormat.pcmFormatFloat32)
+  guard audioFormat.channelCount == 1,
+        audioFormat.commonFormat == .pcmFormatFloat32 else {
+    fputs("Expect mono 32-bit float WAV\n", stderr)
+    return
+  }
+  if Int(audioFormat.sampleRate) != 16000 {
+    fputs("Warning: sampleRate \(Int(audioFormat.sampleRate)) != 16000; results may degrade\n", stderr)
+  }
java-api-examples/run-non-streaming-decode-file-wenet-ctc.sh (1)

5-20: Build guard is OK.
CMake/JNI flags and fallback build logic look fine.

c-api-examples/wenet-ctc-c-api.c (1)

56-82: C example lifecycle/cleanup LGTM.
Config zeroing, decode, result retrieval, and frees are correct.

sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineModelConfig.java (1)

15-16: Wenet CTC field integration is correct.
New field, getter, builder default, and setter are consistent with existing patterns.

Also applies to: 36-37, 85-87, 135-136, 175-178

sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineWenetCtcModelConfig.java (1)

3-30: Minimal, immutable config class LGTM.
Builder pattern and accessor match the project’s style.

java-api-examples/NonStreamingDecodeFileWenetCtc.java (2)

24-40: Config wiring looks correct for Wenet CTC + greedy decode.


10-13: Fix documentation link for WeNet CTC model download
Replace the SenseVoice URL with the correct WeNet CTC download page.

File: java-api-examples/NonStreamingDecodeFileWenetCtc.java
Lines: 10–13

-    // https://k2-fsa.github.io/sherpa/onnx/sense-voice/index.html
-    // to download model files
+    // Refer to https://k2-fsa.github.io/sherpa/onnx/pretrained_models/wenet/index.html to download offline WeNet CTC model files.
cxx-api-examples/CMakeLists.txt (2)

33-35: Add Wenet CTC C++ example target — looks good.


52-60: PortAudio microphone example for Wenet CTC — wiring is consistent with others.

wasm/nodejs/sherpa-onnx-wasm-nodejs.cc (2)

88-89: LGTM: new wenet_ctc field plumbed into the printer.


17-17: Confirmed SherpaOnnxOfflineWenetCtcModelConfig struct size: the C API defines it with exactly one const char *model field, so static_assert(sizeof(...)=4) is valid.

harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/non-streaming-asr.cc (3)

64-78: Add Wenet CTC parsing helper — LGTM

Zero-init, presence checks, and SHERPA_ONNX_ASSIGN_ATTR_STR usage are consistent with other model parsers.


244-244: Wire wenet_ctc into OfflineModelConfig — LGTM

Integration mirrors the ordering used for other sub-configs.


337-338: Free newly added wenet_ctc strings — LGTM

Cleanup matches allocation sites. No leaks observed for wenet_ctc.model.

wasm/asr/sherpa-onnx-asr.js (4)

54-56: Add free path for wenetCtc — LGTM

Recursively freeing wenetCtc matches other sub-configs and prevents leaks.


1021-1026: Default wenetCtc block — LGTM

Ensures stable shape when callers omit wenetCtc.


1221-1230: Copy wenetCtc into aggregate buffer and expose handle — LGTM

Offset math and return object are consistent with previous sections.


1108-1114: No struct layout issue: wenet_ctc follows canary Verified in sherpa-onnx/c-api/c-api.h (lines 507–508) that SherpaOnnxOfflineWenetCtcModelConfig wenet_ctc; immediately follows ...CanaryModelConfig canary;, so offsets match.

flutter/sherpa_onnx/lib/src/offline_recognizer.dart (3)

128-147: OfflineWenetCtcModelConfig: consistent and minimal

Matches existing config patterns (const ctor, fromJson/toJson, toString). No issues.


373-373: Wire wenetCtc through OfflineModelConfig — LGTM

Constructor default, JSON (in/out), and field are correctly added and consistent with other model configs.

Also applies to: 430-433, 462-462, 484-484


721-722: FFI allocation for wenetCtc.model is correct
toNativeUtf8() is used at line 721 and the matching calloc.free() at line 769 mirrors the pattern for all other model fields, so no changes needed.

sherpa-onnx/pascal-api/sherpa_onnx.pas (8)

316-320: New Wenet CTC config record is well-formed.

Type mirrors existing model-config patterns and is consistent in naming.


395-395: OfflineModelConfig now exposes WenetCtc.

Plumbed at the right level alongside other model families.


803-805: FFI record for Wenet CTC added correctly.

Field type (PAnsiChar) matches convention used elsewhere.


862-862: FFI OfflineModelConfig includes WenetCtc.

Placement aligns with the Pascal-side record.


1671-1676: ToString for Wenet CTC config looks good.

Matches formatting used by sibling configs.


1766-1768: ModelConfig.ToString extended with Canary and WenetCtc—good coverage.

Format specifiers and parameter ordering are consistent.

Also applies to: 1775-1776


1854-1855: Config conversion wires WenetCtc to the C struct.

Correct PAnsiChar conversion; aligns with the rest of ConvertOfflineRecognizerConfig.


803-805: ABI/layout parity confirmed for SherpaOnnxOfflineWenetCtcModelConfig
The Pascal FFI record SherpaOnnxOfflineWenetCtcModelConfig (Model: PAnsiChar) exactly matches the C header’s single const char *model field, and within SherpaOnnxOfflineModelConfig the fields ZipformerCtc, Canary, WenetCtc appear in the same order as zipformer_ctc, canary, wenet_ctc in the C struct.

Comment on lines +96 to +99
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2
tar xvf sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2
rm sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify model asset availability to avoid flaky CI

The dated tarball may not exist immediately; preflight the URL.


🏁 Script executed:

#!/usr/bin/env bash
# Expect HTTP 200
url="https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2"
code=$(curl -sI "$url" | awk 'NR==1{print $2}')
echo "HTTP $code for $url"
test "$code" = "200"

Length of output: 166


Preflight model download URL with redirects and fail on missing asset
Replace the simple curl -SL check with a preflight that follows redirects and errors on HTTP >= 400, for example:

- name: Verify model asset availability
  run: |
    url=https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2
    curl -sSfLI "$url"
🤖 Prompt for AI Agents
.github/workflows/c-api.yaml around lines 96 to 99: the current steps use a
plain `curl -SL` download which doesn't preflight the URL for HTTP errors;
replace it with a preflight check that follows redirects and fails on HTTP
status >= 400 (e.g., use curl -sSfLI "$url"), then only proceed to download
(with -L -O) and extract; ensure the workflow step exits non-zero on missing
asset so CI fails early.

Comment on lines +101 to +104
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2
tar xvf sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2
rm sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Preflight the release asset to avoid CI flakiness

Check the tarball URL returns 200 before proceeding.


🏁 Script executed:

#!/usr/bin/env bash
url="https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2"
code=$(curl -sI "$url" | awk 'NR==1{print $2}')
echo "HTTP $code for $url"
test "$code" = "200"

Length of output: 166


Use a HEAD request that follows redirects and fails on errors before downloading
Replace the simple curl -sI check with a preflight that follows redirects and exits non-zero on HTTP ≥400, for example:

- name: Preflight release asset
  run: |
    url="https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10.tar.bz2"
    curl --head --silent --fail --location "$url" > /dev/null
🤖 Prompt for AI Agents
.github/workflows/cxx-api.yaml lines 101 to 104: the workflow downloads a
release asset without a preflight check; add a HEAD preflight that follows
redirects and fails on HTTP errors before the download (use curl with
--head/--head or -I plus --location and --fail), then proceed with the existing
curl -SL -O and extraction only if the preflight succeeds; ensure the job exits
non‑zero on HTTP >=400 by using curl --head --silent --fail --location "$url" >
/dev/null before the download step.

Comment on lines +44 to +57
static int32_t RecordCallback(const void *input_buffer,
void * /*output_buffer*/,
unsigned long frames_per_buffer, // NOLINT
const PaStreamCallbackTimeInfo * /*time_info*/,
PaStreamCallbackFlags /*status_flags*/,
void * /*user_data*/) {
std::lock_guard<std::mutex> lock(mutex);
samples_queue.emplace(
reinterpret_cast<const float *>(input_buffer),
reinterpret_cast<const float *>(input_buffer) + frames_per_buffer);
condition_variable.notify_one();

return stop ? paComplete : paContinue;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Guard against null input_buffer in PortAudio callback

PortAudio may pass a null input buffer; current code dereferences it and can crash.

Apply:

 static int32_t RecordCallback(const void *input_buffer,
                               void * /*output_buffer*/,
                               unsigned long frames_per_buffer,  // NOLINT
                               const PaStreamCallbackTimeInfo * /*time_info*/,
                               PaStreamCallbackFlags /*status_flags*/,
                               void * /*user_data*/) {
-  std::lock_guard<std::mutex> lock(mutex);
-  samples_queue.emplace(
-      reinterpret_cast<const float *>(input_buffer),
-      reinterpret_cast<const float *>(input_buffer) + frames_per_buffer);
+  std::lock_guard<std::mutex> lock(mutex);
+  if (!input_buffer) {
+    samples_queue.emplace(frames_per_buffer, 0.0f);  // silence
+  } else {
+    samples_queue.emplace(
+        reinterpret_cast<const float *>(input_buffer),
+        reinterpret_cast<const float *>(input_buffer) + frames_per_buffer);
+  }
   condition_variable.notify_one();
 
   return stop ? paComplete : paContinue;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
static int32_t RecordCallback(const void *input_buffer,
void * /*output_buffer*/,
unsigned long frames_per_buffer, // NOLINT
const PaStreamCallbackTimeInfo * /*time_info*/,
PaStreamCallbackFlags /*status_flags*/,
void * /*user_data*/) {
std::lock_guard<std::mutex> lock(mutex);
samples_queue.emplace(
reinterpret_cast<const float *>(input_buffer),
reinterpret_cast<const float *>(input_buffer) + frames_per_buffer);
condition_variable.notify_one();
return stop ? paComplete : paContinue;
}
static int32_t RecordCallback(const void *input_buffer,
void * /*output_buffer*/,
unsigned long frames_per_buffer, // NOLINT
const PaStreamCallbackTimeInfo * /*time_info*/,
PaStreamCallbackFlags /*status_flags*/,
void * /*user_data*/) {
std::lock_guard<std::mutex> lock(mutex);
if (!input_buffer) {
// PortAudio indicates input underflow by passing NULL;
// push silence so downstream still sees frames_per_buffer samples.
samples_queue.emplace(frames_per_buffer, 0.0f);
} else {
// Normal case: copy the incoming audio samples
samples_queue.emplace(
reinterpret_cast<const float *>(input_buffer),
reinterpret_cast<const float *>(input_buffer) + frames_per_buffer);
}
condition_variable.notify_one();
return stop ? paComplete : paContinue;
}

Comment on lines +190 to +194
if (buffer.size() > 10 * window_size) {
offset -= buffer.size() - 10 * window_size;
buffer = {buffer.end() - 10 * window_size, buffer.end()};
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix vector slicing (current code won’t compile)

buffer = {buffer.end() - 10 * window_size, buffer.end()}; creates an initializer_list of iterators. Use assign or range constructor.

-        offset -= buffer.size() - 10 * window_size;
-        buffer = {buffer.end() - 10 * window_size, buffer.end()};
+        offset -= static_cast<int32_t>(buffer.size() - 10 * window_size);
+        buffer.assign(buffer.end() - 10 * window_size, buffer.end());
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (buffer.size() > 10 * window_size) {
offset -= buffer.size() - 10 * window_size;
buffer = {buffer.end() - 10 * window_size, buffer.end()};
}
}
if (buffer.size() > 10 * window_size) {
- offset -= buffer.size() - 10 * window_size;
offset -= static_cast<int32_t>(buffer.size() - 10 * window_size);
buffer.assign(buffer.end() - 10 * window_size, buffer.end());
}
🤖 Prompt for AI Agents
In cxx-api-examples/wenet-ctc-simulate-streaming-microphone-cxx-api.cc around
lines 190-194, the slice operation uses brace-initialization with iterators
which creates an initializer_list and won’t compile; replace the assignment with
a proper vector range copy by either calling buffer.assign(buffer.end() - 10 *
window_size, buffer.end()) or constructing a new vector from the iterator range
and assigning that (e.g., buffer =
std::vector<decltype(buffer)::value_type>(buffer.end() - 10 * window_size,
buffer.end())); ensure you use the correct element type when constructing the
temporary vector.

Comment on lines +27 to +35
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);
result = recognizer.getResult(stream)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Free resources after use.

Release native handles to avoid leaks.

 const recognizer = new sherpa_onnx.OfflineRecognizer(config);
@@
 recognizer.decode(stream);
-const result = recognizer.getResult(stream);
+const result = recognizer.getResult(stream);
+stream.free?.();
+recognizer.free?.();

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In nodejs-addon-examples/test_asr_non_streaming_wenet_ctc.js around lines 27 to
35, the OfflineRecognizer and Stream native handles are not released after use;
modify the flow to free native resources by calling the appropriate teardown
methods (e.g., stream.delete() and recognizer.delete(), and if getResult()
returns a native object free it too with result.delete() or result.dispose()
depending on the binding) in a finally block so resources are always released
even on errors.

stream.acceptWaveform({sampleRate: wave.sampleRate, samples: wave.samples});

recognizer.decode(stream);
result = recognizer.getResult(stream)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Undeclared global ‘result’.

Assigning without const/let creates a global; strict mode would throw. Declare it.

-recognizer.decode(stream);
-result = recognizer.getResult(stream)
+recognizer.decode(stream);
+const result = recognizer.getResult(stream);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
result = recognizer.getResult(stream)
recognizer.decode(stream);
const result = recognizer.getResult(stream);
🤖 Prompt for AI Agents
In nodejs-addon-examples/test_asr_non_streaming_wenet_ctc.js around line 35, the
variable result is assigned without declaration which creates a global; change
the assignment to declare the variable (use const if value won't change or let
if it will) so it is block-scoped and compatible with strict mode.

Comment on lines +3 to +13
extension AudioBuffer {
func array() -> [Float] {
return Array(UnsafeBufferPointer(self))
}
}

extension AVAudioPCMBuffer {
func array() -> [Float] {
return self.audioBufferList.pointee.mBuffers.array()
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix AudioBuffer/AVAudioPCMBuffer to Float[] conversion.
Current AudioBuffer.array() won’t compile and AVAudioPCMBuffer.array() is unsafe. Use floatChannelData and frameLength.

-extension AudioBuffer {
-  func array() -> [Float] {
-    return Array(UnsafeBufferPointer(self))
-  }
-}
-
-extension AVAudioPCMBuffer {
-  func array() -> [Float] {
-    return self.audioBufferList.pointee.mBuffers.array()
-  }
-}
+extension AVAudioPCMBuffer {
+  func array() -> [Float] {
+    guard let ch0 = self.floatChannelData?.pointee else { return [] }
+    let count = Int(self.frameLength)
+    return Array(UnsafeBufferPointer(start: ch0, count: count))
+  }
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
extension AudioBuffer {
func array() -> [Float] {
return Array(UnsafeBufferPointer(self))
}
}
extension AVAudioPCMBuffer {
func array() -> [Float] {
return self.audioBufferList.pointee.mBuffers.array()
}
}
extension AVAudioPCMBuffer {
func array() -> [Float] {
guard let ch0 = self.floatChannelData?.pointee else { return [] }
let count = Int(self.frameLength)
return Array(UnsafeBufferPointer(start: ch0, count: count))
}
}
🤖 Prompt for AI Agents
In swift-api-examples/wenet-ctc-asr.swift around lines 3 to 13, the current
AudioBuffer.array() implementation won't compile and the
AVAudioPCMBuffer.array() uses audioBufferList unsafely; replace both to use
AVAudioPCMBuffer.floatChannelData and frameLength safely: implement
AVAudioPCMBuffer.array() to guard let floatChannelData = self.floatChannelData,
read the first channel pointer floatChannelData[0], and construct a [Float] from
UnsafeBufferPointer(start: floatChannelData[0], count: Int(self.frameLength));
remove or update the AudioBuffer.array() extension to either be omitted or, if
needed, read its mData by safely binding it to Float and using mDataByteSize /
MemoryLayout<Float>.size as the count, guarding for nil pointers and zero
lengths to avoid unsafe access.

Comment on lines +45 to +56
let audioFile = try! AVAudioFile(forReading: fileURL as URL)

let audioFormat = audioFile.processingFormat
assert(audioFormat.channelCount == 1)
assert(audioFormat.commonFormat == AVAudioCommonFormat.pcmFormatFloat32)

let audioFrameCount = UInt32(audioFile.length)
let audioFileBuffer = AVAudioPCMBuffer(pcmFormat: audioFormat, frameCapacity: audioFrameCount)

try! audioFile.read(into: audioFileBuffer!)
let array: [Float]! = audioFileBuffer?.array()
let result = recognizer.decode(samples: array, sampleRate: Int(audioFormat.sampleRate))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid force-try/implicitly unwrapped optionals; handle errors.
Replace try! and ! with guarded unwrapping and error handling. Also satisfy SwiftLint.

-  let audioFile = try! AVAudioFile(forReading: fileURL as URL)
+  let audioFile: AVAudioFile
+  do {
+    audioFile = try AVAudioFile(forReading: fileURL as URL)
+  } catch {
+    fputs("Failed to open WAV: \(error)\n", stderr)
+    return
+  }
@@
-  let audioFileBuffer = AVAudioPCMBuffer(pcmFormat: audioFormat, frameCapacity: audioFrameCount)
-
-  try! audioFile.read(into: audioFileBuffer!)
-  let array: [Float]! = audioFileBuffer?.array()
+  guard let audioFileBuffer = AVAudioPCMBuffer(pcmFormat: audioFormat, frameCapacity: audioFrameCount) else {
+    fputs("Failed to allocate audio buffer\n", stderr)
+    return
+  }
+  do {
+    try audioFile.read(into: audioFileBuffer)
+  } catch {
+    fputs("Failed to read audio: \(error)\n", stderr)
+    return
+  }
+  let array: [Float] = audioFileBuffer.array()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let audioFile = try! AVAudioFile(forReading: fileURL as URL)
let audioFormat = audioFile.processingFormat
assert(audioFormat.channelCount == 1)
assert(audioFormat.commonFormat == AVAudioCommonFormat.pcmFormatFloat32)
let audioFrameCount = UInt32(audioFile.length)
let audioFileBuffer = AVAudioPCMBuffer(pcmFormat: audioFormat, frameCapacity: audioFrameCount)
try! audioFile.read(into: audioFileBuffer!)
let array: [Float]! = audioFileBuffer?.array()
let result = recognizer.decode(samples: array, sampleRate: Int(audioFormat.sampleRate))
// Safely open the audio file
let audioFile: AVAudioFile
do {
audioFile = try AVAudioFile(forReading: fileURL as URL)
} catch {
fputs("Failed to open WAV: \(error)\n", stderr)
return
}
let audioFormat = audioFile.processingFormat
assert(audioFormat.channelCount == 1)
assert(audioFormat.commonFormat == AVAudioCommonFormat.pcmFormatFloat32)
let audioFrameCount = UInt32(audioFile.length)
// Safely allocate the audio buffer
guard let audioFileBuffer = AVAudioPCMBuffer(pcmFormat: audioFormat, frameCapacity: audioFrameCount) else {
fputs("Failed to allocate audio buffer\n", stderr)
return
}
// Safely read into the buffer
do {
try audioFile.read(into: audioFileBuffer)
} catch {
fputs("Failed to read audio: \(error)\n", stderr)
return
}
// Directly extract the samples array
let array: [Float] = audioFileBuffer.array()
let result = recognizer.decode(samples: array, sampleRate: Int(audioFormat.sampleRate))
🧰 Tools
🪛 SwiftLint (0.57.0)

[Error] 45-45: Force tries should be avoided

(force_try)


[Error] 54-54: Force tries should be avoided

(force_try)

🤖 Prompt for AI Agents
swift-api-examples/wenet-ctc-asr.swift lines 45-56: the code uses try! and
implicitly unwrapped optionals (audioFile, audioFileBuffer, array) and assert
for runtime checks; replace these with safe error handling and guarded
unwrapping to satisfy SwiftLint. Wrap the file-reading calls in a do/catch and
handle or rethrow errors from AVAudioFile(forReading:) and read(into:), use
guard let to validate audioFile.processingFormat properties (channelCount == 1
and commonFormat == .pcmFormatFloat32) and to unwrap AVAudioPCMBuffer and its
sample array, and log/return/throw on failure instead of force-unwrapping;
ensure sampleRate is converted safely and propagate errors or return a Result so
no force-try or implicitly unwrapped optionals remain.

Comment on lines +139 to +141
fprintf(stdout, "----------offline wenet ctc model config----------\n");
fprintf(stdout, "model: %s\n", wenet_ctc->model);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Null-safe print to avoid UB when model is unset.

Printing %s with a null pointer can crash. Apply a safe fallback.

-  fprintf(stdout, "model: %s\n", wenet_ctc->model);
+  fprintf(stdout, "model: %s\n", wenet_ctc->model ? wenet_ctc->model : "");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fprintf(stdout, "----------offline wenet ctc model config----------\n");
fprintf(stdout, "model: %s\n", wenet_ctc->model);
fprintf(stdout, "----------offline wenet ctc model config----------\n");
fprintf(stdout, "model: %s\n", wenet_ctc->model ? wenet_ctc->model : "");
🤖 Prompt for AI Agents
In wasm/nodejs/sherpa-onnx-wasm-nodejs.cc around lines 139 to 141, the code
prints wenet_ctc->model using "%s" which is undefined behavior if the pointer is
null; update the printing to be null-safe by checking wenet_ctc->model and using
a fallback string (e.g., "(null)" or "") when it's NULL, then pass that safe
string to fprintf so the log never receives a null pointer.

@csukuangfj csukuangfj merged commit 7e42ba2 into k2-fsa:master Sep 10, 2025
12 of 37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant