Kazeia-engine/dist/cosyvoice/kazeia_cosyvoice_jni.cpp

120 lines
4.8 KiB
C++

// JNI bridge pour CosyVoice3 distillé (cosyvoice.cpp, CPU-only, pas de FastRPC/SELinux).
// Pattern aligne sur kazeia_tts_jni.cpp (package com.kazeia.tts).
// load(gguf) -> handle modele ; loadVoice(handle, cvps) -> handle voix ; synthesize(voice, text, speed) -> float PCM 24kHz.
#include <jni.h>
#include <cstdlib>
#include <cstring>
#include <string>
#include "cosyvoice.h"
// NB : on n'appelle AUCUN symbole ggml depuis la JNI (ggml est statique+masque dans libcosyvoice).
// -> la JNI ne lie que libcosyvoice.so. Chargement via l'API haut-niveau (backend interne).
namespace {
struct Voice {
cosyvoice_prompt_speech_t ps = nullptr;
cosyvoice_prompt_t prompt = nullptr;
cosyvoice_tts_context_t tts = nullptr;
};
const char* cstr(JNIEnv* env, jstring s, std::string& buf) {
const char* p = env->GetStringUTFChars(s, nullptr);
buf = p ? p : "";
if (p) env->ReleaseStringUTFChars(s, p);
return buf.c_str();
}
} // namespace
extern "C" {
// nativeLoad(gguf, nfe, nThreads) : configure le mode reflow distille + charge le modele. Retourne le handle (0 si echec).
JNIEXPORT jlong JNICALL
Java_com_kazeia_tts_CosyVoiceJni_nativeLoad(JNIEnv* env, jobject, jstring jgguf, jint nfe, jint nThreads) {
std::string gguf;
cstr(env, jgguf, gguf);
// mode student distille : schedule lineaire + CFG off + N pas (lu par le loader patche)
setenv("CV_REFLOW", "1", 1);
char nbuf[8];
snprintf(nbuf, sizeof(nbuf), "%d", nfe > 0 ? (int)nfe : 2);
setenv("CV_NFE", nbuf, 1);
// streaming chunk-causal + cache K/V prompt persistant (2c) : flow ÷4 sur les tours 2+ (meme voix),
// RTF<1 soutenu sur V79. CV_PROMPT_DET = bruit prompt deterministe (prerequis du cache, qualite preservee).
setenv("CV_STREAM", "1", 1);
setenv("CV_STREAM_CACHE", "1", 1);
setenv("CV_PROMPT_DET", "1", 1);
// enregistrement des backends ggml (interne a libcosyvoice ; indispensable avant load)
cosyvoice_init_backend_from_path(nullptr);
cosyvoice_context_params_t params;
cosyvoice_init_default_context_params(&params);
// backend par defaut (CPU) + threads = concurrence materielle (8 sur V79) gere en interne.
// nThreads conserve dans l'API mais non force ici (le defaut = nb de coeurs, optimal V79).
(void)nThreads;
cosyvoice_context_t ctx = cosyvoice_load_from_file_with_params(gguf.c_str(), &params);
return reinterpret_cast<jlong>(ctx);
}
// nativeLoadVoice(handle, cvps) : charge un profil voix pre-enrole. Retourne le handle voix (0 si echec).
JNIEXPORT jlong JNICALL
Java_com_kazeia_tts_CosyVoiceJni_nativeLoadVoice(JNIEnv* env, jobject, jlong handle, jstring jcvps) {
auto ctx = reinterpret_cast<cosyvoice_context_t>(handle);
if (!ctx) return 0;
std::string cvps;
cstr(env, jcvps, cvps);
auto* v = new Voice();
v->ps = cosyvoice_prompt_speech_load_from_file(cvps.c_str());
if (!v->ps) { delete v; return 0; }
v->prompt = cosyvoice_prompt_init_from_prompt_speech(ctx, v->ps);
if (!v->prompt) { cosyvoice_prompt_speech_free(v->ps); delete v; return 0; }
v->tts = cosyvoice_tts_context_new(ctx, v->prompt);
if (!v->tts) { cosyvoice_prompt_free(v->prompt); cosyvoice_prompt_speech_free(v->ps); delete v; return 0; }
// build CPU-only = NO_ICU : la normalisation de texte ICU n'est pas dispo (normaliser cote app si besoin).
// split = true : decoupe par phrase (premier son plus rapide, streaming).
cosyvoice_tts_context_set_text_normalization_enabled(v->tts, false);
cosyvoice_tts_context_set_split_text_enabled(v->tts, true);
return reinterpret_cast<jlong>(v);
}
// nativeSynthesize(voice, text, speed) : synthese zero-shot. Retourne float[] PCM 24kHz mono (null si echec).
JNIEXPORT jfloatArray JNICALL
Java_com_kazeia_tts_CosyVoiceJni_nativeSynthesize(JNIEnv* env, jobject, jlong voiceHandle, jstring jtext, jfloat speed) {
auto* v = reinterpret_cast<Voice*>(voiceHandle);
if (!v || !v->tts) return nullptr;
std::string text;
cstr(env, jtext, text);
cosyvoice_generated_speech result = {};
if (!cosyvoice_tts_zero_shot(v->tts, text.c_str(), speed > 0 ? speed : 1.0f, &result)
|| !result.data || result.length == 0) {
return nullptr;
}
jfloatArray out = env->NewFloatArray(result.length);
if (out) env->SetFloatArrayRegion(out, 0, result.length, result.data);
return out;
}
JNIEXPORT void JNICALL
Java_com_kazeia_tts_CosyVoiceJni_nativeFreeVoice(JNIEnv*, jobject, jlong voiceHandle) {
auto* v = reinterpret_cast<Voice*>(voiceHandle);
if (!v) return;
if (v->tts) cosyvoice_tts_context_free(v->tts);
if (v->prompt) cosyvoice_prompt_free(v->prompt);
if (v->ps) cosyvoice_prompt_speech_free(v->ps);
delete v;
}
JNIEXPORT void JNICALL
Java_com_kazeia_tts_CosyVoiceJni_nativeFree(JNIEnv*, jobject, jlong handle) {
auto ctx = reinterpret_cast<cosyvoice_context_t>(handle);
if (ctx) cosyvoice_free(ctx);
}
} // extern "C"