Skip to content

Refactor JNI - #3107

Merged
csukuangfj merged 1 commit into
k2-fsa:masterfrom
csukuangfj:java-type-safe-callback
Jan 29, 2026
Merged

csukuangfj merged 1 commit into
k2-fsa:masterfrom
csukuangfj:java-type-safe-callback

Conversation

@csukuangfj

@csukuangfj csukuangfj commented Jan 29, 2026 •

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

Release Notes

  • New Features

    • Added ability to query the number of speakers in TTS engine via new API method.
    • Extended TTS callback support with consumer-based variants for flexible audio stream handling.
    • Added configuration-based callback generation method for advanced use cases.
  • Bug Fixes

    • Improved error handling across multiple components to explicitly report initialization failures instead of silent failures.
  • Documentation

    • Added Javadoc clarification for callback behavior.

✏️ Tip: You can customize this high-level summary in your review settings.

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

coderabbitai Bot commented Jan 29, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR adds defensive null-pointer validation to constructors across nine native-backed Java classes, expands the OfflineTts API with getNumSpeakers() and Consumer-based callback overloads alongside OfflineTtsCallback variants, and unifies the JNI callback mechanism in offline-tts.cc from multiple specialized handlers to a single CallCallback pattern.

Changes

Cohort / File(s) Summary
Constructor Validation Guards
AudioTagging.java, KeywordSpotter.java, OfflinePunctuation.java, OfflineRecognizer.java, OfflineSpeakerDiarization.java, OfflineSpeechDenoiser.java, OnlineRecognizer.java, SpeakerEmbeddingExtractor.java, SpokenLanguageIdentification.java, Vad.java
Added identical null-pointer validation pattern: after native object creation via newFromFile(config), throw IllegalArgumentException if ptr == 0 with descriptive error message.
OfflineTts API Expansion
OfflineTts.java
Added getNumSpeakers() public method; introduced multiple overloads of generateWithCallback() accepting both OfflineTtsCallback and Consumer<float[]> parameters; added adapter methods converting Consumer<float[]> to OfflineTtsCallback; updated native method signatures to use OfflineTtsCallback instead of Object; added constructor validation and Consumer import.
Callback Documentation
OfflineTtsCallback.java
Added Javadoc comments to invoke(float[] samples) method documenting parameter semantics and return convention (1 to continue, 0 to stop).
JNI Callback Unification
offline-tts.cc
Replaced multiple distinct callback handlers (Consumer, Function, invoke-based paths) with single unified CallCallback mechanism; simplified callback handling by removing specialized wrappers and fallback logic; updated generateWithCallbackImpl and generateWithConfigImpl to use uniform callback pattern with null-callback returning 1.
Example Code Cleanup
PocketTts.java
Removed unused imports (java.util.function.Consumer, java.util.function.Function); simplified callback lambdas by removing explicit Consumer wrapper and relying on implicit behavior; replaced Function<float[], Integer> lambda with simpler variant that prints and implicitly returns 1.

Sequence Diagram

sequenceDiagram
    participant JavaApp as Java Application
    participant OfflineTts as OfflineTts.java
    participant Adapter as Callback Adapter
    participant JNI as offline-tts.cc
    participant Native as Native TTS Engine

    JavaApp->>OfflineTts: generateWithCallback(text, Consumer)
    OfflineTts->>Adapter: convert Consumer to OfflineTtsCallback
    OfflineTts->>JNI: generateWithCallbackImpl(ptr, text, callback)
    JNI->>Native: Generate(config, callback_wrapper)
    loop For each audio chunk
        Native->>JNI: callback_wrapper(audio_chunk)
        JNI->>Adapter: convert float[] chunk
        Adapter->>OfflineTts: invoke(float[])
        OfflineTts->>JavaApp: Consumer.accept(float[])
        JavaApp-->>OfflineTts: (implicit return 1)
        OfflineTts-->>JNI: return 1 (continue)
    end
    JNI-->>OfflineTts: GeneratedAudio
    OfflineTts-->>JavaApp: return GeneratedAudio
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Add Zipvoice #2487: Overlaps with OfflineTts callback type changes and getNumSpeakers addition in Generate API for Zipvoice integration.
  • Add Java and Kotlin API for PocketTTS #3095: Related through PocketTts callback simplification and JNI callback refactoring in offline-tts.cc.
  • Refactor JNI #2661: Related at the native construction boundary; this PR adds ptr==0 checks while that PR modifies newFromFile to return 0 on config failure.

Suggested labels

size:XL

Poem

🐰 Callbacks now flow with graceful ease,
Consumers adapt where structures please,
Null pointers caught before they break,
Native guards for safety's sake,
A unified path the engines take! 🎵

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Refactor JNI' is vague and generic, using a non-descriptive term that doesn't convey meaningful information about the specific changes made. Use a more descriptive title that clarifies the main objective, such as 'Add native pointer validation and type-safe callbacks to TTS and recognizers' or 'Implement null-pointer checks in JNI constructors and refactor callback handling'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing touches
  • 📝 Generate docstrings

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 @csukuangfj, 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 refactors the Java Native Interface (JNI) layer for the sherpa-onnx library, primarily enhancing the OfflineTts component. The changes introduce more robust error handling in various API constructors by validating native object initialization. Furthermore, the callback mechanism for Text-to-Speech operations has been streamlined, offering more convenient java.util.function.Consumer based interfaces in Java while simplifying the underlying C++ JNI implementation.

Highlights

  • Improved Error Handling in Constructors: Added IllegalArgumentException throws in constructors of several sherpa-onnx Java API classes (e.g., AudioTagging, KeywordSpotter, OfflineTts) if the native object creation (newFromFile) returns a null pointer (0). This makes the API more robust by failing fast on invalid configurations.
  • Streamlined Offline TTS Callbacks: The OfflineTts class now provides new generateWithCallback and generateWithConfigAndCallback overloads that accept java.util.function.Consumer<float[]> for simpler, more idiomatic Java callback usage. This reduces boilerplate for users who only need to consume audio samples.
  • Simplified JNI Callback Implementation: The native C++ code (offline-tts.cc) has been refactored to remove separate handling for Consumer and Function interfaces. All callbacks are now handled uniformly through the OfflineTtsCallback interface, simplifying the JNI bridge logic.
  • Removed Unused Imports and Code: The PocketTts.java example file was cleaned up by removing unused Consumer and Function imports and simplifying lambda expressions, reflecting the changes in the OfflineTts API.
  • Added getNumSpeakers() to OfflineTts: A new method getNumSpeakers() was added to the OfflineTts class, providing access to the number of speakers supported by the TTS engine.
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.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

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. ↩

@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 significantly refactors the JNI layer, especially for TTS callbacks, enhancing type safety by replacing generic Object types with the specific OfflineTtsCallback functional interface. This is a great improvement. The introduction of Consumer<float[]> overloads provides a more convenient API for users. Additionally, the widespread addition of null checks in constructors after native object creation improves the robustness of the code. The changes are well-implemented and raise the quality of the API. I have a few minor suggestions to further improve code readability and maintainability.

Comment on lines +61 to +63
) {

return generateWithCallback(text, sid, 1.0f, consumer);

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

This method contains an unnecessary blank line. Please remove it to maintain consistent code formatting.

Suggested change
) {
return generateWithCallback(text, sid, 1.0f, consumer);
) {
return generateWithCallback(text, sid, 1.0f, consumer);

Comment on lines +487 to +494
std::function<int32_t(const float *, int32_t, float)> callback_wrapper =
[env, callback](const float *samples, int32_t n, float) -> int32_t {
jfloatArray samples_arr = env->NewFloatArray(n);
env->SetFloatArrayRegion(samples_arr, 0, n, samples);
int32_t ret = CallCallback(env, callback, samples_arr);
env->DeleteLocalRef(samples_arr);
return ret;
};

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

This callback_wrapper lambda is identical to the one in Java_com_k2fsa_sherpa_onnx_OfflineTts_generateWithCallbackImpl on lines 456-463. To avoid code duplication and improve maintainability, I suggest extracting this logic into a static helper function.

For example:

static std::function<int32_t(const float *, int32_t, float)>
CreateCallbackWrapper(JNIEnv *env, jobject callback) {
  return [env, callback](const float *samples, int32_t n, float) -> int32_t {
    jfloatArray samples_arr = env->NewFloatArray(n);
    env->SetFloatArrayRegion(samples_arr, 0, n, samples);
    int32_t ret = CallCallback(env, callback, samples_arr);
    env->DeleteLocalRef(samples_arr);
    return ret;
  };
}

You could then use this helper in both generateWithCallbackImpl and generateWithConfigImpl.

@csukuangfj
csukuangfj requested a review from Copilot January 29, 2026 02:30
@csukuangfj
csukuangfj merged commit 8dc6244 into k2-fsa:master Jan 29, 2026
5 checks passed
@csukuangfj
csukuangfj deleted the java-type-safe-callback branch January 29, 2026 02:32

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

This PR refactors the JNI layer for offline TTS functionality, simplifying callback handling and improving error handling across Java API classes. The refactoring removes direct JNI support for Consumer and Function callbacks in favor of a single OfflineTtsCallback interface, while maintaining backward compatibility through Java-level adapter methods.

Changes:

  • Simplified JNI callback mechanism by removing CallConsumerCallback and CallFunctionCallback functions, replacing them with a unified CallCallback that only handles OfflineTtsCallback
  • Added null-safety checks for callbacks in JNI layer, avoiding unnecessary lambda creation when callback is null
  • Added comprehensive error handling to constructors of 10 Java classes, throwing IllegalArgumentException when native object creation fails
  • Introduced getNumSpeakers() method to query speaker count from TTS engine
  • Added Consumer-based convenience overloads for callback methods, internally converting to OfflineTtsCallback
  • Added Javadoc to OfflineTtsCallback interface clarifying return value semantics

Reviewed changes

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

Show a summary per file
File Description
sherpa-onnx/jni/offline-tts.cc Refactored callback handling to use single unified approach, added null callback checks
sherpa-onnx/java-api/.../OfflineTts.java Changed callback parameter types to specific interface, added Consumer overloads, added getNumSpeakers() method, added error handling
sherpa-onnx/java-api/.../OfflineTtsCallback.java Added Javadoc documentation for callback behavior
sherpa-onnx/java-api/.../Vad.java Added constructor error handling
sherpa-onnx/java-api/.../SpokenLanguageIdentification.java Added constructor error handling
sherpa-onnx/java-api/.../SpeakerEmbeddingExtractor.java Added constructor error handling
sherpa-onnx/java-api/.../OnlineRecognizer.java Added constructor error handling
sherpa-onnx/java-api/.../OfflineSpeechDenoiser.java Added constructor error handling
sherpa-onnx/java-api/.../OfflineSpeakerDiarization.java Added constructor error handling, minor whitespace change
sherpa-onnx/java-api/.../OfflineRecognizer.java Added constructor error handling
sherpa-onnx/java-api/.../OfflinePunctuation.java Added constructor error handling
sherpa-onnx/java-api/.../KeywordSpotter.java Added constructor error handling
sherpa-onnx/java-api/.../AudioTagging.java Added constructor error handling
java-api-examples/PocketTts.java Updated example to use new callback API, removed unused imports

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

Comment on lines +23 to +25
public int getNumSpeakers() {
return getNumSpeakers(ptr);
}

Copilot AI Jan 29, 2026

Copy link

Choose a reason for hiding this comment

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

Missing documentation for this public method. Consider adding Javadoc to explain its purpose and return value, similar to the getSampleRate method above it.

Copilot uses AI. Check for mistakes.

private native OfflineSpeakerDiarizationSegment[] processWithCallback(long ptr, float[] samples, OfflineSpeakerDiarizationCallback callback, long arg);
} No newline at end of file
}

Copilot AI Jan 29, 2026

Copy link

Choose a reason for hiding this comment

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

This appears to be an unintentional whitespace-only change. The closing brace was moved from line 60 to line 61 without any functional modification.

Copilot uses AI. Check for mistakes.
int sid,
Consumer<float[]> consumer
) {

Copilot AI Jan 29, 2026

Copy link

Choose a reason for hiding this comment

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

This extra blank line is inconsistent with the formatting of other methods in this file. Consider removing it for consistency.

Copilot uses AI. Check for mistakes.
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