// Isolated pinned whisper.cpp C API. Binary mono PCM input; strict UTF-8 JSON. #include "whisper.h" #include "ggml-backend.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #ifndef NOMINMAX #define NOMINMAX #endif #include #include #include #endif using Clock=std::chrono::steady_clock; constexpr size_t MAX_SAMPLES=480000, MAX_OUTPUT=4*1024*1024; constexpr const char* POLICY="live-window-vad-dtw-v1;silero-v6.2.0;base;30s;vad-reset;threshold0.5;minspeech250ms;minsilence100ms;maxspeechFLT_MAX;pad30ms;vad-overlap0.1s;source-sample-intersections;beam5;bestof5;temp0;tempinc0.2;entropy2.4;logprob-1;nospeech0.6;suppressblank;no-suppress-nst;auto-language;no-context;dtw-base;no-flash;contiguous-intervals;first-last-token-points;exact-utf8"; static double elapsed(Clock::time_point start){return std::chrono::duration(Clock::now()-start).count();} static std::string backend="unknown"; static void logger(ggml_log_level level,const char * text,void*) { // Native logs can contain recognized bytes. Never forward them in production. const std::string value(text); const auto using_at=value.find(": using "), end=value.find(" backend"); if(using_at!=std::string::npos && end!=std::string::npos && end>using_at+8) backend=value.substr(using_at+8,end-using_at-8); if(value.find("no GPU found")!=std::string::npos)backend="cpu"; (void)level; } // Reject malformed sequences, overlong encodings, surrogates and > U+10FFFF. static bool utf8(const std::string & text) { size_t i=0; while(i(text[i++]); if(c<128)continue; unsigned n;uint32_t point,minimum; if(c>=0xc2 && c<=0xdf){n=1;point=c&31;minimum=128;} else if(c>=0xe0 && c<=0xef){n=2;point=c&15;minimum=2048;} else if(c>=0xf0 && c<=0xf4){n=3;point=c&7;minimum=65536;} else return false; if(i+n>text.size())return false; for(unsigned k=0;k(text[i++]);if((next&0xc0)!=0x80)return false;point=(point<<6)|(next&63);} if(point0x10ffff || (point>=0xd800 && point<=0xdfff))return false; } return true; } static std::string quoted(const std::string & text) { if(!utf8(text))throw std::runtime_error("malformed_recognition_utf8"); std::string result="\"";const char * hex="0123456789abcdef"; for(unsigned char c:text){if(c=='"'||c=='\\'){result+='\\';result+=static_cast(c);}else if(c<32){result+="\\u00";result+=hex[c>>4];result+=hex[c&15];}else result+=static_cast(c);} return result+'"'; } static void send(const std::string & json) { if(json.empty() || json.size()>MAX_OUTPUT)throw std::runtime_error("output_limit"); uint32_t n=static_cast(json.size());char prefix[4];for(int i=0;i<4;i++)prefix[i]=static_cast((n>>(i*8))&255); std::cout.write(prefix,4);std::cout.write(json.data(),json.size());std::cout.flush(); if(!std::cout)throw std::runtime_error("output_closed"); } static bool boundary(std::string text) { while(!text.empty() && std::string("\"')]").find(text.back())!=std::string::npos)text.pop_back(); for(const std::string suffix:{".","!","?","。","!","?"})if(text.size()>=suffix.size() && text.compare(text.size()-suffix.size(),suffix.size(),suffix)==0)return true; return false; } struct Runtime { std::string base_path,vad_path;bool auto_device; whisper_context * base=nullptr;whisper_vad_context * vad=nullptr; ~Runtime(){if(base)whisper_free(base);if(vad)whisper_vad_free(vad);} void load_base(bool gpu) { if(!gpu)backend="cpu"; auto params=whisper_context_default_params();params.use_gpu=gpu;params.flash_attn=false; params.dtw_token_timestamps=true;params.dtw_aheads_preset=WHISPER_AHEADS_BASE; base=whisper_init_from_file_with_params(base_path.c_str(),params); if(!base)throw std::runtime_error("base_load_failed"); } std::string request(unsigned op,const std::vector&pcm) { double load=0,vad_time=0,asr_time=0;std::ostringstream body; if(op==1) { if(!vad){auto start=Clock::now();auto params=whisper_vad_default_context_params();params.n_threads=1;params.use_gpu=auto_device; if(!auto_device)backend="cpu"; vad=whisper_vad_init_from_file_with_params(vad_path.c_str(),params); if(!vad)throw std::runtime_error("vad_load_failed");load=elapsed(start);} auto start=Clock::now(); if(!whisper_vad_detect_speech(vad,pcm.data(),static_cast(pcm.size())))throw std::runtime_error("vad_failed"); const whisper_vad_params policy{0.5f,250,100,FLT_MAX,30,0.1f}; auto segments=whisper_vad_segments_from_probs(vad,policy);if(!segments)throw std::runtime_error("vad_intervals_failed"); body<<",\"intervals_samples\":[";size_t previous=0;bool comma=false; const int n=whisper_vad_segments_n_segments(segments); if(n<0||n>1000){whisper_vad_free_segments(segments);throw std::runtime_error("vad_interval_limit");} for(int i=0;i=b||std::floor(a)!=a||std::floor(b)!=b){whisper_vad_free_segments(segments);throw std::runtime_error("vad_invalid_interval");} // Native VAD tail padding intersects source samples; never invent PCM. const size_t first=static_cast(std::min(a*160.0,static_cast(pcm.size()))); const size_t last=static_cast(std::min(b*160.0,static_cast(pcm.size()))); if(first(pcm.size())); if(status!=0)throw std::runtime_error("recognition_failed"); body<<",\"sentences\":[";bool comma=false;int64_t previous=0; const int count=whisper_full_n_segments(base);if(count<0||count>1000)throw std::runtime_error("segment_limit"); for(int i=0;i4096||!utf8(original))throw std::runtime_error("malformed_recognition_utf8"); std::string exact,phrase,merged;int64_t first=-1,last=-1,merge_first=-1; const int tokens=whisper_full_n_tokens(base,i);if(tokens<0||tokens>4096)throw std::runtime_error("token_limit"); auto append=[&](){if(phrase.find_first_not_of(" \r\n\t")!=std::string::npos){if(comma)body<<',';comma=true;body<<"{\"start_cs\":"<=whisper_token_eot(base))continue; if(token.t_dtw(token.t_dtw)*160>pcm.size())throw std::runtime_error("invalid_dtw_point"); previous=token.t_dtw;if(merge_first<0)merge_first=token.t_dtw; merged+=whisper_token_to_str(base,token.id); if(merged.size()>4096)throw std::runtime_error("token_text_limit"); if(!utf8(merged))continue; exact+=merged;if(first<0)first=merge_first;last=token.t_dtw;phrase+=merged; if(boundary(merged))append();merged.clear();merge_first=-1; } if(!merged.empty() || exact!=original)throw std::runtime_error("malformed_recognition_utf8");append(); } body<<']';asr_time=elapsed(start); }else throw std::runtime_error("unknown_operation"); std::ostringstream output;output<<"{\"ok\":true,\"operation\":"<&args){ if(args.size()==2 && args[1]=="--self-test"){ if(!utf8("\xe2\x80\x8d") || utf8("\xe2\x80") || utf8("\xed\xa0\x80") || utf8("\xc0\x80") || utf8("\xf4\x90\x80\x80"))return 5; if(quoted(std::string("a\n\"\\"))!="\"a\\u000a\\\"\\\\\"")return 5; return 0; } if(args.size()!=4)return 2; const uint16_t endian=1;if(*reinterpret_cast(&endian)!=1||sizeof(float)!=4)return 2; #ifdef _WIN32 _setmode(_fileno(stdin),_O_BINARY);_setmode(_fileno(stdout),_O_BINARY); SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); #endif if(args[3]!="auto" && args[3]!="cpu")return 2; const char* force_cpu=std::getenv("SEARCHI_FORCE_CPU"); const bool auto_device=args[3]=="auto" && !(force_cpu && std::string(force_cpu)=="1"); whisper_log_set(logger,nullptr); const auto directory=std::filesystem::u8path(args[0]).parent_path().u8string(); if(auto_device)ggml_backend_load_all_from_path(directory.c_str()); else ggml_backend_load_cpu_only_from_path(directory.c_str()); Runtime runtime{args[1],args[2],auto_device}; #ifdef SEARCHI_DISTRIBUTION_AUTO const bool distribution_auto=true; #else const bool distribution_auto=false; #endif send(std::string("{\"ready\":true,\"protocol\":1,\"runtime\":\"d1be6fde11ac6e0407606b4e42fe72d34add8037\",\"distribution_auto\":")+(distribution_auto?"true":"false")+",\"policy\":"+quoted(POLICY)+'}'); while(true){char header[5];std::cin.read(header,5);if(std::cin.gcount()==0&&std::cin.eof())return 0;if(std::cin.gcount()!=5)return 3; uint32_t count=0;for(int i=0;i<4;i++)count|=static_cast(static_cast(header[i+1]))<<(i*8); if(count==0||count>MAX_SAMPLES)return 3;std::vector pcm(count); std::cin.read(reinterpret_cast(pcm.data()),count*4);if(!std::cin)return 3; if(!std::all_of(pcm.begin(),pcm.end(),[](float v){return std::isfinite(v);} ))return 3; try{send(runtime.request(static_cast(header[0]),pcm));} catch(const std::exception&error){send(std::string("{\"ok\":false,\"error\":")+quoted(error.what())+'}');} } } #ifdef _WIN32 int wmain(int argc,wchar_t**argv){std::vector args;for(int i=0;i(argv,argv+argc));} #endif