Skip to content

Add Kotlin and Java API for ZipVoice models - #3344

Merged
csukuangfj merged 1 commit into
k2-fsa:masterfrom
csukuangfj:jni-zipvoice
Mar 18, 2026
Merged

csukuangfj merged 1 commit into
k2-fsa:masterfrom
csukuangfj:jni-zipvoice

Conversation

@csukuangfj

@csukuangfj csukuangfj commented Mar 18, 2026 •

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

Release Notes

  • New Features

    • Added ZipVoice Text-to-Speech support with Java and Kotlin API implementations and example code.
  • Chores

    • Enhanced CI/CD pipelines with new TTS test workflows and improved path-based trigger configurations for Kotlin components.

@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Mar 18, 2026
@coderabbitai

coderabbitai Bot commented Mar 18, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR adds ZipVoice TTS model support across Java/Kotlin language bindings, including new configuration classes, native JNI parsing, example applications, and test workflows.

Changes

Cohort / File(s) Summary
GitHub Actions Workflows
.github/workflows/jni.yaml, .github/workflows/run-java-test.yaml
Added path trigger for kotlin-api in JNI workflow; extended Java test workflow with ZipVoice TTS test execution (run-zipvoice-tts.sh, run-non-streaming-tts-coqui-de.sh, run-non-streaming-tts-vits-zh.sh) and corresponding cleanup steps for vocoder/model artifacts.
Java/Kotlin API Configuration Classes
sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineTtsZipVoiceModelConfig.java, sherpa-onnx/kotlin-api/Tts.kt, sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineTtsModelConfig.java
Introduced new OfflineTtsZipVoiceModelConfig class with fluent Builder pattern (tokens, encoder, decoder, vocoder, dataDir, lexicon, featScale, tShift, targetRms, guidanceScale fields); extended OfflineTtsModelConfig with zipvoice field and getter; added corresponding Kotlin data class.
Java/Kotlin Examples
java-api-examples/ZipVoiceTts.java, java-api-examples/run-zipvoice-tts.sh, java-api-examples/README.md, kotlin-api-examples/test_zipvoice_tts.kt, kotlin-api-examples/run.sh
Added ZipVoiceTts.java example demonstrating model initialization, reference audio loading, and generation with timing; created run-zipvoice-tts.sh build/download/execution orchestration script; added Kotlin test file with ZipVoiceTts test workflow; updated README and run.sh to reference new examples.
Java Build Configuration
sherpa-onnx/java-api/Makefile
Added OfflineTtsZipVoiceModelConfig.java to Java source files compilation list.
JNI Native Binding
sherpa-onnx/jni/offline-tts.cc
Added parsing logic for OfflineTtsZipVoiceModelConfig fields (tokens, encoder, decoder, vocoder, data_dir, lexicon, feat_scale, t_shift, target_rms, guidance_scale) within GetOfflineTtsConfig function with corresponding JNI reference creation and cleanup.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

size:L

Poem

🐰 ZipVoice now hops through Java streams,
With Kotlin configs fulfilling TTS dreams,
New builders flutter, examples compile bright,
JNI parsers binding models tight,
One model type more makes testing just right! 🎵

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main objective of the PR, which adds Kotlin and Java API support for ZipVoice TTS models across multiple files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly expands the capabilities of the sherpa-onnx library by integrating support for ZipVoice Text-to-Speech models into both its Java and Kotlin APIs. This enables users to perform zero-shot TTS, generating speech from text using a reference audio and text, directly within their Java and Kotlin applications. The changes include new configuration classes, updated build processes, and comprehensive examples to facilitate adoption.

Highlights

  • Java API Support: Added comprehensive Java API support for ZipVoice Text-to-Speech (TTS) models, including a new configuration class and an example application.
  • Kotlin API Support: Introduced Kotlin API support for ZipVoice TTS models, complete with a new data class for configuration and an example usage script.
  • Zero-Shot TTS Examples: Provided new example scripts and code for both Java and Kotlin to demonstrate how to perform zero-shot TTS using ZipVoice models.
  • Model Configuration Integration: Integrated ZipVoice model configuration into the existing OfflineTtsModelConfig structure across Java, Kotlin, and JNI layers.
