91 lines
3.0 KiB
Kotlin
91 lines
3.0 KiB
Kotlin
package com.kazeia.tts
|
|
// Engine TTS Qwen3 in-process (libkazeia_tts.so). Load une fois, synthesize N fois.
|
|
// Le bridge natif est dans dist/jni/kazeia_tts_jni.cpp ; build via dist/build_kazeia_tts.sh
|
|
// ou dist/CMakeLists.txt.
|
|
//
|
|
// Empreinte RAM typique : ~3.5 GB (talker f32 ~1.6 GB + CP f16 mixte ~250 MB +
|
|
// decoder ~325 MB + vocab Qwen3 ~50 MB + fixtures text_embed/tp_* ~1.2 GB).
|
|
|
|
class TtsJni {
|
|
external fun nativeLoad(
|
|
talkerGguf: String,
|
|
vocabGguf: String,
|
|
dumpDir: String,
|
|
useHtp: Boolean,
|
|
nThreads: Int,
|
|
cpUseCache: Boolean
|
|
): Long
|
|
|
|
external fun nativeSynthesize(
|
|
handle: Long,
|
|
text: String,
|
|
outWavPath: String,
|
|
maxSteps: Int,
|
|
seed: Int,
|
|
cpTemp: Float, cpTopK: Int, cpTopP: Float, cpRepPenalty: Float,
|
|
talkerTemp: Float, talkerTopK: Int, talkerTopP: Float, talkerRepPenalty: Float
|
|
): IntArray // [err, frames, total_ms, prefill_ms, talker_ms, cp_ms, decoder_ms]
|
|
|
|
external fun nativeFree(handle: Long)
|
|
|
|
companion object { init { System.loadLibrary("kazeia_tts") } }
|
|
}
|
|
|
|
data class TtsResult(
|
|
val frames: Int,
|
|
val audioMs: Int,
|
|
val totalMs: Int,
|
|
val prefillMs: Int,
|
|
val talkerMs: Int,
|
|
val cpMs: Int,
|
|
val decoderMs: Int
|
|
)
|
|
|
|
// Configuration sampling. Défauts validés sur fixture FR (cf project_tts_session_28may).
|
|
data class TtsSampling(
|
|
// Défauts validés à l'écoute (top_p=0.95 + rep=1.10 moins robotique vs 1.0/1.05).
|
|
val cpTemp: Float = 0.9f, val cpTopK: Int = 50, val cpTopP: Float = 0.95f, val cpRepPenalty: Float = 1.10f,
|
|
val talkerTemp: Float = 0.9f, val talkerTopK: Int = 50, val talkerTopP: Float = 0.95f, val talkerRepPenalty: Float = 1.10f
|
|
)
|
|
|
|
class TtsEngine(
|
|
talkerGguf: String,
|
|
vocabGguf: String,
|
|
dumpDir: String,
|
|
useHtp: Boolean = false,
|
|
nThreads: Int = 6,
|
|
cpUseCache: Boolean = true
|
|
) {
|
|
private val jni = TtsJni()
|
|
private val h = jni.nativeLoad(talkerGguf, vocabGguf, dumpDir, useHtp, nThreads, cpUseCache)
|
|
init { require(h != 0L) { "TtsEngine: load FAILED (talker=$talkerGguf, vocab=$vocabGguf, dump=$dumpDir)" } }
|
|
|
|
@Throws(IllegalStateException::class)
|
|
fun synthesize(
|
|
text: String,
|
|
outWavPath: String,
|
|
maxSteps: Int = 256,
|
|
seed: Int = 42,
|
|
sampling: TtsSampling = TtsSampling()
|
|
): TtsResult {
|
|
val r = jni.nativeSynthesize(
|
|
h, text, outWavPath, maxSteps, seed,
|
|
sampling.cpTemp, sampling.cpTopK, sampling.cpTopP, sampling.cpRepPenalty,
|
|
sampling.talkerTemp, sampling.talkerTopK, sampling.talkerTopP, sampling.talkerRepPenalty
|
|
)
|
|
if (r[0] != 0) error("TtsEngine.synthesize err=${r[0]} for text=\"$text\"")
|
|
val frames = r[1]
|
|
return TtsResult(
|
|
frames = frames,
|
|
audioMs = frames * 1000 / 12, // 12 frames/s = 80 ms par frame
|
|
totalMs = r[2],
|
|
prefillMs = r[3],
|
|
talkerMs = r[4],
|
|
cpMs = r[5],
|
|
decoderMs = r[6]
|
|
)
|
|
}
|
|
|
|
fun release() { jni.nativeFree(h) }
|
|
}
|