114 lines
4.5 KiB
C++
114 lines
4.5 KiB
C++
// kazeia_stt_cli : binaire S2 standalone transcrivant un WAV via stt_engine
|
|
// (Whisper NPU QNN + decoder KV-cache).
|
|
//
|
|
// Usage : kazeia_stt_cli <model_dir> <audio16k_mono.wav> [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 <cstdio>
|
|
#include <cstring>
|
|
#include <cstdint>
|
|
#include <vector>
|
|
#include <string>
|
|
|
|
static std::vector<int16_t> 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<uint8_t> 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<int16_t> 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 <model_dir> <audio1.wav> [audio2.wav ...] [-- htp|cpu]\n", argv[0]);
|
|
fprintf(stderr, " Si plusieurs audios passés, ils sont transcrits en séquence sur le même engine\n");
|
|
fprintf(stderr, " (reproduit le pattern d'usage in-app où l'engine est load-once N transcribes).\n");
|
|
return 1;
|
|
}
|
|
const char * model_dir = argv[1];
|
|
|
|
// Parse audio list + optional flags (lang, htp/cpu) à la fin
|
|
std::vector<std::string> wav_paths;
|
|
const char * lang = "fr";
|
|
bool use_htp = true;
|
|
for (int i = 2; i < argc; ++i) {
|
|
std::string a = argv[i];
|
|
if (a == "cpu") { use_htp = false; }
|
|
else if (a == "htp") { use_htp = true; }
|
|
else if (a == "fr" || a == "en" || a == "auto") { lang = argv[i]; }
|
|
else wav_paths.push_back(a);
|
|
}
|
|
if (wav_paths.empty()) { fprintf(stderr, "ERROR: au moins 1 audio requis\n"); return 1; }
|
|
|
|
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; }
|
|
|
|
int n_runs = 1;
|
|
if (const char* p = std::getenv("KZSTT_RUNS")) { int v = atoi(p); if (v > 0) n_runs = v; }
|
|
|
|
fprintf(stderr, "\n=== Sequence de %zu audios, %d runs/audio, lang=%s, %s ===\n",
|
|
wav_paths.size(), n_runs, lang, use_htp ? "NPU" : "CPU");
|
|
|
|
for (size_t ai = 0; ai < wav_paths.size(); ++ai) {
|
|
const std::string & wp = wav_paths[ai];
|
|
int sr = 0;
|
|
auto pcm = read_wav_pcm16(wp.c_str(), sr);
|
|
if (pcm.empty()) { fprintf(stderr, "skip %s (read fail)\n", wp.c_str()); continue; }
|
|
if (sr != 16000) { fprintf(stderr, "skip %s (sr=%d)\n", wp.c_str(), sr); continue; }
|
|
|
|
SttTranscribeCfg tc;
|
|
tc.pcm16 = pcm.data();
|
|
tc.n_samples = (int)pcm.size();
|
|
tc.sample_rate = 16000;
|
|
tc.language = lang;
|
|
tc.force_transcribe = true;
|
|
|
|
for (int run = 0; run < n_runs; ++run) {
|
|
auto R = stt_engine_transcribe(eng, tc);
|
|
if (R.err) { fprintf(stderr, "transcribe FAIL err=%d on %s\n", R.err, wp.c_str()); continue; }
|
|
printf("[%zu/%zu run=%d] %-30s : mel %3d enc %3d dec %4d (%2d tok) total %4d ms | text='%s'\n",
|
|
ai+1, wp.size() ? ai+1 : 0, run,
|
|
wp.substr(wp.find_last_of('/') + 1).c_str(),
|
|
R.mel_ms, R.encoder_ms, R.decoder_ms, R.n_tokens, R.total_ms,
|
|
R.text.c_str());
|
|
}
|
|
}
|
|
|
|
stt_engine_free(eng);
|
|
return 0;
|
|
}
|