Kazeia-engine/dist/cosyvoice/CosyVoiceTtsEngine.kt

58 lines
2.3 KiB
Kotlin

package com.kazeia.tts
/**
* Moteur TTS CosyVoice3 distillé (clonage vocal zero-shot, 9 langues : fr, en, de, es, it, ru, zh, ja, ko).
* CPU-only (pas de FastRPC/SELinux). Modèle = GGUF distillé 2-NFE. Voix = artefacts .cvps pré-enrôlés offline.
*
* Empreinte : ~1 Go (modèle) + ~30 Mo / voix chargée. RTF ~1,8 sur SM8750 (génère par phrase pour streaming).
*
* Pour le portugais/arabe et les 14 autres langues : voir le moteur Chatterbox (dispatch par langue côté app).
*/
object CosyVoiceJni {
external fun nativeLoad(ggufPath: String, nfe: Int, nThreads: Int): Long
external fun nativeLoadVoice(handle: Long, cvpsPath: String): Long
external fun nativeSynthesize(voiceHandle: Long, text: String, speed: Float): FloatArray?
external fun nativeFreeVoice(voiceHandle: Long)
external fun nativeFree(handle: Long)
}
class CosyVoiceTtsEngine(
modelGguf: String,
nfe: Int = 2, // pas de flow du student distillé (2 = recommandé, qualité = teacher)
nThreads: Int = 8, // V79 : 8 cœurs
) {
companion object {
const val SAMPLE_RATE = 24_000
@Volatile private var loaded = false
fun ensureLibrary() {
if (!loaded) { System.loadLibrary("kazeia_cosyvoice"); loaded = true }
}
}
private val handle: Long
init {
ensureLibrary()
handle = CosyVoiceJni.nativeLoad(modelGguf, nfe, nThreads)
require(handle != 0L) { "CosyVoice: échec de chargement du modèle $modelGguf" }
}
/** Charge un profil voix pré-enrôlé (.cvps, généré offline depuis la phrase de consentement). */
fun loadVoice(cvpsPath: String): Voice {
val v = CosyVoiceJni.nativeLoadVoice(handle, cvpsPath)
require(v != 0L) { "CosyVoice: échec de chargement de la voix $cvpsPath" }
return Voice(v)
}
/** Synthèse zero-shot : texte → PCM float[-1,1] mono 24 kHz. Génère phrase par phrase en amont pour le streaming. */
fun synthesize(voice: Voice, text: String, speed: Float = 1.0f): FloatArray =
CosyVoiceJni.nativeSynthesize(voice.handle, text, speed)
?: error("CosyVoice: échec de synthèse")
fun release() = CosyVoiceJni.nativeFree(handle)
inner class Voice(internal val handle: Long) {
fun release() = CosyVoiceJni.nativeFreeVoice(handle)
}
}