// Engine STT Whisper-Small Qualcomm AI Hub (HfWhisper KV-cache) in-process. // Load une fois (encoder QAIRT context + decoder QAIRT context + mel_filters + // vocab), transcribe N fois (PCM 16kHz mono -> texte FR/EN). // // Backend : ONNX Runtime + QNN ExecutionProvider (HTP V79 sur SM8750). // Empreinte RAM : ~545 MB (encoder ctx 201 MB + decoder ctx 345 MB). // // Bench de référence prod (Whisper-Small, FR, audio 1.6s) : // mel 189 ms (CPU C++) + encoder 125 ms (NPU) + decoder 510 ms (NPU, 22 tokens) // total 825 ms, RTF 0.51, RAM peak 545 MB. // // Construit pour remplacer WhisperHybridEngine.kt + libmel_extractor.so + VadStage.kt // derrière une API unifiée Kazeia-Engine. La sélection de backend (HTP vs CPU) est // automatique via QNN EP : si le contexte QAIRT est trouvé, HTP ; sinon CPU fallback. #pragma once #include #include #include struct SttEngine; // opaque struct SttEngineLoadCfg { // Dir contenant : HfWhisperEncoder.onnx + HfWhisperEncoder_qairt_context.bin, // HfWhisperDecoder.onnx + HfWhisperDecoder_qairt_context.bin, // mel_filters.json (ou .bin), vocab.json (BPE byte-level). const char * model_dir = nullptr; // 1 = QNN HTP (NPU, défaut prod). 0 = CPU fallback (ONNX Runtime CPU EP). // QNN EP requiert que libQnnHtp.so + libQnnHtpV79Skel.so + libcdsprpc.so soient // dans LD_LIBRARY_PATH (côté tablette : push depuis dist/lib/). bool use_htp = true; int n_threads = 6; int max_decode_steps = 200; // borne hard du loop decoder autorégressif }; struct SttTranscribeCfg { // PCM mono 16-bit signed, n'importe quelle longueur (le mel pad fait à 30s). // Si vous avez du PCM 24kHz, resamplez externe avant (ou utilisez SttTranscribeWaveform). const int16_t * pcm16 = nullptr; int n_samples = 0; int sample_rate = 16000; // "fr", "en", "auto", etc. Whisper-Small supporte 99 langues. "auto" = détection // via la langue token de Whisper. const char * language = "fr"; // 1 = force le mode "transcribe" (FR -> texte FR). 0 = laisse Whisper choisir // entre transcribe et translate (translate force la sortie en EN, pas voulu pour // Kazeia FR). Défaut : 1 (cf override prod KazeiaService). bool force_transcribe = true; // Inclure les timestamps (segments + offsets) dans la sortie. bool with_timestamps = false; }; struct SttSegment { int t0_ms; // début segment (ms relatif au début audio) int t1_ms; // fin segment std::string text; }; struct SttTranscribeResult { int err = 0; // 0 = ok, <0 = err std::string text; // texte concaténé (sans timestamps) std::vector segments; // vide si !with_timestamps std::string detected_language; // "fr", "en", ... // Métriques perf (ms) int mel_ms = 0; int encoder_ms = 0; int decoder_ms = 0; int total_ms = 0; float rtf = 0.0f; // total_ms / (audio_duration_ms) int n_tokens = 0; }; // Charge encoder + decoder + mel basis + vocab. Retourne nullptr si échec. // Idempotent vis-à-vis de llama_backend_init() (cohabite avec tts_engine). SttEngine * stt_engine_load(const SttEngineLoadCfg & cfg); // Transcrit une fenêtre audio. Stateful pour le warm path NPU (sessions ORT // gardées en RAM entre appels). SttTranscribeResult stt_engine_transcribe(SttEngine * eng, const SttTranscribeCfg & cfg); // Libère tout (encoder ctx, decoder ctx, sessions ORT, vocab). void stt_engine_free(SttEngine * eng); // Helper VAD RMS énergie : renvoie true si le buffer PCM contient de la parole // (énergie > seuil). Compatible drop-in avec VadStage.kt prod (frame=1600 @ 16kHz, // seuil 150 PCM, ≥3 frames consécutives parole, ≥8 frames silence). // Tu peux passer le PCM en streaming par chunks de 100ms et lire is_speech / is_end_of_speech. struct SttVadState; SttVadState * stt_vad_new(int sample_rate = 16000, int frame_size = 1600, int rms_threshold = 150, int min_speech_frames = 3, int silence_end_frames = 8); bool stt_vad_push(SttVadState * s, const int16_t * pcm, int n_samples); bool stt_vad_is_speech(const SttVadState * s); bool stt_vad_is_end_of_speech(const SttVadState * s); void stt_vad_reset(SttVadState * s); void stt_vad_free(SttVadState * s);