Ignored Files
  • Ignored by pattern: .github/workflows/** (2)
    • .github/workflows/jni.yaml
    • .github/workflows/run-java-test.yaml
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds ZipVoice model support to the Kotlin and Java TTS APIs, wiring the new model config through JNI and providing runnable examples + CI coverage.

Changes:

  • Add OfflineTtsZipVoiceModelConfig to Kotlin API and Java API, and expose it via OfflineTtsModelConfig.
  • Extend JNI config parsing (offline-tts.cc) to read ZipVoice fields into native OfflineTtsConfig.
  • Add ZipVoice TTS example scripts for Kotlin/Java and run them in GitHub Actions workflows.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
sherpa-onnx/kotlin-api/Tts.kt Adds Kotlin ZipVoice model config and exposes it on OfflineTtsModelConfig.
sherpa-onnx/jni/offline-tts.cc Reads ZipVoice config fields from JVM objects into native config.
sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineTtsZipVoiceModelConfig.java Introduces Java builder-style ZipVoice config.
sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineTtsModelConfig.java Adds ZipVoice config field + getter/setter on the Java model config.
sherpa-onnx/java-api/Makefile Ensures the new ZipVoice config is compiled into sherpa-onnx.jar.
kotlin-api-examples/test_zipvoice_tts.kt New Kotlin ZipVoice zero-shot TTS example.
kotlin-api-examples/run.sh Builds/runs the Kotlin ZipVoice example in the JNI example suite.
java-api-examples/run-zipvoice-tts.sh New Java ZipVoice runnable script (downloads model + vocoder).
java-api-examples/ZipVoiceTts.java New Java ZipVoice example program using callback + RTF reporting.
java-api-examples/README.md Documents the new ZipVoice example script.
.github/workflows/run-java-test.yaml Runs the ZipVoice Java TTS example in CI and cleans up artifacts.
.github/workflows/jni.yaml Triggers JNI workflow when sherpa-onnx/kotlin-api/** changes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +93 to +96
public Builder setZipvoice(OfflineTtsZipVoiceModelConfig zipvoice) {
this.zipvoice = zipvoice;
return this;
}
Comment on lines +207 to +208
"Lcom/k2fsa/sherpa/onnx/OfflineTtsZipVoiceModelConfig;");
jobject zipvoice = env->GetObjectField(model, fid);

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
kotlin-api-examples/test_zipvoice_tts.kt (1)

27-41: Ensure OfflineTts is always released on exceptions.

release() currently runs only on the success path. Wrap generation/save in try/finally so the native pointer is always freed.

♻️ Proposed fix
-  val tts = OfflineTts(config = config)
-  val text = "小米的价值观是真诚, 热爱. 真诚,就是不欺人也不自欺. 热爱, 就是全心投入并享受其中."
-  val referenceText = "那还是三十六年前, 一九八七年. 我呢考上了武汉大学的计算机系."
-  val genConfig = GenerationConfig(
-    referenceAudio = wave.samples,
-    referenceSampleRate = wave.sampleRate,
-    referenceText = referenceText,
-    numSteps = 4,
-    extra = mapOf("min_char_in_sentence" to "10"),
-  )
-
-  val audio = tts.generateWithConfigAndCallback(text = text, config = genConfig, callback = ::callback)
-  audio.save(filename = "test-zipvoice-zh-en.wav")
-  tts.release()
+  val tts = OfflineTts(config = config)
+  try {
+    val text = "小米的价值观是真诚, 热爱. 真诚,就是不欺人也不自欺. 热爱, 就是全心投入并享受其中."
+    val referenceText = "那还是三十六年前, 一九八七年. 我呢考上了武汉大学的计算机系."
+    val genConfig = GenerationConfig(
+      referenceAudio = wave.samples,
+      referenceSampleRate = wave.sampleRate,
+      referenceText = referenceText,
+      numSteps = 4,
+      extra = mapOf("min_char_in_sentence" to "10"),
+    )
+
+    val audio = tts.generateWithConfigAndCallback(
+      text = text,
+      config = genConfig,
+      callback = ::callback
+    )
+    audio.save(filename = "test-zipvoice-zh-en.wav")
+  } finally {
+    tts.release()
+  }
   println("Saved to test-zipvoice-zh-en.wav")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kotlin-api-examples/test_zipvoice_tts.kt` around lines 27 - 41, The
OfflineTts instance (tts) is only released on the success path; wrap the
generation and save calls in a try/finally so tts.release() always runs on error
or success. Specifically, enclose the call to
tts.generateWithConfigAndCallback(...) and audio.save(...) inside a try block
and call tts.release() in the finally block (still referencing OfflineTts,
generateWithConfigAndCallback, audio.save, and tts.release) to ensure the native
pointer is freed even if an exception is thrown.
.github/workflows/run-java-test.yaml (1)

354-363: Make cleanup idempotent for standalone files.

Use rm -f for single-file cleanup so reruns or partial states don’t fail the step.

♻️ Proposed fix
-           rm vocos-22khz-univ.onnx
-           rm vocos_24khz.onnx
+           rm -f vocos-22khz-univ.onnx
+           rm -f vocos_24khz.onnx
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/run-java-test.yaml around lines 354 - 363, The cleanup
step currently uses plain rm for standalone files (vocos-22khz-univ.onnx and
vocos_24khz.onnx) which can fail if the files are already absent; change those
two lines to use rm -f to make the cleanup idempotent (leave the existing
wildcard rm -rf lines alone for directories/patterns).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@sherpa-onnx/jni/offline-tts.cc`:
- Around line 205-240: The code calls env->GetObjectClass(zipvoice) and
subsequent SHERPA_ONNX_JNI_READ_* macros without checking whether jobject
zipvoice is null, which can cause JNI failures; update the logic around
fid/zipvoice to check if zipvoice == nullptr after env->GetObjectField(model,
fid) and only call env->GetObjectClass(zipvoice) and execute the
SHERPA_ONNX_JNI_READ_STRING / SHERPA_ONNX_JNI_READ_FLOAT reads when zipvoice is
non-null, otherwise skip those reads and either leave ans.model.zipvoice fields
at safe defaults or run the existing validation/error path so null configs are
handled cleanly.

---

Nitpick comments:
In @.github/workflows/run-java-test.yaml:
- Around line 354-363: The cleanup step currently uses plain rm for standalone
files (vocos-22khz-univ.onnx and vocos_24khz.onnx) which can fail if the files
are already absent; change those two lines to use rm -f to make the cleanup
idempotent (leave the existing wildcard rm -rf lines alone for
directories/patterns).

In `@kotlin-api-examples/test_zipvoice_tts.kt`:
- Around line 27-41: The OfflineTts instance (tts) is only released on the
success path; wrap the generation and save calls in a try/finally so
tts.release() always runs on error or success. Specifically, enclose the call to
tts.generateWithConfigAndCallback(...) and audio.save(...) inside a try block
and call tts.release() in the finally block (still referencing OfflineTts,
generateWithConfigAndCallback, audio.save, and tts.release) to ensure the native
pointer is freed even if an exception is thrown.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f4294e07-0759-4a60-b58a-334d3add3aee

📥 Commits

Reviewing files that changed from the base of the PR and between a19f4b0 and df5eb10.

📒 Files selected for processing (12)
  • .github/workflows/jni.yaml
  • .github/workflows/run-java-test.yaml
  • java-api-examples/README.md
  • java-api-examples/ZipVoiceTts.java
  • java-api-examples/run-zipvoice-tts.sh
  • kotlin-api-examples/run.sh
  • kotlin-api-examples/test_zipvoice_tts.kt
  • sherpa-onnx/java-api/Makefile
  • sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineTtsModelConfig.java
  • sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineTtsZipVoiceModelConfig.java
  • sherpa-onnx/jni/offline-tts.cc
  • sherpa-onnx/kotlin-api/Tts.kt

Comment on lines +205 to +240
fid = env->GetFieldID(
model_config_cls, "zipvoice",
"Lcom/k2fsa/sherpa/onnx/OfflineTtsZipVoiceModelConfig;");
jobject zipvoice = env->GetObjectField(model, fid);
jclass zipvoice_cls = env->GetObjectClass(zipvoice);

SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.tokens, tokens, zipvoice_cls,
zipvoice);

SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.encoder, encoder, zipvoice_cls,
zipvoice);

SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.decoder, decoder, zipvoice_cls,
zipvoice);

SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.vocoder, vocoder, zipvoice_cls,
zipvoice);

SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.data_dir, dataDir, zipvoice_cls,
zipvoice);

SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.lexicon, lexicon, zipvoice_cls,
zipvoice);

SHERPA_ONNX_JNI_READ_FLOAT(ans.model.zipvoice.feat_scale, featScale,
zipvoice_cls, zipvoice);

SHERPA_ONNX_JNI_READ_FLOAT(ans.model.zipvoice.t_shift, tShift, zipvoice_cls,
zipvoice);

SHERPA_ONNX_JNI_READ_FLOAT(ans.model.zipvoice.target_rms, targetRms,
zipvoice_cls, zipvoice);

SHERPA_ONNX_JNI_READ_FLOAT(ans.model.zipvoice.guidance_scale, guidanceScale,
zipvoice_cls, zipvoice);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard zipvoice object before calling GetObjectClass.

At Line 209, zipvoice is used without a null check. A null zipvoice config can trigger JNI failure before config validation, instead of cleanly falling back to defaults/error handling.

🛠️ Proposed fix
   fid = env->GetFieldID(
       model_config_cls, "zipvoice",
       "Lcom/k2fsa/sherpa/onnx/OfflineTtsZipVoiceModelConfig;");
   jobject zipvoice = env->GetObjectField(model, fid);
-  jclass zipvoice_cls = env->GetObjectClass(zipvoice);
-
-  SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.tokens, tokens, zipvoice_cls,
-                              zipvoice);
-
-  SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.encoder, encoder, zipvoice_cls,
-                              zipvoice);
-
-  SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.decoder, decoder, zipvoice_cls,
-                              zipvoice);
-
-  SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.vocoder, vocoder, zipvoice_cls,
-                              zipvoice);
-
-  SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.data_dir, dataDir, zipvoice_cls,
-                              zipvoice);
-
-  SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.lexicon, lexicon, zipvoice_cls,
-                              zipvoice);
-
-  SHERPA_ONNX_JNI_READ_FLOAT(ans.model.zipvoice.feat_scale, featScale,
-                             zipvoice_cls, zipvoice);
-
-  SHERPA_ONNX_JNI_READ_FLOAT(ans.model.zipvoice.t_shift, tShift, zipvoice_cls,
-                             zipvoice);
-
-  SHERPA_ONNX_JNI_READ_FLOAT(ans.model.zipvoice.target_rms, targetRms,
-                             zipvoice_cls, zipvoice);
-
-  SHERPA_ONNX_JNI_READ_FLOAT(ans.model.zipvoice.guidance_scale, guidanceScale,
-                             zipvoice_cls, zipvoice);
+  jclass zipvoice_cls = nullptr;
+  if (zipvoice != nullptr) {
+    zipvoice_cls = env->GetObjectClass(zipvoice);
+
+    SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.tokens, tokens, zipvoice_cls,
+                                zipvoice);
+    SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.encoder, encoder,
+                                zipvoice_cls, zipvoice);
+    SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.decoder, decoder,
+                                zipvoice_cls, zipvoice);
+    SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.vocoder, vocoder,
+                                zipvoice_cls, zipvoice);
+    SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.data_dir, dataDir,
+                                zipvoice_cls, zipvoice);
+    SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.lexicon, lexicon,
+                                zipvoice_cls, zipvoice);
+    SHERPA_ONNX_JNI_READ_FLOAT(ans.model.zipvoice.feat_scale, featScale,
+                               zipvoice_cls, zipvoice);
+    SHERPA_ONNX_JNI_READ_FLOAT(ans.model.zipvoice.t_shift, tShift, zipvoice_cls,
+                               zipvoice);
+    SHERPA_ONNX_JNI_READ_FLOAT(ans.model.zipvoice.target_rms, targetRms,
+                               zipvoice_cls, zipvoice);
+    SHERPA_ONNX_JNI_READ_FLOAT(ans.model.zipvoice.guidance_scale, guidanceScale,
+                               zipvoice_cls, zipvoice);
+  }
📝 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
fid = env->GetFieldID(
model_config_cls, "zipvoice",
"Lcom/k2fsa/sherpa/onnx/OfflineTtsZipVoiceModelConfig;");
jobject zipvoice = env->GetObjectField(model, fid);
jclass zipvoice_cls = env->GetObjectClass(zipvoice);
SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.tokens, tokens, zipvoice_cls,
zipvoice);
SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.encoder, encoder, zipvoice_cls,
zipvoice);
SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.decoder, decoder, zipvoice_cls,
zipvoice);
SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.vocoder, vocoder, zipvoice_cls,
zipvoice);
SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.data_dir, dataDir, zipvoice_cls,
zipvoice);
SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.lexicon, lexicon, zipvoice_cls,
zipvoice);
SHERPA_ONNX_JNI_READ_FLOAT(ans.model.zipvoice.feat_scale, featScale,
zipvoice_cls, zipvoice);
SHERPA_ONNX_JNI_READ_FLOAT(ans.model.zipvoice.t_shift, tShift, zipvoice_cls,
zipvoice);
SHERPA_ONNX_JNI_READ_FLOAT(ans.model.zipvoice.target_rms, targetRms,
zipvoice_cls, zipvoice);
SHERPA_ONNX_JNI_READ_FLOAT(ans.model.zipvoice.guidance_scale, guidanceScale,
zipvoice_cls, zipvoice);
fid = env->GetFieldID(
model_config_cls, "zipvoice",
"Lcom/k2fsa/sherpa/onnx/OfflineTtsZipVoiceModelConfig;");
jobject zipvoice = env->GetObjectField(model, fid);
jclass zipvoice_cls = nullptr;
if (zipvoice != nullptr) {
zipvoice_cls = env->GetObjectClass(zipvoice);
SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.tokens, tokens, zipvoice_cls,
zipvoice);
SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.encoder, encoder,
zipvoice_cls, zipvoice);
SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.decoder, decoder,
zipvoice_cls, zipvoice);
SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.vocoder, vocoder,
zipvoice_cls, zipvoice);
SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.data_dir, dataDir,
zipvoice_cls, zipvoice);
SHERPA_ONNX_JNI_READ_STRING(ans.model.zipvoice.lexicon, lexicon,
zipvoice_cls, zipvoice);
SHERPA_ONNX_JNI_READ_FLOAT(ans.model.zipvoice.feat_scale, featScale,
zipvoice_cls, zipvoice);
SHERPA_ONNX_JNI_READ_FLOAT(ans.model.zipvoice.t_shift, tShift, zipvoice_cls,
zipvoice);
SHERPA_ONNX_JNI_READ_FLOAT(ans.model.zipvoice.target_rms, targetRms,
zipvoice_cls, zipvoice);
SHERPA_ONNX_JNI_READ_FLOAT(ans.model.zipvoice.guidance_scale, guidanceScale,
zipvoice_cls, zipvoice);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sherpa-onnx/jni/offline-tts.cc` around lines 205 - 240, The code calls
env->GetObjectClass(zipvoice) and subsequent SHERPA_ONNX_JNI_READ_* macros
without checking whether jobject zipvoice is null, which can cause JNI failures;
update the logic around fid/zipvoice to check if zipvoice == nullptr after
env->GetObjectField(model, fid) and only call env->GetObjectClass(zipvoice) and
execute the SHERPA_ONNX_JNI_READ_STRING / SHERPA_ONNX_JNI_READ_FLOAT reads when
zipvoice is non-null, otherwise skip those reads and either leave
ans.model.zipvoice fields at safe defaults or run the existing validation/error
path so null configs are handled cleanly.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces support for ZipVoice TTS models in both the Java and Kotlin APIs. It includes new example applications, corresponding run scripts, and updates to the configuration classes and JNI bindings. The changes are well-integrated and follow the existing project structure. I have one suggestion to improve consistency across the new shell scripts.

# to download more models
if [ ! -f ./sherpa-onnx-zipvoice-distill-int8-zh-en-emilia/encoder.int8.onnx ]; then
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/sherpa-onnx-zipvoice-distill-int8-zh-en-emilia.tar.bz2
tar xf sherpa-onnx-zipvoice-distill-int8-zh-en-emilia.tar.bz2

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

For consistency with other scripts in the repository, such as kotlin-api-examples/run.sh, it is recommended to use tar xvf instead of tar xf. The v flag provides verbose output, which can be helpful for debugging during file extraction.

Suggested change
tar xf sherpa-onnx-zipvoice-distill-int8-zh-en-emilia.tar.bz2
tar xvf sherpa-onnx-zipvoice-distill-int8-zh-en-emilia.tar.bz2

@csukuangfj
csukuangfj merged commit 286d693 into k2-fsa:master Mar 18, 2026
5 checks passed
@csukuangfj
csukuangfj deleted the jni-zipvoice branch March 18, 2026 05:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants