mirror of
https://github.com/k2-fsa/sherpa-onnx.git
synced 2026-11-10 21:11:03 +08:00
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.
133 lines
4.8 KiB
Java
133 lines
4.8 KiB
Java
// Copyright 2026 Xiaomi Corporation
|
|
|
|
// This file shows how to use a PocketTTS English model
|
|
// for voice cloning.
|
|
import com.k2fsa.sherpa.onnx.*;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
|
|
public class PocketTts {
|
|
public static void main(String[] args) {
|
|
LibraryUtils.enableDebug();
|
|
// please visit
|
|
// https://k2-fsa.github.io/sherpa/onnx/tts/pocket.html
|
|
// to download model files
|
|
String lmFlow = "./sherpa-onnx-pocket-tts-int8-2026-01-26/lm_flow.int8.onnx";
|
|
String lmMain = "./sherpa-onnx-pocket-tts-int8-2026-01-26/lm_main.int8.onnx";
|
|
String encoder = "./sherpa-onnx-pocket-tts-int8-2026-01-26/encoder.onnx";
|
|
String decoder = "./sherpa-onnx-pocket-tts-int8-2026-01-26/decoder.int8.onnx";
|
|
String textConditioner = "./sherpa-onnx-pocket-tts-int8-2026-01-26/text_conditioner.onnx";
|
|
String vocabJson = "./sherpa-onnx-pocket-tts-int8-2026-01-26/vocab.json";
|
|
String tokenScoresJson = "./sherpa-onnx-pocket-tts-int8-2026-01-26/token_scores.json";
|
|
String text =
|
|
"Today as always, men fall into two groups: slaves and free men. Whoever does not have"
|
|
+ " two-thirds of his day for himself, is a slave, whatever he may be: a statesman, a"
|
|
+ " businessman, an official, or a scholar.";
|
|
|
|
OfflineTtsPocketModelConfig pocketModelConfig =
|
|
OfflineTtsPocketModelConfig.builder()
|
|
.setLmMain(lmMain)
|
|
.setLmFlow(lmFlow)
|
|
.setEncoder(encoder)
|
|
.setDecoder(decoder)
|
|
.setTextConditioner(textConditioner)
|
|
.setVocabJson(vocabJson)
|
|
.setTokenScoresJson(tokenScoresJson)
|
|
.build();
|
|
|
|
OfflineTtsModelConfig modelConfig =
|
|
OfflineTtsModelConfig.builder()
|
|
.setPocket(pocketModelConfig)
|
|
.setNumThreads(2)
|
|
.setDebug(true)
|
|
.build();
|
|
|
|
OfflineTtsConfig config = OfflineTtsConfig.builder().setModel(modelConfig).build();
|
|
OfflineTts tts = new OfflineTts(config);
|
|
|
|
String referenceAudioFilename = "./sherpa-onnx-pocket-tts-int8-2026-01-26/test_wavs/bria.wav";
|
|
WaveReader reader = new WaveReader(referenceAudioFilename);
|
|
|
|
GenerationConfig genConfig = new GenerationConfig();
|
|
genConfig.setReferenceAudio(reader.getSamples());
|
|
genConfig.setReferenceSampleRate(reader.getSampleRate());
|
|
genConfig.setNumSteps(5);
|
|
|
|
Map<String, String> extra = new HashMap<>();
|
|
extra.put("temperature", "0.7");
|
|
extra.put("chunk_size", "15");
|
|
|
|
genConfig.setExtra(extra);
|
|
|
|
long start = System.currentTimeMillis();
|
|
GeneratedAudio audio = null;
|
|
|
|
// You can choose one of the following callback style
|
|
// ---------------------------------------------------
|
|
// 1. Anonymous class implementing OfflineTtsCallback
|
|
// ---------------------------------------------------
|
|
if (true) {
|
|
audio =
|
|
tts.generateWithConfigAndCallback(
|
|
text,
|
|
genConfig,
|
|
new OfflineTtsCallback() {
|
|
@Override
|
|
public Integer invoke(float[] samples) {
|
|
// you can play the generated samples in a separate thread
|
|
System.out.println("callback got called with " + samples.length + " samples");
|
|
// 1 = continue, 0 = stop
|
|
return 1;
|
|
}
|
|
});
|
|
}
|
|
|
|
// -------------------------------
|
|
// 2. Lambda implementing OfflineTtsCallback
|
|
// -------------------------------
|
|
if (false) {
|
|
audio =
|
|
tts.generateWithConfigAndCallback(
|
|
text,
|
|
genConfig,
|
|
samples -> {
|
|
System.out.println("Lambda Integer callback: " + samples.length);
|
|
return 1; // continue
|
|
});
|
|
}
|
|
|
|
if (false) {
|
|
audio =
|
|
tts.generateWithConfigAndCallback(
|
|
text,
|
|
genConfig,
|
|
samples -> {
|
|
System.out.println("Consumer: " + samples.length);
|
|
// implicitly, it returns 1 internally
|
|
});
|
|
}
|
|
|
|
if (audio == null) {
|
|
System.err.println("No audio was generated. Please enable at least one callback branch.");
|
|
return;
|
|
}
|
|
|
|
long stop = System.currentTimeMillis();
|
|
|
|
float timeElapsedSeconds = (stop - start) / 1000.0f;
|
|
|
|
float audioDuration = audio.getSamples().length / (float) audio.getSampleRate();
|
|
float real_time_factor = timeElapsedSeconds / audioDuration;
|
|
|
|
String waveFilename = "pocket-tts-bria.wav";
|
|
audio.save(waveFilename);
|
|
System.out.printf("-- elapsed : %.3f seconds\n", timeElapsedSeconds);
|
|
System.out.printf("-- audio duration: %.3f seconds\n", audioDuration);
|
|
System.out.printf("-- real-time factor (RTF): %.3f\n", real_time_factor);
|
|
System.out.printf("-- text: %s\n", text);
|
|
System.out.printf("-- Saved to %s\n", waveFilename);
|
|
|
|
tts.release();
|
|
}
|
|
}
|