Kazeia-engine/dist/jni/TtsEngine.kt

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) }
}