Kazeia-engine/dist/jni/tts_pipeline.cpp

114 lines
5.1 KiB
C++

// CLI dev autour de tts_engine. La logique est maintenant dans tts_engine.{h,cpp}
// (réutilisée par le JNI). Ce binaire reste pour les bench A/B et la régression.
//
// Usage:
// tts_pipeline <talker_gguf> <dump_dir> <out.wav> [cpu|htp] [max_steps]
//
// Variables d'environnement :
// KZTTS_TEXT, KZTTS_VOCAB_GGUF : texte arbitraire au lieu de input_ids_full.bin (fixture)
// (le mode fixture est conservé pour bench/régression)
// KZTTS_CP_CACHE : 1 (défaut) = cp_predict_cached, 0 = cp_predict oracle
// KZTTS_SEED : seed sampling (défaut 42)
// KZTTS_THREADS : threads CPU (défaut 6)
// KZTTS_TEMP / TOPK / TOPP / REPP : sampling Talker
// KZTTS_CP_TEMP / CP_TOPK / CP_TOPP / CP_REPP : sampling CP
#include "tts_engine.h"
#include "kazeia_text_tokenizer.h"
#include "llama.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <string>
#include <vector>
static float env_f(const char* k, float dflt) { const char* v = getenv(k); return v ? (float)atof(v) : dflt; }
static int env_i(const char* k, int dflt) { const char* v = getenv(k); return v ? atoi(v) : dflt; }
int main(int argc, char** argv) {
if (argc < 4) {
printf("usage: %s <talker_gguf> <dump_dir> <out.wav> [cpu|htp] [max_steps]\n", argv[0]);
printf(" Texte arbitraire :\n");
printf(" KZTTS_VOCAB_GGUF=/path/qwen3.gguf KZTTS_TEXT=\"phrase libre\" %s ...\n", argv[0]);
printf(" Mode fixture (input_ids_full.bin) : aucune variable d'env requise.\n");
return 1;
}
const char* talker_gguf = argv[1];
std::string D = argv[2]; if (D.back() != '/') D += '/';
const char* out_wav = argv[3];
const bool use_htp = (argc >= 5 && !strcmp(argv[4], "htp"));
const int max_steps = (argc >= 6) ? atoi(argv[5]) : 256;
const char* kz_text = getenv("KZTTS_TEXT");
const char* kz_vocab_gguf = getenv("KZTTS_VOCAB_GGUF");
if (!kz_text || !kz_vocab_gguf) {
fprintf(stderr, "tts_pipeline: mode fixture (input_ids_full.bin) demandé mais non implémenté\n");
fprintf(stderr, " -> poser KZTTS_TEXT et KZTTS_VOCAB_GGUF pour utiliser le pipeline live.\n");
fprintf(stderr, " (Le mode fixture a été déporté en branche fixture pour test_engine séparé.)\n");
return 2;
}
TtsEngineLoadCfg lc;
lc.talker_gguf = talker_gguf;
lc.vocab_gguf = kz_vocab_gguf;
lc.dump_dir = D.c_str();
lc.use_htp = use_htp;
lc.n_threads = env_i("KZTTS_THREADS", 6);
lc.cp_use_cache = env_i("KZTTS_CP_CACHE", 1) != 0;
// Clonage vocal embarqué (opt-in via env)
lc.speaker_encoder_gguf = getenv("KZTTS_SPK_GGUF"); // ex: tts_dump/speaker_encoder.gguf
lc.mel_basis_path = getenv("KZTTS_MEL_BASIS"); // ex: tts_dump/mel_basis_qwen3tts.bin
auto * eng = tts_engine_load(lc);
if (!eng) { fprintf(stderr, "tts_engine_load FAIL\n"); return 3; }
// Si KZTTS_REF_WAV pointe sur un WAV mono 24kHz, on en extrait le x_vector et
// l'utilise comme override pour ce synth (clonage vocal in-process).
std::vector<float> ref_xvec;
if (const char * ref_wav = getenv("KZTTS_REF_WAV")) {
ref_xvec = tts_engine_encode_speaker_wav(eng, ref_wav);
if (ref_xvec.empty()) {
fprintf(stderr, "tts_pipeline: KZTTS_REF_WAV=%s FAIL (encoder missing or read FAIL)\n", ref_wav);
} else {
fprintf(stderr, "tts_pipeline: KZTTS_REF_WAV=%s -> x_vector[%zu] OK\n", ref_wav, ref_xvec.size());
}
}
TtsSynthesizeCfg sc;
sc.text = kz_text;
sc.out_wav_path = out_wav;
sc.max_steps = max_steps;
sc.seed = env_i("KZTTS_SEED", 42);
sc.cp_temp = env_f("KZTTS_CP_TEMP", 0.9f);
sc.cp_top_k = env_i("KZTTS_CP_TOPK", 50);
sc.cp_top_p = env_f("KZTTS_CP_TOPP", 0.95f);
sc.cp_rep_penalty = env_f("KZTTS_CP_REPP", 1.10f);
sc.talker_temp = env_f("KZTTS_TEMP", 0.9f);
sc.talker_top_k = env_i("KZTTS_TOPK", 50);
sc.talker_top_p = env_f("KZTTS_TOPP", 0.95f);
sc.talker_rep_penalty = env_f("KZTTS_REPP", 1.10f);
if (!ref_xvec.empty()) {
sc.xvector_override = ref_xvec.data();
sc.xvector_override_len = (int)ref_xvec.size();
}
auto R = tts_engine_synthesize(eng, sc);
if (R.err) { fprintf(stderr, "tts_engine_synthesize FAIL err=%d\n", R.err); tts_engine_free(eng); return 4; }
printf("=== TTS : N=%d frames (audio %.2fs) en %.3fs (RTF %.2f) ===\n",
R.frames, R.audio_s, R.total_s, R.total_s / R.audio_s);
printf(" prefill %.3fs | talker_loop %.3fs | cp_loop %.3fs | decoder %.3fs\n",
R.prefill_s, R.talker_loop_s, R.cp_loop_s, R.decoder_s);
printf(" per-frame talker=%.1fms cp=%.1fms\n",
R.talker_loop_s * 1000.0 / R.frames, R.cp_loop_s * 1000.0 / R.frames);
if (R.ttfb_s > 0) {
printf(" STREAMING : TTFB=%.3fs (premier chunk audio prêt) | %d chunks\n",
R.ttfb_s, R.n_chunks);
}
printf("WAV -> %s\n", out_wav);
tts_engine_free(eng);
return 0;
}