Files
searchi/tools/speech-vad/diagnostic.cpp
T

82 lines
4.2 KiB
C++

// Research only. Input: verified mono 16-kHz little-endian float32 PCM.
#include "whisper.h"
#include <atomic>
#include <chrono>
#include <cmath>
#include <cfloat>
#include <cstdio>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <vector>
static std::atomic<bool> failed{false};
static void log_message(enum ggml_log_level level, const char * text, void *) {
if (level == GGML_LOG_LEVEL_ERROR) failed.store(true);
std::fputs(text, stderr);
}
using Clock = std::chrono::steady_clock;
static double elapsed(Clock::time_point start) {
return std::chrono::duration<double, std::milli>(Clock::now() - start).count();
}
int main(int argc, char ** argv) {
if (argc != 3) { std::cerr << "Expected MODEL PCM_F32LE\n"; return 2; }
const unsigned short endian = 1;
if (*reinterpret_cast<const unsigned char *>(&endian) != 1 || sizeof(float) != 4) return 2;
std::ifstream input(argv[2], std::ios::binary | std::ios::ate);
if (!input) return 2;
const auto bytes = input.tellg();
// At most ten minutes. Never allocate according to an unchecked file size.
if (bytes <= 0 || bytes > 16000LL * 600 * 4 || bytes % 4 != 0) return 2;
std::vector<float> samples(static_cast<size_t>(bytes) / 4);
input.seekg(0);
if (!input.read(reinterpret_cast<char *>(samples.data()), bytes)) return 2;
for (float sample : samples) if (!std::isfinite(sample) || std::abs(sample) > 1.0f) return 2;
whisper_log_set(log_message, nullptr);
auto context_params = whisper_vad_default_context_params();
context_params.n_threads = 1;
context_params.use_gpu = false;
auto started = Clock::now();
auto * context = whisper_vad_init_from_file_with_params(argv[1], context_params);
const double load_ms = elapsed(started);
if (!context || failed.load()) { if (context) whisper_vad_free(context); return 3; }
started = Clock::now();
const bool detected = whisper_vad_detect_speech(context, samples.data(), static_cast<int>(samples.size()));
const double detect_ms = elapsed(started);
const int count = whisper_vad_n_probs(context);
const float * probs = whisper_vad_probs(context);
bool valid = detected && !failed.load() && probs && count == static_cast<int>((samples.size() + 511) / 512);
for (int i = 0; valid && i < count; ++i) valid = std::isfinite(probs[i]) && probs[i] >= 0 && probs[i] <= 1;
if (!valid) { whisper_vad_free(context); return 3; }
started = Clock::now();
const whisper_vad_params params{0.5f, 250, 100, FLT_MAX, 30, 0.1f};
auto * segments = whisper_vad_segments_from_probs(context, params);
const double segment_ms = elapsed(started);
if (!segments || failed.load()) { if (segments) whisper_vad_free_segments(segments); whisper_vad_free(context); return 3; }
float previous_end = 0;
for (int i = 0; i < whisper_vad_segments_n_segments(segments); ++i) {
const float start = whisper_vad_segments_get_segment_t0(segments, i);
const float end = whisper_vad_segments_get_segment_t1(segments, i);
if (!std::isfinite(start) || !std::isfinite(end) || start < previous_end || end <= start) {
whisper_vad_free_segments(segments); whisper_vad_free(context); return 3;
}
previous_end = end;
}
// Emit only after all native operations succeed. Raw centiseconds retained;
// the caller maps intervals to verified source bounds and absolute origin.
std::cout << std::setprecision(9) << "{\"schema\":1,\"sample_rate\":16000,\"samples\":" << samples.size()
<< ",\"threads\":1,\"gpu\":false,\"load_ms\":" << load_ms
<< ",\"detect_ms\":" << detect_ms << ",\"segment_ms\":" << segment_ms << ",\"probabilities\":[";
for (int i = 0; i < count; ++i) { if (i) std::cout << ','; std::cout << probs[i]; }
std::cout << "],\"intervals_cs\":[";
for (int i = 0; i < whisper_vad_segments_n_segments(segments); ++i) {
if (i) std::cout << ',';
std::cout << '[' << whisper_vad_segments_get_segment_t0(segments, i) << ','
<< whisper_vad_segments_get_segment_t1(segments, i) << ']';
}
std::cout << "]}\n";
whisper_vad_free_segments(segments);
whisper_vad_free(context);
return std::cout.good() ? 0 : 4;
}