From 6da4a1c12f3e23bcd24bbff0a7df0b95ed283f60 Mon Sep 17 00:00:00 2001 From: Fangjun Kuang Date: Fri, 29 Mar 2024 19:25:55 +0800 Subject: [PATCH] Add Go API for speaker identification (#718) --- .github/workflows/test-go-package.yaml | 6 + .github/workflows/test-go.yaml | 6 + .gitignore | 2 + go-api-examples/README.md | 4 + go-api-examples/speaker-identification/go.mod | 3 + .../speaker-identification/main.go | 146 ++++++++++++ go-api-examples/speaker-identification/run.sh | 13 ++ go-api-examples/vad-asr-paraformer/main.go | 4 +- go-api-examples/vad-asr-whisper/main.go | 4 +- .../vad-speaker-identification/go.mod | 3 + .../vad-speaker-identification/main.go | 221 ++++++++++++++++++ .../vad-speaker-identification/run.sh | 13 ++ .../main.go | 4 +- .../UserInterfaceState.xcuserstate | Bin 42948 -> 0 bytes .../xcschemes/xcschememanagement.plist | 14 -- .../UserInterfaceState.xcuserstate | Bin 16738 -> 0 bytes .../speaker-identification/.gitignore | 1 + .../_internal/speaker-identification/go.mod | 5 + .../_internal/speaker-identification/main.go | 1 + .../_internal/speaker-identification/run.sh | 1 + .../go/_internal/vad-asr-paraformer/go.mod | 5 - .../vad-speaker-identification/go.mod | 5 + .../vad-speaker-identification/main.go | 1 + .../vad-speaker-identification/run.sh | 1 + scripts/go/sherpa_onnx.go | 205 +++++++++++++++- 25 files changed, 641 insertions(+), 27 deletions(-) create mode 100644 go-api-examples/speaker-identification/go.mod create mode 100644 go-api-examples/speaker-identification/main.go create mode 100755 go-api-examples/speaker-identification/run.sh create mode 100644 go-api-examples/vad-speaker-identification/go.mod create mode 100644 go-api-examples/vad-speaker-identification/main.go create mode 100755 go-api-examples/vad-speaker-identification/run.sh delete mode 100644 ios-swift/SherpaOnnx/SherpaOnnx.xcodeproj/project.xcworkspace/xcuserdata/fangjun.xcuserdatad/UserInterfaceState.xcuserstate delete mode 100644 ios-swift/SherpaOnnx/SherpaOnnx.xcodeproj/xcuserdata/fangjun.xcuserdatad/xcschemes/xcschememanagement.plist delete mode 100644 ios-swiftui/SherpaOnnx/SherpaOnnx.xcodeproj/project.xcworkspace/xcuserdata/fangjun.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 scripts/go/_internal/speaker-identification/.gitignore create mode 100644 scripts/go/_internal/speaker-identification/go.mod create mode 120000 scripts/go/_internal/speaker-identification/main.go create mode 120000 scripts/go/_internal/speaker-identification/run.sh create mode 100644 scripts/go/_internal/vad-speaker-identification/go.mod create mode 120000 scripts/go/_internal/vad-speaker-identification/main.go create mode 120000 scripts/go/_internal/vad-speaker-identification/run.sh diff --git a/.github/workflows/test-go-package.yaml b/.github/workflows/test-go-package.yaml index 6ef4235f..d761be4f 100644 --- a/.github/workflows/test-go-package.yaml +++ b/.github/workflows/test-go-package.yaml @@ -66,6 +66,12 @@ jobs: run: | gcc --version + - name: Test speaker identification + shell: bash + run: | + cd go-api-examples/speaker-identification + ./run.sh + - name: Test non-streaming TTS (Linux/macOS) if: matrix.os != 'windows-latest' shell: bash diff --git a/.github/workflows/test-go.yaml b/.github/workflows/test-go.yaml index ae9b6f00..298403ec 100644 --- a/.github/workflows/test-go.yaml +++ b/.github/workflows/test-go.yaml @@ -74,6 +74,12 @@ jobs: go mod tidy go build + - name: Test speaker identification + shell: bash + run: | + cd scripts/go/_internal/speaker-identification/ + ./run.sh + - name: Test non-streaming TTS (macOS) shell: bash run: | diff --git a/.gitignore b/.gitignore index ea1d57e9..c2c87424 100644 --- a/.gitignore +++ b/.gitignore @@ -88,3 +88,5 @@ vits-mms-* *.tar.bz2 sherpa-onnx-paraformer-trilingual-zh-cantonese-en sr-data +*xcworkspace/xcuserdata/* + diff --git a/go-api-examples/README.md b/go-api-examples/README.md index 51a44b38..91f2c76e 100644 --- a/go-api-examples/README.md +++ b/go-api-examples/README.md @@ -26,4 +26,8 @@ for details. - [./vad-spoken-language-identification](./vad-spoken-language-identification) It shows how to use silero VAD + Whisper for spoken language identification. +- [./speaker-identification](./speaker-identification) It shows how to use Go API for speaker identification. + +- [./vad-speaker-identification](./vad-speaker-identification) It shows how to use Go API for VAD + speaker identification. + [sherpa-onnx]: https://github.com/k2-fsa/sherpa-onnx diff --git a/go-api-examples/speaker-identification/go.mod b/go-api-examples/speaker-identification/go.mod new file mode 100644 index 00000000..188a7b98 --- /dev/null +++ b/go-api-examples/speaker-identification/go.mod @@ -0,0 +1,3 @@ +module speaker-identification + +go 1.12 diff --git a/go-api-examples/speaker-identification/main.go b/go-api-examples/speaker-identification/main.go new file mode 100644 index 00000000..7f2c791f --- /dev/null +++ b/go-api-examples/speaker-identification/main.go @@ -0,0 +1,146 @@ +package main + +import ( + sherpa "github.com/k2-fsa/sherpa-onnx-go/sherpa_onnx" + "log" +) + +func createSpeakerEmbeddingExtractor() *sherpa.SpeakerEmbeddingExtractor { + config := sherpa.SpeakerEmbeddingExtractorConfig{} + + // Please download the model from + // https://github.com/k2-fsa/sherpa-onnx/releases/download/speaker-recongition-models/3dspeaker_speech_campplus_sv_zh-cn_16k-common.onnx + // + // You can find more models at + // https://github.com/k2-fsa/sherpa-onnx/releases/tag/speaker-recongition-models + + config.Model = "./3dspeaker_speech_campplus_sv_zh-cn_16k-common.onnx" + config.NumThreads = 1 + config.Debug = 1 + config.Provider = "cpu" + + ex := sherpa.NewSpeakerEmbeddingExtractor(&config) + return ex +} + +func computeEmbeddings(ex *sherpa.SpeakerEmbeddingExtractor, files []string) [][]float32 { + embeddings := make([][]float32, len(files)) + + for i, f := range files { + wave := sherpa.ReadWave(f) + + stream := ex.CreateStream() + defer sherpa.DeleteOnlineStream(stream) + stream.AcceptWaveform(wave.SampleRate, wave.Samples) + stream.InputFinished() + embeddings[i] = ex.Compute(stream) + } + + return embeddings + +} + +func registerSpeakers(ex *sherpa.SpeakerEmbeddingExtractor, manager *sherpa.SpeakerEmbeddingManager) { + // Please download the test data from + // https://github.com/csukuangfj/sr-data + spk1_files := []string{ + "./sr-data/enroll/fangjun-sr-1.wav", + "./sr-data/enroll/fangjun-sr-2.wav", + "./sr-data/enroll/fangjun-sr-3.wav", + } + + spk2_files := []string{ + "./sr-data/enroll/leijun-sr-1.wav", + "./sr-data/enroll/leijun-sr-2.wav", + } + + spk1_embeddings := computeEmbeddings(ex, spk1_files) + spk2_embeddings := computeEmbeddings(ex, spk2_files) + + ok := manager.RegisterV("fangjun", spk1_embeddings) + if !ok { + panic("Failed to register fangjun") + } + + ok = manager.RegisterV("leijun", spk2_embeddings) + if !ok { + panic("Failed to register leijun") + } + + if !manager.Contains("fangjun") { + panic("Failed to find fangjun") + } + + if !manager.Contains("leijun") { + panic("Failed to find leijun") + } + + if manager.NumSpeakers() != 2 { + panic("There should be only 2 speakers") + } + + all_speakers := manager.AllSpeakers() + log.Printf("All speakers: %v\n", all_speakers) +} + +func main() { + log.SetFlags(log.LstdFlags | log.Lmicroseconds) + + ex := createSpeakerEmbeddingExtractor() + defer sherpa.DeleteSpeakerEmbeddingExtractor(ex) + + manager := sherpa.NewSpeakerEmbeddingManager(ex.Dim()) + defer sherpa.DeleteSpeakerEmbeddingManager(manager) + registerSpeakers(ex, manager) + + // Please download the test data from + // https://github.com/csukuangfj/sr-data + test1 := "./sr-data/test/fangjun-test-sr-1.wav" + embeddings := computeEmbeddings(ex, []string{test1})[0] + threshold := float32(0.6) + name := manager.Search(embeddings, threshold) + if len(name) > 0 { + log.Printf("%v matches %v", test1, name) + } else { + log.Printf("No matches found for %v", test1) + } + + test2 := "./sr-data/test/leijun-test-sr-1.wav" + embeddings = computeEmbeddings(ex, []string{test2})[0] + name = manager.Search(embeddings, threshold) + if len(name) > 0 { + log.Printf("%v matches %v", test2, name) + } else { + log.Printf("No matches found for %v", test2) + } + + test3 := "./sr-data/test/liudehua-test-sr-1.wav" + embeddings = computeEmbeddings(ex, []string{test3})[0] + name = manager.Search(embeddings, threshold) + if len(name) > 0 { + log.Printf("%v matches %v", test3, name) + } else { + log.Printf("No matches found for %v", test3) + } + + if !manager.Remove("fangjun") { + panic("Failed to deregister fangjun") + } else { + log.Print("fangjun deregistered\n") + } + + test1 = "./sr-data/test/fangjun-test-sr-1.wav" + embeddings = computeEmbeddings(ex, []string{test1})[0] + name = manager.Search(embeddings, threshold) + if len(name) > 0 { + log.Printf("%v matches %v", test1, name) + } else { + log.Printf("No matches found for %v", test1) + } +} + +func chk(err error) { + if err != nil { + panic(err) + } +} diff --git a/go-api-examples/speaker-identification/run.sh b/go-api-examples/speaker-identification/run.sh new file mode 100755 index 00000000..3829eee9 --- /dev/null +++ b/go-api-examples/speaker-identification/run.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +if [ ! -f ./3dspeaker_speech_campplus_sv_zh-cn_16k-common.onnx ]; then + curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/speaker-recongition-models/3dspeaker_speech_campplus_sv_zh-cn_16k-common.onnx +fi + +if [ ! -f ./sr-data/enroll/fangjun-sr-1.wav ]; then + git clone https://github.com/csukuangfj/sr-data +fi + +go mod tidy +go build +./speaker-identification diff --git a/go-api-examples/vad-asr-paraformer/main.go b/go-api-examples/vad-asr-paraformer/main.go index 25515da6..54e1ed1c 100644 --- a/go-api-examples/vad-asr-paraformer/main.go +++ b/go-api-examples/vad-asr-paraformer/main.go @@ -104,7 +104,7 @@ func main() { duration := float32(len(speechSegment.Samples)) / float32(config.SampleRate) - audio := &sherpa.GeneratedAudio{} + audio := &sherpa.Wave{} audio.Samples = speechSegment.Samples audio.SampleRate = config.SampleRate @@ -120,7 +120,7 @@ func main() { chk(s.Stop()) } -func decode(recognizer *sherpa.OfflineRecognizer, audio *sherpa.GeneratedAudio, id int) { +func decode(recognizer *sherpa.OfflineRecognizer, audio *sherpa.Wave, id int) { stream := sherpa.NewOfflineStream(recognizer) defer sherpa.DeleteOfflineStream(stream) stream.AcceptWaveform(audio.SampleRate, audio.Samples) diff --git a/go-api-examples/vad-asr-whisper/main.go b/go-api-examples/vad-asr-whisper/main.go index 55ed7c86..85c675ef 100644 --- a/go-api-examples/vad-asr-whisper/main.go +++ b/go-api-examples/vad-asr-whisper/main.go @@ -102,7 +102,7 @@ func main() { duration := float32(len(speechSegment.Samples)) / float32(config.SampleRate) - audio := &sherpa.GeneratedAudio{} + audio := &sherpa.Wave{} audio.Samples = speechSegment.Samples audio.SampleRate = config.SampleRate @@ -118,7 +118,7 @@ func main() { chk(s.Stop()) } -func decode(recognizer *sherpa.OfflineRecognizer, audio *sherpa.GeneratedAudio, id int) { +func decode(recognizer *sherpa.OfflineRecognizer, audio *sherpa.Wave, id int) { stream := sherpa.NewOfflineStream(recognizer) defer sherpa.DeleteOfflineStream(stream) stream.AcceptWaveform(audio.SampleRate, audio.Samples) diff --git a/go-api-examples/vad-speaker-identification/go.mod b/go-api-examples/vad-speaker-identification/go.mod new file mode 100644 index 00000000..9b1f1563 --- /dev/null +++ b/go-api-examples/vad-speaker-identification/go.mod @@ -0,0 +1,3 @@ +module vad-speaker-identification + +go 1.12 diff --git a/go-api-examples/vad-speaker-identification/main.go b/go-api-examples/vad-speaker-identification/main.go new file mode 100644 index 00000000..f2b69d09 --- /dev/null +++ b/go-api-examples/vad-speaker-identification/main.go @@ -0,0 +1,221 @@ +package main + +import ( + "fmt" + "github.com/gordonklaus/portaudio" + sherpa "github.com/k2-fsa/sherpa-onnx-go/sherpa_onnx" + "log" +) + +func createSpeakerEmbeddingExtractor() *sherpa.SpeakerEmbeddingExtractor { + config := sherpa.SpeakerEmbeddingExtractorConfig{} + + // Please download the model from + // https://github.com/k2-fsa/sherpa-onnx/releases/download/speaker-recongition-models/3dspeaker_speech_campplus_sv_zh-cn_16k-common.onnx + // + // You can find more models at + // https://github.com/k2-fsa/sherpa-onnx/releases/tag/speaker-recongition-models + + config.Model = "./3dspeaker_speech_campplus_sv_zh-cn_16k-common.onnx" + config.NumThreads = 2 + config.Debug = 1 + config.Provider = "cpu" + + ex := sherpa.NewSpeakerEmbeddingExtractor(&config) + return ex +} + +func computeEmbeddings(ex *sherpa.SpeakerEmbeddingExtractor, files []string) [][]float32 { + embeddings := make([][]float32, len(files)) + + for i, f := range files { + wave := sherpa.ReadWave(f) + + stream := ex.CreateStream() + defer sherpa.DeleteOnlineStream(stream) + stream.AcceptWaveform(wave.SampleRate, wave.Samples) + stream.InputFinished() + embeddings[i] = ex.Compute(stream) + } + + return embeddings + +} + +func registerSpeakers(ex *sherpa.SpeakerEmbeddingExtractor, manager *sherpa.SpeakerEmbeddingManager) { + // Please download the test data from + // https://github.com/csukuangfj/sr-data + spk1_files := []string{ + "./sr-data/enroll/fangjun-sr-1.wav", + "./sr-data/enroll/fangjun-sr-2.wav", + "./sr-data/enroll/fangjun-sr-3.wav", + } + + spk2_files := []string{ + "./sr-data/enroll/leijun-sr-1.wav", + "./sr-data/enroll/leijun-sr-2.wav", + } + + spk1_embeddings := computeEmbeddings(ex, spk1_files) + spk2_embeddings := computeEmbeddings(ex, spk2_files) + + ok := manager.RegisterV("fangjun", spk1_embeddings) + if !ok { + panic("Failed to register fangjun") + } + + ok = manager.RegisterV("leijun", spk2_embeddings) + if !ok { + panic("Failed to register leijun") + } + + if !manager.Contains("fangjun") { + panic("Failed to find fangjun") + } + + if !manager.Contains("leijun") { + panic("Failed to find leijun") + } + + if manager.NumSpeakers() != 2 { + panic("There should be only 2 speakers") + } + + all_speakers := manager.AllSpeakers() + log.Printf("All speakers: %v\n", all_speakers) +} + +func createVad() *sherpa.VoiceActivityDetector { + config := sherpa.VadModelConfig{} + + // Please download silero_vad.onnx from + // https://github.com/snakers4/silero-vad/blob/master/files/silero_vad.onnx + + config.SileroVad.Model = "./silero_vad.onnx" + config.SileroVad.Threshold = 0.5 + config.SileroVad.MinSilenceDuration = 0.5 + config.SileroVad.MinSpeechDuration = 0.5 + config.SileroVad.WindowSize = 512 + config.SampleRate = 16000 + config.NumThreads = 1 + config.Provider = "cpu" + config.Debug = 1 + + var bufferSizeInSeconds float32 = 20 + + vad := sherpa.NewVoiceActivityDetector(&config, bufferSizeInSeconds) + return vad +} + +func main() { + log.SetFlags(log.LstdFlags | log.Lmicroseconds) + + vad := createVad() + defer sherpa.DeleteVoiceActivityDetector(vad) + + ex := createSpeakerEmbeddingExtractor() + defer sherpa.DeleteSpeakerEmbeddingExtractor(ex) + + manager := sherpa.NewSpeakerEmbeddingManager(ex.Dim()) + defer sherpa.DeleteSpeakerEmbeddingManager(manager) + registerSpeakers(ex, manager) + + err := portaudio.Initialize() + if err != nil { + log.Fatalf("Unable to initialize portaudio: %v\n", err) + } + defer portaudio.Terminate() + + default_device, err := portaudio.DefaultInputDevice() + if err != nil { + log.Fatal("Failed to get default input device: %v\n", err) + } + log.Printf("Selected default input device: %s\n", default_device.Name) + param := portaudio.StreamParameters{} + param.Input.Device = default_device + param.Input.Channels = 1 + param.Input.Latency = default_device.DefaultHighInputLatency + + param.SampleRate = 16000 + param.FramesPerBuffer = 0 + param.Flags = portaudio.ClipOff + + // you can choose another value for 0.1 if you want + samplesPerCall := int32(param.SampleRate * 0.1) // 0.1 second + samples := make([]float32, samplesPerCall) + + s, err := portaudio.OpenStream(param, samples) + if err != nil { + log.Fatalf("Failed to open the stream") + } + + defer s.Close() + chk(s.Start()) + + log.Print("Started! Please speak") + printed := false + + k := 0 + for { + chk(s.Read()) + vad.AcceptWaveform(samples) + + if vad.IsSpeech() && !printed { + printed = true + log.Print("Detected speech\n") + } + + if !vad.IsSpeech() { + printed = false + } + + for !vad.IsEmpty() { + speechSegment := vad.Front() + vad.Pop() + + audio := &sherpa.Wave{} + audio.Samples = speechSegment.Samples + audio.SampleRate = 16000 + + // Now decode it + go decode(ex, manager, audio, k) + + k += 1 + } + } + + chk(s.Stop()) + +} + +func chk(err error) { + if err != nil { + panic(err) + } +} + +func decode(ex *sherpa.SpeakerEmbeddingExtractor, manager *sherpa.SpeakerEmbeddingManager, audio *sherpa.GeneratedAudio, id int) { + stream := ex.CreateStream() + defer sherpa.DeleteOnlineStream(stream) + + stream.AcceptWaveform(audio.SampleRate, audio.Samples) + stream.InputFinished() + embeddings := ex.Compute(stream) + threshold := float32(0.5) + name := manager.Search(embeddings, threshold) + if len(name) > 0 { + log.Printf("Found speaker: %v\n", name) + } else { + log.Print("Unknown speaker\n") + name = "Unknown" + } + + duration := float32(len(audio.Samples)) / float32(audio.SampleRate) + + filename := fmt.Sprintf("seg-%d-%.2f-seconds-%s.wav", id, duration, name) + ok := audio.Save(filename) + if ok { + log.Printf("Saved to %s", filename) + } + log.Print("----------\n") +} diff --git a/go-api-examples/vad-speaker-identification/run.sh b/go-api-examples/vad-speaker-identification/run.sh new file mode 100755 index 00000000..3829eee9 --- /dev/null +++ b/go-api-examples/vad-speaker-identification/run.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +if [ ! -f ./3dspeaker_speech_campplus_sv_zh-cn_16k-common.onnx ]; then + curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/speaker-recongition-models/3dspeaker_speech_campplus_sv_zh-cn_16k-common.onnx +fi + +if [ ! -f ./sr-data/enroll/fangjun-sr-1.wav ]; then + git clone https://github.com/csukuangfj/sr-data +fi + +go mod tidy +go build +./speaker-identification diff --git a/go-api-examples/vad-spoken-language-identification/main.go b/go-api-examples/vad-spoken-language-identification/main.go index 5db250e8..71c37532 100644 --- a/go-api-examples/vad-spoken-language-identification/main.go +++ b/go-api-examples/vad-spoken-language-identification/main.go @@ -99,7 +99,7 @@ func main() { duration := float32(len(speechSegment.Samples)) / float32(config.SampleRate) - audio := &sherpa.GeneratedAudio{} + audio := &sherpa.Wave{} audio.Samples = speechSegment.Samples audio.SampleRate = config.SampleRate @@ -115,7 +115,7 @@ func main() { chk(s.Stop()) } -func decode(slid *sherpa.SpokenLanguageIdentification, audio *sherpa.GeneratedAudio, id int) { +func decode(slid *sherpa.SpokenLanguageIdentification, audio *sherpa.Wave, id int) { stream := slid.CreateStream() defer sherpa.DeleteOfflineStream(stream) diff --git a/ios-swift/SherpaOnnx/SherpaOnnx.xcodeproj/project.xcworkspace/xcuserdata/fangjun.xcuserdatad/UserInterfaceState.xcuserstate b/ios-swift/SherpaOnnx/SherpaOnnx.xcodeproj/project.xcworkspace/xcuserdata/fangjun.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index e6383ab056cbdcdc5ea5d9ba630091960c05d39f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42948 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 K{^I|)F8Dvq69-8E diff --git a/ios-swift/SherpaOnnx/SherpaOnnx.xcodeproj/xcuserdata/fangjun.xcuserdatad/xcschemes/xcschememanagement.plist b/ios-swift/SherpaOnnx/SherpaOnnx.xcodeproj/xcuserdata/fangjun.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index 4f6d363e..00000000 --- a/ios-swift/SherpaOnnx/SherpaOnnx.xcodeproj/xcuserdata/fangjun.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,14 +0,0 @@ - - - - - SchemeUserState - - SherpaOnnx.xcscheme_^#shared#^_ - - orderHint - 0 - - - - diff --git a/ios-swiftui/SherpaOnnx/SherpaOnnx.xcodeproj/project.xcworkspace/xcuserdata/fangjun.xcuserdatad/UserInterfaceState.xcuserstate b/ios-swiftui/SherpaOnnx/SherpaOnnx.xcodeproj/project.xcworkspace/xcuserdata/fangjun.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index 86affab0e9c6646a04cd5298d6cf2b5ecb035fc6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16738 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 diff --git a/scripts/go/_internal/speaker-identification/.gitignore b/scripts/go/_internal/speaker-identification/.gitignore new file mode 100644 index 00000000..41c46990 --- /dev/null +++ b/scripts/go/_internal/speaker-identification/.gitignore @@ -0,0 +1 @@ +speaker-identification diff --git a/scripts/go/_internal/speaker-identification/go.mod b/scripts/go/_internal/speaker-identification/go.mod new file mode 100644 index 00000000..38c62bc3 --- /dev/null +++ b/scripts/go/_internal/speaker-identification/go.mod @@ -0,0 +1,5 @@ +module speaker-identification + +go 1.12 + +replace github.com/k2-fsa/sherpa-onnx-go/sherpa_onnx => ../ diff --git a/scripts/go/_internal/speaker-identification/main.go b/scripts/go/_internal/speaker-identification/main.go new file mode 120000 index 00000000..90623708 --- /dev/null +++ b/scripts/go/_internal/speaker-identification/main.go @@ -0,0 +1 @@ +../../../../go-api-examples/speaker-identification/main.go \ No newline at end of file diff --git a/scripts/go/_internal/speaker-identification/run.sh b/scripts/go/_internal/speaker-identification/run.sh new file mode 120000 index 00000000..41164f3e --- /dev/null +++ b/scripts/go/_internal/speaker-identification/run.sh @@ -0,0 +1 @@ +../../../../go-api-examples/speaker-identification/run.sh \ No newline at end of file diff --git a/scripts/go/_internal/vad-asr-paraformer/go.mod b/scripts/go/_internal/vad-asr-paraformer/go.mod index d0130405..7b763cc8 100644 --- a/scripts/go/_internal/vad-asr-paraformer/go.mod +++ b/scripts/go/_internal/vad-asr-paraformer/go.mod @@ -3,8 +3,3 @@ module vad-asr-paraformer go 1.12 replace github.com/k2-fsa/sherpa-onnx-go/sherpa_onnx => ../ - -require ( - github.com/gordonklaus/portaudio v0.0.0-20230709114228-aafa478834f5 - github.com/k2-fsa/sherpa-onnx-go/sherpa_onnx v0.0.0-00010101000000-000000000000 -) diff --git a/scripts/go/_internal/vad-speaker-identification/go.mod b/scripts/go/_internal/vad-speaker-identification/go.mod new file mode 100644 index 00000000..faee85d3 --- /dev/null +++ b/scripts/go/_internal/vad-speaker-identification/go.mod @@ -0,0 +1,5 @@ +module vad-speaker-identification + +go 1.12 + +replace github.com/k2-fsa/sherpa-onnx-go/sherpa_onnx => ../ diff --git a/scripts/go/_internal/vad-speaker-identification/main.go b/scripts/go/_internal/vad-speaker-identification/main.go new file mode 120000 index 00000000..3109e8b0 --- /dev/null +++ b/scripts/go/_internal/vad-speaker-identification/main.go @@ -0,0 +1 @@ +../../../../go-api-examples/vad-speaker-identification/main.go \ No newline at end of file diff --git a/scripts/go/_internal/vad-speaker-identification/run.sh b/scripts/go/_internal/vad-speaker-identification/run.sh new file mode 120000 index 00000000..d3520535 --- /dev/null +++ b/scripts/go/_internal/vad-speaker-identification/run.sh @@ -0,0 +1 @@ +../../../../go-api-examples/vad-speaker-identification/run.sh \ No newline at end of file diff --git a/scripts/go/sherpa_onnx.go b/scripts/go/sherpa_onnx.go index 01dc5948..1b4c60ab 100644 --- a/scripts/go/sherpa_onnx.go +++ b/scripts/go/sherpa_onnx.go @@ -746,11 +746,11 @@ func (vad *VoiceActivityDetector) AcceptWaveform(samples []float32) { } func (vad *VoiceActivityDetector) IsEmpty() bool { - return 1 == int(C.SherpaOnnxVoiceActivityDetectorEmpty(vad.impl)) + return int(C.SherpaOnnxVoiceActivityDetectorEmpty(vad.impl)) == 1 } func (vad *VoiceActivityDetector) IsSpeech() bool { - return 1 == int(C.SherpaOnnxVoiceActivityDetectorDetected(vad.impl)) + return int(C.SherpaOnnxVoiceActivityDetectorDetected(vad.impl)) == 1 } func (vad *VoiceActivityDetector) Pop() { @@ -852,3 +852,204 @@ func (slid *SpokenLanguageIdentification) Compute(stream *OfflineStream) *Spoken return ans } + +// ============================================================ +// For speaker embedding extraction +// ============================================================ + +type SpeakerEmbeddingExtractorConfig struct { + Model string + NumThreads int + Debug int + Provider string +} + +type SpeakerEmbeddingExtractor struct { + impl *C.struct_SherpaOnnxSpeakerEmbeddingExtractor +} + +// The user has to invoke [DeleteSpeakerEmbeddingExtractor]() to free the returned value +// to avoid memory leak +func NewSpeakerEmbeddingExtractor(config *SpeakerEmbeddingExtractorConfig) *SpeakerEmbeddingExtractor { + c := C.struct_SherpaOnnxSpeakerEmbeddingExtractorConfig{} + + c.model = C.CString(config.Model) + defer C.free(unsafe.Pointer(c.model)) + + c.num_threads = C.int(config.NumThreads) + c.debug = C.int(config.Debug) + + c.provider = C.CString(config.Provider) + defer C.free(unsafe.Pointer(c.provider)) + + ex := &SpeakerEmbeddingExtractor{} + ex.impl = C.SherpaOnnxCreateSpeakerEmbeddingExtractor(&c) + + return ex +} + +func DeleteSpeakerEmbeddingExtractor(ex *SpeakerEmbeddingExtractor) { + C.SherpaOnnxDestroySpeakerEmbeddingExtractor(ex.impl) + ex.impl = nil +} + +func (ex *SpeakerEmbeddingExtractor) Dim() int { + return int(C.SherpaOnnxSpeakerEmbeddingExtractorDim(ex.impl)) +} + +// The user is responsible to invoke [DeleteOnlineStream]() to free +// the returned stream to avoid memory leak +func (ex *SpeakerEmbeddingExtractor) CreateStream() *OnlineStream { + stream := &OnlineStream{} + stream.impl = C.SherpaOnnxSpeakerEmbeddingExtractorCreateStream(ex.impl) + return stream +} + +func (ex *SpeakerEmbeddingExtractor) IsReady(stream *OnlineStream) bool { + return int(C.SherpaOnnxSpeakerEmbeddingExtractorIsReady(ex.impl, stream.impl)) == 1 +} + +func (ex *SpeakerEmbeddingExtractor) Compute(stream *OnlineStream) []float32 { + embedding := C.SherpaOnnxSpeakerEmbeddingExtractorComputeEmbedding(ex.impl, stream.impl) + defer C.SherpaOnnxSpeakerEmbeddingExtractorDestroyEmbedding(embedding) + + n := ex.Dim() + ans := make([]float32, n) + + // see https://stackoverflow.com/questions/48756732/what-does-1-30c-yourtype-do-exactly-in-cgo + // :n:n means 0:n:n, means low:high:capacity + c := (*[1 << 28]C.float)(unsafe.Pointer(embedding))[:n:n] + + for i := 0; i < n; i++ { + ans[i] = float32(c[i]) + } + + return ans +} + +type SpeakerEmbeddingManager struct { + impl *C.struct_SherpaOnnxSpeakerEmbeddingManager +} + +// The user has to invoke [DeleteSpeakerEmbeddingManager]() to free the returned +// value to avoid memory leak +func NewSpeakerEmbeddingManager(dim int) *SpeakerEmbeddingManager { + m := &SpeakerEmbeddingManager{} + m.impl = C.SherpaOnnxCreateSpeakerEmbeddingManager(C.int(dim)) + return m +} + +func DeleteSpeakerEmbeddingManager(m *SpeakerEmbeddingManager) { + C.SherpaOnnxDestroySpeakerEmbeddingManager(m.impl) + m.impl = nil +} + +func (m *SpeakerEmbeddingManager) Register(name string, embedding []float32) bool { + s := C.CString(name) + defer C.free(unsafe.Pointer(s)) + + return C.int(C.SherpaOnnxSpeakerEmbeddingManagerAdd(m.impl, s, (*C.float)(&embedding[0]))) == 1 +} + +func (m *SpeakerEmbeddingManager) RegisterV(name string, embeddings [][]float32) bool { + s := C.CString(name) + defer C.free(unsafe.Pointer(s)) + + if len(embeddings) == 0 { + return false + } + + dim := len(embeddings[0]) + v := make([]float32, 0, dim*len(embeddings)) + for _, embedding := range embeddings { + v = append(v, embedding...) + } + + return C.int(C.SherpaOnnxSpeakerEmbeddingManagerAddListFlattened(m.impl, s, (*C.float)(&v[0]), C.int(len(embeddings)))) == 1 +} + +func (m *SpeakerEmbeddingManager) Remove(name string) bool { + s := C.CString(name) + defer C.free(unsafe.Pointer(s)) + + return C.int(C.SherpaOnnxSpeakerEmbeddingManagerRemove(m.impl, s)) == 1 +} + +func (m *SpeakerEmbeddingManager) Search(embedding []float32, threshold float32) string { + var s string + + name := C.SherpaOnnxSpeakerEmbeddingManagerSearch(m.impl, (*C.float)(&embedding[0]), C.float(threshold)) + defer C.SherpaOnnxSpeakerEmbeddingManagerFreeSearch(name) + + if name != nil { + s = C.GoString(name) + } + + return s +} + +func (m *SpeakerEmbeddingManager) Verify(name string, embedding []float32, threshold float32) bool { + s := C.CString(name) + defer C.free(unsafe.Pointer(s)) + + return C.int(C.SherpaOnnxSpeakerEmbeddingManagerVerify(m.impl, s, (*C.float)(&embedding[0]), C.float(threshold))) == 1 +} + +func (m *SpeakerEmbeddingManager) Contains(name string) bool { + s := C.CString(name) + defer C.free(unsafe.Pointer(s)) + + return C.int(C.SherpaOnnxSpeakerEmbeddingManagerContains(m.impl, s)) == 1 +} + +func (m *SpeakerEmbeddingManager) NumSpeakers() int { + return int(C.SherpaOnnxSpeakerEmbeddingManagerNumSpeakers(m.impl)) +} + +func (m *SpeakerEmbeddingManager) AllSpeakers() []string { + all_speakers := C.SherpaOnnxSpeakerEmbeddingManagerGetAllSpeakers(m.impl) + defer C.SherpaOnnxSpeakerEmbeddingManagerFreeAllSpeakers(all_speakers) + + n := m.NumSpeakers() + if n == 0 { + return nil + } + + // https://stackoverflow.com/questions/62012070/convert-array-of-strings-from-cgo-in-go + p := (*[1 << 28]*C.char)(unsafe.Pointer(all_speakers))[:n:n] + + ans := make([]string, n) + + for i := 0; i < n; i++ { + ans[i] = C.GoString(p[i]) + } + + return ans +} + +// Wave + +// single channel wave +type Wave = GeneratedAudio + +func ReadWave(filename string) *Wave { + s := C.CString(filename) + defer C.free(unsafe.Pointer(s)) + + w := C.SherpaOnnxReadWave(s) + defer C.SherpaOnnxFreeWave(w) + + n := int(w.num_samples) + + ans := &Wave{} + ans.SampleRate = int(w.sample_rate) + samples := (*[1 << 28]C.float)(unsafe.Pointer(w.samples))[:n:n] + + ans.Samples = make([]float32, n) + + for i := 0; i < n; i++ { + ans.Samples[i] = float32(samples[i]) + } + + return ans +}