120 lines
4.8 KiB
C++
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(¶ms);
|
|
|
|
// 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(), ¶ms);
|
|
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"
|