A Golang (CGO + ONNX Runtime) speech detector powered by Silero VAD
- Batch detection — feed a complete audio buffer and get all speech segments at once
- Streaming detection — feed arbitrary-sized chunks in real-time and receive speech start/end events
- Probability mode — per-frame speech probability for external hangover / FSM logic
- Detector pool — goroutine-safe pool of pre-loaded detectors for concurrent workloads
- Min/Max speech duration — automatically discard short segments and split long ones
- Speech padding — configurable padding to avoid aggressive cutting
- Golang >= v1.21
- A C compiler (e.g. GCC)
- ONNX Runtime (v1.18.x recommended)
- A Silero VAD ONNX model (v5)
go get github.com/yunlongliang/silero-vad-go/speechYou need to download and install ONNX Runtime, then set the following environment variables.
export LD_RUN_PATH="/usr/local/lib/onnxruntime-linux-x64-1.18.1/lib"
export LIBRARY_PATH="/usr/local/lib/onnxruntime-linux-x64-1.18.1/lib"
export C_INCLUDE_PATH="/usr/local/include/onnxruntime-linux-x64-1.18.1/include"export LIBRARY_PATH="/usr/local/lib/onnxruntime-osx-arm64-1.18.1/lib"
export C_INCLUDE_PATH="/usr/local/include/onnxruntime-osx-arm64-1.18.1/include"
sudo update_dyld_shared_cacheProcess a complete audio buffer and get all speech segments:
package main
import (
"fmt"
"log"
"github.com/yunlongliang/silero-vad-go/speech"
)
func main() {
sd, err := speech.NewDetector(speech.DetectorConfig{
ModelPath: "/path/to/silero_vad.onnx",
SampleRate: 16000,
Threshold: 0.5,
MinSilenceDurationMs: 100,
SpeechPadMs: 30,
MinSpeechDurationMs: 250,
})
if err != nil {
log.Fatal(err)
}
defer sd.Destroy()
// pcm: []float32 mono audio at 16kHz
segments, err := sd.Detect(pcm)
if err != nil {
log.Fatal(err)
}
for _, s := range segments {
fmt.Printf("Speech: %dms - %dms\n", s.SpeechStartAt, s.SpeechEndAt)
}
}Feed audio chunks in real-time and receive events:
sd, _ := speech.NewDetector(speech.DetectorConfig{
ModelPath: "/path/to/silero_vad.onnx",
SampleRate: 16000,
Threshold: 0.5,
MinSilenceDurationMs: 100,
SpeechPadMs: 30,
MinSpeechDurationMs: 250,
MaxSpeechDurationS: 30,
})
defer sd.Destroy()
// Feed chunks as they arrive (any size works)
for chunk := range audioChunks {
events, err := sd.ProcessChunk(chunk)
if err != nil {
log.Fatal(err)
}
for _, e := range events {
switch e.Type {
case speech.EventSpeechStart:
fmt.Printf("Speech started at %dms\n", e.Segment.SpeechStartAt)
case speech.EventSpeechEnd:
fmt.Printf("Speech ended [%dms - %dms]\n", e.Segment.SpeechStartAt, e.Segment.SpeechEndAt)
}
}
}
// Flush remaining speech at end of stream
events, _ := sd.Flush()
for _, e := range events {
fmt.Printf("Speech ended (flush) [%dms - %dms]\n", e.Segment.SpeechStartAt, e.Segment.SpeechEndAt)
}Use when you run your own thresholds and hangover FSM (e.g. weighted detectors + start/stop confirm) instead of the library’s segmenter.
Feed fixed windows only: 512 samples at 16 kHz, or 256 at 8 kHz. Each call runs one Silero inference and returns a speech probability in [0, 1]. Model RNN/state and the v5 context buffer are updated; no ProcessChunk / Detect segment events are produced.
sd, _ := speech.NewDetector(speech.DetectorConfig{
ModelPath: "/path/to/silero_vad.onnx",
SampleRate: 16000,
Threshold: 0.5, // unused by Probability; for your own thresholds
})
defer sd.Destroy()
// frame512 must be exactly 512 float32 samples at 16 kHz
prob, err := sd.Probability(frame512)
if err != nil {
log.Fatal(err)
}
// use prob with external thresholds / hangover FSMInfer is an alias for Probability. Call Reset() before starting a new stream. A Detector is not goroutine-safe; do not mix Probability with ProcessChunk/Detect on the same instance—use separate detectors. Prefer DetectorPool when multiple goroutines need detectors.
| API | Role |
|---|---|
Probability / Infer |
One window → float in [0, 1]; your FSM |
ProcessChunk / Detect |
Library hangover → start/end segments or events |
Use a pool for safe concurrent detection from multiple goroutines:
pool, err := speech.NewDetectorPool(speech.PoolConfig{
ModelPath: "/path/to/silero_vad.onnx",
SampleRate: 16000,
PoolSize: 4,
LogLevel: speech.LogLevelError,
})
if err != nil {
log.Fatal(err)
}
defer pool.Destroy()
// Batch mode via pool
segments, err := pool.Detect(pcm, speech.DetectOptions{
Threshold: 0.5,
MinSilenceDurationMs: 100,
SpeechPadMs: 30,
MinSpeechDurationMs: 250,
})
// Streaming mode via pool
sd, _ := pool.Acquire(speech.DetectOptions{
Threshold: 0.5,
MinSilenceDurationMs: 100,
SpeechPadMs: 30,
MinSpeechDurationMs: 250,
MaxSpeechDurationS: 30,
})
// ... use sd.ProcessChunk / sd.Flush ...
pool.Release(sd)cd examples
go run main.go -model /path/to/silero_vad.onnx -wav /path/to/audio.wav -rate 16000See examples/main.go for the full example demonstrating both batch and streaming modes.
| Type | Description |
|---|---|
DetectorConfig |
Configuration for creating a Detector |
Detector |
Core VAD detector (batch, streaming, probability); not goroutine-safe |
Segment |
Speech segment with start/end timestamps (ms) |
SpeechEvent |
Streaming event with type and segment |
EventType |
EventSpeechStart or EventSpeechEnd |
DetectOptions |
Per-call options for DetectorPool |
DetectorPool |
Goroutine-safe pool of detectors |
PoolConfig |
Configuration for creating a DetectorPool |
| Method | Description |
|---|---|
Probability / Infer |
One fixed window → speech probability [0, 1] |
Detect |
Batch segmenter over a full buffer |
ProcessChunk / Flush |
Streaming segmenter with start/end events |
Reset |
Clear model state, context, and segment FSM |
MIT License — see LICENSE for full text.
Based on streamer45/silero-vad-go and the Go example from snakers4/silero-vad.