Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/scripts/test-rust.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ cd rust-api-examples

./run-version.sh

./run-silero-vad-remove-silence.sh

Copilot AI Feb 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds a network-dependent step (downloads model/audio via curl) to the primary Rust test script, which can make CI flaky and slower. Consider gating it behind an env flag (e.g., RUN_NETWORK_TESTS=1), adding retries/checksums, and/or caching the assets in CI to keep the test pipeline deterministic.

Suggested change
./run-silero-vad-remove-silence.sh
if [ "${RUN_NETWORK_TESTS:-0}" = "1" ]; then
./run-silero-vad-remove-silence.sh
fi

Copilot uses AI. Check for mistakes.

./run-nemo-parakeet-en.sh
./run-zipformer-vi.sh
./run-zipformer-zh-en.sh
Expand Down
10 changes: 5 additions & 5 deletions rust-api-examples/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions rust-api-examples/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
[package]
name = "rust-api-examples"
version = "0.1.6"
version = "0.1.7"
edition = "2021"

[dependencies]
anyhow = "1.0"
clap = { version = "4.5", features = ["derive"] }
sherpa-onnx = "0.1.6"
sherpa-onnx = "0.1.7"
# sherpa-onnx = { path = "../sherpa-onnx/rust/sherpa-onnx" }

cpal = { version = "0.16", optional = true } # cross-platform audio I/O
Expand Down
13 changes: 13 additions & 0 deletions rust-api-examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,19 @@ cargo run --example sense_voice -- \
--tokens ./sherpa-onnx-sense-voice-zh-en-ja-ko-yue-int8-2024-07-17/tokens.txt
```

### Example 5: Remove silences from a file using SileroVAD

```bash

curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/silero_vad.onnx
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/lei-jun-test.wav

