// Research only. Input: verified mono 16-kHz little-endian float32 PCM. #include "whisper.h" #include #include #include #include #include #include #include #include #include static std::atomic 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(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(&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 samples(static_cast(bytes) / 4); input.seekg(0); if (!input.read(reinterpret_cast(samples.data()), bytes)) return 2; // Float decoding may legitimately overshoot unity. Preserve source samples; // clipping would change the experiment. Reject nonfinite input only. for (float sample : samples) if (!std::isfinite(sample)) 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(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((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; }