// kazeia_stt_cli : binaire S2 standalone transcrivant un WAV via stt_engine // (Whisper NPU QNN + decoder KV-cache). // // Usage : kazeia_stt_cli [language=fr] [cpu|htp=htp] // model_dir doit contenir : HfWhisperEncoder.onnx + _qairt_context.bin, // HfWhisperDecoder.onnx + _qairt_context.bin, // mel_filters.json (ou .bin), vocab.json #include "stt_engine.h" #include #include #include #include #include static std::vector read_wav_pcm16(const char* path, int & sr_out) { FILE* f = fopen(path, "rb"); if (!f) { fprintf(stderr, "open %s FAIL\n", path); return {}; } char riff[12]; if (fread(riff, 1, 12, f) != 12 || memcmp(riff, "RIFF", 4) != 0 || memcmp(riff+8, "WAVE", 4) != 0) { fprintf(stderr, "not RIFF/WAVE\n"); fclose(f); return {}; } uint16_t channels = 0, bps = 0; uint32_t sr = 0, data_sz = 0; long data_off = -1; while (!feof(f)) { char cid[4]; uint32_t csz; if (fread(cid, 1, 4, f) != 4) break; if (fread(&csz, 4, 1, f) != 1) break; if (memcmp(cid, "fmt ", 4) == 0) { std::vector buf(csz); fread(buf.data(), 1, csz, f); channels = *(uint16_t*)&buf[2]; sr = *(uint32_t*)&buf[4]; bps = *(uint16_t*)&buf[14]; } else if (memcmp(cid, "data", 4) == 0) { data_sz = csz; data_off = ftell(f); break; } else { fseek(f, csz, SEEK_CUR); } } if (data_off < 0 || channels != 1 || bps != 16) { fprintf(stderr, "WAV mono 16-bit attendu (ch=%d bps=%d)\n", channels, bps); fclose(f); return {}; } sr_out = (int)sr; fseek(f, data_off, SEEK_SET); std::vector pcm(data_sz / 2); fread(pcm.data(), 2, pcm.size(), f); fclose(f); return pcm; } int main(int argc, char** argv) { setvbuf(stderr, nullptr, _IONBF, 0); setvbuf(stdout, nullptr, _IONBF, 0); if (argc < 3) { fprintf(stderr, "usage: %s [language=fr] [cpu|htp=htp]\n", argv[0]); return 1; } const char * model_dir = argv[1]; const char * wav_path = argv[2]; const char * lang = (argc >= 4) ? argv[3] : "fr"; const bool use_htp = !(argc >= 5 && !strcmp(argv[4], "cpu")); int sr = 0; auto pcm = read_wav_pcm16(wav_path, sr); if (pcm.empty()) return 2; fprintf(stderr, "wav : %zu samples @ %d Hz = %.2f s\n", pcm.size(), sr, (double)pcm.size() / sr); if (sr != 16000) { fprintf(stderr, "ERROR: sr=%d != 16000 (Whisper requiert 16kHz)\n", sr); return 3; } SttEngineLoadCfg lc; lc.model_dir = model_dir; lc.use_htp = use_htp; lc.n_threads = 6; auto * eng = stt_engine_load(lc); if (!eng) { fprintf(stderr, "stt_engine_load FAIL\n"); return 4; } SttTranscribeCfg tc; tc.pcm16 = pcm.data(); tc.n_samples = (int)pcm.size(); tc.sample_rate = 16000; tc.language = lang; tc.force_transcribe = true; // Mode warm-bench : si KZSTT_RUNS=N défini, fait N transcribes successifs sur le // même engine et reporte chacun. Utile pour distinguer cold-start (1er run lent) // vs warm path (runs suivants). int n_runs = 1; if (const char* p = std::getenv("KZSTT_RUNS")) { int v = atoi(p); if (v > 0) n_runs = v; } for (int run = 0; run < n_runs; ++run) { auto R = stt_engine_transcribe(eng, tc); if (R.err) { fprintf(stderr, "transcribe[%d] FAIL err=%d\n", run, R.err); stt_engine_free(eng); return 5; } const char * tag = (n_runs > 1) ? (run == 0 ? "[cold]" : "[warm]") : ""; if (run == 0 || n_runs == 1) { printf("\n=== TRANSCRIPTION (%s, %s) %s ===\n", use_htp ? "NPU" : "CPU", lang, tag); printf("%s\n", R.text.c_str()); } printf("run %d %-6s : mel %3d enc %3d dec %4d (%2d tok) total %4d ms RTF %.3f\n", run, tag, R.mel_ms, R.encoder_ms, R.decoder_ms, R.n_tokens, R.total_ms, R.rtf); } stt_engine_free(eng); return 0; }