cargo run --example silero_vad_remove_silence -- \
--input ./lei-jun-test.wav \
--output ./no-silence.wav \
--silero-vad-model ./silero_vad.onnx
```

# Alternative rust bindings for sherpa-onnx

Please see also https://github.com/thewh1teagle/sherpa-rs
109 changes: 109 additions & 0 deletions rust-api-examples/examples/silero_vad_remove_silence.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright (c) 2026 Xiaomi Corporation

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The copyright year is set to 2026. It's generally good practice to either use the current year or a range (e.g., 2023-2024) if the project is actively maintained. If 2026 is intentional for some future-proofing, it might be worth a small comment explaining why.

//
// This file demonstrates how to use silero VAD with sherpa-onnx's
// Rust API to remove non-speech segments and save speech-only audio.
//
// See ../README.md for how to run it

use clap::Parser;
use sherpa_onnx::{self, SileroVadModelConfig, VadModelConfig, VoiceActivityDetector, Wave};

/// Simple VAD example: remove non-speech segments from a WAV file
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// Path to input WAV file
#[arg(long)]
input: String,

/// Path to output WAV file
#[arg(long)]
output: String,

/// Path to Silero VAD ONNX model
#[arg(long)]
silero_vad_model: String,
}

fn main() -> anyhow::Result<()> {
let args = Args::parse();

// Read WAV file
let wave = Wave::read(&args.input)
.ok_or_else(|| anyhow::anyhow!("Failed to read WAV file: {}", &args.input))?;
let sample_rate = wave.sample_rate();
let input_num_samples = wave.num_samples();
let input_duration = input_num_samples as f32 / sample_rate as f32;

println!(
"Input WAV: sample rate: {}, num samples: {}, duration: {:.2}s",
sample_rate, input_num_samples, input_duration
);

// Configure VAD
let mut silero_config = SileroVadModelConfig::default();
silero_config.model = Some(args.silero_vad_model);

// You can tune the values below
silero_config.threshold = 0.5;
silero_config.min_silence_duration = 0.25;
silero_config.min_speech_duration = 0.25;
silero_config.max_speech_duration = 5.0;

let vad_config = VadModelConfig {
silero_vad: silero_config,
ten_vad: Default::default(),
sample_rate,
num_threads: 1,
provider: Some("cpu".to_string()),
debug: false,
};

let vad = VoiceActivityDetector::create(&vad_config, 30.0)
.expect("Failed to create VoiceActivityDetector");

let mut speech_samples = Vec::new();
const WINDOW_SIZE: usize = 512;

for chunk in wave.samples().chunks(WINDOW_SIZE) {
vad.accept_waveform(chunk);

while let Some(seg) = vad.front() {
speech_samples.extend_from_slice(seg.samples());
vad.pop();
}
}

vad.flush();
while let Some(seg) = vad.front() {
speech_samples.extend_from_slice(seg.samples());
vad.pop();
}

// Write speech-only samples to output WAV
let ok = sherpa_onnx::write(&args.output, &speech_samples, sample_rate);
if ok {
println!("Saved speech-only audio to {}", args.output);
} else {
println!("Failed to save speech-only audio to {}", args.output);
}
Comment on lines +84 to +89

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The sherpa_onnx::write function returns a boolean indicating success or failure. While the if ok block handles the success case, the else block only prints a failure message. It might be beneficial to return an anyhow::Result from main that propagates this error, or at least log the error more formally if this is a critical operation.

    let ok = sherpa_onnx::write(&args.output, &speech_samples, sample_rate);
    if !ok {
        anyhow::bail!("Failed to save speech-only audio to {}", args.output);
    }
    println!("Saved speech-only audio to {}", args.output);

Comment on lines +83 to +89

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n rust-api-examples/examples/silero_vad_remove_silence.rs | head -100

Repository: k2-fsa/sherpa-onnx

Length of output: 3855


🏁 Script executed:

cat -n rust-api-examples/examples/silero_vad_remove_silence.rs | tail -20

Repository: k2-fsa/sherpa-onnx

Length of output: 816


🏁 Script executed:

find . -name "Cargo.toml" -path "*/rust-api-examples/*" -o -name "Cargo.toml" -path "*" | head -5

Repository: k2-fsa/sherpa-onnx

Length of output: 210


🏁 Script executed:

cat rust-api-examples/Cargo.toml

Repository: k2-fsa/sherpa-onnx

Length of output: 586


Propagate write failures via non-zero exit.

The function returns anyhow::Result<()> but masks write failures by returning Ok(()) regardless of the write result. This allows the program to exit with code 0 even when writing fails, making it impossible for CI to detect the failure.

Suggested fix
-    let ok = sherpa_onnx::write(&args.output, &speech_samples, sample_rate);
-    if ok {
-        println!("Saved speech-only audio to {}", args.output);
-    } else {
-        println!("Failed to save speech-only audio to {}", args.output);
-    }
+    if !sherpa_onnx::write(&args.output, &speech_samples, sample_rate) {
+        anyhow::bail!("Failed to save speech-only audio to {}", args.output);
+    }
+    println!("Saved speech-only audio to {}", args.output);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Write speech-only samples to output WAV
let ok = sherpa_onnx::write(&args.output, &speech_samples, sample_rate);
if ok {
println!("Saved speech-only audio to {}", args.output);
} else {
println!("Failed to save speech-only audio to {}", args.output);
}
// Write speech-only samples to output WAV
if !sherpa_onnx::write(&args.output, &speech_samples, sample_rate) {
anyhow::bail!("Failed to save speech-only audio to {}", args.output);
}
println!("Saved speech-only audio to {}", args.output);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust-api-examples/examples/silero_vad_remove_silence.rs` around lines 83 -
89, The code currently ignores failures from sherpa_onnx::write by always
returning Ok(()) from main; change main (the function returning
anyhow::Result<()>) to propagate write failures: check the boolean result of
sherpa_onnx::write(&args.output, &speech_samples, sample_rate) and if false
return an Err (e.g., anyhow::anyhow! or anyhow::bail!) with a descriptive
message mentioning args.output so the process exits non-zero; keep or adjust the
println! messages as needed but ensure the false branch returns an error instead
of continuing to Ok(()).


