//go:build windows
|
|
package engine
|
|
import (
|
"fmt"
|
"time"
|
"voicesnap/internal/logger"
|
"voicesnap/internal/model"
|
|
_ "github.com/k2-fsa/sherpa-onnx-go-windows"
|
sherpa "github.com/k2-fsa/sherpa-onnx-go/sherpa_onnx"
|
)
|
|
type sherpaEngine struct {
|
recognizer *sherpa.OfflineRecognizer
|
provider string
|
hwInfo string
|
}
|
|
func newPlatformEngine(resolved model.ResolvedModel) (Engine, error) {
|
// Try DirectML first, then CPU fallback
|
providers := []struct {
|
name string
|
provider string
|
}{
|
{"DirectML (GPU)", "directml"},
|
{"CPU", "cpu"},
|
}
|
|
for _, p := range providers {
|
config := sherpa.OfflineRecognizerConfig{}
|
config.FeatConfig.SampleRate = 16000
|
config.FeatConfig.FeatureDim = 80
|
config.ModelConfig.SenseVoice.Model = resolved.Files["model"]
|
config.ModelConfig.SenseVoice.Language = resolved.Profile.LanguageParam
|
config.ModelConfig.SenseVoice.UseInverseTextNormalization = 1
|
config.ModelConfig.Tokens = resolved.Files["tokens"]
|
config.ModelConfig.NumThreads = resolved.Profile.NumThreads
|
config.ModelConfig.Provider = p.provider
|
config.DecodingMethod = "greedy_search"
|
|
recognizer := sherpa.NewOfflineRecognizer(&config)
|
if recognizer != nil {
|
info := fmt.Sprintf("SenseVoice ยท %s", p.name)
|
logger.Info("Engine initialized: %s", info)
|
return &sherpaEngine{
|
recognizer: recognizer,
|
provider: p.provider,
|
hwInfo: info,
|
}, nil
|
}
|
logger.Info("Failed to init with %s, trying next provider", p.name)
|
}
|
|
return nil, fmt.Errorf("failed to initialize sherpa-onnx with any provider")
|
}
|
|
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
|
}
|
|
func (e *sherpaEngine) HardwareInfo() string {
|
return e.hwInfo
|
}
|
|
func (e *sherpaEngine) Provider() string {
|
return e.provider
|
}
|
|
func (e *sherpaEngine) ReleaseTailCaptureDelay() time.Duration {
|
return senseVoiceReleaseTailDelay
|
}
|
|
func (e *sherpaEngine) Close() {
|
if e.recognizer != nil {
|
sherpa.DeleteOfflineRecognizer(e.recognizer)
|
e.recognizer = nil
|
}
|
}
|