// Test standalone S1.3 : exercer kazeia_mel (config_whisper) + VAD RMS. // Usage : kazeia_stt_test // // 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 #include #include #include #include static std::vector 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 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 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 \n", argv[0]); return 1; } // 1) Mel basis 80x201 (Whisper) auto cfg = kazeia_mel::config_whisper(); std::vector 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 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; }