From c81234e32422760078728ff3ae8936fdfe8536b7 Mon Sep 17 00:00:00 2001 From: Julian Pscheid Date: Tue, 11 Aug 2026 16:42:32 -0700 Subject: [PATCH 1/2] feat: expose pyannote window shift ratio in bindings Expose the pyannote segmentation window shift ratio through the C API, CXX wrapper, and supported language bindings. Treat non-positive C values as the native 0.1 default so callers that zero-initialize config structs keep their existing behavior. --- .../diarization/SpeakerDiarizationObject.kt | 3 ++- c-api-examples/offline-speaker-diarization-c-api.c | 2 ++ .../offline-speaker-diarization-cxx-api.cc | 1 + .../bin/speaker-diarization.dart | 2 +- .../offline-speaker-diarization/Program.cs | 7 ++++--- .../lib/src/offline_speaker_diarization.dart | 2 ++ .../src/offline_speaker_diarization_config.dart | 8 +++++++- .../sherpa_onnx/lib/src/sherpa_onnx_bindings.dart | 3 +++ .../non-streaming-speaker-diarization/main.go | 1 + .../main/cpp/non-streaming-speaker-diarization.cc | 1 + .../components/NonStreamingSpeakerDiarization.ets | 1 + .../main/ets/workers/SpeakerDiarizationWorker.ets | 3 ++- .../OfflineSpeakerDiarizationDemo.java | 5 ++++- .../test_offline_speaker_diarization.kt | 5 ++++- .../test_offline_speaker_diarization.js | 1 + .../test-offline-speaker-diarization.js | 1 + pascal-api-examples/speaker-diarization/main.pas | 1 + python-api-examples/offline-speaker-diarization.py | 2 +- .../examples/offline_speaker_diarization.rs | 1 + ...fflineSpeakerSegmentationPyannoteModelConfig.cs | 3 ++- scripts/go/sherpa_onnx.go | 4 +++- scripts/node-addon-api/lib/types.js | 9 +++++---- sherpa-onnx/c-api/c-api.cc | 4 ++++ sherpa-onnx/c-api/c-api.h | 7 +++++++ sherpa-onnx/c-api/cxx-api.cc | 2 ++ sherpa-onnx/c-api/cxx-api.h | 2 ++ sherpa-onnx/c-api/docs/speaker-diarization.dox | 3 +++ .../offline-speaker-segmentation-pyannote-model.cc | 5 +++++ ...lineSpeakerSegmentationPyannoteModelConfig.java | 12 ++++++++++++ sherpa-onnx/jni/offline-speaker-diarization.cc | 3 +++ .../kotlin-api/OfflineSpeakerDiarization.kt | 1 + sherpa-onnx/pascal-api/sherpa_onnx.pas | 8 +++++++- .../python/csrc/offline-speaker-diarization.cc | 4 +++- .../src/offline_speaker_diarization.rs | 1 + .../sherpa-onnx/src/offline_speaker_diarization.rs | 14 +++++++++++++- swift-api-examples/SherpaOnnx.swift | 11 +++++++---- swift-api-examples/speaker-diarization.swift | 4 +++- .../sherpa-onnx-speaker-diarization.js | 11 ++++++++--- .../sherpa-onnx-wasm-main-speaker-diarization.cc | 4 +++- 39 files changed, 134 insertions(+), 28 deletions(-) diff --git a/android/SherpaOnnxSpeakerDiarization/app/src/main/java/com/k2fsa/sherpa/onnx/speaker/diarization/SpeakerDiarizationObject.kt b/android/SherpaOnnxSpeakerDiarization/app/src/main/java/com/k2fsa/sherpa/onnx/speaker/diarization/SpeakerDiarizationObject.kt index 9df6bd5616..614169eaf0 100644 --- a/android/SherpaOnnxSpeakerDiarization/app/src/main/java/com/k2fsa/sherpa/onnx/speaker/diarization/SpeakerDiarizationObject.kt +++ b/android/SherpaOnnxSpeakerDiarization/app/src/main/java/com/k2fsa/sherpa/onnx/speaker/diarization/SpeakerDiarizationObject.kt @@ -48,7 +48,8 @@ object SpeakerDiarizationObject { val config = OfflineSpeakerDiarizationConfig( segmentation = OfflineSpeakerSegmentationModelConfig( pyannote = OfflineSpeakerSegmentationPyannoteModelConfig( - segmentationModel + model = segmentationModel, + windowShiftRatio = 0.1f, ), debug = true, ), diff --git a/c-api-examples/offline-speaker-diarization-c-api.c b/c-api-examples/offline-speaker-diarization-c-api.c index d5a17dd0b7..dbee34b816 100644 --- a/c-api-examples/offline-speaker-diarization-c-api.c +++ b/c-api-examples/offline-speaker-diarization-c-api.c @@ -73,6 +73,8 @@ int main() { memset(&config, 0, sizeof(config)); config.segmentation.pyannote.model = segmentation_model; + // A value of 0 also uses the default of 0.1. + config.segmentation.pyannote.window_shift_ratio = 0.1f; config.embedding.model = embedding_extractor_model; // the test wave ./0-four-speakers-zh.wav has 4 speakers, so diff --git a/cxx-api-examples/offline-speaker-diarization-cxx-api.cc b/cxx-api-examples/offline-speaker-diarization-cxx-api.cc index a3cd6ce3dc..f58463dbe3 100644 --- a/cxx-api-examples/offline-speaker-diarization-cxx-api.cc +++ b/cxx-api-examples/offline-speaker-diarization-cxx-api.cc @@ -31,6 +31,7 @@ int32_t main() { OfflineSpeakerDiarizationConfig config; config.segmentation.pyannote.model = "./sherpa-onnx-pyannote-segmentation-3-0/model.onnx"; + config.segmentation.pyannote.window_shift_ratio = 0.1f; config.embedding.model = "./3dspeaker_speech_eres2net_base_sv_zh-cn_3dspeaker_16k.onnx"; config.clustering.num_clusters = 4; diff --git a/dart-api-examples/speaker-diarization/bin/speaker-diarization.dart b/dart-api-examples/speaker-diarization/bin/speaker-diarization.dart index 760adc8680..8e7c7afc74 100644 --- a/dart-api-examples/speaker-diarization/bin/speaker-diarization.dart +++ b/dart-api-examples/speaker-diarization/bin/speaker-diarization.dart @@ -46,7 +46,7 @@ void main(List arguments) async { final segmentationConfig = sherpa_onnx.OfflineSpeakerSegmentationModelConfig( pyannote: sherpa_onnx.OfflineSpeakerSegmentationPyannoteModelConfig( - model: segmentationModel), + model: segmentationModel, windowShiftRatio: 0.1), ); final embeddingConfig = diff --git a/dotnet-examples/offline-speaker-diarization/Program.cs b/dotnet-examples/offline-speaker-diarization/Program.cs index 4d8d91b0e5..38b6a12bff 100644 --- a/dotnet-examples/offline-speaker-diarization/Program.cs +++ b/dotnet-examples/offline-speaker-diarization/Program.cs @@ -39,9 +39,10 @@ class OfflineSpeakerDiarizationDemo { static void Main(string[] args) { - var config = new OfflineSpeakerDiarizationConfig(); - config.Segmentation.Pyannote.Model = "./sherpa-onnx-pyannote-segmentation-3-0/model.onnx"; - config.Embedding.Model = "./3dspeaker_speech_eres2net_base_sv_zh-cn_3dspeaker_16k.onnx"; + var config = new OfflineSpeakerDiarizationConfig(); + config.Segmentation.Pyannote.Model = "./sherpa-onnx-pyannote-segmentation-3-0/model.onnx"; + config.Segmentation.Pyannote.WindowShiftRatio = 0.1f; + config.Embedding.Model = "./3dspeaker_speech_eres2net_base_sv_zh-cn_3dspeaker_16k.onnx"; // the test wave ./0-four-speakers-zh.wav has 4 speakers, so // we set num_clusters to 4 diff --git a/flutter/sherpa_onnx/lib/src/offline_speaker_diarization.dart b/flutter/sherpa_onnx/lib/src/offline_speaker_diarization.dart index 965e6381d3..f65303b94b 100644 --- a/flutter/sherpa_onnx/lib/src/offline_speaker_diarization.dart +++ b/flutter/sherpa_onnx/lib/src/offline_speaker_diarization.dart @@ -40,6 +40,8 @@ class OfflineSpeakerDiarization { c.ref.segmentation.pyannote.model = config.segmentation.pyannote.model.toNativeUtf8(); + c.ref.segmentation.pyannote.windowShiftRatio = + config.segmentation.pyannote.windowShiftRatio; c.ref.segmentation.numThreads = config.segmentation.numThreads; c.ref.segmentation.debug = config.segmentation.debug ? 1 : 0; c.ref.segmentation.provider = config.segmentation.provider.toNativeUtf8(); diff --git a/flutter/sherpa_onnx/lib/src/offline_speaker_diarization_config.dart b/flutter/sherpa_onnx/lib/src/offline_speaker_diarization_config.dart index 474784e0de..061add193d 100644 --- a/flutter/sherpa_onnx/lib/src/offline_speaker_diarization_config.dart +++ b/flutter/sherpa_onnx/lib/src/offline_speaker_diarization_config.dart @@ -43,25 +43,31 @@ class OfflineSpeakerDiarizationSegment { class OfflineSpeakerSegmentationPyannoteModelConfig { const OfflineSpeakerSegmentationPyannoteModelConfig({ this.model = '', + this.windowShiftRatio = 0.1, }); factory OfflineSpeakerSegmentationPyannoteModelConfig.fromJson( Map json) { return OfflineSpeakerSegmentationPyannoteModelConfig( model: json['model'] as String? ?? '', + windowShiftRatio: + (json['windowShiftRatio'] as num?)?.toDouble() ?? 0.1, ); } @override String toString() { - return 'OfflineSpeakerSegmentationPyannoteModelConfig(model: $model)'; + return 'OfflineSpeakerSegmentationPyannoteModelConfig(model: $model, ' + 'windowShiftRatio: $windowShiftRatio)'; } Map toJson() => { 'model': model, + 'windowShiftRatio': windowShiftRatio, }; final String model; + final double windowShiftRatio; } /// Segmentation model configuration for speaker diarization. diff --git a/flutter/sherpa_onnx/lib/src/sherpa_onnx_bindings.dart b/flutter/sherpa_onnx/lib/src/sherpa_onnx_bindings.dart index 95a0ee4b4d..ca38b91d71 100644 --- a/flutter/sherpa_onnx/lib/src/sherpa_onnx_bindings.dart +++ b/flutter/sherpa_onnx/lib/src/sherpa_onnx_bindings.dart @@ -71,6 +71,9 @@ final class SherpaOnnxOfflineSpeakerDiarizationSegment extends Struct { final class SherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig extends Struct { external Pointer model; + + @Float() + external double windowShiftRatio; } final class SherpaOnnxOfflineSpeakerSegmentationModelConfig extends Struct { diff --git a/go-api-examples/non-streaming-speaker-diarization/main.go b/go-api-examples/non-streaming-speaker-diarization/main.go index 7b975bf614..45d46c8b45 100644 --- a/go-api-examples/non-streaming-speaker-diarization/main.go +++ b/go-api-examples/non-streaming-speaker-diarization/main.go @@ -38,6 +38,7 @@ func initSpeakerDiarization() *sherpa.OfflineSpeakerDiarization { config := sherpa.OfflineSpeakerDiarizationConfig{} config.Segmentation.Pyannote.Model = "./sherpa-onnx-pyannote-segmentation-3-0/model.onnx" + config.Segmentation.Pyannote.WindowShiftRatio = 0.1 config.Embedding.Model = "./3dspeaker_speech_eres2net_base_sv_zh-cn_3dspeaker_16k.onnx" // The test wave file contains 4 speakers, so we use 4 here diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/non-streaming-speaker-diarization.cc b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/non-streaming-speaker-diarization.cc index 60ccef969c..0443c3e234 100644 --- a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/non-streaming-speaker-diarization.cc +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/non-streaming-speaker-diarization.cc @@ -23,6 +23,7 @@ GetOfflineSpeakerSegmentationPyannoteModelConfig(Napi::Object obj) { Napi::Object o = obj.Get("pyannote").As(); SHERPA_ONNX_ASSIGN_ATTR_STR(model, model); + SHERPA_ONNX_ASSIGN_ATTR_FLOAT(window_shift_ratio, windowShiftRatio); return c; } diff --git a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/ets/components/NonStreamingSpeakerDiarization.ets b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/ets/components/NonStreamingSpeakerDiarization.ets index 176da87a58..0b59023e3b 100644 --- a/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/ets/components/NonStreamingSpeakerDiarization.ets +++ b/harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/ets/components/NonStreamingSpeakerDiarization.ets @@ -10,6 +10,7 @@ import { SpeakerEmbeddingExtractorConfig } from './SpeakerIdentification'; export class OfflineSpeakerSegmentationPyannoteModelConfig { public model: string = ''; + public windowShiftRatio: number = 0.1; } export class OfflineSpeakerSegmentationModelConfig { diff --git a/harmony-os/SherpaOnnxSpeakerDiarization/entry/src/main/ets/workers/SpeakerDiarizationWorker.ets b/harmony-os/SherpaOnnxSpeakerDiarization/entry/src/main/ets/workers/SpeakerDiarizationWorker.ets index 4a297ec877..8eb9508520 100644 --- a/harmony-os/SherpaOnnxSpeakerDiarization/entry/src/main/ets/workers/SpeakerDiarizationWorker.ets +++ b/harmony-os/SherpaOnnxSpeakerDiarization/entry/src/main/ets/workers/SpeakerDiarizationWorker.ets @@ -32,6 +32,7 @@ function initOfflineSpeakerDiarization(context: Context): OfflineSpeakerDiarizat // // Also, please delete unused files to reduce the size of the app config.segmentation.pyannote.model = 'sherpa-onnx-pyannote-segmentation-3-0/model.int8.onnx'; + config.segmentation.pyannote.windowShiftRatio = 0.1; config.segmentation.numThreads = 2; config.segmentation.debug = true; @@ -186,4 +187,4 @@ workerPort.onmessageerror = (e: MessageEvents) => { * @param e error message */ workerPort.onerror = (e: ErrorEvent) => { -} \ No newline at end of file +} diff --git a/java-api-examples/OfflineSpeakerDiarizationDemo.java b/java-api-examples/OfflineSpeakerDiarizationDemo.java index 56279e58fa..ae5c698ea0 100644 --- a/java-api-examples/OfflineSpeakerDiarizationDemo.java +++ b/java-api-examples/OfflineSpeakerDiarizationDemo.java @@ -39,7 +39,10 @@ public static void main(String[] args) { WaveReader reader = new WaveReader(waveFilename); OfflineSpeakerSegmentationPyannoteModelConfig pyannote = - OfflineSpeakerSegmentationPyannoteModelConfig.builder().setModel(segmentationModel).build(); + OfflineSpeakerSegmentationPyannoteModelConfig.builder() + .setModel(segmentationModel) + .setWindowShiftRatio(0.1f) + .build(); OfflineSpeakerSegmentationModelConfig segmentation = OfflineSpeakerSegmentationModelConfig.builder() diff --git a/kotlin-api-examples/test_offline_speaker_diarization.kt b/kotlin-api-examples/test_offline_speaker_diarization.kt index 96c33f062e..d149d4db74 100644 --- a/kotlin-api-examples/test_offline_speaker_diarization.kt +++ b/kotlin-api-examples/test_offline_speaker_diarization.kt @@ -15,7 +15,10 @@ fun callback(numProcessedChunks: Int, numTotalChunks: Int, arg: Long): Int { fun testOfflineSpeakerDiarization() { var config = OfflineSpeakerDiarizationConfig( segmentation=OfflineSpeakerSegmentationModelConfig( - pyannote=OfflineSpeakerSegmentationPyannoteModelConfig("./sherpa-onnx-pyannote-segmentation-3-0/model.onnx"), + pyannote=OfflineSpeakerSegmentationPyannoteModelConfig( + model="./sherpa-onnx-pyannote-segmentation-3-0/model.onnx", + windowShiftRatio=0.1f, + ), ), embedding=SpeakerEmbeddingExtractorConfig( model="./3dspeaker_speech_eres2net_base_sv_zh-cn_3dspeaker_16k.onnx", diff --git a/nodejs-addon-examples/test_offline_speaker_diarization.js b/nodejs-addon-examples/test_offline_speaker_diarization.js index bf881e13cf..b453f5955a 100644 --- a/nodejs-addon-examples/test_offline_speaker_diarization.js +++ b/nodejs-addon-examples/test_offline_speaker_diarization.js @@ -20,6 +20,7 @@ const config = { segmentation: { pyannote: { model: './sherpa-onnx-pyannote-segmentation-3-0/model.onnx', + windowShiftRatio: 0.1, }, }, embedding: { diff --git a/nodejs-examples/test-offline-speaker-diarization.js b/nodejs-examples/test-offline-speaker-diarization.js index 2f80fc5a35..3d97b13e75 100644 --- a/nodejs-examples/test-offline-speaker-diarization.js +++ b/nodejs-examples/test-offline-speaker-diarization.js @@ -20,6 +20,7 @@ const config = { segmentation: { pyannote: { model: './sherpa-onnx-pyannote-segmentation-3-0/model.onnx', + windowShiftRatio: 0.1, debug: 1, }, }, diff --git a/pascal-api-examples/speaker-diarization/main.pas b/pascal-api-examples/speaker-diarization/main.pas index 35d915d0ba..0f67f648ff 100644 --- a/pascal-api-examples/speaker-diarization/main.pas +++ b/pascal-api-examples/speaker-diarization/main.pas @@ -62,6 +62,7 @@ function ProgressCallback( Wave := SherpaOnnxReadWave('./0-four-speakers-zh.wav'); Config.Segmentation.Pyannote.Model := './sherpa-onnx-pyannote-segmentation-3-0/model.onnx'; + Config.Segmentation.Pyannote.WindowShiftRatio := 0.1; Config.Embedding.Model := './3dspeaker_speech_eres2net_base_sv_zh-cn_3dspeaker_16k.onnx'; { diff --git a/python-api-examples/offline-speaker-diarization.py b/python-api-examples/offline-speaker-diarization.py index bdf4448d08..689935f6e9 100755 --- a/python-api-examples/offline-speaker-diarization.py +++ b/python-api-examples/offline-speaker-diarization.py @@ -73,7 +73,7 @@ def init_speaker_diarization(num_speakers: int = -1, cluster_threshold: float = config = sherpa_onnx.OfflineSpeakerDiarizationConfig( segmentation=sherpa_onnx.OfflineSpeakerSegmentationModelConfig( pyannote=sherpa_onnx.OfflineSpeakerSegmentationPyannoteModelConfig( - model=segmentation_model + model=segmentation_model, window_shift_ratio=0.1 ), ), embedding=sherpa_onnx.SpeakerEmbeddingExtractorConfig( diff --git a/rust-api-examples/examples/offline_speaker_diarization.rs b/rust-api-examples/examples/offline_speaker_diarization.rs index a34a1ec80e..46ab6ce0f4 100644 --- a/rust-api-examples/examples/offline_speaker_diarization.rs +++ b/rust-api-examples/examples/offline_speaker_diarization.rs @@ -9,6 +9,7 @@ fn main() { segmentation: OfflineSpeakerSegmentationModelConfig { pyannote: OfflineSpeakerSegmentationPyannoteModelConfig { model: Some("./sherpa-onnx-pyannote-segmentation-3-0/model.onnx".into()), + window_shift_ratio: 0.1, }, ..Default::default() }, diff --git a/scripts/dotnet/OfflineSpeakerSegmentationPyannoteModelConfig.cs b/scripts/dotnet/OfflineSpeakerSegmentationPyannoteModelConfig.cs index 3197621257..3a48187fb7 100644 --- a/scripts/dotnet/OfflineSpeakerSegmentationPyannoteModelConfig.cs +++ b/scripts/dotnet/OfflineSpeakerSegmentationPyannoteModelConfig.cs @@ -11,10 +11,11 @@ public struct OfflineSpeakerSegmentationPyannoteModelConfig public OfflineSpeakerSegmentationPyannoteModelConfig() { Model = ""; + WindowShiftRatio = 0.1f; } [MarshalAs(UnmanagedType.LPStr)] public string Model; + public float WindowShiftRatio; } } - diff --git a/scripts/go/sherpa_onnx.go b/scripts/go/sherpa_onnx.go index f1ac2d6bfa..7ae26ab2cf 100644 --- a/scripts/go/sherpa_onnx.go +++ b/scripts/go/sherpa_onnx.go @@ -2080,7 +2080,8 @@ func ReadWave(filename string) *Wave { // For offline speaker diarization // ============================================================ type OfflineSpeakerSegmentationPyannoteModelConfig struct { - Model string + Model string + WindowShiftRatio float32 } type OfflineSpeakerSegmentationModelConfig struct { @@ -2116,6 +2117,7 @@ func NewOfflineSpeakerDiarization(config *OfflineSpeakerDiarizationConfig) *Offl c := C.struct_SherpaOnnxOfflineSpeakerDiarizationConfig{} c.segmentation.pyannote.model = C.CString(config.Segmentation.Pyannote.Model) defer C.free(unsafe.Pointer(c.segmentation.pyannote.model)) + c.segmentation.pyannote.window_shift_ratio = C.float(config.Segmentation.Pyannote.WindowShiftRatio) c.segmentation.num_threads = C.int(config.Segmentation.NumThreads) diff --git a/scripts/node-addon-api/lib/types.js b/scripts/node-addon-api/lib/types.js index 98f6b78eb6..ef9bec1995 100644 --- a/scripts/node-addon-api/lib/types.js +++ b/scripts/node-addon-api/lib/types.js @@ -645,10 +645,11 @@ */ /** - * Offline speaker segmentation (pyannote) model config - * @typedef {Object} OfflineSpeakerSegmentationPyannoteModelConfig - * @property {string} [model] - */ + * Offline speaker segmentation (pyannote) model config + * @typedef {Object} OfflineSpeakerSegmentationPyannoteModelConfig + * @property {string} [model] + * @property {number} [windowShiftRatio=0.1] + */ /** * Offline speaker segmentation model config diff --git a/sherpa-onnx/c-api/c-api.cc b/sherpa-onnx/c-api/c-api.cc index 4db9fb8f0c..8e80db937c 100644 --- a/sherpa-onnx/c-api/c-api.cc +++ b/sherpa-onnx/c-api/c-api.cc @@ -3149,6 +3149,10 @@ GetOfflineSpeakerDiarizationConfig( sd_config.segmentation.pyannote.model = SHERPA_ONNX_OR(config->segmentation.pyannote.model, ""); + sd_config.segmentation.pyannote.window_shift_ratio = + config->segmentation.pyannote.window_shift_ratio <= 0 + ? 0.1f + : config->segmentation.pyannote.window_shift_ratio; sd_config.segmentation.num_threads = GetNumThreads(config->segmentation.num_threads); sd_config.segmentation.debug = config->segmentation.debug; diff --git a/sherpa-onnx/c-api/c-api.h b/sherpa-onnx/c-api/c-api.h index 3a1c53a81f..3d464a607c 100644 --- a/sherpa-onnx/c-api/c-api.h +++ b/sherpa-onnx/c-api/c-api.h @@ -3836,6 +3836,13 @@ SHERPA_ONNX_API int32_t SherpaOnnxLinearResamplerResampleGetOutputSampleRate( typedef struct SherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig { /** Segmentation model filename. */ const char *model; + /** + * Sliding-window shift as a fraction of the model window size. + * + * Must be in (0, 1]. Set to 0 (or a negative value) to use the default of + * 0.1. + */ + float window_shift_ratio; } SherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig; /** diff --git a/sherpa-onnx/c-api/cxx-api.cc b/sherpa-onnx/c-api/cxx-api.cc index 8e9d7a9772..45e2893852 100644 --- a/sherpa-onnx/c-api/cxx-api.cc +++ b/sherpa-onnx/c-api/cxx-api.cc @@ -1464,6 +1464,8 @@ OfflineSpeakerDiarization OfflineSpeakerDiarization::Create( memset(&c, 0, sizeof(c)); c.segmentation.pyannote.model = config.segmentation.pyannote.model.c_str(); + c.segmentation.pyannote.window_shift_ratio = + config.segmentation.pyannote.window_shift_ratio; c.segmentation.num_threads = config.segmentation.num_threads; c.segmentation.debug = config.segmentation.debug; c.segmentation.provider = config.segmentation.provider.c_str(); diff --git a/sherpa-onnx/c-api/cxx-api.h b/sherpa-onnx/c-api/cxx-api.h index feb8d68bfc..d1e1273be3 100644 --- a/sherpa-onnx/c-api/cxx-api.h +++ b/sherpa-onnx/c-api/cxx-api.h @@ -1912,6 +1912,8 @@ class SHERPA_ONNX_API SpeakerEmbeddingManager struct OfflineSpeakerSegmentationPyannoteModelConfig { /** Segmentation model filename. */ std::string model; + /** Sliding-window shift as a fraction of the model window size. */ + float window_shift_ratio = 0.1f; }; /** @brief Segmentation model configuration for offline speaker diarization. */ diff --git a/sherpa-onnx/c-api/docs/speaker-diarization.dox b/sherpa-onnx/c-api/docs/speaker-diarization.dox index afd5ac83a3..633b8d96ef 100644 --- a/sherpa-onnx/c-api/docs/speaker-diarization.dox +++ b/sherpa-onnx/c-api/docs/speaker-diarization.dox @@ -14,6 +14,9 @@ SherpaOnnxOfflineSpeakerDiarizationConfig config; memset(&config, 0, sizeof(config)); config.segmentation.pyannote.model = "./sherpa-onnx-pyannote-segmentation-3-0/model.onnx"; +// Zero uses the default window shift ratio of 0.1. Set a value in (0, 1] +// to change the sliding-window overlap. +config.segmentation.pyannote.window_shift_ratio = 0.1f; config.embedding.model = "./3dspeaker_speech_eres2net_base_sv_zh-cn_3dspeaker_16k.onnx"; config.clustering.num_clusters = 4; diff --git a/sherpa-onnx/csrc/offline-speaker-segmentation-pyannote-model.cc b/sherpa-onnx/csrc/offline-speaker-segmentation-pyannote-model.cc index d27a8fe6d8..27493ed4b0 100644 --- a/sherpa-onnx/csrc/offline-speaker-segmentation-pyannote-model.cc +++ b/sherpa-onnx/csrc/offline-speaker-segmentation-pyannote-model.cc @@ -112,6 +112,11 @@ class OfflineSpeakerSegmentationPyannoteModel::Impl { meta_data_.window_shift = static_cast(window_shift); } + if (config_.debug) { + SHERPA_ONNX_LOGE("Pyannote window shift: %d samples", + meta_data_.window_shift); + } + SHERPA_ONNX_READ_META_DATA(meta_data_.receptive_field_size, "receptive_field_size"); SHERPA_ONNX_READ_META_DATA(meta_data_.receptive_field_shift, diff --git a/sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineSpeakerSegmentationPyannoteModelConfig.java b/sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineSpeakerSegmentationPyannoteModelConfig.java index 51fd998742..33becc3d41 100644 --- a/sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineSpeakerSegmentationPyannoteModelConfig.java +++ b/sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineSpeakerSegmentationPyannoteModelConfig.java @@ -4,9 +4,11 @@ public class OfflineSpeakerSegmentationPyannoteModelConfig { private final String model; + private final float windowShiftRatio; private OfflineSpeakerSegmentationPyannoteModelConfig(Builder builder) { this.model = builder.model; + this.windowShiftRatio = builder.windowShiftRatio; } public static Builder builder() { @@ -17,8 +19,13 @@ public String getModel() { return model; } + public float getWindowShiftRatio() { + return windowShiftRatio; + } + public static class Builder { private String model = ""; + private float windowShiftRatio = 0.1f; public OfflineSpeakerSegmentationPyannoteModelConfig build() { return new OfflineSpeakerSegmentationPyannoteModelConfig(this); @@ -28,5 +35,10 @@ public Builder setModel(String model) { this.model = model; return this; } + + public Builder setWindowShiftRatio(float value) { + this.windowShiftRatio = value; + return this; + } } } diff --git a/sherpa-onnx/jni/offline-speaker-diarization.cc b/sherpa-onnx/jni/offline-speaker-diarization.cc index 7b38173079..79fb1041ea 100644 --- a/sherpa-onnx/jni/offline-speaker-diarization.cc +++ b/sherpa-onnx/jni/offline-speaker-diarization.cc @@ -33,6 +33,9 @@ static OfflineSpeakerDiarizationConfig GetOfflineSpeakerDiarizationConfig( SHERPA_ONNX_JNI_READ_STRING(ans.segmentation.pyannote.model, model, pyannote_config_cls, pyannote_config); + SHERPA_ONNX_JNI_READ_FLOAT(ans.segmentation.pyannote.window_shift_ratio, + windowShiftRatio, pyannote_config_cls, + pyannote_config); SHERPA_ONNX_JNI_READ_INT(ans.segmentation.num_threads, numThreads, segmentation_config_cls, segmentation_config); diff --git a/sherpa-onnx/kotlin-api/OfflineSpeakerDiarization.kt b/sherpa-onnx/kotlin-api/OfflineSpeakerDiarization.kt index 0008955d71..feb84bc9c7 100644 --- a/sherpa-onnx/kotlin-api/OfflineSpeakerDiarization.kt +++ b/sherpa-onnx/kotlin-api/OfflineSpeakerDiarization.kt @@ -4,6 +4,7 @@ import android.content.res.AssetManager data class OfflineSpeakerSegmentationPyannoteModelConfig( var model: String = "", + var windowShiftRatio: Float = 0.1f, ) data class OfflineSpeakerSegmentationModelConfig( diff --git a/sherpa-onnx/pascal-api/sherpa_onnx.pas b/sherpa-onnx/pascal-api/sherpa_onnx.pas index cc8e86aae6..22514e4270 100644 --- a/sherpa-onnx/pascal-api/sherpa_onnx.pas +++ b/sherpa-onnx/pascal-api/sherpa_onnx.pas @@ -657,6 +657,7 @@ TSherpaOnnxVoiceActivityDetector = class TSherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig = record Model: AnsiString; + WindowShiftRatio: Single; function ToString: AnsiString; end; @@ -1261,6 +1262,7 @@ SherpaOnnxResampleOut = record SherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig = record Model: PAnsiChar; + WindowShiftRatio: cfloat; end; SherpaOnnxOfflineSpeakerSegmentationModelConfig = record @@ -3227,7 +3229,8 @@ procedure TSherpaOnnxLinearResampler.Reset; function TSherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig.ToString: AnsiString; begin Result := Format('TSherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig(' + - 'Model := %s)',[Self.Model]); + 'Model := %s, WindowShiftRatio := %f)', + [Self.Model, Self.WindowShiftRatio]); end; function TSherpaOnnxOfflineSpeakerSegmentationModelConfig.ToString: AnsiString; @@ -3243,6 +3246,7 @@ function TSherpaOnnxOfflineSpeakerSegmentationModelConfig.ToString: AnsiString; class operator TSherpaOnnxOfflineSpeakerSegmentationModelConfig.Initialize({$IFDEF FPC}var{$ELSE}out{$ENDIF} Dest: TSherpaOnnxOfflineSpeakerSegmentationModelConfig); begin + Dest.Pyannote.WindowShiftRatio := 0.1; Dest.NumThreads := 1; Dest.Debug := False; Dest.Provider := 'cpu'; @@ -3311,6 +3315,8 @@ constructor TSherpaOnnxOfflineSpeakerDiarization.Create(Config: TSherpaOnnxOffli begin C := Default(SherpaOnnxOfflineSpeakerDiarizationConfig); C.Segmentation.Pyannote.Model := PAnsiChar(Config.Segmentation.Pyannote.Model); + C.Segmentation.Pyannote.WindowShiftRatio := + Config.Segmentation.Pyannote.WindowShiftRatio; C.Segmentation.NumThreads := Config.Segmentation.NumThreads; C.Segmentation.Debug := Ord(Config.Segmentation.Debug); C.Segmentation.Provider := PAnsiChar(Config.Segmentation.Provider); diff --git a/sherpa-onnx/python/csrc/offline-speaker-diarization.cc b/sherpa-onnx/python/csrc/offline-speaker-diarization.cc index 9f1bb8469d..518c8896e6 100644 --- a/sherpa-onnx/python/csrc/offline-speaker-diarization.cc +++ b/sherpa-onnx/python/csrc/offline-speaker-diarization.cc @@ -51,8 +51,10 @@ static void PybindOfflineSpeakerSegmentationPyannoteModelConfig(py::module *m) { using PyClass = OfflineSpeakerSegmentationPyannoteModelConfig; py::class_(*m, "OfflineSpeakerSegmentationPyannoteModelConfig") .def(py::init<>()) - .def(py::init(), py::arg("model")) + .def(py::init(), py::arg("model"), + py::arg("window_shift_ratio") = 0.1f) .def_readwrite("model", &PyClass::model) + .def_readwrite("window_shift_ratio", &PyClass::window_shift_ratio) .def("__str__", &PyClass::ToString) .def("validate", &PyClass::Validate); } diff --git a/sherpa-onnx/rust/sherpa-onnx-sys/src/offline_speaker_diarization.rs b/sherpa-onnx/rust/sherpa-onnx-sys/src/offline_speaker_diarization.rs index 00232e0a98..ae1f29915e 100644 --- a/sherpa-onnx/rust/sherpa-onnx-sys/src/offline_speaker_diarization.rs +++ b/sherpa-onnx/rust/sherpa-onnx-sys/src/offline_speaker_diarization.rs @@ -8,6 +8,7 @@ use std::os::raw::{c_char, c_float}; #[derive(Debug, Copy, Clone)] pub struct OfflineSpeakerSegmentationPyannoteModelConfig { pub model: *const c_char, + pub window_shift_ratio: c_float, } #[repr(C)] diff --git a/sherpa-onnx/rust/sherpa-onnx/src/offline_speaker_diarization.rs b/sherpa-onnx/rust/sherpa-onnx/src/offline_speaker_diarization.rs index 8718dc07c8..93e8e5727d 100644 --- a/sherpa-onnx/rust/sherpa-onnx/src/offline_speaker_diarization.rs +++ b/sherpa-onnx/rust/sherpa-onnx/src/offline_speaker_diarization.rs @@ -8,10 +8,21 @@ use sherpa_onnx_sys as sys; use std::ffi::CString; use std::slice; -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] /// Pyannote segmentation model path. pub struct OfflineSpeakerSegmentationPyannoteModelConfig { pub model: Option, + /// Sliding-window shift as a fraction of the model window size. + pub window_shift_ratio: f32, +} + +impl Default for OfflineSpeakerSegmentationPyannoteModelConfig { + fn default() -> Self { + Self { + model: None, + window_shift_ratio: 0.1, + } + } } impl OfflineSpeakerSegmentationPyannoteModelConfig { @@ -21,6 +32,7 @@ impl OfflineSpeakerSegmentationPyannoteModelConfig { ) -> sys::OfflineSpeakerSegmentationPyannoteModelConfig { sys::OfflineSpeakerSegmentationPyannoteModelConfig { model: to_c_ptr(&self.model, cstrings), + window_shift_ratio: self.window_shift_ratio, } } } diff --git a/swift-api-examples/SherpaOnnx.swift b/swift-api-examples/SherpaOnnx.swift index 3aa22e91b0..9336bd584f 100644 --- a/swift-api-examples/SherpaOnnx.swift +++ b/swift-api-examples/SherpaOnnx.swift @@ -1757,10 +1757,13 @@ public class SherpaOnnxOnlinePunctuationWrapper { } } -public func sherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig(model: String) - -> SherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig -{ - return SherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig(model: toCPointer(model)) +public func sherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig( + model: String, + windowShiftRatio: Float = 0.1 +) -> SherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig { + return SherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig( + model: toCPointer(model), + window_shift_ratio: windowShiftRatio) } public func sherpaOnnxOfflineSpeakerSegmentationModelConfig( diff --git a/swift-api-examples/speaker-diarization.swift b/swift-api-examples/speaker-diarization.swift index 2191f90d77..1f47407f76 100644 --- a/swift-api-examples/speaker-diarization.swift +++ b/swift-api-examples/speaker-diarization.swift @@ -21,7 +21,9 @@ func run() { let numSpeakers = 4 var config = sherpaOnnxOfflineSpeakerDiarizationConfig( segmentation: sherpaOnnxOfflineSpeakerSegmentationModelConfig( - pyannote: sherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig(model: segmentationModel)), + pyannote: sherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig( + model: segmentationModel, + windowShiftRatio: 0.1)), embedding: sherpaOnnxSpeakerEmbeddingExtractorConfig(model: embeddingExtractorModel), clustering: sherpaOnnxFastClusteringConfig(numClusters: numSpeakers) ) diff --git a/wasm/speaker-diarization/sherpa-onnx-speaker-diarization.js b/wasm/speaker-diarization/sherpa-onnx-speaker-diarization.js index 865749cc5a..44e138e73c 100644 --- a/wasm/speaker-diarization/sherpa-onnx-speaker-diarization.js +++ b/wasm/speaker-diarization/sherpa-onnx-speaker-diarization.js @@ -29,7 +29,7 @@ function initSherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig( const n = modelLen; const buffer = Module._malloc(n); - const len = 1 * 4; + const len = 2 * 4; const ptr = Module._malloc(len); let offset = 0; @@ -38,6 +38,10 @@ function initSherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig( offset = 0; Module.setValue(ptr, buffer + offset, 'i8*'); + offset += 4; + + Module.setValue( + ptr + offset, config.windowShiftRatio ?? 0, 'float'); return { buffer: buffer, @@ -50,6 +54,7 @@ function initSherpaOnnxOfflineSpeakerSegmentationModelConfig(config, Module) { if (!('pyannote' in config)) { config.pyannote = { model: '', + windowShiftRatio: 0.1, }; } @@ -138,7 +143,7 @@ function initSherpaOnnxFastClusteringConfig(config, Module) { function initSherpaOnnxOfflineSpeakerDiarizationConfig(config, Module) { if (!('segmentation' in config)) { config.segmentation = { - pyannote: {model: ''}, + pyannote: {model: '', windowShiftRatio: 0.1}, numThreads: 1, debug: 0, provider: 'cpu', @@ -277,7 +282,7 @@ class OfflineSpeakerDiarization { function createOfflineSpeakerDiarization(Module, myConfig) { let config = { segmentation: { - pyannote: {model: './segmentation.onnx'}, + pyannote: {model: './segmentation.onnx', windowShiftRatio: 0.1}, debug: 1, }, embedding: { diff --git a/wasm/speaker-diarization/sherpa-onnx-wasm-main-speaker-diarization.cc b/wasm/speaker-diarization/sherpa-onnx-wasm-main-speaker-diarization.cc index 6e83f61d8b..78c34c3699 100644 --- a/wasm/speaker-diarization/sherpa-onnx-wasm-main-speaker-diarization.cc +++ b/wasm/speaker-diarization/sherpa-onnx-wasm-main-speaker-diarization.cc @@ -14,7 +14,7 @@ extern "C" { static_assert(sizeof(SherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig) == - 1 * 4, + 2 * 4, ""); static_assert( @@ -39,6 +39,8 @@ void MyPrint(const SherpaOnnxOfflineSpeakerDiarizationConfig *sd_config) { fprintf(stdout, "----------segmentation config----------\n"); fprintf(stdout, "pyannote model: %s\n", segmentation.pyannote.model); + fprintf(stdout, "pyannote window shift ratio: %.3f\n", + segmentation.pyannote.window_shift_ratio); fprintf(stdout, "num threads: %d\n", segmentation.num_threads); fprintf(stdout, "debug: %d\n", segmentation.debug); fprintf(stdout, "provider: %s\n", segmentation.provider); From 8ceb6b6eff7ff6a891456ad0f032d5f507af4f63 Mon Sep 17 00:00:00 2001 From: Julian Pscheid Date: Wed, 12 Aug 2026 06:06:17 -0700 Subject: [PATCH 2/2] docs: describe window_shift_ratio as a shift, not an overlap The C API example called the value the sliding-window overlap. It sets the shift, and a smaller shift is what produces more overlap, so say that directly and note the compute cost that comes with it. --- sherpa-onnx/c-api/docs/speaker-diarization.dox | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sherpa-onnx/c-api/docs/speaker-diarization.dox b/sherpa-onnx/c-api/docs/speaker-diarization.dox index 633b8d96ef..659dc12ff4 100644 --- a/sherpa-onnx/c-api/docs/speaker-diarization.dox +++ b/sherpa-onnx/c-api/docs/speaker-diarization.dox @@ -14,8 +14,9 @@ SherpaOnnxOfflineSpeakerDiarizationConfig config; memset(&config, 0, sizeof(config)); config.segmentation.pyannote.model = "./sherpa-onnx-pyannote-segmentation-3-0/model.onnx"; -// Zero uses the default window shift ratio of 0.1. Set a value in (0, 1] -// to change the sliding-window overlap. +// Zero uses the default window shift ratio of 0.1. Set a value in (0, 1] to +// change the sliding-window shift; smaller values mean more overlap and more +// compute. config.segmentation.pyannote.window_shift_ratio = 0.1f; config.embedding.model = "./3dspeaker_speech_eres2net_base_sv_zh-cn_3dspeaker_16k.onnx";