Downloads · 30 days
0
Bbkblo/DhVaani-0.5-MNN
DhVaani-0.5-MNN is a text-to-speech model from Bbkblo. Use it when you need text read aloud. It is set up for mnn. The card lists the license as apache-2.0.
DhVaani-0.5 converted to MNN for fully offline, on-device text-to-speech. Ships with a complete, working Android integration — C++ engine, JNI bridge, Kotlin API and a runnable sample app in android/.
Downloads · 30 days
0
Access
Public
Updated Sep 1, 2026
Repo size
333 MB
Likes
0
Public
Click a slice to open those files.
.mnn143 MB · 97%
From the Hugging Face model README
DhVaani-0.5 converted to MNN for fully offline, on-device text-to-speech.
Ships with a complete, working Android integration — C++ engine, JNI bridge,
Kotlin API and a runnable sample app in android/.
v1.1 — models were rebuilt. The
.mnnfiles published before 2026-09-01 produced silence. If you pulled this repo earlier, re-download. Details in What was wrong.
git clone https://huggingface.co/Bbkblo/DhVaani-0.5-MNN
cd DhVaani-0.5-MNN/android
./tools/fetch_mnn.sh # official MNN 3.6.1 prebuilt .so + headers
./tools/fetch_models.sh # models into sample assets
cp ~/my_voice.wav sample/src/main/assets/dhvaani/prompt.wav
./gradlew :sample:installDebug
val tts = DhVaani.fromAssets(context)
tts.setPrompt(promptPcm, 24000, "exactly what the reference clip says")
val audio = tts.synthesize("नमस्ते दुनिया") // FloatArray, 24 kHz mono
tts.play(audio!!)
Full guide: ANDROID_SETUP.md
| File | Role | Size |
|---|---|---|
text_encoder_int8.mnn | text → text_condition | 5.8 MB |
fm_decoder_int8.mnn | flow-matching decoder (t, x, cond) → v | 119 MB |
vocoder_backbone.mnn | mels → hidden | 13 MB |
mel_fb.bin | mel filterbank + window (C++, flat float32) | 205 KB |
vocos_head.bin | vocos output layer + window (C++) | 2.1 MB |
mel_fb.npz / vocos_head.npz | same data for the Python reference | 0.2 / 2.1 MB |
tokens.txt | vocabulary | 8 KB |
model.json | architecture config | 0.7 KB |
android/ | library module + sample app + tools | — |
dhvaani_mnn.py | Python reference pipeline on MNN | — |
dhvaani_torchfree.py | Python reference pipeline on ONNX | — |
Runtime footprint: ~140 MB of assets.
| Model | Inputs | Output |
|---|---|---|
text_encoder_int8.mnn | tokens[1,N] i32, prompt_tokens[1,M] i32, prompt_features_len (rank-0 i32), speed (rank-0 f32) | text_condition[1,T,100] |
fm_decoder_int8.mnn | t (rank-0 f32), x[1,T,100], text_condition[1,T,100], speech_condition[1,T,100], guidance_scale (rank-0 f32) | v[1,T,100] |
vocoder_backbone.mnn | mels[1,100,T] | hidden[1,T,512] |
Two things that bite people:
MNN::Express::Module. The stock MNNNetInstance Java API exposes only the
legacy Interpreter/Session path and cannot run these models. That's why
this repo ships its own JNI layer.{} and not {1}.The mel filterbank, the vocos output projection and the ISTFT are not in the
graphs — they are implemented in android/dhvaani/src/main/cpp/dhvaani.cpp.
Measured, not assumed. Reproduce with dhvaani_mnn.py and android/tools/cli_test.cpp.
MNN vs fp32 ONNX, identical inputs:
| Module | Correlation |
|---|---|
text_encoder_int8.mnn | +0.99997 |
vocoder_backbone.mnn | +0.99972 |
| final mel spectrogram, full 8-step pipeline | +0.99430 |
C++ engine components vs the Python reference:
| Component | Result |
|---|---|
| mel filterbank / STFT | corr +1.0000 |
| ISTFT | corr +1.0000, max diff 2.5e-07 |
| tokenizer | exact, 38/38 ids |
| resampler 48k→24k | max error 1.1e-05 |
Android build (NDK r27c, arm64-v8a, android-24): libdhvaani.so 1.1 MB,
9/9 JNI symbols exported, 8/8 undefined MNN symbols resolve against the official
prebuilt libMNN_Express.so. armeabi-v7a builds too.
Sample output: test/dhvaani_mnn_cpp.wav.
The original .mnn files were converted from the INT8 ONNX exports.
MNNConvert does not correctly fold ONNX QDQ (QuantizeLinear/DequantizeLinear)
nodes in these graphs. The models loaded and ran without any error, but the
numbers were garbage:
| Module (v1.0) | Correlation vs fp32 ONNX |
|---|---|
text_encoder_int8.mnn | −0.087 |
fm_decoder_int8.mnn | +0.0007 (i.e. noise) |
vocoder_backbone.mnn | fine — it was converted from fp32 |
The flow decoder's output barely changed with t, mel values came out in the
range [-132, +187] instead of [-7, +4], and the rendered audio was silence
(RMS 0.0001, zero voiced frames).
Fix: convert from the fp32 ONNX and let MNN do its own weight quantization. Same file size, numerically faithful:
mnnconvert -f ONNX --modelFile fm_decoder.onnx \
--MNNModel fm_decoder_int8.mnn --bizCode fm \
--weightQuantBits 8 --weightQuantAsymmetric
pip install mnn==3.6.1 numpy soundfile scipy
python3 dhvaani_mnn.py \
--text "आपका टेक्स्ट" \
--prompt-wav prompt.wav --prompt-text "prompt ka text" \
--dir . --out out.wav --num-step 8 --threads 4
promptText must match it
word for word. This matters more than any inference setting.numStep is the quality/speed dial and dominates runtime (cost is linear).
4 = rough, 8 = balanced, 16 = best.