58 lines
2.3 KiB
Kotlin
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)
|
|
}
|
|
}
|