Ariver
2026-07-12 24f551a39eae849d891da26cc91f90d354e3a2db
C1.source/privatevoice.src/internal/engine/engine_windows.go
@@ -4,7 +4,10 @@
import (
   "fmt"
   "syscall"
   "time"
   "unsafe"
   "voicesnap/internal/logger"
   "voicesnap/internal/model"
@@ -14,10 +17,22 @@
type sherpaEngine struct {
   recognizer *sherpa.OfflineRecognizer
   provider   string
   hwInfo     string
}
var (
   kernel32              = syscall.NewLazyDLL("kernel32.dll")
   procGetShortPathNameW = kernel32.NewProc("GetShortPathNameW")
)
func newPlatformEngine(resolved model.ResolvedModel) (Engine, error) {
   modelPath := windowsRuntimePath(resolved.Files["model"])
   tokensPath := windowsRuntimePath(resolved.Files["tokens"])
   if modelPath != resolved.Files["model"] || tokensPath != resolved.Files["tokens"] {
      logger.Info("Using Windows short paths for sherpa runtime model files")
   }
   // Try DirectML first, then CPU fallback
   providers := []struct {
      name     string
@@ -31,10 +46,10 @@
      config := sherpa.OfflineRecognizerConfig{}
      config.FeatConfig.SampleRate = 16000
      config.FeatConfig.FeatureDim = 80
      config.ModelConfig.SenseVoice.Model = resolved.Files["model"]
      config.ModelConfig.SenseVoice.Model = modelPath
      config.ModelConfig.SenseVoice.Language = resolved.Profile.LanguageParam
      config.ModelConfig.SenseVoice.UseInverseTextNormalization = 1
      config.ModelConfig.Tokens = resolved.Files["tokens"]
      config.ModelConfig.Tokens = tokensPath
      config.ModelConfig.NumThreads = resolved.Profile.NumThreads
      config.ModelConfig.Provider = p.provider
      config.DecodingMethod = "greedy_search"
@@ -45,6 +60,7 @@
         logger.Info("Engine initialized: %s", info)
         return &sherpaEngine{
            recognizer: recognizer,
            provider:   p.provider,
            hwInfo:     info,
         }, nil
      }
@@ -54,14 +70,78 @@
   return nil, fmt.Errorf("failed to initialize sherpa-onnx with any provider")
}
func windowsRuntimePath(path string) string {
   if path == "" {
      return path
   }
   if isASCII(path) {
      return path
   }
   short, ok := shortPathName(path)
   if !ok || short == "" {
      return path
   }
   return short
}
func isASCII(path string) bool {
   for _, r := range path {
      if r > 127 {
         return false
      }
   }
   return true
}
func shortPathName(path string) (string, bool) {
   pathPtr, err := syscall.UTF16PtrFromString(path)
   if err != nil {
      return "", false
   }
   buf := make([]uint16, 32768)
   ret, _, _ := procGetShortPathNameW.Call(
      uintptr(unsafe.Pointer(pathPtr)),
      uintptr(unsafe.Pointer(&buf[0])),
      uintptr(len(buf)),
   )
   if ret == 0 || ret >= uintptr(len(buf)) {
      return "", false
   }
   return syscall.UTF16ToString(buf[:int(ret)]), true
}
func (e *sherpaEngine) Recognize(samples []float32) (string, error) {
   totalStart := time.Now()
   streamStart := time.Now()
   stream := sherpa.NewOfflineStream(e.recognizer)
   streamMS := durationMS(time.Since(streamStart))
   defer sherpa.DeleteOfflineStream(stream)
   acceptStart := time.Now()
   stream.AcceptWaveform(16000, samples)
   acceptMS := durationMS(time.Since(acceptStart))
   decodeStart := time.Now()
   e.recognizer.Decode(stream)
   decodeMS := durationMS(time.Since(decodeStart))
   resultStart := time.Now()
   result := stream.GetResult()
   resultMS := durationMS(time.Since(resultStart))
   logger.Info(
      "PERF engine_recognize_detail backend=%q provider=%q engine=%q samples=%d audio_ms=%d create_stream_ms=%d accept_ms=%d decode_ms=%d result_ms=%d total_ms=%d",
      model.BackendSenseVoice,
      e.provider,
      e.hwInfo,
      len(samples),
      audioDurationMS(samples),
      streamMS,
      acceptMS,
      decodeMS,
      resultMS,
      durationMS(time.Since(totalStart)),
   )
   return result.Text, nil
}
@@ -70,6 +150,10 @@
   return e.hwInfo
}
func (e *sherpaEngine) Provider() string {
   return e.provider
}
func (e *sherpaEngine) ReleaseTailCaptureDelay() time.Duration {
   return senseVoiceReleaseTailDelay
}