// Summary
let output_num_samples = speech_samples.len();
let output_duration = output_num_samples as f32 / sample_rate as f32;
println!("\n=== Summary ===");
println!(
"Input: sample rate = {}, samples = {}, duration = {:.2}s",
sample_rate, input_num_samples, input_duration
);
println!(
"Output: sample rate = {}, samples = {}, duration = {:.2}s",
sample_rate, output_num_samples, output_duration
);
println!(
"Removed non-speech: {:.2}% of input removed",
100.0 * (1.0 - output_duration / input_duration)
);

Ok(())
}
16 changes: 16 additions & 0 deletions rust-api-examples/run-silero-vad-remove-silence.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -ex

# https://k2-fsa.github.io/sherpa/onnx/vad/silero-vad.html
if [ ! -f "./silero_vad.onnx" ]; then
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/silero_vad.onnx
fi

if [ ! -f ./lei-jun-test.wav ]; then
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/lei-jun-test.wav
fi
Comment on lines +5 to +11

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, find the script file
find . -name "*run-silero-vad-remove-silence.sh" -type f

Repository: k2-fsa/sherpa-onnx

Length of output: 114


🏁 Script executed:

# Check if the file exists and read its content
if [ -f "./rust-api-examples/run-silero-vad-remove-silence.sh" ]; then
  cat -n "./rust-api-examples/run-silero-vad-remove-silence.sh"
fi

Repository: k2-fsa/sherpa-onnx

Length of output: 696


🌐 Web query:

curl -SL -O HTTP 4xx 5xx error behavior exit code

💡 Result:

By default, curl -SL -O <url> does not treat HTTP 4xx/5xx as a failure. If the HTTP response is received successfully (even 404/500), curl typically exits 0 and (with -O) will save the server’s error page into the output file. (ec.haxx.se)

To make curl fail (non‑zero exit) on HTTP errors, add one of these:

  • Fail on HTTP ≥ 400 and discard the body (good for -O downloads):
    • curl -fSL -O <url>
    • -f/--fail makes HTTP errors return exit code 22 (“HTTP not found / requested page not found”), and this code only appears if --fail is used. (curl.se)
  • Fail on HTTP ≥ 400 but keep the body (useful for debugging API errors):
    • curl --fail-with-body -SL <url>
    • This also returns exit code 22 while still outputting/saving the response body. (ec.haxx.se)

Notes on your flags:

  • -L follows redirects; it doesn’t change 4xx/5xx exit behavior by itself. (ec.haxx.se)
  • -S (“show error”) is commonly paired with -s (silent) to still print errors; on its own it doesn’t make HTTP 4xx/5xx fail—you still need -f or --fail-with-body. (superuser.com)

Citations:


🌐 Web query:

curl -f flag fail on HTTP error exit code

💡 Result:

curl -f / --fail makes curl treat HTTP response codes ≥ 400 as an error, so the command exits non‑zero instead of 0. Specifically, it returns exit code 22 (CURLE_HTTP_RETURNED_ERROR). By default it also suppresses the response body for those HTTP errors (“fail silently”). [1][2]

Common patterns:

  • Fail the script on 4xx/5xx (no body on error):
    curl -f https://example.com
    echo $?
  • Show an error message while still being silent otherwise:
    curl -fsS https://example.com
  • If you want to fail but still keep the body on HTTP errors (curl ≥ 7.76):
    curl --fail-with-body https://example.com
    ``` [3]
    

Sources: [1] [2] [3]


Fail fast on download errors.
curl -SL -O doesn't fail on HTTP 4xx/5xx by default, so the script can proceed with corrupt or missing files. Add -f to make set -e effective.

🔧 Suggested fix
-  curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/silero_vad.onnx
+  curl -fSL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/silero_vad.onnx
...
-  curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/lei-jun-test.wav
+  curl -fSL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/lei-jun-test.wav
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if [ ! -f "./silero_vad.onnx" ]; then
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/silero_vad.onnx
fi
if [ ! -f ./lei-jun-test.wav ]; then
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/lei-jun-test.wav
fi
if [ ! -f "./silero_vad.onnx" ]; then
curl -fSL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/silero_vad.onnx
fi
if [ ! -f ./lei-jun-test.wav ]; then
curl -fSL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/lei-jun-test.wav
fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust-api-examples/run-silero-vad-remove-silence.sh` around lines 5 - 11, The
curl invocations that download "./silero_vad.onnx" and "./lei-jun-test.wav"
should use the --fail flag so the script fails on HTTP 4xx/5xx; update the two
lines containing "curl -SL -O https://.../silero_vad.onnx" and "curl -SL -O
https://.../lei-jun-test.wav" to include -f (e.g., "curl -fSL -O ..." or "--fail
-SL -O ...") so set -e will stop the script on download errors.


