121 lines
5.4 KiB
C++
121 lines
5.4 KiB
C++
// Harness JNI sans VM Java : on dlopen libkazeia_tts.so, on récupère les Java_*
|
|
// symbols, et on les appelle avec un JNIEnv mock minimal (juste les fonctions que
|
|
// kazeia_tts_jni.cpp utilise : GetStringUTFChars, ReleaseStringUTFChars, NewIntArray,
|
|
// SetIntArrayRegion). jstring est juste un alias opaque pour const char*.
|
|
//
|
|
// Permet de valider le bridge JNI sans installer l'app — utile pour CI / régression.
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <unistd.h> // _exit
|
|
#include <dlfcn.h>
|
|
#include <jni.h>
|
|
#include <string>
|
|
|
|
namespace {
|
|
|
|
// Pour notre mock, jstring = const char* tel quel (alias).
|
|
const char * mock_GetStringUTFChars(JNIEnv*, jstring s, jboolean*) { return (const char*)s; }
|
|
void mock_ReleaseStringUTFChars(JNIEnv*, jstring, const char*) {}
|
|
|
|
// jintArray = pointeur vers une struct {int n; jint data[n]} qu'on alloue.
|
|
struct IntArr { int n; jint data[1]; };
|
|
jintArray mock_NewIntArray(JNIEnv*, jsize len) {
|
|
auto* a = (IntArr*)malloc(sizeof(int) + (size_t)len * sizeof(jint));
|
|
a->n = len;
|
|
return (jintArray)a;
|
|
}
|
|
void mock_SetIntArrayRegion(JNIEnv*, jintArray arr, jsize start, jsize len, const jint* buf) {
|
|
auto* a = (IntArr*)arr;
|
|
memcpy(a->data + start, buf, (size_t)len * sizeof(jint));
|
|
}
|
|
|
|
struct MockNativeInterface {
|
|
// Les offsets et le layout doivent matcher jni.h JNINativeInterface. Plutôt que
|
|
// de reproduire toute la struct, on alloue une zone large, et on pose les
|
|
// function pointers AUX BONS OFFSETS (cf jni.h: GetStringUTFChars = entry 169,
|
|
// ReleaseStringUTFChars = 170, NewIntArray = 187, SetIntArrayRegion = 207).
|
|
// Pour rester portable, on utilise directement l'API via le struct JNINativeInterface
|
|
// déclaré dans jni.h.
|
|
JNINativeInterface iface{};
|
|
JNINativeInterface * ptr;
|
|
MockNativeInterface() {
|
|
// On laisse tout à nullptr, on remplit juste les méthodes utilisées.
|
|
iface.GetStringUTFChars = mock_GetStringUTFChars;
|
|
iface.ReleaseStringUTFChars = mock_ReleaseStringUTFChars;
|
|
iface.NewIntArray = mock_NewIntArray;
|
|
iface.SetIntArrayRegion = mock_SetIntArrayRegion;
|
|
ptr = &iface;
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
int main(int argc, char** argv) {
|
|
if (argc < 6) {
|
|
fprintf(stderr,"usage: %s <libkazeia_tts.so> <talker_gguf> <vocab_gguf> <dump_dir> <out.wav>\n", argv[0]);
|
|
return 1;
|
|
}
|
|
const char* lib_path = argv[1];
|
|
const char* talker_gguf = argv[2];
|
|
const char* vocab_gguf = argv[3];
|
|
const char* dump_dir = argv[4];
|
|
const char* out_wav = argv[5];
|
|
|
|
void* dl = dlopen(lib_path, RTLD_NOW | RTLD_LOCAL);
|
|
if (!dl) { fprintf(stderr, "dlopen FAIL %s : %s\n", lib_path, dlerror()); return 2; }
|
|
|
|
using FnLoad = jlong (*)(JNIEnv*, jobject, jstring, jstring, jstring, jboolean, jint, jboolean);
|
|
using FnSynth = jintArray (*)(JNIEnv*, jobject, jlong, jstring, jstring, jint, jint,
|
|
jfloat, jint, jfloat, jfloat, jfloat, jint, jfloat, jfloat);
|
|
using FnFree = void (*)(JNIEnv*, jobject, jlong);
|
|
|
|
auto p_load = (FnLoad)dlsym(dl, "Java_com_kazeia_tts_TtsJni_nativeLoad");
|
|
auto p_synth = (FnSynth)dlsym(dl, "Java_com_kazeia_tts_TtsJni_nativeSynthesize");
|
|
auto p_free = (FnFree)dlsym(dl, "Java_com_kazeia_tts_TtsJni_nativeFree");
|
|
if (!p_load || !p_synth || !p_free) {
|
|
fprintf(stderr, "dlsym FAIL : load=%p synth=%p free=%p\n", (void*)p_load, (void*)p_synth, (void*)p_free);
|
|
return 3;
|
|
}
|
|
|
|
MockNativeInterface ifc;
|
|
JNIEnv env_buf;
|
|
env_buf.functions = ifc.ptr;
|
|
JNIEnv* env = &env_buf;
|
|
jobject self = nullptr; // pas utilisé dans nos handlers
|
|
|
|
jlong h = p_load(env, self,
|
|
(jstring)talker_gguf, (jstring)vocab_gguf, (jstring)dump_dir,
|
|
/*useHtp=*/JNI_FALSE, /*nThreads=*/6, /*cpUseCache=*/JNI_FALSE);
|
|
if (h == 0) { fprintf(stderr, "nativeLoad returned 0\n"); dlclose(dl); return 4; }
|
|
fprintf(stderr,"nativeLoad OK -> handle=%p\n", (void*)(uintptr_t)h);
|
|
|
|
// 2 appels : même phrase puis phrase différente.
|
|
auto run = [&](const char* text, const char* out_path) {
|
|
auto* arr = p_synth(env, self, h,
|
|
(jstring)text, (jstring)out_path,
|
|
/*maxSteps=*/256, /*seed=*/42,
|
|
/*cpTemp=*/0.9f, /*cpTopK=*/50, /*cpTopP=*/1.0f, /*cpRepPenalty=*/1.05f,
|
|
/*talkerTemp=*/0.9f, /*talkerTopK=*/50, /*talkerTopP=*/1.0f, /*talkerRepPenalty=*/1.05f);
|
|
IntArr* a = (IntArr*)arr;
|
|
fprintf(stderr,"[%s] err=%d frames=%d total=%dms (pf=%d talker=%d cp=%d dec=%d) -> %s\n",
|
|
text, a->data[0], a->data[1], a->data[2], a->data[3], a->data[4], a->data[5], a->data[6], out_path);
|
|
int err = a->data[0];
|
|
free(a);
|
|
return err;
|
|
};
|
|
|
|
std::string out1 = out_wav; out1 += ".1.wav";
|
|
std::string out2 = out_wav; out2 += ".2.wav";
|
|
int e1 = run("Bonjour je m'appelle Kazeia", out1.c_str());
|
|
int e2 = run("Bonsoir, comment tu te sens ce soir ?", out2.c_str());
|
|
|
|
p_free(env, self, h);
|
|
fprintf(stderr,"OK e1=%d e2=%d\n", e1, e2);
|
|
fflush(stderr); fflush(stdout);
|
|
// Pas de dlclose ni return : destructors statiques de libllama/ggml peuvent segfaulter
|
|
// après unmap (atexit/global dtors). En contexte app Android, la .so reste chargée
|
|
// pour toute la vie du process, donc ce path n'existe pas. _exit court-circuite.
|
|
_exit((e1 == 0 && e2 == 0) ? 0 : 5);
|
|
}
|