mirror of
https://github.com/mudler/LocalAI.git
synced 2026-08-02 19:40:11 -04:00
Add backend/go/voice-detect implementing the Backend gRPC voice subset (VoiceEmbed/VoiceVerify/VoiceAnalyze) over libvoicedetect.so via purego, mirroring the parakeet-cpp / omnivoice-cpp backends. The flat voicedetect_capi C ABI is dlopen'd cgo-less; malloc'd string and float-vector returns are owned by Go and released through the matching capi free functions, with the per-ctx last error surfaced into Go errors. Calls are serialized via base.SingleThread since the C context is not reentrant. Proto field mapping: - VoiceEmbed: VoiceEmbedRequest.audio (path) -> embed_path -> Embedding+Model. - VoiceVerify: audio1/audio2 + threshold (<=0 falls back to the verify_threshold option, default 0.25) -> verify_paths -> verified/distance/ threshold/confidence/model/processing_time_ms. - VoiceAnalyze: audio (path) -> analyze_path_json; the JSON age/gender/emotion document maps to a single VoiceAnalysis segment (start/end 0; gender "label" -> dominant_gender with the remaining float scores as the gender map; emotion label/scores -> dominant_emotion/emotion). The Makefile pins voice-detect.cpp to 47546430, clones+builds libvoicedetect.so with ggml static-linked (PIC, GGML_NATIVE off) so dlopen needs no external libggml/libvoicedetect; ldd on the artifact shows only system libs. Ginkgo tests cover option parsing and analyze-JSON mapping; embed/verify smoke specs gate on VOICEDETECT_BACKEND_TEST_MODEL + VOICEDETECT_BACKEND_TEST_WAV. Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Assisted-by: Claude:claude-opus-4-8 [Claude Code]
47 lines
1.2 KiB
Go
47 lines
1.2 KiB
Go
package main
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// defaultVerifyThreshold is the cosine-distance cutoff used when a request does
|
|
// not set one. Matches the Python speaker-recognition backend's default so the
|
|
// two implementations agree on verdicts out of the box.
|
|
const defaultVerifyThreshold float32 = 0.25
|
|
|
|
// loadOptions holds the parsed model-level options for voice-detect.
|
|
type loadOptions struct {
|
|
verifyThreshold float32
|
|
modelName string
|
|
}
|
|
|
|
func splitOption(o string) (key, value string, ok bool) {
|
|
i := strings.Index(o, ":")
|
|
if i < 0 {
|
|
return "", "", false
|
|
}
|
|
return strings.TrimSpace(o[:i]), strings.TrimSpace(o[i+1:]), true
|
|
}
|
|
|
|
// parseOptions reads the backend "key:value" option slice. Unknown keys are
|
|
// ignored. Defaults: verify_threshold 0.25, model_name derived from the file.
|
|
func parseOptions(opts []string) loadOptions {
|
|
o := loadOptions{verifyThreshold: defaultVerifyThreshold}
|
|
for _, oo := range opts {
|
|
key, value, ok := splitOption(oo)
|
|
if !ok {
|
|
continue
|
|
}
|
|
switch key {
|
|
case "verify_threshold", "threshold":
|
|
if f, err := strconv.ParseFloat(value, 32); err == nil && f > 0 {
|
|
o.verifyThreshold = float32(f)
|
|
}
|
|
case "model_name":
|
|
o.modelName = value
|
|
}
|
|
}
|
|
return o
|
|
}
|