cargo run --example silero_vad_remove_silence -- \
--input ./lei-jun-test.wav \
--output ./no-silence.wav \
--silero-vad-model ./silero_vad.onnx
56 changes: 55 additions & 1 deletion sherpa-onnx/c-api/c-api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1128,7 +1128,7 @@ struct SherpaOnnxVoiceActivityDetector {
std::unique_ptr<sherpa_onnx::VoiceActivityDetector> impl;
};

sherpa_onnx::VadModelConfig GetVadModelConfig(
static sherpa_onnx::VadModelConfig GetVadModelConfig(
const SherpaOnnxVadModelConfig *config) {
sherpa_onnx::VadModelConfig vad_config;

Expand Down Expand Up @@ -1185,6 +1185,11 @@ sherpa_onnx::VadModelConfig GetVadModelConfig(

const SherpaOnnxVoiceActivityDetector *SherpaOnnxCreateVoiceActivityDetector(
const SherpaOnnxVadModelConfig *config, float buffer_size_in_seconds) {
if (!config) {
SHERPA_ONNX_LOGE("vad config is nullptr");
return nullptr;
}
Comment on lines +1188 to +1191

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

OHOS path skips the same null guard when mgr != nullptr.

The new check correctly prevents GetVadModelConfig(nullptr) from dereferencing a null pointer. However, SherpaOnnxCreateVoiceActivityDetectorOHOS (around Line 2879) calls GetVadModelConfig(config) directly without a prior null check on the mgr != nullptr branch, leaving a null-dereference gap there.

🛡️ Proposed fix for the OHOS path
 const SherpaOnnxVoiceActivityDetector *
 SherpaOnnxCreateVoiceActivityDetectorOHOS(
     const SherpaOnnxVadModelConfig *config, float buffer_size_in_seconds,
     NativeResourceManager *mgr) {
   if (mgr == nullptr) {
     return SherpaOnnxCreateVoiceActivityDetector(config,
                                                  buffer_size_in_seconds);
   }

+  if (!config) {
+    SHERPA_ONNX_LOGE("vad config is nullptr");
+    return nullptr;
+  }
+
   auto vad_config = GetVadModelConfig(config);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sherpa-onnx/c-api/c-api.cc` around lines 1188 - 1191,
SherpaOnnxCreateVoiceActivityDetectorOHOS currently calls
GetVadModelConfig(config) on the OHOS branch without ensuring the returned
config is non-null; update SherpaOnnxCreateVoiceActivityDetectorOHOS to check
the result of GetVadModelConfig(config) (and/or verify mgr and config before
calling) and handle a nullptr by logging an error and returning nullptr instead
of proceeding, so GetVadModelConfig(nullptr) cannot be dereferenced; reference
the GetVadModelConfig function and the SherpaOnnxCreateVoiceActivityDetectorOHOS
caller to locate where to add the null check and error handling.


auto vad_config = GetVadModelConfig(config);

if (!vad_config.Validate()) {
Expand All @@ -1206,31 +1211,70 @@ void SherpaOnnxDestroyVoiceActivityDetector(

void SherpaOnnxVoiceActivityDetectorAcceptWaveform(
const SherpaOnnxVoiceActivityDetector *p, const float *samples, int32_t n) {
if (!p) {
SHERPA_ONNX_LOGE("vad is nullptr");
return;
}

if (!samples) {
SHERPA_ONNX_LOGE("samples is nullptr");
return;
}

p->impl->AcceptWaveform(samples, n);
}

int32_t SherpaOnnxVoiceActivityDetectorEmpty(
const SherpaOnnxVoiceActivityDetector *p) {
if (!p) {
SHERPA_ONNX_LOGE("vad is nullptr");
return 1; // 1 means it is empty
}

return p->impl->Empty();
}

int32_t SherpaOnnxVoiceActivityDetectorDetected(
const SherpaOnnxVoiceActivityDetector *p) {
if (!p) {
SHERPA_ONNX_LOGE("vad is nullptr");
return 0;
}

return p->impl->IsSpeechDetected();
}

void SherpaOnnxVoiceActivityDetectorPop(
const SherpaOnnxVoiceActivityDetector *p) {
if (!p) {
SHERPA_ONNX_LOGE("vad is nullptr");
return;
}

p->impl->Pop();
}

void SherpaOnnxVoiceActivityDetectorClear(
const SherpaOnnxVoiceActivityDetector *p) {
if (!p) {
SHERPA_ONNX_LOGE("vad is nullptr");
return;
}

p->impl->Clear();
}

const SherpaOnnxSpeechSegment *SherpaOnnxVoiceActivityDetectorFront(
const SherpaOnnxVoiceActivityDetector *p) {
if (!p) {
SHERPA_ONNX_LOGE("vad is nullptr");
return nullptr;
}

if (SherpaOnnxVoiceActivityDetectorEmpty(p)) {
return nullptr;
}
Comment on lines +1274 to +1276

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Adding a null check for SherpaOnnxVoiceActivityDetectorEmpty(p) before calling p->impl->Front() is a good defensive programming practice. This prevents potential crashes if Front() is called on an empty detector, improving the robustness of the C API.


const sherpa_onnx::SpeechSegment &segment = p->impl->Front();

SherpaOnnxSpeechSegment *ans = new SherpaOnnxSpeechSegment;
Expand All @@ -1251,11 +1295,21 @@ void SherpaOnnxDestroySpeechSegment(const SherpaOnnxSpeechSegment *p) {

void SherpaOnnxVoiceActivityDetectorReset(
const SherpaOnnxVoiceActivityDetector *p) {
if (!p) {
SHERPA_ONNX_LOGE("vad is nullptr");
return;
}

p->impl->Reset();
}

void SherpaOnnxVoiceActivityDetectorFlush(
const SherpaOnnxVoiceActivityDetector *p) {
if (!p) {
SHERPA_ONNX_LOGE("vad is nullptr");
return;
}

p->impl->Flush();
}

Expand Down
13 changes: 12 additions & 1 deletion sherpa-onnx/csrc/voice-activity-detector.cc
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,18 @@ class VoiceActivityDetector::Impl {

void Clear() { std::queue<SpeechSegment>().swap(segments_); }

const SpeechSegment &Front() const { return segments_.front(); }
const SpeechSegment &Front() const {
static SpeechSegment tmp;

if (Empty()) {
SHERPA_ONNX_LOGE(
"Make sure you call this method only when Empty() returns false; "
"Return an empty segment");
return tmp;
}

return segments_.front();
}

void Reset() {
std::queue<SpeechSegment>().swap(segments_);
Expand Down
2 changes: 1 addition & 1 deletion sherpa-onnx/rust/sherpa-onnx-sys/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sherpa-onnx-sys"
version = "0.1.6"
version = "0.1.7"
edition = "2021"
description = "Raw FFI bindings to the sherpa-onnx C API"
license = "Apache-2.0"
Expand Down
2 changes: 2 additions & 0 deletions sherpa-onnx/rust/sherpa-onnx-sys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ extern "C" {

pub mod offline_asr;
pub mod online_asr;
pub mod vad;
pub mod wave;

pub use offline_asr::*;
pub use online_asr::*;
pub use vad::*;
pub use wave::*;
Loading
Loading