A JNI wrapper for Piper, a fast, local neural text-to-speech system.
Java >= 17 is supported. This library aims to support the following platforms:
- Windows x86_64
- Linux x86_64/arm64 (built with Ubuntu Focal Fossa, GLIBC version 2.31)
- macOS x86_64/arm64 (built for macOS 14 Sonoma and newer)
The JAR includes the piper-jni native library, which contains the Piper source, and the shared libraries it depends on: espeak-ng and onxxruntime.
The package is distributed through Maven Central:
<dependency>
<groupId>io.github.jvoice-project</groupId>
<artifactId>piper-jni</artifactId>
<!-- replace $version with a specific version -->
<version>$version</version>
</dependency>repositories {
mavenCentral()
}
dependencies {
implementation 'io.github.jvoice-project:piper-jni:+' // gets the latest version
}All releases are signed with the PGP key 047A5F7D27B9F2408F31EB6D577886B2F4A44CB7,
you can find the public key on keys.openpgp.org.
To import the public key, use the following command:
gpg --keyserver keys.openpgp.org --recv-keys 047A5F7D27B9F2408F31EB6D577886B2F4A44CB7You can also find the package's jar attached to each release.
String textToSpeak = "Hello, world!";
// Load the native library before using PiperJNI
PiperJNI.loadLibrary();
try (var voice = PiperJNI.loadVoice(Paths.get("/path/to/en_US-lessac-medium.onnx"), Path.of("/path/to/en_US-lessac-medium.onnx.json"))) {
int sampleRate = voice.getSampleRate();
short[] samples = voice.textToAudio(textToSpeak);
// Do something with the samples...
}When synthesizing text, the Java API layer produces audio with the following specification:
| Attribute | Value / Format |
|---|---|
| Container / Header | Raw PCM stream (no header). Wrap with AudioSystem.write for RIFF WAV (.wav). |
| Data Type | short[] (Java 16-bit primitive array) |
| Encoding Format | 16-bit Signed Linear PCM (PCM_SIGNED) |
| Sample Value Range | -32768 to 32767 |
| Channels | 1 (Mono) |
| Sample Rate | Model-dependent, retrieved via voice.getSampleRate() (e.g. 22050, 16000, 24000 Hz) |
| Endianness | Host native in memory; Little-Endian when serialized to .wav file |
You need to have Java >= 17 and C++ setup.
After cloning the project, you need to init the piper submodule by running:
git submodule update --initYou can build for all supported Linux platforms (amd64, arm64) using Docker:
./scripts/build_linux-all.shThis uses docker buildx to build the native libraries and places them in src/main/resources/native.
If you prefer to build locally for your current platform:
- Linux: Run
./scripts/build_linux.sh - macOS: Run
./scripts/build_macos.sh - Windows: Run
.\scripts\build_win.cmd
These scripts compile the JNI shared library and copy it directly to the corresponding src/main/resources/native folder.
Finally, you can build the Java library, run the tests, and package the JAR with Maven:
./mvnw packageMaven will download the Piper voice and configure the unit tests to use it. Optionally, you can override the voice model name, download URL as well as the text to speak:
./mvnw \
-Dtest.model.name="de_DE-thorsten-medium.onnx" \
-Dtest.model.url="https://huggingface.co/rhasspy/piper-voices/resolve/main/de/de_DE/thorsten/medium" \
-Dtest.text="Guten Tag!" \
packageIf you want to add any missing piper functionality, you need to:
- Add the native method description in
PiperBinding.java. - Run the
gen_header.shscript to regenerate theio_github_jvoiceproject_piperjni_internal_PiperBinding.hheader file. - Add the native method implementation in
io_github_jvoiceproject_piperjni_internal_PiperBinding.cpp. - Add a new test for it at
PiperJNITest.java.