Kazeia-engine/dist/jni/stt_test.cpp

114 lines
4.4 KiB
C++

// Test standalone S1.3 : exercer kazeia_mel (config_whisper) + VAD RMS.
// Usage : kazeia_stt_test <mel_filters.bin> <audio16k_mono.wav>
//
// Vérifie que le mel produit a la bonne shape [80 x 3000] et que les valeurs sont
// dans la plage Whisper (~[-1, 0.5] après norm). Permet de valider le mel partagé
// AVANT de brancher ORT/QNN en S2.
#include "kazeia_mel.h"
#include "stt_engine.h"
#include <cstdio>
#include <cstdint>
#include <cstring>
#include <vector>
#include <fstream>
static std::vector<int16_t> read_wav_pcm16(const char* path, int & sr_out, int & channels_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 (got ch=%d bps=%d)\n", channels, bps);
fclose(f); return {};
}
sr_out = (int)sr; channels_out = channels;
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 <mel_filters.bin> <audio16k_mono.wav>\n", argv[0]);
return 1;
}
// 1) Mel basis 80x201 (Whisper)
auto cfg = kazeia_mel::config_whisper();
std::vector<float> mel_basis;
if (!kazeia_mel::load_mel_basis(argv[1], cfg, mel_basis)) return 2;
fprintf(stderr, "mel_basis Whisper [%d, %d] OK (%zu f32)\n",
cfg.n_mels, cfg.n_fft/2+1, mel_basis.size());
// 2) WAV
int sr = 0, ch = 0;
auto pcm = read_wav_pcm16(argv[2], sr, ch);
if (pcm.empty()) return 3;
fprintf(stderr, "wav : %zu samples @ %d Hz = %.2f s\n",
pcm.size(), sr, (double)pcm.size() / sr);
if (sr != 16000) fprintf(stderr, "WARNING: sr=%d != 16000 (resample externe requis)\n", sr);
// 3) Convert to f32
std::vector<float> wav(pcm.size());
for (size_t i = 0; i < pcm.size(); ++i) wav[i] = (float)pcm[i] / 32768.0f;
// 4) Mel via kazeia_mel
int T = 0;
auto mel = kazeia_mel::compute(wav, mel_basis, cfg, T);
fprintf(stderr, "mel computed : [%d mels, %d frames] = %zu f32 (cible 80 x 3000 Whisper)\n",
cfg.n_mels, T, mel.size());
float mn = 1e9f, mx = -1e9f; double sum = 0;
for (float v : mel) { if (v < mn) mn = v; if (v > mx) mx = v; sum += v; }
fprintf(stderr, "mel stats : min=%.4f max=%.4f mean=%.4f\n",
mn, mx, (float)(sum / mel.size()));
fprintf(stderr, "(plage Whisper attendue ~[-1.0, 0.5])\n");
// 5) VAD test : push tout le PCM par chunks de 1600 samples
fprintf(stderr, "\n=== VAD RMS test (frame=1600=100ms, seuil=150) ===\n");
SttVadState * vad = stt_vad_new(16000, 1600, 150, 3, 8);
int speech_seen = 0, eos_count = 0;
for (size_t off = 0; off + 1600 <= pcm.size(); off += 1600) {
stt_vad_push(vad, pcm.data() + off, 1600);
if (stt_vad_is_speech(vad)) speech_seen += 1;
if (stt_vad_is_end_of_speech(vad)) eos_count += 1;
}
fprintf(stderr, "VAD : %d frames de parole / %zu total, %d end-of-speech\n",
speech_seen, pcm.size() / 1600, eos_count);
stt_vad_free(vad);
// 6) Dump mel pour comparaison externe si KZSTT_DUMP_MEL=path
if (const char * p = std::getenv("KZSTT_DUMP_MEL")) {
std::ofstream f(p, std::ios::binary);
f.write((char*)mel.data(), mel.size() * sizeof(float));
fprintf(stderr, "mel dumped -> %s\n", p);
}
return 0;
}